Efficiently initialise std::set with a sequence of numbers

喜你入骨 提交于 2019-12-30 02:44:47

问题


An obvious (naive?) approach would be:

std::set<int> s;
for (int i = 0; i < SIZE; ++i) {
    s.insert(i);
}

That's reasonable readable, but from what I understand, not optimal since it involves repeatedly searching for the insertion position and does not take advantage of the fact that the input sequence is already sorted.

Is there a more elegant/efficient (or a de facto) way of initialising an std::set with a sequence of numbers?

Or, more generically, how does one efficiently insert an ordered list of entries into a collection?


Update:

Looking through the docs, I've just noticed the constructor that accepts an iterator to indicate the position for insertion:

iterator insert ( iterator position, const value_type& x );

Which means this would be more efficient:

std::set<int> s;
std::set<int>::iterator it = s.begin();
for (int i = 0; i < SIZE; ++i) {
    it = s.insert(it, i);
}

That looks reasonably, but I'm still open to more suggestions.


回答1:


The right iterator to use as the hint has changed between C++03 and C++11. With C++03, you want to use the position of the previous item (just as you and most of the replies have shown).

In C++11, you want to use the iterator to the item immediately after the one you're about to insert. When you're inserting in order, this makes things a bit simpler: you always use your_container.end():

std::set<int> s;
for (int i = 0; i < SIZE; ++i) 
    s.insert(s.end(), i);

You can, of course, use an algorithm (e.g., std::iota) or iterator (e.g., boost::counting_iterator, as @pmr already mentioned) to generate your values, but as far as the insertion itself goes, for a current implementation you want to use .end() as the hint, rather than the iterator returned by the previous insertion.




回答2:


The prettiest would be:

#include <set>
#include <boost/iterator/counting_iterator.hpp>

int main()
{
  const int SIZE = 100;
  std::set<int> s(boost::counting_iterator<int>(0), 
                  boost::counting_iterator<int>(SIZE));

  return 0;
}

If you aim for raw efficiency, using the hinted insert version could be helpful:

const int SIZE = 100;
std::set<int> s;
auto hint = s.begin();
for(int i = 0; i < SIZE; ++i)
  hint = s.insert(hint, i);

Being able to declaring hint along with the counter would be nice and give us a clean scope, but this requires struct hackery which I find a little obfuscating.

std::set<int> s;
for(struct {int i; std::set<int>::iterator hint;} 
      st = {0, s.begin()};
    st.i < SIZE; ++(st.i))
  st.hint = s.insert(st.hint, st.i);



回答3:


#include <algorithm>
#include <set>
#include <iterator>

int main()
{
    std::set<int> s;
    int i = 0;
    std::generate_n(std::inserter(s, s.begin()), 10, [&i](){ return i++; });
}

This is (I think) equivalent to your second version, but IMHO looks much better.

C++03 version would be:

struct inc {
    static int i;
    explicit inc(int i_) { i = i_; }
    int operator()() { return i++; }
};

int inc::i = 0;

int main()
{
    std::set<int> s;
    std::generate_n(std::inserter(s, s.end()), SIZE, inc(0));
}



回答4:


Well you can use the insert() version of set<> in which you can provide the position as hint where the element might get inserted.

iterator insert ( iterator position, const value_type& x );

Complexity: This version is logarithmic in general, but amortized constant if x is inserted right after the element pointed by position.




回答5:


This can be accomplished in a single line of code. The lambda capture can initialize the variable i to 0 and the mutable specifier allows i to be updated within the lambda function:

generate_n( inserter( s, s.begin() ), SIZE, [ i=0 ]() mutable { return i++; });


来源:https://stackoverflow.com/questions/11017200/efficiently-initialise-stdset-with-a-sequence-of-numbers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!