範囲オブジェクト再び

範囲オブジェクトが不思議だなと思って調べて見た。(1..5)だけじゃなく、("1".."5")も、ちゃんと文字列として受け付けて範囲オブジェクトが生成される。

irb(main):035:0> ("1".."5").to_a
=> ["1", "2", "3", "4", "5"]

文字列は数字じゃなくてもいい。

irb(main):037:0> ("a".."f").to_a
=> ["a", "b", "c", "d", "e", "f"]

じゃあ大文字と小文字をまたぐとどうなるのか。

irb(main):038:0> ("y".."B").to_a
=> []

あらだめ。ASCIIコード忘れたけど、そういえば大文字が先だったか。じゃあ、これはどうだ。

irb(main):039:0> ("Y".."b").to_a
=> ["Y", "Z"]

ほぉ。どうも文字コードが基準になっているようだけど、文字のクラスが違うものは範囲オブジェクトにはならないのか。Rangeクラスのドキュメント(http://ruby-doc.org/core-1.8.7/classes/Range.html)を見ると、<=>演算子による比較が可能で、ある要素の次の要素を取り出すsuccというメソッドが定義されたオブジェクトであれば、どんなものでも範囲オブジェクトが生成できるんだとか。なんか集合論で順序を導入するみたいな話でおもしろい。

じゃあ、succを調べて見よう。

irb(main):040:0> "a".succ
=> "b"

まあ、そりゃそうだ。じゃあ、zの次は?

irb(main):041:0> "z".succ
=> "aa"
irb(main):042:0> "Z".succ
=> "AA"

えぇぇぇーっ。繰り上がってるがな! しかし、zの次がaaということは次のような表現も可能か?

irb(main):043:0> ("aa".."ac").to_a
=> ["aa", "ab", "ac"]
irb(main):044:0>  ("aa".."bc").to_a
=> ["aa", "ab", "ac", "ad", "ae", "af", "ag", "ah", "ai", "aj", "ak", "al", "am", 
"an", "ao", "ap", "aq", "ar", "as", "at", "au", "av", "aw", "ax", "ay", "az", 
"ba", "bb", "bc"]
irb(main):045:0> ("z".."ac").to_a
=> []

うーん、分かるような分からないような。"z".."ac"が展開されない理由はなんだ? "z" <=> "aa" はnilじゃないし、"z".succは"aa"なのになぁ。と思ったら、以下の例だと展開された。

irb(main):071:0> ("a".."ac").to_a
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", 
"o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "aa", "ab", "ac"]

うーん、これはおかしい。これと同じ理屈を適用すれば、("1".."15")は展開できて、("9".."15")はnilを返さないとおかしい、というか一貫性がない。文字列は文字列でも数値の文字列は別の扱いなんだろうか。と思ったら、なんと、("9".."15")はnilを返してきた。

irb(main):072:0> ("1".."15").to_a
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"]
irb(main):073:0> ("9".."15").to_a
=> []

うわー、そうなのか。なんかちょっとびっくり。あれ、、、ということは、写真取り込みスクリプト

numbers = Range.new($1,$2).to_a.to_i

というのは二重の意味でダメだったらしい。$1には"1"、$2には"5"のように数字の文字列が入っている。だから範囲オブジェクトを作る前に文字列を数値に直しておかないと、「-e 9-12」というような指定のときに動かない……。正しくは、

numbers = Range.new($1.to_i,$2.to_i).to_a

なのか……。

範囲オブジェクトは、("あ".."お")のように、ひらがなとかでも使えるようだけど、

irb(main):062:0> print ("あ".."お").to_a
あぃいぅうぇえぉお=> nil
irb(main):063:0> print "ん".succ
&#12436;=> nil

とかなって、何だか下手なことをするとハマりそうな感じ。あ、最後の「ゔ」が実体参照に化けてしまった。

急に思い出した。RubyのString#succって、Perlのマジカルインクリメント由来なんじゃないだろうか。詳しくは忘れてしまったけど、そういえば初めて文字列をインクリメントできると知ったときに感動したように思う。

irb(main):074:0> "a0".succ
=> "a1"
irb(main):075:0> "a9".succ
=> "b0"

うーん、Perlのマジカルインクリメントと同じ仕様という気がしてきた。まあ、そのうちまた調べてみよう。