2

I have a sorting algorithm on a vector, and I want to apply it to several vectors, without knowing how much. The only thing I'm sure is that there will be at least 1 vector (always the same) on which I will perform my algorithm. Other will just follow.

Here's an example :

void sort(std::vector<int>& sortVector, std::vector<double>& follow1, std::vector<char>& follow2, ... ){
    for (int i = 1; i<vector.size(); ++i){
        if ( vector[i-1] > vector[i] ) { //I know it's not sorting here, it's only for the example
            std::swap(vector[i-1], vector[i]);
            std::swap(follow1[i-1], follow1[i]);
            std::swap(follow2[i-1], follow2[i]);
            ....
        }
    }
 }

I was thinking about using variadic function, but since it's a recursive function, I was wondering if it won't take too much time to everytime create my va_arg list (I'm working on vector sized 500millions/1billions ...). So does something else exists?

As I'm writing this question, I'm understanding that maybe i'm fooling myself, and there is no other way to achieve what I want and variadic function is maybe not that long. (I really don't know, in fact).

EDIT : In fact, I'm doing an Octree-sorting of datas in order to be usable in opengl.
Since my datas are not always the same (e.g OBJ files will gives me normals, PTS files will gives me Intensity and Colors, ...), I want to be able to reorder all my vectors (in which are contained my datas) so that they have the same order as the position vectors (The vector that contains the positions of my points, it'll be always here).

But all my vectors will have same length, and I want all my followervector to be reorganised as the first one.

If i have 3 Vectors, if I swap first and third values in my first vector, I want to swap first and thrid values in my 2 others vectors.

But my vectors are not all the same. Some will be std::vector<char>, other std::vector<Vec3>, std::vector<unsigned>, and so on.

Raph Schim
  • 528
  • 7
  • 30
  • Are you sure you don't actually have a vector of vectors? How do you intend to pass these parameters to the function, without knowing how many you have? – vgru Sep 04 '17 at 07:18
  • 2
    Don't write your own `sort` function. Think how you can refactor your data to use std::sort. – n. m. could be an AI Sep 04 '17 at 07:22
  • In fact, It's an octree-sort for Pointcloud. And my datas could come from everywhere, meaning that everytime i'll have position, but I can have colors, intensity, normals, ... or not. So for every informations I have, i have to apply my sort to this vector. I'm not sure if i can use std::sort for such a thing :/ – Raph Schim Sep 04 '17 at 07:23
  • 3
    Yes there are many ways to use std::sort with your data. The easiest way would be to sort an array of integers (0..N-1) where the comparison function uses your first array. The result will be a permutation of indices you can use to rearrange any number of arrays. – n. m. could be an AI Sep 04 '17 at 07:35
  • it's not clear what you want to do exactly. I provided an answer that I think doesn't do what you want. Please add some clarification. I am sure I can provide an answer if I understand your needs. All the vectors have the same length? You want to sort the 1st vector and then all other vectors be reordered as the 1st? E.g. if the 3rd element in the first vector goes to 1st position after sort, then in all other vectors the 3rd element to be moved in the 1st position? – bolov Sep 04 '17 at 07:37
  • n.m. I'll have to take a deeper look. But rewriting my sorting function is not what I'm looking for atm. bolov : I will add these informations :) But you're right. All vectors are same length. and yes, I want all vectors to be reordered as the first one. I will edit my post right now – Raph Schim Sep 04 '17 at 07:40
  • You should use @username when talking to users (not under their own posts), otherwise they (in most cases) won't get a notification. – HolyBlackCat Sep 04 '17 at 07:50
  • Thanks for the tip! I knew for the at[username], but I believe it doesn't work with 2 ppl. @bolov I edited my post. – Raph Schim Sep 04 '17 at 07:54
  • Unrelated to your problem: please reconsider the name `vector` for your first argument – Caleth Sep 04 '17 at 08:49
  • You don't need to rewrite it, you need to scrap it. – n. m. could be an AI Sep 04 '17 at 09:37
  • Related to [sorting-zipped-locked-containers-in-c-using-boost-or-the-stl](https://stackoverflow.com/questions/13840998/sorting-zipped-locked-containers-in-c-using-boost-or-the-stl) – Jarod42 Sep 04 '17 at 13:26

4 Answers4

2

With range-v3, you may use zip, something like:

template <typename T, typename ... Ranges>
void sort(std::vector<T>& refVector, Ranges&& ... ranges){
    ranges::sort(ranges::view::zip(refVector, std::forward<Ranges>(ranges)...));
}

Demo

Or if you don't want to use ranges to compare (for ties in refVector), you can project to use only refVector:

template <typename T, typename ... Ranges>
void sort(std::vector<T>& refVector, Ranges&& ... ranges){
    ranges::sort(ranges::view::zip(refVector, std::forward<Ranges>(ranges)...),
                 std::less<>{},
                 [](auto& tup) -> T& { return std::get<0>(tup); });
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

Although, I totally agree with the comment of n.m. I suggest to use a vector of vectors which contain the follow vectors and than do a loop over all follow vectors.

void sort(std::vector<int>& vector, std::vector<std::vector<double>>& followers){
    for (int i = 1; i<vector.size(); ++i){
        if ( vector[i-1] > vector[i] ) { 
            std::swap(vector[i-1], vector[i]);
            for (auto & follow : followers) 
                std::swap(follow[i-1], follow[i]);          
        }
    }
 }

Nevertheless, as n.m. pointed out, perhaps think about putting all your data you like to sort in a class like structure. Than you can have a vector of your class and apply std::sort, see here.

struct MyStruct
{
    int key;  //content of your int vector named "vector"
    double follow1; 
    std::string follow2;
    // all your inforrmation of the follow vectors go here.

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
};

struct less_than_key
{
    inline bool operator() (const MyStruct& struct1, const MyStruct& struct2)
    {
        return (struct1.key < struct2.key);
    }
};

std::vector < MyStruct > vec;

vec.push_back(MyStruct(4, 1.2, "test"));
vec.push_back(MyStruct(3, 2.8, "a"));
vec.push_back(MyStruct(2, 0.0, "is"));
vec.push_back(MyStruct(1, -10.5, "this"));

std::sort(vec.begin(), vec.end(), less_than_key());
schorsch312
  • 5,553
  • 5
  • 28
  • 57
0

The main problem here is that the std::sort algorithm cannot operate on multiple vectors at the same time.

For the purpose of demonstration, let's assume you have a std::vector<int> v1 and a std::vector<char> v2 (of the same size of course) and you want to sort both depending on the values in v1. To solve this, I basically see three possible solutions, all of which generalize to an arbitrary number of vectors:


1) Put all your data into a single vector.

Define a struct, say Data, that keeps an entry of every data vector.

struct Data 
{
    int d1;
    char d2;
    // extend here for more vectors
};

Now construct a new std::vector<Data> and fill it from your original vectors:

std::vector<Data> d(v1.size());
for(std::size_t i = 0; i < d.size(); ++i)
{
    d[i].d1 = v1[i];
    d[i].d2 = v2[i];
    // extend here for more vectors
}

Since everything is stored inside a single vector now, you can use std::sort to bring it into order. Since we want it to be sorted based on the first entry (d1), which stores the values of the first vector, we use a custom predicate:

std::sort(d.begin(), d.end(), 
    [](const Data& l, const Data& r) { return l.d1 < r.d1; });

Afterwards, all data is sorted in d based on the first vector's values. You can now either work on with the combined vector d or you split the data into the original vectors:

std::transform(d.begin(), d.end(), v1.begin(), 
    [](const Data& e) { return e.d1; });
std::transform(d.begin(), d.end(), v2.begin(),
    [](const Data& e) { return e.d2; });
// extend here for more vectors

2) Use the first vector to compute the indices of the sorted range and use these indices to bring all vectors into order:

