【Ruby】配列クラス

「はじめてのRuby」を読んでのメモ。

今回は第13章「配列(Array)クラス」



配列は「インデックスのついたオブジェクトの集まり」で、

  • あるインデックスの要素(オブジェクト)を取り出すことができる
print name[2]
  • あるインデックスの要素に好きな値(オブジェクト)を格納できる
name[0] = "A"
  • イテレータを使うことで要素を1つ1つ取り出すことができる
name.each{|name| puts name}

といった機能を持っている。


今回は、配列クラスの次の点について掘り下げる。

  • 配列の作り方
  • インデックスの使い方
  • 「集合」としての配列、「列」としての配列
  • 配列の主なメソッド
  • 配列とイテレータ
  • 配列内の各要素を処理する
  • 複数の配列に並行してアクセスする


配列の作り方

基本的にはこう。

nums = [1, 2, 3, 4, 5]
strs = ["a", "b", "c"]

他にも作り方がある。

Array.new

# 引数を指定しない
a = Array.new
p a  #=> []

# 引数1つ
a = Array.new(5)
p a  #=> [nil, nil, nil, nil, nil]

# 引数2つ
a = Array.new(5, 2)
p a  #=> [2, 2, 2, 2, 2]


%w, %i

lang = %w(Ruby Perl Python Scheme Pike REBOL)
p lang  #=> ["Ruby", "Perl", "Python", "Scheme", "Pike", "REBOL"]
# "" や , を省略できる

lang = %i(Ruby Perl Python Scheme Pike REBOL)
p lang  #=> [:Ruby, :Perl, :Python, :Scheme, :Pike, :REBOL]
# 要素がシンボル

区切り文字は () でなくてもよい。

lang = %w<Ruby Perl Python Scheme Pike REBOL>
p array  #=> ["Ruby", "Perl", "Python", "Scheme", "Pike", "REBOL"]

lang = %w@Ruby Perl Python Scheme Pike REBOL@
p array  #=> ["Ruby", "Perl", "Python", "Scheme", "Pike", "REBOL"]

見やすさに支障がない程度に、お好みで。

to_a

color_table = {black: "000000", white: "FFFFFF"}
p color_table.to_a  #=> [[:black, "000000"], [:white, "FFFFFF"]]
# ハッシュに使うと配列の配列が作られる

str = "ABCD"
p str.to_a          #=> (NoMethodError)

num = 1234
p num.to_a          #=> (NoMethodError)
# String, Numeric クラスには使えない


split

strs = "It is a pen".split()
p strs  #=> ["It", "is", "a", "pen"]

str = "It is a pen".split
p str  #=> ["It", "is", "a", "pen"]
# () は省略できる

num = 1234.split
p num  #=> (NoMethodError)
# Numeric クラスには使えない


インデックスの使い方

要素を取り出す

インテックスによる要素の取り出しには [] を使う。

strs = ["A", "B", "C", "D", "E"]

p strs[1]      #=> "B"
p strs[6]      #=> nil
p strs[-1]     #=> "E"
p strs[-5]     #=> "A"

p strs[2..3]   #=> ["C", "D"]
# 2番目から3番目の要素を取り出す
p strs[2...3]  #=> ["C"]
p strs[-3..-2] #=> ["C", "D"]
p strs[2..6]   #=> ["C", "D", "E"]
p strs[3..2]   #=> []
p strs[-2..-3] #=> []

p strs[2, 3]   #=> ["C", "D", "E"]
# 2番目の要素から3つの要素を取り出す
p strs[0, 3]   #=> ["A", "B", "C"]
p strs[2, 5]   #=> ["C", "D", "E"]
p strs[4, -2]  #=> nil
p strs[-5, 3]  #=> ["A", "B", "C"]

これらと同じ働きをする一般的なメソッドも一応ある。

p strs.at(1)        #=> "B"
p strs.slice(1)     #=> "B"
p strs.slice(2..3)  #=> ["C", "D"]
p strs.slice(2, 3)  #=> ["C", "D", "E"]


要素を置き換える

strs = ["A", "B", "C", "D", "E"]
strs[1] = "b"
p strs  #=> ["A", "b", "C", "D", "E"]

strs[0..3] = ["a","b","c","d"]
p strs  #=> ["a", "b", "c", "d", "E"]

strs[0..3] = "A","B","C","D"
p strs  #=> ["A", "B", "C", "D", "E"]
# [] を外すことも可能

strs[0, 3] = ["a","b","c"]
p strs  #=> ["a", "b", "c", "D", "E"]


要素を挿入する

strs = ["A", "B", "C", "D", "E"]
strs[2, 0] = ["X","Y"]
p strs  #=> ["A", "B", "X", "Y", "C", "D", "E"]

「0個の要素と置き換える」という変則的な書き方。

複数のインデックスから配列を作る

# とびとびに要素を参照する
strs = ["A", "B", "C", "D", "E"]
p strs.values_at(0,2,4)   #=> ["A", "C", "E"]
p strs.values_at(0..2,4)  #=> ["A", "B", "C", "E"]


集合としての配列

ary1 = ["a", "b", "c"]
ary2 = ["b", "c", "d"]

# 積集合
p (ary1 & ary2)  #=> ["b", "c"]

# 和集合
p (ary1 | ary2)  #=> ["a", "b", "c", "d"]

# 和集合っぽいもの
p (ary1 + ary2)  #=> ["a", "b", "c", "b", "c", "d"]

# 差集合
p (ary1 - ary2)  #=> ["a"]


「列」としての配列

