हैश डिफ़ॉल्ट मान, जैसे Hash.new ([]) का उपयोग करते समय अजीब, अप्रत्याशित व्यवहार (मूल्य बदलते / बदलते)


107

इस कोड पर विचार करें:

h = Hash.new(0)  # New hash pairs will by default have 0 as values
h[1] += 1  #=> {1=>1}
h[2] += 2  #=> {2=>2}

यह सब ठीक है, लेकिन:

h = Hash.new([])  # Empty array as default value
h[1] <<= 1  #=> {1=>[1]}                  ← Ok
h[2] <<= 2  #=> {1=>[1,2], 2=>[1,2]}      ← Why did `1` change?
h[3] << 3   #=> {1=>[1,2,3], 2=>[1,2,3]}  ← Where is `3`?

इस बिंदु पर मुझे उम्मीद है कि हैश:

{1=>[1], 2=>[2], 3=>[3]}

लेकिन यह उससे बहुत दूर है। क्या हो रहा है और मुझे वह व्यवहार कैसे मिल सकता है जिसकी मुझे उम्मीद है?

जवाबों:


164

सबसे पहले, ध्यान दें कि यह व्यवहार किसी भी डिफ़ॉल्ट मान पर लागू होता है जो बाद में उत्परिवर्तित होता है (जैसे हैश और स्ट्रिंग्स), न कि केवल सरणियाँ।

TL; DR : Hash.new { |h, k| h[k] = [] }यदि आप सबसे मुहावरेदार समाधान चाहते हैं तो इसका उपयोग करें और इसकी परवाह न करें।


क्या काम नहीं करता

Hash.new([])काम क्यों नहीं करता

आइए अधिक गहराई से देखें कि Hash.new([])काम क्यों नहीं करता है:

h = Hash.new([])
h[0] << 'a'  #=> ["a"]
h[1] << 'b'  #=> ["a", "b"]
h[1]         #=> ["a", "b"]

h[0].object_id == h[1].object_id  #=> true
h  #=> {}

हम देख सकते हैं कि हमारी डिफ़ॉल्ट वस्तु का पुन: उपयोग और परिवर्तन किया जा रहा है (यह इसलिए है क्योंकि इसे एक और केवल डिफ़ॉल्ट मान के रूप में पारित किया गया है, हैश का नया, नया डिफ़ॉल्ट मान प्राप्त करने का कोई तरीका नहीं है), लेकिन कोई कुंजी या मान क्यों नहीं हैं सरणी में, h[1]अभी भी हमें एक मूल्य देने के बावजूद ? यहाँ एक संकेत है:

h[42]  #=> ["a", "b"]

प्रत्येक []कॉल द्वारा लौटाया गया सरणी केवल डिफ़ॉल्ट मान है, जिसे हम इस समय बदल रहे हैं ताकि अब हमारे नए मूल्य शामिल हों। चूंकि <<हैश को असाइन नहीं करता है (वहाँ कभी नहीं एक के बिना रूबी में काम किया जा सकता है =वर्तमान ), हम कभी नहीं हमारी वास्तविक हैश में कुछ भी रख दिया है। इसके बजाय हमें उपयोग करना होगा <<=(जो कि <<जैसा +=है +):

h[2] <<= 'c'  #=> ["a", "b", "c"]
h             #=> {2=>["a", "b", "c"]}

यह इस प्रकार है:

h[2] = (h[2] << 'c')

Hash.new { [] }काम क्यों नहीं करता

का उपयोग करते हुए Hash.new { [] }हल पुन: उपयोग और मूल डिफ़ॉल्ट मान परिवर्तनशील (के रूप में दिए गए ब्लॉक हर बार कहा जाता है, एक नई सरणी लौटने) की समस्या है, लेकिन नहीं काम समस्या:

h = Hash.new { [] }
h[0] << 'a'   #=> ["a"]
h[1] <<= 'b'  #=> ["b"]
h             #=> {1=>["b"]}

क्या काम करता है

असाइनमेंट का तरीका

हम हमेशा उपयोग करने के लिए याद है <<=, तो Hash.new { [] } है एक व्यवहार्य समाधान है, लेकिन यह थोड़ा अजीब और गैर मुहावरेदार (मैं कभी नहीं देखा है है <<=जंगली में प्रयुक्त)। यह भी सूक्ष्म कीड़े के लिए प्रवण है अगर<< अनजाने में उपयोग किए ।

परस्पर चलने का तरीका

के लिए दस्तावेज़Hash.new राज्यों (जोर मेरा अपना):

