johncssjs
johncssjs

Reputation: 71

How to search nested hash of arrays and arrays of hash and only return matching object from the parent node?

Say I have the below ruby hash nested

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"],
  "book5" => {
    "book5,1" => "buyer5"
  }
}]

And I want to search for a string that matches buyer35. On match, I want it to return the following result

"book3" => {
    "3" => [{
      "6" => [{
        "7" => "buyer35"
      }]
    }]
  }]

All, other non matching keys,values, arrays should be omitted. I have the following example, but it doesn't quite work

def search(hash)
    hash.each_with_object({}) do |(key, value), obj|
      if value.is_a?(Hash)
        returned_hash = search(value)
        obj[key] = returned_hash unless returned_hash.empty?
      elsif value.is_a?(Array)
        obj[key] = value if value.any? { |v| matches(v) }
      elsif matches(key) || matches(value)
        obj[key] = value
      end
    end
end


def matches(str)
    match_criteria = /#{Regexp.escape("buyer35")}/i
    (str.is_a?(String) || str == true || str == false) && str.to_s.match?(match_criteria)
end



....
=> search(hash_or_array)

Any help is appreciated. I realize, I need to use recursion, but can't quite figure how to build/keep track of the matched node from the parent node.

Upvotes: 1

Views: 479

Answers (1)

Cary Swoveland
Cary Swoveland

Reputation: 110685

You can use the following recursive method.

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
recurse(hash_or_array, "buyer35")
  #=> [{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}]
recurse(hash_or_array, "buyer24")
  #=>[{"book2"=>{"book22"=>"buyer24"}}]
recurse(hash_or_array, "buyer33")
  #=> [{"book3"=>{"2"=>"buyer33"}}]
recurse(hash_or_array, 11)
  #=> [{"book3"=>{"3"=>[{"5"=>11}]}}]
recurse(hash_or_array, "buyer5")
  #=>[{"book5"=>{"book5,1"=>"buyer5"}}]

If desired, one may write, for example,

recurse(hash_or_array, "buyer35").first
  #=> {"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}

Upvotes: 2

Related Questions