ここまで、範囲オブジェクトとして整数の範囲のみを扱ってきましたが、文字や文字列の範囲を扱うこともできます。
range_03.rbは、文字の範囲オブジェクトを生成し、それをfor文を用いてコンソールに出力する例です。
# a, b, c, d puts "alphabets" for alphabet in "a".."d" puts alphabet end # $, %, & puts "symbols" for symbol in "$".."&" puts symbol end # 0, 1, 2, 3 puts "numbers" for number in "0".."3" puts number end # あ, ぃ, い, ぅ, う puts "hiragana" for hiragana in "あ".."う" puts hiragana end
alphabets a b c d symbols $ % & numbers 0 1 2 3 hiragana あ ぃ い ぅ う
alphabetsのforループでは、アルファベットのaからdまでの範囲を出力しています。また、symbolsのforループでは、記号の範囲を出力しており、numbersのforループでは、0から3の数字を表す文字の範囲を出力してます。これらの並び順はASCIIコードの順番となっています。ただし、hiraganaのforループでは、UTF-8のコード順となっていることに注意してください。
また、range_04.rbは、文字列に対する範囲オブジェクトを生成し、それをfor文を用いてコンソールに出力する例です。
# aa, ab, ac, ... ba, bc, bd, ... ca, cb, cc puts "strings" for string in "aa".."cc" puts string end
$ ruby range_04.rb aa ab ac : (中略) az ba bb bc : (中略) bz ca cb cc
文字列の場合は、文字列の末尾からa、b、c……と次の文字に移り、zまできたところで、末尾から2番目の文字が次の文字に移り……というような動作を文字列の先頭まで行います。
範囲オブジェクトは、含まれる要素に対してsuccメソッドを使うことで「次のオブジェクト」を生成しています。「succ」は「successor」の略で、「後に来るもの」を表す英単語です。
ここでは、文字列を表すクラスであるStringに「succ」というメソッドが定義されているため、range_03.rbやrange_04.rbの出力に示したような動作となります。pryを用いてString#succの動作を確かめてみましょう。
[1] pry(main)> "a".succ => "b" [2] pry(main)> "$".succ => "%" [3] pry(main)> "0".succ => "1" [4] pry(main)> "あ".succ => "ぃ" [5] pry(main)> "aa".succ => "ab" [6] pry(main)> "az".succ => "ba"
[1]から[6]に示した結果全てにおいて、range_03.rbやrange_04.rbの出力と矛盾していないことが分かります。
Range#include?を使うと、引数に与えたオブジェクトが範囲内に存在するかを調べることができます。内部的に「===」演算子による比較で範囲チェックを行っています。
[1] pry(main)> (0..5).include?(5) => true [2] pry(main)> (0...5).include?(5) => false
[1]の例では「..」演算子を用いており5は範囲に含まれるため、trueが返っています。[2]の例では、「...」演算子を用いており5は範囲に含まれないため、falseが返っています。
Range#cover?を使うと、Range#include?と同様、引数に与えたオブジェクトが範囲内に存在するかを調べることができます。
Range#include?との違いは、Range#cover?は内部的に「<=>」演算子を使って値を比較しているところです。この違いは、文字列で構成される範囲オブジェクトに対して範囲チェックを行う場合に現れます。
[3] pry(main)> ("a".."z").include?("alice") => false [4] pry(main)> ("a".."z").cover?("alice") => true [5] pry(main)> ("a".."z").cover?("zzz") => false
[3]では、aからzのアルファベットを並べて、それらを1個1個、"alice"という文字列と「===」演算子を用いて比較しています。文字列(Stringクラス)に定義されている「===」演算子は、文字列の内容が等しいか等しくないかを判定するため、"a"から"z"のどの文字列とも合致しない"alice"は、この範囲オブジェクトに含まれない、という結果になります。
一方[4]と[5]では、「<=>」演算子を用いて比較を行っています。文字列における「<=>」演算子は、2つの文字列を辞書の順で比較し、左辺が先ならば-1、右辺が先ならば1、同じなら0を返します。
つまり、文字列の範囲におけるRange#cover?の場合は、辞書の特定の範囲を想像して、そこに含まれているかどうかを返すと解釈できます。("a".."z")で構成される「辞書」は、"z"という単語以降のページがないため、"zzz"はこの範囲に含まれません。従って、[5]の出力はfalseとなります。
範囲オブジェクトの始端の要素を取り出したい場合には、Range#firstが使えます。以下はその例です。
[1] pry(main)> (1..5).first => 1 [2] pry(main)> (1...5).first => 1 [3] pry(main)> (1...5).first(2) => [1, 2]
[1]では、1〜5の整数から成る範囲オブジェクトを生成し、始端の要素である1を取り出しています。[2]も「...」演算子を用いていますが、[1]と同様に1を取り出しています。
また、[3]のように引数を与えることで、始端から任意の個数の要素を取り出せます。[3]では引数に2を与えて、1と2から成る配列オブジェクトを取り出しています。
範囲オブジェクトの終端の要素を取り出したい場合には、Range#lastが使えます。以下はその例です。
[1] pry(main)> (1..5).last => 5 [2] pry(main)> (1...5).last => 5 [3] pry(main)> (1...5).last(2) => [3, 4]
[1]では、1〜5の整数を要素として持つ範囲オブジェクトの終端である要素、5を取り出しています。
ここで注意すべきは、[2]の結果です。引数を省略した場合は、「...」演算子で生成した終端を含まない範囲オブジェクトであっても、5が取り出されています。
[3]では引数を付けることでRange#firstと同様、終端から2個の要素を取り出しています。引数を明示的に指定した場合、返り値の配列には終端が含まれていないことに留意してください。
範囲オブジェクトに含まれる要素ごとに繰り返し処理を行いたい場合、Range#stepが使えます。引数に整数nを指定することで、n個おきに処理を繰り返せます。繰り返したい処理はブロックとしてRange#stepに与えます。
以下は0から10の範囲オブジェクトを生成し、Range#stepを用いて3個おきにその値を出力する例です。ブロック引数iに要素が格納されます。
[1] pry(main)> (0..10).step(3) {|i| puts i} 0 3 6 9 => 0..10
引数を省略した場合は、範囲オブジェクトに含まれる全ての要素が対象となります。
[2] pry(main)> (0..10).step {|i| puts i} 0 1 2 3 4 5 6 7 8 9 10 => 0..10
以上、特定の範囲を表現するためのRangeクラスについて解説しました。さらに詳しく知りたい場合は、リファレンスマニュアルが参考になりますので、ぜひチェックしてください。
Copyright © ITmedia, Inc. All Rights Reserved.