43

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

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]

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

4

15 に答える 15

60

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

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

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

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

ruby_digこれをバックパッチするという宝石があります。

于 2011-04-04T22:15:23.830 に答える
43

Hashこの問題を完全に解決するArrayというメソッドdigがあります。

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

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

2.3より前のバージョンのRubyを使用している場合は、ruby_diggemを使用できます。

于 2016-01-06T03:02:05.257 に答える
31

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

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'] -> {}

上記のコードは、デフォルトのprocが同じデフォルトのprocで新しいハッシュを作成するハッシュを作成します。したがって、見えないキーのルックアップが発生したときにデフォルト値として作成されたハッシュは、同じデフォルトの動作になります。

編集:詳細

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

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

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

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

2行目では、ハッシュのデフォルト値を新しいHashインスタンスに設定しています。このハッシュのデフォルトの動作は、ルックアップが発生しているハッシュProcから作成されたものを渡すことによって提供されます。default_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]が作成されたとき、それは'sdefault_procによって提供されました。これには2つの効果があります。recursive_hashdefault_proc

  1. のデフォルトの動作recursive_hash[:foo]は。と同じrecursive_hashです。
  2. recursive_hash[:foo]'sによって作成されたハッシュのデフォルトの動作は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=>{}}}}
于 2013-06-28T02:58:48.367 に答える
14

このためにrubygemを作りました。つるを試してみてください。

インストール:

gem install vine

使用法:

hash.access("a.b.c")
于 2011-12-08T14:15:59.577 に答える
7

最も読みやすい解決策の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
于 2013-12-18T01:07:31.103 に答える
3
value = structure[:a][:b] rescue nil
于 2011-04-04T22:16:50.853 に答える
2

途中で適切なチェックを行って掘り下げるための追加の可変個引数メソッドを使用して、ハッシュサブクラスを構築することができます。このようなもの(もちろんより良い名前で):

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

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

>> 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
于 2011-04-04T23:16:55.987 に答える
2

解決策1

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

class NilClass; def to_hash; {} end end

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

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のインスタンスを最初から作成し、キー値を割り当てると機能しますが、ハッシュをこのクラスのインスタンスに変換する場合は、問題が発生する可能性があります。

于 2011-04-05T03:40:36.753 に答える
1

XKeys gemは、ネストされたハッシュ(:: Hash)またはハッシュと配列(:: Auto、キー/インデックスタイプに基づく)を読み取り、自動で活性化します。 #[]および#[]=。番兵記号:[]は配列の最後にプッシュします。

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
于 2014-04-08T22:37:03.770 に答える
1

ハッシュ用のこのモンキーパッチ関数は(少なくとも私にとっては)最も簡単なはずです。また、構造を変更しません。つまり、nil'sをに変更し{}ます。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
于 2014-07-05T17:39:30.337 に答える
0

andand gemを使用できますが、私はますます警戒するようになっています。

>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil
于 2011-04-04T22:13:47.727 に答える
0

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

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

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

私は個人的に後者の2つのアプローチのいずれかを好みます。最初のものがRuby言語に統合されていればかわいいと思いますが。(しかし、モンキーパッチは悪い考えです。そうしないでください。特に、あなたがどんなにクールなハッカーであるかを示すためではありません。)

于 2011-04-04T22:15:04.017 に答える
0

私がやるわけではありませんが、モンキーパッチを適用できます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の答えを参考にしてください。はい、タイピングの方が多いですが、それはより安全だからです。

于 2011-04-04T22:16:27.843 に答える
0

私の場合、各セルがアイテムのリストである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
于 2012-12-28T21:11:57.023 に答える
0

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

# --------------------------------------------------------------------
# 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ます。

于 2013-03-28T15:37:40.357 に答える