分解とは、ArrayやHashのようなコレクションから要素を取り出す操作のことです。分解した値は、同じ文の中で変数に代入できます。
多重代入とは、1つの文の中で複数の変数に値を分解して代入する機能です。これによりコードはより簡潔で読みやすくなり、代入する変数はfirst, second, third = [1, 2, 3]のようにカンマで区切って並べます。
分解の場面では、スプラット演算子(*)と二重スプラット演算子(**)がよく使われます。スプラット演算子(*)を使うと、複数の配列をそれぞれ_分解_して、1つの新しい共通の配列にまとめられます。二重スプラット演算子(**)を使うと、複数のハッシュをそれぞれ_分解_して、1つの新しい共通のハッシュにまとめられます。これは、複数の値を受け取る位置引数と、任意の数のキーワード引数を受け取る書き方を区別するための構文です。
スプラット演算子(*)をコレクションを伴わずに使うと、複数の値を1つの配列に_まとめます_(合成します)。これは多重代入でよく使われ、個別に代入されなかった「残りの」要素を1つの変数にまとめることができます。
Rubyでは、任意の数の位置引数やキーワード引数を受け取るメソッドを使ったり定義したりするときに、この分解と合成の仕組みをよく使います。引数はdef some_method(*arguments, **keyword_arguments)のように定義し、some_method(*some_array, **some_hash)のように渡すのを、よく目にするでしょう。
*<variable_name>と**<variable_name>を、*や**と混同しないでください。*と**はそれぞれ乗算とべき乗に使われますが、*<variable_name>と**<variable_name>は合成と分解の演算子として使われます。
多重代入を使うと、1行で複数の変数に代入できます。値はカンマ,で区切ります。
>> a, b = 1, 2
=> [1, 2]
>> a
=> 1
多重代入は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]
これは「並列代入」とも呼ばれ、一時変数を避けるために使えます。
変数の数が値より多い場合、余った変数には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
合成とは、複数の値を1つの配列にまとめて変数に代入する機能です。値を_分解_し、変更を加えてから、その結果を再び_合成_して変数に戻したいときに便利です。また、2つ以上の配列やハッシュをマージすることもできます。
*)を使った配列の合成配列の合成は、スプラット演算子(*)で行えます。これはすべての値を1つの配列にまとめます。
>> 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"]
**)を使ったハッシュの合成ハッシュの合成は、二重スプラット演算子(**)で行います。これはあるハッシュのすべてのキーと値のペアを別のハッシュにまとめたり、2つのハッシュを結合したりします。
>> 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の前後に引数を書いて、特定の位置引数を指定することもできます。これは配列の分解と同じように機能します。
引数は決まった順序で並べる必要があります。
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