【问题标题】:Improve this recursive function for hash traversal in Ruby改进 Ruby 中哈希遍历的递归函数
【发布时间】:2009-12-31 19:33:06
【问题描述】:

我编写了一个方法,将值的散列(必要时嵌套)转换为链,该链可与 eval 一起使用以从对象动态返回值。

例如通过像 { :user => { :club => :title }} 这样的哈希,它将返回“user.club.title”,然后我可以对其进行评估。 (这样做的目的是为视图编写一个方法,通过传入对象和我想要显示的属性列表,我可以快速转储对象的内容,例如: item_row(@user, :name, :email, { :club => :title })

这就是我所拥有的。它有效,但我知道它可以改进。很想知道你会如何改进它。

# hash = { :user => { :club => :title }}
# we want to end up with user.club.title
def hash_to_eval_chain(hash)
  raise "Hash cannot contain multiple key-value pairs unless they are nested" if hash.size > 1
  hash.each_pair do |key, value|
    chain = key.to_s + "."
    if value.is_a? String or value.is_a? Symbol
      chain += value.to_s
    elsif value.is_a? Hash
      chain += hash_to_eval_chain(value)
    else
      raise "Only strings, symbols, and hashes are allowed as values in the hash."
    end
    # returning from inside the each_pair block only makes sense because we only ever accept hashes
    # with a single key-value pair
    return chain
  end
end

puts hash_to_eval_chain({ :club => :title }) # => club.title

puts hash_to_eval_chain({ :user => { :club => :title }}) # => user.club.title

puts hash_to_eval_chain({ :user => { :club => { :owners => :name }}}) # => user.club.owners.name

puts ({ :user => { :club => { :owners => :name }}}).to_s # => userclubownersname (close, but lacks the periods)

【问题讨论】:

  • 您是否手动创建这些哈希?如果是这样,为什么不跳过哈希并只传递字符串? item_row(@user, "name", "email", "club.title")

标签: ruby recursion


【解决方案1】:

def hash_to_arr(h)
    arr = []
    while h.kind_of?(Hash)
            # FIXME : check h.size ?
            k = h.keys[0]
            arr.push(k)
            h = h[k]
    end
    arr.push h
end

puts hash_to_arr(:club).join('.') #=> "club"
puts hash_to_arr(:club => :title).join('.') #=> "club.title"
puts hash_to_arr(:user => {:club => :title}).join('.') #=> "user.club.title"
puts hash_to_arr(:user => {:club => {:owners => :name}}).join('.') #=> "user.club.owners.name"
  • 调用 .join('.') 获取字符串。
  • 不检查除 Hash 之外的其他类型,我希望它们在被 Array#join('.') 调用时能够很好地响应 #to_s。
  • 没有递归调用
  • 短代码

最大的变化是避免迭代,因为我们对 1 元素哈希感兴趣。顺便说一句,像 [:club, :title, :owners] 这样的数组可能更适合您的使用。

干杯,
津巴布韦

【讨论】:

  • 不错!我更喜欢递归地解决它,但您的解决方案本身就很优雅。
  • 如此优雅,我决定了解几个字符。请参阅我的第二个答案。 :) 好玩!
【解决方案2】:

受其他一些答案的启发,这是一种使用递归 send() 而不是 eval() 的方法:

def extract(obj, hash)
  k, v = hash.to_a[0]
  v.is_a?(Hash) ? extract(obj.send(k), v) : obj.send(k).send(v)
end

在问题中提到的情况下,extract(@user, {:club => :title}) 将导致@user.send(:club).send(:title)

