107
60

More than 3 years have passed since last update.

javascript - の iterator がちょっと非力だったので

Posted at

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-xiterablejs-combinatoricsBigIntがなくても動きます。Number.MAX_SAFE_INTEGER以上の番号は扱えませんが。

あとは

Githubをご覧くださいませ。enjoy!

Dan the Lazy JavaScripter

107
60
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
107
60