【问题标题】:How to group Ruby enumerable/array by more than one field?如何按多个字段对 Ruby 可枚举/数组进行分组?
【发布时间】:2014-10-14 13:55:38
【问题描述】:

我有一个数据结构事件:

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)

我想根据actiondate对数据进行分组,得到最终结果:

{ 
 "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' 重复了一次。 action2 一次在 '1/8/2014' 和两次在 '2/8/2014'。

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

【问题讨论】:

  • 我忘了一个支架。它应该是一个哈希。
  • 不清楚您要做什么。请编辑您的问题并说明清楚。在您明确您的问题之前,我的答案将被隐藏。
  • 我想根据操作和日期对结果进行分组。例如,action1 在 '1/8/2014' 重复了 3 次,在 '8/8/2014' 重复了一次。和 action2 一次在 '1/8/2014' 和两次在 '2/8/2014'。
  • 我们知道您希望根据操作和日期对结果进行分组。价值数字代表什么?用文字来解释,而不仅仅是举例。
  • 因此您希望这些值表示存在 Event 对象的次数。为什么不能这样写?

标签: ruby-on-rails ruby grouping enumerable


【解决方案1】:
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

【讨论】:

  • 感谢您愿意帮助@sawa。结果不是我想要的。我想根据日期对结果进行分组。例如,我不应该得到“2014 年 1 月 8 日”=> 5。因为在 2014 年 1 月 8 日那个日期只有一个 action2。
  • 您可以通过将第二行放在.tap { |h| ... } 中使其成为一个不错的单行。
  • @CarySwoveland 感谢您的建议。我犯了一个错误。
【解决方案2】:

@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}}

如果有兴趣,我很乐意为任何一种方法提供解释。

【讨论】:

    猜你喜欢
    • 2016-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-14
    • 1970-01-01
    • 2016-06-23
    • 1970-01-01
    相关资源
    最近更新 更多