排序一对向量

时间:2014-11-20 20:36:18

标签: c++ sorting vector

我知道如何对对的矢量进行排序,但是如何对一对矢量进行排序?我可以想到写一个自定义"虚拟"迭代器在一对向量上并对其进行排序,但这看起来相当复杂。有没有更简单的方法? 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}

我指的是一对&#34;矢量&#34;作为keysvalues的对(存储为例如std::pair<std::vector<size_t>, std::vector<double> >)。向量具有相同的长度。

4 个答案:

答案 0 :(得分:5)

让我们做一个排序/置换迭代器,这样我们就可以说:

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

查看 Live On Coliru ,打印

Keys:   1   2   3   4   5   
Values: e   b   d   c   a   

基于这个想法here,我实现了这个:

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进行排序,我有点懒。当多个键值共享相同的值时,可能会导致稳定排序出现问题。但是,在这种情况下,很难定义什么是“稳定”排序,所以我认为现在不重要。

完整列表

<强> Live On Coliru

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

答案 1 :(得分:4)

仅用于比较,这是拆分迭代器方法需要多少代码:

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的不同实现所需的差异量让我觉得这个hackish解决方案实际上可能是更便携。但排序更好:

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

当然它提供了the correct result

答案 2 :(得分:2)

受到Mark Ransom评论的启发,这是一个可怕的黑客攻击,也是一个如何不做的例子。我只是为了娱乐而写它,因为我想知道它会变得多么复杂。这不是我的问题的答案,我不会用这个。我只是想分享一个奇怪的想法。请不要downvote。

实际上,忽略多线程,我相信这可以做到:

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

这显然是非常令人震惊的,以可怕的方式滥用,但可以说比这对迭代器短得多,而且性能可能相似。它actually works

请注意,swap()可以在ValueVectorSingleton内实现,而std命名空间中注入的vv只会调用它。这样可以避免公开kva。此外,可以进一步检查bkv的地址,以确保它们位于KeyType内,而不是其他一些向量。此外,这仅限于按一个矢量的值排序(不能同时按两个矢量中的相应值排序)。模板参数可能只是ValueType和{{1}},这是匆忙写的。

答案 3 :(得分:2)

这是我用过的数组和索引数组一起使用的解决方案( - 可能是来自这里的某个地方?):

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

DEMO


编辑:我刚才意识到上述情况与您要求的方向相反。