给定以下带有#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
答案 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