Skip to content

Instantly share code, notes, and snippets.

@ecarter
Created December 2, 2011 15:40
Show Gist options
  • Save ecarter/1423674 to your computer and use it in GitHub Desktop.
Save ecarter/1423674 to your computer and use it in GitHub Desktop.
Order an array of objects based on another array order
/**
* Sort array of objects based on another array
*/
function mapOrder (array, order, key) {
array.sort( function (a, b) {
var A = a[key], B = b[key];
if (order.indexOf(A) > order.indexOf(B)) {
return 1;
} else {
return -1;
}
});
return array;
};
/**
* Example:
*/
var item_array, item_order, ordered_array;
item_array = [
{ id: 2, label: 'Two' }
, { id: 3, label: 'Three' }
, { id: 5, label: 'Five' }
, { id: 4, label: 'Four' }
, { id: 1, label: 'One'}
];
item_order = [1,2,3,4,5];
ordered_array = mapOrder(item_array, item_order, 'id');
console.log('Ordered:', JSON.stringify(ordered_array));
@anchal20
Copy link

@abdekalder: It is possible, just a small tweek when sending the params to mapOrder function. You can refer my fiddle for the approach.
I hope it is helpful.

Thanks,
Anchal

@ismailbayram
Copy link

awesome man !

@hibangun
Copy link

what if, let's say

item_array = [ 
  { id: 2, label: 'Two' }
, { id: 3, label: 'Three' }
, { id: 5, label: 'Five' }
, { id: 4, label: 'Four' }
, { id: 1, label: 'One'}
, { id: 6, label: 'Six'} // new object that doesn't have in item_order
];

item_order = [1,2,3,4,5];

The result would be

[ 
  { id: 6, label: 'Six'} // new object is on top
, { id: 1, label: 'One' }
, { id: 2, label: 'Two' }
, { id: 3, label: 'Three' }
, { id: 4, label: 'Four' }
, { id: 5, label: 'Five'}
];

how can we move object that not found in item_order eg:{id: 6, label: 'Six') to be on the bottom instead?

@ecarter @gy0857478 @abdekalder @anchal20 @ismailbayram

@kiloc
Copy link

kiloc commented Oct 31, 2018

it's great

@rikardocorp
Copy link

rikardocorp commented Nov 20, 2018

if you want to insert new elements at the end of the array

Change the line 11:
if (order.indexOf(A) > order.indexOf(B))
by:
if (order.indexOf(A) > order.indexOf(B) || order.indexOf(A) === -1 || order.indexOf(B) === -1)

@ecarter @hibangun

@PrnOnii
Copy link

PrnOnii commented Mar 7, 2019

@rikardocorp
I think you made a slight mistake.
Your change does not add new elements at the END of the array but rather at the BEGINNING.

If you really want to add at the END of the array, you change lines 11 to 15 :
if (order.indexOf(A) > order.indexOf(B)) {
return 1;
} else {
return -1;
}
To this :
if (order.indexOf(A) < order.indexOf(B) || order.indexOf(A) === -1 || order.indexOf(B) === -1) {
return -1;
} else {
return 1;
}

@JD-V
Copy link

JD-V commented Mar 11, 2019

@rikardocorp
None of above comments were useful in my case

so I tweaked the logic a bit like this and it worked!!
`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;`

@MontoyaAndres
Copy link

It works perfectly with uuid's 😄

@fa-901
Copy link

fa-901 commented Mar 28, 2019

Thank you. You are my saviour

@pmsoltani
Copy link

pmsoltani commented Oct 25, 2019

Just what I was looking for. Thanks. I made a new version from it:

const mapOrder = (array, order, key) => {
  array.sort((a, b) => {
    const A = a[key];
    const B = b[key];
    return order.indexOf(A) > order.indexOf(B) ? 1 : -1;
  });
  return array;
};

@rexl2018
Copy link

rexl2018 commented Feb 14, 2020

not good in time complexity as O(n*n*log(n)).

Here is another version of O(n):

