(050) 配列

Ippei Kishida

Last-modified:2018/07/05 19:47:42.

1 多数の変数を統一的に管理

君がある授業の担当教員だとして、 60人のレポート点数の平均・分散・標準偏差を求め、そこから偏差値を求めたいとする。 どのようなコードを書けば良いだろうか? 前回習ったループを使うことができそうな気もするが、 ここまでの知識ではそれを上手く使うことができないだろう。 かといってループを使わなければ似たような処理を 60回記述しなければならない。 これは非常に骨が折れるわりに精度の低い、実に bad なやり方だ。 このような時に威力を発揮するのが配列という仕組みだ。

コードにするときにどのような変数を用意するかを考えよう。 まず思い付くのは愚直に1つずつ別の名前の変数を用意することだ。

student0 = 82
student1 = 72
student2 = 23
:
:

このように60個の変数を一個ずつ用意してやることができる。 1 しかし結局、それぞれの変数の名前は別々につけられているので、 統一的に扱うことができない。 たとえば総和を求めるときは以下のようなコードになってしまう。 2

sum = student0 + student1 + student2 +
  student3 + student4 + student5 +
  ...

さて、「名前に数字を使っているのだからその部分だけ修正する方法があってもいいのに」 と思ったかもしれない。 しかし、名前は既に与えられたものなのでこれを書き変えることはできない。 もし君が「新一」という名前だったとして、勝手に「新二」に変えられたりしたら嫌だろう。

配列とは変数を幾つも用意してやって、それを統一的な名前で呼んだり、 1つずつ取り出して扱うことができるようにしたものである。 概念的には、変数が1つの箱であるのに対し、 配列はその箱が数珠繋ぎになってひとかたまりになっているようなイメージで良い。 変数のところで行った空き地の喩えと対応させるならば、マンションに喩えることができる。 ○○マンションという配列の、△△号室にデータを詰めるわけだ。

具体的なコードを示していこう。 最初のコードに対応した書き方をするならば、

student = Array.new
student[0] = 82
student[1] = 72
student[2] = 23
:
:

最初に student が配列(Arrayクラス) であることを示す。 new というのは多くのクラスで、新しいオブジェクト 3 を生成する命令。 右辺で生成した新しい配列を左辺の名前に代入しているわけだ。 その次からは最初のコードに似ており、[] で数字を囲っている。 この括弧は変数名の一部ではなく、配列 student に対する操作であることを示している。 すなわち、『student の0番目の箱に整数クラスの「82」という値を代入』 といったような操作をしていることになる。 4 書き方はもう少し、簡略化することができる。

student = [82, 72, 23, 64, 72]

60人分書くのは面倒なので、ここでは5人分にしている。 右辺の [] はそれが配列オブジェクトであることを示し、 その中はカンマ区切りで幾つでも書き込むことができる。

1.1 データの取り出し

[] で要素番号を指定してやることで普通の1個の変数のように扱うことができる。 番号は 0 番目から始まる。

print student[0]

1.1.1 ループと組み合わせる

配列は、ループと組み合わせて使うと強力な威力を発揮する。 サンプルを示そう。

student = [82, 72, 23, 64, 72]
sum = 0
5.times do |i|
  sum += student[i]
end
print sum

……文章量は少ないが、この手法は非常に重要なのでよく理解されたい。

1.1.2 入れるデータは何でも良い

ここでは整数値を入れているが、入れるデータは何でも良い。 たとえば生徒の名前を入れたければ、

name = Array.new
name[0] = "izumi"
name[1] = "uda"
name[2] = "tamiya"
:
:

1つの配列の要素によって異なるクラスのデータを入れることもできるが、 プログラムが複雑になるのですべきではない。 5

1.1.3 多重配列

配列はそもそも、1次元的な箱の繋がりを示したものであった。 これは実は、全体で1個の「配列クラスのオブジェクト」である。 配列に「入れるデータは何でも良い」と先に述べたが、 「配列の1つの要素」に、「別のひとかたまりの配列」を入れることも可能である。 コードで示そう。

#arrayMultiplex1.rb
student = Array.new #生徒1人ずつのデータ
student[0] = [63, 46, 57, 69, 87]
student[1] = [86, 89, 49, 92, 91]
student[2] = [77, 85, 72, 44, 94]
student[3] = [92, 92, 94, 71, 43]
student[4] = [41, 70, 43, 83, 60]
p student

もしくは、

#arrayMultiplex1.rb
student = Array.new #生徒1人ずつのデータ
student = [
  [63, 46, 57, 69, 87],
  [86, 89, 49, 92, 91],
  [77, 85, 72, 44, 94],
  [92, 92, 94, 71, 43],
  [41, 70, 43, 83, 60]
]
p student

上記のコードの最後で p student という命令が使われている。 この p は「その時点でのその値を出力せよ」という意味で、 配列のみならず数値や文字列もキチンと出してくれる。 プログラム実行で「動作が怪しいな」と思うときの多くは、 何らかの変数が上手くセットされていないことに因る。 そこでその変数に p を作用させることで、手軽に値を確認することができる。 ただし、 基本的に p はデバッグ用の機能なので、 ユーザに示す演算結果などはキチンと print 文などを使うこと。

2重配列の全ての要素を走査したいのならば 2重ループを使えば良い。 また、2重だけでなく幾つでも階層を増やすことができる。

2 その他の配列操作

前節までで多くの言語で共通の概念・操作を説明した。 以下では Ruby に用意されている便利な機能を紹介しておこう。

2.1 要素の追加: Array.push

配列に要素の追加は push を使うことでできる。

#arrayPush.rb
array = Array.new
p array
array.push(1)
p array
array.push(2)
p array

作成した時点ではこの配列 array は空のため、 まず [] と表示される。 push は配列に要素を追加する命令で、 array と名付けられた配列に直接「.」で繋げる形で記述する。

2.2 要素のサイズ: Array.size

Array.push を使って配列に要素を追加していると、配列の要素の数が変化する。 その時の配列の数を別の変数 numItems のように持っておいて push の度に カウントするのは面倒だし、間違いも起きやすい。 そこで Array.size を使えば配列の現在のサイズを取得することができる。

#arraySize.rb
array = [12,34,56]
p array.size #=>3
array.push(78)
p array.size #=>4

ここで p array.size #=>3 のように右端にコメントを付加して記述した。 この #=> というのはその操作でどのようなオブジェクトになるのかを 文中で示すために、(主に説明の文脈で)よく使われる。 本稿でもこれからはこの記法を積極的に使っていく。

2.3 最初に配列の要素数を決める: Array.new(num)

Array.new に数字を引数として与えることで、 初期化時点での配列の要素数を決めてやることができる。

#arrayNew.rb
p array = Array.new(10) #=> [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]

ここで、配列の代入の行でそのまま p を作用させているが、そのままの意味だ。 まず右辺で新しいオブジェクトが生成されて、代入演算で左辺の変数に代入され、 その値が p で作用されて画面に出力される、という仕組みになっている。

さて、領域が確保されたものの実際の要素には値が代入されていない。 nil は この「未初期化」を表す値である。 nil は論理値 false と同様に機能する。

2.4 配列の要素に値を詰める: Array.fill

Array.new(num) を使うことで、未初期化値の詰まった配列を得ることができた。 Array.fill を使うことで、この全要素を一括して初期化することができる。

#arrayNew.rb
array = Array.new(3)
p array.fill(0.0) #=> [0.0, 0.0, 0.0]

2.5 配列の全要素を走査: Array.each

配列全体に処理を加えたい場面では、 以下の arrayEach.rb のように each を使うと便利である。

array = [0, 1, 2]
array.each do |item|
  print item * 2, "\n"
end

each で配列 array の要素を一つずつ取り出し、仮変数 item に代入して end までの部分をループしている。

2.6 ソート(並べ直し): Array.sort

トランプゲームで手札を配られたとしよう。 ゲームの種類は何でも良いが多くの場合、カードを弱い(強い)順に並べることだろう。 このように一定のルールに従ってデータを並べ直すことは「ソート(sort)」と呼ばれる。 ソートの問題は教育的に良い題材であるため、 本演習でも後の章で自前でソートプログラムを書く課題を出す予定だが、 とりあえずは Ruby 側で用意しているものを使ってくれれば良い。

#arraySort.rb
array1 = [8, 3, 2, 9, 4, 0, 5, 7, 6, 1]
array2 = array1.sort
p array1 #=>[8, 3, 2, 9, 4, 0, 5, 7, 6, 1]
p array2 #=>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

sort を作用させても元の配列には変化がないことに注意。 元の配列を変化させたい場合には

array1 = array1.sort

のように自分に代入しても良いが、

array1.sort!

のように末尾に「!」を付けた Array.sort! でも同じことができる。

2.7 その他

Ruby に関する書籍やインターネットなどでも 利用できるクラスやメソッドの情報が得られる。

3 課題

10人の学生の素点が以下のようだった。

    27 75 32 92 25 59 93 33 82 60 
  1. 点数をソートして表示せよ。

  2. この時の合計点、平均点、分散 を求め、表示するプログラムを作成せよ。 \[\begin{aligned} 平均: &\bar{x} &= \frac1n \sum_{i=1}^{n} x_i \\ 分散: &V = \sigma^2 &= \frac{1}{n} \sum_{i=1}^{n} (x_i - \bar{x})^2 %\\ %標準偏差: &\sqrt{V} = \sigma &= \sqrt{\frac{1}{n} \sum_{i=1}^{n} (x_i-\bar{x})} %\\ %偏差値: &y &= 10(\frac{x - \bar{x}}{\sigma}) + 50 \end{aligned}\]


  1. 0から数え始めていることに気付いたかもしれない。 基本的にコンピュータは 0 から数えるものであり、 1から数えるケースの方が少数だと思ってしまった方が良い。

  2. Ruby では行をまたいで1つの処理を記述することができる。 それができる条件は「前の行がそこで処理が終わってないことが明確なとき」。 少しややこしいのだが、ここでは「演算子のすぐあとで改行すれば、 次の行は続きと見做される」と思っておけば良い。

  3. オブジェクトという単語は始めて出てきたが、 とりあえずはクラスで作る1個1個のモノだと思っておけば良い。 変数には通常、1つのオブジェクトが入れられる。

  4. やはり、コンピュータは数を 0 から数える。 配列の場合に限っては 1 から数え始めるプログラミング言語(Fotran など)もあるのだが、 それはどちらかといえば少数だ。

  5. C/C++ などでは、配列を宣言するときにその中に何を入れるのかを明示する必要があるので、 場所によって異なるクラスのデータが入ることは基本的にはない。