【问题标题】:How to merge arrays of hashes with matching a column value如何将哈希数组与匹配的列值合并
【发布时间】:2019-07-26 18:29:24
【问题描述】:

我想合并两个数组 array1array2 并匹配列值。两个数组之间的绘图 ID 可能匹配也可能不匹配。匹配的列是array1中的Plot ID和array2中的Plotting ID。

主要是array1。 array1 中的列值应首先出现在预期输出中。

如果 array2 与 array1 不匹配,则使用零值合并 array2 列名

array1 = [
{"Date" => "2019-01-01", "Plot ID" => 234},
{"Date" => "2019-01-01", "Plot ID" => 235},
{"Date" => "2019-01-01", "Plot ID" => 236},
{"Date" => "2019-01-01", "Plot ID" => 237},
{"Date" => "2019-01-01", "Plot ID" => 238},
{"Date" => "2019-01-01", "Plot ID" => 239},
{"Date" => "2019-01-01", "Plot ID" => 240},
{"Date" => "2019-01-01", "Plot ID" => 241}
]

array2 = [
{"Date" => "2019-01-01", "Plotting ID" => 234, "size"=> 20, "visit" => 10, "price" => 103},
{"Date" => "2019-01-01", "Plotting ID" => 500,  "size"=> 40, "visit" => 22, "price" => 233},
{"Date" => "2019-01-01", "Plotting ID" => 236,  "size"=> 25, "visit" => 34, "price" => 423},
{"Date" => "2019-01-01", "Plotting ID" => 600,  "size"=> 79, "visit" => 55, "price" => 234}
]

预期输出:

[
{"Date" => "2019-01-01", "Plot ID" => 234, "size"=> 20, "visit" => 10, "price" => 103},
{"Date" => "2019-01-01", "Plot ID" => 235, "size"=> 0, "visit" => 0, "price" => 0},
{"Date" => "2019-01-01", "Plot ID" => 236, "size"=> 25, "visit" => 34, "price" => 423},
{"Date" => "2019-01-01", "Plot ID" => 237, "size"=> 0, "visit" => 0, "price" => 0},
{"Date" => "2019-01-01", "Plot ID" => 238, "size"=> 0, "visit" => 0, "price" => 0},
{"Date" => "2019-01-01", "Plot ID" => 239, "size"=> 0, "visit" => 0, "price" => 0},
{"Date" => "2019-01-01", "Plot ID" => 240, "size"=> 0, "visit" => 0, "price" => 0},
{"Date" => "2019-01-01", "Plot ID" => 241, "size"=> 0, "visit" => 0, "price" => 0}
]

【问题讨论】:

  • [{"Date" => "2019-01-01", "Plot ID" => 234}, "size"=> 20, visit => 10, "price" => 103}]你确定要这样的结果吗?
  • 预期输出中有语法错误,中间有一个额外的}
  • 你想要的方式有语法错误。
  • “Plot ID”和“Date”是否需要匹配才能将它们合并在一起?
  • 您已经在 cmets 中回答了几个问题。请编辑您的问题以包含该信息,以使现在和将来的读者受益,他们不会阅读所有 cmets。这也将阻止额外的反对票。

标签: arrays ruby hash merge


【解决方案1】:

仅当 array2 不包含任何重复的 Plotting ID 值时,此答案才有效。 (如果有重复的Plotting ID 它仍然有效,但它使用数组中曾经存在的最后一条记录。)

