35

I have this code here that has two arrays. It sorts arr[], so that the highest value will be in index 0. Now the second array arr1[] contains strings, I'd like the code to apply whatever changes where made to arr[] to arr1[]. So that arr[0] would return 6, while arr1[0] would return the string "d1". Notice how "d1" was at the same index as 6? After sorting I'd like the same values to still have their string counterparts.

How would I go about doing this?

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <functional>
using namespace std;

int main() {
  int arr[ 5 ] = { 4, 1, 3, 6, 2 };  
  string arr1[ 5 ] = { "a1", "b1", "c1", "d1", "e1" };

  std::sort( arr, arr + 5, std::greater< int >() );
  cout << arr[0] << arr1[0] << endl;

  system("pause");
}
Arun
  • 19,750
  • 10
  • 51
  • 60
rectangletangle
  • 50,393
  • 94
  • 205
  • 275
  • once you have sorted `arr` the original sort order is no longer known. You'll need to store the original order if you want to sort the other array by simple assignment. – Matt Ellen Oct 11 '10 at 19:25
  • If `arr` and `arr1` are related, why aren't they stored together (say as a structure) in the first place? – casablanca Oct 11 '10 at 19:27
  • 2
    duplicate: http://stackoverflow.com/questions/236172/how-do-i-sort-a-stdvector-by-the-values-of-a-different-stdvector – Shay Erlichmen Oct 11 '10 at 19:29

4 Answers4

42

Rather than sort the arrays, sort the indices. I.e., you have

int arr[5]={4,1,3,6,2}
string arr1[5]={"a1","b1","c1","d1","e1"};

and you make

int indices[5]={0,1,2,3,4};

now you make a sort indices comparator that looks like this (just and idea, you'll probably have to fix it a little)

class sort_indices
{
   private:
     int* mparr;
   public:
     sort_indices(int* parr) : mparr(parr) {}
     bool operator()(int i, int j) const { return mparr[i]<mparr[j]; }
}

now you can use the stl sort

std::sort(indices, indices+5, sort_indices(arr));

when you're done, the indices array will be such that arr[indices[0]] is the first element. and likewise arr1[indices[0]] is the corresponding pair.

This is also a very useful trick when you're trying to sort a large data object, you don't need to move the data around at every swap, just the indices.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
miked
  • 3,458
  • 1
  • 22
  • 25
  • Good answer apart from the minor point that he is sorting descending, but yes, this is the way to do it. – CashCow Oct 11 '10 at 19:36
  • 1
    Yeah, and the operator() needs to be const for this to work too. :) – miked Oct 11 '10 at 19:38
  • 11
    Note that if you genuinely need the two arrays sorted, then using the array of indices to put the original arrays into order isn't entirely trivial. – Steve Jessop Oct 12 '10 at 09:57
  • 1
    @SteveJessop looks trivial to me? create new arrays ar[] and ar1[] and then loop it with ar[i] = arr[indices[i]] and ar1[i] = arr1[indices[i]] – rint Nov 10 '17 at 15:58
  • 4
    @rint: It's nearly trivial, but I'd say not quite. In the example, the arrays are of a type that's copy-assignable. It's not trivial that this is always the case :-) Also, the use of a second array might require more memory than `std::sort` alone would. So moving across to a second array and then moving back is trivial to write, but not optimal, and you may or may not care. – Steve Jessop Jan 10 '18 at 00:14
  • This approach requires one n-sized indices array to sort indices. Then applying this permutation on top of the original array will require another temporary array of size n. Thus it's equivalent to creating a array of pairs and then sort them and copy back values – router Dec 14 '22 at 11:58
13

You need to combine them together and then sort the combined pair and then un-combine the pairs.

int arr[ 5 ] = { ... };
string arr1[ 5 ] = { ... };
pair<int, string> pairs[ 5 ];

for ( int i = 0; i < 5; ++i )
  pairs[ i ] = make_pair( arr[ i ], arr1[ i ] );

sort( pairs.begin(), pairs.end() );

for ( int i = 0; i < 5; ++i )
{
  arr[ i ] = pairs[ i ].first;
  arr1[ i ] = pairs[ i ].second;
}

Really though, if arr and arr1 are related then they should be stored as the pair (or at least a custom struct) anyway. That way you don't need to use this as an intermediate step.

Peter Alexander
  • 53,344
  • 14
  • 119
  • 168
10

Write your own iterator and use STD:sort. It's easily coded in less than 50 lines without 3rd party libraries. Swap function IS VERY IMPORTANT here.

#include <iostream>
#include <iterator>     // std::iterator, std::input_iterator_tag
#include <algorithm>

using namespace std;

