【问题标题】:Ruby group hashes based on matching keys and store the value of non matching keys in an array [closed]基于匹配键的Ruby组散列并将不匹配键的值存储在数组中[关闭]
【发布时间】:2019-11-19 21:47:15
【问题描述】:

需要根据下面的哈希值来实现给定的输出

foos = [ { :key => 'Foo', :value => 1, :revenue => 2 },
         { :key => 'Foo', :value => 1, :revenue => 4 },
         { :key => 'Bar', :value => 2, :revenue => 7 },
         { :key => 'bar', :value => 2, :revenue => 9 },
         { :key => 'Zampa', :value => 4, :revenue => 9 }]

输出应该是:

{ :key => 'Foo', :value => 1, :revenue => [2,4] } #Merging row 1 & 2 as they share same :key 'Foo'
{ :key => 'Bar', :value => 2, :revenue => [7,9] } #Merging row 3 & 4 as they share same :key 'Bar'
{ :key => 'Zampa', :value => 4, :revenue => 9 } 

合并应该基于:Key字段的值 我是 ruby​​ 新手,如何在 ruby​​ 中实现这一点。

【问题讨论】:

  • 如果value 键的值不同会怎样?
  • 那么这也将被合并到像收入这样的数组中。但在我的应用程序中,只有一个键的值(收入)会有所不同。但我的方法应该适用于所有其他关键价值和收入。
  • collect 数组中的最终顺序是否重要?
  • No..ordering 没关系
  • Avinaba,我不关注您的第一条评论。数组是否也可以包含哈希{ :key => 'Foo', :value => 2, :revenue => 6 }?如果是,你想要的结果是什么?如果否,如果您排除了密钥 :value,您的示例会更清楚。

标签: ruby hash ruby-hash


【解决方案1】:

您可以使用group_byfoos 数组按:key 分组。但是,我会首先将 downcase :key 值,因为您希望 'Bar''bar' 最终在同一个组中。

# We first need to unify the keys of the hashes before we can start
# grouping. 'Bar' != 'bar' so they would be split up in two separate
# groups. Judging from the output you don't want this.
foos.each { |foo| foo[:key].downcase! }

# Now that all keys are downcased we can group based upon the value of
# the :key key.
grouped_foos = foos.group_by { |foo| foo[:key] }

# Now we need to map over the resulting hash and create a single result
# for each group.
grouped_foos.transform_values! do |foos|
  # First I'll transform the structure of `foos`, from:
  #
  #     [{a: 1, b: 2}, {a: 3, b: 4}]
  #
  # into:
  #
  #     [[:a, 1], [:b, 2], [:a, 3], [:b, 4]]
  #
  tmp = foos.flat_map(&:to_a)

  # Then I'll group the above structure based upon the first value in
  # each array, simultaneously removing the first element. Resulting in:
  #
  #     {a: [[1], [3]], b: [[2], [4]]}
  #
  tmp = tmp.group_by(&:shift)

  # We now need to flatten the values by one level. Resulting in:
  #
  #     {a: [1, 3], b: [2, 4]}
  #
  tmp.transform_values! { |values| values.flatten(1) }

  # The next step is remove duplicate values. We currently have:
  #
  #     {key: ['foo', 'foo'], value: [1, 1], revenue: [2, 4]}
  #
  # whereas we want:
  #
  #     {key: ['foo'], value: [1], revenue: [2, 4]}
  #
  tmp.transform_values!(&:uniq)

  # Lastly if the array only contains a single value we want to use the
  # value instead of an array. Transforming the above structure into:
  #
  #     {key: 'foo', value: 1, revenue: [2, 4]}
  #
  tmp.transform_values! { |head, *tail| tail.empty? ? head : [head, *tail] }

  # Finally we need to return our new hash.
  tmp 
end

结合以上步骤我们得到如下结果:

foos.each { |foo| foo[:key].downcase! }
grouped_foos = foos.group_by { |foo| foo[:key] }

grouped_foos.transform_values! do |foos|
  foos.flat_map(&:to_a).group_by(&:shift)
      .transform_values { |values| values.flatten(1).uniq }
      .transform_values { |head, *tail| tail.empty? ? head : [head, *tail] }
end

如果您不想修改(大写的)原始foos 结构,则必须替换:

foos.each { |foo| foo[:key].downcase! }
# with
unified_keys = foos.map(&:dup).each { |foo| foo[:key] = foo[:key].downcase }

然后从那时起使用新的unified_keys 结构。

上述解决方案产生以下结果:

grouped_foos
#=> {"foo"  =>{:key=>"foo",   :value=>1, :revenue=>[2, 4]},
#    "bar"  =>{:key=>"bar",   :value=>2, :revenue=>[7, 9]},
#    "zampa"=>{:key=>"zampa", :value=>4, :revenue=>9}}

你可以通过请求grouped_foos的值得到你想要的结果:

grouped_foos.values
#=> [{:key=>"foo",   :value=>1, :revenue=>[2, 4]},
#    {:key=>"bar",   :value=>2, :revenue=>[7, 9]},
#    {:key=>"zampa", :value=>4, :revenue=>9}]

【讨论】:

  • 你的回答比较清晰详细,所以希望OP澄清一下真正期待什么,能接受你的回答。
【解决方案2】:

您可以尝试按keyvalue 分组,然后映射revenue 值:

foos
  .group_by { |e| e.values_at(:key, :value) }
  .map do |(key, value), values|
    { key: key, value: value, revenue: values.map { |e| e[:revenue] } }
  end
# [{:key=>"Foo", :value=>1, :revenue=>[2, 4]}, {:key=>"Bar", :value=>2, :revenue=>[7]}, {:key=>"bar", :value=>2, :revenue=>[9]}, {:key=>"Zampa", :value=>4, :revenue=>[9]}]

【讨论】:

  • 或者使用.group_by { |foo| foo.slice(:key, :value) },然后将map更改为.map { |foo, foos| foo.merge revenue: foos.map { |foo| foo[:revenue] } }
  • 这可能是一个不同的答案@3limin4t0r。刚刚更新为使用 values_atslice 提示。
  • 但是如果 hash 中的 key 数量不限于 3。如果是 10 或 15 或更多。但它应该根据包含的值合并:key。那应该是什么方法。 @塞巴斯蒂安·帕尔马
【解决方案3】:

使用减少

result=foos.group_by { |x| x[:key] }.values.map do |arr|
  arr.reduce do |h1, h2|
    h1.merge(h2) do |k, v1, v2|
      k.eql?(:revenue) ? [v1, v2] : v1
    end
  end
end

p 结果

[{:key=>"Foo", :value=>1, :revenue=>[2, 4]}, {:key=>"bar", :value=>2, :revenue=>[7, 9]}, {:key=>"Zampa", :value=>4, :revenue=>9}]

【讨论】:

    猜你喜欢
    • 2022-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    • 1970-01-01
    • 2021-10-13
    • 2019-07-13
    相关资源
    最近更新 更多