在深层嵌套的哈希中查找链

时间:2017-01-08 17:11:40

标签: ruby

给定以下带有#find_chains方法的类,问题是找到导致给定key_to_find的所有链。

class HashDeepUtils
  def initialize(hash)
    @hash = hash
    @chains = [[]]
  end

  def find_chains(key_to_find)
    if hash.has_key?(key_to_find)
      @chains[0] << key_to_find
      @chains.unshift([])
    else
      @hash.each_key do |key|
        deep_utils = HashDeepUtils.new(@hash[key])
        probable_chains = deep_utils.find_chains(key_to_find)
        if probable_chains.any?
          @chains[0] << key
          @chains[0] += probable_chains[0]
        end
      end
    end

    return @chains
  end
end

我编写了以下测试来测试这种行为。

class TestChainFinding < Minitest::Test
  def test_finds_single_chain
    hash = {test: 'test'}
    deep_utils = HashDeepUtils.new(hash)
    assert_equal [[:test]], deep_utils.find_chains(:test)
  end

  def test_finds_nested_chain
    hash = {test: {foo: 'bar'}}
    deep_utils = HashDeepUtils.new(hash)
    assert_equal [[:test, :foo]], deep_utils.find_chains(:foo)
  end

  def test_finds_multiple_nested_chains
    hash = {test: {foo: 'bar'}, foo: 'baz'}
    deep_utils = HashDeepUtils.new(hash)
    assert_equal [[:test, :foo], [:foo]], deep_utils.find_chains(:foo)
  end
end

3 个答案:

答案 0 :(得分:3)

您正在通过创建绝对不必要的类来滥用OOP。纯递归函数完美地解决了这个任务:

def chains(hash, key, path = [], memo = [])
  hash.each_with_object(memo) do |(k, v), acc|
    memo.push(path + [k]) if k == key
    chains(v, key, (path + [k]), memo) if v.is_a?(Hash)
  end
end

hash = {test: {foo: 'bar'}, foo: 'baz'}
chains(hash, :foo)
#⇒ [[:test, :foo], [:foo]]

chains({test: {foo: {baz: 'baz', foo: 'foo'}}, foo: 'foo'}, :foo)
#⇒ [[:test, :foo], [:test, :foo, :foo], [:foo]]

答案 1 :(得分:1)

递归方法

def chains(hash, key)
  hash.each_with_object([]) do |(k,v), arr|
    arr.concat([[k]]) if k == key
    if v.is_a?(Hash)
      a = chains(hash[k], key)
      arr.concat(a.map { |b| [k, *b] }) unless a.empty?
    end
  end
end

<强>实施例

chains({ test: {foo: 'bar'}, foo: 'baz' }, :foo)
  #=> [[:test, :foo], [:foo]]

chains({ test: { foo: { baz: 'baz', foo: 'foo' } }, foo: 'foo' }, :foo)
  #=> [[:test, :foo], [:test, :foo, :foo], [:foo]]

<强>解释

我插入了一些puts语句来显示递归方法执行的步骤和计算。

def chains(hash, key, lvl=0)
  level_indent = ' '*(4*lvl)
  loop_indent  = ' '*(4*lvl + 2)
  puts "\n#{level_indent}lvl=#{lvl}"
  puts "#{level_indent}entered chains(#{hash}, #{key})"
  puts "#{level_indent}hash=#{hash}"
  hash.each_with_object([]) do |(k,v), arr|
    puts "#{loop_indent}k=#{k}, v=#{v}, arr=#{arr}"
    puts "#{loop_indent}#{k}==#{key} is #{k==key}"   
    arr.concat([[k]]) if k == key
    puts "#{loop_indent}arr=#{arr}" if k == key
    puts "#{loop_indent}#{v}.is_a?(Hash) = #{v.is_a?(Hash)}"
    if v.is_a?(Hash)
      puts "#{loop_indent}calling chains(#{hash[k]}, #{key}, #{lvl+1})"
      a = chains(hash[k], key, lvl+1)
      puts "#{loop_indent}a=#{a} returned from lvl #{lvl+1}"
      puts "#{loop_indent}a.map { |b| [k, *b] }= #{a.map { |b| [k, *b] }}"
      arr.concat(a.map { |b| [k, *b] }) unless a.empty?
      puts "#{loop_indent}arr=#{arr}"
    end
  end
