学习路径
/
Ruby
Ruby
/
教学大纲
/
多重赋值与解构
多重

多重赋值与解构 属于 Ruby

1 个练习

关于 多重赋值与解构

解构是指从集合(例如 Array 或 Hash)中提取元素的操作。解构出来的值可以在同一条语句中赋给变量。

多重赋值是指在一条语句中把解构出来的值赋给多个变量。这样可以让代码更简洁易读,做法是用逗号分隔待赋值的变量,例如 first, second, third = [1, 2, 3]。

展开运算符(*)和双展开运算符(**)常用于解构的场景。展开运算符(*)可以把多个数组合并成一个数组,做法是把每个数组都_解构_进一个新的公共数组。双展开运算符(**)可以把多个哈希合并成一个哈希,做法是把每个哈希都_解构_进一个新的公共哈希。这种语法用于区分两种情况:作为位置实参接受多个值,以及接受任意多个关键字实参。

当展开运算符(*)不作用于集合时,它会把若干个值_打包_(也就是组合)成一个数组。这在多重赋值中很常用,用来把所有没有单独赋值的“剩余”元素归到一个变量里。

在 Ruby 中,使用或定义接受任意数量位置实参或关键字实参的方法时,经常会用到这种解构/组合的写法。你常常会看到这些实参被定义为 def some_method(*arguments, **keyword_arguments),并以 some_method(*some_array, **some_hash) 的方式使用。

Caution

*<variable_name> 和 **<variable_name> 不要与 * 和 ** 混淆。* 和 ** 分别用于乘法和幂运算,而 *<variable_name> 和 **<variable_name> 则用作组合与解构运算符。

多重赋值

多重赋值让你可以在一行里给多个变量赋值。值之间用逗号,分隔:

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

多重赋值不限于一种数据类型:

>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true

多重赋值可以用来交换数组中的元素。这种做法在排序算法中非常常见。例如:

>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Note

这也称为“并行赋值”,可以用来避免使用临时变量。

如果变量的数量多于值,多出来的变量会被赋值为nil:

>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil

如果值的数量多于变量,多出来的值会被忽略:

>> a, b, c = 1, 2, 3, 4
>> b
=> 2
>> c
=> 3

解构

在 Ruby 中,可以把数组/哈希的元素解构到不同的变量里。由于数组中的值按顺序排列,它们会按相同的顺序被拆分到各个变量中:

>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"

如果有不需要的值,可以用_表示“已收集但不使用”:

>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"

深层解构

从数组里的数组(也就是嵌套数组)中解构并赋值,方式和浅层解构相同,但需要用带定界符的解构表达式(())来明确值的上下文或位置:

>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"

你也可以只深度拆分嵌套数组的一部分:

>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"

如果解构中变量的位置不正确,或者值的数量不对,就会得到语法错误:

fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
(a, b), (d) = fruits_vegetables

syntax error, unexpected ')', expecting '.' or &. or :: or '['
((a, b), (d)) = fruits_vegetables

在这里动手试试,你会发现起决定作用的是第一个模式,而不是右侧可用的值。这个语法错误与数据结构无关。

用单个展开运算符(*)解构数组

[解构数组][decomposition]时,可以使用展开运算符(*)捕获“剩余”的值。这比切片数组更清晰(切片在某些情况下可读性较差)。例如,我们可以取出第一个元素,然后把剩余的值赋给一个不含第一个元素的新数组:

>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]

也可以取出数组开头和结尾的值,同时把中间的所有值归为一组:

>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]

在深层解构中也可以使用 *:

>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]

解构 Hash

解构哈希与解构数组稍有不同。要拆分哈希,需要先把它转换成一个数组。否则无法解构:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil

要把 Hash 转换成数组,可以使用to_a方法:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> fruits_inventory.to_a
=> [[:apple, 6], [:banana, 2], [:cherry, 3]]
>> x, y, z = fruits_inventory.to_a
>> x
=> [:apple, 6]

如果想拆分出键,可以使用keys方法:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple

如果想拆分出值,可以使用values方法:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6

组合

组合是指把多个值归入一个数组并赋给变量的能力。当你想要_解构_值、做些修改,再把结果_组合_回一个变量时,这很有用。它还可以把 2 个或更多数组/哈希合并起来。

