Як я можу сортувати два вектори однаково, з критеріями, що використовує лише один із векторів?


84

Як я можу сортувати два вектори однаково, з критеріями, що використовує лише один із векторів?

Наприклад, припустимо, у мене є два вектори однакового розміру:

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

Потім я сортую, vectorAвикористовуючи якусь функцію порівняння. Це сортування впорядковано vectorA. Як я можу застосувати те саме переупорядкування vectorB?


Одним із варіантів є створення структури:

struct ExampleStruct {
    MyObject mo;
    int i;
};

а потім відсортувати вектор, що містить вміст vectorAта vectorBзаархівований до одного вектора:

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

Це не здається ідеальним рішенням. Чи існують інші варіанти, особливо в C ++ 11?


Чи можете ви навести приклад з деяким входом і відповідним бажаним результатом? У мене проблеми з розумінням питання
Енді Проул,

Я думаю, що він хоче (ефективно) відсортувати вміст vectorAі те, і vectorBінше за вмістом vectorB.
Mooing Duck

Я думаю, що це питання майже дублікат, якщо не точний дублікат
Mooing Duck

Що можна сказати про сортування третього вектора (з індексів 0, ... vectorA.size ()) на основі значень у vectorA та "застосування" цих індексів на vectorB? Наприклад, як у stackoverflow.com/a/10581051/417197
Андре

Особисто я волів би vector<pair<MyObject, int>>. Тоді вам не доведеться турбуватися про те, що два списки не синхронізуються; одне сортування впорядковує обидва набори даних одночасно. І немає жодної зайвої структури, щоб писати.
cHao

Відповіді:


118

Пошук перестановки сортування

Враховуючи a std::vector<T>та порівняння для T's, ми хочемо мати можливість знайти перестановку, яку ви використали б, якщо б ви сортували вектор за допомогою цього порівняння.

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

Застосування перестановки сортування

Враховуючи a 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щоб змінити наданий йому вектор, а не повертати нову відсортовану копію. Цей підхід все ще має лінійну часову складність і використовує по одному біту на елемент у вашому векторі. Теоретично це все ще лінійна космічна складність; але, на практиці, коли 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
У моєму компіляторі мені довелося змінити "Порівняти та порівняти" на "Порівняти порівняти".
SmallChess

2
Мені також потрібно було включити заголовок <numeric>, щоб запустити функцію std :: iota (vs2012).
Сміт

1
" Ви, звичайно, можете змінити, apply_permutationщоб змінити вектор, який ви йому надаєте, а не повертати нову відсортовану копію. " Я вважаю, що це, хоча б концептуально, корисне доповнення до відповіді. Ви б реалізували це так само, як і воно apply_permutationсаме?
ildjarn

2
@ildjarn Я оновив відповідь вашою пропозицією.
Тімоті Шилдс

2
Дякуємо за чудовий фрагмент! декларація sort_permutation Compare& compareповинна бути const, однак. В іншому випадку ви не можете використовувати std :: less <T> або подібний.
kotakotakota

9

З range-v3 це просто, відсортуйте zip-вид:

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

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

Я розумію, що ви маєте на увазі, але пари посилань працювати не будуть легко.
ruben2020

1
@ ruben2020: Це може , але це просто пластир для вирішення проблеми. Якщо у вас є дві частини даних, які переплітаються настільки, що для сортування однієї слід сортувати іншу, здається, що ви насправді є ще не інтегрованим об’єктом.
cHao

1

Я припускаю, що vectorA та vectorB мають однакову довжину. Ви можете створити ще один вектор, назвемо його pos, де:

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

а потім ви можете сортувати vectorB за допомогою pos, тобто створити vectorBsorted де:

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

а потім vectorBsorted сортується за тією ж перестановкою індексів, що і vectorA.


0

Я не впевнений, що це працює, але я б використав щось подібне. Наприклад, для сортування двох векторів я б використав метод сортування за спаданням бульбашок та векторні пари.

Для сортування бульбашок за спаданням я створив би функцію, яка вимагає векторної пари.

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 векторні значення в одну векторну пару. Якщо ви можете одночасно додавати значення, скористайтеся цим, а потім викличте функцію сортування міхурів.

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

Нещодавно я написав правильний zip-ітератор, який працює з алгоритмами stl. Це дозволяє створювати такий код:

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];
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.