简体   繁体   English

如何将哈希与哈希数组中的不同键/值对合并? Ruby

[英]How to merge hashes with different key/value pairs in array of hashes? Ruby

Here is the array of hashes:这是哈希数组:

array = [
  {:ID=>"aaa", :step2=>80},
  {:ID=>"aaa", :step1=>160},
  {:ID=>"aaa", :step3=>70},
  {:ID=>"bbb", :step1=>80}
]

I'm trying to merge the hashes with the same :ID and insert missing keys with value = 0, like follow:我正在尝试合并具有相同:ID的散列并插入 value = 0 的缺失键,如下所示:

array = [
  {:ID=>"aaa", :step1 => 160, :step2 => 80, :step3 => 70},
  {:ID=>"bbb", :step1 => 80, :step2 => 0, :step3 => 0}
]

Here is my solution:这是我的解决方案:

array = [
  {ID: "aaa", step2: 80},
  {ID: "aaa", step1: 160},
  {ID: "aaa", step3: 70},
  {ID: "bbb", step1: 80}
]

def group_by_id(hashes)
  # gather all IDs
  ids = hashes.map { |h| h[:ID] }.uniq
  keys = hashes.reduce([]) { |keys, hash| keys |= hash.keys }

  default_hash = {}
  keys.each do |key|
    default_hash[key] = 0
  end

  ids.map do |id|
    hashes.select { |hash| hash[:ID] == id }
          .reduce(default_hash) { |reduced, hash| reduced.merge(hash) }
  end
end

desired_array = [
  {ID: "aaa", step1: 160, step2: 80, step3: 70},
  {ID: "bbb", step1: 80, step2: 0, step3: 0}
]

output = group_by_id(array)
puts output
puts desired_array == output

The #each_with_object method may be useful here. #each_with_object方法在这里可能很有用。 In this case we'll pass along a hash h that gets updated for each element in array .在这种情况下,我们将传递一个 hash h ,它会针对array中的每个元素进行更新。 That hash is then returned by the #each_with_object method.然后由#each_with_object方法返回 hash。

Note: ||= assigns the right hand side to the left hand side if the left hand side is nil or false .注意: ||=如果左侧为nilfalse ,则将右侧分配给左侧。

array.each_with_object({}) { |x, h| (h[x[:ID]] ||= {}).update(x) }

Yields:产量:

{"aaa"=>{"ID"=>"aaa", "step3"=>70, "step1"=>160, "step2"=>80}, 
 "bbb"=>{"ID"=>"bbb", "step1"=>80}}

Then we need only use #values to get the data we want.然后我们只需要使用#values来获取我们想要的数据。

array
  .each_with_object({}) { |x, h| (h[x[:ID]] ||= {}).update(x) }
  .values

Yields:产量:

[{"ID"=>"aaa", "step3"=>70, "step1"=>160, "step2"=>80}, 
 {"ID"=>"bbb", "step1"=>80}]

But you want missing keys filled in with 0 .但是您希望缺少用0填充的键。 For this we have to know what all of the keys are, and then we can use #each_with_object again.为此,我们必须知道所有的键是什么,然后我们可以再次使用#each_with_object

grouped = array
           .each_with_object({}) { |x, h| (h[x[:ID]] ||= {}).update(x) }
           .values

all_keys = grouped.map(&:keys).flatten.uniq

grouped.map! { |h| all_keys.each_with_object(h) { |k, _h| _h[k] ||= 0 } }

Now grouped is:现在grouped为:

[{"ID"=>"aaa", "step2"=>80, "step1"=>160, "step3"=>70}, 
 {"ID"=>"bbb", "step1"=>80, "step2"=>0, "step3"=>0}]

This can be done in four steps.这可以分四个步骤完成。

array = [{:ID=>"aaa", :step2=>80}, {:ID=>"aaa", :step1=>160},
         {:ID=>"aaa", :step3=>70}, {:ID=>"bbb", :step1=>80}]

