Подтвердить что ты не робот

Сортировка сжатых (заблокированных) контейнеров в С++ с использованием boost или STL

Что я хочу сделать: Я хочу сортировать 2, или 3, или N векторы, заблокированные вместе, не копируя их в кортеж. То есть, оставляя многословие в стороне, что-то вроде:

vector<int>    v1 = {  1,   2,   3,   4,   5};
vector<double> v2 = { 11,  22,  33,  44,  55};
vector<long>   v3 = {111, 222, 333, 444, 555};

typedef tuple<int&,double&,long&> tup_t;
sort(zip(v1,v2,v3),[](tup_t t1, tup_t t2){ return t1.get<0>() > t2.get<0>(); });

for(auto& t : zip(v1,v2,v3))
  cout << t.get<0>() << " " << t.get<1>() << " " << t.get<2>() << endl;

Это должно выводить:

5 55 555
4 44 444
...
1 11 111

Как я делаю это прямо сейчас: Я реализовал свою собственную quicksort, где первый массив, который я передаю, используется для сравнения, а перестановки применяются ко всем другим массивам. Я просто не мог понять, как повторно использовать std:: sort для моей проблемы (например, перестановки извлечения).

Что я пробовал: boost:: zip_iterator и boost:: zip_range (с boost:: comb range), но и std:: sort и boost:: range:: algorithm:: sort жалуются, что итераторы/диапазоны доступны только для чтения, а не для произвольного доступа...

Вопрос: Как сортировать N векторов в шаге блокировки (zipped)? Проблема выглядит довольно общей и распространенной, поэтому я предполагаю, что должно быть простое решение через, вероятно, очень сложную библиотеку, но я просто не могу ее найти...

Замечания: да, в stackoverflow есть похожие вопросы, этот вопрос задается очень часто в разных формах. Однако они всегда закрываются одним из следующих ответов:

  • Скопируйте свои векторы в пару/кортеж и отсортируйте этот кортеж...
  • Скопируйте свои векторы в структуру с одним членом на вектор и отсортируйте вектор structs...
  • Внедрите свою собственную функцию сортировки для вашей конкретной проблемы...
  • Используйте вспомогательный массив индексов...
  • Используйте boost:: zip_iterator без примера или с примером, который создает плохие результаты.

Подсказка:

  • Я нашел этот поток в расширенный список рассылки, который указывает на это от Энтони Уильямса. Хотя это, похоже, работает только для пар, они также обсуждают TupleIteratorType, но я не смог его найти.
  • user673679 нашел этот пост, содержащий хорошее решение для двух контейнер контейнер. Это также устраняет проблему (акцент мой):

[...] Основная проблема заключается в том, что "пары" ссылок на массивы не ведут себя так, как должны [...] Я просто решил злоупотреблять нотацией итератора и писать что-то, что работает. Это связано с тем, что написание, фактически, несоответствующего итератора, где ссылка типа значения не совпадает с эталонным типом.

Ответ: см. комментарий по interjay ниже (это также частично отвечает на вопрос будущего):

#include "tupleit.hh"
#include <vector>
#include <iostream>
#include <boost/range.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/algorithm/for_each.hpp>

template <typename... T>
auto zip(T&... containers)
    -> boost::iterator_range<decltype(iterators::makeTupleIterator(std::begin(containers)...))> {
  return boost::make_iterator_range(iterators::makeTupleIterator(std::begin(containers)...),
                                      iterators::makeTupleIterator(std::end(containers)...));
}

int main() {

  typedef boost::tuple<int&,double&,long&> tup_t;

  std::vector<int>    a = {   1,   2,   3,   4 };
  std::vector<double> b = {  11,  22,  33,  44 };
  std::vector<long>   c = { 111, 222, 333, 444 };

  auto print = [](tup_t t){ std::cout << t.get<0>() << " " << t.get<1>() << " " << t.get<2>() << std::endl; };

  boost::for_each( zip(a, b, c), print);

  boost::sort( zip(a, b, c), [](tup_t i, tup_t j){ return i.get<0>() > j.get<0>(); });

  for ( auto tup : zip(a, b, c) ) print(tup);

  return 0;
}

Будущий вопрос: предыдущий ответ работает для контейнеров последовательностей. Можем ли мы получить его также для работы с сортируемыми контейнерами (например, последовательностями и списками)? Для этого потребуются random_access и двунаправленные TupleIterators, а также алгоритм сортировки, который работает с двунаправленными итераторами.

Обновление:, это работает для комбинаций последовательноподобных контейнеров. Однако для смешивания списка потребуется, чтобы std:: sort поддерживал двунаправленныеИтераторы (это не так).

4b9b3361

Ответ 1

Здесь приведен рабочий пример, основанный на библиотеке range-v3, которая была предложена для стандартизации

#include <range/v3/all.hpp>
#include <iostream>

using namespace ranges;

int main() 
{
    std::vector<int> a1{15, 7, 3,  5};
    std::vector<int> a2{ 1, 2, 6, 21};
    sort(view::zip(a1, a2), std::less<>{}, &std::pair<int, int>::first); 
    std::cout << view::all(a1) << '\n';
    std::cout << view::all(a2) << '\n';
}

Пример Live (требуется недавний компилятор с хорошей поддержкой С++ 14, а не VS 2015).

Ответ 2

Для двух контейнеров, здесь версия, которая компилируется на gcc 4.4.6, основывается на документе, упомянутом выше. В более поздних версиях gcc вы можете поменять boost:: tuple для std:: tuple

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

# include <boost/iterator/iterator_facade.hpp>
# include <boost/tuple/tuple.hpp> 

using namespace std;

template <class T, class T2>
struct helper_type {
  typedef boost::tuple<typename iterator_traits<T>::value_type, typename iterator_traits<T2>::value_type> value_type;
  typedef boost::tuple<typename iterator_traits<T>::value_type&, typename iterator_traits<T2>::value_type&> ref_type;
};

template <typename T1, typename T2>
class dual_iterator : public boost::iterator_facade<dual_iterator<T1, T2>,
                                                    typename helper_type<T1, T2>::value_type,
                                                    boost::random_access_traversal_tag,
                                                    typename helper_type<T1, T2>::ref_type> {
public:
   explicit dual_iterator(T1 iter1, T2 iter2) : mIter1(iter1), mIter2(iter2) {}
   typedef typename iterator_traits<T1>::difference_type difference_type;
private:
   void increment() { ++mIter1; ++mIter2; }
   void decrement() { --mIter1; --mIter2; }
   bool equal(dual_iterator const& other) const { return mIter1 == other.mIter1; }
   typename helper_type<T1, T2>::ref_type dereference() const { return (typename helper_type<T1, T2>::ref_type(*mIter1, *mIter2)); }
   difference_type distance_to(dual_iterator const& other) const { return other.mIter1 - mIter1; }
   void advance(difference_type n) { mIter1 += n; mIter2 += n; }

   T1 mIter1;
   T2 mIter2;
   friend class boost::iterator_core_access;
};

template <typename T1, typename T2>
dual_iterator<T1, T2> make_iter(T1 t1, T2 t2) { return dual_iterator<T1, T2>(t1, t2); }

template <class T1, class T2> struct iter_comp {
  typedef typename helper_type<T1, T2>::value_type T;
  bool operator()(const T& t1, const T& t2) { return get<0>(t1) < get<0>(t2); }
};

template <class T1, class T2> iter_comp<T1, T2> make_comp(T1 t1, T2 t2) { return iter_comp<T1, T2>(); }

template<class T> void print(T& items) {
  copy(items.begin(), items.end(), ostream_iterator<typename T::value_type>(cout, " ")); cout << endl;
}

int main() {
  vector<double> nums1 = {3, 2, 1, 0};
  vector<char> nums2 = {'D','C', 'B', 'A'};
  sort(make_iter(nums1.begin(), nums2.begin()), 
       make_iter(nums1.end(), nums2.end()), 
       make_comp(nums1.begin(), nums2.begin()));
  print(nums1);
  print(nums2);
}

Ответ 3

Создайте вспомогательный массив, содержащий индексы 0..N-1. Сортируйте этот массив с помощью специализированного компаратора, который фактически возвращает результаты сравнения элементов одного из ваших основных массивов. Затем используйте отсортированный вспомогательный массив для распечатки первичных массивов в правильном порядке.

Ответ 4

Приятно познакомиться с другим интернет-археологом!

Как сортировать N векторов в шаге блокировки (zipped)? Проблема выглядит довольно общий и общий, поэтому я предполагаю, что должно быть простое решение через, вероятно, очень сложную библиотеку, но я просто не могу ее найти.

Иногда назад я продолжал ту же охоту за сокровищами с аналогичными предположениями...
Никогда не находил сокровище: (

Я следил за теми же дорожками, что и вы:

  • Пройдите через обычные подозреваемые boost.iterator/boost.range/boost.fusion/boost.oven, и после довольно много экспериментов и исследований поймите, что они не могут решить эту конкретную проблему.
  • Пройдите много вопросов на SO, только чтобы понять, что каждый из них был закрыт либо с неправильным ответом (например, рекомендуя boost:: zip_iterator, который не работает в этом случае, когда вы указываете), либо с некоторым обходным решением которые избегают сути дела.
  • Пройдите много сообщений в блоге, список рассылки, только чтобы понять, что никто на самом деле не решил проблему, кроме....
  • После долгих исследований окончательно откопайте старый код от Антониуса Вильгельма, который утверждает, что разработал общее решение "TupleIterator" и заблокировал его в каком-то архиве "tupleit.zip". Исторические источники настолько ограничены в этом, что я до сих пор не уверен, что этот архив - это миф, легенда или если он все еще похоронен где-то в потерянном слое Интернета:)

Хорошо, более серьезно, статья Энтони Уильямса предполагает, что проблема на самом деле действительно сложная, поэтому не удивительно, что никакая существующая библиотека, подобная boost, не решила ее.

Ответ 5

Я рад сказать, что нашел решение после похожей охоты за сокровищами. range-v3 - отличная идея, если вы можете его использовать, но если вам действительно нужен итератор, проект HPX создал один, и он работает отлично с сортировкой.

Из-за недосмотра, который, как мы надеемся, будет исправлен, он по-прежнему требует, чтобы вы связывались с библиотекой HPX, но это ОК для меня, потому что все дело в том, чтобы использовать параллельные алгоритмы С++ 17, которые HPX обеспечивает реализацию.

#include <hpx/util/zip_iterator.hpp>

using zip_it = 
    hpx::util::zip_iterator<std::vector<std::size_t>::iterator,
                            std::vector<std::size_t>::iterator,
                            std::vector<double>::iterator>;

int main() {
    std::vector<std::size_t> rows{3, 2, 1};
    std::vector<std::size_t> cols{1, 2, 3};
    std::vector<double> values{4.0, 5.0, 6.0};

    auto start = hpx::util::make_zip_iterator(rows.begin(), cols.begin(), values.begin());
    auto stop  = hpx::util::make_zip_iterator(rows.end(), cols.end(), values.end());

    std::sort(start, stop);

    for ( int i = 0; i < 3; ++i ) {
        std::cerr << rows[i] << ", " << cols[i] << ", " << values[i] << "\n";
    }
}