Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Count elements lower than a given value in a std::set

Tags:

c++

set

stl

I need to find how many elements are lower than a given one in a std::set.

I thought the right function to use was std::lower_bound which returns an iterator to the first element which is greater or equal to the given one....so the index of this iterator is what I'm looking for...but I can't find the index from the iterator:

#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;
}

This does not compile:

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:

Using std::vector instead of std::set works perfectly.

Looks like operator- is not valid for a std::set::iterator. Why? Then, how can you easily (without calling std::previous or std::next untill bound is reached...this would not be efficient) find the position of a given iterator in the container? If you can't, then what alterantive can I use to find the index of a given element...?

like image 797
jpo38 Avatar asked Mar 09 '16 14:03

jpo38


2 Answers

The correct way to do a lower bound search is with std::set's own lower_bound function, which is specially designed to work with this sorted, associative, non-random-access container.

So, instead of this:

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

use this:

mySet.lower_bound(2);

This is logarithmic in the size of the container, which is much better than a std::count_if approach (which doesn't know about the sortyness of the comparator, and therefore must visit all the nodes and is thus linear).

However, you then must also use std::distance from the beginning to the lower bound, which is not only linear but also necessarily "slow" in practice (due to the non-random-access).

Nathan's solution seems optimal given that you do not want to simply find the lower bound, but find its distance from the "start" of the container.

like image 163
Lightness Races in Orbit Avatar answered Nov 19 '22 15:11

Lightness Races in Orbit


Looks like operator- is not valid for a std::set::iterator. Why?

Indeed, an implementation of std::set::iterator::operator-() can't exist in constant complexity since the elements are not contiguous in memory.


Then, how can you easily (without calling std::previous or std::next until bound is reached...this would not be efficient) find the position of a given iterator in the container?

You can't, std::set::iterator is not a RandomAccessIterator. See std::distance() documentation:

Complexity

Linear.


If you can't, then what alterantive can I use to find the index of a given element...?

I'd suggest to count your elements without having to compute an iterator distance: std::count_if() can help us:

#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

like image 3
YSC Avatar answered Nov 19 '22 15:11

YSC