Ruby链两个'group_by'方法

时间:2016-06-12 20:16:33

标签: ruby

我有一个对象数组,如下所示:

[
  {day: 'Monday', class: 1, name: 'X'},
  {day: 'Monday', class: 2, name: 'Y'},
  {day: 'Tuesday', class: 1, name: 'Z'},
  {day: 'Monday', class: 1, name: 'T'}
]

我希望按天分组,然后按类

分组
groupedArray['Monday'] => {'1' => [{name: 'X'}, {name: 'T'}], '2' => [{name: 'Y'}]}

我见过有一个

group_by { |a| [a.day, a.class]}

但是这会创建一个带[day,class]键的哈希值。

有没有办法可以实现这一目标,而不必先将它们分组,然后每天迭代,然后按类分组,然后将它们推入新的哈希值?

1 个答案:

答案 0 :(得分:2)

arr = [
  {day: 'Monday',  class: 1, name: 'X'},
  {day: 'Monday',  class: 2, name: 'Y'},
  {day: 'Tuesday', class: 1, name: 'Z'},
  {day: 'Monday',  class: 1, name: 'T'}
]

获得所需散列的一种方法是使用Hash#update(也称为merge!)的形式,该形式使用块来确定合并的两个散列中存在的键的值。这里做了两次,首先当:day的值相同时,然后对于每次这样的事件,当:class的值相同时(对于给定的:day值)。

arr.each_with_object({}) { |g,h|
  h.update(g[:day]=>{ g[:class].to_s=>[{name: g[:name] }] }) { |_,h1,h2|
    h1.update(h2) { |_,p,q| p+q } } }
  #=> {"Monday" =>{"1"=>[{:name=>"X"}, {:name=>"T"}], "2"=>[{:name=>"Y"}]},
  #    "Tuesday"=>{"1"=>[{:name=>"Z"}]}} 

步骤如下。

enum = arr.each_with_object({})
  #=> #<Enumerator: [{:day=>"Monday",  :class=>1, :name=>"X"},
  #                  {:day=>"Monday",  :class=>2, :name=>"Y"},
  #                  {:day=>"Tuesday", :class=>1, :name=>"Z"},
  #                  {:day=>"Monday",  :class=>1, :name=>"T"}]:each_with_object({})> 

我们可以看到这个枚举器通过将它转换为数组而生成的值:

enum.to_a
  #=> [[{:day=>"Monday",  :class=>1, :name=>"X"}, {}],
  #    [{:day=>"Monday",  :class=>2, :name=>"Y"}, {}],
  #    [{:day=>"Tuesday", :class=>1, :name=>"Z"}, {}],
  #    [{:day=>"Monday",  :class=>1, :name=>"T"}, {}]] 

每个数组中的空哈希是正在构建和返回的哈希。它最初是空的,但会在处理enum的每个元素时部分形成。

enum的第一个元素传递给块(Enumerator#each),块变量使用并行赋值分配(somtimes称为多重赋值< / em>的):

g,h = enum.next
  #=> [{:day=>"Monday", :class=>1, :name=>"X"}, {}] 
g #=> {:day=>"Monday", :class=>1, :name=>"X"} 
h #=> {} 

我们现在执行块计算:

h.update(g[:day]=>{ g[:class].to_s=>[{name: g[:name] }] })
  #=> {}.update("Monday"=>{ "1"=>[{name: "X"}] })
  #=> {"Monday"=>{"1"=>[{:name=>"X"}]}}

此操作返回h的更新值,即正在构造的哈希值。

请注意update的参数

"Monday"=>{ "1"=>[{name: "X"}] }

的简写
{ "Monday"=>{ "1"=>[{name: "X"}] } }

因为合并的两个哈希中都没有键"Monday"h没有键),块

{ |_,h1,h2| h1.update(h2) { |_,p,q| p+q } } }

未用于确定"Monday"的值。

现在将enum的下一个值传递给块并分配块变量:

g,h = enum.next
  #=> [{:day=>"Monday", :class=>2, :name=>"Y"},
  #    {"Monday"=>{"1"=>[{:name=>"X"}]}}] 
g #=> {:day=>"Monday", :class=>2, :name=>"Y"} 
h #=> {"Monday"=>{"1"=>[{:name=>"X"}]}}

请注意h已更新。我们现在执行块计算:

h.update(g[:day]=>{ g[:class].to_s=>[{name: g[:name] }] })
  # {"Monday"=>{"1"=>[{:name=>"X"}]}}.update("Monday"=>{ "2"=>[{name: "Y"}] })

两个哈希合并共享密钥&#34;星期一&#34;。因此,我们必须使用该块来确定&#34; Monday&#34;的合并值:

{ |k,h1,h2| h1.update(h2) { |m,p,q| p+q } } }
  #=> {"1"=>[{:name=>"X"}]}.update("2"=>[{name: "Y"}])
  #=> {"1"=>[{:name=>"X"}], "2"=>[{:name=>"Y"}]} 

请参阅update的文档,了解外部kh1的块变量h2updatem的说明内部pqupdatekm是公钥的值。因为它们没有用于块计算中,所以我用下划线替换它们,这是常见的做法。

现在:

h #=> { "Monday" => { "1"=>[{ :name=>"X" }], "2"=>[{ :name=>"Y"}] } }

在此操作之前,哈希h["Monday]还没有密钥2,因此第二个update不需要使用块

{ |_,p,q| p+q }

但是,当enum的最后一个元素合并到h时,会使用此块,因为:day:class的值都相同两个哈希合并。

其余的计算方法类似。