ruby (w/rails) group_by, then group_by again - ruby-on-rails

Is there a better way to do the following?
prices = Price...all.group_by(&:foreign_key_id)
#prices = Hash.new
prices.each {|k, v| #prices[k] = Hash.new if !#prices[k]; #prices[k] = v.group_by {|g| g.created_at.to_time.to_i } }
I'd like to do something like
prices.each {|k,v| v = v.group_by(&:created_at) }
But this doesn't seem to work because v is an array and group_by produces a hash. Perhaps there's a way to do this with inject?

prices.inject(Hash.new{ |h, k| h[k] = {} }) { |h, (k, v)|
h[k] = v.group_by(&:created_at)
h
}
Added: Less function way:
prices.keys.each { |k|
prices[k] = prices[k].group_by(&:created_at)
}

This should work:
prices = Price...all.inject({}) do |hash, el|
(hash[el.foreign_key_id] ||={})[el.created_at] << el
hash
end
but I doubt that you will have two records with the same created_at value.
You may need set format for created_at.
Something like this:
prices = Price...all.inject({}) do |hash, el|
(hash[el.foreign_key_id] ||={})[el.created_at.to_s(:db)] << el
hash
end

may be
prices.each {|k,v| prices[k] = v.group_by(&:created_at) }
or
new_prices = {}
prices.each {|k,v| new_prices[k] = v.group_by(&:created_at) }
Hash doesn't support inject

I understand the second group_by, but the first one I think it will be a Model logic, so, depend on your type of relation you can use in your model the options: :uniq => true or :group => 'foreign_key_id' . You can read it here http://guides.rubyonrails.org/association_basics.html

Related

how to combine 2 arrays in rails such that there are no duplicates

Consider I have 2 arrays,
o = ["16", "16", "119"]
d = ["97", "119", "97"]
Output that is needed is like this:
{16=>[97, 119], 119=>[97]}
I tried using .zip but it didn't work. How do I do it?
You could chain group_by and with_index to group the elements in d by the corresponding element in o:
d.group_by.with_index { |_, i| o[i] }
#=> {"16"=>["97", "119"], "119"=>["97"]}
To get integers, you have to add some to_i calls:
d.map(&:to_i).group_by.with_index { |_, i| o[i].to_i }
#=> {16=>[97, 119], 119=>[97]}
First thing that comes to mind is this:
result = Hash.new { |h, k| h[k] = [] }
o.zip(d) { |a, b| result[a] << b }
result #=> {"16"=>["97", "119"], "119"=>["97"]}
There probably is a better way though, but this should get you thinking.
o.map(&:to_i).zip(d.map(&:to_i)).group_by(&:first).each_value{|a| a.map!(&:last)}
# => {16=>[97, 119], 119=>[97]}

Ruby - how to add an array to a hash key?

I have this loop:
car_data = Hash.new
Car.all.each do |c|
car_data[c.brand] = c.id
car_data['NEW'] << c.id if c.new == 1
end
I have this snipper and trying to save all the new cars to car_data['NEW'], but this code keeps only one item in the hash (there should be 8).
I also tried to define that car_data['NEW'] as an array:
car_data = Hash.new
car_data['NEW'] = Hash.new
Car.all.each do |c|
car_data[c.brand] = c.id
car_data['NEW'] << c.id if c.new == 1
end
But the result was the same - just one item.
How do I save the whole array to the hash key element?
Thank you.
car_data['NEW'] has to be declared as Array.
car_data = Hash.new
car_data['NEW'] = []
Car.all.each do |c|
car_data[c.brand] = c.id
car_data['NEW'] << c.id if c.new == 1
end
You can also do it in a single step
car_data = { new: [] }
Car.all.each do |c|
car_data[c.brand] = c.id
car_data[:new] << c.id if c.new == 1
end
Frankly, it seems a little bit odd to me to use a Hash in that way. In particular, mixin different kind of information in Hash is a very bad approach inherited from other non object oriented languages.
I'd use at least two separate variables. But I don't know enough about the context to provide a meaningful example.
You wrote, that you tried to define (initialize) car_data['NEW'] as an array, but what you did is... initialized it as a hash.
Change:
car_data['NEW'] = Hash.new
To:
car_data['NEW'] = []
The full code would look like:
car_data = Hash.new
car_data['NEW'] = []
Car.all.each do |c|
car_data[c.brand] = c.id
car_data['NEW'] << c.id if c.new == 1
end
car_data = Car.all.each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, memo|
memo[c.brand] = c.id
memo['NEW'] << c.id if c.new == 1
end
or, simpler, let’s create it on the fly if needed:
car_data = Car.all.each_with_object({}) do |c, memo|
memo[c.brand] = c.id
(memo['NEW'] ||= []) << c.id if c.new == 1
end
Please also refer to comment by #tadman below, if the NEW key is to be existing in any case.

rails 4 data iteration from activerecord search

I am trying to iterate over a hash returned from an active record search.
the data comes back {[k, v] => v, [k,v] => v, etc.} and I need to place that data in 3 separate columns of a table.
what I have so far in a helper is
data = {}
connector = 0
us_cords = 0
eu_cords = 0
molex_connector = 0
chart_data_two.each do |key, value|
data[key[0]] ||= Hash.new
data[key[0]][key[1]] = value
end
return data
This gives me the k out of the k,v pair above and then a hash with "v" => v from above.
so I am having a hard time wrapping my head around iterating over the data hash and putting it into the view table in each column.
The view has #table_data_two = chart_qty_monthly_data(#chart_data_two) and then #table_data_two.each do |k,v| for generating each row/column.
Eventually it might be nice to do only one merged cell for month with 4 cells for model and then the quantities
Here's one way to build that:
data = Hash.new { |h, k| h[k] = {} }
chart_data_two.each do |key, value|
model_quantity = { key[0] => value }
data[key[1]].merge!(model_quantity)
end
If you want it to be in order by month, you can change the keys to integers and sort it:
data = Hash.new { |h, k| h[k] = {} }
chart_data_two.each do |key, value|
model_quantity = { key[0] => value }
data[key[1].to_i].merge!(model_quantity)
end
sorted_data = data.sort.to_h
It's not perfect, so I'll look it over again and see what could be improved, but it will at least get you started, assuming I understood your question correctly.
ended up doing it a bit different:
data = {}
#initialize hash keys in the order you want them to end up in
(1..12).each do |month|
data[month] = {"Connector" => 0, "US Cords" => 0, "EU Cords" => 0}
end
chart_data_two.each do |key, value|
#key[1] is month. we're referencing the keys we initialized above
#key[2] is model. we're adding a new key to the nested hash we initialized above
#logger.info "#{key[0]}, #{key[1]}, #{value}"
data[key[1].to_i][key[0]] = value
#logger.info "%%%%%%%% inside iterator #{data}"
end
#logger.info "$$$$$ passing #{data} into chart"
return data
end
I got help with figuring that out, but I wanted to post it here.

How do I convert an array of hashes into a sorted hash?

If I have an array of hashes, each with a day key:
[
{:day=>4,:name=>'Jay'},
{:day=>1,:name=>'Ben'},
{:day=>4,:name=>'Jill'}
]
What is the best way to convert it to a hash with sorted day values as the keys:
{
:1=>[{:day=>1,:name=>'Ben'}],
:4=>[{:day=>4,:name=>'Jay'},{:day=>4,:name=>'Jill'}]
}
I'm using Ruby 1.9.2 and Rails 3.1.1
Personally, I wouldn't bother "sorting" the keys (which amounts to ordering-by-entry-time in Ruby 1.9) until I actually needed to. Then you can use group_by:
arr = [{:day=>4,:name=>'Jay'}, {:day=>1,:name=>'Ben'}, {:day=>4,:name=>'Jill'}]
arr.group_by { |a| a[:day] }
=> {4=>[{:day=>4, :name=>"Jay"}, {:day=>4, :name=>"Jill"}],
1=>[{:day=>1, :name=>"Ben"}]}
Instead, sort the keys when you actually need them.
Assuming you array is called is list, here's one way using the reduce method:
list.reduce({}) { |hash, item|
(hash[item[:day]] ||= []) << item; hash
}
Here's another using the map method, but you have to carry a holder variable around:
hash = {}
list.each { |item|
(hash[item[:day]] ||= []) << item
}
Once you have the unsorted hash say in variable foo, you can sort it as,
Hash[foo.sort]
Simple answer:
data = [
{:day=>4,:name=>'Jay'},
{:day=>1,:name=>'Ben'},
{:day=>4,:name=>'Jill'}
]
#expected solution
sol = {
1=>[{:day=>1,:name=>'Ben'}],
4=>[{:day=>4,:name=>'Jay'},{:day=>4,:name=>'Jill'}]
}
res = {}
data.each{|h|
res[h[:day]] ||= []
res[h[:day]] << h
}
p res
p res == sol #check value
p res.keys == sol.keys #check order
Problem with this solution: The hash is not sorted as requested. (Same problem has Anurags solution).
So you must modify the answer a bit:
res = {}
data.sort_by{|h| h[:day]}.each{|h|
res[h[:day]] ||= []
res[h[:day]] << h
}
p res
p res == sol #check value
p res.keys == sol.keys #check order
In Rails you can use OrderedHash:
ActiveSupport::OrderedHash[arr.group_by { |a| a[:day] }.sort_by(&:first)]
Update: In fact in Ruby 1.9 hash is ordered, so using ActiveSupport extension is not required:
Hash[arr.group_by { |a| a[:day] }.sort_by(&:first)]

Creating a new hash with default keys

I want to create a hash with an index that comes from an array.
ary = ["a", "b", "c"]
h = Hash.new(ary.each{|a| h[a] = 0})
My goal is to start with a hash like this:
h = {"a"=>0, "b"=>0, "c"=>0}
so that later when the hash has changed I can reset it with h.default
Unfortunately the way I'm setting up the hash is not working... any ideas?
You should instantiate your hash h first, and then fill it with the contents of the array:
h = {}
ary = ["a", "b", "c"]
ary.each{|a| h[a] = 0}
Use the default value feature for the hash
h = Hash.new(0)
h["a"] # => 0
In this approach, the key is not set.
h.key?("a") # => false
Other approach is to set the missing key when accessed.
h = Hash.new {|h, k| h[k] = 0}
h["a"] # => 0
h.key?("a") # => true
Even in this approach, the operations like key? will fail if you haven't accessed the key before.
h.key?("b") # => false
h["b"] # => 0
h.key?("b") # => true
You can always resort to brute force, which has the least boundary conditions.
h = Hash.new.tap {|h| ["a", "b", "c"].each{|k| h[k] = 0}}
h.key?("b") # => true
h["b"] # => 0
You can do it like this where you expand a list into zero-initialized values:
list = %w[ a b c ]
hash = Hash[list.collect { |i| [ i, 0 ] }]
You can also make a Hash that simply has a default value of 0 for any given key:
hash = Hash.new { |h, k| h[k] = 0 }
Any new key referenced will be pre-initialized to the default value and this will avoid having to initialize the whole hash.
This may not be the most efficient way, but I always appreciate one-liners that reveal a little more about Ruby's versatility:
h = Hash[['a', 'b', 'c'].collect { |v| [v, 0] }]
Or another one-liner that does the same thing:
h = ['a', 'b', 'c'].inject({}) {|h, v| h[v] = 0; h }
By the way, from a performance standpoint, the one-liners run about 80% of the speed of:
h = {}
ary = ['a','b','c']
ary.each { |a| h[a]=0 }
Rails 6 added index_with on Enumerable module. This will help in creating a hash from an enumerator with default or fetched values.
ary = %w[a b c]
hash = ary.index_with(0) # => {"a"=>0, "b"=>0, "c"=>0}
Another option is to use the Enum#inject method which I'm a fan of for its cleanliness. I haven't benchmarked it compared to the other options though.
h = ary.inject({}) {|hash, key| hash[key] = 0; hash}
Alternate way of having a hash with the keys actually added
Hash[[:a, :b, :c].zip([])] # => {:a=>nil, :b=>nil, :c=>nil}
Hash[[:a, :b, :c].zip(Array.new(3, 0))] # => {:a=>0, :b=>0, :c=>0}

Resources