2012-10-24 15 views
21

specification के अनुसार, हैश की कुंजी के रूप में उपयोग किए जाने वाले तार डुप्लीकेट और जमे हुए हैं। अन्य उत्परिवर्तनीय वस्तुओं में ऐसा विशेष विचार प्रतीत नहीं होता है। उदाहरण के लिए, एक सरणी कुंजी के साथ, निम्नलिखित संभव है।हैश जमे हुए के लिए स्ट्रिंग कुंजी क्यों है?

a = [0] 
h = {a => :a} 
h.keys.first[0] = 1 
h # => {[1] => :a} 
h[[1]] # => nil 
h.rehash 
h[[1]] # => :a 

दूसरी तरफ, एक स्ट्रिंग कुंजी के साथ एक ही चीज़ नहीं की जा सकती है।

s = "a" 
h = {s => :s} 
h.keys.first.upcase! # => RuntimeError: can't modify frozen String 

स्ट्रिंग क्यों हैश कुंजी की बात करते समय अन्य परिवर्तनीय वस्तुओं से भिन्न होने के लिए डिज़ाइन किया गया है? क्या कोई उपयोग केस है जहां यह विनिर्देश उपयोगी हो जाता है? इस विनिर्देश के अन्य परिणामों के क्या हैं?


मेरे पास वास्तव में एक उपयोग केस है जहां तारों के बारे में इस तरह के विशेष विनिर्देश की अनुपस्थिति उपयोगी हो सकती है। यही है, मैंने yaml मणि के साथ मैन्युअल रूप से लिखी गई YAML फ़ाइल के साथ पढ़ा है जो हैश का वर्णन करता है। कुंजी तार हो सकती है, और मैं मूल वाईएएमएल फ़ाइल में केस असंवेदनशीलता की अनुमति देना चाहता हूं। जब मैं एक फ़ाइल को पढ़ने, मैं इस तरह एक हैश मिल सकता है:

h = {"foo" => :foo, "Bar" => :bar, "BAZ" => :baz} 

और मैं लोअर केस में इसे पाने के लिए कुंजी को सामान्य करना चाहते हैं: कुछ इस तरह कर रही द्वारा

h = {"foo" => :foo, "bar" => :bar, "baz" => :baz} 

:

h.keys.each(&:downcase!) 

लेकिन यह ऊपर बताए गए कारण के लिए एक त्रुटि देता है।

+0

ऐसा लगता है, मेरे उद्देश्य के लिए, मैं सबसे अच्छा कर सकता हूं 'h.keys.each {| s | एच। स्टोर (एस। डीलकेस, एच। डिलीट (एस))} '। – sawa

+0

मैं केवल "क्यों" पर अनुमान लगा सकता हूं। साथ ही स्ट्रिंग एरे की तुलना में अधिक सामान्य उपयोग केस होने के कारण, मुझे लगता है कि एक स्ट्रिंग को ठंडा करना लागू करना आसान होगा। अगर मुझे पर्ल पता था, तो मैं देखता हूं कि रूबी अपने हैश व्यवहार में पर्ल के साथ संगत होने की कोशिश कर रहा है या नहीं। अगर मैं जापानी में कुशल था, तो मैं देखता था कि चाबियों को ठंडा करने के दौरान लागू किया गया था, और देखें कि क्या यह एक मेलिंग सूची पर एक बग रिपोर्ट या चर्चा का परिणाम था (संभवतया रूबी के इतिहास में कुछ के लिए जापानी में)। –

+1