列は、キュー(FIFO)やスタック(LIFO)といったデータ構造を作るのに向いている。
Ruby の配列には先頭や末尾に対してデータを挿入したり、取り出すといったメソッドが揃っているので、この構造が簡単に実現できる。

# 要素を加える
strs = ["A", "B", "C", "D", "E"]
p strs.unshift("Z")  #=> ["Z", "A", "B", "C", "D", "E"]
p strs.push("F")     #=> ["Z", "A", "B", "C", "D", "E", "F"]
p strs.push("G","H") #=> ["Z", "A", "B", "C", "D", "E", "F", "G", "H"]


# 要素を取り除く
strs = ["A", "B", "C", "D", "E"]
p strs.shift    #=> "A"
p strs.pop      #=> "E"
p strs          #=> ["B", "C", "D"]
p strs.shift(2) #=> ["B", "C"]
p strs          #=> ["D"]

# 要素を参照する
strs = ["A", "B", "C", "D", "E"]
p strs.first    #=> "A"
p strs.last     #=> "E"
p strs          #=> ["A", "B", "C", "D", "E"]
p strs.first(2) #=> ["A", "B"]

pop や shift などのレシーバにあたるオブジェクトの値そのものを変更してしまうメソッドを破壊的なメソッドと呼ぶ。破壊的なメソッドを使うときにレシーバと同じオブジェクトを参照している変数がある場合、その変数の値も変化するので注意が必要である。
なお、sort に対して sort! のように、同じ名前で「!」がついているメソッドがあり、破壊的な方を使う際は注意が必要という意味で「!」をつけるというルールが存在する。

a = [1, 2, 3, 4]
b = a
p a.shift  #=> 1
p a        #=> [2, 3, 4]
p b        #=> [2, 3, 4]

追加する場合でもオブジェクトの値は変化する模様。

a = [1, 2, 3, 4]
b = a
p a.push(5)  #=> [1, 2, 3, 4, 5]
p a          #=> [1, 2, 3, 4, 5]
p b          #=> [1, 2, 3, 4, 5]

こちらは創造的なメソッドとでも呼べばよいのだろうか。
メソッド自体に、というよりはレシーバにあたるオブジェクトを参照している変数の扱いに気をつけるべきだろう。

ちょっと検証してみた。

# Num
a = 1
b = a
p b #=> 1
a = 3
p b #=> 1
# 値を保持

# Array
a = [1, 2, 3]
b = a
p b  #=> [1, 2, 3]
a[1] = 5
p b  #=> [1, 5, 3]
# 値を保持しない

# String
a = "A"
b = a
p b  #=> "A"
a = "B"
p b  #=> "A"
# 値を保持

Array のみ参照先の値が変わると、その変数の値も変化する模様。注意が必要か?

配列の主なメソッド

要素の追加

<<
a = [1, 2, 3, 4, 5]
a << 6
p a  #=> [1, 2, 3, 4, 5, 6]
a << [7,8]
p a  #=> [1, 2, 3, 4, 5, 6, [7, 8]]
a << 9,10  #=> Error
# 引数は1つ
concat
a = [1, 2, 3, 4, 5]
p a.concat([6,7])  #=> [1, 2, 3, 4, 5, 6, 7]
p a.concat(8)      #=> Error
# 引数は配列

要素の除去

compact
a = [1, nil, 3, nil, 5]
p a.compact      #=> [1, 3, 5]
# nil を除去
delete(x)
a = [1, 2, 3, 2, 2]
a.delete(2)
p a              #=> [1, 3]
a.delete(1,3)    #=> Error
# 要素 x を除去
delete_at(n)
a = [1, 2, 3, 4, 5]
a.delete_at(2)
p a              #=> [1, 2, 4, 5]
a.delete_at(2,3) #=> Error
# n 番目の要素を除去
delte_if{|item| ... } , reject!{|item| ...}
a = [1, 2, 3, 4, 5]
a.delete_if{|i| i > 3}
p a  #=> [1, 2, 3]

a = [1, 2, 3, 4, 5]
a.reject!{|i| i > 3}
p a  #=> [1, 2, 3]
# ブロックを実行した結果が真の場合に item を除去
slice!(n)
a = [1, 2, 3, 4, 5]
a.slice!(0)
p a  #=> [2, 3, 4, 5]
a.slice!(2..3)
p a  #=> [2, 3]
# 指定した部分を除去
uniq!
a = [1, 2, 2, 4, 5, 4]
a.uniq!
p a  #=> [1, 2, 4, 5]
# 重複する要素を除去

配列の要素を置き換える

collect!{|item| ... } , map!{|item| ... }
a = [1, 2, 3, 4, 5]
a.collect!{|i| i * 2}
p a  #=> [2, 4, 6, 8, 10]

a = [1, 2, 3, 4, 5]
a.map!{|i| i * 2}
p a  #=> [2, 4, 6, 8, 10]
fill
a = [1, 2, 3, 4, 5]
p a.fill(0)       #=> [0, 0, 0, 0, 0]
p a.fill(1,2)     #=> [0, 0, 1, 1, 1]
p a.fill(2,2,2)   #=> [0, 0, 2, 2, 1]
p a.fill(3,0..3)  #=> [3, 3, 3, 3, 1]
flatten
a = [1, [2, 3], [[4]], 5]
p a.flatten  #=> [1, 2, 3, 4, 5]
reverse
a = [1, 2, 3, 4, 5]
p a.reverse  #=> [5, 4, 3, 2, 1]
sort
a = [3, 1, 5, 2, 4]
p a.sort #=> [1, 2, 3, 4, 5]



次回に続きます。