5. 配列

Rubyには、数値や文字列といったオブジェクトを格納するために用意されたオブジェクトが存在します。
これを「コレクション」や「コンテナ」と呼びます。

配列(Array)

配列とはオブジェクトをまとめて管理する方法の1つで、複数のオブジェクトを順番に配置したものです。
配列に入れられた要素は配列番号(インデックス)によって管理されます。

1
[elem0, elem1, elem2, ...]

配列の作成

配列は[](ブラケット)の中にカンマ区切りで要素を記述します。
Rubyでは配列の要素に型の異なる値を入れることも可能です。

define_array.rb
1
2
3
4
5
6
7
8
# 兄弟の名前を brother_names という配列に代入
brother_names = ["Ichiro", "Jiro", "Saburo"]

# 3の倍数を multiples_of_3 という配列に代入
multiples_of_3 = [3, 6, 9, 12, 15]

# 1つの配列に異なる型の値を代入することもできる
mix_type = [1, "two", [3, 4], :symbol]    #数値, 文字列, 配列, シンボルを要素に持つ配列

配列には様々な操作をすることができます。

配列要素の参照

配列は、配列名[添字]という形式で配列の各要素を参照することができます。
添字は配列番号を表します。このとき、添字は0から始まるという点に注意が必要です。
また、添字に負の値をとるものは配列の最後の要素からの要素を示します。

print_array.rb
1
2
3
4
5
6
7
numbers = [100, 200, 300]

p numbers[1] #=> 200
p numbers[2] #=> 300

p numbers[-1] #=> 300 ※配列の最後の要素
p numbers[-2] #=> 200 配列の最後から2番目の要素

また、存在しない要素が指定された場合はnilとなります。

1
2
p numbers[4]
#=> nil

要素の追加・変更

要素を追加したい場合、添字を指定して代入する方法と配列の最後に要素を追加する方法があります。

add_element.rb
1
2
3
4
5
6
7
8
9
names = [] # namesという配列を作成

names[0] = "Taro"
p names
#=> ["Taro"]

names << "Hanako" # names の一番最後に "Hanako" を追加
p names
#=> ["Taro", "Hanako"]

また、すでに値が入っている要素の添字を指定して代入することで、値を書き換えることができます。

change_element.rb
1
2
3
4
5
names = ["Taro", "Hanako"]

names[0] = "Jiro" # names の 要素番号0番目を「"Jiro"」に変更する
p names
#=> ["Jiro", “Hanako”]

配列の結合

配列同士結合させたい場合は以下の通りです。

concat_arrays.rb
1
2
3
4
5
6
7
8
9
10
nums = [1, 2, 3]

p nums + [4, 5]
#=> [1, 2, 3, 4, 5]

p nums
#=> [1, 2, 3] ※元の配列が置き換わるわけではない

p nums - [2, 3] # 引くことも可能
#=> [1]

配列の範囲指定

配列から要素を部分的に取り出したい時、Rubyでは以下のように記述することで簡単に取り出すことができます。

1
2
array[a..b]    # 配列のa番目からb番目までの要素で配列を作る
array[a,b]     # 配列のa番目からb個の要素を取り出して配列を作る

以下は、それぞれの範囲指定の例です。

pick_elements.rb
1
2
3
4
5
6
7
names = ["Ichiro", "Jiro", "Saburo", "Hanako", "Mariko"]

p names[0..2]  # 0~2番目の要素を取得
#=> ["Ichiro", "Jiro", "Saburo"]

p names[1,3]   # 1番から3個の要素を取得
#=> ["Jiro", "Saburo", "Hanako"]

配列の要素数(length・count)

配列の要素数を調べるにはlengthまたはcountを使用します。
countは引数を与えることで配列内に引数と同じ要素がいくつあるかを調べることができます。

count.rb
1
2
3
4
5
6
7
8
answers = ["たけのこ", "きのこ", "たけのこ", "きのこ", "たけのこ"]

p answers.length #=> 5
p answers.count  #=> 5

takenoko_count = answers.count("たけのこ")
puts "#{answers.length}人中#{takenoko_count}人が「たけのこ派」です!!"
#=> 5人中3人が「たけのこ派」です!!

各要素の参照(each・each_with_index)

eachメソッドは配列の全ての要素を一つずつ参照して処理を行う場合に使用されるメソッドです。
オブジェクトの集合(ここでは配列)に対して、要素を一つずつブロックdo ~ endに渡し、繰り返し処理を行うことができます。
each_with_indexを使うと、eachメソッドにインデックス(番号)情報を付加することができます。

