【问题标题】:Count elements lower than a given value in a std::set计数低于 std::set 中给定值的元素
【发布时间】:2016-03-09 14:22:32
【问题描述】:

我需要找出std::set 中有多少元素低于给定元素。

我认为正确的函数是std::lower_bound,它返回一个迭代器到第一个大于或等于给定元素的元素..所以这个迭代器的索引就是我要找的.. .但我无法从迭代器中找到索引:

#include <iostream>
#include <algorithm>
#include <set>

int main()
{
    std::set<int> mySet;
    mySet.insert( 1 );
    mySet.insert( 2 );
    mySet.insert( 3 );
    mySet.insert( 4 );

    std::set<int>::const_iterator found = std::lower_bound( mySet.begin(), mySet.end(), 2 );

    if ( found != mySet.end() )
        std::cout << "Value 2 was found at position " << ( found - mySet.begin() ) << std::endl;
else
        std::cout << "Value 2 was not found" << std::endl;
}

这不会编译:

16:63: error: no match for 'operator-' (operand types are 'std::set<int>::const_iterator {aka std::_Rb_tree_const_iterator<int>}' and 'std::set<int>::iterator {aka std::_Rb_tree_const_iterator<int>}')
16:63: note: candidates are:
In file included from /usr/include/c++/4.9/vector:65:0,
                 from /usr/include/c++/4.9/bits/random.h:34,
                 from /usr/include/c++/4.9/random:49,
                 from /usr/include/c++/4.9/bits/stl_algo.h:66,
                 from /usr/include/c++/4.9/algorithm:62,
                 from 3:

使用 std::vector 代替 std::set 可以工作perfectly

看起来 operator- 对 std::set::iterator 无效。为什么? 那么,你怎么能轻松地(不调用std::previousstd::next 直到达到界限......这不会有效)找到给定迭代器在容器中的位置?如果不能,那么我可以使用什么替代方法来查找给定元素的索引...?

【问题讨论】:

    标签: c++ stl set


    【解决方案1】:

    进行下限搜索的正确方法是使用std::set's own lower_bound function,它是专门为使用这种排序的、关联的、非随机访问的容器而设计的。

    所以,不要这样:

    std::lower_bound( mySet.begin(), mySet.end(), 2 );
    

    使用这个:

    mySet.lower_bound(2);
    

    这是容器大小的对数,比a std::count_if approach好很多(它不知道比较器的排序,因此必须访问所有节点,因此是线性的)。

    但是,您还必须从开始到下限使用std::distance,这不仅是线性的,而且在实践中也必然“慢”(由于非随机访问)。

    Nathan's solution 似乎是最佳选择,因为您不想简单地找到下限,而是找到它与容器“开始”的距离。

    【讨论】:

    • 我从未注意到std::[multi]set(和地图)有自己的lower_bound :)
    • @YSC:神奇! :D
    • 这是一个有趣的答案。
    【解决方案2】:

    看起来 operator- 对 std::set::iterator 无效。为什么?

    事实上,std::set::iterator::operator-() 的实现不可能以恒定的复杂性存在,因为这些元素在内存中并不连续。


    那么,你怎样才能轻松地(在达到 bound 之前不调用 std::previous 或 std::next ......这不会有效)找到给定迭代器在容器中的位置?

    你不能,std::set::iterator 不是RandomAccessIterator。请参阅std::distance() 文档:

    复杂性

    线性。


    如果你不能,那么我可以使用什么替代方法来查找给定元素的索引...?

    我建议在不必计算迭代器距离的情况下计算您的元素:std::count_if() 可以帮助我们:

    #include <iostream>
    #include <algorithm>
    #include <set>
    
    int main()
    {
        std::set<int> mySet;
        mySet.insert( 1 );
        mySet.insert( 2 );
        mySet.insert( 3 );
        mySet.insert( 4 );
    
        const std::size_t lower_than_three = std::count_if(
             std::begin(mySet)
            , std::end(mySet)
            , [](int elem){ return elem < 3; } );
        std::cout << lower_than_three << std::endl;    
    }
    

    Demo

    【讨论】:

      【解决方案3】:

      由于std::set::iteratorBidirectionalIterator,除非我们使用减量运算符,否则我们不能从中减去。不过,我们可以做的只是遍历集合并计算迭代次数,直到达到比我们正在寻找的更大的数字。

      std::set<int> mySet;
      // fill values
      int counter = 0;
      for (auto it = mySet.begin(), *it < some_value && it != mySet.end(); ++it)
      {
          if (e < some_value)
              counter++;
      }
      

      这是最糟糕的mySet.size() 迭代,它在处理双向迭代器时尽可能快。

      还要注意std::lower_bound 没有 O(log N) 复杂度,因为我们没有使用RandomAccessIterator。当使用非 RandomAccessIterator 时,它具有线性复杂度。

      【讨论】:

        【解决方案4】:

        您可以使用以下代码:

        #include <algorithm>
        #include <set>
        #include <iostream>
        
        int main()
        {
            std::set<int> mySet;
            mySet.insert( 1 );
            mySet.insert( 2 );
            mySet.insert( 3 );
            mySet.insert( 4 );
        
            std::set<int>::const_iterator found = std::lower_bound( mySet.begin(), mySet.end(), 2 );
            std::size_t dist = std::distance(found, mySet.end());
            std::cout << "Number of lower bound elements: " << dist << std::endl;
        }
        

        【讨论】:

        • 请忘记带有 set/map 迭代器的 std::lower_bound,它是 o(n)。请改用set.lower_bound
        【解决方案5】:

        扩展所有现有答案 - 您可以随时编写自己的 operator-

        template<class T, class = typename 
            std::enable_if<
            std::is_same<
            typename T::iterator_category,
            std::bidirectional_iterator_tag
        >::value>::type>
        typename std::iterator_traits<T>::difference_type operator-(const T& a, const T& b)
        {
            return std::distance(b, a);
        }
        

        【讨论】:

        • 不错的方法!谢谢。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-25
        • 1970-01-01
        • 1970-01-01
        • 2018-01-11
        • 1970-01-01
        • 2020-03-11
        相关资源
        最近更新 更多