रूबी में ऐरे स्लाइसिंग: अस्वाभाविक व्यवहार के लिए स्पष्टीकरण (रूबीकोन्स.कॉम से लिया गया)


232

मैं रूबी Koans में अभ्यास के माध्यम से जा रहा था और मैं निम्नलिखित रूबी quirk द्वारा मारा गया था कि मैं वास्तव में अलौकिक पाया:

array = [:peanut, :butter, :and, :jelly]

array[0]     #=> :peanut    #OK!
array[0,1]   #=> [:peanut]  #OK!
array[0,2]   #=> [:peanut, :butter]  #OK!
array[0,0]   #=> []    #OK!
array[2]     #=> :and  #OK!
array[2,2]   #=> [:and, :jelly]  #OK!
array[2,20]  #=> [:and, :jelly]  #OK!
array[4]     #=> nil  #OK!
array[4,0]   #=> []   #HUH??  Why's that?
array[4,100] #=> []   #Still HUH, but consistent with previous one
array[5]     #=> nil  #consistent with array[4] #=> nil  
array[5,0]   #=> nil  #WOW.  Now I don't understand anything anymore...

तो array[5,0]बराबर क्यों नहीं है array[4,0]? क्या कोई कारण है कि जब आप (लंबाई + 1) वें स्थान पर शुरू करते हैं तो सरणी स्लाइसिंग यह अजीब व्यवहार करती है ??



ऐसा लगता है कि पहला नंबर इंडेक्स शुरू करने वाला है, दूसरा नंबर स्लाइस के कितने तत्व हैं
ऑस्टिन

जवाबों:


185

स्लाइसिंग और इंडेक्सिंग दो अलग-अलग ऑपरेशन हैं, और एक के व्यवहार को दूसरे से दूर करना जहां आपकी समस्या निहित है।

स्लाइस में पहला तर्क तत्व को नहीं बल्कि तत्वों के बीच के स्थानों को परिभाषित करता है, स्पैन को परिभाषित करता है (और स्वयं तत्वों को नहीं):

  :peanut   :butter   :and   :jelly
0         1         2      3        4

4 अभी भी सरणी के भीतर है, बस मुश्किल से; यदि आप 0 तत्वों का अनुरोध करते हैं, तो आपको सरणी का खाली छोर मिलता है। लेकिन कोई इंडेक्स 5 नहीं है, इसलिए आप वहां से स्लाइस नहीं कर सकते।

जब आप इंडेक्स (जैसे array[4]) करते हैं, तो आप स्वयं तत्वों की ओर इशारा करते हैं, इसलिए सूचकांक केवल 0 से 3 तक जाते हैं।


8
एक अच्छा अनुमान जब तक यह स्रोत द्वारा समर्थित नहीं है। अगर हम ओपी और अन्य टिप्पणीकारों की तरह "क्यों" की व्याख्या करने के लिए पूछ रहे हैं, तो कोई बात नहीं। आपका चित्र Array को छोड़कर समझ में आता है [4] शून्य है। ऐरे [3] है: जेली। मुझे उम्मीद है कि ऐरे [4, एन] शून्य होगा लेकिन यह ओपी की तरह ही है। यदि यह एक जगह है, तो यह एक बहुत बेकार जगह है क्योंकि ऐरे [4, -1] शून्य है। इसलिए आप ऐरे [4] के साथ कुछ नहीं कर सकते।
स्क्वैरिज्म

5
@ स्क्वैरिज्म मुझे सिर्फ चार्ल्स ओलिवर नट्टर (ट्विटर पर @ हेडियस) से पुष्टि मिली कि यह सही स्पष्टीकरण है। वह एक बड़े समय के रूबी देव हैं, इसलिए मैं उनके शब्द को बहुत आधिकारिक मानूंगा।
हांक गे

18
इस व्यवहार के लिए निम्नलिखित औचित्य है: ब्लेड.नगाओकॉट .ac.jp
मैट

4
सही स्पष्टीकरण। रूबी-कोर पर इसी तरह की चर्चा: redmine.ruby-lang.org/issues/4245 , redmine.ruby-lang.org/issues/4541
Marc-André Lafortune

