1つのベクトルのみを使用する基準を使用して、2つのベクトルを同じ方法で並べ替えるにはどうすればよいですか?


85

1つのベクトルのみを使用する基準を使用して、2つのベクトルを同じ方法で並べ替えるにはどうすればよいですか?

たとえば、同じサイズの2つのベクトルがあるとします。

vector<MyObject> vectorA;
vector<int> vectorB;

次に、vectorAいくつかの比較関数を使用して並べ替えます。その並べ替えは並べ替えられましたvectorA。同じ並べ替えを適用するにはどうすればよいvectorBですか?


1つのオプションは、構造体を作成することです。

struct ExampleStruct {
    MyObject mo;
    int i;
};

その後、ソートの内容が含まれているベクトルvectorAvectorB、単一のベクターにジップアップ:

// vectorC[i] is vectorA[i] and vectorB[i] combined
vector<ExampleStruct> vectorC;

これは理想的な解決策とは思えません。特にC ++ 11で他のオプションはありますか?


いくつかの入力とそれに対応する望ましい出力の例を提供できますか?質問を理解するのに問題があります
Andy Prowl 2013年

彼は(効果的に)内容をvectorA、そしてvectorB両方の内容でソートしたいと思っていると思いますvectorB
Mooing Duck 2013

この質問は、正確な重複ではないにしても、ほぼ重複していると思います
Mooing Duck 2013

vectorAの値に基づいて(インデックス0、... vectorA.size()の)3番目のベクトルをソートし、それらのインデックスをvectorBに「適用」するのはどうですか?例えばのようstackoverflow.com/a/10581051/417197
アンドレ・

個人的には、私はむしろ持っていると思いvector<pair<MyObject, int>>ます。そうすれば、2つのリストが同期しなくなることを心配する必要はありません。1つのソートで、両方のデータセットを同時に並べ替えます。そして、書く必要のある余分な構造体はありません。
cHao 2013年

回答:


118

ソート順列を見つける

std::vector<T>との比較が与えられたT場合、この比較を使用してベクトルを並べ替える場合に使用する順列を見つけられるようにする必要があります。

template <typename T, typename Compare>
std::vector<std::size_t> sort_permutation(
    const std::vector<T>& vec,
    Compare& compare)
{
    std::vector<std::size_t> p(vec.size());
    std::iota(p.begin(), p.end(), 0);
    std::sort(p.begin(), p.end(),
        [&](std::size_t i, std::size_t j){ return compare(vec[i], vec[j]); });
    return p;
}

ソート順列の適用

std::vector<T>順列が与えられた場合、順列std::vector<T>に従って並べ替えられる新しいものを構築できるようにしたいと考えています。

template <typename T>
std::vector<T> apply_permutation(
    const std::vector<T>& vec,
    const std::vector<std::size_t>& p)
{
    std::vector<T> sorted_vec(vec.size());
    std::transform(p.begin(), p.end(), sorted_vec.begin(),
        [&](std::size_t i){ return vec[i]; });
    return sorted_vec;
}

