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.