I've got this:
a = [[123,1],[124,1],[125,1],[126,2],[127,3],[128,3]]
And I would like to turn a into b:
ordered by value
random within array of value
// updated:
b = [[124,123,125],[126],[128,127]]
How to do this in ruby? Im using rails.
a.group_by(&:last).
sort_by(&:first).
map(&:last).
map {|el| el.map(&:first).shuffle }
One solution is:
a = [[123,1],[124,1],[125,1],[126,2],[127,3],[128,3]]
a = a.sort {|d, e| d[1] <=> e[1]}
prev = a[0][1]; result = []; group = [];
a.each do |e|
if e[1] == prev
group << e[0]
else
result << group.shuffle
group = [e[0]]
prev = e[1]
end
end
result << group
p result
run:
$ ruby t.rb
[[125, 123, 124], [126], [127, 128]]
a.reduce([]){|m,i|m[i[1]]=(m[i[1]]||[])<<i[0];m}.compact
Related
I am working on this exercise in pil4.
Exercise 15.5:
The approach of avoiding constructors when saving tables with cycles is too radical. It is
possible to save the table in a more pleasant format using constructors for the simple case, and to use
assignments later only to fix sharing and loops. Reimplement the function save (Figure 15.3, “Saving
tables with cycles”) using this approach. Add to it all the goodies that you have implemented in the previous
exercises (indentation, record syntax, and list syntax).
I have tried this with the code below, but it seems not to work on the nested table with a string key.
local function basicSerialize(o)
-- number or string
return string.format("%q",o)
end
local function save(name,value,saved,indentation,isArray)
indentation = indentation or 0
saved = saved or {}
local t = type(value)
local space = string.rep(" ",indentation + 2)
local space2 = string.rep(" ",indentation + 4)
if not isArray then io.write(name," = ") end
if t == "number" or t == "string" or t == "boolean" or t == "nil" then
io.write(basicSerialize(value),"\n")
elseif t == "table" then
if saved[value] then
io.write(saved[value],"\n")
else
if #value > 0 then
if indentation > 0 then io.write(space) end
io.write("{\n")
end
local indexes = {}
for i = 1,#value do
if type(value[i]) ~= "table" then
io.write(space2)
io.write(basicSerialize(value[i]))
else
local fname = string.format("%s[%s]",name,i)
save(fname,value[i],saved,indentation + 2,true)
end
io.write(",\n")
indexes[i] = true
end
if #value > 0 then
if indentation > 0 then io.write(space) end
io.write("}\n")
else
io.write("{}\n")
end
saved[value] = name
for k,v in pairs(value) do
if not indexes[k] then
k = basicSerialize(k)
local fname = string.format("%s[%s]",name,k)
save(fname,v,saved,indentation + 2)
io.write("\n")
end
end
end
else
error("cannot save a " .. t)
end
end
local a = { 1,2,3, {"one","Two"} ,5, {4,b = 4,5,6} ,a = "ddd"}
local b = { k = a[4]}
local t = {}
save("a",a,t)
save("b",b,t)
print()
And I got the wrong ouput.
a = {
1,
2,
3,
{
"one",
"Two",
}
,
5,
{
4,
5,
6,
}
a[6]["b"] = 4
,
}
a["a"] = "ddd"
b = {}
b["k"] = a[4]
How could I make the text ' a[6]["b"] = 4 ' jump out of the table constructor?
I have this hash
obj= {"User"=>["user_error", "Jack", "Jill1"], "Project"=>[ "project_error", "xxx"], "Task"=>[39], "Date"=>"date_error", "Time (Hours)"=>["time_error", "-2"], "Comment"=>"comment_error"}
I have to extract the error values of the keys and store them else where
.The end result should be
error = ["user_error", "project_error","date_error","time_error","comment_error"]
obj = {"User"=>["Jack", "Jill1"], "Project"=>[ "xxx"], "Task"=>[39], "Date"=>nil, "Time (Hours)"=>["-2"], "Comment"=>nil}
could some one help how to do this?
Not too pretty, but you can do something like this:
errors = obj.each_with_object([]) do |(k, v), err|
if v.is_a?(Array) && v.first =~ /_error$/
err << v.shift
elsif v =~ /_error$/
err << v
obj[k] = nil
end
end
Results:
errors
#=> ["user_error", "project_error", "date_error", "time_error", "comment_error"]
obj
#=> {"User"=>["Jack", "Jill1"], "Project"=>["xxx"], "Task"=>[39], "Date"=>nil, "Time (Hours)"=>["-2"], "Comment"=>nil}
You could DRY the code a bit by transforming all values to arrays first, but you will get empty arrays instead of nil for Date and Comment keys:
errors = obj.each_with_object([]) do |(k, v), err|
obj[k] = v = [v].flatten
err << v.shift if v.first =~ /_error$/
end
errors
#=> ["user_error", "project_error", "date_error", "time_error", "comment_error"]
obj
#=> {"User"=>["Jack", "Jill1"], "Project"=>["xxx"], "Task"=>[39], "Date"=>[], "Time (Hours)"=>["-2"], "Comment"=>[]}
You can do the following:
errors = []
obj.map do |class_name, strings|
errors.push(strings.shift) # shift remove the first element of the array
obj[class_name] = strings
end
Let's say i have two relation arrays of a user's daily buy and sell.
how do i iterate through both of them using .each and still let the the longer array run independently once the shorter one is exhaused. Below i want to find the ratio of someone's daily buys and sells. But can't get the ratio because it's always 1 as i'm iterating through the longer array once for each item of the shorter array.
users = User.all
ratios = Hash.new
users.each do |user|
if user.buys.count > 0 && user.sells.count > 0
ratios[user.name] = Hash.new
buy_array = []
sell_array = []
date = ""
daily_buy = user.buys.group_by(&:created_at)
daily_sell = user.sells.group_by(&:created_at)
daily_buy.each do |buy|
daily_sell.each do |sell|
if buy[0].to_date == sell[0].to_date
date = buy[0].to_date
buy_array << buy[1]
sell_array << sell[1]
end
end
end
ratio_hash[user.name][date] = (buy_array.length.round(2)/sell_array.length)
end
end
Thanks!
You could concat both arrays and get rid of duplicated elements by doing:
(a_array + b_array).uniq.each do |num|
# code goes here
end
Uniq method API
daily_buy = user.buys.group_by(&:created_at)
daily_sell = user.sells.group_by(&:created_at
buys_and_sells = daily_buy + daily_sell
totals = buys_and_sells.inject({}) do |hsh, transaction|
hsh['buys'] ||= 0;
hsh['sells'] ||= 0;
hsh['buys'] += 1 if transaction.is_a?(Buy)
hsh['sells'] += 1 if transaction.is_a?(Sell)
hsh
end
hsh['buys']/hsh['sells']
I think the above might do it...rather than collecting each thing in to separate arrays, concat them together, then run through each item in the combined array, increasing the count in the appropriate key of the hash returned by the inject.
In this case you can't loop them with each use for loop
this code will give you a hint
ar = [1,2,3,4,5]
br = [1,2,3]
array_l = (ar.length > br.length) ? ar.length : br.length
for i in 0..array_l
if ar[i] and br[i]
puts ar[i].to_s + " " + br[i].to_s
elsif ar[i]
puts ar[i].to_s
elsif br[i]
puts br[i].to_s
end
end
Need to double each value in my array. I know double is not a command, but not sure what else to use.
odds = [1,3,5,7,9]
array.each do |x|
x += double
print "#{x}"
end
Use Array#map to create a new array
odds = [1,3,5,7,9]
arr = odds.map{|x| x*2}
arr.inspect
# => [2,6,10,14,18]
To modify the same array use Array#map!
odds = [1,3,5,7,9]
odds.map!{|x| x*2}
odds.inspect
# => [2,6,10,14,18]
Do you mean 'double' as in multiply by 2, or double as in duplicate?
array = [1,3,5,7,9]
array.each { |x| print "#{x*2}"; }
But you probably want either a new array, or to map your existing array,
result = []
result = array.map { |x| x*2 }
#or
result = array.map! { |x| x*2 }
Here is an example of duplicate,
result = []
array.map { |x| result << x; result << x; } #duplicate
see here: http://www.natontesting.com/2011/01/01/rubys-each-select-and-reject-methods/
Doing the Simplest Thing Possible
The simplest thing to do is to simply iterate over your array with Array#map and Kernel#p. For example:
odds = [1, 3, 5, 7, 9]
odds.map { |i| p i*2 }
Defining a Custom Method
If you need more control, you can create a custom method that handles a block or returns an enumerator. For example:
def double *array
array.flatten!
block_given? ? array.map { |i| yield i*2 } : array.map { |i| i*2 }.to_enum
end
enumerator = double 1, 2, 3
#=> #<Enumerator: ...>
enumerator.each { |i| p i }
#=> [2, 4, 6]
double(1, 2, 3) { |i| p i }
#=> [2, 4, 6]
This is probably overkill for your use case, but it's a useful technique to know if you want to work with enumerators and blocks. Hope it helps!
The way that I have found to do it is to times / equal it by 2. For example:
odds = [1,3,5,7,9]
odds.each do |x|
x *= 2
print x
end
I have such code:
def accum_search
if params[:akbcap].present?
akbcap_array = [12,18,19,20,25,30,35,36,38,40,41,42,44,45,46,47,50,52,53,54,55,56,58,60,61,62,63,64,65,66,68,69,70,71,72,74,75,77,80,85,88,90,91,92,95,98,100,102,110,115,120,125,130,135,140,170,180,185,190,192,200,210,220,225]
min, max = params[:akbcap].split('-').map {|s| s.to_i }
logger.warn("!#!!!!!!!!!!!! AAA !!!!!!!!!!")
logger.warn(min)
logger.warn(max)
caprange = min...max
sa = akbcap_array.select {|n| caprange.include? n }
##cross = OtherProductsCrossList.find(:all, :conditions => {:cross_value => 1})
cap = "*"+params[:akbcap]+"*"
sa.each do |s|
logger.warn(s)
#accums = Accumulator.by_capacity(s).by_size(params[:akbsize]).by_brand(params[:akbbrand])
end
else
#accums = Accumulator.by_capacity(50).by_size(params[:akbsize]).by_brand(params[:akbbrand])
end
end
As you see i have such part:
sa.each do |s|
logger.warn(s)
#accums = Accumulator.by_capacity(s).by_size(params[:akbsize]).by_brand(params[:akbbrand])
end
but could i add on every iteration in #accums data from search? now it has last value( I could done it via arrays... but how to do via class-variable?
Yes, initiate it before the loop and use the << operator to append. End with flatten to make it a single dimension array.
#accums = []
# ...
sa.each do |s|
#accums << Accumulator.several_method_calls......
end
#accums.flatten!
or for compactness:
result = sa.map{|s| Accumulator.several_method_calls...... }.flatten