Rubyでの配列メソッドの使用方法
イントロダクション
配列はプログラム内でデータのリストを表現することができます。配列にデータがある場合、ソートしたり、重複を削除したり、順序を逆にしたり、配列の一部を抽出したり、特定のデータを検索したりすることができます。また、配列を文字列に変換したり、データの配列を別の配列に変換したり、配列を1つの値にまとめたりすることもできます。
このチュートリアルでは、配列に格納されたデータを扱うためにRubyが提供する最も実用的な方法を探索します。
このチュートリアルを進めると、「!」で終わるメソッドがいくつか出てきます。これらのメソッドには、元の値を変更したり例外を発生させたりするなどの副作用がよくあります。このチュートリアルで使用する多くのメソッドには、この接尾辞を持った関連メソッドがあります。
また、疑問符(?)で終わるメソッドにも出会うことがあります。これらのメソッドは真偽値を返します。
これらはRuby全体で使用される命名規則です。プログラムレベルで強制されるものではありません。ただ、メソッドから期待できるものを特定する別の方法です。
配列メソッドの探索を始める前に、要素にアクセスするいくつかの方法を見てみましょう。
要素にアクセス
Rubyでの配列の操作についてのチュートリアルをすでにフォローしている場合は、インデックスを使用して個別の要素にアクセスできることを知っています。インデックスは0から始まりますので、次のようになります。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks[0] # "Tiger"
sharks[1] # "Great White"
sharks[-1] # "Angel"
あなたも思い出すかもしれませんが、最初と最後の要素を取得するために最初と最後のメソッドを使用することもできます。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.first # "Tiger"
sharks.last # "Angel"
最後に、存在しない要素にアクセスすると、nilが返されます。しかし、エラーを取得したい場合は、fetchメソッドを使用してください。
sharks.fetch(42)
IndexError: index 42 outside of array bounds: -4…4
エラーを発生させる代わりに、独自のデフォルト値を指定することもできます。
sharks.fetch(42, "Nope") # "Nope"
では、配列から複数の要素を取得する方法を見てみましょう。
複数の要素の取得
単一の要素ではなく、配列から値のサブセットを取得したい場合があります。
もし、始点のインデックスを指定し、その後に要素の数を指定すれば、それらの値を含んだ新しい配列を取得できます。たとえば、以下のようにしてsharks配列から中間の2つのエントリーを取得することができます。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks[1,2] # ["Great White", "Hammerhead"]
1番目のインデックスは「グレートホワイト」としてスタートし、2つの要素を指定します。そのため、新しい配列には「グレートホワイト」と「ハンマーヘッド」が含まれます。
同じことをするために、スライスメソッドを使用することができます。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.slice(1,2) # ["Great White", "Hammerhead"]
スライスメソッドは新しい配列を返し、元の配列は変更されません。ただし、スライス!メソッドを使用すると、元の配列も変更されます。
takeメソッドは、配列の先頭から指定した数のエントリを取得することができます。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.take(2) # ["Tiger", "Great White"]
時々、特定の値ではなく、配列からランダムな値を取得したいと思うことがあります。それについて探ってみましょう。
配列からランダムなエントリーを取得する。 (Ryōretsu kara randamuna entorī o shutoku suru)
ゲームの開発をしているかもしれませんし、コンテストの勝者を選ぶプログラムを作成しているかもしれません。そのような場合には、ランダムな値が必要です。一般的な解決策として、可能な選択肢を配列に格納し、ランダムなインデックスを選択する方法があります。
配列からランダムな要素を取得するには、配列の最初のインデックスから最後のインデックスまでの範囲でランダムなインデックスを生成し、その値をインデックスとして使用して値を取得する方法があります。しかし、より簡単な方法があります:sampleメソッドは配列からランダムなエントリーを取得します。
ランダムに選ばれたストック回答の配列から、マジック8ボールゲームの初歩的なバージョンを作るためにそれを使いましょう。
8ボール.rbを日本語に自然な形で言い換えてください。ただし、一つのオプションだけで十分です。
ボーリング8.rb
answers = ["Yes", "No", "Maybe", "Ask again later"]
print answers.sample
Maybe
サンプルメソッドは引数も受け入れますが、それはランダムなエントリの配列を返します。したがって、複数のランダムなエントリが必要な場合は、希望する数を指定してください。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sample = sharks.sample(2)
print sample
[“Whale”, “Great White”]
次に、配列内の特定の要素を探し方について見てみましょう。
要素の検索とフィルタリングの探し方
配列内の特定の要素を探している時は、通常、該当する要素が見つかるまで配列の要素を繰り返し処理することになります。しかし、Rubyの配列には、配列内を検索するプロセスを簡略化するために特に設計されたいくつかのメソッドが用意されています。
要素が存在するかどうかだけを確認したい場合は、include?メソッドを使用することができます。このメソッドは、指定されたデータが配列の要素であればtrueを返します。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sharks.include? "Tiger" # true
["a", "b", "c"].include? 2 # false
ただし、include?は完全一致を必要とするため、部分的な単語を探すことはできません。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sharks.include? "Tiger" # true
sharks.include? "tiger" # false
sharks.include? "ti" # false
指定した条件に一致する、配列内の最初の要素を見つけて返すfindメソッド。
例えば、文字”a”を含むsharks配列の最初のエントリを特定するには、各エントリを比較し、最初のエントリを見つけた時点で反復処理を停止するように、eachメソッドを使用することができます。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = nil
sharks.each do |shark|
if sharks.include? "a"
result = shark
break
end
end
同じことをするために、”find”メソッドを使用することもできます。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.find {|item| item.include?("a")}
print result
Hammerhead
与えられたブロックを配列の各要素に対して実行するfindメソッドです。ブロック内の最後の式がtrueと評価される場合、findメソッドはその値を返し、反復を停止します。全ての要素を反復した後でも何も見つからない場合は、nilを返します。
セレクトメソッドは同様の方法で機能しますが、条件に一致する要素すべてを含む新しい配列を作成して返します。一つの値を返すだけでなく、処理を終了する代わりに。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
results = sharks.select {|item| item.include?("a")}
print results
[“Hammerhead”, “Great White”, “Whale”]
rejectメソッドは、条件に一致しない要素を含まない新しい配列を返します。要素を削除するフィルターと考えることができます。以下は、文字”a”を含むすべてのエントリーを拒否する例です。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
results = sharks.reject {|item| item.include?("a")}
print results
[“Tiger”]
新しい配列を返すのは、selectとrejectの両方のメソッドですが、オリジナルの配列は変更されません。しかし、select!とreject!メソッドを使用すると、オリジナルの配列が変更されます。
find_allメソッドはselectの別名ですが、find_all!メソッドは存在しません。
次に、配列の値をソートする方法について見てみましょう。
配列のソート
データを整理することは一般的な方法です。名前のリストをアルファベット順に並べたり、数字を小さい順から大きい順に並べ替える必要があるかもしれません。
Rubyの配列には、要素の順序を逆にすることができるreverseメソッドがあります。既に整理されたデータのリストがある場合、reverseは要素を簡単に反転させるための便利な方法です。
sharks = ["Angel", "Great White", "Hammerhead", "Tiger"]
reversed_sharks = sharks.reverse
print reversed_sharks
[“Tiger”, “Hammerhead”, “Great White”, “Angel”]
逆メソッドは新しい配列を返し、元の配列は変更しません。オリジナルの配列を変更したい場合は、reverse!メソッドを使用してください。
ただし、配列を逆にすることは常にデータを整理する最も効率的で実用的な方法ではありません。ソートメソッドを使用して、配列の要素を好みの方法で整理してください。
単純な文字列や数字の配列においては、ソートメソッドは効率的であり、求めている結果を提供します。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort
print sorted_sharks
[“Angel”, “Great White”, “Hammerhead”, “Tiger”]
ただし、異なる方法で物事をソートしたい場合は、ソートメソッドにその方法を教える必要があります。ソートメソッドは、Rubyブロックを受け取り、それにより配列内の要素にアクセスして比較することができます。
比較するためには、比較演算子(<=>)を使用します。この演算子は、2つのRubyオブジェクトを比較し、左側のオブジェクトが小さい場合は-1を、オブジェクトが同じ場合は0を、左側のオブジェクトが大きい場合は1を返します。
1 <=> 2 # -1
2 <=> 2 # 0
2 <=> 1 # 1
Rubyのsortメソッドは、配列内の値をソートするために-1、0、または1を返す必要があるブロックを受け付けます。
ここには、昇順に並べ替える配列のエントリーを明示的に比較する例があります。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort{|a,b| a <=> b }
print sorted_sharks
変数aとbは、比較対象となる配列内の個々の要素を表します。結果は以下のようになります。
[“Angel”, “Great White”, “Hammerhead”, “Tiger”]
鮫の並び順を逆にするには、比較対象のオブジェクトを逆にします。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort{|a,b| b <=> a }
print sorted_sharks
[“Tiger”, “Hammerhead”, “Great White”, “Angel”]
ソートメソッドは、整数、浮動小数点数、文字列などの単純なデータ型の配列には非常に優れています。しかし、より複雑なオブジェクトが含まれる配列の場合、少し手間をかける必要があります。
次は、各ハッシュがサメを表している配列です。
sharks = [
{name: "Hammerhead"},
{name: "Great white"},
{name: "Angel"}
]
この配列をソートすることは簡単ではありません。配列にソートを呼び出すことは失敗します。
sharks.sort
ArgumentError: comparison of Hash with Hash failed
比較をするためには、ソートに何を比較したいか伝える必要があります。したがって、ハッシュの:nameキーの値を比較します。
sorted_sharks.sort{|a, b| a[:name] <=> b[:name]}
print sorted_sharks
[{:name=>”Angel”}, {:name=>”Great white”}, {:name=>”Hammerhead”}]
より複雑な構造で作業をしている場合は、ソートにより効率的なアルゴリズムを使用するsort_byメソッドを検討してみることをおすすめします。sort_byメソッドでは、配列内の現在の要素への参照という1つの引数を必要とするブロックを使用します。
sharks = [
{name: "Hammerhead"},
{name: "Great white"},
{name: "Angel"}
]
sorted_sharks = sharks.sort_by{|shark| shark[:name] }
print sorted_sharks
[{:name=>”Angel”}, {:name=>”Great white”}, {:name=>”Hammerhead”}]
sort_byメソッドは、特定のキーの値に基づいてオブジェクトを比較するのに最適なソートアルゴリズムであるシュワルツィアン変換を実装しています。そのため、オブジェクトのコレクションを比較する際には、効率的であるためsort_byを使用することが多くなります。
sort と sort_by は新しい配列を返し、元の配列をそのままにします。元の配列を変更したい場合は、代わりに sort! と sort_by! を使用してください。
並べ替えるだけでなく、重複を取り除きたい場合もあるかもしれません。
重複した要素を削除する。
時々、重複があるデータのリストを受け取ることがあります。配列を繰り返して重複を取り除くこともできますが、Rubyのuniqメソッドを使うと、それがかなり簡単になります。uniqメソッドは、重複した値を取り除いた新しい配列を返します。
[1,2,3,4,1,5,3].uniq # [1,2,3,4,5]
時には、2つのデータセットをマージすると、重複が発生することがあります。以下の2つのサメの配列を取り上げましょう。
sharks = ["Tiger", "Great White"]
new_sharks = ["Tiger", "Hammerhead"]
それらを合算すれば、重複したエントリが得られます。 (Sorera o gōsan sureba, jūfuku shita entorī ga eraremasu)
sharks + new_sharks
# ["Tiger", "Great White", "Tiger", "Hammerhead"]
重複を削除するためにuniqを使用することができますが、それらを完全に避ける方がベターです。配列を追加する代わりに、パイプ演算子|を使用して配列を結合してください。
sharks | new_sharks
# ["Tiger", "Great White", "Hammerhead"]
Rubyの配列では、引き算もサポートされており、new_sharksをsharksから引くことで新しい値のみを取得することができます。
sharks = ["Tiger", "Great White"]
new_sharks = ["Tiger", "Hammerhead"]
sharks - new_sharks # ["Great White"]
次に、各要素の値を操作する方法について見てみましょう。
データの変換
マップメソッドとその別名であるコレクトは、配列の内容を変換することができます。つまり、配列の各要素に対して操作を行うことができます。
例えば、配列の各要素に算術演算を行い、新しい値を含んだ新しい配列を作成するために、mapを使用することができます。
numbers = [2,4,6,8]
# square each number
squared_numbers = numbers.map {|number| number * number}
print squared_numbers
squared_numbers変数は、元の数字を二乗した配列です。
[4, 16, 36, 64]
マップは、ウェブアプリケーションでよく使われるため、配列をHTMLのドロップダウンリストの要素に変換するために使用されます。次は、その簡略化されたバージョンの一例です。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
options = sharks.map {|shark| "<option>#{shark}</option>"}
print options
現在、オプションの配列には各シャークがHTMLタグので囲まれています。
["<option>Hammerhead</option>", "<option>Great White</option>", "<option>Tiger</option>", "<option>Whale</option>"]
マップは新しい配列を返し、元の配列を変更しません。マップを使うと既存の配列が変更されます。また、マップにはコレクトと呼ばれる別名があります。自分のコードでは一貫性を持ってどちらかを使うようにしてください。
mapは新しい配列を返すため、その配列はさらに変形や操作が可能であり、文字列に変換することさえできます。次はそれを見てみましょう。
配列を文字列に変換する
Rubyのすべてのオブジェクトはto_sメソッドを持ち、オブジェクトを文字列に変換します。これがprint文が使用するものです。私たちのサメの配列を考えてみましょう。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
to_sメソッドを呼び出すことで、この文字列が作成されます。
"[\"Hammerhead\", \"Great White\", \"Tiger\", \"Whale\"]"
それはデバッグには役立ちますが、実際のプログラムではあまり役に立ちません。
joinメソッドは、配列を文字列に変換しますが、要素をどのように結合するかの制御を提供します。joinメソッドは、区切り文字として使用したい文字を指定する引数を受け取ります。例えば、サメの配列をスペースで区切られたサメの名前の文字列に変換するには、次のようにします:
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join(" ")
print result
Hammerhead Great White Tiger Whale
もし各鮫の名前をコンマとスペースで区切りたい場合は、デリミタとしてコンマとスペースを使用してください。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join(", ")
print result
Hammerhead, Great White, Tiger, Whale
引数を指定しない場合、joinメソッドでも文字列は返されますが、区切り文字が含まれません。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join
print result
HammerheadGreat WhiteTigerWhale
joinとmapを併用することで、データの配列を出力に変換する方法があります。まず、mapを使用してデータの各要素を変換し、次にjoinを使用してそれら全体を改行文字を区切りとした文字列に変換します。私たちが以前にシャークの配列をHTML要素の配列に変換する例を紹介しましたよね?同じ例ですが、今回はjoinを使用して要素の配列を改行文字を区切りとした文字列に変換します。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
options = sharks.map {|shark| "<option>#{shark}</option>"}
output = options.join("\n")
print output
<option>Hammerhead</option> <option>Great White</option> <option>Tiger</option> <option>Whale</option>
配列を文字列に変換する代わりに、その内容の総計を求めたり、他の種類の変換を行って単一の値を得ることもできます。それが次に取り組む内容です。
配列を単一の値に縮小する
データセットを使用して作業を行っている場合、合計などの単一の値にデータを集約する必要がある場合があります。これを行う方法の1つは、変数とeachメソッドを使用することです。
result = 0
[1, 2, 3].each {|num| result += num}
print result
6
代わりに、この操作を行うためにreduceメソッドを使用することができます。reduceメソッドは配列を反復処理し、各要素に対して2項演算を実行することで累積合計を保持します。
reduceメソッドは、結果の初期値とともに、結果への参照と現在の要素への参照を持つブロックを受け入れます。ブロック内で、最終結果を計算するためのロジックを指定します。
配列を合計したいので、結果を0で初期化し、ブロック内で現在の値を結果に追加します。
output = [1,2,3].reduce(0) {|result, current| result += current }
print output
6
もし結果を0で初期化する予定であれば、引数を省略し、単にブロックを渡すことができます。これにより、自動的に結果は配列の最初の値に設定されます。
output = [1,2,3].reduce {|result, current| result += current }
print output
6
「reduce」メソッドでは、配列内の各エントリーに対して、バイナリメソッドまたは別のオブジェクトを引数として受け入れるメソッドを指定して実行することができます。その結果を使って、「reduce」は単一の値を作成します。
Rubyで2 + 2 を書くと、実際には整数の2に対して+メソッドを呼び出しています。
2.+(2) # 4
Rubyはいくつかの文法的な糖衣構文を使用しており、2 + 2と表現することができます。
reduceメソッドでは、シンボルとしてその名前を渡すことで、バイナリメソッドを指定することができます。つまり、配列を合計するためにreduceメソッドに:+を渡すことができます。
output = [1, 2, 3].reduce(:+)
print output
6
reduceを使えば、単に数値のリストを合計するだけでなく、値を変換することもできます。reduceは配列を単一の値にまとめることを覚えておいてください。ただし、その単一の値が別の配列であってはならないというルールはありません。
仮に整数に変換する必要がある値のリストがあるとしましょうが、整数に変換可能な値のみを取得したいです。
非数値の値を排除するためにrejectを使用し、残りの値を整数に変換するためにmapを使用することができます。しかし、reduceを使って一つの手順で全てを行うこともできます。以下に説明します。
初期値として空の配列を使用します。次に、ブロック内で現在の値をIntegerメソッドを使って整数に変換します。値が整数に変換できない場合は、Integerが例外を発生させます。この例外をキャッチして、値にnilを割り当てます。
その値を配列に入れてくださいが、nilでない場合のみです。
コードの見た目はこちらです。これを試してみてください。
values = ["1", "2", "a", "3"]
integers = values.reduce([]) do |array, current|
val = Integer(current) rescue nil
array.push(val) unless val.nil?
array
end
print integers
[1,2,3]
リスト内の要素を単一の値に変換する必要がある場合、reduceを使用して解決することができる場合があります。
結論
このチュートリアルでは、配列を操作するためにいくつかの方法を使用しました。個々の要素を取得し、配列を検索して値を取得し、要素を並べ替え、データを変換して新しい配列や文字列、総計を作成しました。これらの概念を使って、Rubyを用いて多くの一般的なプログラミング問題を解決することができます。
これらの関連チュートリアルを見て、データをRubyで扱う方法をさらに探索することを忘れずに。
- How to Work with Strings in Ruby
- How To Work with Arrays in Ruby
- Understanding Data Types in Ruby