user1448892
user1448892

Reputation: 3771

Sort a JavaScript array based on another array

Is it possible to sort and rearrange an array that looks like the following?

itemsArray = [
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]

to match the arrangement of this array:

sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ]

Unfortunately, I don’t have any IDs to keep track on. I would need to priority the items-array to match the sortingArr as close as possible.

Here is the output I’m looking for:

itemsArray = [
    ['Bob', 'b'],
    ['Jason', 'c'],
    ['Henry', 'b'],
    ['Thomas', 'b']
    ['Anne', 'a'],
    ['Andrew', 'd'],
]

How can this be done?

Upvotes: 376

Views: 315047

Answers (28)

Fawaz Ahmed
Fawaz Ahmed

Reputation: 1594

let itemsArray = [
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]

let sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ]


function sortArray(sortedArr, unsortedArr, unsortedArrToSort){

    let holderArr = []
    let invertedIndex = {}
    // initialize
    unsortedArr.forEach((e)=>invertedIndex[e] = {indices:[],index:0})
    // create inverted index to save time
    unsortedArr.forEach((e,i)=>invertedIndex[e]['indices'].push(i))

    for(let sortValue of sortedArr){

        let currIndex = invertedIndex?.[sortValue]?.['index']

        let searchedIndex = invertedIndex?.[sortValue]?.['indices']?.[currIndex]

        if(searchedIndex===undefined)
        continue

        holderArr.push(unsortedArrToSort[searchedIndex])

        invertedIndex[sortValue]['index']+=1
    }

    return holderArr

}

console.log(sortArray(sortingArr, itemsArray.map(e=>e[1]), itemsArray))

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386868

This approach features a sorting algorithm where for each item of the order array, the data is searched for a corresponding item and this item is swapped by the left neighbor until the item is at the right position.

 a   b   b   d   c   b   data
<b>  a   b   d   c   b   right order of <>
 b  <c>  a   b   d   b
 b   c  <b>  a   d   b
 b   c   b  <b>  a   d
 b   c   b   b  <a>  d
 b   c   b   b   a  <d>

const
    data = [['Anne', 'a'], ['Bob', 'b'], ['Henry', 'b'], ['Andrew', 'd'], ['Jason', 'c'], ['Thomas', 'b']],
    order = ['b', 'c', 'b', 'b', 'a', 'd'];

let i = 0;

for (const o of order) {
    let j = i;
    while (data[j][1] !== o) j++;
    while (i !== j) {
        [data[j], data[j - 1]] = [data[j - 1], data[j]];
        j--;
    }
    i++;
}

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

user2521295
user2521295

Reputation: 901

In case you get here needing to do this with an array of objects, here is an adaptation of Durgpal Singh's awesome answer:

const itemsArray = [
  { name: 'Anne', id: 'a' },
  { name: 'Bob', id: 'b' },
  { name: 'Henry', id: 'b' },
  { name: 'Andrew', id: 'd' },
  { name: 'Jason', id: 'c' },
  { name: 'Thomas', id: 'b' }
]

const sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ]

Object.keys(itemsArray).sort((a, b) => {
  return sortingArr.indexOf(itemsArray[a].id) - sortingArr.indexOf(itemsArray[b].id);
})

Upvotes: 9

niko
niko

Reputation: 116

With a numerical sortingArr:

itemsArray.sort(function(a, b){  
  return sortingArr[itemsArray.indexOf(a)] - sortingArr[itemsArray.indexOf(b)];
});

Upvotes: 0

JD-V
JD-V

Reputation: 3716

Use:

let sortedOrder = [ 'b', 'c', 'b', 'b' ]
let itemsArray = [ 
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]
a.itemsArray(function (a, b) {
    let A = a[1]
    let B = b[1]

    if(A != undefined)
        A = A.toLowerCase()

    if(B != undefined)
        B = B.toLowerCase()

    let indA = sortedOrder.indexOf(A)
    let indB = sortedOrder.indexOf(B)

    if (indA == -1 )
        indA = sortedOrder.length-1
    if( indB == -1)
        indB = sortedOrder.length-1

    if (indA < indB ) {
        return -1;
    } else if (indA > indB) {
        return 1;
    }
    return 0;
})

This solution will append the objects at the end if the sorting key is not present in the reference array.

Upvotes: 3

Luca Di Liello
Luca Di Liello

Reputation: 1643

Something like:

//array1: array of elements to be sorted
//array2: array with the indexes

