如何从父节点搜索数组的嵌套散列和散列数组以及 return 多个匹配对象?

How to search nested hash of arrays and arrays of hash and return multiple matching objects from the parent node?

假设我有以下 ruby 嵌套哈希

hash_or_array = [{
  "book1" => "buyer1",
  "book2" => {
    "book21" => "buyer21", "book22" => ["buyer23", "buyer24", true]
  },
  "book3" => {
    "0" => "buyer31", "1" => "buyer32", "2" => "buyer33", 
    "3" => [{
      "4" => "buyer34",
      "5" => [10, 11],
      "6" => [{
        "7" => "buyer35"
      }]
    }]
  },
  "book4" => ["buyer41", "buyer42", "buyer43", "buyer35"],
  "book5" => {
    "book5,1" => "buyer5"
  }
}]

我想查找字符串 buyer35。匹配后,它应该return以下

[
    {
        "book3" => {
            "0" => "buyer31", "1" => "buyer32", "2" => "buyer33", 
            "3" => [{
                "4" => "buyer34",
                "5" => [10, 11],
                "6" => [{
                    "7" => "buyer35"
                }]
            }]
        }
    },
    {
        "book4" => ["buyer41", "buyer42", "buyer43", "buyer35"]
    }
]

下面的解决方案(来自下面的另一个 SO 问题,link),return 是第一个匹配项,但我想弄清楚如何 return 多个匹配项

def recurse(obj, target)
  case obj
  when Array
    obj.each do |e|
      case e
      when Array, Hash
        rv = recurse(e, target)
        return [rv] unless rv.nil?
      when target
        return e
      end
    end
  when Hash
    obj.each do |k,v|
      case v
      when Array, Hash
        rv = recurse(v, target)
        return {k=>rv} unless rv.nil?
      when target
        return {k=>v}
      end
    end
  end
  nil
end

这是原问答:

更新:正确的 return 格式应该是

[
    {
        "book3" => {
            "3" => [{
                "6" => [{
                    "7" => "buyer35"
                }]
            }]
        }
    },
    {
        "book4" => ["buyer41", "buyer42", "buyer43", "buyer35"]
    }
]

下面的代码似乎可以为这种特定情况生成所需的输出:

hash_or_array.inject([]) do |result, x|
  x.keep_if { |k, v| v.to_s =~ /buyer35/ }
  result << x
end

这是一个递归搜索任何嵌套数组或散列中的目标值的函数。然后该函数用于 select 包含目标的条目的顶级条目 hash_or_array 并将它们添加到数组中。

require 'pp'

def find_value(obj, target, found = false)
  found = true if obj == target
  return found unless obj.is_a?(Array) || obj.is_a?(Hash)
  case obj
  when Hash 
    obj.each { |k, v| found = find_value(v, target, found) }
  when Array
    obj.each { |v| found = find_value(v, target, found) }
  end
  found
end

found_entries = hash_or_array.inject([]) {|entries, obj| entries << obj.select { |k, v| find_value({ k => v }, "buyer35") }}

pp found_entries

=>

[{"book3"=>
   {"0"=>"buyer31",
    "1"=>"buyer32",
    "2"=>"buyer33",
    "3"=>[{"4"=>"buyer34", "5"=>[10, 11], "6"=>[{"7"=>"buyer35"}]}]},
  "book4"=>["buyer41", "buyer42", "buyer43", "buyer35"]}]

这是针对您的“真实”问题的递归解决方案。因为它改变了原始对象,所以我使用了一个“技巧”来先制作一个深拷贝。 keep_entries_with 生成一个具有原始形状的对象作为您的输入,但由于您的输出形状不同,第二步只是将过滤后的结果转换为您想要的输出形状。

deep_copy = Marshal.load(Marshal.dump(hash_or_array))

def keep_entries_with(target, obj)
  return unless obj.is_a?(Hash) || obj.is_a?(Array)
  case obj
  when Hash
    keep_entries_with(target, obj.values)
    obj.keep_if do |k, v|
      v == target ||
      v.is_a?(Hash) && v.values.any? { _1 == target || _1.is_a?(Hash) || _1.is_a?(Array) } ||
      v.is_a?(Array) && v.any? { _1 == target || _1.is_a?(Hash) || _1.is_a?(Array) }
    end
  when Array
    obj.each do |v|
      keep_entries_with(target, v)
    end
  end
end

filtered = keep_entries_with("buyer35", deep_copy)

final_result = filtered.first.map { |k, v| { k => v } }
pp final_result

产生:

[{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}},
 {"book4"=>["buyer41", "buyer42", "buyer43", "buyer35"]}]