each.rb
1
2
3
4
5
6
7
8
9
answers = ["たけのこ", "きのこ", "たけのこ", "きのこ", "たけのこ"]

answers.each do |answer|
  p "私は#{answer}派です"
end

answers.each_with_index do |value, idx|
  p "#{idx+1}人目は#{value}派です"
end

要素の整列(sort・sort_by)

配列の要素を並べ替えるにはsortを使用します。
特に条件を指定しない場合、小さい順(昇順)にソートされます。

sort.rb
1
2
3
4
5
6
7
8
9
10
11
12
nums = [5, 1, 4, 2, 3]

p nums.sort
#=> [1, 2, 3, 4, 5]
# 並び替えた結果を出力しているだけ(元の配列は変わらない)

p nums
#=> [5, 1, 4, 2, 3]

nums.sort! # 元の配列も変えてしまう(破壊的メソッド)
p nums
#=> [1, 2, 3, 4, 5]

Note : !の付いたメソッド

Rubyではオブジェクトに影響を与えるメソッド(破壊的メソッド)は名前の末尾に!を付ける規約があります。
安易に使用するとオブジェクトのデータを意図せず書き換えてしまいます。
メソッドを使用したり、自分で作成する場合は注意しましょう。

sortは以下のように記述することで、自分で条件を設定してソートすることもできます。
文字列数でソートする場合の例は以下のようになります。
その時は、ブロックを渡して、条件を指定しましょう。
一時的にabに値を配置して、比較しています。

sort_with_block.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
stations = ["Kouenji", "Tokyo", "Shibuya", "Ikebukuro"]

# 「文字列の頭文字のアルファベット順」で昇順ソート
sorted_stations = stations.sort
p sorted_stations
#=>["Ikebukuro", "Kouenji", "Shibuya", "Tokyo"]

# 「文字列の長さ(lengthの値)」で昇順ソート
sorted_stations = stations.sort { |a, b| a.length <=> b.length }
p sorted_stations
#=>["Tokyo", "Kouenji", "Shibuya", "Ikebukuro"]

# 「文字列の長さ(lengthの値)」で降順ソート
sorted_stations = stations.sort { |a, b| b.length <=> a.length }
p sorted_stations
#=>["Ikebukuro", "Shibuya", "Kouenji", "Tokyo"]

しかし、この書き方だと2つの要素に対してそれぞれメソッドを呼び出し処理回数が増えてしまう為、要素が小さい順に並ぶソート(昇順ソート)には、一般的に sort_by メソッドが使用されます。

sort_by.rb
1
2
3
4
5
stations = ["Kouenji", "Tokyo", "Shibuya", "Ikebukuro"]

sorted_stations = stations.sort_by { |station| station.length }
p sorted_stations
#=>["Tokyo", "Kouenji", "Shibuya", "Ikebukuro"]

Note : <=>演算子

a <=> b とした場合、
a > b のとき 1
a == b のとき 0
a < b のとき -1
比較できない場合はnilを返します。
sortはこの演算子を内部的に使用して並べ替えを行っています。

配列の中身を結合して文字列にする(join)

joinメソッドを用いると、配列の各要素を連結して文字列をつくることができます。
また、引数に文字列を指定した場合、その文字列で各要素をつなげます。

join.rb
1
2
3
4
5
6
7
8
strs = ["I", "have", "a", "pen"]

str = strs.join
p strs #=> ["I", "have", "a", "pen"]
p str  #=> "Ihaveapen"

str = strs.join("_")
p str  #=> "I_have_a_pen"

各要素に対して処理を行う(map)

要素の数だけ繰り返しブロックを実行し、その戻り値の配列を返します。
以下の例では次の二つの処理を行っています。

  1. 3つの数値からなる配列numbersの各要素をnumとしてブロックに渡し、2倍した要素を新たな配列の要素として返却
  2. 3つの数値からなる配列numbersの各要素をnumとしてブロックに渡し、各要素を文字列に変換した要素を新たな配列の要素として返却
map.rb
1
2
3
4
5
6
7
8
9
10
11
numbers = [10, 20, 30]

# 各値を2倍する
twice_numbers = numbers.map { |num| num * 2 }

p twice_numbers
#=> [20, 40, 60]

# 各数値を文字列に変換する
p number_strs = numbers.map { |num| num.to_s }
#=> ["10", "20", "30"]

なお、mapメソッドもmap!とすれば、元の要素を置き換えることができます。

任意の要素を取り出す(select)

ブロックの戻り値が真になったときの要素を集め、配列にして返します(については後述)。
下記の例は、2で割り切れるものだけを新しい配列として返しています。