array1 = array2.map((object, i) => array1[object]);

The map function may not be available in all versions of JavaScript.

Upvotes: 21

Sushruth
Sushruth

Reputation: 1134

You could also use some modified version of the code below in ES6 style. This code is for arrays like:

var arrayToBeSorted = [1,2,3,4,5];
var arrayWithReferenceOrder = [3,5,8,9];

The actual operation:

arrayToBeSorted = arrayWithReferenceOrder.filter(v => arrayToBeSorted.includes(v));

The actual operation in ES5:

arrayToBeSorted = arrayWithReferenceOrder.filter(function(v) {
    return arrayToBeSorted.includes(v);
});

Should result in arrayToBeSorted = [3,5]

It does not destroy the reference array.

Upvotes: 23

Luca Rainone
Luca Rainone

Reputation: 16468

This should work:

var i,search, itemsArraySorted = [];
while(sortingArr.length) {
    search = sortingArr.shift();
    for(i = 0; i<itemsArray.length; i++) {
        if(itemsArray[i][1] == search) {
            itemsArraySorted.push(itemsArray[i]);
            break;
        }
    } 
}

itemsArray = itemsArraySorted;

Upvotes: 0

georg
georg

Reputation: 215049

Something like:

items = [
    ['Anne', 'a'],
    ['Bob', 'b'],
    ['Henry', 'b'],
    ['Andrew', 'd'],
    ['Jason', 'c'],
    ['Thomas', 'b']
]

sorting = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
result = []

sorting.forEach(function(key) {
    var found = false;
    items = items.filter(function(item) {
        if(!found && item[1] == key) {
            result.push(item);
            found = true;
            return false;
        } else
            return true;
    })
})

result.forEach(function(item) {
    document.writeln(item[0]) /// Bob Jason Henry Thomas Andrew
})

Here's shorter code, but it destroys the sorting array:

result = items.map(function(item) {
    var n = sorting.indexOf(item[1]);
    sorting[n] = '';
    return [n, item]
}).sort().map(function(j) { return j[1] })

Upvotes: 94

Gadde
Gadde

Reputation: 1481

You can do something like this:

function getSorted(itemsArray, sortingArr) {
  var result = [];
  for(var i=0; i<arr.length; i++) {
    result[i] = arr[sortArr[i]];
  }
  return result;
}

You can test it out here.

Note: this assumes the arrays you pass in are equal in size. You'd need to add some additional checks if this may not be the case.

Refer to this link.

Reference

Upvotes: -5

Can Rau
Can Rau

Reputation: 3819

ES6

const arrayMap = itemsArray.reduce(
  (accumulator, currentValue) => ({
    ...accumulator,
    [currentValue[1]]: currentValue,
  }),
  {}
);
const result = sortingArr.map(key => arrayMap[key]);

More examples with different input arrays

Upvotes: 12

Bullsized
Bullsized

Reputation: 605

I hope that I am helping someone, but if you are trying to sort an array of objects by another array on the first array's key, for example, you want to sort this array of objects:

const foo = [
  {name: 'currency-question', key: 'value'},
  {name: 'phone-question', key: 'value'},
  {name: 'date-question', key: 'value'},
  {name: 'text-question', key: 'value'}
];        

by this array:

const bar = ['text-question', 'phone-question', 'currency-question', 'date-question'];

you can do so by:

foo.sort((a, b) => bar.indexOf(a.name) - bar.indexOf(b.name));

Upvotes: 6

Durgpal Singh
Durgpal Singh

Reputation: 11983

One-Line answer.

itemsArray.sort(function(a, b){  
  return sortingArr.indexOf(a) - sortingArr.indexOf(b);
});

Or even shorter:

itemsArray.sort((a, b) => sortingArr.indexOf(a) - sortingArr.indexOf(b));

Upvotes: 649

Howard
Howard

Reputation: 499

This seems to work for me:

var outputArray=['10','6','8','10','4','6','2','10','4','0','2','10','0'];
var template=['0','2','4','6','8','10'];
var temp=[];

for(i=0;i<template.length;i++) {
  for(x=0;x<outputArray.length;x++){
    if(template[i] == outputArray[x]) temp.push(outputArray[x])
  };
}

outputArray = temp;
alert(outputArray)

Upvotes: 0

Pacuraru Daniel
Pacuraru Daniel

