web-dev-qa-db-ja.com

rubyでネストされたハッシュの要素にアクセスする

私はRubyで書かれた小さなユーティリティを使っています。これはネストされたハッシュを広範囲に使用します。現在、ネストされたハッシュ要素へのアクセスを次のようにチェックしています。

structure = { :a => { :b => 'foo' }}

# I want structure[:a][:b]

value = nil

if structure.has_key?(:a) && structure[:a].has_key?(:b) then
  value = structure[:a][:b]
end

これを行うためのより良い方法はありますか?言いたいことがあります:

value = structure[:a][:b]

:aがnilなどのキーでない場合、structureを取得します。

30
Paul Morie

最近私がこれを行う方法は次のとおりです。

h = Hash.new { |h,k| h[k] = {} }

これにより、不足しているキーのエントリとして新しいハッシュを作成するハッシュが得られますが、キーの第2レベルではnilが返されます。

h['foo'] -> {}
h['foo']['bar'] -> nil

これをネストして、この方法で対処できる複数のレイヤーを追加できます。

h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }

h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil

default_procメソッドを使用して無期限にチェーンすることもできます。

h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }

h['bar'] -> {}
h['tar']['star']['par'] -> {}

上記のコードは、デフォルトプロシージャが同じデフォルトプロシージャで新しいハッシュを作成するハッシュを作成します。そのため、不可視のキーの検索が発生したときにデフォルト値として作成されたハッシュは、同じデフォルトの動作になります。

編集:詳細

Rubyハッシュを使用すると、新しいキーの検索が発生したときのデフォルト値の作成方法を制御できます。指定した場合、この動作はProcオブジェクトとしてカプセル化され、 default_proc および default_proc= メソッドを介して到達可能です。デフォルトのprocは、ブロックを Hash.new に渡すことでも指定できます。

このコードを少し分解してみましょう。これは慣用的なRubyではありませんが、複数行に分けるのは簡単です:

1. recursive_hash = Hash.new do |h, k|
2.   h[k] = Hash.new(&h.default_proc)
3. end

行1は、変数recursive_hashを新しいHashとして宣言し、ブロックを開始してrecursive_hashdefault_procにします。ブロックには2つのオブジェクトが渡されます。h(キールックアップが実行されるHashインスタンス)と、k(ルックアップされるキー)です。

行2は、ハッシュのデフォルト値を新しいHashインスタンスに設定します。このハッシュのデフォルトの動作は、ルックアップが発生しているハッシュのdefault_procから作成されたProcを渡すことで提供されます。すなわち、ブロック自体が定義しているデフォルトのプロシージャ。

IRBセッションの例を次に示します。

irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}

recursive_hash[:foo]のハッシュが作成されると、default_procrecursive_hashdefault_procによって提供されました。これには2つの効果があります。

  1. recursive_hash[:foo]のデフォルトの動作は、recursive_hashと同じです。
  2. recursive_hash[:foo]default_procによって作成されたハッシュのデフォルトの動作は、recursive_hashと同じです。

したがって、IRBを継続すると、次の結果が得られます。

irb(main):016:0> recursive_hash[:foo][:bar]
=> {}
irb(main):017:0> recursive_hash
=> {:foo=>{:bar=>{}}}
irb(main):018:0> recursive_hash[:foo][:bar][:zap]
=> {}
irb(main):019:0> recursive_hash
=> {:foo=>{:bar=>{:zap=>{}}}}
27
Paul Morie

伝統的に、あなたは本当にこのようなことをしなければなりませんでした:

structure[:a] && structure[:a][:b]

ただし、Ruby 2.3はメソッドを追加しました Hash#Dig この方法をより優雅にします​​:

structure.Dig :a, :b # nil if it misses anywhere along the way

Ruby_Dig というgemがあり、これをバックパッチします。

38
DigitalRoss

Ruby 2.3.0では、この問題を完全に解決するDigHashの両方に Arrayと呼ばれる新しいメソッド が導入されました。

value = structure.Dig(:a, :b)

