将数组转换为哈希并将计数器值添加到新哈希

时间:2019-07-01 20:08:07

标签: ruby

我有以下哈希数组:

[
  {"BREAD" => {:price => 1.50, :discount => true }},
  {"BREAD" => {:price => 1.50, :discount => true }},
  {"MARMITE"    => {:price => 1.60, :discount => false}}
]

我想将此数组转换为包含每个项目计数的哈希:

输出:

 {
  "BREAD" => {:price => 1.50, :discount => true, :count => 2},
  "MARMITE"    => {:price => 1.60, :discount => false, :count => 1}
   }

我尝试了两种方法将数组转换为哈希。

new_cart = cart.inject(:merge)

hash = Hash[cart.collect { |item| [item, ""] } ]

两者都可以,但是后来我对如何捕获和传递计数值感到困惑。

预期产量

 {
  "BREAD" => {:price => 1.50, :discount => true, :count => 2},
  "MARMITE"    => {:price => 1.60, :discount => false, :count => 1}
   }

2 个答案:

答案 0 :(得分:0)

我们得到了数组:

arr = [
  {"BREAD"   => {:price => 1.50, :discount => true }},
  {"BREAD"   => {:price => 1.50, :discount => true }},
  {"MARMITE" => {:price => 1.60, :discount => false}}
]

并假设每个哈希具有一个密钥,并且如果两个哈希具有相同的(单个)密钥,则在两个哈希中该密钥的值都相同。

第一步是创建一个空哈希,将在其中添加键值对:

h = {}

现在,我们遍历arr来构建哈希h。我添加了一个puts语句以在计算中显示中间值。

arr.each do |g|
  k, v = g.first
  puts "k=#{k}, v=#{v}"
  if h.key?(k)
    h[k][:count] += 1
  else
    h[k] = v.merge({ :count => 1 })
  end
end

显示:

k=BREAD, v={:price=>1.5, :discount=>true}
k=BREAD, v={:price=>1.5, :discount=>true}
k=MARMITE, v={:price=>1.6, :discount=>false}

并返回:

  #=> [{"BREAD"  =>{:price=>1.5, :discount=>true}},
  #    {"BREAD"  =>{:price=>1.5, :discount=>true}},
  #    {"MARMITE"=>{:price=>1.6, :discount=>false}}] 

each总是返回其接收者(这里是arr),这不是我们想要的。

h #=> {"BREAD"=>{:price=>1.5, :discount=>true, :count=>2},
  #    "MARMITE"=>{:price=>1.6, :discount=>false, :count=>1}} 

是我们需要的结果。请参见Hash#key?(又名has_key?),Hash#[]Hash#[]=Hash#merge

现在让我们将其包装在一个方法中。

def hashify(arr)
  h = {}
  arr.each do |g|
    k, v = g.first
    if h.key?(k)
      h[k][:count] += 1
    else
      h[k] = v.merge({ :count=>1 })
    end
  end
  h
end

hashify(arr)
  #=> {"BREAD"=>{:price=>1.5, :discount=>true, :count=>2},
  #    "MARMITE"=>{:price=>1.6, :discount=>false, :count=>1}} 

Rubyists通常会使用方法Enumerable#each_with_object进行简化。

def hashify(arr)
  arr.each_with_object({}) do |g,h|
    k, v = g.first
    if h.key?(k)
      h[k][:count] += 1
    else
      h[k] = v.merge({ :count => 1 })
    end
  end
end

比较两种方法以识别它们的差异。参见Enumerable#each_with_object

当键(如此处)是符号时,Ruby允许您将简写{ count: 1 }用作{ :count=>1 }。此外,当散列为参数时,她允许您写:count = 1count: 1时不用大括号。例如,

{}.merge('cat'=>'meow', dog:'woof', :pig=>'oink')
  #=> {"cat"=>"meow", :dog=>"woof", :pig=>"oink"} 

当键是符号时,看到count: 1的形式可能更常见,而当散列是参数时,省略大括号。

您可能会在这里看到进一步的改进。首先创建

h = arr.group_by { |h| h.keys.first }
  #=> {"BREAD"  =>[{"BREAD"=>{:price=>1.5, :discount=>true}},
  #                {"BREAD"=>{:price=>1.5, :discount=>true}}],
  #    "MARMITE"=>[{"MARMITE"=>{:price=>1.6, :discount=>false}}]} 

请参见Enumerable#group_by。现在将值(数组)转换为其大小:

counts = h.transform_values { |arr| arr.size }
  #=> {"BREAD"=>2, "MARMITE"=>1}

可以缩写形式:

counts = h.transform_values(&:size)
  #=> {"BREAD"=>2, "MARMITE"=>1}

请参见Hash#transform_values。我们现在可以写:

uniq_arr = arr.uniq
  #=> [{"BREAD"=>{:price=>1.5, :discount=>true}}, 
  #=   {"MARMITE"=>{:price=>1.6, :discount=>false}}] 

uniq_arr.each_with_object({}) do |g,h|
  puts "g=#{g}"
  k,v = g.first
  puts "  k=#{k}, v=#{v}"
  h[k] = v.merge(counts: counts[k])
  puts "  h=#{h}"
end

其中显示:

g={"BREAD"=>{:price=>1.5, :discount=>true}}
  k=BREAD, v={:price=>1.5, :discount=>true}
  h={"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2}}
g={"MARMITE"=>{:price=>1.6, :discount=>false}}
  k=MARMITE, v={:price=>1.6, :discount=>false}
  h={"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2},
     "MARMITE"=>{:price=>1.6, :discount=>false, :counts=>1}}

并返回:

  #=> {"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2},
  #    "MARMITE"=>{:price=>1.6, :discount=>false, :counts=>1}} 

请参见Array#uniq

答案 1 :(得分:0)

这可以解决问题:

arr = [
  { bread: { price: 1.50, discount: true } },
  { bread: { price: 1.50, discount: true } },
  { marmite: { price: 1.60, discount: false } }
]

获取每次哈希值的计数,添加为键值对并存储:

h = arr.uniq.each { |x| x[x.first.first][:count] = arr.count(x) }

然后将散列转换为数组,展平为单个数组,然后构造一个哈希:

Hash[*h.collect(&:to_a).flatten] 
#=> {:bread=>{:price=>1.50, :discount=>true, :count=>2}, :marmite=>{:price=>1.60, :discount=>false, :count=>1}}

结合了以下两个不错的主意:   https://raycodingdotnet.wordpress.com/2013/08/05/array-of-hashes-into-single-hash-in-ruby/ 和这里:   http://carol-nichols.com/2015/08/07/ruby-occurrence-couting/

相关问题