Reputation: 1205

  const result = sortingArr.map((i) => {
    const pos = itemsArray.findIndex(j => j[1] === i);
    const item = itemsArray[pos];
    itemsArray.splice(pos, 1);
    return item;
  });

Upvotes: 1

integrater
integrater

Reputation: 77

this.arrToBeSorted =  this.arrToBeSorted.sort(function(a, b){  
  return uppthis.sorrtingByArray.findIndex(x => x.Id == a.ByPramaeterSorted) - uppthis.sorrtingByArray.findIndex(x => x.Id == b.ByPramaeterSorted);
});

Upvotes: -1

Debabrata Nayak
Debabrata Nayak

Reputation: 455

function sortFunc(a, b) {
  var sortingArr = ["A", "B", "C"];
  return sortingArr.indexOf(a.type) - sortingArr.indexOf(b.type);
}

const itemsArray = [
  {
    type: "A",
  },
  {
    type: "C",
  },
  {
    type: "B",
  },
];
console.log(itemsArray);
itemsArray.sort(sortFunc);
console.log(itemsArray);

Upvotes: 19

El.
El.

Reputation: 1245

This is what I was looking for and I did for sorting an Array of Arrays based on another Array:

It's On^3 and might not be the best practice(ES6)

function sortArray(arr, arr1){
      return arr.map(item => {
        let a = [];
        for(let i=0; i< arr1.length; i++){
          for (const el of item) {
            if(el == arr1[i]){
              a.push(el);
            }   
            }
          }
          return a;
      });
    }
    
    const arr1 = ['fname', 'city', 'name'];
  const arr = [['fname', 'city', 'name'],
  ['fname', 'city', 'name', 'name', 'city','fname']];
  console.log(sortArray(arr,arr1));
It might help someone

Upvotes: 4

Holger Tidemand
Holger Tidemand

Reputation: 41

You could try this method.

const sortListByRanking = (rankingList, listToSort) => {
  let result = []

  for (let id of rankingList) {
    for (let item of listToSort) {
      if (item && item[1] === id) {
        result.push(item)
      }
    }
  }

  return result
}

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386868

For getting a new ordered array, you could take a Map and collect all items with the wanted key in an array and map the wanted ordered keys by taking sifted element of the wanted group.

var itemsArray = [['Anne', 'a'], ['Bob', 'b'], ['Henry', 'b'], ['Andrew', 'd'], ['Jason', 'c'], ['Thomas', 'b']],
    sortingArr = [ 'b', 'c', 'b', 'b', 'a', 'd' ],
    map = itemsArray.reduce((m, a) => m.set(a[1], (m.get(a[1]) || []).concat([a])), new Map),
    result = sortingArr.map(k => (map.get(k) || []).shift());

console.log(result);

Upvotes: 6

Harshal Patil
Harshal Patil

Reputation: 41

let a = ['A', 'B', 'C' ]

let b = [3, 2, 1]

let c = [1.0, 5.0, 2.0]

// these array can be sorted by sorting order of b

const zip = rows => rows[0].map((_, c) => rows.map(row => row[c]))

const sortBy = (a, b, c) => {
  const zippedArray = zip([a, b, c])
  const sortedZipped = zippedArray.sort((x, y) => x[1] - y[1])

  return zip(sortedZipped)
}

sortBy(a, b, c)

Upvotes: 4

johnny
johnny

Reputation: 19755

I had to do this for a JSON payload I receive from an API, but it wasn't in the order I wanted it.

Array to be the reference array, the one you want the second array sorted by:

var columns = [
    {last_name: "last_name"},
    {first_name: "first_name"},
    {book_description: "book_description"},
    {book_id: "book_id"},
    {book_number: "book_number"},
    {due_date: "due_date"},
    {loaned_out: "loaned_out"}
];

I did these as objects because these will have other properties eventually.

Created array:

 var referenceArray= [];
 for (var key in columns) {
     for (var j in columns[key]){
         referenceArray.push(j);
     }
  }

Used this with result set from database. I don't know how efficient it is but with the few number of columns I used, it worked fine.

result.forEach((element, index, array) => {                            
    var tr = document.createElement('tr');
    for (var i = 0; i < referenceArray.length - 1; i++) {
        var td = document.createElement('td');
        td.innerHTML = element[referenceArray[i]];
        tr.appendChild(td);

    }
    tableBody.appendChild(tr);
}); 

Upvotes: 2

Joe.CK
Joe.CK

Reputation: 349

Use intersection of two arrays.

Ex:

var sortArray = ['a', 'b', 'c',  'd', 'e'];

var arrayToBeSort = ['z', 's', 'b',  'e', 'a'];

_.intersection(sortArray, arrayToBeSort) 

=> ['a', 'b', 'e']

if 'z and 's' are out of range of first array, append it at the end of result

Upvotes: -1

Don McCurdy
Don McCurdy

Reputation: 12000

Case 1: Original Question (No Libraries)

Plenty of other answers that work. :)

Case 2: Original Question (Lodash.js or Underscore.js)

var groups = _.groupBy(itemArray, 1);
var result = _.map(sortArray, function (i) { return groups[i].shift(); });

Case 3: Sort Array1 as if it were Array2

I'm guessing that most people came here looking for an equivalent to PHP's array_multisort (I did) so I thought I'd post that answer as well. There are a couple options:

1. There's an existing JS implementation of array_multisort(). Thanks to @Adnan for pointing it out in the comments. It is pretty large, though.

2. Write your own. (JSFiddle demo)

function refSort (targetData, refData) {
  // Create an array of indices [0, 1, 2, ...N].
  var indices = Object.keys(refData);

  // Sort array of indices according to the reference data.
  indices.sort(function(indexA, indexB) {
    if (refData[indexA] < refData[indexB]) {
      return -1;
    } else if (refData[indexA] > refData[indexB]) {
      return 1;
    }
    return 0;
  });

  // Map array of indices to corresponding values of the target array.
  return indices.map(function(index) {
    return targetData[index];
  });
}

3. Lodash.js or Underscore.js (both popular, smaller libraries that focus on performance) offer helper functions that allow you to do this:

    var result = _.chain(sortArray)
      .pairs()
      .sortBy(1)
      .map(function (i) { return itemArray[i[0]]; })
      .value();

...Which will (1) group the sortArray into [index, value] pairs, (2) sort them by the value (you can also provide a callback here), (3) replace each of the pairs with the item from the itemArray at the index the pair originated from.

Upvotes: 32

Julien Royer
Julien Royer

Reputation: 1429

I would use an intermediary object (itemsMap), thus avoiding quadratic complexity:

function createItemsMap(itemsArray) { // {"a": ["Anne"], "b": ["Bob", "Henry"], …}
  var itemsMap = {};
  for (var i = 0, item; (item = itemsArray[i]); ++i) {
    (itemsMap[item[1]] || (itemsMap[item[1]] = [])).push(item[0]);
  }
  return itemsMap;
}

function sortByKeys(itemsArray, sortingArr) {
  var itemsMap = createItemsMap(itemsArray), result = [];
  for (var i = 0; i < sortingArr.length; ++i) {
    var key = sortingArr[i];
    result.push([itemsMap[key].shift(), key]);
  }
  return result;
}

See http://jsfiddle.net/eUskE/

Upvotes: 8

Mitch Satchwell
Mitch Satchwell

Reputation: 4830

var sortedArray = [];
for(var i=0; i < sortingArr.length; i++) {
    var found = false;
    for(var j=0; j < itemsArray.length && !found; j++) {
        if(itemsArray[j][1] == sortingArr[i]) {
            sortedArray.push(itemsArray[j]);
            itemsArray.splice(j,1);
            found = true;
        }
    }
}

http://jsfiddle.net/s7b2P/

Resulting order: Bob,Jason,Henry,Thomas,Anne,Andrew

Upvotes: 6

David Lewis
David Lewis

Reputation: 830

If you use the native array sort function, you can pass in a custom comparator to be used when sorting the array. The comparator should return a negative number if the first value is less than the second, zero if they're equal, and a positive number if the first value is greater.

So if I understand the example you're giving correctly, you could do something like:

function sortFunc(a, b) {
  var sortingArr = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
  return sortingArr.indexOf(a[1]) - sortingArr.indexOf(b[1]);
}

itemsArray.sort(sortFunc);

Upvotes: 51

toxicate20
toxicate20

Reputation: 5410

Use the $.inArray() method from jQuery. You then could do something like this

var sortingArr = [ 'b', 'c', 'b', 'b', 'c', 'd' ];
var newSortedArray = new Array();

for(var i=sortingArr.length; i--;) {
 var foundIn = $.inArray(sortingArr[i], itemsArray);
 newSortedArray.push(itemsArray[foundIn]);
}

Upvotes: -1

Related Questions