いずれかのレベルでキーが欠落している場合、nilを返します。

Ruby 2.3より古いバージョンを使用している場合、Ruby_Dig gemまたは自分で実装する:

module RubyDig
  def Dig(key, *rest)
    if value = (self[key] rescue nil)
      if rest.empty?
        value
      elsif value.respond_to?(:Dig)
    value.Dig(*rest)
      end
    end
  end
end

if Ruby_VERSION < '2.3'
  Array.send(:include, RubyDig)
  Hash.send(:include, RubyDig)
end
34
user513951

私はこれのためにrubygemを作りました。 Vine を試してください。

インストール:

gem install Vine

使用法:

hash.access("a.b.c")
14
Cheng

最も読みやすい解決策の1つは Hashie を使用していると思います:

require 'hashie'
myhash = Hashie::Mash.new({foo: {bar: "blah" }})

myhash.foo.bar
=> "blah"    

myhash.foo?
=> true

# use "underscore dot" for multi-level testing
myhash.foo_.bar?
=> true
myhash.foo_.huh_.what?
=> false
7
Javid Jamae
value = structure[:a][:b] rescue nil
3
fl00r

ソリューション1

前に私の質問でこれを提案しました:

class NilClass; def to_hash; {} end end

Hash#to_hashはすでに定義されており、自己を返します。その後、次のことができます。

value = structure[:a].to_hash[:b]

to_hashは、前のキー検索が失敗したときに空のハッシュを取得することを保証します。

Solution2

この解決策は、サブクラスを使用するという点でmuの精神に似ていますが、それでも多少異なります。特定のキーに値がない場合、デフォルト値を使用せず、空のハッシュの値を作成するため、DigitalRossの回答が指摘しているように、混乱の問題を抱えていませんmuが短すぎます。

class NilFreeHash < Hash
  def [] key; key?(key) ? super(key) : self[key] = NilFreeHash.new end
end

structure = NilFreeHash.new
structure[:a][:b] = 3
p strucrture[:a][:b] # => 3

ただし、質問で指定された仕様からは逸脱しています。未定義のキーが指定されると、nilの空のハッシュインスタンスが返されます。

p structure[:c] # => {}

このNilFreeHashのインスタンスを最初から構築し、Key-Valueを割り当てると機能しますが、ハッシュをこのクラスのインスタンスに変換する場合は問題になる可能性があります。

2
sawa
require 'xkeys'

structure = {}.extend XKeys::Hash
structure[:a, :b] # nil
structure[:a, :b, :else => 0] # 0 (contextual default)
structure[:a] # nil, even after above
structure[:a, :b] = 'foo'
structure[:a, :b] # foo
1
Brian K

このHash用のモンキーパッチ関数は(少なくとも私にとっては)最も簡単なはずです。また、構造を変更しません。つまり、nil{}に変更します。生のソースからツリーを読んでいる場合でも、それはまだ適用されます。 JSON。また、空のハッシュオブジェクトを生成したり、文字列を解析したりする必要もありません。 rescue nilは、このような低リスクに対して十分な勇気を持っているので、実際には私にとって良い簡単な解決策でしたが、本質的にパフォーマンスに欠点があることがわかりました。

class ::Hash
  def recurse(*keys)
    v = self[keys.shift]
    while keys.length > 0
      return nil if not v.is_a? Hash
      v = v[keys.shift]
    end
    v
  end
end

例:

> structure = { :a => { :b => 'foo' }}
=> {:a=>{:b=>"foo"}}

> structure.recurse(:a, :b)
=> "foo"

> structure.recurse(:a, :x)
=> nil

また、保存された配列を使用して再生できることも優れています。

> keys = [:a, :b]
=> [:a, :b]

> structure.recurse(*keys)
=> "foo"

> structure.recurse(*keys, :x1, :x2)
=> nil
1
konsolebox

途中で適切なチェックを行って掘り下げるための特別な可変メソッドを備えたHashサブクラスを構築することができます。このようなもの(もちろん、より良い名前で):

