[英]Grouping an array of hashes
so I'm working on a project where I have an array of hashes: 所以我正在一个有很多哈希的项目上工作:
[{:year=>2016, :month=>12, :account_id=>133, :price=>5},
{:year=>2016, :month=>11, :account_id=>134, :price=>3},
{:year=>2016, :month=>11, :account_id=>135, :price=>0},
{:year=>2015, :month=>12, :account_id=>145, :price=>4},
{:year=>2015, :month=>12, :account_id=>163, :price=>11}]
and basically I want to condense this down into the form: 基本上我想将其浓缩为以下形式:
{ 2016 => { 12 => { 1 => {:account_id=>133, :price=>5}},
11 => { 1 => {:account_id=>134, :price=>3},
2 => {:account_id=>135, :price=>0}}},
2015 => { 12 => { 1 => {:account_id=>145, :price=>4},
2 => {:account_id=>163, :price=>11}}}}
but I'm having real trouble getting this done, at the moment I have: 但目前我遇到了麻烦,目前我有:
data_array = data_array.group_by{|x| x[:year]}
data_array.each{|x| x.group_by{|y| y[:month]}}
but this doesn't seem to work, I get an error saying no implicit conversion of Symbol into Integer. 但这似乎不起作用,我收到一条错误消息,指出没有将Symbol隐式转换为Integer。
Any help with understanding where I've gone wrong and what to do would be greatly appreciated. 我们将不胜感激,帮助您了解我在哪里出错了以及该怎么做。
Here's a longer but possibly better solution, with 3 helper methods : 这是一个更长但可能更好的解决方案,其中包含3种辅助方法:
class Array
# Remove key from array of hashes
def remove_key(key)
map do |h|
h.delete(key)
h
end
end
# Group hashes by values for given key, sort by value,
# remove key from hashes, apply optional block to array of hashes.
def to_grouped_hash(key)
by_key = group_by { |h| h[key] }.sort_by { |value, _| value }
by_key.map do |value, hashes|
hashes_without = hashes.remove_key(key)
new_hashes = block_given? ? yield(hashes_without) : hashes_without
[value, new_hashes]
end.to_h
end
# Convert array to indexed hash
def to_indexed_hash(first = 0)
map.with_index(first) { |v, i| [i, v] }.to_h
end
end
Your script can then be written as : 您的脚本可以这样写:
data.to_grouped_hash(:year) do |year_data|
year_data.to_grouped_hash(:month) do |month_data|
month_data.to_indexed_hash(1)
end
end
It doesn't need Rails or Activesupport, and returns : 它不需要Rails或Activesupport,并返回:
{2015=>
{12=>
{1=>{:account_id=>145, :balance=>4}, 2=>{:account_id=>163, :balance=>11}}},
2016=>
{11=>
{1=>{:account_id=>134, :balance=>3}, 2=>{:account_id=>135, :balance=>0}},
12=>{1=>{:account_id=>133, :price=>5}}}}
Refinements could be use to avoid polluting the Array class. 可以使用优化来避免污染Array类。
# require 'active_support/core_ext/hash'
# ^ uncomment in plain ruby script.
data.group_by{|h| h[:year]}
.map{|year, year_data|
[
year,
year_data.group_by{|month_data| month_data[:month]}.map{|month, vs| [month, vs.map.with_index(1){|v,i| [i,v.except(:year, :month)]}.to_h]}
.to_h]
}.to_h
It uses Hash#except from ActiveSupport. 它使用ActiveSupport 之外的Hash# 。
It outputs : 输出:
{
2016 => {
12 => {
1 => {
:account_id => 133,
:price => 5
}
},
11 => {
1 => {
:account_id => 134,
:balance => 3
},
2 => {
:account_id => 135,
:balance => 0
}
}
},
2015 => {
12 => {
1 => {
:account_id => 145,
:balance => 4
},
2 => {
:account_id => 163,
:balance => 11
}
}
}
}
Know I'm late with this, but this problem has a beautiful recursive structure that deserves to be seen. 知道我迟到了,但是这个问题具有漂亮的递归结构,值得一看。
Inputs are the array of hashes and a list of keys to group on. 输入是哈希数组和要分组的键列表。
For the base case, the key list is empty. 对于基本情况,密钥列表为空。 Just convert the array of hashes into an index-valued hash.
只需将哈希数组转换为索引值的哈希即可。
Otherwise, use the first key in the list to accumulate a hash with corresponding input values as keys, each mapped to a list of hashes with that key deleted. 否则,使用列表中的第一个键累积具有相应输入值的哈希作为键,每个值映射到已删除该键的哈希表。 Each of these lists is just a smaller instance of the same problem using the remaining tail of keys!
这些列表中的每一个只是使用剩余键尾的同一问题的较小实例! So recur to take care of them.
因此,请重复照顾它们。
def group_and_index(a, keys)
if keys.empty?
a.each_with_object({}) {|h, ih| ih[ih.size + 1] = h }
else
r = Hash.new {|h, k| h[k] = [] }
a.each {|h| r[h.delete(keys[0])].push(h) }
r.each {|k, a| r[k] = group_and_index(a, keys[1..-1]) }
end
end
If a key is missing in any of the input hashes, a nil
will be used. 如果任何输入哈希中都缺少键,则将使用
nil
。 Note this function modifies the original hashes. 请注意,此功能会修改原始哈希。 Call on
a.map{|h| h.clone}
调用
a.map{|h| h.clone}
a.map{|h| h.clone}
if that's not desired. a.map{|h| h.clone}
如果不需要)。 To get the example result: 要获得示例结果:
group_and_index(array_of_hashes, [:year, :month])
arr = [{:year=>2016, :month=>12, :account_id=>133, :price=>5},
{:year=>2016, :month=>11, :account_id=>134, :price=>3},
{:year=>2016, :month=>11, :account_id=>135, :price=>0},
{:year=>2015, :month=>12, :account_id=>145, :price=>4},
{:year=>2015, :month=>12, :account_id=>163, :price=>11}]
arr.each_with_object({}) do |g,h|
f = h.dig(g[:year], g[:month])
counter = f ? f.size+1 : 1
h.update(g[:year]=>{ g[:month]=>
{ counter=>{ account_id: g[:account_id], price: g[:price] } } }) { |_yr,oh,nh|
oh.merge(nh) { |_mon,ooh,nnh| ooh.merge(nnh) } }
end
#=> {2016=>{12=>{1=>{:account_id=>133, :price=>5}},
# 11=>{1=>{:account_id=>134, :price=>3},
# 2=>{:account_id=>135, :price=>0}}
# },
# 2015=>{12=>{1=>{:account_id=>145, :price=>4},
# 2=>{:account_id=>163, :price=>11}}
# }
# }
This uses the methods Hash#dig and the forms of Hash#update (aka merge!
) and Hash#merge that employ a block to determine the values of keys that are present in both hashes being merged. 这使用方法Hash#dig以及Hash#update (也称为
merge!
)和Hash#merge的形式 ,后者采用一个块来确定要合并的两个哈希中存在的键的值。 (See the docs for details.) Note that there are such blocks at two difference levels. (有关详细信息,请参阅文档。)请注意,存在两个不同级别的此类块。 If, for example,
例如,如果
{ 2016=>{ 11=>{ {1=>{:account_id=>133, :price=>5 } } } } }
{ 2016=>{ 11=>{ {2=>{:account_id=>135, :price=>0 } } } } }
are being merged, the block would determine the value of 2016
. 被合并时,该区块将确定
2016
的值。 That involves merging the two hashes 这涉及合并两个哈希
{ 11=>{ {1=>{:account_id=>133, :price=>5 } } } }
{ 11=>{ {2=>{:account_id=>135, :price=>0 } } } }
which would call an inner block to determine the value of 11
. 这将调用一个内部块来确定
11
的值。
Here is a simple two-liner 这是一个简单的两层
h = Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = [] }}
ary.each { |each| h[each.delete(:year)][each.delete(:month)] << each }
NB, this modifies the input but I assume you are not interested in the original input after transforming it. 注意,这会修改输入,但是我认为您对原始输入进行了转换后不感兴趣。
Value of h
h
值
{
2016=>{12=>[{:account_id=>133, :price=>5}], 11=>[{:account_id=>134, :price=>3}, {:account_id=>135, :price=>0}]},
2015=>{12=>[{:account_id=>145, :price=>4}, {:account_id=>163, :price=>11}]}
}
You can access the values in h
with 您可以使用以下命令访问
h
的值
h[2016][11][1] # => {:account_id=>135, :price=>0}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.