ウェブサイト検索

Rubyで配列メソッドを使用する方法


紹介

配列を使用すると、プログラム内でデータのリストを表現できます。一度配列にデータが入ると、それをソートしたり、重複を削除したり、順序を逆転させたり、配列の一部を抽出したり、特定のデータを探したりすることができます。また、配列を文字列に変換したり、一つのデータの配列を別のものに変換したり、配列を単一の値に集約したりすることもできます。

このチュートリアルでは、Rubyが配列に格納されたデータを扱うために提供する最も実用的な方法のいくつかを探ります。

このチュートリアルを進めるうちに、感嘆符(!)で終わるいくつかのメソッドを見るでしょう。これらのメソッドは、元の値を変更したり、例外を発生させたりするなどの副作用を持つことがよくあります。このチュートリアルで使用する多くのメソッドには、この接尾辞を持つ関連メソッドがあります。

また、疑問符(?)で終わるメソッドにも出会うでしょう。これらのメソッドは真偽値を返します。

これはRuby全体で使用される命名規則です。これはプログラムレベルで強制されるものではなく、メソッドから期待できることを識別するための別の方法に過ぎません。

配列メソッドの探求を始めるにあたり、要素にアクセスするいくつかの方法を見てみましょう。

要素にアクセスする

もしあなたがすでに「Rubyで配列を扱う方法」というチュートリアルに従ったことがあるなら、ゼロベースのインデックスを使って個々の要素にアクセスできることを知っているでしょう。このように:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks[0] # "Tiger"
sharks[1] # "Great White"
sharks[-1] # "Angel"

あなたはまた、配列の最初と最後の要素を取得するために first と last メソッドを使用できることを思い出すかもしれません:

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つの要素を指定するので、"グレートホワイト"と"ハンマーヘッド"を含む新しい配列が得られます。

同じことをするためにsliceメソッドを使うことができます:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.slice(1,2) # ["Great White", "Hammerhead"]

slice メソッドは新しい配列を返し、元の配列は変更されません。ただし、slice! メソッドを使用すると、元の配列も変更されます。

このtakeメソッドを使うと、配列の最初から指定された数のエントリを取得できます:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.take(2) # ["Tiger", "Great White"]

時々、特定の値ではなく、配列からランダムな値を取得したいことがあります。これについて探ってみましょう。

配列からランダムなエントリを取得する

あなたはおそらく、運に任せたゲームに取り組んでいるか、もしくはコンテストの勝者を選ぶプログラムを書いているのかもしれません。そのようなことには、何らかのランダムな値が必要です。一般的な解決策は、可能な選択肢を配列に入れ、ランダムなインデックスを選択することです。

配列からランダムな要素を取得するには、0 と配列の最後のインデックスの間でランダムなインデックスを生成し、それをインデックスとして値を取得することができますが、もっと簡単な方法があります:sample メソッドは配列からランダムなエントリを取得します。

これを使って、ストック回答の配列からランダムな回答を取得し、魔法の8ボールゲームの原始的なバージョンを作りましょう:

answers = ["Yes", "No", "Maybe", "Ask again later"]
print answers.sample
Maybe

sample メソッドは、ランダムなエントリの配列を返す引数も受け付けているので、もし1つ以上のランダムなエントリが必要な場合は、欲しい数を指定するだけです:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sample = sharks.sample(2)
print sample
["Whale", "Great White"]

次に、配列内の特定の要素を見つける方法を見てみましょう。

要素の検索とフィルタリング

配列内の特定の要素を探すとき、通常は探しているものが見つかるまでその要素を繰り返し処理します。しかし、Rubyの配列は、配列を検索するプロセスを簡素化するために特別に設計されたいくつかのメソッドを提供します。

要素が存在するかどうかだけを確認したい場合は、指定されたデータが配列の要素である場合に true を返す include? メソッドを使うことができます:

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 メソッドは、指定した条件に一致する配列内の最初の要素を見つけて返します。

例えば、sharks 配列の中で、文字 a を含む最初のエントリーを特定するためには、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 を返します。

select メソッドは類似した方法で機能しますが、単一の値を返して停止するのではなく、条件に一致するすべての要素を含む新しい配列を構築します。

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メソッドは新しい配列を返し、元の配列を変更しません。元の配列を変更したい場合は、reverse!メソッドを使用してください。

