Sort two vectors with sort ()

2

In an exercise of Programming Principles and Practice Using C ++ they ask to order two vectors; one of names and another of ages, and in the end they must coincide. For example:

I have 2 vectors:

vector<string> nombres;
vector<double> edades;

And when filling the vectors with push_back () I get the following output:

(María, 19)
(Arturo, 12)
(José, 20)
(Chabelo, 1000)

When using the function sort (names) I get:

(Arturo, 19)
(Chabelo, 12)
(José, 20)
(María, 1000)

And if I also use sort (ages) I get:

(Arturo, 12)
(Chabelo, 19)
(José, 20)
(María, 1000)

My question is how can I use the sort () function with the vector names to generate the following:

(Arturo, 12)
(Chabelo, 1000)
(José, 20)
(María, 19)
    
asked by akko 29.12.2016 в 09:36
source

4 answers

2

There is a way to sort the two simultaneously without having to move the data to a new container. The idea is to create a new vector that contains the ordered indexes of the reference vector. This new vector can be used to reorder the two vectors.

The first, for convenience, is to provide a generic way to compare the elements of the main vector. In this case the generic method will try to sort the elements using the operator " less than ":

template<class T>
struct DefaultFuncComparar
{
  bool operator()(T const& a, T const& b)
  {
    return a < b;
  }
};

And now the sort function:

template<class T, class U, class FuncComparar = std::function<bool(T const&, T const&)>>
void OrdenarVectores(
  std::vector<T>& a,
  std::vector<U>& b,
  FuncComparar comparar = DefaultFuncComparar<T>())
{
  std::vector<T> copiaA{a};
  std::vector<U> copiaB{b};

  // Creamos el vector de índices
  std::vector<std::size_t> vector_indices(a.size());
  std::iota(vector_indices.begin(), vector_indices.end(), 0);

  // Ordenamos los índices en base al vector "a"
  std::sort(vector_indices.begin(), vector_indices.end(),
      [&](std::size_t i, std::size_t j){ return comparar(a[i], a[j]); });

  // Ordenamos los vectores "a" y "b" en base al vector de índices
  std::transform(vector_indices.begin(), vector_indices.end(), a.begin(),
      [&](std::size_t i){ return copiaA[i]; });

  std::transform(vector_indices.begin(), vector_indices.end(), b.begin(),
      [&](std::size_t i){ return copiaB[i]; });
}

If we now try it with the following code:

int main(){
  std::vector<std::string> nombres = { "Maria", "Arturo", "Jose", "Chabelo" };
  std::vector<int> edades = { 19, 12, 20, 1000 };

  OrdenarVectores(nombres,edades);

  std::cout << "Ordenados por nombre\n";
  for( unsigned i=0u; i<nombres.size(); i++)
  {
    std::cout << edades[i] << ' ' << nombres[i] << '\n';
  }
  std::cout << '\n';

  OrdenarVectores(edades,nombres);

  std::cout << "Ordenados por edad\n";
  for( unsigned i=0u; i<nombres.size(); i++)
  {
    std::cout << edades[i] << ' ' << nombres[i] << '\n';
  }
}

The result is the following:

Ordenados por nombre
12 Arturo
1000 Chabelo
20 Jose
19 Maria

Ordenados por edad
12 Arturo
19 Maria
20 Jose
1000 Chabelo

In any case, if it turns out that the two vectors are related to the point that they have to maintain the same indexes, then the design you propose is not the most appropriate. Keeping two or more vectors synchronized can complicate the design of the program too much.

    
answered by 31.12.2016 в 16:05
2

I would like to add a version with variádicas templates to existing solutions

Proposal.

template <std::size_t INDEX, typename TUPLE>
void push(const TUPLE &) {}

template <std::size_t INDEX, typename TUPLE, typename T, typename ... TYPES>
void push(const TUPLE &tupla, std::vector<T> &t, std::vector<TYPES> &...args)
{
    t.push_back(std::get<INDEX>(tupla));
    push<INDEX + 1>(tupla, args ...);
}

template <typename T>
T pop(std::vector<T> &v)
{
    auto t = v.back();
    v.pop_back();
    return t;
}

template <typename T, typename ... TYPES>
void ordena(std::vector<T> &t, std::vector<TYPES> &...args)
{
    using tupla = std::tuple<T, TYPES ...>;
    std::vector<tupla> valores(t.size());

    std::generate(valores.begin(), valores.end(), [&]() -> tupla
    {
        return { pop(t), pop(args)... };
    });

    std::sort(valores.begin(), valores.end(), [](auto i, auto d)
    {
        return std::get<0>(i) < std::get<0>(d);
    });

    for (const auto &valor : valores)
    {
        push<0>(valor, t, args...);
    }
}

This approach does not rely on obtaining indexes to subsequently sort through these indexes ( as it does eferion ) if not moving 1 the data of each of the vectors to a vector with a tuple and ordering said vector, then moving 1 again the data of the tuple vector to the vectors original vectors.

Explanation.

The order function receives between 1 and infinite 2 vectors of any sort and orders them all with the order of the first received vector:

template <typename T, typename ... TYPES>
void ordena(std::vector<T> &t, std::vector<TYPES> &...args)

The t parameter is the first vector, which will be used as the sort reference, the args parameter is the parameter package with the rest of the vectors. The function only accepts vectors other than containers (although it could be adapted to allow it).

The first thing that is done is to create a tuple of all types of the vectors to create a vector with that tuple and order all the values at the same time:

