Сортировка пары векторов
Я знаю, как сортировать вектор пар, но как вы сортируете пару векторов? Я могу подумать о написании пользовательского "виртуального" итератора над парой векторов и сортировке этого, но это кажется довольно сложным. Есть ли более простой способ? Есть ли в C++03? Я хотел бы использовать std::sort
.
эта проблема возникает при обработке некоторых данных, генерируемых в аппаратном обеспечении, где пара массивов имеет больше смысла, чем массив пар (с тех пор все виды проблем шага и выравнивания). Я понимаю, что в противном случае сохранение пары векторов вместо вектора пар будет недостатком дизайна (структура массивов). Я ищу быстрое решение, копирование данных в вектор пар, а затем обратно (я верну его в HW, чтобы сделать больше обработки) не является вариантом.
пример:
keys = {5, 2, 3, 1, 4}
values = {a, b, d, e, c}
и после сортировки (по первому вектору):
keys = {1, 2, 3, 4, 5}
values = {e, b, d, c, a}
Я имею в виду " пару векторы" как пара keys
и values
(хранится, например,std::pair<std::vector<size_t>, std::vector<double> >
). Векторы имеют одинаковую длину.
4 ответов
давайте сделаем итератор сортировки / перестановки, чтобы мы могли просто сказать:
int keys[] = { 5, 2, 3, 1, 4 };
char vals[] = { 'a', 'b', 'd', 'e', 'c' };
std::sort(make_dual_iter(begin(keys), begin(vals)),
make_dual_iter(end(keys), end(vals)));
// output
std::copy(begin(keys), end(keys), std::ostream_iterator<int> (std::cout << "\nKeys:\t", "\t"));
std::copy(begin(vals), end(vals), std::ostream_iterator<char>(std::cout << "\nValues:\t", "\t"));
видеть Жить На Coliru, печать
Keys: 1 2 3 4 5
Values: e b d c a
на основе идеи здесь, я реализовал это:
namespace detail {
template <class KI, class VI> struct helper {
using value_type = boost::tuple<typename std::iterator_traits<KI>::value_type, typename std::iterator_traits<VI>::value_type>;
using ref_type = boost::tuple<typename std::iterator_traits<KI>::reference, typename std::iterator_traits<VI>::reference>;
using difference_type = typename std::iterator_traits<KI>::difference_type;
};
}
template <typename KI, typename VI, typename H = typename detail::helper<KI, VI> >
class dual_iter : public boost::iterator_facade<dual_iter<KI, VI>, // CRTP
typename H::value_type, std::random_access_iterator_tag, typename H::ref_type, typename H::difference_type>
{
public:
dual_iter() = default;
dual_iter(KI ki, VI vi) : _ki(ki), _vi(vi) { }
KI _ki;
VI _vi;
private:
friend class boost::iterator_core_access;
void increment() { ++_ki; ++_vi; }
void decrement() { --_ki; --_vi; }
bool equal(dual_iter const& other) const { return (_ki == other._ki); }
typename detail::helper<KI, VI>::ref_type dereference() const {
return (typename detail::helper<KI, VI>::ref_type(*_ki, *_vi));
}
void advance(typename H::difference_type n) { _ki += n; _vi += n; }
typename H::difference_type distance_to(dual_iter const& other) const { return ( other._ki - _ki); }
};
теперь функция фабрики просто:
template <class KI, class VI>
dual_iter<KI, VI> make_dual_iter(KI ki, VI vi) { return {ki, vi}; }
Примечание я был немного ленив, используя boost/tuples/tuple_comparison.hpp
для сортировки. Это мог бы проблема со стабильной сортировкой когда несколько ключевых значений совпадает. Однако в этом случае трудно определить, что такое" стабильная " сортировка, поэтому я не думал, что это важно сейчас.
ПОЛНЫЙ СПИСОК
#include <boost/iterator/iterator_adaptor.hpp>
#include <boost/tuple/tuple_comparison.hpp>
namespace boost { namespace tuples {
// MSVC might not require this
template <typename T, typename U>
inline void swap(boost::tuple<T&, U&> a, boost::tuple<T&, U&> b) noexcept {
using std::swap;
swap(boost::get<0>(a), boost::get<0>(b));
swap(boost::get<1>(a), boost::get<1>(b));
}
} }
namespace detail {
template <class KI, class VI> struct helper {
using value_type = boost::tuple<typename std::iterator_traits<KI>::value_type, typename std::iterator_traits<VI>::value_type>;
using ref_type = boost::tuple<typename std::iterator_traits<KI>::reference, typename std::iterator_traits<VI>::reference>;
using difference_type = typename std::iterator_traits<KI>::difference_type;
};
}
template <typename KI, typename VI, typename H = typename detail::helper<KI, VI> >
class dual_iter : public boost::iterator_facade<dual_iter<KI, VI>, // CRTP
typename H::value_type, std::random_access_iterator_tag, typename H::ref_type, typename H::difference_type>
{
public:
dual_iter() = default;
dual_iter(KI ki, VI vi) : _ki(ki), _vi(vi) { }
KI _ki;
VI _vi;
private:
friend class boost::iterator_core_access;
void increment() { ++_ki; ++_vi; }
void decrement() { --_ki; --_vi; }
bool equal(dual_iter const& other) const { return (_ki == other._ki); }
typename detail::helper<KI, VI>::ref_type dereference() const {
return (typename detail::helper<KI, VI>::ref_type(*_ki, *_vi));
}
void advance(typename H::difference_type n) { _ki += n; _vi += n; }
typename H::difference_type distance_to(dual_iter const& other) const { return ( other._ki - _ki); }
};
template <class KI, class VI>
dual_iter<KI, VI> make_dual_iter(KI ki, VI vi) { return {ki, vi}; }
#include <iostream>
using std::begin;
using std::end;
int main()
{
int keys[] = { 5, 2, 3, 1, 4 };
char vals[] = { 'a', 'b', 'd', 'e', 'c' };
std::sort(make_dual_iter(begin(keys), begin(vals)),
make_dual_iter(end(keys), end(vals)));
std::copy(begin(keys), end(keys), std::ostream_iterator<int> (std::cout << "\nKeys:\t", "\t"));
std::copy(begin(vals), end(vals), std::ostream_iterator<char>(std::cout << "\nValues:\t", "\t"));
}
просто для сравнения, вот сколько кода требует подход split iterator:
template <class V0, class V1>
class CRefPair { // overrides copy semantics of std::pair
protected:
V0 &m_v0;
V1 &m_v1;
public:
CRefPair(V0 &v0, V1 &v1)
:m_v0(v0), m_v1(v1)
{}
void swap(CRefPair &other)
{
std::swap(m_v0, other.m_v0);
std::swap(m_v1, other.m_v1);
}
operator std::pair<V0, V1>() const // both g++ and msvc sort requires this (to get a pivot)
{
return std::pair<V0, V1>(m_v0, m_v1);
}
CRefPair &operator =(std::pair<V0, V1> v) // both g++ and msvc sort requires this (for insertion sort)
{
m_v0 = v.first;
m_v1 = v.second;
return *this;
}
CRefPair &operator =(const CRefPair &other) // required by g++ (for _GLIBCXX_MOVE)
{
m_v0 = other.m_v0;
m_v1 = other.m_v1;
return *this;
}
};
template <class V0, class V1>
inline bool operator <(std::pair<V0, V1> a, CRefPair<V0, V1> b) // required by both g++ and msvc
{
return a < std::pair<V0, V1>(b); // default pairwise lexicographical comparison
}
template <class V0, class V1>
inline bool operator <(CRefPair<V0, V1> a, std::pair<V0, V1> b) // required by both g++ and msvc
{
return std::pair<V0, V1>(a) < b; // default pairwise lexicographical comparison
}
template <class V0, class V1>
inline bool operator <(CRefPair<V0, V1> a, CRefPair<V0, V1> b) // required by both g++ and msvc
{
return std::pair<V0, V1>(a) < std::pair<V0, V1>(b); // default pairwise lexicographical comparison
}
namespace std {
template <class V0, class V1>
inline void swap(CRefPair<V0, V1> &a, CRefPair<V0, V1> &b)
{
a.swap(b);
}
} // ~std
template <class It0, class It1>
class CPairIterator : public std::random_access_iterator_tag {
public:
typedef typename std::iterator_traits<It0>::value_type value_type0;
typedef typename std::iterator_traits<It1>::value_type value_type1;
typedef std::pair<value_type0, value_type1> value_type;
typedef typename std::iterator_traits<It0>::difference_type difference_type;
typedef /*typename std::iterator_traits<It0>::distance_type*/difference_type distance_type; // no distance_type in g++, only in msvc
typedef typename std::iterator_traits<It0>::iterator_category iterator_category;
typedef CRefPair<value_type0, value_type1> reference;
typedef reference *pointer; // not so sure about this, probably can't be implemented in a meaningful way, won't be able to overload ->
// keep the iterator traits happy
protected:
It0 m_it0;
It1 m_it1;
public:
CPairIterator(const CPairIterator &r_other)
:m_it0(r_other.m_it0), m_it1(r_other.m_it1)
{}
CPairIterator(It0 it0 = It0(), It1 it1 = It1())
:m_it0(it0), m_it1(it1)
{}
reference operator *()
{
return reference(*m_it0, *m_it1);
}
value_type operator *() const
{
return value_type(*m_it0, *m_it1);
}
difference_type operator -(const CPairIterator &other) const
{
assert(m_it0 - other.m_it0 == m_it1 - other.m_it1);
// the iterators always need to have the same position
// (incomplete check but the best we can do without having also begin / end in either vector)
return m_it0 - other.m_it0;
}
bool operator ==(const CPairIterator &other) const
{
assert(m_it0 - other.m_it0 == m_it1 - other.m_it1);
return m_it0 == other.m_it0;
}
bool operator !=(const CPairIterator &other) const
{
return !(*this == other);
}
bool operator <(const CPairIterator &other) const
{
assert(m_it0 - other.m_it0 == m_it1 - other.m_it1);
return m_it0 < other.m_it0;
}
bool operator >=(const CPairIterator &other) const
{
return !(*this < other);
}
bool operator <=(const CPairIterator &other) const
{
return !(other < *this);
}
bool operator >(const CPairIterator &other) const
{
return other < *this;
}
CPairIterator operator +(distance_type d) const
{
return CPairIterator(m_it0 + d, m_it1 + d);
}
CPairIterator operator -(distance_type d) const
{
return *this + -d;
}
CPairIterator &operator +=(distance_type d)
{
return *this = *this + d;
}
CPairIterator &operator -=(distance_type d)
{
return *this = *this + -d;
}
CPairIterator &operator ++()
{
return *this += 1;
}
CPairIterator &operator --()
{
return *this += -1;
}
CPairIterator operator ++(int) // msvc sort actually needs this, g++ does not
{
CPairIterator old = *this;
++ (*this);
return old;
}
CPairIterator operator --(int)
{
CPairIterator old = *this;
-- (*this);
return old;
}
};
template <class It0, class It1>
inline CPairIterator<It0, It1> make_pair_iterator(It0 it0, It1 it1)
{
return CPairIterator<It0, It1>(it0, it1);
}
Это вроде грубо по краям, может быть, я просто плохо перегружаю сравнения, но количество различий, необходимых для поддержки различных реализаций std::sort
заставляет меня думать, что хакерское решение может быть более портативным. Но сортировка гораздо приятнее:
struct CompareByFirst {
bool operator ()(std::pair<size_t, char> a, std::pair<size_t, char> b) const
{
return a.first < b.first;
}
};
std::vector<char> vv; // filled by values
std::vector<size_t> kv; // filled by keys
std::sort(make_pair_iterator(kv.begin(), vv.begin()),
make_pair_iterator(kv.end(), vv.end()), CompareByFirst());
// nice
и конечно, это дает правильный результат.
вдохновленный комментарием Марка Рэнсома, это ужасный Хак и пример того, как этого не делать. Я написал его только для развлечения и потому, что мне было интересно, насколько сложно это будет. Это не ответ на мой вопрос, я не буду использовать этот. Я просто хотел поделиться странной идеей. Пожалуйста, не понижайте голос.
на самом деле, игнорируя многопоточность, я считаю, что это можно сделать:
template <class KeyType, class ValueVectorType>
struct MyKeyWrapper { // all is public to save getters
KeyType k;
bool operator <(const MyKeyWrapper &other) const { return k < other.k; }
};
template <class KeyType, class ValueVectorType>
struct ValueVectorSingleton { // all is public to save getters, but kv and vv should be only accessible by getters
static std::vector<MyKeyWrapper<KeyType, ValueVectorType> > *kv;
static ValueVectorType *vv;
static void StartSort(std::vector<MyKeyWrapper<KeyType, ValueVectorType> > &_kv, ValueVectorType &_vv)
{
assert(!kv && !vv); // can't sort two at once (if multithreading)
assert(_kv.size() == _vv.size());
kv = &_kv, vv = &_vv; // not an attempt of an atomic operation
}
static void EndSort()
{
kv = 0, vv = 0; // not an attempt of an atomic operation
}
};
template <class KeyType, class ValueVectorType>
std::vector<MyKeyWrapper<KeyType, ValueVectorType> >
*ValueVectorSingleton<KeyType, ValueVectorType>::kv = 0;
template <class KeyType, class ValueVectorType>
ValueVectorType *ValueVectorSingleton<KeyType, ValueVectorType>::vv = 0;
namespace std {
template <class KeyType, class ValueVectorType>
void swap(MyKeyWrapper<KeyType, ValueVectorType> &a,
MyKeyWrapper<KeyType, ValueVectorType> &b)
{
assert((ValueVectorSingleton<KeyType, ValueVectorType>::vv &&
ValueVectorSingleton<KeyType, ValueVectorType>::kv)); // if this triggers, someone forgot to call StartSort()
ValueVectorType &vv = *ValueVectorSingleton<KeyType, ValueVectorType>::vv;
std::vector<MyKeyWrapper<KeyType, ValueVectorType> > &kv =
*ValueVectorSingleton<KeyType, ValueVectorType>::kv;
size_t ai = &kv.front() - &a, bi = &kv.front() - &b; // get indices in key vector
std::swap(a, b); // swap keys
std::swap(vv[ai], vv[bi]); // and any associated values
}
} // ~std
и сортировкой как:
std::vector<char> vv; // filled by values
std::vector<MyKeyWrapper<size_t, std::vector<char> > > kv; // filled by keys, casted to MyKeyWrapper
ValueVectorSingleton<size_t, std::vector<char> >::StartSort(kv, vv);
std::sort(kv.begin(), kv.end());
ValueVectorSingleton<size_t, std::vector<char> >::EndSort();
// trick std::sort into using the custom std::swap which also swaps the other vectors
это очевидно очень отвратительно, тривиально злоупотреблять ужасными способами, но, возможно, намного короче пары итераторов и, вероятно, похож по производительности. И это работает.
отметим, что swap()
может быть реализован внутри ValueVectorSingleton
и вводят в std
пространство имен не называешь его. Это позволит избежать необходимости делать vv
и kv
общественности. Кроме того, адреса a
и b
может быть проверено, чтобы убедиться, что они находятся внутри kv
и не какой-то другой вектор. Кроме того, это ограничено сортировкой по значениям только одного вектора (невозможно Сортировать по соответствующим значениям в обоих векторах одновременно). И параметры шаблона могут быть просто KeyType
и ValueType
, это было написано в спешке.
вот решение, которое я когда-то использовал для сортировки массива вместе с массивом индексов (--может быть, это откуда-то здесь?):
template <class iterator>
class IndexComparison
{
public:
IndexComparison (iterator const& _begin, iterator const& _end) :
begin (_begin),
end (_end)
{}
bool operator()(size_t a, size_t b) const
{
return *std::next(begin,a) < *std::next(begin,b);
}
private:
const iterator begin;
const iterator end;
};
использование:
std::vector<int> values{5,2,5,1,9};
std::vector<size_t> indices(values.size());
std::iota(indices.begin(),indices.end(),0);
std::sort(indices.begin(),indices.end()
, IndexComparison<decltype(values.cbegin())>(values.cbegin(),values.cend()));
после этого целые числа в векторе indices
перестановки такие, что они соответствуют возрастающим значениям в векторе values
. Легко расширить это от меньше-сравнения к общим функциям сравнения.
далее, чтобы отсортировать также значения, вы можете сделать другой
std::sort(values.begin(),values.end());
используя ту же функцию сравнения. Это решение для ленивых. Конечно, вы можете альтернативно также использовать отсортированные индексы по
auto temp=values;
for(size_t i=0;i<indices.size();++i)
{
values[i]=temp[indices[i]];
}
EDIT: я только что понял, что выше сортируется в противоположном направлении, чем тот, который вы просили.