Ruby Style: как проверить, существует ли вложенный хеш-элемент - PullRequest
55 голосов
/ 30 ноября 2009

Рассмотрим "человека", хранящегося в хэше. Два примера:

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

Итак, как бы вы проверили структуру хэша, особенно если он глубоко вложен (даже глубже, чем приведенные здесь примеры)? Может быть, лучше спросить: что такое «Рубиновый способ» сделать это?

Ответы [ 16 ]

79 голосов
/ 30 ноября 2009

Самый очевидный способ сделать это - просто проверить каждый шаг пути:

has_children = slate[:person] && slate[:person][:children]

Использование .nil? действительно требуется, только когда вы используете false в качестве значения заполнителя, и на практике это происходит редко. Как правило, вы можете просто проверить его существование.

Обновление : Если вы используете Ruby 2.3 или более позднюю версию, есть встроенный метод dig, который выполняет действия, описанные в этом ответе.

Если нет, вы также можете определить свой собственный метод хеширования, который может существенно упростить это:

class Hash
  def dig(*path)
    path.inject(self) do |location, key|
      location.respond_to?(:keys) ? location[key] : nil
    end
  end
end

Этот метод будет проверять каждый шаг пути и избегать срабатывания при вызовах на ноль. Для мелких структур полезность несколько ограничена, но для глубоко вложенных структур я нахожу это бесценным:

has_children = slate.dig(:person, :children)

Вы также можете сделать это более надежным, например, проверяя, действительно ли заполнена запись: children:

children = slate.dig(:person, :children)
has_children = children && !children.empty?
20 голосов
/ 13 ноября 2015

В Ruby 2.3 у нас будет поддержка оператора безопасной навигации: https://www.ruby -lang.org / ен / Новости / 2015/11 / 11 / рубин-2-3-0-preview1 выпущен /

has_children теперь можно записать как:

has_children = slate[:person]&.[](:children)

dig также добавляется:

has_children = slate.dig(:person, :children)
13 голосов
/ 14 апреля 2014

Другая альтернатива:

dino.fetch(:person, {})[:children]
4 голосов
/ 30 ноября 2009

Вы можете использовать камень andand:

require 'andand'

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

Вы можете найти дальнейшие объяснения в http://andand.rubyforge.org/.

2 голосов
/ 06 сентября 2016

Традиционно вам действительно приходилось делать что-то вроде этого:

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

Однако в Ruby 2.3 добавлена ​​функция, которая делает этот способ более изящным:

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

Существует камень под названием ruby_dig, который исправит это для вас.

2 голосов
/ 30 ноября 2009

Можно использовать хеш со значением по умолчанию {} - пустой хеш. Например,

dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false

Это работает и с уже созданным Hash:

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
1 голос
/ 16 декабря 2016
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

Это сведет все хеши в один, а затем любой? возвращает true, если существует какой-либо ключ, соответствующий подстроке "children". Это также может помочь.

1 голос
/ 31 мая 2016

Упрощение ответов выше здесь:

Создайте метод рекурсивного хеширования, значение которого не может быть равно нулю, как показано ниже.

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 голос
/ 04 августа 2015

Вот способ, которым вы можете выполнить глубокую проверку любых ложных значений в хэше и любых вложенных хэшей, не применяя обезьяны для исправления класса Ruby Hash (ПОЖАЛУЙСТА, не производите исправления обезьяны для классов 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 голос
/ 30 ноября 2009
dino_has_children = !dino.fetch(person, {})[:children].nil?

Обратите внимание, что в рельсах вы также можете сделать:

dino_has_children = !dino[person].try(:[], :children).nil?   # 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...