如何通过多个字段对Ruby可枚举/数组进行分组?

时间:2014-08-21 03:21:15

标签: ruby-on-rails ruby grouping enumerable

我有一个数据结构事件:

Event = Struct.new(:action, :date, :id)

data= []
data << Event.new('action1', '1/8/2014', 1)
data << Event.new('action1', '1/8/2014', 2)
data << Event.new('action1', '1/8/2014', 3)
data << Event.new('action1', '8/8/2014', 4)
data << Event.new('action2', '1/8/2014', 5)
data << Event.new('action2', '2/8/2014', 6)
data << Event.new('action2', '2/8/2014', 7)

我想根据操作 date 对数据进行分组,以获得最终结果:

{ 
 "action1" => {'1/8/2014' => 3, '8/8/2014' => 1 },
 "action2" => {'1/8/2014' => 1, '2/8/2014' => 2 }
}

最终结果显示,action1在'1/8/2014'重复了3次,在'8/8/2014'重复了一次。并且行动2在'1/8/2014'一次,两次在'2/8/2014'。

我尝试使用#group_by{|x| x.action}首先按行动对结果进行分组,然后尝试使用注入,但我的解决方案不是简单的。

2 个答案:

答案 0 :(得分:2)

Hash.new{|h, k| h[k] = Hash.new{|h, k| h[k] = 0}}
.tap{|h| data.each{|e| h[e.action][e.date] += 1}}

结果h是:

{
  "action1" => {"1/8/2014" => 3, "8/8/2014" => 1},
  "action2" => {"1/8/2014" => 1, "2/8/2014" => 2}
}

,或者

data.each_with_object(Hash.new{|h, k| h[k] = Hash.new{|h, k| h[k] = 0}}) do
  |e, h| h[e.action][e.date] += 1
end

答案 1 :(得分:1)

@sawa给出了一个可爱的解决方案,但这里还有两个。第一个我喜欢,第二个,不是那么多,虽然我认为它可以简化一些。

<强>#1

这个使用了Hash#update(又名merge)的形式。该块仅对键值对起作用,其中键由正在构建的哈希和正在合并的哈希包含。回想一下,当块中没有使用块变量时,块变量可以用下划线(或下划线后跟描述符,例如_key)替换。 (使用下划线只会引起人们的注意。)

data.each_with_object({}) do |d,h|
  h.update({ d.action=>{ d.date=>1 } }) do |_,ohash,_|
    ohash[d.date] = (ohash[d.date] || 0) + 1
    ohash
  end
end
  #=> {"action1"=>{"1/8/2014"=>3, "8/8/2014"=>1}, 
  #    "action2"=>{"1/8/2014"=>1, "2/8/2014"=>2}}

<强>#2

第二种方法在两个级别分别使用Enumerable#group_by,按行动分组,然后按日期分组。

data.map { |d| [d.action, d.date] }
    .group_by(&:first)
    .tap { |h| h.keys.each { |k|
                 h[k]=h[k].group_by { |_,d| d }
                          .tap { |g| g.keys.each {|kk| g[kk]=g[kk].size} } } }
  #=> {"action1"=>{"1/8/2014"=>3, "8/8/2014"=>1}, 
  #    "action2"=>{"1/8/2014"=>1, "2/8/2014"=>2}}

如果有兴趣的话,我很乐意为这两种方法提供解释。