end

chains({ test: {foo: 'bar'}, foo: 'baz' }, :foo)
  # lvl=0
  # entered chains({:test=>{:foo=>"bar"}, :foo=>"baz"}, foo)
  # hash={:test=>{:foo=>"bar"}, :foo=>"baz"}
  #   k=test, v={:foo=>"bar"}, arr=[]
  #   test==foo is false
  #   {:foo=>"bar"}.is_a?(Hash) = true
  #   calling chains({:foo=>"bar"}, foo, 1)

  #     lvl=1
  #     entered chains({:foo=>"bar"}, foo)
  #     hash={:foo=>"bar"}
  #       k=foo, v=bar, arr=[]
  #       foo==foo is true
  #       arr=[[:foo]]
  #       bar.is_a?(Hash) = false

  #   a=[[:foo]] returned from lvl 1
  #   a.map { |b| [k, *b] }= [[:test, :foo]]
  #   arr=[[:test, :foo]]
  #   k=foo, v=baz, arr=[[:test, :foo]]
  #   foo==foo is true
  #   arr=[[:test, :foo], [:foo]]
  #   baz.is_a?(Hash) = false
  #=> [[:test, :foo], [:foo]] 

chains({ test: { foo: { baz: 'baz', foo: 'foo' } }, foo: 'foo' }, :foo)
  # lvl=0
  # entered chains({:test=>{:foo=>{:baz=>"baz", :foo=>"foo"}}, :foo=>"foo"}, foo)
  # hash={:test=>{:foo=>{:baz=>"baz", :foo=>"foo"}}, :foo=>"foo"}
  #   k=test, v={:foo=>{:baz=>"baz", :foo=>"foo"}}, arr=[]
  #   test==foo is false
  #   {:foo=>{:baz=>"baz", :foo=>"foo"}}.is_a?(Hash) = true
  #   calling chains({:foo=>{:baz=>"baz", :foo=>"foo"}}, foo, 1)

  #     lvl=1
  #     entered chains({:foo=>{:baz=>"baz", :foo=>"foo"}}, foo)
  #     hash={:foo=>{:baz=>"baz", :foo=>"foo"}}
  #       k=foo, v={:baz=>"baz", :foo=>"foo"}, arr=[]
  #       foo==foo is true
  #       arr=[[:foo]]
  #       {:baz=>"baz", :foo=>"foo"}.is_a?(Hash) = true
  #       calling chains({:baz=>"baz", :foo=>"foo"}, foo, 2)

  #         lvl=2
  #         entered chains({:baz=>"baz", :foo=>"foo"}, foo)
  #         hash={:baz=>"baz", :foo=>"foo"}
  #           k=baz, v=baz, arr=[]
  #           baz==foo is false
  #           baz.is_a?(Hash) = false
  #           k=foo, v=foo, arr=[]
  #           foo==foo is true
  #           arr=[[:foo]]
  #           foo.is_a?(Hash) = false

  #       a=[[:foo]] returned from lvl 2
  #       a.map { |b| [k, *b] }= [[:foo, :foo]]
  #       arr=[[:foo], [:foo, :foo]]

  #   a=[[:foo], [:foo, :foo]] returned from lvl 1
  #   a.map { |b| [k, *b] }= [[:test, :foo], [:test, :foo, :foo]]
  #   arr=[[:test, :foo], [:test, :foo, :foo]]
  #   k=foo, v=foo, arr=[[:test, :foo], [:test, :foo, :foo]]
  #   foo==foo is true
  #   arr=[[:test, :foo], [:test, :foo, :foo], [:foo]]
  #   foo.is_a?(Hash) = false
  #=> [[:test, :foo], [:test, :foo, :foo], [:foo]] 

答案 2 :(得分:0)

在不滥用OOP的情况下回答,但更接近问题中的原始代码。

class HashDeepUtils
  def initialize(hash)
    @hash = hash
  end

  def find_chains(key_to_find, hash = @hash)
    chains = []

    if hash.has_key?(key_to_find)
      chains.unshift([key_to_find])
    end

    hash.each_key do |key|
      next unless hash[key].is_a?(Hash)

      further_chains = find_chains(key_to_find, hash[key])
      further_chains.each do |further_chain|
        chains.unshift([])
        chains[0] << key
        chains[0] += further_chain
      end
    end

    chains
  end
end