はじめに

この記事は書籍「プロを目指す人のためのRuby入門」に掲載できなかったトピックを著者自らが紹介するアドベントカレンダーの8日目です。
本文に出てくる章番号や項番号は書籍の中で使われている番号です。

今回はよく使われる配列の便利メソッドを紹介していきます。

必要な前提知識

「プロを目指す人のためのRuby入門」の第4章まで読み終わっていること。

よく使われる配列の便利メソッド

配列には便利なメソッドがたくさんあります。本当ならすべて紹介したいのですが、そうするといつまでたっても終わらないので、ここでは特に利用頻度の高い以下のメソッドを紹介します。

  • compact/compact!
  • empty?
  • flatten/flatten!/flat_map/collect_concat
  • index/find_index
  • include?
  • join
  • min/max/minmax
  • reverse/reverse_each
  • shuffle/shuffle!/sample
  • sort/sort!
  • sum(Ruby 2.4以降)
  • transpose
  • zip
  • uniq/uniq!
  • Array()

なお、!が付くメソッドはいずれも破壊的メソッドです。新しい配列を返すのではなく、そのメソッドを呼びだした配列自身の中身が変更されます。

compact/compact!

compactメソッドはnilの要素を取り除いた配列を新しく生成して返します。

a = [1, nil, 'abc', false]
b = a.compact
b #=> [1, 'abc', false]

empty?

配列の要素数が0の場合にtrueを返します。

[1, 2, 3].empty?  #=> false
[].empty?         #=> true

flatten/flatten!/flat_map/collect_concat

flattenメソッドは入れ子になった配列を平坦にした新しい配列を返します。

a = [[1, 2], [3, 4], [5, 6]]
a.flatten #=> [1, 2, 3, 4, 5, 6]

flat_mapメソッド(エイリアスメソッドはcollect_concat)はmapメソッドとflattenを組み合わせたようなメソッドです。たとえば以下のコードの結果は配列の配列が返ります。

sentences = ['Ruby is fan', 'I love Ruby']
sentences.map { |s| s.split(' ') } #=> [["Ruby", "is", "fan"], ["I", "love", "Ruby"]]

一方、flat_mapメソッドを使うとネストしないフラットな配列が返ります。

sentences = ['Ruby is fan', 'I love Ruby']
sentences.flat_map { |s| s.split(' ') } #=> ["Ruby", "is", "fan", "I", "love", "Ruby"]

index/find_index

indexメソッド(エイリアスメソッドはfind_index)は引数と一致する要素が最初に見つかったときの添え字を返します。

a = [10, 20, 30, 10, 20, 30]
a.index(30) #=> 2

ブロックを使うと、ブロックの戻り値が最初に真になった要素の添え字を返します。

a = [10, 20, 30, 10, 20, 30]
a.index { |n| n >= 20 } #=> 1

include?

include?メソッドは引数と等しい要素が配列に含まれている場合にtrueを返します。

a = [10, 20, 30]
a.include?(20) #=> true
a.include?(50) #=> false

join

joinメソッドは配列の各要素を1つに連結した文字列を返します。

words = ['apple', 'melon', 'orange']
words.join #=> "applemelonorange"

引数に文字列を渡すとその文字列が間に挟み込まれます。

words = ['apple', 'melon', 'orange']
words.join(', ') #=> "apple, melon, orange"

min/max/minmax

minメソッドは配列の中で最小の要素を、maxメソッドは最大の要素をそれぞれ返します。

a = [3, 6, 2, 1, 5, 4]
a.min #=> 1
a.max #=> 6

minmaxメソッドは最小の要素と最大の要素を配列に入れて返します。

a = [3, 6, 2, 1, 5, 4]
a.minmax #=> [1, 6]

reverse/reverse!/reverse_each

reverseメソッドは配列の要素を逆に並び替えた新しい配列を返します。

a = [1, 2, 3, 4]
a.reverse #=> [4, 3, 2, 1]

reverse_eachメソッドは逆順に繰り返し処理を行うメソッドです。

a = [1, 2, 3, 4]
a.reverse_each.map { |n| n * 10 } #=> [40, 30, 20, 10]

shuffle/shuffle!/sample

shuffleメソッドは配列の要素をランダムに並び替えるメソッドです。

a = [1, 2, 3, 4, 5 ,6]
a.shuffle #=> [2, 4, 5, 6, 3, 1]

sampleメソッドは配列からランダムに1つの要素を取り出します。引数を渡すと、その個数だけランダムに取り出した要素(ただし重複したインデックスは選択しない)を返します。

a = [1, 2, 3, 4, 5 ,6]
a.sample #=> 2
a.sample(3) #=> [3, 5, 4]

sort/sort!

sortメソッドは配列の要素を順番に並び替えた新しい配列を返します。

a = [5, 2, 6, 3, 1, 4]
a.sort #=> [1, 2, 3, 4, 5, 6]

sum(Ruby 2.4以降)

Ruby 2.4ではsumメソッドが追加されました。
これにより、配列の中身の合計値を求めたりできます。

