0

I need to sort a std::vector by index. Let me explain it with an example:

Imagine I have a std::vector of 12 positions (but can be 18 for example) filled with some values (it doesn't have to be sorted):

Vector Index:    0    1     2     3     4     5     6     7     8     9     10    11  
Vector Values:   3    0     2     3     2     0     1     2     2     4     5     3

I want to sort it every 3 index. This means: the first 3 [0-2] stay, then I need to have [6-8] and then the others. So it will end up like this (new index 3 has the value of previous idx 6):

Vector Index:    0    1     2     3     4     5     6     7     8     9     10    11
Vector Values:   3    0     2     1     2     2     3     2     0     4      5    3

I'm trying to make it in one line using std::sort + lambda but I can't get it. Also discovered the std::partition() function and tried to use it but the result was really bad hehe

Found also this similar question which orders by odd and even index but can't figure out how to make it in my case or even if it is possible: Sort vector by even and odd index

Thank you so much!

Note 0: No, my vector is not always sorted. It was just an example. I've changed the values

Note 1: I know it sound strange... think it like hte vecotr positions are like: yes yes yes no no no yes yes yes no no no yes yes yes... so the 'yes' positions will go in the same order but before the 'no' positions

Note 2: If there isn't a way with lambda then I thought making it with a loop and auxiliar vars but it's more ugly I think.

Note 3: Another example:

Vector Index:   0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  
Vector Values:  3  0  2  3  2  0  1  2  2  4   5   3   2   3   0   0   2   1

Sorted Values:  3  0  2  1  2  2  2  3  0  3   2   0   4   5   3   0   2   1

The final Vector Values is sorted (in term of old index): 0 1 2 6 7 8 12 13 14 3 4 5 9 10 11 15 16 17

You can imagine those index in 2 colums, so I want first the Left ones and then the Right one:

  0 1 2      3 4 5
  6 7 8     9 10 11
 12 13 14   15 16 17
Megasa3
  • 766
  • 10
  • 25
  • 3
    I don't understand what you're trying to do exactly. The resulting vector doesn't seem to be *sorted* in any way. Do you instead want to *move* elements around based on the indexes? – cigien Mar 07 '21 at 17:23
  • Did you check this topic? https://stackoverflow.com/questions/1577475/c-sorting-and-keeping-track-of-indexes – Alireza Abbasi Mar 07 '21 at 17:27
  • @AlirezaAbbasi yes, found it too but again they sort using elements, not index... and Whati i really need is to sort indexes. But thank you so much for the info :) – Megasa3 Mar 07 '21 at 17:32
  • @cigien yeah i know its a little bit tricky. The index will be sorted like: yes yes yes, no no no, yes yes yes, no no no so the 'yes' will go first than the 'no' indexes.. maybe its more clear now? – Megasa3 Mar 07 '21 at 17:34
  • Is your original vector always sorted? If so, the provided answer solves the problem, but if the original vector is not sorted, it won't work. Could you clarify the question please? – cigien Mar 07 '21 at 20:51
  • @cigien no, it is not sorted. It was just an example, I've added it to the question as it is ambiguous, sorry. – Megasa3 Mar 07 '21 at 21:24
  • Thanks for clarifying. I'd still suggest editing the example numbers as well, to make it clearer. – cigien Mar 07 '21 at 21:27
  • @cigien thanks to you for your help. Done! I've changed the numbers and repeated them as it may happen too – Megasa3 Mar 07 '21 at 21:37
  • Sorry, but your question is even more confusing now. Could you add some more examples, say, when you have 18 elements. Also, how are you comparing the chunks of 3 elements? – cigien Mar 07 '21 at 21:44
  • @cigien done again! :) sorry for the explanation.. I know it's a little bit tricky what I want – Megasa3 Mar 08 '21 at 08:18

2 Answers2

2

You don't want std::sort, you want std::rotate.

    std::vector<int> v = {20, 21, 22, 23, 24, 25,
                          26, 27, 28, 29, 30, 31};
    auto b = std::next(std::begin(v), 3); // skip first three elements
    auto const re = std::end(v);  // keep track of the actual end
    auto e = std::next(b, 6);  // the end of our current block
    while(e < re) {
        auto mid = std::next(b, 3);
        std::rotate(b, mid, e);
        b = e;
        std::advance(e, 6);
    }
    // print the results
    std::copy(std::begin(v), std::end(v), std::ostream_iterator<int>(std::cout, " "));

This code assumes you always do two groups of 3 for each rotation, but you could obviously work with whichever arbitrary ranges you wanted.

The output looks like what you'd want:

20 21 22 26 27 28 23 24 25 29 30 31

Update: @Blastfurnace pointed out that std::swap_ranges would work as well. The rotate call can be replaced with the following line:

std::swap_ranges(b, mid, mid);  // passing mid twice on purpose
Stephen Newell
  • 7,330
  • 1
  • 24
  • 28
  • Great answer. I totally misunderstood the question. – Aykhan Hagverdili Mar 07 '21 at 18:01
  • 1
    If both sections are the same size then [`std::swap_ranges`](https://en.cppreference.com/w/cpp/algorithm/swap_ranges) may be a better choice. It's also generally faster than `std::rotate`. – Blastfurnace Mar 07 '21 at 21:28
  • Thank you so much for the answer but as others pointed out, this will work only if the vector is sorted and in my case, it may not be. It was an example but was confusing. Sorry. Just added the info on the question – Megasa3 Mar 07 '21 at 21:31
  • @Blastfurnace - I wan't aware of `std::swap_ranges`, so thanks. I'll update the answer. – Stephen Newell Mar 07 '21 at 22:54
  • @Megasa3 - By replacing the original values with the new ones, this algorithm still provides the same output that matches what you want. Where does sorting come into play? – Stephen Newell Mar 07 '21 at 22:56
  • @StephenNewell Oh.. I've made a new try today and seems it works. Could you extend it to a vector of 18 elements for example? I can't make it work with other lenghts. Thanks! – Megasa3 Mar 08 '21 at 08:43
  • Ok, I made a little change that works for 18: `auto mid = std::next(b, a); std::rotate(b, mid, e); b = e; std::advance(e, a); a += 3;` – Megasa3 Mar 08 '21 at 09:41
1

With the range-v3 library, you can write this quite conveniently, and it's very readable. Assuming your original vector is called input:

namespace rs = ranges;
namespace rv = ranges::views;

// input [3, 0, 2, 3, 2, 0, 1, 2, 2, 4, 5, 3, 2, 3, 0, 0, 2, 1]

auto by_3s = input | rv::chunk(3); // [[3, 0, 2], [3, 2, 0], [1, 2, 2], [4, 5, 3], [2, 3, 0], [0, 2, 1]]

auto result = rv::concat(by_3s | rv::stride(2),               // [[3, 0, 2],  [1, 2, 2], [2, 3, 0]]
                         by_3s | rv::drop(1) | rv::stride(2)) // [[3, 2, 0],  [4, 5, 3], [0, 2, 1]]
              | rv::join
              | rs::to<std::vector<int>>;  // [3, 0, 2, 1, 2, 2, 2, 3, 0, 3, 2, 0, 4, 5, 3, 0, 2, 1]

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • Ohhh I'm afraid I can't use external libraries but I didn't even know about it and seems to be sooo cool!! As you say, it's very readable. Thank you so much. – Megasa3 Mar 09 '21 at 08:16