получить хэш-значение ruby ​​по ключам - PullRequest
3 голосов
/ 04 марта 2012

У меня есть Hash вроде этого, представляющий дерево данных

hash = {
    'key1' => {
         'sub1' => 1,
         'sub2' => 2
    },
    'key2' => 3
}

Я хочу исследовать дерево с массивом ключей, представляющих путь. Некоторые примеры:

с простым путем:

keys = ['key2']

Я хочу получить 3

с этим путем:

keys = ['key1', 'sub1']

Я хочу получить 1

с неверным путем:

keys = ['key1', 'sub1', 'blabla']
keys = ['key1', 'blabla']

получить nil

и т.д ... и т.д ... вы поняли

Ответы [ 5 ]

7 голосов
/ 04 марта 2012
keys.inject(hash) {|acc, value| acc[value]}
4 голосов
/ 04 марта 2012

Не проверяет ошибки, но

h = {'k1' => {'s1' => 1, 's2' => 2}, 'k2' => 3}
ks = ['k1', 's1']
ks.inject(h){|hh, k| hh[k]}  # => 1
['k1', 's2'].inject(h){|hh, k| hh[k]} # => 2
['k2'].inject(h){|hh, k| hh[k]} # => 3
2 голосов
/ 04 марта 2012

Лучше помнить об объекте, поэтому давайте добавим функцию в класс Hash

# in intialize or whatever
class Hash
  def find_path path #recursive
    key = path.first
    if _path.size == 1 # end of path => return value
      [key]
    elsif [key].kind_of?(Hash) # continue
      [key].find_path path[1..-1]
    else # have to continue but not a has => out
      nil
    end
  end

  def find_path path # not recursive
    _path = path.clone #copy to shift
    _tree = self #start with self
    while(_path.size > 1 && _tree) do #while not the end and _tree 
      _v = _tree[_path.shift]
      _tree = _v.kind_of?(Hash) ? _v : nil
    end
    _tree ? _tree[_path.first] : nil
  end
end

Таким образом:

hash = {:v1 => {:v1.1 => "yes", :v1.2 => "false"}}
hash.find_path [:v1, :v1.1]
# => "yes"
hash.find_path [:v1]
# => {:v1.1 => "yes", :v1.2 => "false"}
hash.find_path [:v2]
# => nil
hash.find_path [:v1, :v1.3]
# => nil
1 голос
/ 04 марта 2012

Вот мое решение (оно похоже на proxygear, но оно не исправляет хеш-патч и работает правильно, когда вызывается без аргументов).Я думаю, что это лучше, чем два решения на основе инъекций, потому что они взорвутся, если вы попытаетесь получить доступ к путям этого DNE.

class TraversibleHash
  attr_accessor :hash

  def initialize(hash)
    self.hash = hash
  end

  def access(*keys)
    recursive_access keys, hash
  end

private

  def recursive_access(keys, hash)
    return nil if keys.empty?
    element = hash.fetch(keys.shift) { return nil }
    return element if keys.empty?
    return nil unless element.kind_of? Hash # <-- should maybe raise an error?
    recursive_access keys, element
  end
end

Вот набор тестов:

describe TraversibleHash do
  let(:traversible) { TraversibleHash.new 'key1' => { 'sub1' => 1, 'sub2' => 2 }, 'key2' => 3, nil => 1 }

  it 'returns nil when invoked without args' do
    traversible.access.should be_nil
  end

  it 'returns nil when accessing nonexistent keys' do
    traversible.access('key3').should be_nil
    traversible.access('key2', 'key3').should be_nil
    traversible.access('key1', 'sub3').should be_nil
    traversible.access('key1', 'sub2', 'subsub1').should be_nil
    traversible.access('abc', 'def', 'ghi').should be_nil
  end

  it 'finds toplevel keys' do
    traversible.access('key2').should == 3
    traversible.access('key1').should == {'sub1' => 1, 'sub2' => 2}
  end

  it 'traverses nested hashes to find nested keys' do
    traversible.access('key1', 'sub1').should == 1
    traversible.access('key1', 'sub2').should == 2
    TraversibleHash.new(1=>{2=>{3=>4}}).access(1, 2, 3).should == 4
  end
end
0 голосов
/ 16 января 2016

Начиная с Ruby 2.3.0, вы можете использовать Hash#dig, что делает именно это:

hash.dig(*keys)

Вы можете использовать камень ruby_dig довы мигрируете.

...