Improve this recursive function to bypass hashes in Ruby

I wrote a method to turn a hash (nested if necessary) of values ​​into a chain that can be used with eval to dynamically return values ​​from an object.

eg. passed a hash, for example {: user => {: club =>: title}}, it will return "user.club.title", which I can then evaluate. (The purpose of this is to write a method for views that allows me to quickly delete the contents of objects by passing in the object and a list of attributes that I want to display, for example: item_row (@user ,: name ,: email, {: club =>: title})

Here is what I have. It works, but I know that it can be improved. Curious to see how you improve it.

# 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)
+3
8

, , 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], . ( , # to_proc), :

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

< 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"
  • .join('.'), .
  • , Hash, , #to_s Array # join ('.').

- , 1 . Btw, , [: club,: title,: owner], , .

Cheers,
    zimbatm

+4

zimbatm code-golfy , .

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"

, , -, 64 :

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

- , , :

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
+1

, Ruby 1.9 Hash # flatten (level), (, ). "."

: 1.9, - , -, . , .

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!, car/cdr.

:

, , . ( Facets # ):

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"
0
myVal = data.try(:[],'user').try(:[],'club').try(:[],'title')

:)

0

For the record you can also use my accepted SO answer here .

This does a deep search in the hash consisting of hashes and arrays, and searches for the key and returns the path.

0
source

Source: https://habr.com/ru/post/1726998/


All Articles