struct Tuple;
struct RefTuple;
#define TUPLE_COMMON_FUNC(C, D, E, F)            \
    C##::C## (Tuple& t) ##D                        \
    C##::C## (RefTuple& t) ##D                    \
    void C##::operator = (Tuple& t) ##E        \
    void C##::operator = (RefTuple& t) ##E    \
    bool C##::operator < (const Tuple& t) const ##F        \
    bool C##::operator < (const RefTuple& t) const ##F
#define ASSIGN_1    : i(t.i), j(t.j), s(t.s) {}
#define ASSIGN_2    { i = t.i; j = t.j; s = t.s; }
#define SORT_CRITERIA \
    return (j < t.j) || (j == t.j && (i < t.i));
struct Tuple {
    int i, j, s;
    TUPLE_COMMON_FUNC(Tuple, ; , ; , ;)
};
struct RefTuple {
    int &i, &j, &s;
    RefTuple(int &x, int &y, int &z): i(x), j(y), s(z) {}
    TUPLE_COMMON_FUNC(RefTuple, ; , ; , ;)
};
TUPLE_COMMON_FUNC(Tuple, ASSIGN_1, ASSIGN_2, {SORT_CRITERIA})
TUPLE_COMMON_FUNC(RefTuple, ASSIGN_1, ASSIGN_2, {SORT_CRITERIA})

void swap(RefTuple& t1, RefTuple& t2) {
    t1.i ^= t2.i; t2.i ^= t1.i; t1.i ^= t2.i;
    t1.j ^= t2.j; t2.j ^= t1.j; t1.j ^= t2.j;
    t1.s ^= t2.s; t2.s ^= t1.s; t1.s ^= t2.s;
}

class IterTuple : public iterator<random_access_iterator_tag, Tuple> {
    int *i, *j, *s, idx;
public:
    IterTuple(int* x, int*y, int* z, int l) : i(x), j(y), s(z), idx(l) {}
    IterTuple(const IterTuple& e) : i(e.i), j(e.j), s(e.s), idx(e.idx) {}
    RefTuple operator*() { return RefTuple(i[idx], j[idx], s[idx]);  }
    IterTuple& operator ++ () { idx++; return *this; }
    IterTuple& operator -- () { idx--; return *this; }
    IterTuple operator ++ (int) { IterTuple tmp(*this); idx++; return tmp; }
    IterTuple operator -- (int) { IterTuple tmp(*this); idx--; return tmp; }
    int operator - (IterTuple& rhs) { return idx - rhs.idx;    }
    IterTuple operator + (int n) { IterTuple tmp(*this); tmp.idx += n; return tmp; }
    IterTuple operator - (int n) { IterTuple tmp(*this); tmp.idx -= n; return tmp; }
    bool operator==(const IterTuple& rhs) {        return idx == rhs.idx;    }
    bool operator!=(const IterTuple& rhs) {     return idx != rhs.idx;  }
    bool operator<(IterTuple& rhs) {     return idx < rhs.idx;   }
};

int Ai[10] = {0, 0, 2, 3, 2, 4, 1, 1, 4, 2};
int Aj[10] = {0, 2, 3, 4, 4, 4, 0, 1, 0, 2};
int Ax[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

int main () {
    IterTuple from(Ai, Aj, Ax, 0);
    IterTuple until(Ai, Aj, Ax, 10);

    sort(from, until);

    for (IterTuple it = from; it != until; it++)
        cout << (*it).i << ' ' << (*it).j << ' ' << (*it).s << '\n';

    return 0;
}
Jeffrey Fung
  • 101
  • 1
  • 4
-1

I believe that writting your own QuickSort variant is simplier and the result will perform better then mapping custom iterators or array with indices.

QuickSort is not stable sort.

template<class A, class B> void QuickSort2Desc(A a[], B b[], int l, int r)
{               
    int i = l;
    int j = r;
    A v = a[(l + r) / 2];
    do {
        while (a[i] > v)i++;
        while (v > a[j])j--;
        if (i <= j)
        {
            std::swap(a[i], a[j]);
            std::swap(b[i], b[j]);          
            i++;
            j--;
        };
    } while (i <= j);
    if (l < j)QuickSort2Desc(a, b, l, j);
    if (i < r)QuickSort2Desc(a, b, i, r);
}
Tomas Kubes
  • 23,880
  • 18
  • 111
  • 148
  • 3
    This code doesn't seem like it could possibly be correct, because it says `if (i <= j) { std::swap(a[i], a[j]) ...` That would lead to swapping `a[i]` with itself whenever `i == j`. So I think "write your own quicksort" is possibly the best solution, but "cut and paste this specific code" is definitely *not* a good idea as of 2019-01-31. – Quuxplusone Feb 01 '19 at 02:02