ruby रूबी में नेस्टेड हैश के तत्वों तक पहुंच




hash hash-of-hashes (12)

मेरे मामले में, मुझे एक द्वि-आयामी मैट्रिक्स की आवश्यकता थी जहां प्रत्येक सेल आइटम की एक सूची है।

मुझे यह तकनीक मिली जो काम करने लगती है। यह ओपी के लिए काम कर सकता है:

$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

मैं रूबी में लिखी गई छोटी उपयोगिता का काम कर रहा हूं जो नेस्टेड हैंश का व्यापक उपयोग करता है। वर्तमान में, मैं निम्नानुसार नेस्टेड हैश तत्वों तक पहुंच की जांच कर रहा हूं:

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 अगर: structure में एक कुंजी नहीं है, आदि


value = structure[:a][:b] rescue nil

हैश के लिए यह बंदर पैच फ़ंक्शन सबसे आसान होना चाहिए (कम से कम मेरे लिए)। यह संरचना को बदलता नहीं है यानी nil बदलना {} । यह तब भी लागू होगा जब आप कच्चे स्रोत जैसे जेएसओएन से पेड़ पढ़ रहे हों। इसे खाली हैश ऑब्जेक्ट्स बनाने की आवश्यकता नहीं है क्योंकि यह स्ट्रिंग को पार्स या पार्स करता है। 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

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

पारंपरिक रूप से, आपको वास्तव में ऐसा कुछ करना था:

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

हालांकि, रुबी 2.3 ने एक फीचर जोड़ा जो इस तरह से अधिक सुंदर बनाता है:

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

ruby_dig नामक एक मणि है जो आपके लिए बैक-पैच करेगा।


मुझे लगता है कि सबसे पठनीय समाधानों में से एक Hashie का उपयोग कर 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

जिस तरह से मैं आमतौर पर इन दिनों ऐसा करता हूं:

h = Hash.new { |h,k| h[k] = {} }

यह आपको एक हैश देगा जो एक लापता कुंजी के लिए प्रवेश के रूप में एक नया हैश बनाता है, लेकिन कुंजी के दूसरे स्तर के लिए शून्य देता है:

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 ऑब्जेक्ट के रूप में encapsulated है और default_proc और default_proc= विधियों के माध्यम से पहुंचा जा सकता है। एक ब्लॉक को Hash.new को पास करके Hash.new को भी निर्दिष्ट किया जा सकता है।

आइए इस कोड को थोड़ा नीचे तोड़ दें। यह मूर्खतापूर्ण रूबी नहीं है, लेकिन इसे कई लाइनों में तोड़ना आसान है:

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

लाइन 1 एक नया Hash होने के लिए एक परिवर्तनीय recursive_hash घोषित करता है और recursive_hash के default_proc होने के लिए ब्लॉक शुरू करता है। ब्लॉक दो वस्तुओं को पारित किया गया है: h , जो Hash उदाहरण है, मुख्य लुकअप पर प्रदर्शन किया जा रहा है, और k , कुंजी को देखा जा रहा है।

लाइन 2 हैश में डिफ़ॉल्ट मान को एक नए Hash उदाहरण में सेट करता है। इस हैश के लिए डिफ़ॉल्ट व्यवहार हैश के डिफ़ॉल्ट_प्रोक से बनाए गए Proc को पास करके आपूर्ति की जा रही है; यानी, डिफ़ॉल्ट प्रो ब्लॉक स्वयं परिभाषित कर रहा है।

आईआरबी सत्र से यहां एक उदाहरण दिया गया है:

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] पर हैश बनाया गया था, तो इसके default_proc को recursive_hash के default_proc द्वारा आपूर्ति की गई थी। इसका दो प्रभाव हैं:

  1. recursive_hash[:foo] लिए डिफ़ॉल्ट व्यवहार recursive_hash[:foo] जैसा ही है।
  2. recursive_hash[:foo] के default_proc द्वारा बनाए गए हैंश के लिए डिफ़ॉल्ट व्यवहार recursive_hash जैसा ही होगा।

तो, आईआरबी में जारी है, हम निम्नलिखित प्राप्त करते हैं:

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=>{}}}}

आप andand मणि का उपयोग कर सकते हैं, लेकिन मैं इसके बारे में अधिक से अधिक सावधान हो रहा हूं:

>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil

मैं वर्तमान में यह कोशिश कर रहा हूं:

# --------------------------------------------------------------------
# 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


रूबी 2.3.0 ने Hash और Array दोनों पर dig एक नई विधि पेश की जो इस समस्या को पूरी तरह हल करती है।

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

अगर किसी भी स्तर पर कुंजी गुम हो जाती है तो यह nil हो जाती है।

यदि आप 2.3 से पुराने रूबी के संस्करण का उपयोग कर रहे हैं, तो आप ruby_dig मणि का उपयोग कर सकते हैं या इसे स्वयं लागू कर सकते हैं:

module RubyDig
  def dig(key, *rest)
    if value = (self[key] rescue nil)
      if rest.empty?
        value
      elsif value.respond_to?(:dig)
    value.dig(*rest)
      end
    end
  end
end

if RUBY_VERSION < '2.3'
  Array.send(:include, RubyDig)
  Hash.send(:include, RubyDig)
end

ऐसा नहीं है कि मैं इसे करूँगा, लेकिन आप NilClass#[] में NilClass#[] कर सकते हैं 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 के उत्तर के साथ जाओ। हां, यह अधिक टाइपिंग है, लेकिन ऐसा इसलिए है क्योंकि यह सुरक्षित है।


आप रास्ते में उपयुक्त चेक के साथ सभी तरह से खोदने के लिए एक अतिरिक्त विविध विधि के साथ एक हैश सबक्लास बना सकते हैं। ऐसा कुछ (कोर्स के बेहतर नाम के साथ):

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 एस का उपयोग करें:

>> 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




hash-of-hashes