しかし、配列を反転させることは、データをソートする最も効率的または実用的な方法ではありません。sortメソッドを使用して、配列内の要素を希望通りにソートしてください。

文字列や数値のシンプルな配列に対しては、sort メソッドが効率的で、求めている結果を得ることができます:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort
print sorted_sharks
["Angel", "Great White", "Hammerhead", "Tiger"]

しかし、もし違う方法で物事を並べ替えたいのであれば、sort メソッドにその方法を教える必要があります。sort メソッドは、配列内の要素にアクセスして比較できるようにする Ruby ブロックを受け取ります。

比較をするために、比較演算子(<=>)を使用します。これはしばしば宇宙船演算子と呼ばれます。この演算子は二つの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"]

「sort」メソッドは、整数、浮動小数点数、文字列などの単純なデータ型を含む配列には最適です。しかし、配列がより複雑なオブジェクトを含む場合は、もう少し作業が必要になります。

これは、各ハッシュがサメを表しているハッシュの配列です。

sharks = [
{name: "Hammerhead"},
{name: "Great white"},
{name: "Angel"}
]

sort でこれをソートするのはそれほど簡単ではありません。配列に sort を呼び出すと失敗します:

sharks.sort
ArgumentError: comparison of Hash with Hash failed

比較を行うために、どのように比較したいかをsortに伝える必要があります。したがって、ハッシュ内の: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"]

もしそれらをまとめると、重複したエントリーが得られます:

sharks + new_sharks
["Tiger", "Great White", "Tiger", "Hammerhead"]

重複を削除するためにuniqを使用することもできますが、最初から重複を導入しない方が良いです。配列を追加するのではなく、配列を結合する|パイプ演算子を使用してください:

sharks | new_sharks
["Tiger", "Great White", "Hammerhead"]

Rubyの配列は引き算もサポートしているので、sharksからnew_sharksを引くことで新しい値だけを取得できます:

sharks = ["Tiger", "Great White"]
new_sharks = ["Tiger", "Hammerhead"]
sharks - new_sharks # ["Great White"]

次に、各要素の値を操作する方法を見てみましょう。

データの変換

mapメソッドとその別名collectは、配列の内容を変換することができ、つまり配列内の各要素に対して操作を実行することができます。

例えば、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]

mapは、ウェブアプリケーションで配列をHTMLのドロップダウンリストの要素に変換するためにしばしば使用されます。これがその例を簡略化したものです:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]

options = sharks.map {|shark| "<option>#{shark}</option>"}

print options

現在、options 配列は各サメが <option></option> HTML タグでラップされています:

["<option>Hammerhead</option>", "<option>Great White</option>", "<option>Tiger</option>", "<option>Whale</option>"]

map は新しい配列を返し、元の配列は変更されません。map! を使用すると、既存の配列が変更されます。また、map には collect というエイリアスがあることを覚えておいてください。コード内では一貫性を持たせて、どちらか一方を使用するようにしましょう。

配列を新しい配列として返すので、配列はさらに変換や操作できたり、文字列に変換したりすることができます。それについて次に見てみましょう。

配列を文字列に変換する

Rubyのすべてのオブジェクトにはto_sメソッドがあり、オブジェクトを文字列に変換します。これがprintステートメントで使用されます。私たちのsharksの配列を考えると:

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を使用して全体を印刷可能な文字列に変換します。私たちのsharks配列を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>

配列を文字列に変換するのではなく、その内容の合計を取得したり、単一の値になるような別の変換を実行したりしたいかもしれません。それが次のステップです。

配列を単一の値に減らす

データセットを扱っていると、データを合計などの単一の値にまとめる必要があることに気付くかもしれません。これを行う方法の一つは、変数とeachメソッドを使用することです:

result = 0
[1, 2, 3].each {|num| result += num}
print result
6

reduce メソッドを使用してこれを行うことができます。reduce メソッドは配列を反復処理し、各要素に対してバイナリ演算を実行することで累積合計を保持します。

この reduce メソッドは、結果の初期値と、結果への参照と現在の要素への参照という2つのローカル値を持つブロックを受け入れます。ブロック内では、最終結果を計算するためのロジックを指定します。

配列を合計したいので、結果を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で扱う方法を引き続き探るために、これらの関連記事を必ずご覧ください:

  • Rubyで文字列を扱う方法
  • Rubyで配列を扱う方法
  • Rubyにおけるデータ型の理解

関連記事: