Std:: inserter with set - insert to begin() или end()?

У меня есть код, который выглядит так:

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(), похоже, что я бы не получил оптимальную алгоритмическую производительность. Можно ли это сделать лучше?

Ответы

Ответ 1

Вы можете использовать пользовательский функтор вместо std::inserter и повторно вызвать out.end() каждый раз, когда вставлен новый элемент.

В качестве альтернативы, если ваши значения отсортированы по убыванию, out.begin() будет в порядке.

Ответ 2

Я выбрал ответ Александра Гесслера как "правильный" ответ, потому что это привело меня к такому решению, которое, как я думал, я опубликую в любом случае. Я написал a last_inserter(), который гарантирует, что позиция вставки всегда является итератором для последнего элемента (или begin(), если пустая), потому что set хочет, чтобы итератор был перед элементом, предшествующим фактической позиции вставки для лучшей производительности (так что не конец() - это будет один после фактической позиции вставки).

Использование в соответствии с исходным примером выглядит следующим образом:

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 С++ 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

Согласно 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. Поэтому он всегда будет указывать на одно значение, которое вы только что вставили, и если вы вставляете его в порядок, должно быть оптимально эффективным.