【问题标题】:How to access nested keys in hashes如何访问哈希中的嵌套键
【发布时间】:2020-07-07 10:22:15
【问题描述】:

我正在使用 Ruby on Rails 和 ClosureTree gem。我有一个嵌套哈希:

{
  :a00=>{
    :a10=>{
      :a20=>{}, 
      :a21=>{}
    }, 
    :a11=>{
      :a22=>{
        :a30=>{}, 
        :a31=>{}
      }
    }
  }
}

我想找到任何给定键的值及其所有“超级键”。例如,对于:a30,我想找到它的值{} 以及它嵌套在其中的哈希键:[:a00, :a11, :a22]

我找到了“Finding Deeply Nested Hash Keys”,它描述了一种满足我标准第一部分的方法,即找到键的值:

def deep_find(obj, key)
  return obj[key] if obj.respond_to?(:key?) && obj.key?(key)

  if obj.is_a? Enumerable
    found = nil
    obj.find { |*a| found = deep_find(a.last, key) }

    found
  end
end

但是,我还没有找到找到“超级密钥”的方法。

【问题讨论】:

  • “例如,对于:a30,我想找到它的值{} 和它嵌套的哈希键” 你能解释一下为什么你已经有密钥:a30,但没有完整路径[:a00, :a11, :a22, :a30]?拥有完整路径后,您可以轻松访问值 tree.dig(*path)。也许我的问题可以更好地表述为:在这种只有部分路径的情况下,您是如何着陆的?
  • 嗨,我没有路径的原因是我使用 ClosureTree gem 中的 #hash_tree 方法从模型生成嵌套哈希。然后我想在不进行任何额外查询的情况下找到密钥的祖先链
  • 您是否仍然可以访问模型实例本身,以便可以调用 model.ancestry_path ?还是会产生额外的查询?

标签: ruby-on-rails ruby hash


【解决方案1】:

我会选择这样的:

def find_node_path(tree, search_key)
  return unless tree.is_a?(Hash)
  return [] if tree.key?(search_key)

  tree.each do |key, node|
    path = find_node_path(node, search_key)
    return [key, *path] if path
  end

  nil
end

path = find_node_path(tree, :a30)
#=> [:a00, :a11, :a22]

# retrieve value
value = tree.dig(*path, :a30)
#=> {}

执行以下操作,如果当前 tree 不是哈希,或者如果在当前 tree 结构中找不到 search_key,则返回 nil

该方法循环遍历哈希的所有键/值对,递归调用find_node_path。如果返回nil,则表示找不到search_key,因此跳转到循环中的下一个迭代。

如果返回值不是nil,则表示search_key 位于node 中的path,相对于tree。在这种情况下,在path 前面加上当前迭代的key 并返回它。

注意:如果search_key 在结构中不是唯一的,则此解决方案确实有效。它只会返回找到的第一个匹配项。由于此解决方案使用depth first,因此当tree = {a1: {a2: {a3: {}}}, b1: {a3: {}}}search_key = :a3 时,它将返回[:a1, :a2]

【讨论】:

  • 这正是我想要的,非常感谢!所有的键都是唯一的,所以应该没问题:)
  • 如果您想在返回的路径中包含search_key,您只需将return [] if ... 更改为return [search_key] if ...
【解决方案2】:

代码

def extract(h,target_key)
  return [target_key, h[target_key]] if h.key?(target_key)
  h.each do |kk,v|
    next unless v.is_a?(Hash)
    arr = extract(v,target_key) 
    return [kk,*arr] unless arr.nil?
  end
  nil
end

示例

h = {
  :a00=>{
    :a10=>{
      :a20=>{1=>2}, 
      :a21=>{3=>4}
    }, 
    :a11=>{
      :a22=>{
        :a30=>{5=>6}, 
        :a31=>{7=>8}
      }
    }
  }
}

[:a00, :a10, :a20, :a21, :a11, :a22, :a30, :a31, 3, :a32].each do |k|
  puts ":#{k} -> #{extract(h,k) || "nil"}"
end

target_key -> extract(h, target_key)

:a00 -> [:a00, {:a10=>{:a20=>{1=>2}, :a21=>{3=>4}},
                :a11=>{:a22=>{:a30=>{5=>6}, :a31=>{7=>8}}}}]
:a10 -> [:a00, :a10, {:a20=>{1=>2}, :a21=>{3=>4}}]
:a20 -> [:a00, :a10, :a20, {1=>2}]
:a21 -> [:a00, :a10, :a21, {3=>4}]
:a11 -> [:a00, :a11, {:a22=>{:a30=>{5=>6}, :a31=>{7=>8}}}]
:a22 -> [:a00, :a11, :a22, {:a30=>{5=>6}, :a31=>{7=>8}}]
:a30 -> [:a00, :a11, :a22, :a30, {5=>6}]
:a31 -> [:a00, :a11, :a22, :a31, {7=>8}]
:3   -> [:a00, :a10, :a21, 3, 4]
:a32 -> nil

说明

