Làm thế nào để Ruby trả về hai giá trị?


94

Bất cứ khi nào tôi hoán đổi các giá trị trong một mảng, tôi đảm bảo rằng tôi đã lưu trữ một trong các giá trị trong một biến tham chiếu. Nhưng tôi thấy rằng Ruby có thể trả về hai giá trị cũng như tự động hoán đổi hai giá trị. Ví dụ,

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

Tôi đã tự hỏi làm thế nào Ruby làm điều này.


9
Về mặt kỹ thuật, Ruby không trả về hai giá trị. Nó có thể trả về một mảng mà lần lượt được gán cho hai biến.
Charles Caldwell

Câu trả lời:


164

Không giống như các ngôn ngữ khác, giá trị trả về của bất kỳ lệnh gọi phương thức nào trong Ruby luôn là một đối tượng. Điều này là có thể bởi vì, giống như mọi thứ trong Ruby, nilbản thân nó là một đối tượng.

Có ba mẫu cơ bản bạn sẽ thấy. Không trả về giá trị cụ thể:

def nothing
end

nothing
# => nil

Trả về một giá trị số ít:

def single
  1
end

x = single
# => 1

Điều này phù hợp với những gì bạn mong đợi từ các ngôn ngữ lập trình khác.

Mọi thứ sẽ khác một chút khi xử lý nhiều giá trị trả về. Chúng cần phải được chỉ định rõ ràng:

def multiple
  return 1, 2
end

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

Khi thực hiện cuộc gọi trả về nhiều giá trị, bạn có thể chia chúng thành các biến độc lập:

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

Chiến lược này cũng hoạt động đối với các loại thay thế mà bạn đang nói đến:

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

8
Bạn có thể trả về một cách rõ ràng một mảng [1, 2]và điều này sẽ hoạt động giống như các ví dụ của bạn ở trên.
Hauleth

6
@hauleth Một quan sát tốt. Tôi nên nói rõ rằng 1,2bản thân nó không hợp lệ, nhưng một trong hai return 1,2hoặc [1,2]hoạt động.
tadman

50

Không, Ruby không thực sự hỗ trợ trả về hai đối tượng. (BTW: bạn trả về các đối tượng, không phải biến. Chính xác hơn, bạn trả về con trỏ cho các đối tượng.)

Tuy nhiên, nó hỗ trợ gán song song. Nếu bạn có nhiều hơn một đối tượng ở bên phải của một phép gán, các đối tượng được thu thập vào một Array:

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

Nếu bạn có nhiều hơn một "target" (biến hoặc phương thức setter) ở phía bên trái của một phép gán, các biến sẽ bị ràng buộc với các phần tử của một Arrayở phía bên phải:

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

Nếu phía bên phải không phải là một Array, nó sẽ được chuyển đổi thành một bằng cách sử dụng to_aryphương thức

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]

Và nếu chúng ta kết hợp cả hai lại với nhau, chúng ta sẽ có

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

Liên quan đến điều này là toán tử biểu tượng ở phía bên trái của một phép gán. Nó có nghĩa là "lấy tất cả các phần tử Arraybên trái của bên tay phải":

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

Và cuối cùng nhưng không kém phần quan trọng, các phép gán song song có thể được lồng vào nhau bằng cách sử dụng dấu ngoặc đơn:

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]

Khi bạn returntừ một phương pháp hay nexthoặc breaktừ một khối, Ruby sẽ đối xử tử tế của việc này như phía bên tay phải của một bài tập, vì vậy

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

Nhân tiện, điều này cũng hoạt động trong danh sách tham số của các phương thức và khối (với các phương thức nghiêm ngặt hơn và khối ít nghiêm ngặt hơn):

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

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

Ví dụ như các khối "ít nghiêm ngặt hơn" là điều tạo nên Hash#eachhiệu quả. Nó thực sự yieldsa đơn phần tử hai Arraychìa khóa và giá trị cho các khối, nhưng chúng ta thường ghi

some_hash.each {|k, v| }

thay vì

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

16

tadman và Jörg W Mittag biết Ruby nhiều hơn tôi, và câu trả lời của họ không sai, nhưng tôi không nghĩ rằng họ đang trả lời những gì OP muốn biết. Tôi nghĩ rằng câu hỏi không rõ ràng mặc dù. Theo hiểu biết của tôi, những gì OP muốn hỏi không liên quan gì đến việc trả về nhiều giá trị.


Câu hỏi thực sự là, khi bạn muốn chuyển giá trị của hai biến ab(hoặc hai vị trí trong một mảng như trong câu hỏi ban đầu), tại sao không cần sử dụng một biến tạm thời tempnhư:

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

nhưng có thể được thực hiện trực tiếp như:

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

Câu trả lời là trong nhiều nhiệm vụ, toàn bộ bên phải được đánh giá trước khi thực hiện toàn bộ bên trái, và nó không được thực hiện từng việc một. Vì vậy, a, b = b, akhông tương đương với a = b; b = a.

Đầu tiên, đánh giá toàn bộ phía bên phải trước khi chuyển nhượng là điều cần thiết sau khi điều chỉnh khi cả hai bên =có số lượng thuật ngữ khác nhau và mô tả của Jörg W Mittag có thể liên quan gián tiếp đến điều đó, nhưng đó không phải là vấn đề chính.


8

Mảng là một lựa chọn tốt nếu bạn chỉ có một vài giá trị. Nếu bạn muốn có nhiều giá trị trả về mà không cần phải biết (và bị nhầm lẫn bởi) thứ tự của kết quả, một giải pháp thay thế sẽ là trả về một Hash chứa bất kỳ giá trị được đặt tên nào mà bạn muốn.

ví dụ

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.