69

For example, if I have these arrays:

var name = ["Bob","Tom","Larry"];
var age =  ["10", "20", "30"];

And I use name.sort() the order of the "name" array becomes:

var name = ["Bob","Larry","Tom"];

But, how can I sort the "name" array and have the "age" array keep the same order? Like this:

var name = ["Bob","Larry","Tom"];
var age =  ["10", "30", "20"];
supercoolville
  • 8,636
  • 20
  • 52
  • 69
  • 1
    Is there a chance to change the way you store the data? To nested arrays, or to objects – zerkms Jul 16 '12 at 06:44
  • 1
    I'm going to make a good guess and say there is no way to do this with the code you've currently posted. Each array doesn't store enough information within it. The name array at any given point doesn't know if it's been changed, there is no property that holds a sort order or similar. – The Muffin Man Jul 16 '12 at 06:48
  • possible duplicate of [JavaScript: manipulate two Arrays](http://stackoverflow.com/questions/8862220/javascript-manipulate-two-arrays) – Shadow The GPT Wizard Jul 16 '12 at 06:50
  • 1
    I think the best way to do this is to create a "Person" class which would contain name and age. The data you have is interconnected, it's not reasonable to have it stored in two independent containers. – Alehar Jul 16 '12 at 06:53

12 Answers12

91

You can sort the existing arrays, or reorganize the data.

Method 1: To use the existing arrays, you can combine, sort, and separate them: (Assuming equal length arrays)

var names = ["Bob","Tom","Larry"];
var ages =  ["10", "20", "30"];

//1) combine the arrays:
var list = [];
for (var j = 0; j < names.length; j++) 
    list.push({'name': names[j], 'age': ages[j]});

//2) sort:
list.sort(function(a, b) {
    return ((a.name < b.name) ? -1 : ((a.name == b.name) ? 0 : 1));
    //Sort could be modified to, for example, sort on the age 
    // if the name is the same. See Bonus section below
});

//3) separate them back out:
for (var k = 0; k < list.length; k++) {
    names[k] = list[k].name;
    ages[k] = list[k].age;
}

This has the advantage of not relying on string parsing techniques, and could be used on any number of arrays that need to be sorted together.

Method 2: Or you can reorganize the data a bit, and just sort a collection of objects:

var list = [
    {name: "Bob", age: 10}, 
    {name: "Tom", age: 20},
    {name: "Larry", age: 30}
    ];

list.sort(function(a, b) {
    return ((a.name < b.name) ? -1 : ((a.name == b.name) ? 0 : 1));
});

for (var i = 0; i<list.length; i++) {
    alert(list[i].name + ", " + list[i].age);
}
​

For the comparisons,-1 means lower index, 0 means equal, and 1 means higher index. And it is worth noting that sort() actually changes the underlying array.

Also worth noting, method 2 is more efficient as you do not have to loop through the entire list twice in addition to the sort.

http://jsfiddle.net/ghBn7/38/

Bonus Here is a generic sort method that takes one or more property names.

function sort_by_property(list, property_name_list) {
  list.sort((a, b) => {
    for (var p = 0; p < property_name_list.length; p++) {
      prop = property_name_list[p];
      if (a[prop] < b[prop]) {
        return -1;
      } else if (a[prop] !== a[prop]) {
        return 1;
      }
    }
    return 0;
  });
}

Usage:

var list = [
        {name: "Bob", age: 10}, 
        {name: "Tom", age: 20},
        {name: "Larry", age: 30},
        {name: "Larry", age: 25}
    ];

sort_by_property(list, ["name", "age"]);

for (var i = 0; i<list.length; i++) {
    console.log(list[i].name + ", " + list[i].age);
}

Output:

  • Bob, 10
  • Larry, 25
  • Larry, 30
  • Tom, 20
jwatts1980
  • 7,254
  • 2
  • 28
  • 44
  • I always forget how to do this in javascript. Serverside scripting languages just have this built in... too spoiled I guess. – AlexMA Mar 14 '13 at 20:36
  • 2
    good answer,exactly what I need. Thanks. Here sort based on `list.sort(function (a, b) { return parseInt(a.age) - parseInt(b.age) })` – Zaid Mirza Sep 22 '16 at 05:12
  • 1
    @TomášZato Sorting two arrays in parallel is not native in JS. This is a pretty simple solution to that, but if you find a simpler one, I'd love to see it! :-) – jwatts1980 Nov 11 '16 at 01:28
12

You could get the indices of name array using Array.from(name.keys()) or [...name.keys()]. Sort the indices based on their value. Then use map to get the value for the corresponding indices in any number of related arrays

const indices = Array.from(name.keys())
indices.sort( (a,b) => name[a].localeCompare(name[b]) )

const sortedName = indices.map(i => name[i]),
const sortedAge = indices.map(i => age[i])

Here's a snippet:

const name = ["Bob","Tom","Larry"],
      age =  ["10", "20", "30"],
      
      indices = Array.from(name.keys())
                     .sort( (a,b) => name[a].localeCompare(name[b]) ),
                     
      sortedName = indices.map(i => name[i]),
      sortedAge = indices.map(i => age[i])

console.log(indices)
console.log(sortedName)
console.log(sortedAge)
adiga
  • 34,372
  • 9
  • 61
  • 83
6

This solution (my work) sorts multiple arrays, without transforming the data to an intermediary structure, and works on large arrays efficiently. It allows passing arrays as a list, or object, and supports a custom compareFunction.

Usage:

let people = ["john", "benny", "sally", "george"];
let peopleIds = [10, 20, 30, 40];

sortArrays([people, peopleIds]);
[["benny", "george", "john", "sally"], [20, 40, 10, 30]] // output

sortArrays({people, peopleIds});
{"people": ["benny", "george", "john", "sally"], "peopleIds": [20, 40, 10, 30]} // output

Algorithm:

  • Create a list of indexes of the main array (sortableArray)
  • Sort the indexes with a custom compareFunction that compares the values, looked up with the index
  • For each input array, map each index, in order, to its value

Implementation:

/**
 *  Sorts all arrays together with the first. Pass either a list of arrays, or a map. Any key is accepted.
 *     Array|Object arrays               [sortableArray, ...otherArrays]; {sortableArray: [], secondaryArray: [], ...}
 *     Function comparator(?,?) -> int   optional compareFunction, compatible with Array.sort(compareFunction)
 */
function sortArrays(arrays, comparator = (a, b) => (a < b) ? -1 : (a > b) ? 1 : 0) {
    let arrayKeys = Object.keys(arrays);
    let sortableArray = Object.values(arrays)[0];
    let indexes = Object.keys(sortableArray);
    let sortedIndexes = indexes.sort((a, b) => comparator(sortableArray[a], sortableArray[b]));

    let sortByIndexes = (array, sortedIndexes) => sortedIndexes.map(sortedIndex => array[sortedIndex]);

    if (Array.isArray(arrays)) {
        return arrayKeys.map(arrayIndex => sortByIndexes(arrays[arrayIndex], sortedIndexes));
    } else {
        let sortedArrays = {};
        arrayKeys.forEach((arrayKey) => {
            sortedArrays[arrayKey] = sortByIndexes(arrays[arrayKey], sortedIndexes);
        });
        return sortedArrays;
    }
}

See also https://gist.github.com/boukeversteegh/3219ffb912ac6ef7282b1f5ce7a379ad

Bouke Versteegh
  • 4,097
  • 1
  • 39
  • 35
  • Hi, what is the complexity?(Both: Time & Space) – Arash Dec 23 '20 at 07:40
  • For N arrays it will do N+1 sorts. This algorithm could be optimized to [1 Sort + N loops] by not sorting the sub-arrays by index, but instead looping over each subarray and copying out the correct element using sortedIndexes. I don't know if Object.values and Object.keys are references or copies. I guess the memory usage would be some low multiple of the input array (between 2-4) – Bouke Versteegh Dec 23 '20 at 15:08
3

If performance matters, there is sort-ids package for that purpose:

var sortIds = require('sort-ids')
var reorder = require('array-rearrange')

var name = ["Bob","Larry","Tom"];
var age =  [30, 20, 10];

var ids = sortIds(age)
reorder(age, ids)
reorder(name, ids)

That is ~5 times faster than the comparator function.

dy_
  • 6,462
  • 5
  • 24
  • 30
  • 1
    faster than what "comparator function"? fastest is `(a, b) => (a - b)`, 6x faster than native `.sort()` https://jsbench.github.io/#c90f8f13202ce5498f811ff79fb281ed – milahu Dec 17 '20 at 20:14
2

It is very similar to jwatts1980's answer (Update 2). Consider reading Sorting with map.

name.map(function (v, i) {
    return {
        value1  : v,
        value2  : age[i]
    };
}).sort(function (a, b) {
    return ((a.value1 < b.value1) ? -1 : ((a.value1 == b.value1) ? 0 : 1));
}).forEach(function (v, i) {
    name[i] = v.value1;
    age[i] = v.value2;
});
Community
  • 1
  • 1
w35l3y
  • 8,613
  • 3
  • 39
  • 51
1

You are trying to sort 2 independet arrays by only calling sort() on one of them.

One way of achieving this would be writing your own sorting methd which would take care of this, meaning when it swaps 2 elements in-place in the "original" array, it should swap 2 elements in-place in the "attribute" array.

Here is a pseudocode on how you might try it.

function mySort(originals, attributes) {
    // Start of your sorting code here
        swap(originals, i, j);
        swap(attributes, i, j);
    // Rest of your sorting code here
}
ioreskovic
  • 5,531
  • 5
  • 39
  • 70
  • 1
    I was actually thinking the same thing, however it seems damn unpractical when there are much easier ways to do this if the data is stored differently. – The Muffin Man Jul 16 '12 at 06:50
1

inspired from @jwatts1980's answer, and @Alexander's answer here I merged both answer's into a quick and dirty solution; The main array is the one to be sorted, the rest just follows its indexes

NOTE: Not very efficient for very very large arrays

 /* @sort argument is the array that has the values to sort
   @followers argument is an array of arrays which are all same length of 'sort'
   all will be sorted accordingly
   example:

   sortMutipleArrays(
         [0, 6, 7, 8, 3, 4, 9], 
         [ ["zr", "sx", "sv", "et", "th", "fr", "nn"], 
           ["zero", "six", "seven", "eight", "three", "four", "nine"] 
         ]
   );

  // Will return

  {  
     sorted: [0, 3, 4, 6, 7, 8, 9], 
     followed: [
      ["zr", th, "fr", "sx", "sv", "et", "nn"], 
      ["zero", "three", "four", "six", "seven", "eight", "nine"]
     ]
   }
 */

You probably want to change the method signature/return structure, but that should be easy though. I did it this way because I needed it

var sortMultipleArrays = function (sort, followers) {
  var index = this.getSortedIndex(sort)
    , followed = [];
  followers.unshift(sort);
  followers.forEach(function(arr){
    var _arr = [];
    for(var i = 0; i < arr.length; i++)
      _arr[i] = arr[index[i]];
    followed.push(_arr);
  });
  var result =  {sorted: followed[0]};
  followed.shift();
  result.followed = followed;
  return result;
};

var getSortedIndex = function (arr) {
  var index = [];
  for (var i = 0; i < arr.length; i++) {
    index.push(i);
  }
  index = index.sort((function(arr){
  /* this will sort ints in descending order, change it based on your needs */
    return function (a, b) {return ((arr[a] > arr[b]) ? -1 : ((arr[a] < arr[b]) ? 1 : 0));
    };
  })(arr));
  return index;
};
Community
  • 1
  • 1
bentael
  • 1,987
  • 2
  • 21
  • 27
1

I was looking for something more generic and functional than the current answers.

Here's what I came up with: an es6 implementation (with no mutations!) that lets you sort as many arrays as you want given a "source" array

/**
 * Given multiple arrays of the same length, sort one (the "source" array), and
 * sort all other arrays to reorder the same way the source array does.
 * 
 * Usage:
 * 
 * sortMultipleArrays( objectWithArrays, sortFunctionToApplyToSource )
 * 
 * sortMultipleArrays(
 *   {
 *    source: [...],
 *    other1: [...],
 *    other2: [...]
 *   },
 *   (a, b) => { return a - b })
 * )
 * 
 * Returns:
 *   {
 *      source: [..sorted source array]
 *      other1: [...other1 sorted in same order as source],
 *      other2: [...other2 sorted in same order as source]
 *   }
 */
export function sortMultipleArrays( namedArrays, sortFn ) {
    const { source } = namedArrays;
    if( !source ) {
        throw new Error('You must pass in an object containing a key named "source" pointing to an array');
    }

    const arrayNames = Object.keys( namedArrays );

    // First build an array combining all arrays into one, eg
    // [{ source: 'source1', other: 'other1' }, { source: 'source2', other: 'other2' } ...]
    return source.map(( value, index ) =>
        arrayNames.reduce((memo, name) => ({
            ...memo,
            [ name ]: namedArrays[ name ][ index ]
        }), {})
    )
    // Then have user defined sort function sort the single array, but only
    // pass in the source value
    .sort(( a, b ) => sortFn( a.source, b.source ))
    // Then turn the source array back into an object with the values being the
    // sorted arrays, eg
    // { source: [ 'source1', 'source2' ], other: [ 'other1', 'other2' ] ... }
    .reduce(( memo, group ) =>
        arrayNames.reduce((ongoingMemo, arrayName) => ({
            ...ongoingMemo,
            [ arrayName ]: [
                ...( ongoingMemo[ arrayName ] || [] ),
                group[ arrayName ]
            ]
        }), memo), {});
}
Andy Ray
  • 30,372
  • 14
  • 101
  • 138
  • Hi @Andy - I was excited when I found this answer because it seems to do exactly what I need. However... It sorts the source array, but the other two seem to come out different every time. – Izzi Dec 01 '21 at 03:56
0

You could append the original index of each member to the value, sort the array, then remove the index and use it to re-order the other array. It will only work where the contents are strings or can be converted to and from strings successfuly.

Another solution is keep a copy of the original array, then after sorting, find where each member is now and adjust the other array appropriately.

RobG
  • 142,382
  • 31
  • 172
  • 209
0

I was having the same issue and came up with this incredibly simple solution. First combine the associated ellements into strings in a seperate array then use parseInt in your sort comparison function like this:

<html>
<body>
<div id="outPut"></div>
<script>
var theNums = [13,12,14];
var theStrs = ["a","b","c"];
var theCombine = [];

for (var x in theNums)
{
    theCombine[x] = theNums[x] + "," + theStrs;
}

var theSorted = theAr.sort(function(a,b)
{
    var c = parseInt(a,10);
    var d = parseInt(b,10);
    return c-d;
});
document.getElementById("outPut").innerHTML = theS;
</script>
</body>
</html>
Tunaki
  • 132,869
  • 46
  • 340
  • 423
-1

How about:

var names = ["Bob","Tom","Larry"];
var ages =  ["10", "20", "30"];
var n = names.slice(0).sort()
var a = [];
for (x in n)
{
i = names.indexOf(n[x]);
a.push(ages[i]);
names[i] = null;
}
names = n
ages = a
  • Not very useful if you have duplicates in your array because indexOf will take the first one. – Johann Aug 25 '20 at 18:13
-2

Simplest explantion is the best, merge the arrays, and then extract after sorting: create an array

name_age=["bob@10","Tom@20","Larry@30"];

sort the array as before, then extract the name and the age, you can use @ to reconise where name ends and age begins. Maybe not a method for the purist, but I have the same issue and this my approach.

Mathias Müller
  • 22,203
  • 13
  • 58
  • 75