function mapOrder(array, order, key) {
    var map = new Map();
    var index = 0;
    var tmp;
    if(!array || !order || array.length!==order.length)
      return array;
    array.forEach(function(it) {
        map.set(it[key], index++);
    });
    order.forEach(function(it) {
      if(map.get(it) === undefined) return array;
    });
    index--;
    for (; index >= 0; index--) {
        if (array[index][key] !== order[index]) {
            tmp = array[index];
            array[index] = array[map.get(order[index])];
            array[map.get(order[index])] = tmp;
            map.set(tmp[key], map.get(order[index]));
        }
    }
    return array;
}

Correct me if I were wrong.

@lokhmakov
Copy link

function mapOrder(a, order, key) {
  const map = order.reduce((r, v, i) => ((r[v] = i), r), {})
  return a.sort((a, b) => map[a[key]] - map[b[key]])
}

const source = [
  {id: 2, label: 'Two'},
  {id: 3, label: 'Three'},
  {id: 5, label: 'Five'},
  {id: 4, label: 'Four'},
  {id: 1, label: 'One'},
]

const order = [1, 2, 3, 4, 5]
const result = mapOrder(source, order, `id`)

@PaulSanchez12
Copy link

Just what I was looking for!
Thank you.

@binfask
Copy link

binfask commented Jun 9, 2020

Thank you so much i was looking for this

@sandy912
Copy link

Thank you

@thegirishagarwal
Copy link

Can you sort Order Array according to source id ?

@ssuess
Copy link

ssuess commented Sep 15, 2020

mapOrder (array, myorder, key) {
     var order = myorder.reduce((r, k, i) => (r[k] = i + 1, r), {})
     const theSort = array.sort((a, b) => (order[a[key]] || Infinity) - (order[b[key]] || Infinity))
     return theSort
   },

This will sort based on array and key given and if there are more items in the sorted array than in the myorder array put those unsorted at the end

@vaartio
Copy link

vaartio commented Nov 27, 2020

How about implementing it as a compare function?

const mapOrder = (order, key) => (a, b) => order.indexOf(a[key]) > order.indexOf(b[key]) ? 1 : -1;

This way, you retain the flexibility that Array’s built-in methods provide.

const item_array = [ 
  { id: 2, label: 'Two' },
  { id: 3, label: 'Three' },
  { id: 5, label: 'Five' },
  { id: 4, label: 'Four' },
  { id: 1, label: 'One'},
];
const item_order = [1,2,3,4,5];

item_array
  .filter(item => item.id > 2)
  .sort(mapOrder(item_order, 'id'))
  .pop()

@Sa-meer
Copy link

Sa-meer commented Jan 13, 2021

Thanks a lot this is exactly what I needed.

@NehaAkashDeo
Copy link

Thanks a lot for your help.

@kkoo95
Copy link

kkoo95 commented May 8, 2021

If you want to always add new items at the end, THIS IS THE ONE:

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

if (indA == -1) {
  indA = sortedOrder.length;
}
if (indB == -1) {
  indB = sortedOrder.length;
}

return indA - indB;

@linuxhackr
Copy link

Finally I got what I was looking for. Thanks man.

@linuxhackr
Copy link

How about implementing it as a compare function?

const mapOrder = (order, key) => (a, b) => order.indexOf(a[key]) > order.indexOf(b[key]) ? 1 : -1;

This way, you retain the flexibility that Array’s built-in methods provide.

const item_array = [ 
  { id: 2, label: 'Two' },
  { id: 3, label: 'Three' },
  { id: 5, label: 'Five' },
  { id: 4, label: 'Four' },
  { id: 1, label: 'One'},
];
const item_order = [1,2,3,4,5];

item_array
  .filter(item => item.id > 2)
  .sort(mapOrder(item_order, 'id'))
  .pop()

Thanks to make it easy

@LemonyPie
Copy link

LemonyPie commented Jul 1, 2021

I think it is possible to just subtract indexes

function mapOrder(order, key) {
  return function(a, b) {
    return order.indexOf(a[key]) - order.indexOf(b[key]) // ascending order
  }
}

e.g.

function sortOrder(order) {
    return function(a, b) {
    return order.indexOf(a) - order.indexOf(b)
    }
}

