js-xiterable というモジュールを書いたというお話です。
map
もねえ、filter
もねえ、そもそもそれほど使われてねえ?
例えばこういうジェネレーターがあったとして
function* count(n) {
for (let i = 0; i < n; i++) yield i;
};
Array
にしたり…
[...count(8)]; // [0,1,2,3,4,5,6,7]
ループを回したり…
for (const i of count(8)){
console.log(i);
}
というところまではいいとして、
count(8).map(v => v*v).filter(v => v % 2 === 1)
とかと書けないのはがっかりしませんか?他は
Swiftも…
(0..<8).map{$0 * $0}.filter{$0 % 2 == 1}
Rubyも…
(0..7).map{|v| v*v}.filter{|v| v % 2 == 1}
かなり毛色が違うとはいえ Python も…
[v * v for v in range(8) if v % 2 == 1]
できるのが当たり前なのですから。
できる!そう js-xiterable ならね
というわけで出来るようにしてみました。こんな感じで使います。
import {Xiterable} from './xiterable.js';
const xcount = n => new Xiterable(() => count(n));
const tens = xcount(10);
const odds = tens.filter(v=>v%2).map(v=>v*v);
const zips = tens.zip(odds);
[...tens]; // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[...odds]; // [ 1, 9, 25, 49, 81]
[...zips]; // [[0, 1], [1, 9], [2, 25], [3, 49], [4, 81]]
徹頭徹尾 lazy
xiterable
なオブジェクトは常に lazy です。要素は本当に必要になるまで一切生成されません。
Swift や Ruby では
(0..<8).map{$0 * $0} // この時点で [0, 1, 4, 9, 16, 25, 36, 49]
が生成される
ので
(0..<Int(1e8)).map{$0*$0}
とかやると要素数 1e8 == 1億のArray
を生成しようとしてしまいます。これを防ぐには
(0..<Int(1e8)).lazy.map{$0*$0}
と明示的にlazy
にしてあげるとよいのですが、xiterable
場合はじめからlazyなので
xcount(1e8).map(v => v*v)
は瞬殺ですし、
[...xcount(1e8).map(v => v*v).take(8)]
も即座に[0,1,4,9,16,25,36,49]
と評価されます。
さらにランダムアクセス可能な無限イテレーターという概念までサポートしていて、例えば
import { xrange } from './xiterable.js'
xrange().nth(42); // 41
なんてこともできます。xrange()
というのはPythonとかのrange()
と同様なのですが、引数がない場合には[0,1,2...]
を無限に生成するジェネレーターとして機能します。しかし.nth()
で任意の番目の要素が取得できるという。
BigInt
もサポート
もう一つの特長として、番号指定にNumber
だけではなくBigInt
も指定可能ということが挙げられます。例えば
[...xrange(0,4)]; // [0,1,2,3]
[...xrange(0n,4n)];// [0n,1n,2n,3n]
なぜそうなっているかといえば、Number.MAX_SAFE_INTEGER
== 9,007,199,254,740,991
より大きな番号も扱う必要が出てきたからです。実はjs-xiterableを書く前にjs-combinatoricsも全面的に書き直したのですが、例えば'abcdefghijklmnopqrstuvwxyz'
の順列(permutation)の総数は26!
== 403,291,461,126,605,635,584,000,000
にもなり、Number
どころかuint64_t
でも足りません。
しかしBigInt
はすでにSafari 13を除くモダンブラウザーでサポートされており、Safariも14からはサポートすることが決定しています。使い始めるにはいい時期だと判断した次第です。
import { Permutation } from 'js-combinatorics';
let it = new Xiterable(
new Permutation('abcdefghijklmnopqrstuvwxyz')
);
it.map(v => v.join('')).nth(403291461126605635583999999n);
// 'zyxwvutsrqponmlkjihgfedcba'
念のため、js-xiterableもjs-combinatoricsもBigInt
がなくても動きます。Number.MAX_SAFE_INTEGER
以上の番号は扱えませんが。
あとは
Githubをご覧くださいませ。enjoy!
Dan the Lazy JavaScripter