First, you attach to all elements in your first vector their current position. Then you sort it using std::sort and a predicate that only compares for the value (ignoring the position).

template<typename T>
std::vector<std::size_t> computeSortIndices(const std::vector<T>& v)
{
    std::vector<std::pair<T, std::size_t>> d(v.size());
    for(std::size_t i = 0; i < v.size(); ++i)
        d[i] = std::make_pair(v[i], i);

    std::sort(d.begin(), d.end(),
        [](const std::pair<T, std::size_t>& l, 
            const std::pair<T, std::size_t>& r)
        {
            return l.first < r.first;
        });

    std::vector<std::size_t> indices(v.size());
    std::transform(d.begin(), d.end(), indices.begin(),
        [](const std::pair<T, std::size_t>& p) { return p.second; });
    return indices;
}

Say in the resulting index vector the entry at position 0 is 8, then this tells you that the vector entries that have to go to the first position in the sorted vectors are those at position 8 in the original ranges.

You then use this information to sort all of your vectors:

template<typename T>
void sortByIndices(std::vector<T>& v, 
    const std::vector<std::size_t>& indices)
{
    assert(v.size() == indices.size());
    std::vector<T> result(v.size());   
    for(std::size_t i = 0; i < indices.size(); ++i)
        result[i] = v[indices[i]];
    v = std::move(result);
}

Any number of vectors may then be sorted like this:

const auto indices = computeSortIndices(v1);
sortByIndices(v1, indices);
sortByIndices(v2, indices);
// extend here for more vectors