array1 = [{"Date" => "2019-01-01", "Plot ID" => 234}, {"Date" => "2019-01-01", "Plot ID" => 235}, {"Date" => "2019-01-01", "Plot ID" => 236}, {"Date" => "2019-01-01", "Plot ID" => 237}, {"Date" => "2019-01-01", "Plot ID" => 238}, {"Date" => "2019-01-01", "Plot ID" => 239}, {"Date" => "2019-01-01", "Plot ID" => 240}, {"Date" => "2019-01-01", "Plot ID" => 241}]
array2 = [{"Date" => "2019-01-01", "Plotting ID" => 234, "size"=> 20, "visit" => 10, "price" => 103}, {"Date" => "2019-01-01", "Plotting ID" => 500,  "size"=> 40, "visit" => 22, "price" => 233}, {"Date" => "2019-01-01", "Plotting ID" => 236,  "size"=> 25, "visit" => 34, "price" => 423}, {"Date" => "2019-01-01", "Plotting ID" => 600,  "size"=> 79, "visit" => 55, "price" => 234}]

array2_lookup = array2.map(&:dup).map { |record| [record.delete('Plotting ID'), record] }.to_h
array2_lookup.default = { 'size' => 0, 'visit' => 0, 'price' => 0 }
pp array1.map { |record| array2_lookup[record['Plot ID']].merge(record) }
# [{"Date"=>"2019-01-01", "size"=>20, "visit"=>10, "price"=>103, "Plot ID"=>234},
#  {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>235},
#  {"Date"=>"2019-01-01", "size"=>25, "visit"=>34, "price"=>423, "Plot ID"=>236},
#  {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>237},
#  {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>238},
#  {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>239},
#  {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>240},
#  {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>241}]

上述解决方案首先循环array2并将其转换为散列,方法是从散列中删除键/值对'Plotting ID'并将值用作键。出于这个原因,我添加了.map(&:dup) 调用,防止array2 中的原始哈希发生变异。如果哈希突变对您来说不是问题,您可以简单地删除它。

创建查找哈希后,我添加了一个在合并哈希时使用的默认值。现在剩下要做的就是遍历array1,查找记录(如果有)或使用默认值并将其与当前元素合并。

这个答案使键有些混乱,但是由于哈希无论如何都是基于键查找(不是键/值顺序),所以这应该不是一个大问题。如果您希望所有键的顺序相同,您可以通过将所有键设置为默认值,将它们的值设置为 nil(或任何其他值,因为它们被覆盖):

array2_lookup.default = { 'Date' => nil, 'size' => 0, 'visit' => 0, 'price' => 0 }
# ...                        ^ added placeholder for ordering purposes
# [{"Date"=>"2019-01-01", "size"=>20, "visit"=>10, "price"=>103, "Plot ID"=>234},
#  {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>235},
#  {"Date"=>"2019-01-01", "size"=>25, "visit"=>34, "price"=>423, "Plot ID"=>236},
#  {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>237},
#  {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>238},
#  {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>239},
#  {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>240},
#  {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>241}]

【讨论】:

  • 我们能不能把array2合并到最后而不是前面
  • 您可以通过使用record 作为接收者并将array2_lookup[record['Plot ID']] 作为merge 参数传递来交换结果哈希的顺序。然而,这确实交换了主导地位。为了解决这个问题,我们必须添加一个块,告诉合并在冲突时使用旧值,而不是新值。 record.merge(array2_lookup[record['Plot ID']]) { |_key, old, _new| old } 见:ruby-doc.org/core-2.6.1/Hash.html#method-i-merge
  • 如果我也需要匹配日期列,我应该更新什么
  • 你可以做同样的事情,但是使用带有Plotting IDDate的数组作为哈希键。然后使用此组合查找值。 gist.github.com/3limin4t0r/47c26c8d2786b954c2b6fbd779eb15ee
  • @Galet 在这种情况下,您应该删除array2_lookup default 分配并将下面的行更改为array1.map { |record| [record, array2_lookup[record['Plot ID']]].compact }.select { |records| records.size == 2 }.map { |record1, record2| record2.merge(record1) }