编辑:正如 zimbatm 所提到的,像 [:club, :title, :owner] 这样的数组可能更干净。如果您改用它(并且在支持 Symbol#to_proc 的环境中运行),您可以这样做:

def extract2(obj, method_array)
  method_array.inject(obj, &:send)
end

【讨论】:

  • 哦,很好。我没有看到发帖人关于评估返回的字符串的内容——有鉴于此,您的 send 版本没有什么味道。
  • 您可以将第二行缩短为 k, v = hash.to_a[0], btw.
  • 哦,您在 extract() 调用的第三行有一个额外的结束括号。
  • 真正优雅,并且在超出我所要求的范围内解决了根本问题的响应中获得了最高分。我将研究您建议的两种方法。
【解决方案3】:

zimbatm 的代码高尔夫回答给了我很大的启发,我决定改进它。

def hash_to_arr(hash)
  arr = []
  arr[arr.size], hash = hash.to_a[0] while hash.kind_of?(Hash)
  arr << hash
end

# > h = { :one => { :two=> { :three => { :four=> :five } } } }
# > hash_to_arr(h).join "."
# => "one.two.three.four.five"

或者,如果你想要它超级高尔夫球,它是 69 64 个字符:

def f(h)a=[];a[a.size],h=h.to_a[0]while h.kind_of? Hash;a<<h end

【讨论】:

  • 呵呵,我看你打高尔夫球很开心
  • 感谢您的回答 - 非常感谢您的时间。
【解决方案4】:

最近遇到了一个类似的问题——想递归地遍历一个哈希,就像它是一棵树一样,同时跟踪所采用的路径:

def dfs(node, path = [], &block)
  case node
  when Hash
    node.each do |key, value|
      path.push(key)
      dfs(value, path, &block)
      path.pop
    end
  when Array
    node.each do |elem|
      yield elem, path if block_given?
    end
  else
    yield node, path if block_given?
  end
end

例如:

tree = {a:{b:1,c:{d:[2,3],e:{f:4}}}}

dfs(tree) do |node, path|
  puts "%s = %s" % [path.join('.'), node]
end

结果:

a.b = 1
a.c.d = 2
a.c.d = 3
a.c.e.f = 4

【讨论】:

    【解决方案5】:

    我认为 Ruby 1.9 有 Hash#flatten(level),它允许您递归地展平哈希(看起来您必须定义递归级别)。然后只需用“。”加入结果数组

    编辑: 刚到 1.9 来试试这个——它只在数组嵌套在哈希值中时才有效,而不是更多的哈希值。抱歉,这对你不起作用。

    【讨论】:

      【解决方案6】:

      这很好但不是很好;请参阅下面的简化方法。

      def hash_to_eval_chain(hsh)
        make_eval_chain(hsh).join "."
      end
      
      private
      def make_eval_chain(obj)
        if obj.is_a? Hash
          raise "Hash cannot contain multiple key-value pairs unless they are nested" if obj.size > 1
          return make_eval_chain(obj.to_a.flatten)
        elsif obj.is_a? Array
          return [ obj.first, *make_eval_chain(obj.last) ] if obj.last.is_a? Hash
          return obj if [String, Symbol, Array].include? obj.class
          raise "Only strings, symbols, and hashes are allowed as values in the hash."
        else
          raise "Expected Hash, received #{obj.class}"
        end
      end
      
      # Usage:
      # irb> hash_to_eval_chain { :one => { :two=> { :three => { :four=> :five } } } }
      # => "one.two.three.four.five"
      

      我找不到一种优雅的方式将它全部整合到一个函数中,所以我希望两个就足够了。

      基本上我意识到{:one =&gt; {:two =&gt; :three}}.to_a.flatten 返回[:one, {:two =&gt; :three}] 并说啊哈!这是经典的汽车/cdr 式递归。

      更好的方法:

      你知道吗,我让这种方式变得比必要的更难。这样更好(灵感来自 Facets 的 Hash#recursively 方法):

      def hash_to_eval_chain(hsh)
        make_eval_chain(hsh).flatten.join "."
      end
      
      private
      def make_eval_chain(obj)
        if obj.is_a? Hash
          return obj.map {|key, val| [key, *make_eval_chain(val)] } if obj.is_a?(Hash) && obj.size <= 1
          raise "Hash cannot contain multiple key-value pairs unless they are nested"
        else
          return obj if [String, Symbol, Array].any? { |klass| obj.is_a? klass }
          raise "Only strings, symbols, and hashes are allowed as values in the Hash."
        end
      end
      
      # Usage:
      # irb> hash_to_eval_chain { :one => { :two=> { :three => { :four=> :five } } } }
      # => "one.two.three.four.five"
      

      【讨论】:

        【解决方案7】:
        myVal = data.try(:[],'user').try(:[],'club').try(:[],'title')
        

        就这么简单;)

        【讨论】:

          【解决方案8】:

          为了记录,你也可以使用我接受的SO answer here

          在由散列和数组组成的散列中进行深度搜索,并查找键并返回路径。

          【讨论】:

            猜你喜欢
            • 2013-11-25
            • 1970-01-01
            • 2014-12-30
            • 2018-03-14
            • 1970-01-01
            • 2014-11-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多