【问题标题】:std::inserter with set - insert to begin() or end()? [duplicate]带有 set 的 std::inserter - 插入到 begin() 或 end()? [复制]
【发布时间】:2011-04-06 07:31:47
【问题描述】:

我有一些看起来像这样的代码:

std::set<int> s1, s2, out;

// ... s1 and s2 are populated ...

std::set_intersection(s1.begin(), s1.end(),
                      s2.begin(), s2.end(),
                      std::inserter(out, out.end()));

我读过如果插入集合的值紧跟在作为“提示”给出的迭代器之后,则插入可以在摊销的常数时间内完成。这在运行集合交集时显然是有益的,特别是因为写入 out 的所有内容都已按顺序排列。

如何保证这种最佳性能?创建std::inserter 时,out 是空的,所以out.begin() == out.end() 所以我看不出我指定out.begin()out.end() 作为提示有什么区别。但是,如果在将每个元素插入begin() 时解释这一点,我似乎不会获得最佳算法性能。这可以做得更好吗?

【问题讨论】:

  • @Ahsleys:至少通过选择end,您不会对算法感到悲观,因为没有后续元素(因此节省了一次比较)。但是,我确实想知道(就像您一样)您传递的迭代器是否会真正演变或停留在开头/结尾。

标签: c++ stl insert iterator set


【解决方案1】:

根据http://gcc.gnu.org/onlinedocs/gcc-4.8.0/libstdc++/api/a01553_source.html

insert_iterator&
operator=(typename _Container::value_type&& __value)
{
  iter = container->insert(iter, std::move(__value));
  ++iter;
  return *this;
}

iter 最初指向您传递给std::inserter 的迭代器。因此,iter 将始终指向您刚刚插入的值之后的值,如果您按顺序插入,则应该是最有效的。

【讨论】:

  • cppreference 还指出,按顺序插入元素的循环(就像设置交集一样)应该使用 end 作为提示,因为插入将发生在 之前提示(自 C++11 以来一直是常量,而以前它必须在提示之后
【解决方案2】:

我选择 Alexander Gessler 的答案作为“正确”答案,因为它引导我找到了这个解决方案,我认为无论如何我都会发布该解决方案。我写了一个last_inserter(),它保证插入位置始终是最后一个元素的迭代器(如果为空,则为 begin()),因为set 想要元素的迭代器preceding最佳性能的实际插入位置(所以不是 end() - 这将是实际插入位置之后的位置)。

按照原例的用法是这样的:

std::set<int> s1, s2, out;

// ... s1 and s2 are populated ...

std::set_intersection(s1.begin(), s1.end(),
                      s2.begin(), s2.end(),
                      last_inserter(out));  // note no iterator provided

这保证了插入提示始终是最后一个元素的迭代器,希望在将输出迭代器用于具有排序范围的集合时提供最佳情况性能,如上所述。

以下是我的实现。我认为它是特定于 Visual C++ 2010 的 STL 实现的平台,因为它在很大程度上基于现有的 insert_iterator,而我只能通过从 std::_Outit 派生来使其工作。如果有人知道如何使这个便携,请告诉我:

// VC10 STL wants this to be a checked output iterator.  I haven't written one, but
// this needs to be defined to silence warnings about this.
#define _SCL_SECURE_NO_WARNINGS

template<class Container>
class last_inserter_iterator : public std::_Outit {
public:
    typedef last_inserter_iterator<Container> _Myt;
    typedef Container container_type;
    typedef typename Container::const_reference const_reference;
    typedef typename Container::value_type _Valty;

    last_inserter_iterator(Container& cont)
        : container(cont)
    {
    }

    _Myt& operator=(const _Valty& _Val)
    {
        container.insert(get_insert_hint(), _Val);
        return (*this);
    }

    _Myt& operator=(_Valty&& _Val)
    {
        container.insert(get_insert_hint(), std::forward<_Valty>(_Val));
        return (*this);
    }

    _Myt& operator*()
    {
        return (*this);
    }

    _Myt& operator++()
    {
        return (*this);
    }

    _Myt& operator++(int)
    {
        return (*this);
    }

protected:
    Container& container;

    typename Container::iterator get_insert_hint() const
    {
        // Container is empty: no last element to insert ahead of; just insert at begin.
        if (container.empty())
            return container.begin();
        else
        {
            // Otherwise return iterator to last element in the container.  std::set wants the
            // element *preceding* the insert position as a hint, so this should be an iterator
            // to the last actual element, not end().
            return (--container.end());
        }
    }
};

template<typename Container>
inline last_inserter_iterator<Container> last_inserter(Container& cont)
{
    return last_inserter_iterator<Container>(cont);
}

【讨论】:

    【解决方案3】:

    您可以使用自定义函子代替std::inserter,并在每次插入新元素时重新调用out.end()

    或者,如果您的值按降序排序,out.begin() 就可以了。

    【讨论】:

    • 所以,基本上,写我自己的end_inserter,插入时总是调用end()?我会试一试...
    • 对于一个集合,迭代器永远不会失效,除了一个被删除的元素,所以你不需要每次都重新调用out.end(),但是你的其他解决方案是正确的,得到结束前的那个。请注意,对于 C++11,如果它仅属于 提示,则它已更改为常量。
    猜你喜欢
    • 2011-08-20
    • 2012-12-04
    • 2015-01-15
    • 1970-01-01
    • 1970-01-01
    • 2020-09-19
    • 1970-01-01
    • 1970-01-01
    • 2010-09-10
    相关资源
    最近更新 更多