web-dev-qa-db-ja.com

Rubyスタイル:ネストされたハッシュ要素が存在するかどうかを確認する方法

ハッシュに保存された「人」を考えてみましょう。 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

それで、特に深くネストされている場合(ここで提供されている例よりもさらに深い)ハッシュの構造をどのようにチェックしますか?たぶんより良い質問は:これを行うための「ルビーの方法」は何ですか?

56
Todd R

これを行う最も明白な方法は、方法の各ステップを単純にチェックすることです。

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?
81
tadman

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)
20
Mario Pérez

別の選択肢:

dino.fetch(:person, {})[:children]
13
Cameron Martin

andand gemを使用できます。

require 'andand'

fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true

詳細な説明は http://and.rubyforge.org/ にあります。

4
paradigmatic

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

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

ただし、Ruby 2.3は、この方法をより優雅にする機能を追加しました。

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

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

2
DigitalRoss
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を返します。これも役立つ場合があります。

2
bharath

デフォルト値{}のハッシュを使用できます-空のハッシュ。例えば、

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
2
kirushik
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
1
josiah

ここで上記の答えを単純化します:

次のように、値を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]
=> {}

キーの値が存在しない場合、空のハッシュを作成してもかまいません:)

1
Abhi

答えは@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
0
gtournie

&key?の組み合わせを使用できます。これは、O(1)であるDigと比較してO(n)です。 NoMethodError: undefined method `[]' for nil:NilClassなしでアクセスされます

fred[:person]&.key?(:children) //=>true
slate[:person]&.key?(:children)
0
aabiro

モジュールを定義して角括弧メソッドのエイリアスを作成し、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"}}
0
Juan Matias

「Ruby」とはどういうことかわかりませんが(!)、私が書いた KeyDial gemを使うと、基本的に元の構文を変更せずにこれを行うことができます。

has_kids = !dino[:person][:children].nil?

になる:

has_kids = !dino.dial[:person][:children].call.nil?

これは、いくつかのトリックを使用してキーアクセス呼び出しを仲介します。 callで、Digの前のキーをdinoしようとし、エラーが検出されると(そうなると)、nilを返します。 nil?その後、もちろんtrueを返します。

0
Convincible

で試すことができます

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 {}
0
MBO

与えられた

x = {:a => {:b => 'c'}}
y = {}

xおよびyを次のように確認できます。

(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil
0
wedesoft