class Thing < Hash
    def find(*path)
        path.inject(self) { |h, x| return nil if(!h.is_a?(Thing) || h[x].nil?); h[x] }
    end
end

次に、ハッシュの代わりにThingsを使用します。

>> x = Thing.new
=> {}
>> x[:a] = Thing.new
=> {}
>> x[:a][:b] = 'k'
=> "k"
>> x.find(:a)
=> {:b=>"k"}
>> x.find(:a, :b)
=> "k"
>> x.find(:a, :b, :c)
=> nil
>> x.find(:a, :c, :d)
=> nil
1
mu is too short

私は現在これを試しています:

# --------------------------------------------------------------------
# System so that we chain methods together without worrying about nil
# values (a la Objective-c).
# Example:
#   params[:foo].try?[:bar]
#
class Object
  # Returns self, unless NilClass (see below)
  def try?
    self
  end
end  
class NilClass
  class MethodMissingSink
    include Singleton
    def method_missing(meth, *args, &block)
    end
  end
  def try?
    MethodMissingSink.instance
  end
end

私はtryに対する引数を知っていますが、たとえばparamsのようなものを調べるときに役立ちます。

0
Jaime Cham

私の場合、各セルがアイテムのリストである2次元マトリックスが必要でした。

私はこのテクニックを見つけました。 OPで機能する場合があります。

$all = Hash.new()

def $all.[](k)
  v = fetch(k, nil)
  return v if v

  h = Hash.new()
  def h.[](k2)
    v = fetch(k2, nil)
    return v if v
    list = Array.new()
    store(k2, list)
    return list
  end

  store(k, h)
  return h
end

$all['g1-a']['g2-a'] << '1'
$all['g1-a']['g2-a'] << '2'

$all['g1-a']['g2-a'] << '3'
$all['g1-a']['g2-b'] << '4'

$all['g1-b']['g2-a'] << '5'
$all['g1-b']['g2-c'] << '6'

$all.keys.each do |group1|
  $all[group1].keys.each do |group2|
    $all[group1][group2].each do |item|
      puts "#{group1} #{group2} #{item}"
    end
  end
end

出力は次のとおりです。

$ Ruby -v && Ruby t.rb
Ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]
g1-a g2-a 1
g1-a g2-a 2
g1-a g2-a 3
g1-a g2-b 4
g1-b g2-a 5
g1-b g2-c 6
0
JohnA

andand gemを使用できますが、次第に注意を払っています。

>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil
0
Michael Kohl

これを行うにはかわいいが間違った方法があります。 NilClassを返す[]メソッドを追加するには、nilをモンキーパッチします。他のソフトウェアが別のバージョンを作成した可能性があるか、またはRubyの将来のバージョンでどの動作が変更される可能性があるかわからないため、これは間違ったアプローチです。

より良いアプローチは、nilに非常によく似ているが、この動作をサポートする新しいオブジェクトを作成することです。この新しいオブジェクトをハッシュのデフォルトの戻り値にします。そして、それはちょうど動作します。

または、ハッシュとキーを渡す単純な「ネストされたルックアップ」関数を作成し、ハッシュを順番にトラバースし、可能な場合にブレークアウトすることができます。

個人的には、後者の2つのアプローチのいずれかを好みます。最初のものがRuby言語に統合されていればかわいいと思います。しかし、モンキーパッチは悪い考えです。それをしないでください。あなたは。)

0
btilly

私がそれをするというわけではありませんが、NilClass#[]

> structure = { :a => { :b => 'foo' }}
#=> {:a=>{:b=>"foo"}}

> structure[:x][:y]
NoMethodError: undefined method `[]' for nil:NilClass
        from (irb):2
        from C:/Ruby/bin/irb:12:in `<main>'

> class NilClass; def [](*a); end; end
#=> nil

> structure[:x][:y]
#=> nil

> structure[:a][:y]
#=> nil

> structure[:a][:b]
#=> "foo"

@DigitalRossの回答をご覧ください。はい、タイピングはより多くなりますが、それはより安全だからです。

0
Phrogz