組み込みクラス: 配列編

配列の初期化

普段は[1,2,3]を使っているが、Array[1,2,3]Array.new([1,2,3])で定義できる。 配列のすべての要素に初期値を設定したい場合はArray.new(10,"non")となるが、これは配列のすべての要素が同じインスタンスとなる。

irb(main):002:0> a = Array.new(10,"non")                                                                                    
=> ["non", "non", "non", "non", "non", "non", "non", "non", "non", "non"]
irb(main):003:0> a.each{|x| p  x.object_id}
70368393002380
70368393002380
70368393002380
70368393002380
70368393002380
70368393002380
70368393002380
70368393002380
70368393002380
70368393002380

配列の要素をすべて別々のオブジェクトにしたい場合は、ブロックに渡した評価後の値が配列の要素に格納されるようにすればよい。 このときコンストラクタに対して引数として、長さだけ渡しておく。

irb(main):004:0> b = Array.new(10){ "non"}
=> ["non", "non", "non", "non", "non", "non", "non", "non", "non", "non"]
irb(main):005:0> b.each{|x|p x.object_id}
70368392131920
70368392131880
70368392131840
70368392131800
70368392131780
70368392131720
70368392131700
70368392131660
70368392131640
70368392131520
=> ["non", "non", "non", "non", "non", "non", "non", "non", "non", "non"]

配列の要素追加

配列に要素を追加するメソッドは+を除いて、破壊的なメソッドである。

+ 破壊的なし

例外的に非破壊的なメソッドである。 連結させた後のobject_idが連結前と異なるのがわかる。

irb(main):006:0> c = Array([2,4,6])
=> [2, 4, 6]
irb(main):008:0> c.object_id
=> 70368393964140
irb(main):009:0> (c + [8,10]).object_id
=> 70368393876840

ちなみに、配列以外は連結できない。

irb(main):011:0> c + 20
Traceback (most recent call last):
        3: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
        2: from (irb):11
        1: from (irb):11:in `+'
TypeError (no implicit conversion of Integer into Array)

<< 破壊的

エイリアスpush

こちらは+と異なり、配列を引数に指定すると「1つの要素」とみなされて、ネストされた配列が返る。

irb(main):013:0> c << [9,10]
=> [2, 4, 6, [9, 10]]

ただし、エイリアスであるpushの場合に限り、多重代入ができるので、配列を連結するように要素の数を増やすことが出来る。 これは<<では指定できない。

irb(main):004:0> array.push(1,2,3)
=> [1, 2, 3, 1, 2, 3]
irb(main):006:0> array<<1,2,3
Traceback (most recent call last):
        1: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
SyntaxError ((irb):6: syntax error, unexpected ',', expecting end-of-input
array<<1,2,3
        ^)

concat 破壊的

一方こちらは引数に配列を指定して「連結」のできるメソッド。concatenateが語源。 配列以外のものは引数に指定できない。

irb(main):003:0> array.concat([1,2,3])
=> [1, 2, 3]
irb(main):005:0> array.concat(1)
Traceback (most recent call last):
        3: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
        2: from (irb):5
        1: from (irb):5:in `concat'
TypeError (no implicit conversion of Integer into Array)

insert 破壊的

第一引数で場所を、第二引数で要素を指定して、配列に追加する。 第二引数は可変長引数になっているので、複数指定できる。

irb(main):009:0> array = "BaseBallBear".split("")
=> ["B", "a", "s", "e", "B", "a", "l", "l", "B", "e", "a", "r"]
irb(main):010:0> array.insert(3,"9","m","m")
=> ["B", "a", "s", "9", "m", "m", "e", "B", "a", "l", "l", "B", "e", "a", "r"]

unshift 破壊的

先頭に追加するメソッド。このメソッドは引数が可変長でないため、多重代入も配列の指定も出来ない。

irb(main):011:0> array = %w|b a s e b a l l b e a r|
=> ["b", "a", "s", "e", "b", "a", "l", "l", "b", "e", "a", "r"]
irb(main):012:0> array.unshift(100)
=> [100, "b", "a", "s", "e", "b", "a", "l", "l", "b", "e", "a", "r"]
irb(main):013:0> array.unshift(%w|b u m p o f|)
=> [["b", "u", "m", "p", "o", "f"], 100, "b", "a", "s", "e", "b", "a", "l", "l", "b", "e", "a", "r"]
irb(main):014:0> array.unshift(b,u,m,p,o,f)
Traceback (most recent call last):
        2: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
        1: from (irb):14