Construct a hash whose values are hashes that comprise the desired array to be returned, before missing zero-valued keys are added在添加缺少的零值键之前,构造一个 hash,其值是包含要返回的所需数组的哈希值

h = array.each_with_object({}) do |g,h|
  h.update(g[:ID]=>g) { |_,o,n| o.merge(n)}
end
  #=> {"aaa"=>{:ID=>"aaa", :step2=>80, :step1=>160, :step3=>70},
  #    "bbb"=>{:ID=>"bbb", :step1=>80, :step4=>40}}

See the form of Hash#update (aka merge ) that takes a block which returns the values of keys that are present in both hashes being merged.请参阅Hash#update (又名merge )的形式,它采用一个块,该块返回正在合并的两个哈希中存在的键的值。 Here that block is:这里的块是:

{ |_,o,n| o.merge(n)}

The block variable _ holds the value of the common key.块变量_保存公共键的值。 The main reason for using an underscore for that variable is to signal to the reader that that key is not used in the block calculation.为该变量使用下划线的主要原因是向读者表明该键未在块计算中使用。 See the doc for definitions of the block variables o and n .有关块变量on的定义,请参见文档。

Construct an array of all unique stepX keys that appear in all elements of array构造出现在数组所有元素中的所有唯一stepX键的array

step_keys = array.flat_map { |g| g.keys }.uniq - [:ID]
  #=> [:step2, :step1, :step3, :step4]

See Enumerable#flat_map .请参阅Enumerable#flat_map

Add the missing keys添加缺少的键

step_keys.each_with_object(h) { |k,g| g.each_value { |v| v[k] ||= 0 } }​
  #=> {"aaa"=>{:ID=>"aaa", :step2=>80, :step1=>160, :step3=>70, :step4=>0},
  #    "bbb"=>{:ID=>"bbb", :step1=>80, :step4=>40, :step2=>0, :step3=>0}}

Now现在

h #=> {"aaa"=>{:ID=>"aaa", :step2=>80, :step1=>160, :step3=>70, :step4=>0},
  #    "bbb"=>{:ID=>"bbb", :step1=>80, :step4=>40, :step2=>0, :step3=>0}}

Return an array containing the values of h返回一个包含h值的数组

h.values
  #=> [{:ID=>"aaa", :step2=>80, :step1=>160, :step3=>70, :step4=>0},
  #    {:ID=>"bbb", :step1=>80, :step4=>40, :step2=>0, :step3=>0}]

These four statements could be combined into a single statement but I would not recommend doing that as readability would suffer and the code would be much harder to test.这四个语句可以组合成一个语句,但我不建议这样做,因为可读性会受到影响并且代码会更难测试。


Depending on requirements, one may be able to write:根据要求,可以编写:

a = array.each_with_object({}) do |g,h|
  h.update(g[:ID]=>Hash.new(0).merge(g)) { |_,o,n| o.merge(n) }
end.values
  #=> [{:ID=>"aaa", :step2=>80, :step1=>160, :step3=>70},
  #    {:ID=>"bbb", :step1=>80, :step4=>40}]

This returns the same array as before, but now:这将返回与以前相同的数组,但现在:

a[0][:step4]
  #=> 0

even though the hash a[0] has no key :step4 .即使 hash a[0]没有键:step4

See the form of Hash::new that takes an argument but now block, the argument being the default value .请参阅Hash::new的形式,它接受一个参数但现在阻塞,参数是默认值 When a hash is defined当定义了 hash

h = Hash.new(0)

then (possibly after keys have been added to h ), h[k] returns the default value when h does not have a key k .然后(可能在将键添加到h之后),当h没有键k时, h[k]返回默认值

There are obvious considerations to weigh in determining if this variant would meet requirements.在确定此变体是否满足要求时,有明显的考虑因素需要权衡。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM