Функция 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_types). Например, я мог бы

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