【解决方案2】:
array1 = [{"Date" => "2019-01-01", "Plot ID" => 234}, {"Date" => "2019-01-01", "Plot ID" => 235}, {"Date" => "2019-01-01", "Plot ID" => 236}, {"Date" => "2019-01-01", "Plot ID" => 237}, {"Date" => "2019-01-01", "Plot ID" => 238}, {"Date" => "2019-01-01", "Plot ID" => 239}, {"Date" => "2019-01-01", "Plot ID" => 240}, {"Date" => "2019-01-01", "Plot ID" => 241}]
array2 = [{"Date" => "2019-01-01", "Plotting ID" => 234, "size"=> 20, "visit" => 10, "price" => 103}, {"Date" => "2019-01-01", "Plotting ID" => 500,  "size"=> 40, "visit" => 22, "price" => 233}, {"Date" => "2019-01-01", "Plotting ID" => 236,  "size"=> 25, "visit" => 34, "price" => 423}, {"Date" => "2019-01-01", "Plotting ID" => 600,  "size"=> 79, "visit" => 55, "price" => 234}]

grouped = (array2 + array1).group_by { |h| h["Plot ID"] || h["Plotting ID"] }
merged = grouped.values.map { |a| a.inject(:merge) }

# and in case you want exact formatting:
template = { "Date" => nil, "Plot ID" => nil, "size"=> 0, "visit" => 0, "price" => 0 }
normalized = merged.each { |h| template.merge(h).slice(*template.keys) }

您可以看到每一行代码如何对数据进行一次有意义且独立的转换。我发现这样的代码更容易在调试器中单步执行。

学习编写那些内置的EnumerableHash 方法并大大简化您的代码!

【讨论】:

  • 阵列 ID 的名称在阵列 1 和阵列 2 中不同。在 array1 中绘制 ID 并在 array2 中绘制 ID
  • 哦,这是故意的?固定。
  • 我想将 array2 中的列名合并为零,即使没有匹配项。如果它们都不匹配,我在结果中看不到 array2 中的列名。
【解决方案3】:

如果我得到正确的答案,也许这是一个可能的选择。

template = {"size"=> 0, "visit" => 0, "price" => 0}
array1.map do |h|
  begin
    h.merge!(template, array2.find { |hh| hh["Plotting ID"] == h["Plot ID"] })
     .then { |hh| hh.delete("Plotting ID") }
  rescue TypeError
  end
end

我使用了rescue,因为 find 可以返回 nil


不带rescue 的选项:
template = {"size"=> 0, "visit" => 0, "price" => 0}
array1.map do |h|
  h2 = {} || array2.find { |hh| hh["Plotting ID"] == h["Plot ID"] if hh.has_key("Plotting ID") } 
  h.merge!(template, h2).then { |hh| hh.delete("Plotting ID") }
end

即使是一个班轮:

array1.map { |h| h.merge!({"size"=> 0, "visit" => 0, "price" => 0}, {} || array2.find { |hh| hh["Plotting ID"] == h["Plot ID"] if hh.has_key("Plotting ID") }).then { |hh| hh.delete("Plotting ID") } }


它修改了array1,所以:
array1

# [{"Date"=>"2019-01-01", "Plot ID"=>234, "size"=>20, "visit"=>10, "price"=>103}, {"Date"=>"2019-01-01", "Plot ID"=>235, "size"=>0, "visit"=>0, "price"=>0}, {"Date"=>"2019-01-01", "Plot ID"=>236, "size"=>25, "visit"=>34, "price"=>423}, {"Date"=>"2019-01-01", "Plot ID"=>237, "size"=>0, "visit"=>0, "price"=>0}, {"Date"=>"2019-01-01", "Plot ID"=>238, "size"=>0, "visit"=>0, "price"=>0}, {"Date"=>"2019-01-01", "Plot ID"=>239, "size"=>0, "visit"=>0, "price"=>0}, {"Date"=>"2019-01-01", "Plot ID"=>240, "size"=>0, "visit"=>0, "price"=>0}, {"Date"=>"2019-01-01", "Plot ID"=>241, "size"=>0, "visit"=>0, "price"=>0}]

