【问题标题】:Reduce an array of nested hashes to a flattened array of key/value pairs将嵌套哈希数组减少为键/值对的扁平数组
【发布时间】:2017-07-29 00:07:32
【问题描述】:

给定以下数据结构:

[
    [:created_at, "07/28/2017"],
    [:valid_record, "true"],
    [:cs_details, { gender: 'm', race: 'w', language: nil } ],
    [:co_details, { description: 'possess', extra: { a: 'a', b: 'b', c: 'c'} } ]
]

我想要一个键/值对数组:

[
        [:created_at, "07/28/2017"],
        [:valid_record, "true"],
        [:gender, 'm'],
        [:race, 'w'],
        [:description, "process"]
        [:a, "a"],
        [:b, "b"],
        [:c, "c"]
]

问题是我不知道如何展平这些哈希值。 flatten 什么都不做:

arr.map(&:flatten)
 => [[:created_at, "07/28/2017"], [:valid_record, "true"], [:cs_details, {:gender=>"m", :race=>"w", :language=>nil}], [:co_details, {:description=>"possess", :extra=>{:a=>"a", :b=>"b", :c=>"c"}}]] 

所以我知道flat_map 也无济于事。我什至无法使用to_a 将这些哈希转换为数组:

arr.map(&:to_a)
 => [[:created_at, "07/28/2017"], [:valid_record, "true"], [:cs_details, {:gender=>"m", :race=>"w", :language=>nil}], [:co_details, {:description=>"possess", :extra=>{:a=>"a", :b=>"b", :c=>"c"}}]] 

上述方法的问题在于它们仅适用于顶级索引。这些哈希值嵌套在数组中。所以我尝试减少,然后在结果上调用 flat_map:

arr.reduce([]) do |acc, (k,v)|
  if v.is_a?(Hash)
    acc << v.map(&:to_a)
  else
    acc << [k,v]
  end
  acc
end.flat_map(&:to_a)
=> [:created_at, "07/28/2017", :valid_record, "true", [:gender, "m"], [:race, "w"], [:language, nil], [:description, "possess"], [:extra, {:a=>"a", :b=>"b", :c=>"c"}]] 

不完全在那里,但更接近。有什么建议吗?

【问题讨论】:

  • 是否保证右边的参数要么是一个值,要么是一个Hash?
  • @Qwerp-Derp 是的
  • 是否要排列所有嵌套的哈希?
  • 是的,我只想要一个键/值对数组,就像我在问题的示例中所示。
  • 旁注: 在这种情况下不要使用reduce,使用each_with_object,后者不会在每次迭代时重新创建对象,而是在原地修改它,节省内存并且对冗余对象实例化没有任何惩罚。

标签: ruby


【解决方案1】:
▶ flattener = ->(k, v) do
▷   case v
▷   when Enumerable then v.flat_map(&flattener)  
▷   when NilClass then []  
▷   else [k, v]  
▷   end  
▷ end  
#⇒ #<Proc:0x000000032169e0@(pry):26 (lambda)>
▶ input.flat_map(&flattener).each_slice(2).to_a
#⇒ [
#    [:created_at, "07/28/2017"],
#    [:valid_record, "true"],
#    [:gender, "m"],
#    [:race, "w"],
#    [:description, "possess"],
#    [:a, "a"],
#    [:b, "b"],
#    [:c, "c"]
#  ]

【讨论】:

  • 请注意,这适用于 ruby​​-2.3.1,但不适用于 ruby​​-2.1.2。后者会抱怨在没有参数的情况下将 flattener 传递给 flat_map
  • input.flat_map {|k,v| flattener.call(k,v) }.each_slice(2).to_a 在 ruby​​-2.1.2
  • @Donato in Ruby 2.1 flattener = -&gt; ((k,v)) 可以工作。我很惊讶原来的版本没有,我认为这是一个错误。
【解决方案2】:

我认为你会受益于编写一个将在数组中的每个项目上调用的辅助函数。为了使结果一致,我们将确保此函数始终返回一个数组数组。换句话说,一个包含一个或多个“条目”的数组,具体取决于索引 1 处的事物是否为哈希。

def extract_entries((k,v))
  if v.is_a? Hash
    v.to_a
  else
    [[k, v]]
  end
end

试一试:

require 'pp'

pp data.map {|item| extract_entries(item)}

输出:

[[[:created_at, "07/28/2017"]],
 [[:valid_record, "true"]],
 [[:gender, "m"], [:race, "w"], [:language, nil]],
 [[:description, "possess"], [:extra, {:a=>"a", :b=>"b", :c=>"c"}]]]

现在,我们可以平展一层以达到您想要的格式:

pp data.map {|item| extract_entries(item)}.flatten(1)

输出:

[[:created_at, "07/28/2017"],
 [:valid_record, "true"],
 [:gender, "m"],
 [:race, "w"],
 [:language, nil],
 [:description, "possess"],
 [:extra, {:a=>"a", :b=>"b", :c=>"c"}]]

【讨论】:

  • 最后一行中的 :extraHash 相关联,这绝对不是有意的。我怀疑这可以在没有递归的情况下完成。
  • 我同意。我喜欢你使用递归的解决方案。
【解决方案3】:
def to_flat(arr)
  arr.flat_map { |k,v| v.is_a?(Hash) ? to_flat(v.compact) : [[k, v]] }
end

测试

arr = [ [:created_at, "07/28/2017"],
        [:valid_record, "true"],
        [:cs_details, { gender: 'm', race: 'w', language: nil } ],
        [:co_details, { description: 'possess', extra: { a: 'a', b: 'b', c: 'c'} } ] ]
to_flat(arr)
#=> [[:created_at, "07/28/2017"], [:valid_record, "true"], [:gender, "m"],
#    [:race, "w"], [:description, "possess"], [:a, "a"], [:b, "b"], [:c, "c"]]

【讨论】:

  • v.class == Hash 是一种代码味道,除非您绝对肯定要丢弃任何 Hash 后代。请改用v.is_a?(Hash)Hash === v
猜你喜欢
  • 1970-01-01
  • 2018-04-21
  • 2018-08-05
  • 1970-01-01
  • 2018-05-30
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 2020-07-02
相关资源
最近更新 更多