Ruby group hashes by value of key
I have an array, which is output by a map/reduce method performed by MongoDB, it looks something like this:
[{"minute"=>30.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>0.0, "count"=>299.0},
{"minute"=>30.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>10.0, "count"=>244.0},
{"minute"=>30.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>1.0, "count"=>204.0},
{"minute"=>45.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>0.0, "count"=>510.0},
{"minute"=>45.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>10.0, "count"=>437.0},
{"minute"=>0.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011开发者_运维百科.0, "type"=>0.0, "count"=>469.0},
{"minute"=>0.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>10.0, "count"=>477.0},
{"minute"=>15.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>0.0, "count"=>481.0},
{"minute"=>15.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>10.0, "count"=>401.0},
{"minute"=>30.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>0.0, "count"=>468.0},
{"minute"=>30.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>10.0, "count"=>448.0},
{"minute"=>45.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>0.0, "count"=>485.0},
{"minute"=>45.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "type"=>10.0, "count"=>518.0}]
You'll notice that there are three distinct values for type
, in this case 0
, 1
, and 2
, now want to do is group this array of hashes by the value its type
key, so for example this array would end out looking like:
{
:type_0 => [
{"minute"=>30.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>299.0},
{"minute"=>45.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>510.0},
{"minute"=>0.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>469.0},
{"minute"=>15.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>481.0},
{"minute"=>30.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>468.0},
{"minute"=>45.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>485.0}
],
:type_1 => [
{"minute"=>30.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>204.0}
],
:type_10 => [
{"minute"=>30.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>244.0},
{"minute"=>45.0, "hour"=>15.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>437.0},
{"minute"=>0.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>477.0},
{"minute"=>15.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>401.0},
{"minute"=>30.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>448.0},
{"minute"=>45.0, "hour"=>16.0, "date"=>5.0, "month"=>9.0, "year"=>2011.0, "count"=>518.0}
]
}
so I know these example arrays are really large, but I think it may be a more simple problem than I'm making it out to be
So basically each array of hashes would be grouped by the value of its type
key, and then returned as a hash with an array for each type, any help at all would be really really helpful, even just some helpful hints would be greatly appreciated.
array.group_by {|x| x['type']}
or if you want the symbol key things you could even
array.group_by {|x| "type_#{x['type']}".to_sym}
I think this best expresses "So basically each array of hashes would be grouped by the value of its type key, and then returned as a hash with an array for each type", even if it leaves the :type
key alone in the output hashes.
Something like this perhaps?
mangled = a.group_by { |h| h['type'].to_i }.each_with_object({ }) do |(k,v), memo|
tk = ('type_' + k.to_s).to_sym
memo[tk] = v.map { |h| h = h.dup; h.delete('type'); h }
end
Or if you don't care about preserving the original data:
mangled = a.group_by { |h| h['type'].to_i }.each_with_object({ }) do |(k,v), memo|
tk = ('type_' + k.to_s).to_sym
memo[tk] = v.map { |h| h.delete('type'); h } # Drop the h.dup in here
end
by_type = {}
a.each do |h|
type = h.delete("type").to_s
# type = ("type_" + type ).to_sym
by_type[ type ] ||= []
by_type[ type ] << h # note: h is modified, without "type" key
end
Note: slightly different hash keys here, i used the type values directly as the key
if you have to have the hash-keys as in your example, you can add the line that is commented out.
P.S.: I just saw Tapio's solution -- it is very nice and short! Note that it only works with Ruby >= 1.9
group_by
collects an enumerable into sets, grouped by the result of a block. You are not constrained to simply get the key's value in this block, so if you would like to omit the 'type'
in those sets you can do it, like in:
array.group_by {|x| "type_#{x.delete('type').to_i}".to_sym}
This will result exactly into what you asked.
Advanced: This goes a little out of scope of the question, but if you want to preserve the original array, you must duplicate every object inside it. This will do the trick:
array.map(&:dup).group_by {|x| "type_#{x.delete('type').to_i}".to_sym}
精彩评论