calling filter, find (array methods) lazily on all iterables
问题很简单:有没有什么方法可以调用数组方法,比如
过滤、查找、映射等不仅在数组上有意义,而且通常在序列上也有意义。ITerable是一个可以被保护的序列,所以过滤一个序列,找到(序列中的第一个元素),映射序列中的元素是有意义的。不管顺序是什么。
假设这样的情况:一个无限的生成器(例如斐波那契序列,生成器一次返回一个项)。我想找到满足给定条件的第一个元素。使用如下排列:
1 | [...fib()].find(conditionFunction) |
将首先使fib序列被转储,这会导致浏览器因内存消耗而崩溃(无限序列)。我可以做的是手动调用循环并在内部使用ConditionFunction。
是否有任何方法可以在(非数组)iterables上延迟调用filter、find、map等?
不幸的是,像find这样的迭代器方法是使用序列协议(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | let asArray = iterable => new Proxy(iterable, { get(target, prop, receiver) { if(prop === 'length') return Number.MAX_SAFE_INTEGER; return target.next().value; } }); function *fib() { let [a, b] = [1, 1]; while (1) { yield b; [a, b] = [b, a + b]; } } found = [].find.call( asArray(fib()), x => x > 500); console.log(found); |
需要更多的工作,但你明白了。
另一种(和IMO更清洁的方法)是重新实现迭代器方法来支持ITerables(并成为生成器本身)。幸运的是,这是非常微不足道的:
1 2 3 4 5 6 7 | function *lazyMap(iter, fn) { for (let x of iter) yield fn(x); } for (let x of lazyMap(fib(), x => x + ' hey'))... |
号
下面是如何使用可链接的方法生成懒惰的迭代器对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | let iter = function (it) { return new _iter(it); }; let _iter = function(it) { this.it = it; }; _iter.prototype[Symbol.iterator] = function *() { for (let x of this.it) { yield x; } }; _iter.prototype.map = function (fn) { let _it = this.it; return iter((function *() { for (let x of _it) { yield fn(x) } })()) }; _iter.prototype.take = function (n) { let _it = this.it; return iter((function *() { for (let x of _it) { yield x; if (!--n) break; } })()) }; // @TODO: filter, find, takeWhile, dropWhile etc // example: // endless fibonacci generator function *fib() { let [a, b] = [1, 1]; while (1) { yield b; [a, b] = [b, a + b]; } } // get first 10 fibs, multiplied by 11 a = iter(fib()) .map(x => x * 11) .take(10) console.log([...a]) |