In che modo Ruby restituisce due valori?


94

Ogni volta che scambio i valori in un array, mi assicuro di aver memorizzato uno dei valori in una variabile di riferimento. Ma ho scoperto che Ruby può restituire due valori e scambiare automaticamente due valori. Per esempio,

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

Mi chiedevo come fa Ruby a farlo.


9
Tecnicamente Ruby non restituisce due valori. Può restituire un array che a sua volta viene assegnato a due variabili.
Charles Caldwell

Risposte:


164

A differenza di altri linguaggi, il valore di ritorno di qualsiasi chiamata al metodo in Ruby è sempre un oggetto. Questo è possibile perché, come tutto in Ruby, nilè esso stesso un oggetto.

Ci sono tre modelli di base che vedrai. Non restituendo alcun valore particolare:

def nothing
end

nothing
# => nil

Restituzione di un valore singolare:

def single
  1
end

x = single
# => 1

Questo è in linea con quello che ti aspetteresti da altri linguaggi di programmazione.

Le cose diventano leggermente diverse quando si tratta di più valori di ritorno. Questi devono essere specificati esplicitamente:

def multiple
  return 1, 2
end

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

Quando si effettua una chiamata che restituisce più valori, è possibile suddividerli in variabili indipendenti:

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

Questa strategia funziona anche per il tipo di sostituzione di cui parli:

a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1

8
Puoi esplicitamente restituire un array [1, 2]e questo funzionerà come i tuoi esempi sopra.
Hauleth

6
@hauleth Una buona osservazione. Avrei dovuto chiarire che di 1,2per sé non è valido, ma return 1,2o [1,2]funziona.
tadman

50

No, Ruby in realtà non supporta la restituzione di due oggetti. (BTW: restituisci oggetti, non variabili. Più precisamente, restituisci puntatori agli oggetti.)

Tuttavia, supporta l'assegnazione parallela. Se hai più di un oggetto sul lato destro di un compito, gli oggetti vengono raccolti in un Array:

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

Se hai più di un "obiettivo" (variabile o metodo setter) sul lato sinistro di un compito, le variabili vengono associate agli elementi di un Arraysul lato destro:

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

Se il lato destro non è un Array, verrà convertito in uno utilizzando il to_arymetodo

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]

E se mettiamo insieme i due, lo otteniamo

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

Correlato a questo è l'operatore splat sul lato sinistro di un compito. Significa "prendi tutti gli elementi rimanenti del Arraylato destro":

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

E, ultimo ma non meno importante, le assegnazioni parallele possono essere annidate usando le parentesi:

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]

Quando si returnda un metodo o nexto breakda un blocco, Ruby tratterà di questo tipo, di come il lato destro di una cessione, in modo

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

A proposito, questo funziona anche negli elenchi di parametri di metodi e blocchi (con metodi più rigidi e blocchi meno rigidi):

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

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

Il fatto che i blocchi siano "meno rigidi" è ad esempio ciò che fa Hash#eachfunzionare. In realtà è yieldun singolo elemento a due elementi Arraychiave e valore per il blocco, ma di solito scriviamo

some_hash.each {|k, v| }

invece di

some_hash.each {|(k, v)| }

16

tadman e Jörg W Mittag conoscono Ruby meglio di me e le loro risposte non sono sbagliate, ma non credo che stiano rispondendo a ciò che OP voleva sapere. Penso che la domanda non fosse chiara però. A quanto mi risulta, ciò che OP voleva chiedere non ha nulla a che fare con la restituzione di più valori.


La vera domanda è, quando vuoi cambiare i valori di due variabili ae b(o due posizioni in un array come nella domanda originale), perché non è necessario usare una variabile temporale tempcome:

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

ma può essere fatto direttamente come:

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

La risposta è che nell'assegnazione multipla, l'intero lato destro viene valutato prima dell'assegnazione dell'intero lato sinistro, e non viene fatto uno per uno. Quindi a, b = b, anon è equivalente aa = b; b = a .

Innanzitutto valutare l'intero lato destro prima dell'assegnazione è una necessità che segue dall'adeguamento quando entrambi i lati di =hanno un numero diverso di termini, e la descrizione di Jörg W Mittag può essere indirettamente correlata a questo, ma non è questo il problema principale.


8

Gli array sono una buona opzione se hai solo pochi valori. Se vuoi più valori di ritorno senza dover conoscere (ed essere confuso da) l'ordine dei risultati, un'alternativa potrebbe essere quella di restituire un hash che contiene tutti i valori nominati che desideri.

per esempio

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.