¿Cómo puedo ordenar dos vectores de la misma manera, con criterios que usan solo uno de los vectores?

84

¿Cómo puedo ordenar dos vectores de la misma manera, con criterios que usan solo uno de los vectores?

Por ejemplo, supongamos que tengo dos vectores del mismo tamaño:

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

Luego clasifico vectorAusando alguna función de comparación. Esa clasificación reordenada vectorA. ¿Cómo puedo aplicarme el mismo reordenamiento vectorB?


Una opción es crear una estructura:

struct ExampleStruct {
    MyObject mo;
    int i;
};

y luego ordenar un vector que contiene el contenido de vectorAy vectorBcomprimido en un solo vector:

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

Esta no parece una solución ideal. ¿Hay otras opciones, especialmente en C ++ 11?

usuario2381422
fuente
¿Puede darnos un ejemplo con alguna entrada y la salida deseada correspondiente? Tengo problemas para entender la pregunta
Andy Prowl
Creo que quiere ordenar (efectivamente) el contenido vectorAy vectorBambos por el contenido de vectorB.
Mooing Duck
Creo que esta pregunta es un duplicado cercano si no es un duplicado exacto
Mooing Pato
¿Qué hay de ordenar un tercer vector (de índices 0, ... vectorA.size ()), basado en los valores en vectorA y "aplicar" esos índices en vectorB? Por ejemplo, como en stackoverflow.com/a/10581051/417197
André
Personalmente, prefiero tener un vector<pair<MyObject, int>>. Entonces no tendría que preocuparse de que las dos listas no estén sincronizadas; un tipo reordena ambos conjuntos de datos simultáneamente. Y no hay estructura adicional que tener que escribir.
cHao

Respuestas:

117

Encontrar una permutación de género

Dada una std::vector<T>y una comparación de T's, queremos poder encontrar la permutación que usaría si tuviera que ordenar el vector usando esta comparación.

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

Aplicar una permutación de ordenación

Dada una std::vector<T>y una permutación, queremos poder construir una nueva std::vector<T>que se reordena según la permutación.

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

Por supuesto, podría modificar apply_permutationpara mutar el vector que le da en lugar de devolver una nueva copia ordenada. Este enfoque sigue siendo una complejidad de tiempo lineal y utiliza un bit por elemento en su vector. Teóricamente, sigue siendo una complejidad espacial lineal; pero, en la práctica, cuando sizeof(T)es grande, la reducción en el uso de la memoria puede ser dramática. ( Ver detalles )

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

Ejemplo

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

Recursos

Timothy Shields
fuente
4
En mi compilador, tuve que cambiar "Comparar y comparar" por "Comparar comparar".
SmallChess
2
También necesitaba incluir el encabezado <numérico> para ejecutar la función std :: iota (vs2012).
Smith
1
"Por supuesto, podría modificar apply_permutationpara mutar el vector que le da en lugar de devolver una nueva copia ordenada " . Descubriría que desarrollar esto, al menos conceptualmente, es una adición útil a la respuesta. ¿Implementarías esto de la misma manera que él apply_permutationmismo?
ildjarn
2
@ildjarn He actualizado la respuesta con tu sugerencia.
Timothy Shields
2
¡Gracias por el excelente fragmento! Sin embargo, la declaración sort_permutation Compare& comparedebe ser constante. De lo contrario, no puede usar std :: less <T> o similar.
kotakotakota
9

Con range-v3 , es simple, ordena una vista zip:

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

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

o utilizar explícitamente la proyección:

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

Manifestación

Jarod42
fuente
Probé en VS2017, nada compilado o funcionado, no importa lo que intenté. Se sabe que esta biblioteca funciona en VS2019, GCC y LLVM.
Contango
4

Me gustaría contribuir con una extensión que se me ocurrió. El objetivo es poder ordenar varios vectores al mismo tiempo utilizando una sintaxis simple.

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

El algoritmo es el mismo que propuso Timothy pero usando plantillas variadas , por lo que podemos ordenar múltiples vectores de tipos arbitrarios al mismo tiempo.

Aquí está el fragmento de código:

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

Pruébelo en Ideone .

Explico esto un poco mejor en esta publicación de blog .

tuket
fuente
3

Clasificación in situ mediante permutación

Usaría una permutación como Timothy, aunque si sus datos son demasiado grandes y no desea asignar más memoria para el vector ordenado, debe hacerlo en el lugar . A continuación, se muestra un ejemplo de ordenación in situ O (n) (complejidad lineal) mediante permutación :

El truco consiste en obtener la permutación y la permutación inversa para saber dónde colocar los datos sobrescritos por el último paso de clasificació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;
    }
}
MtCS
fuente
1
Aunque parece que es O (N ^ 2), no lo es. El while interno solo se ejecuta si el elemento no está ordenado. A medida que el while interno ordena los datos, salta un poco las iteraciones del while
interno
2
  1. Haz un vector de pares con tus vectores individuales.
    inicializar vector de pares
    Sumando a un vector de par

  2. Hacer un comparador de ordenación personalizado:
    ordenar un vector de objetos personalizados
    http://rosettacode.org/wiki/Sort_using_a_custom_comparator#C.2B.2B

  3. Ordena tu vector de pares.

  4. Separe su vector de pares en vectores individuales.

  5. Pon todo esto en una función.

Código:

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);
     }
}
ruben2020
fuente
vector de pares de referencias?
Mooing Duck
Entiendo lo que quieres decir, pero los pares de referencias no van a funcionar fácilmente.
ruben2020
1
@ ruben2020: Puede , pero al hacerlo solo se soluciona el problema. Si tiene dos datos entrelazados lo suficiente como para ordenar uno debería ordenar el otro, parecería que lo que realmente tiene es un objeto aún no integrado.
cHao
1

Supongo que vectorA y vectorB tienen longitudes iguales. Podría crear otro vector, llamémoslo pos, donde:

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

y luego, puede ordenar vectorB usando pos, es decir, crear vectorBsorted donde:

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

y luego vectorBsorted se ordena mediante la misma permutación de índices que vectorA.

pkacprzak
fuente
0

No estoy seguro de si esto funciona, pero usaría algo como esto. Por ejemplo, para clasificar dos vectores, usaría el método de clasificación de burbujas descendentes y pares de vectores.

Para la clasificación de burbujas descendente, crearía una función que requiera un par de vectores.

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

Después de eso, pondría sus 2 valores vectoriales en un par de vectores. Si puede agregar valores al mismo tiempo, use este y luego llame a la función de clasificación de burbujas.

vector< pair<MyObject,int> > my_vector;

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

bubbleSort(my_vector);

Si desea usar valores después de sumar a sus 2 vectores, puede usar este y luego llamar a la función de clasificación de burbujas.

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

Espero que esto ayude. Saludos, Caner

Caner Saygin
fuente
0

Recientemente escribí un iterador zip adecuado que funciona con los algoritmos stl. Te permite producir código como este:

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;

Está contenido en un solo encabezado y el único requisito es C ++ 17. Compruébalo en GitHub .

También hay una publicación sobre codereview que contiene todo el código fuente.

DarioP
fuente
0

Basado en la respuesta de Timothy Shields.
Con un pequeño ajuste apply_permutaion, puede aplicar la permutación a múltiples vectores de diferentes tipos a la vez con el uso de una expresión de pliegue.

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