根据我的经验,解释递归方法如何工作的唯一有用方法是显示使用puts 语句对方法进行加盐并运行示例的结果。此外,为了在检查输出时保持直截了当,有必要在每个方法调用自身时缩进输出,并在方法返回时“取消缩进”输出。我在下面为我的方法extract 完成了该操作。完成计算需要时间和耐心,但是任何这样做的人都应该对这种方法的工作原理有一个深刻的理解,也许还应该了解一些关于递归方法的知识。当然,无需了解下面显示所执行计算的代码。

INDENT = 6
$col = 4-INDENT
def indent
  $col += INDENT
end
def undent
  $col -= INDENT
end
def prn
  print " "*$col
end
def divide
  puts "#<p></p>"
end

def extract(h,target_key)
  divide
  indent
  prn; puts "entering extract"
  prn; puts "h=#{h}"
  prn; puts "h.key?(#{target_key}) = #{h.key?(target_key)}"
  if h.key?(target_key)
    prn; puts "returning #{[target_key, h[target_key]]}"
    undent
    divide
    return [target_key, h[target_key]]
  end
  h.each do |kk,v|
    prn; puts "  kk=#{kk}"
    prn; puts "  v=#{v}"
    prn; puts "  v.is_a?(Hash) = #{v.is_a?(Hash)}"
    prn; puts "  skip key" unless v.is_a?(Hash)  
    next unless v.is_a?(Hash)
    prn; puts "  call extract(#{v},target_key)"
    arr = extract(v,target_key)
    prn; puts "  arr=#{arr.nil? ? "nil" : arr} returned"
    if arr
       prn; puts "  target key found"
       prn; puts "  returning [#{[kk,*arr]}]"
       undent
       divide  
       return [kk,*arr]
    end
  end
  prn; puts "returning nil"
  undent
  divide 
  nil
end

extract(h,:a30)

entering extract
h={:a00=>{:a10=>{:a20=>{1=>2},..., :a31=>{7=>8}}}}}
h.key?(a30) = false
  kk=a00
  v={:a10=>{:a20=>{1=>2},..., :a31=>{7=>8}}}}
  v.is_a?(Hash) = true
  call extract({:a10=>{:a20..., :a31=>{7=>8}}}},target_key)

      entering extract
      h={:a10=>{:a20=>{1=>2},...,:a31=>{7=>8}}}}
      h.key?(a30) = false
        kk=a10
        v={:a20=>{1=>2}, :a21=>{3=>4}}
        v.is_a?(Hash) = true
        call extract({:a20=>{1=>2}, :a21=>{3=>4}},target_key)

            entering extract
            h={:a20=>{1=>2}, :a21=>{3=>4}}
            h.key?(a30) = false
              kk=a20
              v={1=>2}
              v.is_a?(Hash) = true
              call extract({1=>2},target_key)

                  entering extract
                  h={1=>2}
                  h.key?(a30) = false
                    kk=1
                    v=2
                    v.is_a?(Hash) = false
                    skip key
                  returning nil

              arr=nil returned
              kk=a21
              v={3=>4}
              v.is_a?(Hash) = true
              call extract({3=>4},target_key)

                  entering extract
                  h={3=>4}
                  h.key?(a30) = false
                    kk=3
                    v=4
                    v.is_a?(Hash) = false
                    skip key
                  returning nil

              arr=nil returned
            returning nil

        arr=nil returned
        kk=a11
        v={:a22=>{:a30=>{5=>6}, :a31=>{7=>8}}}
        v.is_a?(Hash) = true
        call extract({:a22=>{:a30..., :a31=>{7=>8}}},target_key)

            entering extract
            h={:a22=>{:a30=>{5=>6}, :a31=>{7=>8}}}
            h.key?(a30) = false
              kk=a22
              v={:a30=>{5=>6}, :a31=>{7=>8}}
              v.is_a?(Hash) = true
              call extract({:a30=>{5=>6},
                :a31=>{7=>8}},target_key)

                  entering extract
                  h={:a30=>{5=>6}, :a31=>{7=>8}}
                  h.key?(a30) = true
                  returning [:a30, {5=>6}]

              arr=[:a30, {5=>6}] returned
              target key found
              returning [[:a22, :a30, {5=>6}]]

        arr=[:a22, :a30, {5=>6}] returned
        target key found
        returning [[:a11, :a22, :a30, {5=>6}]]

  arr=[:a11, :a22, :a30, {5=>6}] returned
  target key found
  returning [[:a00, :a11, :a22, :a30, {5=>6}]]

  #=> [:a00, :a11, :a22, :a30, {5=>6}]

【讨论】:

  • 虽然答案在代码方面看起来不错,但我不能在没有任何解释的情况下对此表示赞同。
  • @3limin4t0r,这是一个公平的评论。在我看来,递归方法的解释是全有或全无的事情。 “全部”选项引导读者逐步完成计算; “无”的选择留给读者自己解决。唉,“全部”选项对于解释器来说是相当耗时的。作为一名学生,我更喜欢通过挖掘来学习的方法,尽管这可能会令人沮丧,但我承认我是少数。因此,我提供了详细的解释。
猜你喜欢
  • 1970-01-01
  • 2011-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-27
  • 2021-12-08
  • 1970-01-01
相关资源
最近更新 更多