Wie gibt Ruby zwei Werte zurück?

93

Immer wenn ich Werte in einem Array austausche, stelle ich sicher, dass ich einen der Werte in einer Referenzvariablen gespeichert habe. Ich habe jedoch festgestellt, dass Ruby zwei Werte zurückgeben und automatisch zwei Werte austauschen kann. Beispielsweise,

array = [1, 3, 5 , 6 ,7]
array[0], array[1] = array[1] , array[0] #=> [3, 1] 

Ich habe mich gefragt, wie Ruby das macht.

Pete
quelle
9
Technisch gesehen gibt Ruby keine zwei Werte zurück. Es kann ein Array zurückgeben, das wiederum zwei Variablen zugewiesen wird.
Charles Caldwell

Antworten:

163

Im Gegensatz zu anderen Sprachen ist der Rückgabewert eines Methodenaufrufs in Ruby immer ein Objekt. Dies ist möglich, weil es wie alles in Ruby nilselbst ein Objekt ist.

Es gibt drei Grundmuster, die Sie sehen werden. Keinen bestimmten Wert zurückgeben:

def nothing
end

nothing
# => nil

Rückgabe eines singulären Wertes:

def single
  1
end

x = single
# => 1

Dies entspricht den Erwartungen anderer Programmiersprachen.

Beim Umgang mit mehreren Rückgabewerten sieht es etwas anders aus. Diese müssen explizit angegeben werden:

def multiple
  return 1, 2
end

x = multiple
# => [ 1, 2 ]
x
# => [ 1, 2 ]

Wenn Sie einen Aufruf tätigen, der mehrere Werte zurückgibt, können Sie diese in unabhängige Variablen aufteilen:

x, y = multiple
# => [ 1, 2 ]
x
# => 1
y
# => 2

Diese Strategie funktioniert auch für die Art der Substitution, von der Sie sprechen:

a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1
Tadman
quelle
8
Sie können ein Array explizit zurückgeben, [1, 2]und dies funktioniert genauso wie in den obigen Beispielen.
Hauleth
6
@hauleth Eine gute Beobachtung. Ich hätte klarstellen sollen, dass es 1,2an sich ungültig ist, aber entweder von return 1,2oder [1,2]funktioniert.
Tadman
50

Nein, Ruby unterstützt die Rückgabe von zwei Objekten nicht. (Übrigens: Sie geben Objekte zurück, keine Variablen. Genauer gesagt, Sie geben Zeiger auf Objekte zurück.)

Es unterstützt jedoch die parallele Zuweisung. Wenn Sie mehr als ein Objekt auf der rechten Seite einer Aufgabe haben, werden die Objekte in einem Array:

foo = 1, 2, 3
# is the same as
foo = [1, 2, 3]

Wenn Sie mehr als ein "Ziel" (Variablen- oder Setter-Methode) auf der linken Seite einer Zuweisung haben, werden die Variablen an Elemente eines Arrayauf der rechten Seite gebunden :

a, b, c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary[2]

Wenn die rechte Seite ist nicht ein Array, wird es zu einem mit dem umgewandelten to_aryVerfahren

a, b, c = not_an_ary
# is the same as
ary = not_an_ary.to_ary
a = ary[0]
b = ary[1]
c = ary[2]

Und wenn wir die beiden zusammenfügen, bekommen wir das

a, b, c = d, e, f
# is the same as
ary = [d, e, f]
a = ary[0]
b = ary[1]
c = ary[2]

Damit verbunden ist der Splat-Operator auf der linken Seite einer Zuweisung. Es bedeutet "nimm alle übrig gebliebenen Elemente von Arrayauf der rechten Seite":

a, b, *c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary.drop(2) # i.e. the rest of the Array

Und zu guter Letzt können parallele Zuweisungen in Klammern verschachtelt werden:

a, (b, c), d = ary
# is the same as
a = ary[0]
b, c = ary[1]
d = ary[2]
# which is the same as
a = ary[0]
b = ary[1][0]
c = ary[1][1]
d = ary[2]

Wenn Sie returnvon einer Methode oder nextoder breakaus einem Block, wird Ruby - dieses Art-of wie die rechte Seite einer Zuweisung behandeln, so

return 1, 2
next 1, 2
break 1, 2
# is the same as
return [1, 2]
next [1, 2]
break [1, 2]

Dies funktioniert übrigens auch in Parameterlisten von Methoden und Blöcken (wobei Methoden strenger und Blöcke weniger streng sind):

def foo(a, (b, c), d) p a, b, c, d end

bar {|a, (b, c), d| p a, b, c, d }

Blöcke, die "weniger streng" sind, machen zum Beispiel die Hash#eachArbeit aus. Es tatsächlich yieldsa einziges Zwei-Element Arrayvon Schlüsseln und Wert in den Block, aber wir in der Regel schreiben

some_hash.each {|k, v| }

anstatt

some_hash.each {|(k, v)| }
Jörg W Mittag
quelle
16

tadman und Jörg W Mittag kennen Ruby besser als ich und ihre Antworten sind nicht falsch, aber ich glaube nicht, dass sie antworten, was OP wissen wollte. Ich denke, dass die Frage nicht klar war. Nach meinem Verständnis hat das, was OP fragen wollte, nichts mit der Rückgabe mehrerer Werte zu tun.


Die eigentliche Frage ist, wenn Sie die Werte von zwei Variablen aund b(oder zwei Positionen in einem Array wie in der ursprünglichen Frage) ändern möchten , warum es nicht erforderlich ist, eine zeitliche Variable tempwie die folgende zu verwenden:

a, b = :foo, :bar
temp = a
a = b
b = temp

kann aber direkt gemacht werden wie:

a, b = :foo, :bar
a, b = b, a

Die Antwort ist, dass bei Mehrfachzuweisungen die gesamte rechte Seite vor der Zuweisung der gesamten linken Seite ausgewertet wird und nicht einzeln ausgeführt wird. Ist a, b = b, aalso nicht gleichbedeutend mit a = b; b = a.

Die erste Bewertung der gesamten rechten Seite vor der Zuweisung ist eine Notwendigkeit, die sich aus der Anpassung ergibt, wenn beide Seiten =unterschiedliche Anzahlen von Begriffen haben, und die Beschreibung von Jörg W Mittag kann indirekt damit zusammenhängen, aber das ist nicht das Hauptproblem.

sawa
quelle
8

Arrays sind eine gute Option, wenn Sie nur wenige Werte haben. Wenn Sie mehrere Rückgabewerte wünschen, ohne die Reihenfolge der Ergebnisse kennen zu müssen (und durch diese verwirrt zu sein), besteht eine Alternative darin, einen Hash zurückzugeben, der die gewünschten benannten Werte enthält.

z.B

def make_hash
  x = 1
  y = 2
  {x: x, y: y}
end

hash = make_hash
# => {:x=>1, :y=>2}
hash[:x]
# => 1
hash[:y]
# => 2
pronoob
quelle