用展开运算符(*)组合数组

组合数组可以用展开运算符(*)完成。它会把所有的值打包进一个数组。

>> fruits = ["apple", "banana", "cherry"]
>> more_fruits = ["orange", "kiwi", "melon", "mango"]

# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>> combined_fruits = *fruits, *more_fruits

>> combined_fruits
=> ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]

用双展开运算符(**)组合哈希

组合哈希使用双展开运算符(**)完成。它会把一个哈希中的所有键/值对打包进另一个哈希,或者把两个哈希合并在一起。

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> more_fruits_inventory = {orange: 4, kiwi: 1, melon: 2, mango: 3}

# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}

# then the pairs are packed into combined_fruits_inventory
>> combined_fruits_inventory
=> {:apple=>6, :banana=>2, :cherry=>3, :orange=>4, :kiwi=>1, :melon=>2, :mango=>3}

在方法中使用展开运算符(*)和双展开运算符(**)

用方法形参进行组合

创建接受任意数量实参的方法时,可以在方法定义中使用 *arguments或**keyword_arguments。*arguments 用来打包任意数量的位置(非关键字)实参,**keyword_arguments 用来打包任意数量的关键字实参。

使用 *arguments:

# This method is defined to take any number of positional arguments
# (Using the single line form of the definition of a method.)

>> def my_method(*arguments)= arguments

# Arguments given to the method are packed into an array

>> my_method(1, 2, 3)
=> [1, 2, 3]

>> my_method("Hello")
=> ["Hello"]

>> my_method(1, 2, 3, "Hello", "Mars")
=> [1, 2, 3, "Hello", "Mars"]

使用 **keyword_arguments:

# This method is defined to take any number of keyword arguments

>> def my_method(**keyword_arguments)= keyword_arguments

# Arguments given to the method are packed into a dictionary

>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}

如果定义的方法没有任何为关键字实参定义的形参(**keyword_arguments或<key_word>: <value>),那么关键字实参会被打包进一个哈希,并赋给最后一个形参。

>> def my_method(a)= a

>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}

*arguments 和 **keyword_arguments 也可以搭配使用:

def my_method(*arguments, **keyword_arguments)
  p arguments.sum
  for (key, value) in keyword_arguments.to_a
    p key.to_s + " = " + value.to_s
  end
end


my_method(1, 2, 3, a: 1, b: 2, c: 3)
6
"a = 1"
"b = 2"
"c = 3"

你也可以在 *arguments 前后写实参,以指定特定的位置实参。这和数组的解构方式相同。

Caution

实参必须按特定的顺序排列:

def my_method(<positional_arguments>, *arguments, <positional_arguments>, <key-word_arguments>, **keyword_arguments)

如果不遵循这个顺序,就会出错。

def my_method(a, b, *arguments)
  p a
  p b
  p arguments
end

my_method(1, 2, 3, 4, 5)
1
2
[3, 4, 5]

你可以在 *arguments 前后写位置实参:

>> def my_method(a, *middle, b)= middle

>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]

你也可以组合位置实参、*arguments、关键字实参和 **keyword_arguments:

>> def my_method(first, *many, last, a:, **keyword_arguments)
     p first
     p many
     p last
     p a
     p keyword_arguments
     end

>> my_method(1, 2, 3, 4, 5, a: 6, b: 7, c: 8)
1
[2, 3, 4]
5
6
{:b => 7, :c => 8}

按错误的顺序写实参会导致错误:

def my_method(a:, **keyword_arguments, first, *arguments, last)
  arguments
end

my_method(1, 2, 3, 4, a: 5)

syntax error, unexpected local variable or method, expecting & or '&'
... my_method(a:, **keyword_arguments, first, *arguments, last)

向方法调用中解构

你可以用展开运算符(*)把实参组成的数组拆分到方法调用中:

def my_method(a, b, c)
  p c
  p b
  p a
end

numbers = [1, 2, 3]
my_method(*numbers)
3
2
1

你也可以用双展开运算符(**)把实参组成的哈希拆分到方法调用中:

def my_method(a:, b:, c:)
  p c
  p b
  p a
end

numbers = {a: 1, b: 2, c: 3}
my_method(**numbers)
3
2
1
通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 多重赋值与解构