216
151

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RubyAdvent Calendar 2016

Day 18

Rubyの多重代入あれこれまとめ

Last updated at Posted at 2016-12-17

Ruby の多重代入はいろいろな書き方があって便利ですが、忘れがちなのでまとめておきます

変数への代入

何の変哲も無い代入

a = 1 #=> 1
a #=> 1

右辺が , 区切りで複数ある場合には配列に変換される

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

左辺も , 区切りで複数ある場合には配列に変換された上で、左辺の各変数に右辺の各要素が代入される

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

右辺の要素の数が左辺の変数の数に満たない場合には、右辺の残りの要素に nil が代入される

a, b = 1 #=> 1
a #=> 1
b #=> nil

右辺の要素の数の方が多い場合、余った要素は無視される

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

右辺の要素の残り全部を配列として受け取ることもできる

  • 変数に * をつけると、残り全部を配列として受け取れる
a, *b = 1, 2, 3 #=> [1, 2, 3]
a #=> 1
b #=> [2, 3]
  • 残りの要素数が 1 つでも配列になる
a, *b = 1, 2 #=> [1, 2]
a #=> 1
b #=> [2]
  • 残りの要素がない場合には nil ではなく空の配列 [] になる
a, *b = 1 #=> 1
a #=> 1
b #=> []

先頭だけ取って、あとは捨てる

  • 明示的に捨てる場合
a, * = 1, 2, 3 #=> [1, 2, 3]
a #=> 1
  • * を省略して , で終わっても、以降は捨てられる
a, = 1, 2, 3 #=> [1, 2, 3]
a #=> 1

末尾だけ取って、後は捨てる

*, b = 1, 2, 3 #=> [1, 2, 3]
b #=> 3

先頭と末尾だけ取って、後は捨てる

a, *, b = 1, 2, 3, 4 #=> [1, 2, 3, 4]
a #=> 1
b #=> 4

2個目と3個目だけ取って、後は捨てる

_, a, b, * = 1, 2, 3, 4 #=> [1, 2, 3, 4]
a #=> 2
b #=> 3
  • _ は普通の変数なので、実は捨てられていなくて、アクセスできる
  • pry などでは特殊な動きをするので注意

全部捨てる

* = 1, 2, 3 #=> [1, 2, 3]

えっ、全部捨てるなら、なんで代入にした?

ネスト

  • 要素が配列になっている場合、変数側に () を使うと、1レベル下の要素を代入することができる
a, (b, c) = 1, [2, 3] #=> [1, [2, 3]]
a #=> 1
b #=> 2
c #=> 3

Hash

  • Hash は通常では1つの値として代入される
a = { a: 1, b: 2 } #=> {:a=>1, :b=>2}
a #=> {:a=>1, :b=>2}
a, b = { a: 1, b: 2 } #=> {:a=>1, :b=>2}
a #=> {:a=>1, :b=>2}
b #=> nil
  • * をつけると Hash#to_a されて多重代入できるようになる
a = *{ a: 1, b: 2 } #=> [[:a, 1], [:b, 2]]
a #=> [[:a, 1], [:b, 2]]
a, b = *{ a: 1, b: 2 } #=> [[:a, 1], [:b, 2]]
a #=> [:a, 1]
b #=> [:b, 2]

ブロック引数への代入

多重代入の基礎が分かったら、ブロック引数への代入に応用してみましょう。

なんの変哲も無い代入

[1, 2, 3].map { |n| n } #=> [1, 2, 3]

配列をそのまま受け取る

  • この例では * は省略できる
[[1, 2], [3, 4]].map { |*a| a } #=> [[1, 2], [3, 4]]

先頭だけをいただく

[[1, 2], [3, 4]].map { |a,| a } #=> [1, 3]

足りなければ nil になる

[[1, 2], [3, 4]].map { |a, b, c| c } #=> [nil, nil]

ブロックの代わりに lambda を使う場合、引数の数に要素の数が満たないと死ぬ

[[1, 2], [3, 4]].map(&->(a, b, c) { c })
#=> ArgumentError: wrong number of arguments (given 1, expected 3)
  • ので、要素の数が足りない恐れがあるときには、さらに () で括ると「第一引数にArray を受け取って、ネストした代入をする」みたいな扱いになって通る
[[1, 2], [3, 4]].map(&->((a, b, c)) { [b, c] }) #=> [[2, nil], [4, nil]]

名前付き引数

  • 通常は Hash が渡ってきても多重代入にはならない
[{ a: 1, b: 2 }, { a: 10, b: 20 }].map { |a, b| [a, b] }
#=> [[{:a=>1, :b=>2}, nil], [{:a=>10, :b=>20}, nil]]
  • Hash#to_a を挟めば、それっぽくはなる
[{ a: 1, b: 2 }, { a: 10, b: 20 }].map(&:to_a).map { |a, b| [a, b] }
#=> [[[:a, 1], [:b, 2]], [[:a, 10], [:b, 20]]]
  • 名前付き引数を使うと、value だけを代入することができる
[{ a: 1, b: 2 }, { a: 10, b: 20 }].map { |a:, b:| [a, b] }
#=> [[1, 2], [10, 20]]
  • ただし、名前付き引数を使うときには、想定されない key が来ると死ぬ
[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |a:, b:| [a, b] }
#=> ArgumentError: unknown keyword: c
  • でも、最後に **x みたいなのを書けば、名前付きで指定していない部分の Hash がもらえる
[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |a:, b:, **x| [a, b, x] }
#=> [[1, 2, {}], [10, 20, {:c=>99}]]
  • 捨てたい時は変数名を省略して ** だけにする
[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |a:, b:, **| [a, b] }
#=> [[1, 2], [10, 20]]
  • 配列の最後の要素が Hash のときにもキーワード引数が使える
[[1, 2, 3, { a: 1, b: 2 }], [4, 5, 6, { a: 10, b: 20 }]].map { |*ary, a:, b:| [a, b] }
#=> [[1, 2], [10, 20]]
  • ネストの先で名前付き引数を使うことはできない
[[{ b: 10 }, 1]].map { |h, n| h } #=> [{:b=>10}]
[[{ b: 10 }, 1]].map { |(b:), n| b } #=> SyntaxError: unexpected '}', expecting end-of-input

Ruby 3.0 以降

Ruby 3.0 以降ではブロック引数で名前付きの引数は使えないので、代わりにパターンマッチを使ったりするしかないかもしれない

[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |h|
  h => {a:, b:}
  [a, b]
}
#=> [[1, 2], [10, 20]]

でも↑の例なら Hash#values_at の方が良さそう……

[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |h|
  h.values_at(:a, :b)
}
#=> [[1, 2], [10, 20]]

右代入

Ruby 3.0.0 に右代入が登場。これは熱い。

42 => n
n
#=> 42

でも多重代入しようとすると、だめっぽい

42, 42 => a, b
#=> -e:1: syntax error, unexpected ',', expecting end-of-input
#=> 42, 42 => a, b

多重代入したい時には配列を使うとできます

[42, 42] => [a, b] => c
a #=> 42
b #=> 42
c #=> [42,42]

右代入パターンマッチもあります

{ a: 42, b: 4242 } => { a:, b: }
a #=> 42
b #=> 4242

複雑なパターンでもいけます

[{ a: 42, b: 4242 }, { c: 424242, d: 42424242 }] => [{ a:, b: }, { c:, d: }]
a #=> 42
b #=> 4242
c #=> 424242
d #=> 42424242

まとめ

  • Ruby の代入の柔軟性の高さ凄い

おまけ

216
151
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
216
151

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?