using tupla = std::tuple<T, TYPES ...>;
std::vector<tupla> valores(t.size());

By expanding the template with vectors nombres and edades the function ordena would look like this:

void ordena(std::vector<std::string> &t, std::vector<double> &p0)
{
    using tupla = std::tuple<std::string, double>;
    std::vector<tupla> valores(t.size());
    ...

If more parameters were used, the expansion of the template would be adapted:

std::vector<std::string> s;
std::vector<double> d;
std::vector<int> i;
std::vector<char> c;

ordena(s, d, i, c);

Would expand to:

void ordena(std::vector<std::string> &t, std::vector<double> &p0, std::vector<int> &p1, std::vector<char> &p2)
{
    using tupla = std::tuple<std::string, double, int, char>;
    std::vector<tupla> valores(t.size());
    ...

Next, each of the values of the original vectors is saved in the vector of tuples, while the original vectors are emptied, this is achieved with std::generate using a lambda:

std::generate(valores.begin(), valores.end(), [&]() -> tupla
{
    return { pop(t), pop(args)... };
});

This instruction, using the last example, would expand as follows:

std::generate(valores.begin(), valores.end(), [&]() -> tupla
{
    return { pop(t), pop(p0), pop(p1), pop(p2) };
});

We call generate with a lambda whose return type is the tuple of the template instantiated, so we will be adding the elements paired with all the elements in the same position in all the vectors passed as a parameter, in our case vector valores after the call to generate would be:

valores 0: <María,   19>
valores 1: <Arturo,  12>
valores 2: <José,    20>
valores 3: <Chabelo, 1000>

To do this we use the auxiliary template function pop , which receives any vector and returns the last element of it at the same time it eliminates it.

Then the tuple of valores is sorted using the first element of the tuple (using the function) std::get<0> ):

std::sort(valores.begin(), valores.end(), [](auto i, auto d)
{
    return std::get<0>(i) < std::get<0>(d);
});

And finally we use the auxiliary recursive template function push :

for (const auto &valor : valores)
{
    push<0>(valor, t, args...);
}

That in the example of nombres and edades would expand like this:

push<0>(const std::tuple<std::string, double> &tupla, std::vector<std::string> &t, std::vector<double> &p0)
{
    t.push_back(std::get<0>(tupla));
    push<1>(tupla, p0);
}

push<1>(const std::tuple<std::string, double> &tupla, std::vector<double> &t)
{
    t.push_back(std::get<1>(tupla));
    push<2>(tupla);
}

push<2>(const std::tuple<std::string, double> &tupla) {}

So the ordena function can be used like this:

int main()
{
    std::vector<std::string> nombres = {"María", "Arturo", "José", "Chabelo"};
    std::vector<double> edades = {19, 12, 20, 1000};

    ordena(nombres, edades);

    return 0;
}

Sorting nombres and edades like this:

| nombres | edades |
+---------+--------+
| Arturo  | 12     |
| Chabelo | 1000   |
| José    | 20     |
| María   | 19     |

And allows you to sort with any 2 number of vectors:

std::vector<std::string> nombres = {"María", "Arturo", "José", "Chabelo"};
std::vector<double> edades = {19, 12, 20, 1000};
std::vector<double> numeros { 3.1415, 1.11, 2.22, 1e6 };

ordena(numeros, edades, nombres);

Sample:

| numeros | edades | nombres |
+---------+--------+---------+
| 1.11    | 19     | María   |
| 2.22    | 20     | José    |
| 3.1415  | 12     | Arturo  |
| 1e+06   | 1000   | Chabelo |

[Here] you can see the code working. Note that the code assumes that all vectors are the same size and that it is expensive at the level of memory rehousing.

1 Deletes the origin and copies the destination, does not use semantics of movement.

2 Infinity no: as many as the compiler allows.

    
answered by 02.01.2017 в 14:47
1

Put both data into a couple and build a single vector of pairs (name, age). Then use a custom comparer to order the vector of pairs.

Construct the vector of pairs:

std::vector< std::pair< string, int> > nombreEdad;
for (size_t n=0; n<nombres.size(); ++n)
    nombreEdad.push_back( std::pair<string,int>(nombres[n], edades[n]));

Tailored comparator:

struct nombreMenor
{
    inline bool operator() (std::pair<string,int>& p1, 
                            const std::pair<string,int>& p2)
    {
        return p1.first.compare( p2.first) < 0;
    }
};

Call sort with the custom comparer.

std::sort(nombreEdad.begin(), nombreEdad.end(), nombreMenor());    

Print result.

for( auto par : nombreEdad )
    std::cout << par.first << "," << par.second << std::endl;
    
answered by 29.12.2016 в 10:23
1

The exercise required creating a function that ordered both vectors - I confused it with the function sort () - The solution I implemented was the following (where age and name are vectors that have already been filled in another function of the program):

void Ordenar(){
    vector<string> cp_nom = nombre;     
    sort(nombre);
    vector<double> cp_edad;     
    for(int i = 0; i < nombre.size(); ++i){
        for(int j = 0; j < cp_nom.size(); ++j){
            if(cp_nom[j] == nombre[i]){
                cp_edad.push_back(edad[j]);
            }
        }
    }           
    edad.clear();
    edad = cp_edad;
}

Where I create a copy of the vector vector string > cp_nom which I use to check the positions of the original vector vector string > name already sorted with the sort () function and so start filling the vector double > cp_edad upon completion of the replacement of the vector double > age with the content of vector double > cp_edad

    
answered by 31.12.2016 в 04:15