This can be improved a bit by extracting the sorted v1 out of computeSortIndices directly, so that you do not need to sort it again using sortByIndices.


3) Implement your own sort function that is able to operate on multiple vectors. I have sketched an implementation of an in-place merge sort that is able to sort any number of vectors depending on the values in the first one.

The core of the merge sort algorithm is implemented by the multiMergeSortRec function, which takes an arbitrary number (> 0) of vectors of arbitrary types. The function splits all vectors into first and second half, sorts both halves recursively and merges the the results back together. Search the web for a full explanation of merge sort if you need more details.

template<typename T, typename... Ts>
void multiMergeSortRec(
    std::size_t b, std::size_t e,
    std::vector<T>& v, std::vector<Ts>&... vs)
{
    const std::size_t dist = e - b;    
    if(dist <= 1)
        return;

    std::size_t m = b + (dist / static_cast<std::size_t>(2));
    // split in half and recursively sort both parts
    multiMergeSortRec(b, m, v, vs...);
    multiMergeSortRec(m, e, v, vs...);
    // merge both sorted parts    
    while(b < m)
    {
        if(v[b] <= v[m])
            ++b;
        else 
        {
            ++m;
            rotateAll(b, m, v, vs...);
            if(m == e)
                break;
        }
    }
}

template<typename T, typename... Ts>
void multiMergeSort(std::vector<T>& v, std::vector<Ts>&... vs)
{
    // TODO: check that all vectors have same length
    if(v.size() < 2)
        return ;
    multiMergeSortRec<T, Ts...>(0, v.size(), v, vs...);
}

In order to operate in-place, parts of the vectors have to be rotated. This is done by the rotateAll function, which again works on an arbitrary number of vectors by recursively processing the variadic parameter pack.

void rotateAll(std::size_t, std::size_t)
{
}

template<typename T, typename... Ts>
void rotateAll(std::size_t b, std::size_t e, 
    std::vector<T>& v, std::vector<Ts>&... vs)
{
    std::rotate(v.begin() + b, v.begin() + e - 1, v.begin() + e);
    rotateAll(b, e, vs...);
}

Note, that the recursive calls of rotateAll are very likely to be inlined by every optimizing compiler, such that the function merely applies std::rotate to all vectors. You can circumvent the need to rotate parts of the vector, if you leave in-place and merge into an additional vector. I like to emphasize that this is neither an optimized nor a fully tested implementation of merge sort. It should serve as a sketch, since you really do not want to use bubble sort whenever you work on large vectors.


Let's quickly compare the above alternatives:

  • 1) is easier to implement, since it relies on an existing (highly optimized and tested) std::sort implementation.
  • 1) needs all data to be copied into the new vector and possibly (depending on your use case) all of it to be copied back.
  • In 1) multiple places have to be extended if you need to attach additional vectors to be sorted.
  • The implementation effort for 2) is mediocre (more than 1, but less and easier than 3), but it relies on optimized and tested std::sort.
  • 2) cannot sort in-place (using the indices) and thus has to make a copy of every vector. Maybe there is an in-place alternative, but I cannot think of one right now (at least an easy one).
  • 2) is easy to extend for additional vectors.
  • For 3) you need to implement sorting yourself, which makes it more difficult to get right.
  • 3) does not need to copy all data. The implementation can be further optimized and can be tweaked for improved performance (out-of-place) or reduced memory consumption (in-place).
  • 3) can work on additional vectors without any change. Just invoke multiMergeSort with one or more additional arguments.
  • All three work for heterogeneous sets of vectors, in contrast to the std::vector<std::vector<>> approach.

Which of the alternatives performs better in your case, is hard to say and should greatly depend on the number of vectors and their size, so if you really need optimal performance (and/or memory usage) you need to measure.

Find an implementation of the above here.

nh_
  • 2,211
  • 14
  • 24
0

By far the easiest solution is to create a helper vector std::vector<size_t> initialized with std::iota(helper.begin(), helper.end(), size_t{});.

Next, sort this array,. obviously not by the array index (iota already did that), but by sortvector[i]. IOW, the predicate is [sortvector&](size_t i, size_t j) { sortVector[i] < sortVector[j]; }.

You now have the proper order of array indices. I.e. if helper[0]==17, then it means that the new front of all vectors should be the original 18th element. Usually the easiest way to produce the sorted result is to copy over elements, and then swap the original vector and the copy, repeated for all vectors. But if copying all elements is too expensive, it can be done in-place. (Note that if O(N) element copes are too expensive, a straightforward std::sort tends to perform badly as well as it needs pivots)

MSalters
  • 173,980
  • 10
  • 155
  • 350