Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
61
Help us understand the problem. What is going on with this article?
@dankogai

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

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

61
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
61
Help us understand the problem. What is going on with this article?