【问题标题】:Efficiently initialise std::set with a sequence of numbers使用数字序列有效地初始化 std::set
【发布时间】:2012-06-16 13:08:48
【问题描述】:

一个明显的(天真的?)方法是:

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

这是合理的可读性,但据我了解,这不是最佳的,因为它涉及重复搜索插入位置并且没有利用输入序列已经排序的事实。

是否有一种更优雅/更有效(或事实上)的方式来初始化带有数字序列的std::set

或者,更一般地说,如何有效地将有序的条目列表插入到集合中?


更新:

查看文档,我刚刚注意到接受迭代器以指示插入位置的构造函数:

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

这意味着这样会更有效率:

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

这看起来很合理,但我仍然愿意接受更多建议。

【问题讨论】:

  • 嗯,如果您真的想找到最有效的方法,您应该对此进行基准测试。由于输入是升序的,我觉得你在错误的位置添加元素(集合保持升序,所以 begin() 会指向最低的数字)。你能对它进行基准测试吗?我真的很感兴趣:D
  • @mfontanini 我正在初始化一个空集,所以begin() 应该可以完成这项工作。我会看看能不能做一个快速的基准测试,但我很确定第二个版本会更快。
  • @mfontanini 在 ideone 上的快速基准测试:naive (0.49s) vs with hint (0.24s) 1000000 条目。
  • 你是对的。感谢您的测试。

标签: c++ stl initialization stdset


【解决方案1】:

最美的是:

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

如果您以原始效率为目标,使用提示插入版本可能会有所帮助:

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

能够将hint 与计数器一起声明会很好,并为我们提供一个干净的范围,但这需要structhackery,我觉得这有点令人困惑。

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);

【讨论】:

【解决方案2】:

您可以使用set&lt;&gt;insert() 版本,您可以在其中提供位置作为提示元素可能插入的位置。

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

复杂性:这个版本通常是对数的,但如果x 被插入到位置指向的元素之后,则摊销常数。

【讨论】:

  • 谢谢 pravs。在我发布问题后,我确实遇到了这一刻,并更新了帖子以反映这一点。不过,+1。
【解决方案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++; });
}

这(我认为)相当于你的第二个版本,但恕我直言看起来好多了。

C++03 版本为:

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

【讨论】:

  • 谢谢。它当然看起来更像 l33t,但从 C++ n00b 的角度来看,它更难遵循。不过,+1 教会了我一些新东西。
  • 我试过了,它实际上比提示版本慢,但比 naive 稍快(至少在 ideone 上)。
  • 嗯...似乎无法让它工作on ideone。我错过了什么?
  • 我更喜欢带有static int 成员的版本。
【解决方案4】:

用作提示的正确迭代器已在 C++03 和 C++11 之间更改。在 C++03 中,您想使用前一项的位置(正如您和大多数回复所显示的那样)。

在 C++11 中,您希望在要插入的项之后立即使用迭代器。当您按顺序插入时,这会使事情变得更简单:您总是使用your_container.end()

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

当然,您可以使用算法(例如,std::iota)或迭代器(例如,@pmr 已经提到的boost::counting_iterator)来生成您的值,但就插入本身而言,对于当前实现你想使用.end() 作为提示,而不是之前插入返回的迭代器。

【讨论】:

  • 有趣。我认为提示的迭代器可能是附近的迭代器。
  • 它可以是任何接近但仍然有帮助的东西,但描述已从:“迭代器 p 是指向插入应该开始搜索的位置的提示。” to:“t 被插入到尽可能靠近 p 之前的位置。”复杂性随之而来。在 C++03 中:“如果 t 在 p 之后插入,则摊销常数。”,但在 C++11 中:“如果 t 在 p 之前插入,则摊销常数。”
  • 奇怪的决定。你有什么提示我可以找到这种变化的理由吗?我有很多 API 遵循 inserted right after 提示方案,其中加速是必不可少的。我也想象它比我示例中的代码使用起来更尴尬。
  • 根据defect report,将其指定为“after”最初是个意外,即使指定了大多数实现也不是这样。
  • 我刚刚在下面测试了 pmr 的代码,提示 .cbegin、.cend 和你的 s.insert(s.end(),i);你的代码是最快的。 Native 是 83,.cend 是 74.5,.cbegin 是 66.5,而你的是 42 flat。谢谢。做得好!集大小为 51,200。
【解决方案5】:

这可以在一行代码中完成。 lambda 捕获可以将变量 i 初始化为 0,而 mutable 说明符允许在 lambda 函数中更新 i

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

【讨论】:

  • 嗨@Yunnosch,我已经添加了一个简短的解释。谢谢,克莱顿
猜你喜欢
  • 1970-01-01
  • 2019-01-16
  • 2013-09-12
  • 1970-01-01
  • 1970-01-01
  • 2012-08-07
  • 2013-05-03
  • 2020-05-14
  • 1970-01-01
相关资源
最近更新 更多