select.rb
1
2
3
nums = [1, 2, 3, 4, 5, 6, 7, 8]
p nums.select { |num| num % 2 == 0 }
#=> [2, 4, 6, 8]

Note : 各要素を取り出して繰り返し演算を行う (inject)

配列のそれぞれの値を使って演算を行いたい場合、injectメソッドを使うと便利です。
例えば、数値からなる配列numsに含まれる数値の合計値を求めたい場合を考えます。
このとき、 合計値は次のように求められます。

inject.rb
1
2
3
4
nums = [1, 2, 3, 4, 5, 6, 7, 8]

puts nums.inject{ |sum, num| sum + num }
#=> 36 ※sumに値が入るわけではない

injectメソッドは、ブロックの第1要素をメモリとして使い、第2要素に配列の各要素が渡されます。
すべての要素を参照した後、ブロックの最終的な値が戻り値として返されます。
また、injectメソッドには初期値を渡すこともできます。
以下は、先ほどの例に初期値10を渡した例です。

inject_with_initial_value.rb
1
2
3
4
nums = [1, 2, 3, 4, 5, 6, 7, 8]

puts nums.inject(10){ |sum, num| sum + num }
#=> 46

実は、初期値を省略した場合には配列の最初の要素nums[0]が渡されています。
このinjectメソッドを応用すると、フィボナッチ数列のような複雑な数列も簡単に求めることができます。
※ ruby2.4から、配列の要素を加算するメソッドとしてsumメソッドが用意されました。
sumメソッドには数値の合計値を求める以外の使い方もありますので、興味があったら調べてみましょう。

sum.rb
1
2
3
4
5
6
7
8
nums = [1, 2, 3, 4, 5, 6, 7, 8]

puts nums.sum
#=> 36

#引数を入れると、その数も加算される
puts nums.sum(10)
#=> 46

配列名について

プログラムが大規模になると変数が何を表しているのか分かりにくくなります。
そのため、変数名は他の人が読んでも分かりやすくしておくことが重要です。
配列を使用する際は以下の点に注意しましょう。

  • 単語には複数形を使用
  • 先頭文字が英字で、英字の小文字アンダーバー_数字で構成される(スネークケース)
  • 何を代入したかわかりやすい名前にする
naming_of_array.rb
1
2
3
4
5
6
7
8
9
# 「最寄り」駅を格納する配列の場合

# 良くない例
station = ["高円寺", "中野", "新宿"]   # 配列だと分かりにくい(変数と勘違いする)
_station_ = ["高円寺", "中野", "新宿"]
stations = ["高円寺", "中野", "新宿"]  # 何の駅の配列なのか分からない

# 良い例
near_stations = ["高円寺", "中野", "新宿"] # 近い駅のリストだとわかりやすい

Note : リテラル

文字列で構成される配列には、もう一つの書き方(リテラル)があります。

percent_literal.rb
1
2
3
4
5
6
7
8
9
10
p %w[ Ichiro Jiro Saburo ]
# => ["Ichiro", "Jiro", "Saburo"]

p %W[ Ichiro Jiro Saburo ]
# => ["Ichiro", "Jiro", "Saburo"]

# %Wを使うと、式が展開される
name = "Ichiro"
p %W[ #{name} ]
# => ["Ichiro"]

この場合""で囲む必要がなく、最近では上記のような書き方をされていることも多いです。

ブロック

先ほどは、do ~ endでブロックを表現しましたが、mapなどで使用した記述もできます。
{ || ... }を使用する書き方は一般的に「一行で表現する」際に使うとされています。

1
array.each { |value| puts value }

人やプロジェクトによって全て { || ... } の形で使用しているところもあるため、実際のチームで作業をする場合などは、事前にルールの確認が必要です。
研修の間は、

  • 処理が複数行に渡る場合: do~end
  • 一行で表現する場合: { || ... }

としましょう(コーディング規約)。


演習問題

問1

以下のように、5つの金額を格納した配列pricesを定義してください。

1
2
p prices
#=> [100, 140, 360, 120, 230]

問1-1

pricesに含まれる金額の合計金額を表示してください。
※余裕があれば、いろいろな求め方を試してみましょう。

1
#=> 合計金額は950円です

問1-2

pricesを使って、中の要素を全て税込価格に置き換えた配列tax_in_pricesを求めてください。
ただし、消費税率は8%とし、小数点以下を四捨五入してください。

1
2
p tax_in_prices
#=> [108, 151, 389, 130, 248]

問1-3

tax_in_pricesの要素を、金額の高い方から3つ表示してください。

1
#=> [389, 248, 151]