18
इसे "बाड़-पोस्टिंग" भी कहा जाता है। पांचवें बाड़-पद (आईडी 4) मौजूद है, लेकिन पांचवां तत्व नहीं है। स्लाइसिंग एक बाड़-पोस्ट ऑपरेशन है, अनुक्रमण एक तत्व ऑपरेशन है।
मैटी के

27

यह इस तथ्य के साथ करना है कि टुकड़ा एक सरणी देता है, Array # स्लाइस से प्रासंगिक स्रोत प्रलेखन:

 *  call-seq:
 *     array[index]                -> obj      or nil
 *     array[start, length]        -> an_array or nil
 *     array[range]                -> an_array or nil
 *     array.slice(index)          -> obj      or nil
 *     array.slice(start, length)  -> an_array or nil
 *     array.slice(range)          -> an_array or nil

जो मुझे सुझाव देता है कि यदि आप उस सीमा को देते हैं जो सीमा से बाहर है, तो यह शून्य पर वापस आ जाएगी, इस प्रकार आपके उदाहरण में array[4,0]मौजूद 4 तत्व के लिए पूछता है, लेकिन शून्य तत्वों की एक सरणी को वापस करने के लिए कहता है। जबकि array[5,0]सीमा से बाहर एक सूचकांक के लिए पूछता है तो यह शून्य देता है। यह शायद अधिक समझ में आता है अगर आपको याद है कि स्लाइस विधि एक नई सरणी लौटा रही है , मूल डेटा संरचना को बदलकर नहीं।

संपादित करें:

टिप्पणियों की समीक्षा करने के बाद मैंने इस उत्तर को संपादित करने का फैसला किया। स्लाइस निम्नलिखित कोड स्निपेट को कॉल करता है जब arg वैल्यू दो होती है:

if (argc == 2) {
    if (SYMBOL_P(argv[0])) {
        rb_raise(rb_eTypeError, "Symbol as array index");
    }
    beg = NUM2LONG(argv[0]);
    len = NUM2LONG(argv[1]);
    if (beg < 0) {
        beg += RARRAY(ary)->len;
    }
    return rb_ary_subseq(ary, beg, len);
}

यदि आप उस array.cकक्षा में देखते हैं जहाँ rb_ary_subseqविधि परिभाषित की गई है, तो आप देखते हैं कि यह शून्य है, यदि लंबाई सीमा से बाहर है, तो सूचकांक नहीं:

if (beg > RARRAY_LEN(ary)) return Qnil;

इस स्थिति में यह तब होता है जब 4 में पारित किया जाता है, यह जांचता है कि 4 तत्व हैं और इस तरह एनआईएल रिटर्न को ट्रिगर नहीं करता है। यह तब चलता है और एक खाली सरणी देता है यदि दूसरा arg शून्य पर सेट है। जब तक कि 5 में पारित नहीं किया जाता है, तब सरणी में 5 तत्व नहीं होते हैं, इसलिए शून्य शून्य मूल्यांकन होने से पहले यह शून्य हो जाता है। 944 लाइन पर यहाँ कोड ।

मेरा मानना ​​है कि यह एक बग है, या कम से कम अप्रत्याशित है और 'सिद्धांत का कम नहीं'। जब मुझे कुछ मिनट मिलेंगे तो मैं माणिक कोर को एक असफल परीक्षण पैच प्रस्तुत करूंगा।


2
लेकिन ... 4 [4,0] सरणी में इंगित तत्व मौजूद नहीं है ... - क्योंकि यह वास्तव में 5the तत्व (0-आधारित गणना, उदाहरण देखें) है। तो यह सीमा से बाहर भी है।
पास्कल वैन हेक

1
आप सही हे। मैं वापस गया और स्रोत को देखा, और ऐसा लगता है कि पहला तर्क सी कोड के अंदर लंबाई के रूप में संभाला जाता है, न कि सूचकांक के रूप में। मैं अपने उत्तर को संपादित करूंगा, इसे प्रतिबिंबित करने के लिए। मुझे लगता है कि इसे बग के रूप में प्रस्तुत किया जा सकता है।
जेड श्नाइडर

23

कम से कम ध्यान दें कि व्यवहार सुसंगत है। 5 से सब कुछ एक ही कार्य करता है; अजीबता केवल पर होती है [4,N]

शायद यह पैटर्न मदद करता है, या शायद मैं बस थक गया हूं और यह बिल्कुल भी मदद नहीं करता है।

array[0,4] => [:peanut, :butter, :and, :jelly]
array[1,3] => [:butter, :and, :jelly]
array[2,2] => [:and, :jelly]
array[3,1] => [:jelly]
array[4,0] => []

पर [4,0], हम सरणी के अंत को पकड़ते हैं। मैं वास्तव में इसे अजीब लगता हूं, जहां तक ​​पैटर्न में सुंदरता जाती है, अगर पिछले एक वापस आ गया nil। इस तरह के संदर्भ के कारण, 4पहले पैरामीटर के लिए एक स्वीकार्य विकल्प है ताकि खाली सरणी वापस आ सके। एक बार जब हम 5 और ऊपर से टकरा जाते हैं, हालांकि, विधि की संभावना पूरी तरह से और पूरी तरह से सीमा से बाहर होने की प्रकृति से तुरंत बाहर निकल जाती है।


12

इसका मतलब यह है कि जब आप एक सरणी टुकड़ा की तुलना में एक वैध अंतराल मानते हैं, न कि केवल एक प्रतिद्वंद्विता:

array = [:peanut, :butter, :and, :jelly]
# replace 0 elements starting at index 5 (insert at end or array):
array[4,0] = [:sandwich]
# replace 0 elements starting at index 0 (insert at head of array):
array[0,0] = [:make, :me, :a]
# array is [:make, :me, :a, :peanut, :butter, :and, :jelly, :sandwich]

# this is just like replacing existing elements:
array[3, 4] = [:grilled, :cheese]
# array is [:make, :me, :a, :grilled, :cheese, :sandwich]

यह संभव नहीं होगा अगर इसके बजाय array[4,0]वापस लौटा nilदिया जाए []। हालाँकि, array[5,0]रिटर्न nilक्योंकि यह सीमा से बाहर है (4-तत्व सरणी के 4 तत्व के बाद सम्मिलित करना सार्थक है, लेकिन 4 तत्व सरणी के 5 वें तत्व के बाद सम्मिलित करना) नहीं है।

स्लाइस सिंटैक्स array[x,y]को " xतत्वों के बाद शुरू करना array, तत्वों का चयन करें " के रूप में पढ़ें y। यह केवल तभी सार्थक है जब arrayइसमें कम से कम xतत्व हों।


11

यह करता है मेकअप भावना

आपको उन स्लाइसों को निर्दिष्ट करने में सक्षम होने की आवश्यकता है, इसलिए उन्हें इस तरह से परिभाषित किया गया है कि स्ट्रिंग की शुरुआत और अंत में शून्य-लंबाई के भाव हैं।

array[4, 0] = :sandwich
array[0, 0] = :crunchy
=> [:crunchy, :peanut, :butter, :and, :jelly, :sandwich]

1
आप उस सीमा तक भी असाइन कर सकते हैं जो कि शून्य के रूप में वापस आती है, इसलिए इस स्पष्टीकरण का विस्तार करना उपयोगी होगा। array[5,0]=:foo # array is now [:peanut, :butter, :and, :jelly, nil, :foo]
mfazekas

असाइन करते समय दूसरा नंबर क्या करता है? इसे नजरअंदाज किया जा रहा है। [26] pry(main)> array[4,5] = [:love, :hope, :peace] => [:peanut, :butter, :and, :jelly, :love, :hope, :peace]
ड्रू वर्ली

@ ड्रूवली को नजरअंदाज नहीं किया जाता:array = [:a, :b, :c, :d, :e]; array[1,2] = :x, :x; array => [:a, :x, :x, :d, :e]
fanaugen

10

मुझे गैरी राइट द्वारा स्पष्टीकरण भी बहुत उपयोगी लगा। http://www.ruby-forum.com/topic/1393096#990065

गैरी राइट का जवाब है -

http://www.ruby-doc.org/core/classes/Array.html

डॉक्स निश्चित रूप से अधिक स्पष्ट हो सकते हैं लेकिन वास्तविक व्यवहार आत्मनिर्भर और उपयोगी है। नोट: मैं स्ट्रिंग का 1.9.X संस्करण मान रहा हूं।

यह निम्नलिखित तरीके से नंबरिंग पर विचार करने में मदद करता है:

  -4  -3  -2  -1    <-- numbering for single argument indexing
   0   1   2   3
 +---+---+---+---+
 | a | b | c | d |
 +---+---+---+---+
 0   1   2   3   4  <-- numbering for two argument indexing or start of range
-4  -3  -2  -1

सामान्य (और समझने योग्य) गलती यह भी मानती है कि एकल तर्क सूचकांक के शब्दार्थ दो तर्क परिदृश्य (या श्रेणी) में पहले तर्क के शब्दार्थ के समान हैं । वे व्यवहार में एक ही चीज नहीं हैं और प्रलेखन यह प्रतिबिंबित नहीं करता है। हालांकि त्रुटि निश्चित रूप से प्रलेखन में है और कार्यान्वयन में नहीं है:

एकल तर्क: सूचकांक स्ट्रिंग के भीतर एक एकल वर्ण स्थिति का प्रतिनिधित्व करता है। इसका परिणाम इंडेक्स या नील में पाया जाने वाला सिंगल कैरेक्टर स्ट्रिंग है क्योंकि दिए गए इंडेक्स में कोई कैरेक्टर नहीं है।

  s = ""
  s[0]    # nil because no character at that position

  s = "abcd"
  s[0]    # "a"
  s[-4]   # "a"
  s[-5]   # nil, no characters before the first one

दो पूर्णांक तर्क: तर्क स्ट्रिंग के एक हिस्से को निकालने या बदलने के लिए पहचानते हैं। विशेष रूप से, स्ट्रिंग के शून्य-चौड़ाई वाले हिस्से को भी पहचाना जा सकता है ताकि स्ट्रिंग के सामने या अंत सहित मौजूदा पात्रों के पहले या बाद में पाठ डाला जा सके। इस स्थिति में, पहला तर्क किसी वर्ण स्थिति की पहचान नहीं करता है, बल्कि वर्णों के बीच की जगह की पहचान करता है जैसा कि ऊपर चित्र में दिखाया गया है। दूसरा तर्क लंबाई है, जो 0 हो सकता है।

s = "abcd"   # each example below assumes s is reset to "abcd"

To insert text before 'a':   s[0,0] = "X"           #  "Xabcd"
To insert text after 'd':    s[4,0] = "Z"           #  "abcdZ"
To replace first two characters: s[0,2] = "AB"      #  "ABcd"
To replace last two characters:  s[-2,2] = "CD"     #  "abCD"
To replace middle two characters: s[1..3] = "XX"    #  "aXXd"

एक श्रेणी का व्यवहार बहुत दिलचस्प है। शुरुआती बिंदु पहले तर्क के समान है जब दो तर्क प्रदान किए जाते हैं (जैसा कि ऊपर वर्णित है), लेकिन सीमा का अंतिम बिंदु एकल अनुक्रमित या "किनारे की स्थिति" के रूप में दो पूर्णांक तर्कों के साथ 'वर्ण स्थिति' हो सकता है। यह अंतर इस बात से निर्धारित होता है कि डबल-डॉट रेंज या ट्रिपल-डॉट रेंज का उपयोग किया जाता है या नहीं:

s = "abcd"
s[1..1]           # "b"
s[1..1] = "X"     # "aXcd"

s[1...1]          # ""
s[1...1] = "X"    # "aXbcd", the range specifies a zero-width portion of
the string

s[1..3]           # "bcd"
s[1..3] = "X"     # "aX",  positions 1, 2, and 3 are replaced.

s[1...3]          # "bc"
s[1...3] = "X"    # "aXd", positions 1, 2, but not quite 3 are replaced.

