【问题标题】:What is the most effective way to get the index of an iterator of an std::vector?获取 std::vector 的迭代器索引的最有效方法是什么?
【发布时间】:2020-09-13 16:36:56
【问题描述】:

我正在迭代一个向量并且需要迭代器当前指向的索引。 AFAIK 这可以通过两种方式完成:

  • it - vec.begin()
  • std::distance(vec.begin(), it)

这些方法的优缺点是什么?

【问题讨论】:

    标签: c++ iterator coding-style


    【解决方案1】:

    除了 int float string 等,你可以在使用 diff 时将额外的数据放到 .second 中。类型如:

    std::map<unsigned long long int, glm::ivec2> voxels_corners;
    std::map<unsigned long long int, glm::ivec2>::iterator it_corners;
    

    struct voxel_map {
        int x,i;
    };
    
    std::map<unsigned long long int, voxel_map> voxels_corners;
    std::map<unsigned long long int, voxel_map>::iterator it_corners;
    

    什么时候

    long long unsigned int index_first=some_key; // llu in this case...
    int i=0;
    voxels_corners.insert(std::make_pair(index_first,glm::ivec2(1,i++)));
    

    long long unsigned int index_first=some_key;
    int index_counter=0;
    voxel_map one;
    one.x=1;
    one.i=index_counter++;
    
    voxels_corners.insert(std::make_pair(index_first,one));
    

    使用正确的类型 ||结构,您可以在 .second 中放入任何内容,包括在插入时递增的索引号。

    而不是

    it_corners - _corners.begin()
    

    std::distance(it_corners.begin(), it_corners)
    

    之后

    it_corners = voxels_corners.find(index_first+bdif_x+x_z);
    

    索引很简单:

    int vertice_index = it_corners->second.y;
    

    当使用 glm::ivec2 类型时

    int vertice_index = it_corners->second.i;
    

    结构数据类型的情况

    【讨论】:

    • 当使用大量数据时,没有它的获得的速度 - vec.begin() 或 std::distance(vec.begin(), it) 使用插入 make_pair 的索引超过100 次……让你想,“女巫一个更好?”使用 .second 字段中的索引以及要使用另一种数据类型/结构存储的其他数据。
    【解决方案2】:

    我刚刚发现了这个:https://greek0.net/boost-range/boost-adaptors-indexed.html

        for (const auto & element : str | boost::adaptors::indexed(0)) {
            std::cout << element.index()
                      << " : "
                      << element.value()
                      << std::endl;
        }
    
    

    【讨论】:

      【解决方案3】:

      这是一个查找“所有”出现的 10 以及索引的示例。认为这会有所帮助。

      void _find_all_test()
      {
          vector<int> ints;
          int val;
          while(cin >> val) ints.push_back(val);
      
          vector<int>::iterator it;
          it = ints.begin();
          int count = ints.size();
          do
          {
              it = find(it,ints.end(), 10);//assuming 10 as search element
              cout << *it << " found at index " << count -(ints.end() - it) << endl;
          }while(++it != ints.end()); 
      }
      

      【讨论】:

        【解决方案4】:

        我更喜欢it - vec.begin() 正是因为 Naveen 给出的相反原因:所以如果你将向量更改为列表,它不会编译。如果你在每次迭代中都这样做,你很容易最终将 O(n) 算法变成 O(n^2) 算法。

        如果您在迭代期间不在容器中跳转,另一种选择是将索引保留为第二个循环计数器。

        注意:it 是容器迭代器的通用名称,std::container_type::iterator it;

        【讨论】:

        • 同意。我想说减号是最好的,但保留第二个循环计数器比使用 std::distance 更好,正是因为这个函数可能很慢。
        • @Steinfeld 它是一个迭代器。 std::container_type::iterator it;
        • 添加第二个循环计数器是如此明显的解决方案,以至于我很尴尬我没有想到它。
        • @UncleBeans 为什么我们不能对 List 使用 - 运算符?
        • @Swapnil 因为std::list 不提供按位置直接访问元素,所以如果你不能做list[5],你应该不能做list.begin() + 5
        【解决方案5】:

        如果您的算法已经被限制/硬编码为仅使用 std::vector::iteratorstd::vector::iterator,那么最终使用哪种方法并不重要。您的算法已经具体化,超出了选择另一个可以产生任何影响的程度。他们都做同样的事情。这只是个人喜好问题。我个人会使用显式减法。

        另一方面,如果您希望在您的算法中保留更高程度的通用性,即允许将来某天将其应用于其他迭代器类型,那么最佳方法取决于根据你的意图。这取决于您希望在此处使用的迭代器类型的限制程度。

        • 如果您使用显式减法,您的算法将被限制在一个相当狭窄的迭代器类:随机访问迭代器。 (这是你现在从std::vector 得到的)

        • 如果您使用distance,您的算法将支持更广泛的迭代器类别:输入迭代器。

        当然,为非随机访问迭代器计算distance 通常是一种低效的操作(而对于随机访问迭代器,它与减法一样有效)。由您来决定您的算法对非随机访问迭代器是否有意义,效率方面。如果由此导致的效率损失是毁灭性的,以至于使您的算法完全无用,那么您应该更好地坚持减法,从而禁止低效使用并迫使用户为其他迭代器类型寻求替代解决方案。如果非随机访问迭代器的效率仍在可用范围内,那么您应该使用distance 并记录该算法在随机访问迭代器上更有效的事实。

        【讨论】:

          【解决方案6】:

          我只对std::vector 使用- 变体——它的意思很清楚,并且操作的简单性(不超过指针减法)由语法(@987654323 @,另一方面,在第一次阅读时听起来像毕达哥拉斯,不是吗?)。正如 UncleBen 所指出的,- 还充当静态断言,以防 vector 意外更改为 list

          而且我认为它更常见 - 但是没有数字可以证明它。主要论点:it - vec.begin() 在源代码中更短 - 更少的打字工作,更少的空间消耗。很明显,您问题的正确答案归结为品味问题,这可以是一个有效的论点。

          【讨论】:

            【解决方案7】:

            正如 UncleBens 和 Naveen 所表明的,两者都有充分的理由。哪个“更好”取决于您想要什么行为:您想保证恒定时间的行为,还是希望它在必要时回退到线性时间?

            it - vec.begin() 需要固定时间,但 operator - 仅在随机访问迭代器上定义,因此代码根本无法使用列表迭代器进行编译。

            std::distance(vec.begin(), it) 适用于所有迭代器类型,但如果用于随机访问迭代器,则只会是一个恒定时间操作。

            没有一个是“更好”的。使用能满足您需求的那个。

            【讨论】:

            • 我过去曾遇到过这种情况。在两个 std::map 迭代器上使用 std::distance 并期望它为 O(N)。
            • @ScaryAardvark:你不是说期望它是 O(1) 吗?
            【解决方案8】:

            根据http://www.cplusplus.com/reference/std/iterator/distance/,由于vec.begin()是一个随机访问迭代器,因此距离方法使用-运算符。

            所以答案是,从性能的角度来看,它是相同的,但如果有人必须阅读和理解您的代码,使用distance() 可能更容易理解。

            【讨论】:

              【解决方案9】:

              我更喜欢std::distance(vec.begin(), it),因为它允许我在不更改任何代码的情况下更改容器。例如,如果您决定使用std::list 而不是不提供随机访问迭代器的std::vector,您的代码仍然可以编译。由于 std::distance 会根据迭代器特征选择最佳方法,因此您也不会出现任何性能下降。

              【讨论】:

              • 当您使用没有随机访问迭代器的容器时,最好不要计算这样的距离,因为它效率低下
              • @Eli:我同意这一点,但在非常特殊的情况下,如果确实需要,那么该代码仍然可以工作。
              • 我认为如果容器发生变化,无论如何都应该更改代码 - 有一个名为 vec 的 std::list 变量是个坏消息。如果代码被重写为通用的,将容器类型作为模板参数,那么我们就可以(并且应该)讨论处理非随机访问迭代器;-)
              • 以及特定容器的专业化。
              • @SteveJessop : 拥有一个名为 vec 的向量也是个坏消息。
              【解决方案10】:

              我喜欢这个:it - vec.begin(),因为对我来说它清楚地说明了“与开始的距离”。对于迭代器,我们习惯于从算术角度进行思考,因此- 符号是这里最清晰的指示符。

              【讨论】:

              • 使用减法来求距离比使用字面意思distance这个词更清楚?
              • @Travis,对我来说是。这是品味和习惯的问题。我们说it++ 而不是std::increment(it),不是吗?那不也算不太清楚吗?
              • ++ 运算符被定义为 STL 序列的一部分,就像我们如何递增迭代器一样。 std::distance 计算第一个和最后一个元素之间的元素数。 - 运算符起作用的事实仅仅是一个巧合。
              • @MSalters:然而,我们使用 ++ :-)
              猜你喜欢
              • 2019-08-31
              • 1970-01-01
              • 1970-01-01
              • 2021-07-27
              • 1970-01-01
              • 2010-10-12
              • 2011-04-14
              • 2021-05-29
              相关资源
              最近更新 更多