【问题标题】:How do we iterate through all elements of a set while inserting new elements to it?我们如何在向集合中插入新元素的同时遍历集合的所有元素?
【发布时间】:2009-10-10 04:38:13
【问题描述】:

考虑一下:

// set_iterator.cpp : Defines the entry point for the console application.

#include "stdafx.h"
#include <iostream>
#include <set>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    set<int> a1;
    set<int> a2;

    a1.insert(3);
    a1.insert(4);
    a1.insert(5);
    a2.insert(1);
    a2.insert(2);
    a2.insert(6);

    set<int>::iterator iter;
    int x = 0;
    for (iter = a1.begin(); iter != a1.end(); ++iter)
    {
        if (x == 0) {
            x = 1;
            a1.insert(a2.begin(), a2.end());
        }
        cout << *iter << endl;
    }

    system("pause");

    return 0;
}

目标是只访问集合中的每个元素一次。我认为在我们将元素插入 a1 后迭代器无效。

输出是 3 4 5 6

1,2 不打印。

我们如何编码这种情况。

【问题讨论】:

  • 我不明白你到底想做什么。为什么不先合并集合?
  • 这只是一个例子。在迭代过程中,a1 的值可能会为 a2 生成必须添加到 a1 的值。并且必须将这些新值添加到 a1 中,并且每个这样的值都被访问以生成更多要访问的值。例如,我正在编写的简单 LR 解析器就是这种情况。

标签: c++ stl


【解决方案1】:

实际上,迭代器仍然有效。集合是基于节点的容器。

问题是集合中的元素总是被排序的。在插入之前,您的集合如下所示:

3 4 5
^
iter

插入后,您的集合如下所示:

1 2 3 4 5 6
    ^
    iter

如果您希望能够做您正在做的事情,您将不得不使用不同的容器。

【讨论】:

  • 不适合一组。考虑一个向量,稍后您对其进行排序并使用std::uniquestd::erase
  • @rlbond 使用std::vector 是个坏主意,因为迭代器可能会失效(当超出其容量时需要重新分配内部数组)。使用std::list 是安全的,因为添加、移动、删除元素不会使其迭代器无效。
【解决方案2】:

当遍历时它的大小增加时,我想只访问集合中的每个元素一次。有什么办法吗? – iamrohitbanga 1 小时前

在您的代码中,a1 = [3, 4, 5]。然后你会得到一个迭代器,它指向 a1 的开头,即'3'。然后将新元素插入 a1,得到 [1, 2, 3, 4, 5, 6]。但是,您仍然指向相同的值“3”。所以现在你继续迭代,你会打印 3、4、5、6。

目前还不清楚为什么要在获得迭代器之后 插入列表。为什么不能像@camh 提到的那样在迭代它们之前插入元素

如果您仍然想这样做,请使用向量,因为这将允许您将元素附加到列表的末尾,这意味着您仍将指向“3”,但列表现在将是 [3 , 4, 5, 1, 2, 6] 并且将按该顺序打印出来。

或者,添加这个:

    for (iter = a1.begin(); iter != a1.end(); ++iter)
    {
            if (x == 0) {
                    x = 1;
                    a1.insert(a2.begin(), a2.end());
                    // Reset the iterator since we've modified the list
                    iter = a1.begin();
            }
            cout << *iter << endl;
    }

这是丑陋的黑客代码,仅在这种特定情况下有效。更好的解决方案是@camh's。如果您出于某种原因不能这样做,那么我们需要更多详细信息。

【讨论】:

  • 一个用例如下:en.wikipedia.org/wiki/LR%280%29_parser 请参阅页面上的表构建主题。这里我们有一组项目 0。它用于构造项目集 1、2、3、4。项目集 3 生成项目集 5,依此类推。没有。原始集合中的项目集正在增加,我们不应该有重复的项目集。这就是为什么我想使用一套。它似乎是标准算法,出现在很多地方,例如计算数据库中一组函数依赖项的覆盖率。
  • 另一个用例示例:参见标记为/* Add new neighbors to seed set */en.wikipedia.org/wiki/DBSCAN#Original_Query-based_Algorithm的行
【解决方案3】:

问题不在于迭代器有效性。

问题在于 set 没有任何定义的顺序(尽管在这种情况下,它选择了排序顺序,但我不相信 STL 的复杂性要求需要 - 另一个实现可能会选择另一个顺序)。

所以任何迭代器在你调用 insert 后仍然有效(所以你可以继续取消引用指针并推进它,它保证仍然到达 end())。但是插入的任何元素都可能出现在您的迭代器“之前”,除非您再次调用 begin(),否则您将看不到它们。

【讨论】:

  • set 保证独特的元素。如果我一次又一次地开始,那么我可能会陷入无限循环,因为我会一次又一次地访问一些元素。在这个例子中没有。但一般来说没有办法处理这种情况。
  • set 始终根据其比较器进行排序。
【解决方案4】:

我们如何编码这种情况。

您认识到“if (x == 0)”块中的代码只执行一次,并且该块中没有任何内容引用循环变量,因此应该将其移出循环。

    a1.insert(a2.begin(), a2.end());
    for (iter = a1.begin(); iter != a1.end(); ++iter)
    {
            cout << *iter << endl;
    }

我不知道你的真实代码是否可以用类似的方式重构,但关于你的迭代器在插入后的有效性,this 说:

Set 有一个重要的属性: 向集合中插入一个新元素 不会使迭代器无效 指向现有元素。

因此,您的迭代器仍然有效,但您不一定假设所有插入的元素都将位于当前位置“之后”,并且任何当前的前向迭代器都将到达它们。

【讨论】:

  • 我想访问集合中的每个元素,因为它的大小在遍历时会增加。有什么办法吗?
猜你喜欢
  • 1970-01-01
  • 2023-03-09
  • 2022-11-20
  • 2021-08-14
  • 1970-01-01
  • 2010-10-29
  • 2018-11-03
  • 2012-10-31
  • 1970-01-01
相关资源
最近更新 更多