루비 스타일 : 중첩 된 해시 요소가 있는지 확인하는 방법
해시에 저장된 "사람"을 고려하십시오. 두 가지 예는 다음과 같습니다.
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
"person"에 자식이 없으면 "children"요소가 없습니다. 따라서 Mr. 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를 사용할 때만 실제로 필요하며 실제로는 드뭅니다. 일반적으로 단순히 존재하는지 테스트 할 수 있습니다.
업데이트 : Ruby 2.3 이상을 사용하는
dig경우이 답변에 설명 된 작업을 수행 하는 기본 제공 메서드가 있습니다.
그렇지 않은 경우이를 상당히 단순화 할 수있는 자체 Hash "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]
andandgem을 사용할 수 있습니다 .
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
http://andand.rubyforge.org/ 에서 자세한 설명을 찾을 수 있습니다 .
기본값 {}-빈 해시로 해시를 사용할 수 있습니다. 예를 들면
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
전통적으로 다음과 같은 작업을 수행해야했습니다.
structure[:a] && structure[:a][:b]
그러나 Ruby 2.3에는 이러한 방식을보다 우아하게 만드는 기능이 추가되었습니다.
structure.dig :a, :b # nil if it misses anywhere along the way
ruby_dig당신을 위해 이것을 백 패치 할 보석 이 있습니다.
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
이것은 모든 해시를 하나로 합친 다음 어떤 것입니까? 하위 문자열 "children"과 일치하는 키가 있으면 true를 반환합니다. 이것은 또한 도움이 될 수 있습니다.
dino_has_children = !dino.fetch(person, {})[:children].nil?
레일에서 다음을 수행 할 수도 있습니다.
dino_has_children = !dino[person].try(:[], :children).nil? #
다음은 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
여기에서 위의 답변을 단순화하십시오.
다음과 같이 값이 nil 일 수없는 Recursive Hash 메서드를 만듭니다.
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]
=> {}
키에 대한 값이 존재하지 않으면 빈 해시를 만들어도 괜찮다면 :)
당신은 함께 놀 수 있습니다
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
대답을 위해 @tadman을 Thks.
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
and if you use RubyInline, this method is 16x faster:
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
You can also define a module to alias the brackets methods and use the Ruby syntax to read/write nested elements.
UPDATE: Instead of overriding the bracket accessors, request Hash instance to extend the module.
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
Then you can do:
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"}}
I don't know how "Ruby" it is(!), but the KeyDial gem which I wrote lets you do this basically without changing your original syntax:
has_kids = !dino[:person][:children].nil?
becomes:
has_kids = !dino.dial[:person][:children].call.nil?
This uses some trickery to intermediate the key access calls. At call, it will try to dig the previous keys on dino, and if it hits an error (as it will), returns nil. nil? then of course returns true.
You can use a combination of & and key? it is O(1) compared to dig which is O(n) and this will make sure person is accessed without NoMethodError: undefined method `[]' for nil:NilClass
fred[:person]&.key?(:children) //=>true
slate[:person]&.key?(:children)
ReferenceURL : https://stackoverflow.com/questions/1820451/ruby-style-how-to-check-whether-a-nested-hash-element-exists
'code' 카테고리의 다른 글
| 위임, 구성 및 집계 구분 (Java OO 디자인) (0) | 2020.12.29 |
|---|---|
| 문자열에 마침표 (0) | 2020.12.29 |
| cglib에 대한 대안이 있습니까? (0) | 2020.12.29 |
| 무효 유효한 코드를 반환합니까? (0) | 2020.12.29 |
| 순전히 기능적인 데이터 구조의 이점은 무엇입니까? (0) | 2020.12.29 |