const arr = ['a', 'b', 'c', 'b', 'c', 'a']
const order = ['a', 'b', 'c']

[...arr].sort(sortOrder(order)) // ["a", "a", "b", "b", "c", "c"]

@kkoo95
Copy link

kkoo95 commented Jul 5, 2021

@ArtemeeSenin it doesn't play well with non-existant values. At least for my use case.

with an array like this ['e', 'a', 'b', 'g', 'c', 'h', 'b', null, 'c', 'i', 'a', 'd'],
it gives ['e', 'g', 'h', null, 'i', 'd', 'a', 'a', 'b', 'b', 'c', 'c']

But I would expect to have all unknown stuff at the end, sorted ascendingly. but here it just stack them off in front

@RatherLogical
Copy link

RatherLogical commented Aug 19, 2021

I made this based off of the OP.
This will sort according to your specified order. It will also sort and add unknown items to the end (a feature the original should've included).

/**
 * Order an array of objects by another array.
 * @param  {array} array The array of objects to sort.
 * @param  {array} order The array of property names to order the objects by.
 * @param  {string} property The property name to use as a sorting target.
 */
function mapOrder(array, order, property) {
    let ordered = [], unordered = [];

    // Iterate over each item in the supplied array of objects, separating ordered and unordered objects into their own arrays.
    array.forEach((item) => {
    if (order.indexOf(item[property]) === -1) {
        unordered.push(item);
    } else {
        ordered.push(item);
    }
    });

    // Sort the ordered array.
    ordered.sort((a, b) => {
    a = a[property], b = b[property];

    if (order.indexOf(a) < order.indexOf(b)) {
        return -1;
    } else {
        return 1;
    }
    });

    // Sort the unordered array.
    unordered.sort((a, b) => {
    a = a[property], b = b[property];

    if (a < b) {
        return -1;
    } else if (a > b) {
        return 1;
    } else {
        return 0;
    }
    });

    // Append the sorted, non-ordered array to the sorted, ordered array.
    ordered.push(...unordered);

    return ordered;
}

@TheOneWayTruth
Copy link

TheOneWayTruth commented Oct 8, 2021

why not use

function mapOrder(array, order, key) {
    return array.sort((a, b) => 
        order.indexOf(a[key]) > order.indexOf(b[key]) ? 1 : -1
    );
}

@lokhmakov
Copy link

lokhmakov commented Oct 9, 2021

why not use

export function mapOrder(array, order, key) {
    array.sort((a, b) => {
        order.indexOf(a[key]) > order.indexOf(b[key]) ? 1 : -1;
    });
    return array;
}

@TheOneWayTruth u just copy author solution and forgot about return of order comparison. U solution does not work.

And u can return result of array.sort without last return array.

const mapOrder = (array, order, key) =>
  array.sort((a, b) => order.indexOf(a[key]) > order.indexOf(b[key]) ? 1 : -1)

But it still topic starter solution.

@prakashmallow
Copy link

Finally I got what I was looking for. Thanks @kkoo95

@sujinleeme
Copy link

sujinleeme commented Mar 20, 2023

Here is the code If you want to move unmated rest of items at the end of array.

export const mapOrder = <T>(array: T[], order: any[], key: keyof T) => {
  return array.sort((a, b) => {
    let weightA = 0;
    let weightB = 0;

    if (!order.includes(a[key])) {
      weightA += 100;
    }

    if (!order.includes(b[key])) {
      weightB += 100;
    }

    return order.indexOf(a[key]) + weightA - (order.indexOf(b[key]) + weightB);
  });
};

Test Case

 const item_array = [
    { id: 2, label: "Two" },
    { id: 3, label: "Three" },
    { id: 5, label: "Five" },
    { id: 4, label: "Four" },
    { id: 1, label: "One" }
  ];

  const item_order = [1, 5];

  const ordered_array = mapOrder(item_array, item_order, "id");
  
console.log("Ordered:", JSON.stringify(ordered_array));
// Ordered: [{"id":1,"label":"One"},{"id":5,"label":"Five"},{"id":2,"label":"Two"},{"id":3,"label":"Three"},{"id":4,"label":"Four"}] 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment