「基礎プログラミングI」では配列の基礎について習った。今回は配列の操作を行う際に役に立ついくつかのメソッドを新たに紹介する。
length
または
size
(復習)
配列の長さ(要素の数)を返す。
push
(復習)
配列の後ろに新しい要素を追加する。同時に複数の要素を追加することもできる。元の配列が直接更新される(破壊的操作)。
例:
練習問題numbers = [7, 5, 1]
numbers.push(22) # -> numbers は [7, 5, 1, 22] になる
numbers.push(10, 13) # -> numbers は [7, 5, 1, 22, 10, 13] になる
p numbers
<<
演算子
配列の後ろに新しい要素を追加する。push
と違い、追加できる要素は1つだけ。元の配列が直接更新される。
例:
練習問題numbers = [7, 5, 1]
numbers << 22 # -> numbers は [7, 5, 1, 22] になる
p numbers
pop
配列の末尾の要素を取り除いた上で、取り除いた要素の値を返す。引数として整数を与えれば同時に複数の要素を取り除くこともできる(配列として返される)。要素がない場合にはnil
を返す。破壊的操作である。
例:
練習問題pop_numbers.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
numbers = [1, 5, 15, 150]
printf("最初は numbers の値は %s です。\n\n", numbers) # このように配列全体を文字列として表示できる
puts "num = numbers.pop のあとは、"
num = numbers.pop
printf("num の値は %s です。\n", num)
printf("numbers の値は %s です。\n\n", numbers)
puts "さらに num = numbers.pop(2) のあとは、"
num = numbers.pop(2)
printf("num の値は %s です。\n", num)
printf("numbers の値は %s です。\n", numbers)
結果:
最初は numbers の値は [1, 5, 15, 150] です。
num = numbers.pop のあとは、
num の値は 150 です。
numbers の値は [1, 5, 15] です。
さらに num = numbers.pop(2) のあとは、
num の値は [5, 15] です。
numbers の値は [1] です。
shift
(復習)
配列の先頭の要素を取り除いた上で、取り除いた要素の値を返す。引数として整数を与えれば同時に複数の要素を取り除くこともできる(配列として返される)。破壊的操作である。
unshift
配列の先頭に新しい要素を追加する。同時に複数の要素を追加することもできる。破壊的操作である。
例:
練習問題numbers = [7, 5, 1]
numbers.unshift(22) # -> numbers は [22, 7, 5, 1] になる
numbers.unshift(10, 13) # -> numbers は [10, 13, 22, 7, 5, 1] になる
p numbers
+
演算子
2つの配列をつなげた新しい配列を返す。もとの配列は変化しない。
例:
練習問題a = [1, 2]
b = [3, 4]
c = a + b
p a # -> 結果: [1, 2]
p b # -> 結果: [3, 4]
p c # -> 結果: [1, 2, 3, 4]
concat
引数に指定した配列を、元の配列の末尾につなげる。もとの配列は変化するが、引数に指定された配列は変化しない。
例:
練習問題a = [1, 2]
b = [3, 4]
a.concat(b)
p a # -> 結果: [1, 2, 3, 4]
p b # -> 結果: [3, 4]
index
引数に指定した値と等しい値が配列の中に入っているか調べ、最初に見つかった位置(インデックス)を返す。見つからない場合はnil
を返す。
例:
練習問題numbers = [3, 4, 5, 3, 4, 5]
p numbers.index(5) # -> 結果: 2
p numbers.index(10) # -> 結果: nil
uniq
または
uniq!
重複した要素を配列から取り除く。uniq
は結果を新しい配列として返す。いっぽう、uniq!
は元の配列を破壊的に書き換える。
例:
練習問題numbers = [3, 4, 5, 3, 6, 5]
nums_uniq = numbers.uniq
p nums_uniq # -> 結果: [3, 4, 5, 6]
p numbers # -> 結果: [3, 4, 5, 3, 6, 5]
numbers.uniq!
p numbers # -> 結果: [3, 4, 5, 6]
join
配列の各要素を文字列化した上で、すべて連結し一つの文字列として返す。引数に文字列を指定すれば、それを区切りとして使う(各要素の間に挟む)。
例:
練習問題letters = ['H', 'e', 'l', 'l', 'o']
words = ["This", "is", "a", "test"]
p letters.join # -> 結果: "Hello"
sentence = words.join(' ') # -> sentence の値は "This is a test"
p [255, 255, 255, 0].join('.') # -> 結果: "255.255.255.0"
select
または
select!
配列の要素の中から、特定の条件を満たす要素のみを含む新しい配列を返す。条件はブロック構文を使って以下のように指定する。
配列名.select {|要素| 要素に対する条件}
以下の例では、配列 numbers
の要素(ここでは x
という変数名で表しているが他の名前でも構わない)から、2で割った余りが0になるという条件を満たす要素(偶数)のみを抽出する。
numbers = [3, 4, 5, 6]
even = numbers.select {|x| x%2 == 0}
p even # -> 結果: [4, 6]
数値に限らずどのような要素でも適用できる。以下の例では正規表現を使って配列 words
から,ひらがなを含むもののみを選ぶ。
words = ["ご飯", "パン", "じゃがいも", "肉", "果物"]
wds_hiragana = words.select {|w| /[ぁ-ん]/ =~ w} #「あ」から「ん」までの文字を含む要素だけが正規表現にマッチし選択される
p wds_hiragana # -> 結果: ["ご飯", "じゃがいも"]
select!
は元の配列を破壊的に書き換える。
reject
または
reject!
select
の逆で, 条件を満たす要素を配列から削除する。reject
は新しい配列を返すのに対し、reject!
は元の配列を直接書き換える。
例:
練習問題words = ["ご飯", "パン", "じゃがいも", "肉", "果物"]
words.reject! {|w| /[ぁ-ん]/ =~ w} # ひらがなを含むものは削除する
p words # -> 結果: ["パン", "肉", "果物"]
[ ]
による部分配列の取得
[ ]
はこれまで words[n]
のように、配列のn+1
番目の要素の参照や代入を行う時に使ってきた。もう一つの使い方として、一つの添え字の代わりに範囲を指定すれば、配列の部分配列を取得できる。
配列名[添え字1..添え字2]
上記のように書くことによって、配列の添え字1から添え字2までの部分を取り出すことができる。
例:
練習問題numbers = [10, 11, 12, 13, 14, 15]
p numbers[1..3] # -> 結果: [11, 12, 13]
配列の後ろからいくつかの要素を取り除いてほしい時、添え字も後ろから数えた方が早い。その場合は [-n]
のように添え字にマイナス記号を付けると末尾からのn
番目の要素を意味することになる(先頭からの数え方と違い、-0
じゃなくて -1
から数え始めることに注意)。
例:
練習問題numbers = [10, 11, 12, 13, 14, 15]
p numbers[-1] # -> 結果: 15
p numbers[1..-2] # -> 結果: [11, 12, 13, 14]
添え字1を省略すると、配列の先頭から添え字2までの部分配列を取得できる。 逆に、添え字2を省略すれば、添え字1から配列の最後までの部分を取り出す。
例:
練習問題numbers = [10, 11, 12, 13, 14, 15]
p numbers[..-2] # -> 結果: [10, 11, 12, 13, 14]
p numbers[2..] # -> 結果: [12, 13, 14, 15]
また、取り出したい範囲が始まる添え字とその長さ(要素数)を指定するという方法もある。
配列名[開始位置の添え字,要素数]
例:
練習問題numbers = [10, 11, 12, 13, 14, 15]
p numbers[2,2] # -> 結果: [12, 13]
p numbers[-4,3] # -> 結果: [12, 13, 14]
以前も説明したとおり、Rubyプログラム中の文字列は文字が入っている配列のように考えることができる。実際に配列と文字列で共通しているメソッドが多くて、上記に説明したメソッドの中次のものは文字列に対しても利用できる。
length
, size
<<
演算子
例:
練習問題word = "Rub"
word << "y"
p word # -> 結果: "Ruby"
+
演算子
例:
練習問題text = "Ruby" + " " + "programming"
p text # -> 結果: "Ruby programming"
concat
例:
練習問題a = "Ruby"
b = "programming"
a.concat(" " + b)
p a # -> 結果: "Ruby programming"
index
例:
練習問題p "Ruby".index("b") # -> 結果: 2
[ ]
による部分文字列の取得
例:
練習問題p "Ruby"[..-2] # -> 結果: "Rub"
p "Ruby"[2,2] # -> 結果: "by"
chomp
またはchomp!
chomp
はこれまで何回か使ったことがある。簡単に説明すれば文字列の末尾から改行文字を削除するためのメソッドである。chomp!
は元の文字列を直接書き換える。
downcase
またはdowncase!
文字列のアルファベット文字をすべて小文字にする。
例:
練習問題p "Hello World".downcase # -> 結果: "hello world"
upcase
またはupcase!
文字列のアルファベット文字をすべて大文字にする。
例:
練習問題p "Hello World".upcase # -> 結果: "HELLO WORLD"
split
元の文字列を、引数に指定したパターン(文字列または正規表現)を区切りと見なして分割し、分割された文字列を配列に格納して返す。引数を省略すると空白を区切りと見なす。
例:
練習問題txt = "This is a text"
p txt.split # -> 結果: ["This", "is", "a", "text"]
p txt.split(' ') # -> 結果: ["This", "is", "a", "text"]
p txt.split(/\s+/) # -> 結果: ["This", "is", "a", "text"]
p txt.split('') # -> 結果: ["T", "h", "i", "s", " ", "i", "s", " ", "a", " ", "t", "e", "x", "t"]
p "255.255.255.0".split('.') # -> 結果: ["255", "255", "255", "0"]
「基礎プログラミングI」の第5回では配列に備わっているメソッドの sort
と reverse
について習った。
sort
または sort!
: 配列の要素を小さい順(昇順)に並べ替える。sort!
は破壊的である。reverse
または reverse!
: 配列の要素の順序を逆にする。reverse!
は破壊的である。sort
または sort
と reverse
の組み合わせを使って、数値や文字列を格納した配列の要素を小さい順(昇順)あるいは大きい順(降順)に整列することができる。
letters = ["d", "う", "b", "お", "あ", "c", "a"]
numbers = [7, 5, 1, 22]
p letters.sort # -> 結果: ["a", "b", "c", "d", "あ", "う", "お"]
numbers.sort!.reverse! # -> numbers は [22, 7, 5, 1] になる
p numbers
数値の大小や文字列の辞書順以外の条件を使って並べ替えを行いたい場合は、sort
や sort!
にはブロック({ }
)を付けてその中で独自のソート基準を指定することができる。
配列名.sort {|要素1, 要素2| 要素1と要素2の比較基準}
具体的な例で見てみよう。例えば、複数の文字列を格納した配列をその文字列の長さの昇順で整列したい場合は以下のように記述すれば良い。
練習問題yoda = ["your", "father", "I", "am"]
vader = yoda.sort {|a,b| a.size <=> b.size}
p vader # -> 結果: ["I", "am", "your", "father"]
sort {|a,b| a.size <=> b.size}
は「任意の配列要素 a
とその次の要素 b
の長さ(size
)を比較した時、b
の方が長いように並べ替えよう」という意味である。
ブロックの中で配列の要素を表している a,b
は違う変数名でも構わない。
ソートの比較基準の記述で使われている「<=>
」とはRubyにおける比較演算子の一つで、比較する値の大小によって異なる結果を整数として返す:
1
-1
0
降順でソートしたい場合は、比較基準の中で配列要素を表す変数を逆に書けば良い。
練習問題yoda = ["your", "father", "I", "am"]
vader_rev = yoda.sort {|a,b| b.size <=> a.size}
p vader_rev # -> 結果: ["father", "your", "am", "I"]
昇順でのソートの場合は sort_by
というもう一つのメソッドでも同じ結果が得られる。
yoda = ["your", "father", "I", "am"]
vader = yoda.sort_by {|x| x.size} # sort {|a,b| a.size <=> b.size} と同じ結果
p vader # -> 結果: ["I", "am", "your", "father"]
vader_rev = yoda.sort_by {|x| x.size}.reverse # sort {|a,b| b.size <=> a.size} と同じ結果
p vader_rev # -> 結果: ["father", "your", "am", "I"]
比較基準の記述で利用できるのは配列要素に備わっているメソッドだけではなく、自分で定義したメソッドも活用できる。次の例では、単語が含むひらがな文字の数を調べるメソッドを作ってその結果をソート基準として用いる。
練習問題def count_hiragana(word) # word が含むひらがな文字の数を調べるメソッド
count = 0 # ひらがな文字を数えるための変数
word.each_char do |char| # each_charメソッドは文字列から各文字を順次に取り出す
if /[ぁ-ん]/ =~ char # ひらがな文字かどうかの確認
count += 1
end
end
return count
end
words = ["ご飯", "パン", "じゃがいも", "食べる"]
sorted = words.sort_by {|w| count_hiragana(w)} # または sort {|a,b| count_hiragana(a) <=> count_hiragana(b)}
p sorted # -> 結果: ["パン", "ご飯", "食べる", "じゃがいも"]
下記のプログラムでは、各配列要素を3で割った時の剰余をソート基準にしている。
練習問題numbers = [0,1,2,3,4,5,6,7,8,9,10,100,120,140,160,180,200,300,400,500,650,800,950]
puts "<x> <x%3>"
for n in numbers.sort_by {|x| x%3}
printf("%3d %5d\n", n, n%3)
end
実行結果:
<x> <x%3>
0 0
9 0
180 0
3 0
6 0
300 0
120 0
400 1
1 1
10 1
100 1
7 1
4 1
160 1
140 2
8 2
200 2
5 2
2 2
500 2
650 2
800 2
950 2
剰余が同じである配列要素同士はさらに数値自体の大小で並べ替えた方が読みやすいだろう。このような場合には複数のソート基準を指定できる。プログラムを改善しよう。
練習問題numbers = [0,1,2,3,4,5,6,7,8,9,10,100,120,140,160,180,200,300,400,500,650,800,950]
puts "<x> <x%3>"
for n in numbers.sort_by {|x| [x%3, x]} # または sort {|a,b| [a%3, b] <=> [a%3, b]}
printf("%3d %5d\n", n, n%3)
end
実行結果:
<x> <x%3>
0 0
3 0
6 0
9 0
120 0
180 0
300 0
1 1
4 1
7 1
10 1
100 1
160 1
400 1
2 2
5 2
8 2
140 2
200 2
500 2
650 2
800 2
950 2
ハッシュに対しても sort
メソッド、sort_by
メソッドで並べ替えができる。ただし、並べ替えの結果はハッシュでなく配列になるので注意が必要である。
例:
練習問題alt = {"富士山" => 3776, "鳥海山" => 2236, "月山" => 1984}
sorted = alt.sort
p sorted # -> 結果: [["富士山", 3776], ["月山", 1984], ["鳥海山", 2236]]
ハッシュの各要素が配列 [キー, 値] になった要素の配列に変換されて、key に基づいて並べ替えが行われた。
ハッシュ.sort {|a, b| ソート基準}
この場合、ハッシュの各要素が「a=
[キー, 値]」と「b=
[キー, 値]」に代入されるので、キーは a[0]
と b[0]
、値は a[1]
と b[1]
でアクセスできる。
また、
ハッシュ.sort_by {|x| ソート基準}
の場合は、ハッシュの各要素が「x=
[キー, 値]」に代入されるので、キーは x[0]
、値は x[1]
になる。
例:
練習問題alt = {"富士山" => 3776, "鳥海山" => 2236, "月山" => 1984}
sorted = alt.sort_by {|x| x[1]} # value に基づいて並べ替える => [["月山", 1984], ["鳥海山", 2236], ["富士山", 3776]]
for k, v in sorted
printf("%3s は %dm\n", k, v)
end
実行結果:
月山 は 1984m
鳥海山 は 2236m
富士山 は 3776m
今回習ったことを使って、ユーザーが指定した条件のもとで辞書項目を整列するプログラム sort_dictionary.rb
を作成せよ。
データ: dictionary.txt
(出典:Noah Webster著, 「American dictionary of the English language」(1828))
データは各行が 単語 : 定義文 というフォーマットになっている。 プログラムではそれを 単語 が key で、定義文(単語の意味の説明)が value のハッシュとして読み込み、ユーザーが指定した条件に沿ってハッシュの並べ替えをすること。
具体的には、ユーザーが入力した単語がそれぞれの辞書項目の定義文に出現している頻度の大きい順にソートし、その順番で並べたハッシュのキーと問い合わせ単語の出現頻度を表示すること。ここで、特定の単語がある辞書項目の定義文に出現している「頻度」の定義とは、定義文が格納された文字列に対して downcase.split(/\s/)
をした結果として返される配列において、その単語に等しい文字列が入っている要素の数とする(downcase
は例えば 「The」と「the」を同じ単語として数えるためである)。
実行結果の例:
{c11xxxx}% ruby sort_dictionary.rb
整列の条件として使う単語を入力してください。
of
<辞書項目> <of の出現頻度>
evening 12
morning 7
night 5
forenoon 4
noon 2
midnight 1
after-noon 1
ヒント:問い合わせ単語の出現頻度を計算する手順はメソッドとして定義し、ソートに指定するブロックの中と結果を表示するところからそのメソッドを呼び出すこと。
発展課題
split(/\s/)
だけをすれば定義文中の単語に記号(カンマやピリオドなど)が付いている場合は別の単語の扱いになってしまう(例えば「night
」の頻度を調べる場合は「night.
」や「night;
」はカウントされない)。改善せよ。