ハッシュに保存された「人」を考えてみましょう。 2つの例は次のとおりです。
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
「人」に子供がいない場合、「子供」要素は存在しません。したがって、スレート氏にとって、彼が両親を持っているかどうかを確認できます。
slate_has_children = !slate[:person][:children].nil?
それで、「スレート」が「人」ハッシュであることを知らない場合はどうでしょうか?考慮してください:
dino = {:pet => {:name => "Dino"}}
もう子供を簡単に確認することはできません。
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
それで、特に深くネストされている場合(ここで提供されている例よりもさらに深い)ハッシュの構造をどのようにチェックしますか?たぶんより良い質問は:これを行うための「ルビーの方法」は何ですか?
これを行う最も明白な方法は、方法の各ステップを単純にチェックすることです。
has_children = slate[:person] && slate[:person][:children]
.nilの使用?実際にプレースホルダー値としてfalseを使用する場合にのみ必要であり、実際にはこれはまれです。一般的に、それが存在するかどうかを簡単にテストできます。
Update:Ruby 2.3以降を使用している場合、組み込みの
Dig
この回答で説明されていることを行うメソッド。
そうでない場合は、独自のハッシュ「Dig」メソッドを定義して、これを大幅に簡素化することもできます。
class Hash
def Dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
このメソッドは、方法の各ステップをチェックし、nilへの呼び出しでトリップするのを防ぎます。浅い構造の場合、ユーティリティは多少制限されますが、深くネストされた構造の場合、非常に貴重です。
has_children = slate.Dig(:person, :children)
また、これをより堅牢にすることもできます。たとえば、:childrenエントリが実際に入力されているかどうかをテストします。
children = slate.Dig(:person, :children)
has_children = children && !children.empty?
Ruby 2.3を使用すると、安全なナビゲーション演算子がサポートされます: https://www.Ruby-lang.org/en/news/2015/11/11/Ruby -2-3-0-preview1-released /
has_children
は次のように書くことができます。
has_children = slate[:person]&.[](:children)
Dig
も追加されています:
has_children = slate.Dig(:person, :children)
別の選択肢:
dino.fetch(:person, {})[:children]
andand
gemを使用できます。
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
詳細な説明は http://and.rubyforge.org/ にあります。
伝統的に、あなたは本当にこのようなことをしなければなりませんでした:
structure[:a] && structure[:a][:b]
ただし、Ruby 2.3は、この方法をより優雅にする機能を追加しました。
structure.Dig :a, :b # nil if it misses anywhere along the way
これをバックパッチするRuby_Dig
というgemがあります。
def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}_#{h_k}"] = h_v
end
else
h[k] = v
end
end
end
irb(main):012:0> fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
=> {:person=>{:name=>"Fred", :spouse=>"Wilma", :children=>{:child=>{:name=>"Pebbles"}}}}
irb(main):013:0> slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
irb(main):014:0> flatten_hash(fred).keys.any? { |k| k.include?("children") }
=> true
irb(main):015:0> flatten_hash(slate).keys.any? { |k| k.include?("children") }
=> false
これにより、すべてのハッシュが1つに平坦化されますか?サブストリング「children」に一致するキーが存在する場合、trueを返します。これも役立つ場合があります。
デフォルト値{}のハッシュを使用できます-空のハッシュ。例えば、
dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false
すでに作成されたハッシュでも同様に機能します:
dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false
または、nilクラスの[]メソッドを定義できます
class NilClass
def [](* args)
nil
end
end
nil[:a] #=> nil
dino_has_children = !dino.fetch(person, {})[:children].nil?
Railsでは以下も実行できます。
dino_has_children = !dino[person].try(:[], :children).nil? #
これは、Rubyハッシュクラスにモンキーパッチを適用せずに、ハッシュおよびネストされたハッシュ内の偽の値を詳細にチェックできる方法です(Rubyクラスにはモンキーパッチを適用しないでください。絶対にすべきではないこと)。
(Railsを想定していますが、Railsの外部で動作するように簡単に変更できます)
def deep_all_present?(hash)
fail ArgumentError, 'deep_all_present? only accepts Hashes' unless hash.is_a? Hash
hash.each do |key, value|
return false if key.blank? || value.blank?
return deep_all_present?(value) if value.is_a? Hash
end
true
end
ここで上記の答えを単純化します:
次のように、値をnilにできない再帰的ハッシュメソッドを作成します。
def recursive_hash
Hash.new {|key, value| key[value] = recursive_hash}
end
> slate = recursive_hash
> slate[:person][:name] = "Mr. Slate"
> slate[:person][:spouse] = "Mrs. Slate"
> slate
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
slate[:person][:state][:city]
=> {}
キーの値が存在しない場合、空のハッシュを作成してもかまいません:)
答えは@tadmanに感謝します。
Perfsが必要な場合(およびRuby <2.3)で立ち往生している場合)、この方法は2.5倍高速です。
unless Hash.method_defined? :Dig
class Hash
def Dig(*path)
val, index, len = self, 0, path.length
index += 1 while(index < len && val = val[path[index]])
val
end
end
end
RubyInline を使用すると、この方法は16倍高速になります。
unless Hash.method_defined? :Dig
require 'inline'
class Hash
inline do |builder|
builder.c_raw '
VALUE Dig(int argc, VALUE *argv, VALUE self) {
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
self = rb_hash_aref(self, *argv);
if (NIL_P(self) || !--argc) return self;
++argv;
return Dig(argc, argv, self);
}'
end
end
end
&
とkey?
の組み合わせを使用できます。これは、O(1)であるDig
と比較してO(n)です。 NoMethodError: undefined method `[]' for nil:NilClass
なしでアクセスされます
fred[:person]&.key?(:children) //=>true
slate[:person]&.key?(:children)
モジュールを定義して角括弧メソッドのエイリアスを作成し、Ruby構文を使用してネストされた要素を読み書きすることもできます。
module Nesty
def []=(*keys,value)
key = keys.pop
if keys.empty?
super(key, value)
else
if self[*keys].is_a? Hash
self[*keys][key] = value
else
self[*keys] = { key => value}
end
end
end
def [](*keys)
self.Dig(*keys)
end
end
class Hash
def nesty
self.extend Nesty
self
end
end
その後、次のことができます。
irb> a = {}.nesty
=> {}
irb> a[:a, :b, :c] = "value"
=> "value"
irb> a
=> {:a=>{:b=>{:c=>"value"}}}
irb> a[:a,:b,:c]
=> "value"
irb> a[:a,:b]
=> {:c=>"value"}
irb> a[:a,:d] = "another value"
=> "another value"
irb> a
=> {:a=>{:b=>{:c=>"value"}, :d=>"another value"}}
「Ruby」とはどういうことかわかりませんが(!)、私が書いた KeyDial gemを使うと、基本的に元の構文を変更せずにこれを行うことができます。
has_kids = !dino[:person][:children].nil?
になる:
has_kids = !dino.dial[:person][:children].call.nil?
これは、いくつかのトリックを使用してキーアクセス呼び出しを仲介します。 call
で、Dig
の前のキーをdino
しようとし、エラーが検出されると(そうなると)、nilを返します。 nil?
その後、もちろんtrueを返します。
で試すことができます
dino.default = {}
または例えば:
empty_hash = {}
empty_hash.default = empty_hash
dino.default = empty_hash
こうすれば電話できます
empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
与えられた
x = {:a => {:b => 'c'}}
y = {}
xおよびyを次のように確認できます。
(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil