Ich habe die Übungen in Ruby Koans durchlaufen und war von der folgenden Ruby-Eigenart beeindruckt, die ich wirklich unerklärlich fand:
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...
Warum ist das array[5,0]
nicht gleich array[4,0]
? Gibt es einen Grund, warum sich das Array-Slicing so seltsam verhält, wenn Sie an der (Länge + 1) -ten Position beginnen?
Antworten:
Slicing und Indizierung sind zwei verschiedene Vorgänge, und wenn Sie das Verhalten des einen vom anderen ableiten, liegt Ihr Problem darin.
Das erste Argument in Slice identifiziert nicht das Element, sondern die Stellen zwischen Elementen und definiert Bereiche (und nicht Elemente selbst):
4 ist immer noch innerhalb des Arrays, gerade noch; Wenn Sie 0 Elemente anfordern, erhalten Sie das leere Ende des Arrays. Es gibt jedoch keinen Index 5, sodass Sie von dort aus nicht schneiden können.
Wenn Sie indexieren (wie
array[4]
), zeigen Sie auf Elemente selbst, sodass die Indizes nur von 0 auf 3 gehen.quelle
Dies hat mit der Tatsache zu tun, dass Slice ein Array zurückgibt, relevante Quelldokumentation von Array # Slice:
was mir nahe legt, dass wenn Sie den Start angeben, der außerhalb der Grenzen liegt, er null zurückgibt, also in Ihrem Beispiel
array[4,0]
nach dem 4. vorhandenen Element fragt, aber ein Array von null Elementen zurückgibt. Währendarray[5,0]
fragt nach einem Index außerhalb der Grenzen, so dass er null zurückgibt. Dies ist möglicherweise sinnvoller, wenn Sie sich daran erinnern, dass die Slice-Methode ein neues Array zurückgibt und die ursprüngliche Datenstruktur nicht ändert.BEARBEITEN:
Nachdem ich die Kommentare überprüft hatte, entschied ich mich, diese Antwort zu bearbeiten. Slice ruft das folgende Code-Snippet auf, wenn der arg-Wert zwei ist:
Wenn Sie in die
array.c
Klasse schauen, in der dierb_ary_subseq
Methode definiert ist, sehen Sie, dass sie null zurückgibt, wenn die Länge außerhalb der Grenzen liegt, nicht der Index:In diesem Fall geschieht dies, wenn 4 übergeben werden. Es prüft, ob 4 Elemente vorhanden sind, und löst daher keine Null-Rückgabe aus. Es geht dann weiter und gibt ein leeres Array zurück, wenn das zweite Argument auf Null gesetzt ist. Wenn 5 übergeben wird, enthält das Array keine 5 Elemente. Daher wird null zurückgegeben, bevor das Argument null ausgewertet wird. Code hier in Zeile 944.
Ich glaube, dies ist ein Fehler oder zumindest unvorhersehbar und nicht das "Prinzip der geringsten Überraschung". Wenn ich ein paar Minuten Zeit habe, werde ich mindestens einen fehlgeschlagenen Test-Patch an Ruby Core senden.
quelle
Beachten Sie zumindest, dass das Verhalten konsistent ist. Ab 5 verhält sich alles gleich; Die Verrücktheit tritt nur bei auf
[4,N]
.Vielleicht hilft dieses Muster, oder vielleicht bin ich nur müde und es hilft überhaupt nicht.
Um
[4,0]
fangen wir das Ende des Arrays. Ich würde es tatsächlich ziemlich seltsam finden, was die Schönheit der Muster angeht, wenn der letzte zurückkommtnil
. Aufgrund eines solchen Kontexts4
ist dies eine akzeptable Option für den ersten Parameter, damit das leere Array zurückgegeben werden kann. Sobald wir jedoch 5 und höher erreicht haben, wird die Methode wahrscheinlich sofort beendet, da sie vollständig und vollständig außerhalb der Grenzen liegt.quelle
Dies ist sinnvoll, wenn Sie bedenken, dass ein Array-Slice ein gültiger l-Wert sein kann, nicht nur ein r-Wert:
Dies wäre nicht möglich, wenn
array[4,0]
zurückgegebennil
statt[]
. Gibt jedocharray[5,0]
zurücknil
weil es außerhalb der Grenzen liegt (das Einfügen nach dem 4. Element eines 4-Element-Arrays ist sinnvoll, das Einfügen nach dem 5. Element eines 4-Element-Arrays jedoch nicht).Lesen Sie die Slice-Syntax
array[x,y]
als "Beginnen Sie nachx
Elementen inarray
und wählen Sie bis zuy
Elementen aus". Dies ist nur sinnvoll, wennarray
mindestensx
Elemente vorhanden sind.quelle
Das macht Sinn
Sie müssen in der Lage sein, diesen Slices zuzuweisen, damit sie so definiert sind, dass der Anfang und das Ende der Zeichenfolge funktionierende Ausdrücke mit der Länge Null enthalten.
quelle
array[5,0]=:foo # array is now [:peanut, :butter, :and, :jelly, nil, :foo]
[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]
Ich fand die Erklärung von Gary Wright ebenfalls sehr hilfreich. http://www.ruby-forum.com/topic/1393096#990065
Die Antwort von Gary Wright lautet -
http://www.ruby-doc.org/core/classes/Array.html
Die Dokumente könnten sicherlich klarer sein, aber das tatsächliche Verhalten ist selbstkonsistent und nützlich. Hinweis: Ich gehe von einer 1.9.X-Version von String aus.
Es ist hilfreich, die Nummerierung folgendermaßen zu berücksichtigen:
Der häufigste (und verständliche) Fehler besteht darin, anzunehmen, dass die Semantik des Einzelargumentindex mit der Semantik des ersten übereinstimmt Arguments im Szenario (oder Bereich) mit zwei Argumenten übereinstimmt. In der Praxis sind sie nicht dasselbe, und die Dokumentation spiegelt dies nicht wider. Der Fehler liegt jedoch definitiv in der Dokumentation und nicht in der Implementierung:
einzelnes Argument: Der Index repräsentiert eine einzelne Zeichenposition innerhalb der Zeichenfolge. Das Ergebnis ist entweder die einzelne Zeichenfolge, die im Index gefunden wird, oder null, da der angegebene Index kein Zeichen enthält.
Zwei ganzzahlige Argumente: Die Argumente identifizieren einen Teil der Zeichenfolge, der extrahiert oder ersetzt werden soll. Insbesondere können auch Teile der Zeichenfolge mit der Breite Null identifiziert werden, so dass Text vor oder nach vorhandenen Zeichen eingefügt werden kann, einschließlich am Anfang oder Ende der Zeichenfolge. In diesem Fall wird das erste Argument nicht eine Zeichenposition identifizieren , sondern statt dem identifiziert , den Raum zwischen den Zeichen , wie im Diagramm oben dargestellt. Das zweite Argument ist die Länge, die 0 sein kann.
Das Verhalten eines Bereichs ist ziemlich interessant. Der Startpunkt ist der gleiche wie das erste Argument, wenn zwei Argumente angegeben werden (wie oben beschrieben), aber der Endpunkt des Bereichs kann die 'Zeichenposition' wie bei der Einzelindizierung oder die "Kantenposition" wie bei zwei ganzzahligen Argumenten sein. Die Differenz wird dadurch bestimmt, ob der Doppelpunktbereich oder der Dreifachpunktbereich verwendet wird:
Wenn Sie diese Beispiele noch einmal durchgehen und darauf bestehen, die Einzelindexsemantik für die Doppel- oder Bereichsindizierungsbeispiele zu verwenden, werden Sie nur verwirrt. Sie müssen die alternative Nummerierung verwenden, die ich im ASCII-Diagramm zeige, um das tatsächliche Verhalten zu modellieren.
quelle
Ich bin damit einverstanden, dass dies wie ein seltsames Verhalten erscheint, aber selbst die offizielle Dokumentation zu
Array#slice
zeigt dasselbe Verhalten wie in Ihrem Beispiel in den folgenden "Sonderfällen":Leider
Array#slice
scheint selbst ihre Beschreibung von keinen Einblick zu geben, warum es so funktioniert:quelle
Eine Erklärung von Jim Weirich
quelle
Betrachten Sie das folgende Array:
Sie können ein Element am Anfang (Kopf) des Arrays einfügen, indem Sie es zuweisen
a[0,0]
. Verwenden Sie, um das Element zwischen"a"
und zu"b"
setzena[1,0]
. Im Wesentlichen in der Notationa[i,n]
,i
stellt einen Index undn
eine Anzahl von Elementen. Wennn=0
, definiert es eine Position zwischen den Elementen des Arrays.Wenn Sie nun über das Ende des Arrays nachdenken, wie können Sie ein Element mit der oben beschriebenen Notation an sein Ende anhängen? Einfach, weisen Sie den Wert zu
a[3,0]
. Dies ist das Ende des Arrays.Wenn Sie also versuchen, auf das Element unter zuzugreifen
a[3,0]
, erhalten Sie[]
. In diesem Fall befinden Sie sich immer noch im Bereich des Arrays. Wenn Sie jedoch versuchen, darauf zuzugreifena[4,0]
, erhalten Sie einennil
Rückgabewert, da Sie sich nicht mehr im Bereich des Arrays befinden.Weitere Informationen finden Sie unter http://mybrainstormings.wordpress.com/2012/09/10/arrays-in-ruby/ .
quelle
tl; dr: Im Quellcode in
array.c
werden verschiedene Funktionen aufgerufen, je nachdem, ob Sie 1 oder 2 Argumente übergeben,Array#slice
was zu unerwarteten Rückgabewerten führt.(Zunächst möchte ich darauf hinweisen, dass ich nicht in C codiere, sondern Ruby seit Jahren verwende. Wenn Sie also nicht mit C vertraut sind, nehmen Sie sich ein paar Minuten Zeit, um sich mit den Grundlagen vertraut zu machen Bei Funktionen und Variablen ist es wirklich nicht so schwer, dem Ruby-Quellcode zu folgen, wie unten gezeigt. Diese Antwort basiert auf Ruby v2.3, ist aber mehr oder weniger dieselbe wie in v1.9.)
Szenario 1
array.length == 4; array.slice(4) #=> nil
Wenn Sie sich den Quellcode für
Array#slice
(rb_ary_aref
) ansehen, sehen Sie, dass, wenn nur ein Argument übergeben wird ( Zeilen 1277-1289 ),rb_ary_entry
aufgerufen wird und der Indexwert übergeben wird (der positiv oder negativ sein kann).rb_ary_entry
Berechnet dann die Position des angeforderten Elements vom Anfang des Arrays an (mit anderen Worten, wenn ein negativer Index übergeben wird, berechnet er das positive Äquivalent) und ruft dannrb_ary_elt
auf, um das angeforderte Element abzurufen.Wie erwartet,
rb_ary_elt
kehrt ,nil
wenn die Länge des Arrayslen
ist kleiner oder gleich den Index (hier genanntoffset
).Szenario 2
array.length == 4; array.slice(4, 0) #=> []
Wenn jedoch 2 Argumente übergeben werden (dh der Startindex
beg
und die Länge des Slicelen
),rb_ary_subseq
wird aufgerufen.In
rb_ary_subseq
Wenn der Startindexbeg
ist größer als die Arraylängealen
,nil
wird zurückgegeben:Andernfalls wird die Länge des resultierenden Slice
len
berechnet. Wenn der Wert Null ist, wird ein leeres Array zurückgegeben:Da der Startindex von 4 nicht größer als ist
array.length
, wird anstelle desnil
erwarteten Werts ein leeres Array zurückgegeben .Frage beantwortet?
Wenn die eigentliche Frage hier nicht "Welcher Code verursacht dies?" Ist, sondern "Warum hat Matz das so gemacht?", Müssen Sie ihm bei der nächsten RubyConf und nur eine Tasse Kaffee kaufen Frag ihn.
quelle