यदि आप इन उदाहरणों के माध्यम से वापस जाते हैं और डबल या श्रेणी अनुक्रमण उदाहरणों के लिए सिंगल इंडेक्स शब्दार्थ का उपयोग करने पर जोर देते हैं तो आप भ्रमित हो जाएंगे। आपको वास्तविक व्यवहार को मॉडल करने के लिए ascii आरेख में प्रदर्शित वैकल्पिक नंबरिंग का उपयोग करने के लिए मिला है।


3
क्या आप उस धागे का मुख्य विचार शामिल कर सकते हैं? (लिंक के मामले में एक दिन अमान्य हो जाता है)
VonC

8

मैं मानता हूं कि यह अजीब व्यवहार की तरह लगता है, लेकिन यहां तक कि आधिकारिक दस्तावेजArray#slice आपके उदाहरण में उसी व्यवहार को प्रदर्शित करता है, जैसे "विशेष" में है:

   a = [ "a", "b", "c", "d", "e" ]
   a[2] +  a[0] + a[1]    #=> "cab"
   a[6]                   #=> nil
   a[1, 2]                #=> [ "b", "c" ]
   a[1..3]                #=> [ "b", "c", "d" ]
   a[4..7]                #=> [ "e" ]
   a[6..10]               #=> nil
   a[-3, 3]               #=> [ "c", "d", "e" ]
   # special cases
   a[5]                   #=> nil
   a[5, 1]                #=> []
   a[5..10]               #=> []

दुर्भाग्य से, यहां तक कि उनके विवरण Array#sliceके बारे में कोई अंतर्दृष्टि प्रदान प्रतीत नहीं होता है क्यों यह इस तरह से काम करता है:

तत्व संदर्भ - तत्व को इंडेक्स पर लौटाता है, या लंबाई तत्वों के लिए शुरू और जारी रखने के लिए एक सबर्रे लौटाता है, या रेंज द्वारा निर्दिष्ट एक सबर्रे देता है । ऋणात्मक सूचकांक सरणी के अंत से पीछे की ओर गिनती करते हैं (-1 अंतिम तत्व है)। यदि इंडेक्स (या स्टार्टिंग इंडेक्स) सीमा से बाहर है, तो शून्य पर लौटें।


7

जिम वेरिच द्वारा प्रदान की गई एक व्याख्या

इसके बारे में सोचने का एक तरीका यह है कि सूचकांक स्थिति 4 सरणी के बहुत किनारे पर है। जब एक टुकड़ा के लिए पूछ रहे हैं, तो आप उस सरणी के जितना वापस छोड़ दिया है। इसलिए सरणी [2,10], सरणी [3,10] और सरणी [4,10] पर विचार करें ... प्रत्येक क्रमशः सरणी के अंत के शेष बिट्स: 2 तत्व, 1 तत्व और 0 तत्व देता है। हालाँकि, स्थिति 5 स्पष्ट रूप से सरणी के बाहर है और किनारे पर नहीं है, इसलिए सरणी [5,10] शून्य है।


6

निम्नलिखित सरणी पर विचार करें:

>> array=["a","b","c"]
=> ["a", "b", "c"]

आप इसे असाइन करके सरणी के भीख (सिर) के लिए एक आइटम सम्मिलित कर सकते हैं a[0,0]। तत्व को बीच में रखना "a"और "b", का उपयोग करना a[1,0]। मूल रूप से, अंकन में a[i,n], iएक सूचकांक और nकई तत्वों का प्रतिनिधित्व करता है। जब n=0, यह सरणी के तत्वों के बीच एक स्थिति को परिभाषित करता है।

अब यदि आप सरणी के अंत के बारे में सोचते हैं, तो आप ऊपर वर्णित नोटेशन का उपयोग करके किसी आइटम को उसके अंत में कैसे जोड़ सकते हैं? सरल, मान निर्दिष्ट करें a[3,0]। यह सरणी की पूंछ है।

इसलिए, यदि आप तत्व को एक्सेस करने का प्रयास करते हैं, तो आपको a[3,0]मिलेगा []। इस मामले में आप अभी भी सरणी की श्रेणी में हैं। लेकिन अगर आप एक्सेस करने की कोशिश करते हैं a[4,0], तो आपको nilरिटर्न वैल्यू मिल जाएगी , क्योंकि आप एरे की रेंज में नहीं हैं।

पर इसके बारे में और अधिक पढ़ें http://mybrainstormings.wordpress.com/2012/09/10/arrays-in-ruby/


0

tl; dr: स्रोत कोड में array.c, अलग-अलग फ़ंक्शंस को इस आधार पर बुलाया जाता है कि क्या आप Array#sliceअनपेक्षित रूप से महत्वपूर्ण मानों के परिणामस्वरूप 1 या 2 तर्क पास करते हैं।

(सबसे पहले, मैं यह बताना चाहूंगा कि मैं सी में कोड नहीं करता, लेकिन वर्षों से रूबी का उपयोग कर रहा हूं। इसलिए यदि आप सी से परिचित नहीं हैं, लेकिन आप मूल बातें से परिचित होने में कुछ मिनट लगते हैं। फ़ंक्शन और वैरिएबल यह वास्तव में रूबी स्रोत कोड का पालन करने के लिए उतना कठिन नहीं है, जैसा कि नीचे दिखाया गया है। यह उत्तर रूबी v2.3 पर आधारित है, लेकिन यह कमोबेश v1.9 है।

दृष्टांत 1

array.length == 4; array.slice(4) #=> nil

यदि आप Array#slice( rb_ary_aref) के लिए स्रोत कोड को देखते हैं, तो आप देखते हैं कि जब केवल एक तर्क ( लाइनों 1277-1289 ) में पारित किया rb_ary_entryजाता है, तो सूचकांक मूल्य (जो सकारात्मक या नकारात्मक हो सकता है) में गुजरता है।

rb_ary_entryफिर सरणी की शुरुआत से (दूसरे शब्दों में, यदि एक नकारात्मक सूचकांक पारित किया जाता है, तो यह सकारात्मक समकक्ष की गणना करता है) से अनुरोधित तत्व की स्थिति की गणना करता है और फिर rb_ary_eltअनुरोध किए गए तत्व को प्राप्त करने के लिए कॉल करता है।

जैसी उम्मीद थी, rb_ary_eltरिटर्न nilजब सरणी की लंबाई lenहै की तुलना में कम या ज्यादा के बराबर सूचकांक (यहाँ बुलाया offset)।

1189:  if (offset < 0 || len <= offset) {
1190:    return Qnil;
1191:  } 

परिदृश्य # 2

array.length == 4; array.slice(4, 0) #=> []

हालाँकि जब 2 तर्कों को पारित किया जाता है (अर्थात शुरुआती सूचकांक beg, और स्लाइस की लंबाई len), rb_ary_subseqकहा जाता है।

में rb_ary_subseq, अगर शुरू करने सूचकांक begहै की तुलना में अधिक सरणी लंबाई alen, nilदिया जाता है:

1208:  long alen = RARRAY_LEN(ary);
1209:
1210:  if (beg > alen) return Qnil;

अन्यथा परिणामस्वरूप स्लाइस की लंबाई की lenगणना की जाती है, और यदि यह शून्य होने के लिए निर्धारित है, तो एक खाली सरणी वापस आ जाती है:

1213:  if (alen < len || alen < beg + len) {
1214:  len = alen - beg;
1215:  }
1216:  klass = rb_obj_class(ary);
1217:  if (len == 0) return ary_new(klass, 0);

इसलिए चूंकि 4 का आरंभिक सूचकांक इससे अधिक नहीं है array.length, इसलिए एक खाली सरणी को उस nilमान के बजाय लौटा दिया जाता है जिसकी कोई उम्मीद कर सकता है।

सवाल का जवाब दिया?

अगर यहां वास्तविक सवाल यह नहीं है कि "कोड किस कारण से ऐसा होता है?", बल्कि "मात्ज़ ने ऐसा क्यों किया?", तो ठीक है, आपको बस उसे अगले रूबिकॉफ़ में एक कप कॉफी खरीदना होगा। उससे पूछो।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.