NameError (undefined local variable or method `b' for main:Object)

配列の要素変更

[] = 破壊的

代入を指定する範囲が配列を超えていた場合の挙動を見てみる。

rb(main):014:0> array = [0,1,2,3]
=> [0, 1, 2, 3]
irb(main):016:0> array[1,2] = [4,5,6,7,8]
=> [4, 5, 6, 7, 8]
irb(main):017:0> array
=> [0, 4, 5, 6, 7, 8, 3]
irb(main):018:0> array[10,2] = [4,5,6,7,8]
=> [4, 5, 6, 7, 8]
irb(main):019:0> array
=> [0, 4, 5, 6, 7, 8, 3, nil, nil, nil, 4, 5, 6, 7, 8]

流し込む配列の長さに応じて、もしくは代入する場所が配列の範囲を超えていた場合は配列の自動拡張が行われている。

instance method Array#[]= (Ruby 2.6.0)

fill破壊的

fillメソッドは引数で指定された部分をすべて自分自身の内容に置き換える。 またブロックを指定することもできる。

irb(main):021:0> array
=> [0, 4, 5, 6, 7, 8, 3, nil, 4, 5, 6, 7, 8, 4, 5, 6, 7, 8]
irb(main):022:0> array.fill{|index| index}
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]

replace 破壊的

replaceはオブジェクトを差し替えることなく、配列を引数に指定されたものにそっくりそのまま取り換えるメソッド。

irb(main):025:0> array.object_id
=> 70368167582280
irb(main):026:0> array.replace([nil])
=> [nil]
irb(main):027:0> array.object_id
=> 70368167582280

配列の要素を参照する

[]

エイリアスslice 要素は範囲でも指定できる。

irb(main):001:0> a = [1,2,3,4,5,6]
=> [1, 2, 3, 4, 5, 6]
irb(main):002:0> p a[12]
nil
=> nil
irb(main):003:0> p a[1..2]
[2, 3]
=> [2, 3]

values_at

[]のエイリアスのように見えるが、要素が一つしか取得されない場合でも、またはnilの場合でも必ず、配列の形で返ってくる。

irb(main):020:0> a.values_at(100)
=> [nil]
irb(main):009:0> p a.values_at(1)
[2]
=> [2]
irb(main):008:0> p a.values_at(1..2)
[2, 3]
=> [2, 3]

at

要素を範囲で指定できない。 配列の範囲外を指定されるとnilが返る。

irb(main):006:0> p a.at(1)
2
=> 2
irb(main):007:0> p a.at(1..2)
Traceback (most recent call last):
        3: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
        2: from (irb):7
        1: from (irb):7:in `at'
TypeError (no implicit conversion of Range into Integer)
irb(main):021:0> a.at(100)
=> nil

fatch

`atとほぼエイリアスの関係だが、配列の範囲外を指定されると2番目の引数を返すか、指定されてなければエラーを返す。

irb(main):011:0> p a.fetch(100)
Traceback (most recent call last):
        3: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
        2: from (irb):11
        1: from (irb):11:in `fetch'
IndexError (index 100 outside of array bounds: -6...6)
irb(main):012:0> p a.fetch(100, "ないよ。")
"ないよ。"
=> "ないよ。

first, last

一番最初(末尾)の数字を取得し、引数が指定されれば、最初(末尾)からその分だけ取得する。 引数は範囲を超えていてもエラーは返らない。

irb(main):022:0> a.first(10)
=> [1, 2, 3, 4, 5, 6]
irb(main):023:0> a.first(2)
=> [1, 2]
irb(main):025:0> a.clear
=> []
irb(main):026:0> a
=> []
irb(main):027:0> a.first
=> nil

assoc, rassoc

配列の配列を検索するメソッド。 assocは配列の配列の最初の要素、rassocは配列の配列の次の要素を見ていき、もっとも最初に合致したものを返す。

irb(main):028:0> a = [[[1,2,3],[4,5,6],[7,8,9]]]
=> [[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]
irb(main):029:0> a.assoc(1)
=> nil
irb(main):030:0> a = [[1,2,3],[4,5,6],[7,8,9]]
=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
irb(main):031:0> a.assoc(4)
=> [4, 5, 6]
irb(main):034:0> a.rassoc(2)
=> [1, 2, 3]

「配列の配列の配列」は探せていないようだ。

include?

返り値はboolean。

index, rindex

Stringクラスでも扱ったが、indexとrindexは探す方向が異なるだけで、同じ要素が重複していなければ、原則返ってくるindexは同じである。

irb(main):038:0> a.flatten!
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
irb(main):039:0> a.index(5)
=> 4
irb(main):040:0> a.rindex(5)
=> 4
irb(main):041:0> a.rindex(9)
=> 8
irb(main):042:0> a.index(9)
=> 8

要素を削除する

入社試験でこのメソッドが思い出せず、未完のコードを提出した覚えがあるので、背筋を正して覚えたい。

delete_at 破壊的

破壊的なメソッドなので、帰り値は削除した要素そのものが返ってくる。 インデックスを指定して削除を行う。要素の複数指定はできない。

要素を指定しての削除はdeleteなので注意すること。

irb(main):038:0> a.flatten!
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
irb(main):043:0> a.delete_at(3)
=> 4
irb(main):044:0> a
=> [1, 2, 3, 5, 6, 7, 8, 9]
irb(main):045:0> a.delete_at(3,5,6,7)
Traceback (most recent call last):
        3: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
        2: from (irb):45
        1: from (irb):45:in `delete_at'
ArgumentError (wrong number of arguments (given 4, expected 1))

delete_if 破壊的

エイリアスは``reject!```。 必ずブロックと併用し、ブロックが評価した結果がtrueのものを削除する。

irb(main):046:0> p a
[1, 2, 3, 5, 6, 7, 8, 9]
=> [1, 2, 3, 5, 6, 7, 8, 9]
irb(main):047:0> p a.reject!{|i| [1,2,3,4].include?(i) }
[5, 6, 7, 8, 9]
=> [5, 6, 7, 8, 9]

delete 破壊的

配列の要素と==の関係にあるものをすべて削除する。 最初の一致だけではない点に注意。

irb(main):048:0> b = [9,9,9,8,8,8,7,7,7]
=> [9, 9, 9, 8, 8, 8, 7, 7, 7]
irb(main):049:0> b.delete(7)
=> 7
irb(main):050:0> p b
[9, 9, 9, 8, 8, 8]
=> [9, 9, 9, 8, 8, 8]

clear 破壊的

全削除。Stringでも使えるのか試してみたら、いけた。

irb(main):051:0> a = "HelloWorld"
=> "HelloWorld"
irb(main):052:0> a.clear
=> ""

slice!破壊的

Stringのsliceは破壊的なしの要素取得に利用するメソッドとして学んだが、それが破壊的なメソッドとしても利用できる。 sliceが[]エイリアスであったように、この破壊的メソッドでも、範囲、始点と長さが指定できる。

irb(main):053:0> p b
[9, 9, 9, 8, 8, 8]
=> [9, 9, 9, 8, 8, 8]
irb(main):055:0> b.slice!(0,3)
=> [9, 9, 9]
irb(main):056:0> p b
[8, 8, 8]
=> [8, 8, 8]
irb(main):057:0> b.slice!(0...2)
=> [8, 8]

shift, pop 破壊的

先頭(末尾)から引数で指定された数だけ要素を取り除く。指定がなければ一つ削除する。 !を付けなくても、これ自体が破壊的なメソッドである。

irb(main):061:0> b = [1,2,3,4,5,6,7,8,10]
=> [1, 2, 3, 4, 5, 6, 7, 8, 10]
irb(main):062:0> b.pop
=> 10
irb(main):063:0> p b
[1, 2, 3, 4, 5, 6, 7, 8]
=> [1, 2, 3, 4, 5, 6, 7, 8]
irb(main):064:0> p b.shift
1
=> 1
irb(main):065:0> p b
[2, 3, 4, 5, 6, 7, 8]
=> [2, 3, 4, 5, 6, 7, 8]

- 破壊的なし

+が破壊的ではないように(concatが破壊的)、こちらも非破壊的なメソッド。 一致するものは全削除であることに留意する。

irb(main):069:0> b = "BaseBallBear".split('')
=> ["B", "a", "s", "e", "B", "a", "l", "l", "B", "e", "a", "r"]
irb(main):070:0> p b
["B", "a", "s", "e", "B", "a", "l", "l", "B", "e", "a", "r"]
=> ["B", "a", "s", "e", "B", "a", "l", "l", "B", "e", "a", "r"]
irb(main):071:0> b - %w|B a s e|
=> ["l", "l", "r"]
irb(main):072:0> p b
["B", "a", "s", "e", "B", "a", "l", "l", "B", "e", "a", "r"]
=> ["B", "a", "s", "e", "B", "a", "l", "l", "B", "e", "a", "r"]

配列の基本動作をすべて見てきたので、あとはトリビアルなメソッドが中心となる。

配列の演算

和集合と積集合が出来る。和集合では、片方の配列に重複があっても、解消してから計算を行っている。

irb(main):073:0> [1,2,3,4,5] | [3,4,5,6,7]
=> [1, 2, 3, 4, 5, 6, 7]
irb(main):074:0> [1,2,3,3,3,4,5] | [3,4,5,6,7]
=> [1, 2, 3, 4, 5, 6, 7]
irb(main):075:0> [1,2,3,3,3,4,5] & [3,4,5,6,7]
=> [3, 4, 5]

配列の比較

先頭から順に比較していくだけで、差がついた要素があればそこで比較を終了する。 UFO演算子が使える。

irb(main):076:0> [1,2,3,100,4] <=> [1,2,3,4,100]
=> 1

要素ごとの繰り返し

each

各要素をブロックに渡す。配列を逆からブロックに渡したいときはreverse_each

each_index

普段つかっているeachエイリアスではない。 ブロックに渡されるのは、配列の要素ではなく、インデックスなので注意する必要がある。

irb(main):001:0> b = "BaseBallBrar".split("")
=> ["B", "a", "s", "e", "B", "a", "l", "l", "B", "r", "a", "r"]
irb(main):002:0> s = []
=> []
irb(main):003:0> b.reverse_each{|x| s.unshift(x)}
=> ["B", "a", "s", "e", "B", "a", "l", "l", "B", "r", "a", "r"]

cycle

ブロックに要素を渡し続ける。

配列のソート

sort 破壊的あり

ブロックを渡してやれば、順番に2つの要素が渡されるので、手動バブルソートや、応用して降順のソートを行うことが出来る。

rb(main):001:0> b = [1,100,99,5,16,32,98]
=> [1, 100, 99, 5, 16, 32, 98]
irb(main):002:0> b.sort!{|a,b| a <=> b}
=> [1, 5, 16, 32, 98, 99, 100]
irb(main):003:0> b.sort!{|a,b| b <=> a }
=> [100, 99, 98, 32, 16, 5, 1]

配列を変換する

uniq 破壊的あり

英単語としてはuniqueの方が自然なので、ちょっと気になる。

irb(main):005:0> b = "BaseBallBear".split('')
=> ["B", "a", "s", "e", "B", "a", "l", "l", "B", "e", "a", "r"]
irb(main):006:0> b.uniqe
Traceback (most recent call last):
        2: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
        1: from (irb):6
NoMethodError (undefined method `uniqe' for ["B", "a", "s", "e", "B", "a", "l", "l", "B", "e", "a", "r"]:Array
Did you mean?  uniq
               uniq!)
irb(main):007:0> b.uniq
=> ["B", "a", "s", "e", "l", "r"]
irb(main):008:0> b.uniq!
=> ["B", "a", "s", "e", "l", "r"]

compact, reverse,shuffle 破壊的あり

それぞれ配列からのnil削除と逆順化とシャッフル

flatten破壊的あり

引数の指定がなければ、ネストした引数を戻して、平滑化した配列にする。

irb(main):013:0>  b = [[[[3]]]]
=> [[[[3]]]]
irb(main):014:0> b.flatten
=> [3]

引数の指定があれば、その回数分ネストを平滑化する。 この配列は3重ネストなので、3を指定すれば元に戻る。

irb(main):015:0> b.flatten(2)
=> [[3]]
irb(main):016:0> b.flatten(1)
=> [[[3]]]
irb(main):017:0> b.flatten(3)
=> [3]
irb(main):018:0> b.flatten(4)
=> [3]
irb(main):019:0> b.flatten(5)
=> [3]

map 破壊的あり

collectがエイリアス。 配列の各要素をブロックに渡してゆく。

irb(main):021:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):022:0> a.map{|x| x + 2}
=> [3, 4, 5, 6, 7]
irb(main):024:0> a
=> [1, 2, 3, 4, 5]
irb(main):025:0> a.map!{|x| x + 2}
=> [3, 4, 5, 6, 7]
irb(main):026:0> a
=> [3, 4, 5, 6, 7]

配列の組み合わせ

どちらも破壊的メソッドは存在しない。

product 破壊的なし

与えられた配列2つから要素をひとつずつとり、すべての組み合わせを要素とする配列を作る。 メソッドを実行するオブジェクトの配列と、引数として指定する配列の2つしか配列がないため、必然的に要素が2つの配列が出来ることになる。

本当は、引数を複数していしてしまえば、いくらでも組み合わせが発生する。

irb(main):029:0> [1,2,3,4,5].product([5,6,7,8,9])
=> [[1, 5], [1, 6], [1, 7], [1, 8], [1, 9], [2, 5], [2, 6], [2, 7], [2, 8], [2, 9], [3, 5], [3, 6], [3, 7], [3, 8<s>]</s>, [3, 9], 
[4, 5], [4, 6], [4, 7], [4, 8], [4, 9], [5, 5], [5, 6], [5, 7], [5, 8], [5, 9]]
irb(main):030:0> [1,2,3,4,5].product([5,6,7,8,9],[10,11,12])
=> [[1, 5, 10], [1, 5, 11], [1, 5, 12], [1, 6, 10], [1, 6, 11], [1, 6, 12], [1, 7, 10], [1, 7, 11], [1, 7, 12], [1, 8, 10], 
[1, 8, 11], [1, 8, 12], [1, 9, 10], [1, 9, 11], [1, 9, 12], [2, 5, 10], [2, 5, 11], [2, 5, 12], [2, 6, 10], [2, 6, 11], [2, 
6, 12], [2, 7, 10], [2, 7, 11], [2, 7, 12], [2, 8, 10], [2, 8, 11], [2, 8, 12], [2, 9, 10], [2, 9, 11], [2, 9, 12], [3, 5, 10], [3, 5, 11], [3, 5, 12], [3, 6, 10], [3, 6, 11], [3, 6, 12], [3, 7, 10], [3, 7, 11], [3, 7, 12], [3, 8, 10], [3, 8, 11], 
[3, 8, 12], [3, 9, 10], [3, 9, 11], [3, 9, 12], [4, 5, 10], [4, 5, 11], [4, 5, 12], [4, 6, 10], [4, 6, 11], [4, 6, 12], [4, 
7, 10], [4, 7, 11], [4, 7, 12], [4, 8, 10], [4, 8, 11], [4, 8, 12], [4, 9, 10], [4, 9, 11], [4, 9, 12], [5, 5, 10], [5, 5, 11], [5, 5, 12], [5, 6, 10], [5, 6, 11], [5, 6, 12], [5, 7, 10], [5, 7, 11], [5, 7, 12], [5, 8, 10], [5, 8, 11], [5, 8, 12], 
[5, 9, 10], [5, 9, 11], [5, 9, 12]]

zip破壊的なし

``product```のようなすべての組み合わせではなく、引数の配列とメソッドを実行する配列のインデックスが同じものを集めて配列を作る。 そのため、引数側の配列の長さが足らないものがあれば、nilが代入される。一方、メソッドを実行する側が足らなければ、組み合わせは実行する側の上限までとなる。

irb(main):032:0> [1,2,3,4,5].zip([1,2,3])
=> [[1, 1], [2, 2], [3, 3], [4, nil], [5, nil]]
irb(main):033:0> [1,2,3,4,5].zip([1,2,3,4,5,6,7,8,9])
=> [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]

上の例から、引数側の6から9までの要素が無視されているのがわかる。