JavaScript从特定元素/索引开始迭代Map()

3
我知道在Map中的元素可以按照插入顺序进行迭代。
假设我们有以下这个map:
const a = new Map();
a.set('x', 5);
a.set('y', 10);
a.set('z', 5);

我们想找到值为5的第一个元素,然后找到下一个具有相同值5的元素。

// el will be 5, 10, 5...
for(const el of a) {
  if(el === 0) {
    // How can I iteratate over `a` starting from index(el) + 1
    for (??) {}
  }    
}

如果我使用Array,我们可以像下面这样做(忽略键):

const a = new Array(5, 10, 5);
for(let i = 0; i < a.length; ++i) {
  if(a[i] === 5) {
    // Here I can start iterating from i + 1
    for(let j = i + 1; j < a.length; ++j) {
         a[j] === 5 && console.log('FOUND!');
    }
  }
}

我对迭代器不是很熟悉,但我认为从map中特定元素开始迭代应该是有可能的。

const x = a.get('x');
 // iterate over Map `a` starting from the element that comes after x

有一个解决方案,但我并不是特别满意,那就是每次执行const elements = a.entries()操作时获取密钥或条目的副本,这样我们就可以快速迭代它,但它会使用大量额外的内存。


1
这是为了将一些元素分组而设计的。我遍历一个列表,并根据当前元素找到一个或多个出现在当前元素之后且符合某些条件的元素。我可以使用一个Array,但是元素可能会被添加/删除,因此我仍然需要一种快速知道每个元素位置的方法(因此Map非常适合,因为我可以根据其id/key直接访问元素)。 - XCS
地图并不是为迭代而设计的。它实现了 Symbol.iterator,但它不允许分叉迭代。 - Nina Scholz
另一个使用案例是,在 Map 中查找元素 x 并删除后加入的所有元素(无需迭代地查找 x)。 - XCS
我会使用一个数组,可能结合索引映射。 - Nina Scholz
@ArashMotamedi 我在我的问题的最后一句提到了这一点。 - XCS
显示剩余3条评论
2个回答

2
你可以使用生成器来实现这个功能,...
一个优点是你可以使用break来提前终止生成器。
下面是一个例子:

const a = new Map();
a.set('x', 5);
a.set('y', 10);
a.set('z', 5);

a.set('a', 10);
a.set('b', 5);

function* findFirstThenNext(m, v) {
  let ix = 0; 
  for (const mm of m) {
    if (v === mm[1]) {
      yield {ix, key:mm[0]};
    }
    ix += 1;
  }
}

let count = 0;
for (const ret of findFirstThenNext(a, 5)) {
  console.log(`Found @${ret.ix} with key ${ret.key}`);
  count ++;
  if (count >= 2) break;
}

使用for循环和迭代器的混合,您可以创建简单的列表,然后使用迭代器执行双重for循环。这里的好处是,如果您在许多地方使用此类for循环,则可以为任何可迭代的内容重复使用makeOuterInnerIter函数。"最初的回答"

const a = new Map();
a.set('x', 5);
a.set('y', 10);
a.set('z', 5);

function* makeOuterInnerIter(iter) {
  const stack = Array.from(iter);
  for (let ol = 0; ol < stack.length; ol += 1) {
    yield {
      value: stack[ol],
      inner: (function *inner() {
        for (let il = ol + 1; il < stack.length; il += 1) yield stack[il];
      })()
    };
  }
}


for (const {value: [okey, ovalue], inner} of makeOuterInnerIter(a)) {
  console.log(`outer: ${okey}: ${ovalue}`);
  for (const [ikey, ivalue] of inner) {
    console.log(`  inner: ${ikey}: ${ivalue}`);
  }
}


顺便说一下,你不一定需要引用 f,你可以只使用 for (const ret of findFirstThenNext(a, 5)),它会以同样的方式工作。 - Patrick Roberts
@ Cristy 我做了第二个片段,认为它可以满足你的需求。 - Keith
但是这样每次执行操作都会分配一个新的Array,所以它与直接获取a.entries()完全相同。如果我们获取了这些条目,那么我们可以简单地遍历它们而不使用任何迭代器。我的问题是如何从Map中的特定元素继续迭代,而不分配一个新的Array - XCS
它只分配一次数组。我假设您想使用迭代器避免for (let i = x; i <y; i += 1)双重循环,以使事情更加清晰。生成器从a到b,不幸的是没有重新启动选项。 - Keith
嗨,克里斯蒂!我有个想法…如果你喜欢用C语言,你考虑过使用WebAssembly吗?如果你觉得JavaScript的地图功能不够好用,可以编译C/C++的地图库并使用它们来代替。 - Keith
显示剩余7条评论

0

使用生成器的另一种方法:

function* itWrapper(iterator, value) {
    let found = false;
    for(let item of iterator) {
        if(item[1] == value) {
            if(found) {
                yield 'FOUND!'; // or: yield item[1];
            } else {
                found = true;
            }
        }
    }
}

然后像这样使用:

for(let item of itWrapper(a[Symbol.iterator](), 5)) {
    console.log(item);
}

由于在该示例中a已经是可迭代的,因此只需要使用for(let item of itWrapper(a, 5))即可。 - Patrick Roberts
这是一个很棒的解决方案,但它在我的情况下不起作用。我可能需要搜索“下一个有相同值的5个项目”,而不一定是第二次出现。只需找到下一个相同值的示例就可以了。 - XCS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接