これを書いたら欲が出て来たので。
ちなみに「プロになるためのJavaScript入門」は参考書にした一冊。この場を借りて献本御礼。
無限リスト
自然数を受け取って対応する値を返す関数を一つ食わせるだけです。
var ll = List.Lazy(function(i){return i}); // also predefined as List.Integers p( ll.length ) // Inifity p( ll.get(42) ) // 42 p( ll.take(42) ) // [0..41] p( ll.map(function(x){ return x * x }) .filter(function(x){ return x % 2 == 1 }) .take(10) ); // 1,9,25,49,81,121,169,225,289,361 p( ll.map(function(x){ return x * x }) .filter(function(x){ return x % 2 == 1 }) .map(function(x){ return Math.sqrt(x) }) .take(10) ); // 1,3,5,7,9,11,13,15,17,19 try { p(ll.toArray()); }catch(e){ p(e) }
注意点
以下は一見問題なさそうですが、無限ループになります。
List.Integers .map(function(x){ return x*x }) .filter(function(x){ return x % 2 === 1 }) .filter(function(x){ return x < 100 }) .take(10) // TAKES FOREVER
何が問題かというと、最後の.take(10)
。これは要素10個がたまるかリストが尽きるまで要素を集めるのですが、要素が5つしかないためこうなります。Ruby 2.0でも同様です。
(1..Float::INFINITY).lazy.select{|x| x < 5}.take(10).force
無限FizzBuzz
var fizzbuzz = List.Lazy(function(n) { if (n % 15 === 0) return 'FizzBuzz'; if (n % 5 === 0) return 'Buzz'; if (n % 3 === 0) return 'Fizz'; return n; }); p( fizzbuzz.length ); p( JSON.stringify(fizzbuzz.take(30), null, 2) ); fizzbuzz = List.Integers .map(function(x, i){ return i % 3 !== 0 ? x : 'Fizz'}) .map(function(x, i){ return i % 5 !== 0 ? x : 'Buzz'}) .map(function(x, i){ return i % 15 !== 0 ? x : 'FizzBuzz'}) p( JSON.stringify(fizzbuzz.take(30), null, 2) );
有限リスト
関数を一個渡す代わりに、getプロパティとlengthプロパティの入ったオブジェクトを以下のように渡すことで、有限リストも実現できます。
// or use List.xrange which is more convenient var ll = List.Lazy({ get:function(i){return i}, length:1e3 }); p( ll.length ) // 1000 p( ll.get(42) ) // 42 p( ll.take(42) ) // [0..41] p( ll.map(function(x){ return x * x }) .filter(function(x){ return x % 2 == 1 }) .take(10) ); // 1,9,25,49,81,121,169,225,289,361 p( ll.map(function(x){ return x * x }) .filter(function(x){ return x % 2 == 1 }) .map(function(x){ return Math.sqrt(x) }) .take(10) ); // 1,3,5,7,9,11,13,15,17,19 /* 有限リストの場合、これも問題ない */ p( ll.map(function(x){ return x * x }) .filter(function(x){ return x % 2 == 1 }) .map(function(x){ return Math.sqrt(x) }) .filter(function(x){ return x < 10 }) .take(10) ); // 1,3,5,7,9 /* toArray() もできる */ p( ll.map(function(x){ return x * x }) .filter(function(x){ return x % 2 == 1 }) .map(function(x){ return Math.sqrt(x) }) .filter(function(x){ return x < 10 }) .toArray() ); // 1,3,5,7,9
List.xrange
もっともこういった場合は、むしろこうしたくなるでしょう。pythonのxrangeと同じです。List.rangeもあります。(やってることは xrange(…).toArray() )
var xrange = List.xrange, range = List.range; p( xrange(42).length ); p( xrange(42).get(22) ); p( xrange(42).take(10) ); p( xrange(42).toArray() ); p( range(10) ); p( range(1,11) ); p( range(0,30,5) ); p( range(0,10,3) ); p( range(0,-10,-1) ); p( JSON.stringify(range(0)) ); p( JSON.stringify(range(1,0)) );
工夫のしどころ
Ruby 2.0のEnumerable::Lazyとは違ったやり方をしています。
Ruby2.0では、mapやselectを「積んで」おき、forceのタイミングでそれを順繰りに適用しています。
irb(main):001:0> (1..Float::INFINITY).lazy => #<Enumerator::Lazy: 1..Infinity> irb(main):002:0> (1..Float::INFINITY).lazy.map{|x| x*x} => #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map> irb(main):003:0> (1..Float::INFINITY).lazy.map{|x| x*x}.select{|x| x%2==1} => #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>:select> irb(main):004:0> (1..Float::INFINITY).lazy.map{|x| x*x}.select{|x| x%2==1}.take(10) => #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>:select>:take(10)> irb(main):005:0> (1..Float::INFINITY).lazy.map{|x| x*x}.select{|x| x%2==1}.take(10).force => [1, 9, 25, 49, 81, 121, 169, 225, 289, 361] irb(main):006:0>
これは以前 Proof of Concept として書いた、
と同様の手法です。
これに対して、List.Lazyではmapやfilterを関数合成で実現しています。これだとfilterをどうするかという問題が生じるわけですが、HaskellのMaybeモナドと同様の手法で解決しています。filterがrejectしたものはNothing相当のものを返し、ary[n]
に相当するlazylist.get(n)
ではそれをundefined
に変換し、lazylist.take(n)
では集めずに捨てるというやり方です。詳しくはソースを参照のこと。
Enjoy!
Dan the Lazy Programmer
このブログにコメントするにはログインが必要です。
さんログアウト
この記事には許可ユーザしかコメントができません。