[1, 2, 3, 4].sum
#=> 10

デフォルトの初期値は0ですが、引数で変更できます。

[].sum
#=> 0

[1, 2, 3, 4].sum(5)
#=> 15

小数の合計値は丸め誤差が発生するので注意してください。

[0.1, 0.1, 0.1].sum
#=> 0.30000000000000004

Ruby 2.3以前ではinjectを使うイディオムがよく使われていました。

# injectで合計値を求める
[1, 2, 3, 4].inject(:+)
#=> 10

# 空の配列だとnilが返る
[].inject(:+)
#=> nil

# nilだと困る場合は初期値を第1引数に渡す
[].inject(0, :+)
#=> 0

文字列の配列を連結する場合は初期値に何らかの文字列を渡します。

['foo', 'bar'].sum('')
#=> 'foobar'

['foo', 'bar'].sum('>>')
#=> '>>foobar'

配列の配列を連結することも可能です。

[[1, 2], [3, 1, 5]].sum([])
#=> [1, 2, 3, 1, 5]

uniq/uniq!

uniqメソッドは配列の中から重複を取り除いた新しい配列を返します。

a = [1, 1, 2, 2, 3, 4]
a.uniq #=> [1, 2, 3, 4]

transpose

transposeメソッドは配列の配列を行列として見立て、行と列を入れ替えた配列を返します。

a = [[10, 20], [30, 40], [50, 60]]
a.transpose #=> [[10, 30, 50], [20, 40, 60]]

zip

zipメソッドは自身の要素と、引数で与えた配列の要素を同じインデックス同士で組み合わせた配列を返します。

# テストに回答した生徒の名前
names = ['Alice', 'Bob', 'Carol']
# 各生徒の点数
points = [80, 90, 60]
# 名前と点数を組み合わせる
names.zip(points) #=> [["Alice", 80], ["Bob", 90], ["Carol", 60]]

上の例であれば、ここからさらにto_hメソッドを使うとハッシュに変換でき、生徒の名前で点数を取得することができます。

point_table = names.zip(points).to_h #=> {"Alice"=>80, "Bob"=>90, "Carol"=>60}
point_table['Alice'] #=> 80
point_table['Carol'] #=> 60

なお、to_hメソッドやハッシュについては第5章で詳しく説明します。

zipメソッドの引数は可変長引数になっているので、2つ以上の配列を渡すこともできます。

lengths = [1, 2, 3]
widths = [10, 20, 30]
heights = [100, 200, 300]
lengths.zip(widths, heights) #=> [[1, 10, 100], [2, 20, 200], [3, 30, 300]]

Array()

ArrayメソッドはKernelモジュール(モジュールについては第8章で説明します)に定義されているメソッドで、どこからでも呼び出すことができます。Arrayメソッドを使うと、引数に応じて次のような配列を返します。

  • nilなら空の配列を返す
  • 数値や文字列であれば、それを要素とする配列を返す
  • 配列は配列のまま返す

厳密には引数のオブジェクトがto_aryメソッドまたはto_aメソッドを持っていればその結果を、持っていなければオブジェクトを配列に入れて返す、というのがArrayメソッドの仕様なのですが、上のように理解しておけばおおむね大丈夫です。

Arrayメソッドの実行例は以下の通りです。

# 配列を渡すと配列が返る
Array([1, 2, 3])
#=> [1, 2, 3]

# nilなら空の配列が返る
Array(nil)
#=> []

# 数値や文字列であればそれが要素になった配列が返る
Array(1)
#=> [1]

Array('abc')
#=> ["abc"]

この性質を利用すると、必ずしも配列とは限らない変数や引数を配列として扱うことができます。

# 引数で渡された文字列に含まれる単語をシャッフルして返す
def shuffle_words(sentences)
  # 条件分岐しなくても引数を配列として扱える
  Array(sentences)
    .flat_map { |s| s.split(' ') }
    .shuffle
    .join(' ')
end

# 配列を渡す
shuffle_words(['Ruby is fun', 'I love Ruby'])
#=> "is fun love I Ruby Ruby"

# 配列ではなくnilを渡す
shuffle_words(nil)
#=> ""

# 配列ではなく文字列を渡す
shuffle_words('Ruby is fun')
#=> "fun Ruby is"

余談:Array()は珍しい大文字で始まるメソッド

Rubyのメソッドは小文字で始めるのが慣習になっていますが、このメソッドは慣習に反して大文字から始まっています。そのため、Arrayクラスと名前が重複するのですが、Rubyのインタプリタは引数や丸括弧がなければクラスオブジェクトとして、引数や丸括弧があればメソッドとして解釈します。

# 引数や丸括弧がなければArrayクラスと見なす
Array
#=> Array
Array.class
#=> Class

# 引数や丸括弧があればArrayメソッドと見なす
Array 'abc'
#=> ["abc"]
Array(1)
#=> [1]

次回予告

次回は*と変数の代入の関係を説明します。