我编写了一个方法,将值的哈希值(如有必要,嵌套)转换为一个可以与eval一起使用的链,从而动态地从对象返回值。
E.g。传递了像{:user =>这样的哈希值{:club => :title}},它将返回“user.club.title”,然后我可以进行评估。 (这点是为视图编写一个方法,允许我通过传入对象和我想要显示的属性列表来快速转储对象的内容,例如:item_row(@ user,:name, :电子邮件,{: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)
答案 0 :(得分:4)
< codegolfing mode = on>
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"
最大的变化是避免迭代,因为我们对1个元素哈希感兴趣。顺便说一句,像[:club,:title,:owner]这样的阵列对你的使用来说可能会更直接。
干杯,
zimbatm
答案 1 :(得分: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
答案 2 :(得分:3)
受到其他一些答案的启发,这是使用递归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
答案 3 :(得分:1)
最近遇到了类似的问题 - 希望以递归方式遍历Hash,就好像它是一棵树,同时跟踪所采用的路径:
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
答案 4 :(得分:0)
我认为Ruby 1.9有Hash#flatten(level),它允许你递归地展平哈希(看起来你必须定义递归级别)。然后用“。”
加入生成的数组编辑:只需要1.9来尝试这个 - 它只适用于数组嵌套在哈希值中,而不是更多哈希值。对不起,这对你不起作用。
答案 5 :(得分:0)
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 => {:two => :three}}.to_a.flatten
返回[:one, {:two => :three}]
并说A-ha !,这是经典的汽车/ 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"
答案 6 :(得分:0)
myVal = data.try(:[],'user').try(:[],'club').try(:[],'title')
就这么简单;)
答案 7 :(得分:0)
为了记录,您还可以使用我接受的SO answer here。
在包含哈希和数组并查找键并返回路径的哈希中进行深度搜索。