hp12c
30 September 2013

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"]

めでたし、めでたし。

と、やったのです。

Rubyの文字列だってえにゅめらぶるしたい!

そうしたら「さっちゃん」こと@ne_sachirou殿より、こうツッコミが。

Alt title

Twitter / ne_sachirou: .。oO(to_enumじゃないのでto_enumと云ふ名前 ...

鋭い指摘です。対話はこう続きます。

Alt title

さっちゃんは本当に残酷ですが、芸人魂に火を付ける天才かも...。

そんなわけでこの記事を上げてみます(結果はあまり期待できない)。


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円で好評発売中! ===

M’ELBORNE BOOKS

ruby_object ruby_tutorial ruby_trivia



Please enable JavaScript to view the comments powered by Disqus. blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS


AltStyle によって変換されたページ (->オリジナル) /