यदि कोई ब्लॉक निर्दिष्ट किया जाता है, तो इसे हैश ऑब्जेक्ट और कुंजी के साथ बुलाया जाएगा, और डिफ़ॉल्ट मान वापस करना चाहिए। आवश्यकता होने पर मूल्य को हैश में संग्रहीत करना ब्लॉक की जिम्मेदारी है

यदि हम <<इसके बजाय उपयोग करना चाहते हैं तो हमें ब्लॉक के भीतर से हैश में डिफ़ॉल्ट मान संग्रहीत करना चाहिए <<=:

h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a'  #=> ["a"]
h[1] << 'b'  #=> ["b"]
h            #=> {0=>["a"], 1=>["b"]}

यह प्रभावी रूप से हमारे व्यक्तिगत कॉल से असाइनमेंट को स्थानांतरित करता है (जो उपयोग करेगा <<=) पास करने Hash.newके लिए अनपेक्षित व्यवहार के बोझ को हटाते हुए, पास हो गया <<

ध्यान दें कि इस पद्धति और अन्य के बीच एक कार्यात्मक अंतर है: यह तरीका पढ़ने पर डिफ़ॉल्ट मान प्रदान करता है (जैसा कि असाइनमेंट हमेशा ब्लॉक के अंदर होता है)। उदाहरण के लिए:

h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1  #=> {:x=>[]}

h2 = Hash.new { [] }
h2[:x]
h2  #=> {}

अपरिवर्तनीय तरीका है

आप सोच रहे होंगे कि ठीक Hash.new([])काम क्यों नहीं Hash.new(0)करता है। कुंजी यह है कि रूबी में न्यूमेरिक्स अपरिवर्तनीय हैं, इसलिए हम स्वाभाविक रूप से उन्हें कभी-कभी उत्परिवर्तित नहीं करते हैं। यदि हमने अपने डिफ़ॉल्ट मान को अपरिवर्तनीय माना है, तो हम Hash.new([])भी ठीक का उपयोग कर सकते हैं :

h = Hash.new([].freeze)
h[0] += ['a']  #=> ["a"]
h[1] += ['b']  #=> ["b"]
h[2]           #=> []
h              #=> {0=>["a"], 1=>["b"]}

हालाँकि, ध्यान दें ([].freeze + [].freeze).frozen? == false। इसलिए, यदि आप यह सुनिश्चित करना चाहते हैं कि अपरिवर्तनीयता पूरी तरह से संरक्षित है, तो आपको नई वस्तु को फिर से स्थिर करने का ध्यान रखना चाहिए।


निष्कर्ष

सभी तरीकों में से, मैं व्यक्तिगत रूप से "अपरिवर्तनीय तरीका" पसंद करता हूं - सामर्थ्य आमतौर पर चीजों को बहुत सरल बनाने के लिए तर्क देता है। यह सब के बाद, एकमात्र तरीका है जिसमें छिपे या सूक्ष्म अप्रत्याशित व्यवहार की कोई संभावना नहीं है। हालांकि, सबसे आम और मुहावरेदार तरीका है "परस्पर मार्ग"।

एक तरफ अंतिम रूप में, हश डिफ़ॉल्ट मानों के इस व्यवहार को रूबी कोन्स में नोट किया गया है ।


But यह कड़ाई से सही नहीं है, instance_variable_setइस तरह के तरीकों को बायपास किया जाता है, लेकिन वे मेटाप्रोग्रामिंग के लिए मौजूद होना चाहिए क्योंकि एल-मान =गतिशील नहीं हो सकता है।


1
यह उल्लेख करता है कि "म्यूटेबल तरीके" का उपयोग करने से हर हैश लुकअप को एक महत्वपूर्ण मूल्य जोड़ी (क्योंकि ब्लॉक में एक असाइनमेंट हो रहा है) स्टोर करने का प्रभाव पड़ता है, जो हमेशा वांछित नहीं हो सकता है।
जौनीपील

@ जॉनीपील्स हर लुकअप नहीं , सिर्फ प्रत्येक कुंजी के लिए पहला। लेकिन मैं देख रहा हूं कि आपका क्या मतलब है, मैं इसे बाद में जवाब में जोड़ दूंगा; धन्यवाद!।
एंड्रयू मार्शल

वूप्स, मैला हो रहा है। आप सही कह रहे हैं, यह अज्ञात कुंजी की पहली खोज है। मैं लगभग की तरह महसूस { [] }के साथ <<=सबसे कम आश्चर्य है, यह सच है कि गलती से भूल के लिए नहीं थे =एक बहुत ही भ्रामक डिबगिंग सत्र को जन्म दे सकता।
जौनीपील

मतभेदों के बारे में बहुत स्पष्ट स्पष्टीकरण जब डिफ़ॉल्ट मानों के साथ हैश की
शुरुआत

23

आप निर्दिष्ट कर रहे हैं कि हैश के लिए डिफ़ॉल्ट मान उस विशेष (प्रारंभिक रूप से खाली) सरणी का संदर्भ है।

मुझे लगता है कि आप चाहते हैं:

h = Hash.new { |hash, key| hash[key] = []; }
h[1]<<=1 
h[2]<<=2 

जो प्रत्येक कुंजी के लिए एक नए सरणी के लिए डिफ़ॉल्ट मान सेट करता है ।


मैं प्रत्येक नए हैश के लिए अलग सरणी उदाहरणों का उपयोग कैसे कर सकता हूं?
वैलेंटाइन वासिलीव

5
वह ब्लॉक संस्करण आपको Arrayप्रत्येक आह्वान पर नए उदाहरण देता है । बुद्धि से h = Hash.new { |hash, key| hash[key] = []; puts hash[key].object_id }; h[1] # => 16348490; h[2] # => 16346570:। इसके अलावा: यदि आप उस ब्लॉक संस्करण का उपयोग करते हैं, जो केवल मान उत्पन्न करने वाले ( ) के बजाय मान ( ) सेट करता है , तो आपको केवल तत्वों को जोड़ने की आवश्यकता होती है , न कि तत्वों को जोड़ने की। {|hash,key| hash[key] = []}{ [] }<<<<=
जेम्स ए। रोसेन

3

ऑपरेटर +=जब उन हैश के लिए लागू होता है, तो उम्मीद के मुताबिक काम करते हैं।

[1] pry(main)> foo = Hash.new( [] )
=> {}
[2] pry(main)> foo[1]+=[1]
=> [1]
[3] pry(main)> foo[2]+=[2]
=> [2]
[4] pry(main)> foo
=> {1=>[1], 2=>[2]}
[5] pry(main)> bar = Hash.new { [] }
=> {}
[6] pry(main)> bar[1]+=[1]
=> [1]
[7] pry(main)> bar[2]+=[2]
=> [2]
[8] pry(main)> bar
=> {1=>[1], 2=>[2]}

ऐसा इसलिए हो सकता है क्योंकि जब इसके दाहिने हाथ पर मूल्यांकन किया जाता है तो इसके foo[bar]+=bazलिए सिंटैक्टिक शुगर होता है, यह डिफ़ॉल्ट मान ऑब्जेक्ट लौटाता है और ऑपरेटर इसे नहीं बदलेगा। बायाँ हाथ इस विधि के लिए वाक्यगत शर्करा है जो डिफ़ॉल्ट मान को नहीं बदलेगाfoo[bar]=foo[bar]+bazfoo[bar]=+[]=

ध्यान दें कि यह लागू नहीं होता है foo[bar]<<=bazक्योंकि यह इसके बराबर होगाfoo[bar]=foo[bar]<<baz और डिफ़ॉल्ट मान << को बदल देगा

इसके अलावा, मैं Hash.new{[]}और के बीच कोई अंतर नहीं पाया Hash.new{|hash, key| hash[key]=[];}। कम से कम रूबी 2.1.2 पर।


अच्छी व्याख्या। ऐसा लगता है कि माणिक 2.1.1 पर मेरे लिए Hash.new{[]}वैसा ही है जैसा Hash.new([])अपेक्षित <<व्यवहार की कमी (हालांकि Hash.new{|hash, key| hash[key]=[];}काम करता है)। अजीब छोटी चीजें सभी चीजों को तोड़ रही हैं: /
बटरवायम्बैट

1

जब आप लिखते हैं,

h = Hash.new([])

आप हैश में सभी तत्वों के लिए सरणी का डिफ़ॉल्ट संदर्भ पास करते हैं। क्योंकि हैश में सभी तत्व समान सरणी को संदर्भित करते हैं।

यदि आप हैश में प्रत्येक तत्व को अलग सरणी के लिए संदर्भित करते हैं, तो आपको उपयोग करना चाहिए

h = Hash.new{[]} 

रूबी में यह कैसे काम करता है, इसके अधिक विवरण के लिए कृपया इसके माध्यम से जाएं: http://ruby-doc.org/core-2.2.0/Array.html#method-c-new


यह गलत है, Hash.new { [] }काम नहीं करता है। देखें मेरा उत्तर जानकारी के लिए। यह पहले से ही एक और जवाब में प्रस्तावित समाधान है।
एंड्रयू मार्शल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.