@AndrewGrimm [यहां] (http://doc.ruby-lang.org/ja/1.9.2/class/Hash.html) यह कहता है कि सरणी और हैश एक हैश के लिए अच्छी चाबियाँ नहीं बनाते क्योंकि उन्हें संशोधित किया जा सकता है, और तार जमे हुए हैं ताकि आपको रिहाश कॉल न करना पड़े। Steenslag के जवाब के साथ संगत। – sawa

उत्तर

20

संक्षेप में यह रूबी अच्छा होने की कोशिश कर रहा है।

जब हैश में कोई कुंजी दर्ज की जाती है, तो कुंजी की hash विधि का उपयोग करके एक विशेष संख्या की गणना की जाती है। हैश ऑब्जेक्ट कुंजी को पुनर्प्राप्त करने के लिए इस नंबर का उपयोग करता है। उदाहरण के लिए, यदि आप पूछते हैं कि h['a'] का मान क्या है, तो हैश स्ट्रिंग 'ए' की hash विधि को कॉल करता है और जांचता है कि उसके पास उस नंबर के लिए संग्रहीत मूल्य है या नहीं।समस्या तब उत्पन्न होती है जब कोई (आप) स्ट्रिंग ऑब्जेक्ट को म्यूट करता है, इसलिए स्ट्रिंग 'ए' अब कुछ और है, आइए 'aa' कहें। हैश को 'एए' के ​​लिए हैश नंबर नहीं मिलेगा।

हैश के लिए सबसे आम प्रकार की चाबियां तार, प्रतीक और पूर्णांक हैं। प्रतीकों और पूर्णांक अपरिवर्तनीय हैं, लेकिन तार नहीं हैं। रूबी आपको ऊपर वर्णित भ्रमित व्यवहार से स्ट्रिंग कुंजी को डुप्लिकेट और फ्रीज करके बचाने की कोशिश करता है। मुझे लगता है कि यह अन्य प्रकार के लिए नहीं किया गया है क्योंकि वहां खराब प्रदर्शन दुष्प्रभाव हो सकते हैं (बड़े सरणी के बारे में सोचें)।

+0

प्रश्न के सैद्धांतिक हिस्से का जवाब देने के लिए धन्यवाद। –

4

स्पष्टीकरण के लिए this thread on the ruby-core mailing list देखें (अजीब बात यह है कि जब मैंने अपने मेल ऐप में मेलिंग सूची खोला तो यह पहला मेल था जब मैंने ठोकर खाई!)।

मैं अपने प्रश्न के पहले भाग के बारे में पता नहीं है, लेकिन ज यहाँ 2 भाग के लिए एक व्यावहारिक जवाब है:

new_hash = {} 
    h.each_pair do |k,v| 
    new_hash.merge!({k.downcase => v}) 
    end 

    h.replace new_hash 

नहीं है कोड के इस प्रकार के क्रमपरिवर्तन के बहुत सारे,

Hash[ h.map{|k,v| [k.downcase, v] } ] 

एक और किया जा रहा है (और आप शायद इन के बारे में पता कर रहे हैं, लेकिन कभी कभी यह व्यावहारिक मार्ग :)

+1

धन्यवाद! बहुत उपयोगी – Bretticus

2

आप askin कर रहे हैं लेने के लिए सबसे अच्छा है 2 अलग-अलग प्रश्न: सैद्धांतिक और व्यावहारिक। Lain पहले जवाब देने के लिए गया था, लेकिन मुझे लगता है मैं एक उचित, lazier अपने व्यावहारिक प्रश्न का हल क्या करने पर विचार प्रदान करना चाहते हैं:

Hash.new { |hsh, key| # this block get's called only if a key is absent 
    downcased = key.to_s.downcase 
    unless downcased == key # if downcasing makes a difference 
    hsh[key] = hsh[downcased] if hsh.has_key? downcased # define a new hash pair 
    end # (otherways just return nil) 
} 

Hash.new निर्माता के साथ प्रयोग किया ब्लॉक के लिए केवल उन लापता चाबियाँ, कि वास्तव में कर रहे शुरू होता है का अनुरोध किया। उपर्युक्त समाधान प्रतीकों को भी स्वीकार करता है।

3

अपरिवर्तनीय कुंजी सामान्य रूप से समझ में आती हैं क्योंकि उनके हैश कोड स्थिर होंगे।

यही कारण है कि तार, विशेष रूप से बदल दिया जाता है एमआरआई कोड के इस हिस्से में:

if (RHASH(hash)->ntbl->type == &identhash || rb_obj_class(key) != rb_cString) { 
    st_insert(RHASH(hash)->ntbl, key, val); 
} 
else { 
    st_insert2(RHASH(hash)->ntbl, key, val, copy_str_key); 
} 

संक्षेप में, स्ट्रिंग कुंजी मामले में, st_insert2 एक समारोह के लिए एक सूचक है कि ट्रिगर किया जाएगा पारित हो जाता है डुप्लिकेट और फ्रीज।

तो अगर हम सैद्धांतिक रूप से अपरिवर्तनीय सूचियों और हैश कुंजी के रूप में अपरिवर्तनीय हैश समर्थन करना चाहते थे, तो हम उस कोड कुछ करने के लिए इस तरह संशोधित कर सकते हैं:

VALUE key_klass; 
key_klass = rb_obj_class(key); 
if (key_klass == rb_cArray || key_klass == rb_cHash) { 
    st_insert2(RHASH(hash)->ntbl, key, val, freeze_obj); 
} 
else if (key_klass == rb_cString) { 
    st_insert2(RHASH(hash)->ntbl, key, val, copy_str_key); 
} 
else { 
    st_insert(RHASH(hash)->ntbl, key, val); 
} 

freeze_obj परिभाषित किए जाएँगे कहाँ के रूप में:

static st_data_t 
freeze_obj(st_data_t obj) 
{ 
    return (st_data_t)rb_obj_freeze((VALUE) obj); 
} 

तो यह आपके द्वारा देखी गई विशिष्ट असंगतता को हल करेगा, जहां सरणी-कुंजी उत्परिवर्तनीय थी। हालांकि वास्तव में संगत होने के लिए, अधिक प्रकार की वस्तुओं को भी अपरिवर्तनीय बनाने की आवश्यकता होगी।

सभी प्रकार, हालांकि। उदाहरण के लिए, फिक्सनम जैसी तत्काल वस्तुओं को ठंडा करने का कोई मतलब नहीं होगा क्योंकि प्रत्येक पूर्णांक मान के अनुरूप फ़िक्सनम का प्रभावी रूप से केवल एक उदाहरण होता है। यही कारण है कि केवल String को इस तरह से विशेष रूप से cased किया जाना चाहिए, Fixnum और Symbol नहीं।

स्ट्रिंग्स रूबी प्रोग्रामर के लिए सुविधा के मामले के रूप में बस एक विशेष अपवाद हैं, क्योंकि तारों को अक्सर हैश कुंजी के रूप में उपयोग किया जाता है।

इसके विपरीत, कारण यह है कि अन्य वस्तु प्रकार इस है, जो बेशक असंगत व्यवहार की ओर जाता है की तरह जमे हुए नहीं हैं, ज्यादातर Matz & कंपनी के लिए सुविधा की बात बढ़त मामलों का समर्थन नहीं है। व्यावहारिक रूप से, तुलनात्मक रूप से कुछ लोग एक कंटेनर ऑब्जेक्ट का उपयोग सरणी या हैश की हैश कुंजी के रूप में करेंगे। तो यदि आप ऐसा करते हैं, तो सम्मिलन से पहले जमा करने के लिए आप पर निर्भर है।

ध्यान दें कि यह प्रदर्शन के बारे में सख्ती से नहीं है, क्योंकि एक गैर-तत्काल वस्तु को ठंडा करने के कार्य में बिट basic.flags बिटफील्ड पर प्रत्येक ऑब्जेक्ट पर मौजूद है। यह निश्चित रूप से एक सस्ता ऑपरेशन है।

प्रदर्शन के बारे में भी बात करते हुए, ध्यान दें कि यदि आप स्ट्रिंग कुंजियों का उपयोग करने जा रहे हैं, और आप कोड के प्रदर्शन-महत्वपूर्ण अनुभाग में हैं, तो आप सम्मिलन करने से पहले अपने तारों को फ्रीज करना चाहेंगे। यदि आप नहीं करते हैं, तो एक डुप्लिकेट ट्रिगर होता है, जो एक अधिक महंगा ऑपरेशन है।

अद्यतन @sawa ने बताया अपने सरणी कुंजी बस जमे हुए छोड़ने का मतलब है कि मूल सरणी कुंजी का उपयोग संदर्भ है, जो भी एक अप्रिय आश्चर्य हो सकता है (के अप्रत्याशित रूप से अपरिवर्तनीय बाहर हो सकता है, हालांकि OTOH यह आप सही काम करेगा एक हैश-कुंजी के रूप में एक सरणी का उपयोग करने के लिए, वास्तव में)।यदि आप इसलिए अनुमान लगाते हैं कि डुप्लिकेट उस से बाहर निकलने का तरीका है, तो आप वास्तव में संभावित ध्यान देने योग्य प्रदर्शन लागत ले सकते हैं। तीसरे हाथ पर, इसे पूरी तरह से अनजान छोड़ दें, और आपको ओपी की मूल अजीबता मिलती है। चारों ओर अजीबता। इन एज मामलों को प्रोग्रामर को स्थगित करने के लिए Matz et al के लिए एक अन्य कारण।

+1

डुप्लिकेट किए बिना मूल कुंजी को फ्रीज करना भ्रमित होगा। एक कुंजी स्वचालित रूप से जमे हुए होने पर डुप्लिकेट करना आवश्यक होगा। यहां तक ​​कि यदि ठंड लगाना सस्ता है, तो सरणी को डुप्लिकेट करना आदि महंगा है, और इसलिए यह सब के बाद एक प्रदर्शन मुद्दा प्रतीत होता है। आपका अंतिम अनुच्छेद जानकारीपूर्ण है। क्या आप निश्चित हैं कि, यदि स्ट्रिंग शुरुआत से जमे हुए है, तो हैश कुंजी के रूप में उपयोग किए जाने पर इसे डुप्लिकेट नहीं किया जाएगा? – sawa

+1

यह सुनिश्चित करने के लिए कि यह कैसे काम करता है, हां, आप इसे यहां देख सकते हैं: 'अगर (ओबीजे_फ्रोज़ेन (मूल)) मूल लौटाता है; 'rb_str_new_frozen()' के शीर्ष पर, वर्तमान में यहां स्थित है: github.com/ruby/ रूबी/ब्लॉब/ट्रंक/स्ट्रिंग सी # एल 673 – manzoid

+1

मुझे जरूरी नहीं है कि "डुप्लिकेटिंग जरूरी हो" ... यदि हैश कुंजी सेट करने के लिए लगातार व्यवहार यह था कि वे सभी आसानी से जमे हुए थे, तो जो लोग असामान्य थे चीजों को एक कुंजी के रूप में एक सरणी का उपयोग करने का प्रयास करें और फिर इसे बाद में बदल दें, जल्दी से पता चलता है कि उपयोग काम नहीं करता है, जब अद्यतन प्रयास जोर से विफल हो जाता है। कभी-कभी स्थिरता शायद सहायक होगी। अब, मैं निश्चित रूप से देखता हूं कि आप कहां से आ रहे हैं ... बस तर्कसंगत लगता है कि स्थिरता, प्रदर्शन, अजीब चीज़ों के परिणाम से प्रोग्रामर की रक्षा करना आदि। – manzoid

संबंधित मुद्दे