I got an array of hashes like this one:
[{1=>6}, {1=>5}, {4=>1}]
I try to group by the keys.
So the solution with named keys was like: group_by { |h| h['keyName'] }
group_by { |h| h['keyName'] }
.
How can I get the following array with short Lambda expressions or with group_by:
[{1=>[5, 6], 4=>[1]}]
EDIT - To explain what I am trying to achieve: I got a database to allocate pupils to courses. Each pupil is able to vote each year for a course.
The votes look like this:
Vote(id: integer, first: integer, second: integer, active: boolean,
student_id: integer, course_id: integer, year_id: integer,
created_at: datetime, updated_at: datetime)
Now I would like to allocate the pupils automatically to a course, if the course is not overstaffed . To find out how many pupils voted for each course I first tried this:
Year.get_active_year.votes.order(:first).map(&:first).group_by(&:itself)
the result looks like this:
{1=>[1, 1], 4=>[4]}
Now I am able to use the .each function:
Year.get_active_year.votes.order(:first).map(&:first).group_by(&:itself).each do |_key, value|
if Year.get_active_year.courses.where(number: _key).first.max_visitor >= value.count
end
end
each course got an explicit number and the pupils just use the course number to vote.
But if I do all this, I lose the information which pupil voted for that course, so I tried to keep the information like this:
Year.get_active_year.votes.order(:first).map{|c| {c.first=> c.student_id}}
Injecting into a default hash:
arr = [{1=>6}, {1=>5}, {4=>1}]
arr.inject(Hash.new{|h,k| h[k]=[]}){|h, e| h[e.first[0]] << e.first[1]; h}
# => {1=>[6, 5], 4=>[1]}
Or, as suggested in the comments:
arr.each.with_object(Hash.new{|h, k| h[k] = []}){|e, h| h[e.first[0]] << e.first[1]}
# => {1=>[6, 5], 4=>[1]}
def group_values(arr)
arr.reduce(Hash.new {|h,k| h[k]=[]}) do |memo, h|
h.each { |k, v| memo[k] << v }
memo
end
end
xs = [{1=>6}, {1=>5}, {4=>1}]
group_values(xs) # => {1=>[6, 5], 4=>[1]}
Note that this solution also works when the hashes contain multiple entries:
ys = [{1=>6, 4=>2}, {1=>5}, {4=>1}]
group_values(ys) # => {1=>[6, 5], 4=>[2, 1]}
arr = [{1=>6}, {1=>5}, {4=>1}]
arr.flat_map(&:to_a).
group_by(&:first).
transform_values { |arr| arr.transpose.last }
#=> {1=>[6, 5], 4=>[1]}
The steps are as follows.
a = arr.flat_map(&:to_a)
#=> [[1, 6], [1, 5], [4, 1]]
b = a.group_by(&:first)
#=> {1=>[[1, 6], [1, 5]], 4=>[[4, 1]]}
b.transform_values { |arr| arr.transpose.last }
#=> {1=>[6, 5], 4=>[1]}
Note that
b.transform_values { |arr| arr.transpose }
#=> {1=>[[1, 1], [6, 5]], 4=>[[4], [1]]}
and arr.flat_map(&:to_a)
can be replaced with arr.map(&:flatten)
.
Another way:
arr.each_with_object({}) do |g,h|
k,v = g.flatten
h.update(k=>[v]) { |_,o,n| o+n }
end
#=> {1=>[6, 5], 4=>[1]}
This uses the form of Hash#update (aka merge!
) that employs the block { |_,o,n| o+n }
{ |_,o,n| o+n }
to determine the values of keys that are present in both hashes being merged. The block variable _
is the common key (represented by an underscore to signal that it is not used in the block calculations). The variables o
and n
are respectively the values of the common key in the two hashes being merged.
One way to achieve this using #group_by
could be to group by the first key of each hash, then #map
over the result to return the corresponding values:
arr = [{1=>6}, {1=>5}, {4=>1}]
arr.group_by {|h| h.keys.first}.map {|k, v| {k => v.map {|h| h.values.first}}}
# => [{1=>[6, 5], 4=>[1]}]
Hope this helps!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.