【讨论】:

  • 没有救援选项有没有其他解决方案
  • @karan,是的,添加了选项(也是单线)
【解决方案4】:
template = (array2.first.keys - array1.first.keys - ["Plotting ID"]).product([0]).to_h
  #=> {"size"=>0, "visit"=>0, "price"=>0}

h = array1.each_with_object({}) { |g,h| h[g["Plot ID"]] = g.merge(template) } 
  #=> {234=>{"Date"=>"2019-01-01", "Plot ID"=>234, "size"=>0, "visit"=>0, "price"=>0},
  #    235=>{"Date"=>"2019-01-01", "Plot ID"=>235, "size"=>0, "visit"=>0, "price"=>0}, 
  #    ...
  #    241=>{"Date"=>"2019-01-01", "Plot ID"=>241, "size"=>0, "visit"=>0, "price"=>0}}

array2.each_with_object(h) { |g,f| f.update(g["Plotting ID"]=>
    g.transform_keys { |k| k == "Plotting ID" ? "Plot ID" : k }) }.values
  #=> [{"Date"=>"2019-01-01", "Plot ID"=>234, "size"=>20, "visit"=>10, "price"=>103}, 
  #    {"Date"=>"2019-01-01", "Plot ID"=>235, "size"=> 0, "visit"=> 0, "price"=>  0},
  #    {"Date"=>"2019-01-01", "Plot ID"=>236, "size"=>25, "visit"=>34, "price"=>423},
  #    {"Date"=>"2019-01-01", "Plot ID"=>237, "size"=> 0, "visit"=> 0, "price"=>  0},
  #    {"Date"=>"2019-01-01", "Plot ID"=>238, "size"=> 0, "visit"=> 0, "price"=>  0},
  #    {"Date"=>"2019-01-01", "Plot ID"=>239, "size"=> 0, "visit"=> 0, "price"=>  0},
  #    {"Date"=>"2019-01-01", "Plot ID"=>240, "size"=> 0, "visit"=> 0, "price"=>  0},
  #    {"Date"=>"2019-01-01", "Plot ID"=>241, "size"=> 0, "visit"=> 0, "price"=>  0},
  #    {"Date"=>"2019-01-01", "Plot ID"=>500, "size"=>40, "visit"=>22, "price"=>233},
  #    {"Date"=>"2019-01-01", "Plot ID"=>600, "size"=>79, "visit"=>55, "price"=>234}] 

如果需要,可以在最后一个表达式中替换 h

如下定义template会更简单:

template = (%w| size visit price |).product([0]).to_h

但是,这有一个缺点,即很容易更改 array2 元素中键的名称和/或数量。

虽然效率较低,但以下内容可能更清晰且更易于维护。

a2 = array2.map { |h| h.transform_keys { |k| k == "Plotting ID" ? "Plot ID" : k } }
  # => [{"Date"=>"2019-01-01", "Plot ID"=>234, "size"=>20, "visit"=>10, "price"=>103},
  #     {"Date"=>"2019-01-01", "Plot ID"=>500, "size"=>40, "visit"=>22, "price"=>233},
  #     {"Date"=>"2019-01-01", "Plot ID"=>236, "size"=>25, "visit"=>34, "price"=>423},
  #     {"Date"=>"2019-01-01", "Plot ID"=>600, "size"=>79, "visit"=>55, "price"=>234}] 
template = (a2.first.keys - array1.first.keys).product([0]).to_h
  #=> <same as earlier value>
h = array1.each_with_object({}) { |g,h| h[g["Plot ID"]] = g.merge(template) }
  #=> <same as earlier value>
a2.each_with_object(h) { |g,f| f.update(g["Plot ID"]=>g) }.values
  #=> <same as earlier value>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-14
    • 1970-01-01
    • 1970-01-01
    • 2020-07-24
    • 1970-01-01
    • 2014-06-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多