在Typescript中处理Iterable
多次遍历的好方法是什么?
通常,Iterable
只能遍历一次,之后不会产生新元素。例如,IterableIterator
是具有此属性的Iterable
。因此,一个多次遍历序列的解决方案是首先将其复制到一个数组(比方说)。
当Iterable
已经多次遍历时,例如使用Array,这种复制是多余的。因此,作为优化,创建possiblyCopyForMultipleTraversals()
函数似乎是值得的,该函数仅在必要时返回副本。此函数的保守实现将检测标准集合,如数组和集合,并避免它们的副本。一般可以实现这样的功能吗?
答案 0 :(得分:1)
这并不一定完美,但是按照"自己创造一个":
如果对象表示MultipleIterable
,其迭代器方法生成" fresh&#34,则可以创建一个true
接口,该接口只公开值为Iterable
的属性。 ;迭代器每次被调用时:
export interface MultipleIterable<T> extends Iterable<T> {
multipleIterable: true;
}
除了可以在Iterable
上调用的功能,还可以检查它是否也是MultipleIterable
:
function isMultableIterable<T>(iterable: Iterable<T>): iterable is MultipleIterable<T> {
return (iterable) && ((iterable as any).multipleIterable === true);
}
然后,如果您愿意,可以增加Array
的原型来表示数组是MultipleIterable
:
declare global {
interface Array<T> {
multipleIterable: true;
}
}
Array.prototype.multipleIterable = true;
类似地,您可以修改任何以这种方式运行的内置迭代的原型。
然后,您可以创建一个ReplayableIterable<T>
类,其构造函数接受任何Iterable<T>
并以这样的方式包装它,使其迭代器始终是新的:
class ReplayableIterable<T> implements MultipleIterable<T> {
multipleIterable = true as true;
[Symbol.iterator](): Iterator<T> {
let cur = 0;
let iterable = this;
return {
next(): IteratorResult<T> {
while (cur >= iterable.iteratorResults.length) {
iterable.iteratorResults.push(iterable.iterator.next());
}
const ret: IteratorResult<T> = iterable.iteratorResults[cur];
cur++;
return ret;
}
}
}
private iterator: Iterator<T>;
private iteratorResults: Array<IteratorResult<T>>;
constructor(iterable: Iterable<T>) {
this.iterator = iterable[Symbol.iterator]();
this.iteratorResults = [];
}
}
这个想法是传入的iterable用于获取一个迭代器,处理迭代器的结果存储在一个数组中。由ReplayableIterable
发出的迭代器只有在耗尽数组内容时才会查询实际的迭代器。这是 lazily 复制迭代器:如果传入的iterable有十亿个元素(或更糟,无限),但你只打算遍历其中的一些,你不必采取复制你永远不会使用的元素的记忆和时间(永远不会终止的生成器的命中时间确实相当大)。
最后,导出一个函数,将任何Iterable
转换为MultipleIterable
,方法是将参数保持不变(如果你给它开始MultipleIterable
)或者构建一个ReplayableIterable
基于此。
export function toMultipleIterable<T>(iterable: Iterable<T>): MultipleIterable<T> {
return isMultableIterable(iterable) ? iterable : new ReplayableIterable(iterable);
}
希望有所帮助;祝你好运!
答案 1 :(得分:0)
也许这是要走的路:
type FIterable<T> = (() => Iterable<T>) | Iterable<T>;
export function* run<T = any>(...its: FIterable<T>[]) {
while (true) {
for (const it of its) {
if (typeof it === 'function') {
yield* it(); // this throw a type error but compile :)
} else {
yield* it;
}
}
}
}
// Example:
export const map: Map<number, number> = new Map([[1, 2], [3, 4]]);
const a = run(() => map.keys(), [5, 6, 7], new Set([8, 9]));
for (const _ of Array(10)) {
console.log(a.next());
};
JS / TS生成器可以使用yield*
迭代生成器。
单向迭代的问题应该用函数解决,为什么不再创建一个迭代?
在此示例中,只能重复地图。
我的tsconfig.json
{
"compilerOptions": {
"module": "UMD",
"target": "es5",
"lib": [
"es2015",
"es2015.iterable",
"dom"
]
},
"files": [
"app.ts"
]
}