もちろんapply_permutation、新しいソートされたコピーを返すのではなく、指定したベクターを変更するように変更することもできます。このアプローチは依然として線形時間計算量であり、ベクトル内のアイテムごとに1ビットを使用します。理論的には、それはまだ線形空間の複雑さです。ただし、実際には、sizeof(T)が大きい場合、メモリ使用量の削減は劇的になります。(詳細を参照

template <typename T>
void apply_permutation_in_place(
    std::vector<T>& vec,
    const std::vector<std::size_t>& p)
{
    std::vector<bool> done(vec.size());
    for (std::size_t i = 0; i < vec.size(); ++i)
    {
        if (done[i])
        {
            continue;
        }
        done[i] = true;
        std::size_t prev_j = i;
        std::size_t j = p[i];
        while (i != j)
        {
            std::swap(vec[prev_j], vec[j]);
            done[j] = true;
            prev_j = j;
            j = p[j];
        }
    }
}

vector<MyObject> vectorA;
vector<int> vectorB;

auto p = sort_permutation(vectorA,
    [](T const& a, T const& b){ /*some comparison*/ });

vectorA = apply_permutation(vectorA, p);
vectorB = apply_permutation(vectorB, p);

リソース


4
私のコンパイラでは、「Compare&compare」を「Comparecompare」に変更する必要がありました。
smallChess 2016年

2
std :: iota関数(vs2012)を実行するには、ヘッダー<numeric>も含める必要がありました。
スミス

1
もちろんapply_permutation、新しいソートされたコピーを返すのではなく、指定したベクトルを変更するように変更することもできます。」少なくとも概念的には、これを具体化することで、答えに役立つことがわかります。これをapply_permutationそれ自体と同じ方法で実装しますか?
ildjarn 2016

2
@ildjarn私はあなたの提案で答えを更新しました。
ティモシーシールド

2
素晴らしいスニペットをありがとう!Compare& compareただし、sort_permutation宣言はconstである必要があります。それ以外の場合は、std :: less <T>などを使用できません。
コタコタコタ

9

範囲-V3、それは簡単で、ソートジップ図です。

std::vector<MyObject> vectorA = /*..*/;
std::vector<int> vectorB = /*..*/;

ranges::v3::sort(ranges::view::zip(vectorA, vectorB));

または明示的に投影を使用します。

ranges::v3::sort(ranges::view::zip(vectorA, vectorB),
                 std::less<>{},
                 [](const auto& t) -> decltype(auto) { return std::get<0>(t); });

デモ


VS2017で試してみましたが、何を試してもコンパイルも機能もしませんでした。このライブラリは、VS2019、GCC、およびLLVMで動作することが知られています。
コンタンゴ

4

思いついた拡張機能で貢献したいと思います。目標は、単純な構文を使用して複数のベクトルを同時にソートできるようにすることです。

sortVectorsAscending(criteriaVec, vec1, vec2, ...)

アルゴリズムはティモシーが提案したものと同じですが、可変個引数テンプレートを使用しているため、任意のタイプの複数のベクトルを同時に並べ替えることができます。

コードスニペットは次のとおりです。

template <typename T, typename Compare>
void getSortPermutation(
    std::vector<unsigned>& out,
    const std::vector<T>& v,
    Compare compare = std::less<T>())
{
    out.resize(v.size());
    std::iota(out.begin(), out.end(), 0);

    std::sort(out.begin(), out.end(),
        [&](unsigned i, unsigned j){ return compare(v[i], v[j]); });
}

template <typename T>
void applyPermutation(
    const std::vector<unsigned>& order,
    std::vector<T>& t)
{
    assert(order.size() == t.size());
    std::vector<T> st(t.size());
    for(unsigned i=0; i<t.size(); i++)
    {
        st[i] = t[order[i]];
    }
    t = st;
}

template <typename T, typename... S>
void applyPermutation(
    const std::vector<unsigned>& order,
    std::vector<T>& t,
    std::vector<S>&... s)
{
    applyPermutation(order, t);
    applyPermutation(order, s...);
}

template<typename T, typename Compare, typename... SS>
void sortVectors(
    const std::vector<T>& t,
    Compare comp,
    std::vector<SS>&... ss)
{
    std::vector<unsigned> order;
    getSortPermutation(order, t, comp);
    applyPermutation(order, ss...);
}

// make less verbose for the usual ascending order
template<typename T, typename... SS>
void sortVectorsAscending(
    const std::vector<T>& t,
    std::vector<SS>&... ss)
{
    sortVectors(t, std::less<T>(), ss...);
}

Ideoneでテストしてください。

これについては、このブログ投稿でもう少し詳しく説明します。


3

順列を使用したインプレースソート

私はティモシーのような順列を使用しますが、データが大きすぎて、ソートされたベクトルにより多くのメモリを割り当てたくない場合は、インプレースで行う必要があります。順列を使用したO(n)(線形複雑度)のインプレースソートの例を次に示し ます

秘訣は、順列と逆順列を取得して、最後の並べ替え手順で上書きされたデータを配置する場所を知ることです。

template <class K, class T> 
void sortByKey(K * keys, T * data, size_t size){
    std::vector<size_t> p(size,0);
    std::vector<size_t> rp(size);
    std::vector<bool> sorted(size, false);
    size_t i = 0;

    // Sort
    std::iota(p.begin(), p.end(), 0);
    std::sort(p.begin(), p.end(),
                    [&](size_t i, size_t j){ return keys[i] < keys[j]; });

    // ----------- Apply permutation in-place ---------- //

    // Get reverse permutation item>position
    for (i = 0; i < size; ++i){
        rp[p[i]] = i;
    }

    i = 0;
    K savedKey;
    T savedData;
    while ( i < size){
        size_t pos = i;
        // Save This element;
        if ( ! sorted[pos] ){
            savedKey = keys[p[pos]];
            savedData = data[p[pos]];
        }
        while ( ! sorted[pos] ){
            // Hold item to be replaced
            K heldKey  = keys[pos];
            T heldData = data[pos];
            // Save where it should go
            size_t heldPos = rp[pos];

            // Replace 
            keys[pos] = savedKey;
            data[pos] = savedData;

            // Get last item to be the pivot
            savedKey = heldKey;
            savedData = heldData;

            // Mark this item as sorted
            sorted[pos] = true;

            // Go to the held item proper location
            pos = heldPos;
        }
        ++i;
    }
}

1
O(N ^ 2)のように見えますが、そうではありません。内側のwhileは、アイテムがソートされていない場合にのみ実行されます。内部がデータをソートしている間、反復中に外部をスキップします...
MtCS 2014

2
  1. 個々のベクトルからペアのベクトルを作成します。
    ペアのベクトルを初期化するペアのベクトルに
    追加する

  2. カスタムソートコンパレータ
    を作成するカスタムオブジェクトのベクトルをソートする
    http://rosettacode.org/wiki/Sort_using_a_custom_comparator#C.2B.2B

  3. ペアのベクトルを並べ替えます。

  4. ペアのベクトルを個々のベクトルに分割します。

  5. これらすべてを関数に入れます。

コード:

std::vector<MyObject> vectorA;
std::vector<int> vectorB;

struct less_than_int
{
    inline bool operator() (const std::pair<MyObject,int>& a, const std::pair<MyObject,int>& b)
    {
        return (a.second < b.second);
    }
};

sortVecPair(vectorA, vectorB, less_than_int());

// make sure vectorA and vectorB are of the same size, before calling function
template <typename T, typename R, typename Compare>
sortVecPair(std::vector<T>& vecA, std::vector<R>& vecB, Compare cmp)
{

    std::vector<pair<T,R>> vecC;
    vecC.reserve(vecA.size());
    for(int i=0; i<vecA.size(); i++)
     {
        vecC.push_back(std::make_pair(vecA[i],vecB[i]);   
     }

    std::sort(vecC.begin(), vecC.end(), cmp);

    vecA.clear();
    vecB.clear();
    vecA.reserve(vecC.size());
    vecB.reserve(vecC.size());
    for(int i=0; i<vecC.size(); i++)
     {
        vecA.push_back(vecC[i].first);
        vecB.push_back(vecC[i].second);
     }
}

参照のペアのベクトル?
Mooing Duck 2013

意味はわかりますが、参照のペアは簡単には機能しません。
ruben2020 2013年

1
@ ruben2020:可能ですが、そうすることで問題を解決できます。2つのデータが十分に絡み合っているため、一方を並べ替えるともう一方を並べ替える必要がある場合、実際に持っているのはまだ統合されていないオブジェクトのように見えます。
cHao 2013年

1

vectorAとvectorBの長さが等しいと仮定しています。別のベクトルを作成することができます。それをposと呼びましょう。ここで:

pos[i] = the position of vectorA[i] after sorting phase

次に、posを使用してvectorBを並べ替えることができます。つまり、vectorBsortedを作成します。

vectorBsorted[pos[i]] = vectorB[i]

次に、vectorBsortedは、vectorAと同じインデックスの順列で並べ替えられます。


0

これが機能するかどうかはわかりませんが、このようなものを使用します。たとえば、2つのベクトルを並べ替えるには、降順のバブルソート方法とベクトルのペアを使用します。

降順のバブルソートの場合、ベクトルペアを必要とする関数を作成します。

void bubbleSort(vector< pair<MyObject,int> >& a)
{
    bool swapp = true;
    while (swapp) {
        int key;
        MyObject temp_obj;
        swapp = false;
        for (size_t i = 0; i < a.size() - 1; i++) {
            if (a[i].first < a[i + 1].first) {
                temp_obj = a[i].first;
                key = a[i].second;

                a[i].first = a[i + 1].first;
                a[i + 1].first = temp_obj;

                a[i].second = a[i + 1].second;
                a[i + 1].second = key;

                swapp = true;
            }
        }
    }
}

その後、2つのベクトル値を1つのベクトルペアに入れます。同時に値を追加できる場合は、これを使用して、バブルソート関数を呼び出します。

vector< pair<MyObject,int> > my_vector;

my_vector.push_back( pair<MyObject,int> (object_value,int_value));

bubbleSort(my_vector);

2つのベクトルに追加した後に値を使用する場合は、これを使用して、バブルソート関数を呼び出すことができます。

vector< pair<MyObject,int> > temp_vector;

for (size_t i = 0; i < vectorA.size(); i++) {
            temp_vector.push_back(pair<MyObject,int> (vectorA[i],vectorB[i]));
        }

bubbleSort(temp_vector);

これがお役に立てば幸いです。よろしく、カナー


0

私は最近、stlアルゴリズムで動作する適切なzipイテレータを作成しました。これにより、次のようなコードを生成できます。

std::vector<int> a{3,1,4,2};
std::vector<std::string> b{"Alice","Bob","Charles","David"};

auto zip = Zip(a,b);
std::sort(zip.begin(), zip.end());

for (const auto & z: zip) std::cout << z << std::endl;

これは単一のヘッダーに含まれており、唯一の要件はC ++ 17です。GitHubでチェックしてください

すべてのソースコードを含むcodereviewへの投稿もあります。


0

ティモシーシールドの答えに基づいています。
少し調整するapply_permutaionだけで、fold式を使用して、異なるタイプの複数のベクトルに一度に順列を適用できます。

template <typename T, typename... Ts>
void apply_permutation(const std::vector<size_t>& perm, std::vector<T>& v, std::vector<Ts>&... vs) {

    std::vector<bool> done(v.size());
    for(size_t i = 0; i < v.size(); ++i) {
        if(done[i]) continue;
        done[i] = true;
        size_t prev = i;
        size_t curr = perm[i];
        while(i != curr) {
            std::swap(v[prev], v[curr]);
            (std::swap(vs[prev], vs[curr]), ...);
            done[curr] = true;
            prev = curr;
            curr = perm[curr];
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.