Функция reduce (для многих объединений множеств) в C++
что я пытаюсь сделать:
У меня есть простая функция объединения наборов в C++ с использованием STL, и я пытаюсь обернуть ее в функцию, которая позволит мне выполнить объединение произвольно многих наборов, содержащихся в структурах данных STL (например,std::list
, std::vector
, std::forward_list
, ...).
как я пытался это сделать: Для начала мой простой набор union:
#include <algorithm>
template <typename set_type>
set_type sunion(const set_type & lhs, const set_type & rhs)
{
set_type result;
std::set_union( lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::inserter(result, result.end()) );
return result;
}
здесь set_type
определяет некоторые STL std::set<T>
, например,std::set<int>
.
заметив несколько раз, что мне в конечном итоге нужно выполнить несколько союзов на итераторах множеств (в Python
это будет reduce
моего sunion
функция над некоторым итерируемым объектом set_type
s). Например, я мог бы
std::vector<std::set<int> > all_sets;
или
std::list<std::set<int> > all_sets;
etc., и я хочу получить объединение всех наборов в all_sets
. Я пытаюсь реализовать простое сокращение для этого, которое по существу делает (более быструю, более элегантную, не копирующую) версию из:
sunion(... sunion( sunion( all_sets.begin(), all_sets.begin()+1 ), all_sets.begin()+2 ) , ... )
по сути, сделать это быстро, я просто хочу объявить set_type result
а затем повторите all_sets
и вставить значение в каждый набор в all_sets
в результате объекта:
template <typename set_type>
set_type sunion_over_iterator_range(const std::iterator<std::forward_iterator_tag, set_type> & begin, const std::iterator<std::forward_iterator_tag, set_type> & end)
{
set_type result;
for (std::iterator<std::forward_iterator_tag, set_type> iter = begin; iter != end; iter++)
{
insert_all(result, *iter);
}
return result;
}
здесь insert_all
определено:
// |= operator; faster than making a copy and performing union
template <typename set_type>
void insert_all(set_type & lhs, const set_type & rhs)
{
for (typename set_type::iterator iter = rhs.begin(); iter != rhs.end(); iter++)
{
lhs.insert(*iter);
}
}
как это не работает:
К сожалению, мой sunion_over_iterator_range(...)
не работает с аргументами std::vector<set_type>::begin(), std::vector<set_type>::end()
, которые типа std::vector<set_type>::iterator
. Я думал std::vector<T>::iterator
возвращает iterator<random_access_iterator_tag, T>
. А
после компиляция не удалась из-за несовместимости типов итераторов, я посмотрел на источник вектора stl (расположенный в / usr / include/c++/4.6/bits / stl_vector.h для g++ 4.6 & Ubuntu 11.10), и был удивлен, увидев typedef для vector<T>::iterator
на typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator;
. Я думал, что ForwardIterator был подтипом RandomAccessIterator, и поэтому должно быть хорошо, но, очевидно, я был неправ, иначе меня бы здесь не было.
как я благодарен и стыдно о подстрекательстве вашего разочарования из-за моей неопытности: Извините, если я показываю свое невежество-я пытаюсь научиться быть лучшим объектно-ориентированным программистом (в прошлом я просто взломал все в коде C-стиля).
я делаю все возможное, тренер! Пожалуйста, помогите мне и избавьте мир от плохого кода, который я бы создал без вашего кода ninja insight...
2 ответов
обычно при использовании итераторов мы не заботимся о фактической категории. Просто позвольте реализации разобраться с этим. Это означает, что просто измените функцию, чтобы принять любой тип:
template <typename T>
typename std::iterator_traits<T>::value_type sunion_over_iterator_range(T begin, T end)
{
typename std::iterator_traits<T>::value_type result;
for (T iter = begin; iter != end; ++ iter)
{
insert_all(result, *iter);
}
return result;
}
обратите внимание, что я использовал typename std::iterator_traits<T>::value_type
, который является типа *iter
.
кстати, шаблон итератора не связан с ООП. (Это не значит, что это плохо).
вот очень наивный подход:
std::set<T> result;
std::vector<std::set<T>> all_sets;
for (std::set<T> & s : all_sets)
{
result.insert(std::make_move_iterator(s.begin()),
std::make_move_iterator(s.end()));
}
это делает недействительными элементы в исходных наборах, хотя на самом деле не перемещает узлы элементов. Если вы хотите оставить исходные наборы нетронутыми, просто удалите make_move_iterator
.
к сожалению нет интерфейса для std::set
это позволяет вам "сращивать" два набора таким образом, чтобы не перераспределять внутренние узлы дерева, поэтому это более или менее так хорошо, как вы можете получить.
здесь вариативный шаблонный подход:
template <typename RSet> void union(RSet &) { }
template <typename RSet, typename ASet, typename ...Rest>
void union(RSet & result, ASet const & a, Rest const &... r)
{
a.insert(a.begin(), a.end());
union(result, r...);
}
использование:
std::set<T> result
union(result, s1, s2, s3, s4);
(здесь возможна подобная оптимизация перемещения; вы даже можете добавить ветвление, которое будет копировать из неизменяемых, но перемещаться из изменяемых или только из rvalues, если хотите.)
вот версия с помощью std::accumulate
:
std::set<T> result =
std::accumulate(all_sets.begin(), all_sets.end(), std::set<T>(),
[](std::set<T> & s, std::set<T> const & t)
{ s.insert(t.begin(), t.end()); return s; } );
эта версия, похоже, полагается на оптимизацию возвращаемого значения, поэтому вы можете сравнить ее с этой взломанной и скорее уродливая версия:
std::set<T> result;
std::accumulate(all_sets.begin(), all_sets.end(), 0,
[&result](int, std::set<T> const & t)
{ result.insert(t.begin(), t.end()); return 0; } );