RubyのStringにもInfinityを!
少し前に、Fixnumに対抗してStringを「えにゅめらぶる」する暴挙に出たのです。つまりFixnumで、
st = 37
st = (st..1.0/0).to_enum
st # => #<Enumerator: 37..Infinity:each>
n.times.map { st.next } # => [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66]
とできるのが羨ましくてStringで、
module EnuString
def to_enum
Enumerator.new do |y|
myself = self
loop { y << myself; myself = myself.next }
end
end
end
String.send(:include, EnuString)
st = "abc37".to_enum
n = 30
n.times.map { st.next } # => ["abc37", "abc38", "abc39", "abc40", "abc41", "abc42", "abc43", "abc44", "abc45", "abc46", "abc47", "abc48", "abc49", "abc50", "abc51", "abc52", "abc53", "abc54", "abc55", "abc56", "abc57", "abc58", "abc59", "abc60", "abc61", "abc62", "abc63", "abc64", "abc65", "abc66"]
めでたし、めでたし。
と、やったのです。
そうしたら「さっちゃん」こと@ne_sachirou殿より、こうツッコミが。
鋭い指摘です。対話はこう続きます。
さっちゃんは本当に残酷ですが、芸人魂に火を付ける天才かも…。
そんなわけでこの記事を上げてみます(結果はあまり期待できない)。
String::INFINITY
遂に、String::INFINITY
というものを発見しまして…。
String::INFINITY # => Infinity
これを使って、こういうことができるようになりました。
str = Range.new('abc37', String::INFINITY).to_enum # => #<Enumerator: #<Enumerator::Generator:0x007faadc04be40>:each>
n = 30
n.times.map { str.next } # => ["abc37", "abc38", "abc39", "abc40", "abc41", "abc42", "abc43", "abc44", "abc45", "abc46", "abc47", "abc48", "abc49", "abc50", "abc51", "abc52", "abc53", "abc54", "abc55", "abc56", "abc57", "abc58", "abc59", "abc60", "abc61", "abc62", "abc63", "abc64", "abc65", "abc66"]
Fixnumと同じように、Rangeの最大値にInfinityをセットすることで無限文字列のRangeが作られます。
Range.new
がお好みでない、ということなら次のようにします。
str = r('abc37', String::INFINITY).to_enum # => #<Enumerator: #<Enumerator::Generator:0x007faadc04be40>:each>
まあこれはただのエイリアスです。
def r(first, last)
Range.new(first, last)
end
で、当然の疑問として、
「リテラルはどうした」ということです。やってみましょう。
str = ('abc37'..String::INFINITY) # =>
# ~> -:67:in `<main>': bad value for range (ArgumentError)
orz…。
リテラルはnew
呼ばないんですね…。
String::INFINITYの正体
まずはRangeのモンキーパッチを見ます。
module RangeExtension
def new(*args, &blk)
obj = allocate
obj.send(:initialize, *args, &blk)
obj
rescue ArgumentError
first, last, *_ = args
if first.is_a?(String) && last==String::INFINITY
EnuString.new(first)
else
super
end
end
end
Range.extend(RangeExtension)
Range.new
でArgumentErrorが起きた場合、その第1および第2引数をチェックして、第1が文字列、第2がString::INFINITY
の場合には、EnuStringオブジェクトなるものを生成しています。
EnuStringの実装を見てみます。
class EnuString < String
def to_enum
Enumerator.new do |y|
myself = self
loop { y << myself; myself = myself.next }
end
end
def to_s
"#{self}..Infinity"
end
end
これは前回のEnuStringモジュールと同じです。で、String::INFINITYの正体ですが…。
String::INFINITY = Float::INFINITY
詐欺!
まあ、これで('文字列'..String::INFINITY)
でArgumentErrorが起きて、めでたしめでたしと。
無限文字列Rangeリテラル
それにしても。
「リテラルが使えないのは、ネタとしてもどうか」という声が聞こえます…。
それで、新しいRangeリテラルを考えました。
通常のRangeリテラルでは範囲演算子..
を使いますが、終端を含まないリテラルを作る場合はドットが3つの範囲演算子...
を使います。
これにヒントを得て、終端がINFINITY
であるRangeリテラルではドットが1つの範囲演算子.
を使うようにしたのです。
str = ('abc37'.INFINITY).to_enum
n = 30
n.times.map { str.next } # => ["abc37", "abc38", "abc39", "abc40", "abc41", "abc42", "abc43", "abc44", "abc45", "abc46", "abc47", "abc48", "abc49", "abc50", "abc51", "abc52", "abc53", "abc54", "abc55", "abc56", "abc57", "abc58", "abc59", "abc60", "abc61", "abc62", "abc63", "abc64", "abc65", "abc66"]
ドットが1つじゃ心もとないという人向けに、._
(ドットアンダーバー)も用意しました。
str = ('abc37'._INFINITY).to_enum
n = 30
n.times.map { str.next } # => ["abc37", "abc38", "abc39", "abc40", "abc41", "abc42", "abc43", "abc44", "abc45", "abc46", "abc47", "abc48", "abc49", "abc50", "abc51", "abc52", "abc53", "abc54", "abc55", "abc56", "abc57", "abc58", "abc59", "abc60", "abc61", "abc62", "abc63", "abc64", "abc65", "abc66"]
これらが今ひとつ範囲に見えないという人向けに、さらに-
演算子も用意しています(この場合はString::INFINITYを使います)。
str = ('abc37' - String::INFINITY).to_enum
n = 30
n.times.map { str.next } # => ["abc37", "abc38", "abc39", "abc40", "abc41", "abc42", "abc43", "abc44", "abc45", "abc46", "abc47", "abc48", "abc49", "abc50", "abc51", "abc52", "abc53", "abc54", "abc55", "abc56", "abc57", "abc58", "abc59", "abc60", "abc61", "abc62", "abc63", "abc64", "abc65", "abc66"]
…。
ええ、もうやめておきます。バレバレですから…。
module StringExtension
INFINITY = Float::INFINITY
def INFINITY
EnuString.new(self)
end
alias :_INFINITY :INFINITY
def -(other)
raise ArgumentError, 'bad value for range' unless other==INFINITY
EnuString.new(self)
end
end
String.send(:include, StringExtension)
単なるStringオブジェクトのメソッド呼び出しでしたm(__)m
それにしても、文字列にInfinityという概念はあり得るんでしょうかねぇ。
まあ、通常、(a_string..'zzz'*100).to_enum
くらいにしとけば問題ないんでしょうけど。
=== Ruby関連電子書籍100円で好評発売中! ===
blog comments powered by Disqus