計算機のプログラムの役割の大きな柱のひとつが データ処理である。多くの数のデータを処理できるプログラムは有用である。 Rubyでは配列を利用することで、多くのデータを処理するプログラムを手軽に作 ることができる。また、Rubyでは配列/ハッシュ/文字列には共通した メソッドが用意されている。それらを複合的に覚えていこう。
配列と文字列はどちらも要素の並びと捉えられる。
配列 の場合
x = [5, 6, 3, 2, 9]
の格納イメージ
x | ||||
x[0] | x[1] | x[2] | x[3] | x[4] |
5 | 6 | 3 | 2 | 9 |
文字列の場合
s = "Hello"
の格納イメージ
s | ||||
s[0] | s[1] | s[2] | s[3] | s[4] |
H | e | l | l | o |
いずれも添字0から始まる入れ物に順序よく収まっているもの と考えると、共通の概念で処理できるものがいくつか存在する。 まずは共通したメソッドを挙げる。
+ other
- 連結・結合
メソッド +
は、2つのオブジェクトを
繋げた新しい配列/文字列を返す。
配列の場合
元の配列と別の配列 other を繋げた新しい 配列を返す。元の配列は破壊されない。
x = [3, 4, 1, 2]
y = [9, 8, 7]
としたとき
x + y
=> [3, 4, 1, 2, 9, 8, 7]
x
=> [3, 4, 1, 2]
y
=> [9, 8, 7]
文字列の場合
元の文字列と別の文字列 other を繋げた新しい 文字列を返す。元の文字列は破壊されない。
s = "abc" t = "hoge"
としたとき
s + t
=> "abchoge"
となる。sとtの値は変わらない。
s => "abc" t => "hoge"
<< obj
-
末尾への破壊的要素追加
メソッド <<
は、末尾に新しい要素を
追加する。元の配列や文字列自体が変更される。
配列の場合
x << 5
=> [3, 4, 1, 2, 5]
x
=> [3, 4, 1, 2, 5]
文字列の場合
s << "hoge"
=> "abchoge"
sの値も変わる。
s
=> "abchoge"
このように、元の(配列の)値を直接書き換える操作を 破壊的操作という。
concat (other)
- 配列の破壊的結合
引数に指定した配列 otherを、元の配列の末尾につなげる。
y = [9, 8, 7]
x.concat(y)
=> [3, 4, 1, 2, 9, 8, 7]
x
=> [3, 4, 1, 2, 9, 8, 7]
y
=> [9, 8, 7]
文字列にも concat
メソッドは使用でき、
<<
と同じ働きをする。
each ブロック
- 配列/ハッシュの各要素に対するブロックの評価
配列/ハッシュの各要素に対して後続するブロックを 繰り返し評価する。
配列の場合
要素を受ける1つのブロック変数で繰り返す。
x = [3, 4, 1, 2, 5]
x.each do |i|
# この例の場合
# i=3, i=4, i=1, i=2, i=5 で5回繰り返す
end
これは、for
によるループに書き換えることができる。
for i in x
……各要素を変数 i に代入して繰り返す……
end
ハッシュの場合
key と value を受ける2つのブロック変数で繰り返す。
h = {"りんご" => 150, "みかん" => 30} h.each do |item, price| # この例の場合 # 「item="りんご", price=150」と # 「item="みかん", price=30」の組で # 2回繰り返す end
これは for
によるループに書き変えることができる。
for item, price in h ……各key, valueを変数 item, price に代入して繰り返す…… end
[nth]
- nth番目の取り出し
配列/文字列の添字 nth の位置の値を取り出す。
添字の先頭は0(ゼロ)、末尾から数えることもできてその場合は
-1 から負の数で指定する。範囲外指定には nil
が返る。
配列の場合
x = [3, 1, 2, 4] x[0] => 3 x[-2] => 2 x[99] => nil
文字列の場合
s = "hoge" s[0] => "h" s[-2] => "g" s[99] => nil
[start..last]
[start, len]
- 部分の取り出し
配列/文字列の部分集合を得る。 開始添字位置(start)と、 終了位置(last)または長さ(len)で指定する。
配列の場合
z = [1, 3, 5, 6, 2, 10, 3, 5] z[2..5] => [5, 6, 2, 10] z[2..-1] => [5, 6, 2, 10, 3, 5] z[-2..-1] => [3, 5] z[0, 7] => [1, 3, 5, 6, 2, 10, 3]
文字列の場合
u = "C1154320" u[2..5] => "1543" u[2..-1] => "154320" u[-2..-1] => "20" u[0, 7] => "C115432"
index(val)
- 指定要素の発見
配列/文字列から特定の値を探し位置を返す。
配列の場合
配列の中に、引数に指定した値 val に等しいものが
あるか調べ、最初に見つかった位置(添字)を返す。見つからなかった場合は、
nil
を返す。
x = [3, 1, 2, 4]
x.index(3)
=> 0
x.index(4)
=> 3
x.index(5)
=> nil
文字列の場合
文字列の中に、引数に指定した部分文字列 val に等しいものが
あるか調べ、最初に見つかった位置(添字)を返す。見つからなかった場合は、
nil
を返す。
m = "koekitaro@e.koeki-u.ac.jp"
m.index("@")
=> 9
m.index("koeki-u.ac.jp")
=> 12
m.index("f.koeki-u.ac.jp")
=> nil
length
- 配列/文字列の長さを返すsize
- length
と同じ
配列/文字列の長さ(要素数)を返す。
配列の場合
配列の要素数を返す。
x = [3, 1, 2, 4]
x.size
=> 4
x.length
=> 4 (同じ)
文字列の場合
文字列の長さを返す(バイト数ではない)。
"abcde".length => 5 "あいうえお".length => 5 "あいうえお".bytesize => 15 (UTF-8の場合)
バイト数は bytesize
メソッドで得られる。
reverse
reverse!
- 逆順にする。
配列/文字列の中味を全て逆順に並べたものを返す。
配列の場合
x = [3, 1, 2, 4]
x.reverse
=> [4, 2, 1, 3]
xの値は変わらない。
x
=> [3, 1, 2, 4]
reverse!
は元の配列を破壊的に変更する。
x.reverse! => [4, 2, 1, 3] x => [4, 2, 1, 3]
文字列の場合
s = "Hello"
s.reverse
=> "olleH"
sの値は変わらない。
s
=> "Hello"
reverse!
は元の配列を破壊的に変更する。
s.reverse! => "olleH" s => "olleH"
配列に固有のメソッドを示す。
join
join(sep)
- 配列を結合して文字列化
配列の各要素の間に文字列 sep を挟んで連結した
文字列を返す。sep を省略した場合は変数 $,
の値が利用される。$,
のデフォルト値はnil
で、その場合挟む文字列は空文字列(""
)である。
連結時に、配列の各要素は文字列化される。
x.join("/")
=> "3/1/2/4"
x.join(", ")
=> "3, 1, 2, 4"
$,
=> nil
x.join()
=> "3124"
shift
- 先頭要素の取り出し
配列の先頭要素を取り出し、その値を返す。配列はひとつずつ前に 詰められる。
x = [3, 1, 2, 4]
x.shift
=> 3
x
=> [1, 2, 4]
これを繰り返すと、配列の要素を先頭から取り出すループが、
while i=x.shift
printf("%d\n", i)
end
のように作成でき、以下の出力が得られる。
3
1
2
4
ループから抜けたときのx
の値は
[]
となる。
unshift(val)
- 先頭への要素の追加
配列の先頭に val を追加し、その配列を返す。 各要素はひとつずつ後にずれる。
x = [3, 1, 2, 4]
x.unshift(20)
=> [20, 3, 1, 2, 4]
x
=> [20, 3, 1, 2, 4]
uniq
uniq!
- 重複要素の削除
配列から重複した要素を取り除き、取り除いた部分を前に詰めた 配列を返す。
z = [1, 0, 2, 0, 3, 9, 7, 7, 1, 3, 2, 6, 5, 2]
z.uniq
=> [1, 0, 2, 3, 9, 7, 6, 5]
zの値は変わらない。
z
=> [1, 0, 2, 0, 3, 9, 7, 7, 1, 3, 2, 6, 5, 2]
いっぽう、uniq!
は、元の配列を破壊的に
書き換えて、重複したものを取り除いた配列を返す。
z = [1, 0, 2, 0, 3, 9, 7, 7, 1, 3, 2, 6, 5, 2]
z.uniq!
=> [1, 0, 2, 3, 9, 7, 6, 5]
z
=> [1, 0, 2, 3, 9, 7, 6, 5]
select ブロック
- 条件を満たす要素の選択
配列の要素をひとつずつ取り出しブロック
に渡し,ブロックが真(false以外)を返す要素のみからなる新規配列を返す。
複数の要素から特定の条件を満たすもののみ選び出したいときに利用する。
たとえば,z
が以下の内容のときを考える。
z = [1, 0, 2, 3, 9, 7, 6, 5]
この場合に偶数のみを含む配列を得たいときは, 「2で割った余りが0に等しいもの」を選ぶことで偶数を選別できる。
even = z.select {|i| i%2 == 0}
=> [0, 2, 6]
ブロック先頭にある |i|
は,順次取り出す要素を変数
i
に代入しつつ繰り返すことを意味する。i
に代入された値で i%2
を計算し,それを0と比較する
ことを繰り返している。
数値に限らずどのような要素でも適用できる。以下の配列
f
から,ひらがなで始まるもののみを選んでみる。
f = ["柿", "りんご", "サクランボ", "桃", "いちご"]
hira = f.select {|el| /[ぁ-ん]/ =~ el}
=> ["りんご", "いちご"]
reject ブロック
reject! ブロック
- 条件を満たす要素の削除
select
メソッドの逆で,
条件を満たす要素を削除する。reject
には破壊的メソッド reject!
がある。
f
=> ["柿", "りんご", "サクランボ", "桃", "いちご"]
f.reject{|i| /[ぁ-ん]/ =~ i}
=> ["柿", "サクランボ", "桃"]
f
=> ["柿", "りんご", "サクランボ", "桃", "いちご"]
f.reject!{|i| /[ぁ-ん]/ =~ i}
=> ["柿", "サクランボ", "桃"]
f
=> ["柿", "サクランボ", "桃"]
sort
sort ブロック
- 配列の内容のソート
配列の内容を並べ換える。ブロック を指定しない場合は
各要素を、演算子 <=>
で比較して
小さい順(昇順)に並べ換える。並べ換える規準を変えたいときには
引数を二つ取るブロック を指定する。具体的には
配列.sort {|x, y| xとyの比較式}
のように書く。たとえば、大きい順(降順)に並べ換えたい場合は、
z = x.sort{|x, y| y <=> x}
などとする(x
とy
の順番に注目)。
sort_by ブロック
-
配列の内容の効率的ソート
ブロックを渡す sort
同様だが、ソート基準とする
値を取り出す式のみ指定する。つまり、
配列.sort {|x| 比較すべき値を取り出す式}
のようにする。 ハッシュの value の大小関係でソートしたいときに有用で、 たとえば
item = {
"アルカリ異音水" => 150,
"おでんつゆ" => 50,
"マグロの煮こごり" => 500
}
という商品名vs.単価リストを持つハッシュの商品名を 単価の小さい(安い)順に並べ換えるには以下のようにする。
item.keys.sort_by{|x| item[x]}
chop
chop!
元の文字列の最後の一文字を取り除いたものを返す(最後が
DOS改行 \r\n
なら2文字削除する)。
chop!
は
元の文字列を破壊的に書き変える。
"abc\n".chop => "abc" "abc\r\n".chop => "abc" "abc\n".chop.chop => "ab"
chomp
(rs)chomp
chomp!
(rs)chomp!
- 文字列の末尾削除(条件付)
文字列末尾から調べて rs という文字列があれば
それを取り除く。なければなにもせず、nil
を返す。
rs を指定しない場合は改行文字が取り除か
れる(厳密には変数 $/
の値)。chomp!
は、
元の文字列を破壊的に変更する。
x="abc\n" x.chomp => "abc" x => "abc\n" (変わらない) x="abc" x.chomp => "abc" x="abc\n" x.chomp! => "abc" x => "abc" (変わる)
通常、ファイルや端末から1行ずつ読み込んだ文字列には
末尾に改行文字(\n
)がつくので、
これを取り除きたいときに利用する。
downcase
downcase!
- 文字列の小文字化upcase
upcase!
- 文字列の大文字化capitalize
capitalize!
- 文字列のキャピタライズ
元の文字列のうちアルファベットをすべて 小文字化
(downcase
)、大文字化(upcase
)、または
先頭大文字化(capitalize
)する。
downcase! upcase! capitalize!
は、破壊的に書き変える。
x="Hello, World!!" x.upcase => "HELLO, WORLD!!" x.downcase => "hello, world!!" x.capitalize => "Hello, world!!"
gsub
(pattern){replace}gsub!
(pattern){replace}
- 文字列の置き換え
元の文字列のうち pattern にマッチするものを すべて replace に置き換え、その結果の 文字列を返す。
単純に文字列を置換することもできるし、
x = "This is a pen" y = x.gsub(/is/){"IS"} x => "This is a pen" y => "ThIS IS a pen"
パターンを指定して、マッチするものすべてを置換することもできる。
x = "This is a pen. That is a pencil." x.gsub(/is/){"IS"} (単純な is 指定) => "ThIS IS a pen. That IS a pencil." x.gsub(/\bis\b/){"IS"} (\bは「単語の境界」を意味する正規表現) => "This IS a pen. That IS a pencil."
単語の末尾1字のみ大文字に変えるには以下のようにする。
x = "This is a pen. That is a pencil." x.gsub(/(\w*)(\w)\b/){$1+$2.upcase} => "ThiS iS A peN. ThaT iS A penciL."
パターンの部分 pattern にグルーピングの ( ) を使った場合、
括弧内の正規表現にマッチした部分文字列が自動的に
$1, $2, $3, ...
に保存される。これらは
置き換え文字列 replace で利用でき、
$1
は1個目の( )にマッチした文字列に、
$2
は2個目の( )にマッチした文字列に、
$N
はN個目の( )にマッチした文字列に、
それぞれ置き換えられる。
上記の正規表現では最初の括弧 (\w*)
が最後の1字を除いた英単語で、2個目の括弧
(\w)
が英単語の最後の1字に
マッチする。\b
は単語の境界を意味するのでマッチングは
1単語ごとに行なわれる。
以下の例は、文字列中に現れるすべての `dog' または `cat' を
それぞれ、`pretty dog', `pretty cat' に置き換える。cat, dog が
複数形だった場合にもマッチし (s?
)、
その場合は置換え後の文字列にもsを付ける。
x="I saw cats scattered by a dog."
x.gsub(/\b(dog|cat)(s?)\b/i){'pretty '+$1+$2}
=> "I saw pretty cats scattered by a pretty dog."
置換え後の文字列に $1, $2
を使う場合には
引用符の外に出すように気を付ける。
sub
(pattern){replace}sub!
(pattern){replace}
- 文字列の置き換え
上記 gsub(gsub!)
はパターンにマッチするものすべてを
置き換えるが、sub(sub!)
は最初の一つだけを置き換える。
その他の点はgsub(gsub!)
と同じ。
x = "This is a pen. That is a pencil."
x.sub(/is/){"IS"}
=> "ThIS is a pen. That is a pencil."
split
split
(sep) - 文字列の分割
元の文字列を、指定したパターン sep を区切りと 見なして分割し、分割された文字列を配列に格納して返す。sep を省略したときは空白を区切りと見なす(厳密には変数 $; の値で、$; がnil の場合は先頭を除いた空白となる)。
特定の記号(カンマなど)で区切られた文字列を一気に分割して
配列に格納するときに利用する。
join
の逆と思えば良い。たとえば、カンマで区切られた次のような
データがあるとする。
中町太郎, 90, 70,20
各項目が「カンマ(,) スペース(が0個以上)」 で区切られている。
これを正規表現にすると /,\s*/
となるので、
以下のようにすると
この行から項目だけを抽出した配列が生成される。
"中町太郎, 90, 70,20".split(/,\s*/)
=> ["中町太郎", "90", "70", "20"]
これまで何度か登場した成績処理プログラムをsplit
を利用して書き換える場合、データの読み込み部分を以下のように
変更すればよい。
正規表現で分解するバージョン:
point = Hash.new while line=gets if /(\S+)\s+(\d+)\s+(\d+)/ =~ line point[$1] = [$2.to_i, $3.to_i] # 配列を代入 end end
↓
split
で分解するバージョン:
point = Hash.new while line=gets data = line.split(/,\s*/) point[data[0]] = data[1..-1] end