-2

I have a Javascript array of object. Each of them has an integer property type in a range between 1 and 4. Depending on the client, I want to sort the array members by an individual number sequence of this type property. My idea was to define an array with the desired sequence, for example const desiredOrder = [4, 2, 3, 1];. Then my script should sort the array of objects by this list, while keeping the overall order. For example:

var list = [
  {id: 1, type: 2},
  {id: 2, type: 4},
  {id: 3, type: 2},
  {id: 4, type: 1},
  {id: 5, type: 2},
  {id; 6, type: 3}
];

var orderedList = [
  {id: 2, type: 4},
  {id: 1, type: 2},
  {id: 3, type: 2},
  {id: 5, type: 2},
  {id; 6, type: 3},
  {id: 4, type: 1}
];

In my real code, there is no actual id! I've just added that to make clear, that the order should not be changed.

How can I achieve that?

Edit:

Thank you for all your ideas. I've created a JSPerf with all four solutions. It looks like the version with the two nested for loops is the fastest by far. You can test it for yourself:

https://jsperf.com/sort-vs-flatmap/1

4
  • 2
    did you try something already? Commented Sep 16, 2019 at 9:57
  • the order should not be changed What you want is a stable sorting algorithm. Commented Sep 16, 2019 at 10:05
  • @joyBlanks I know how to use the sort method, but not in such a context, where keeping the overall order is a requirement. Commented Sep 16, 2019 at 10:55
  • @PatrickRoberts The solution you've mentioned looks quite complex compared to the answers below. What are the advantages of the linked solution (other than that is adds to the prototype)? Commented Sep 16, 2019 at 11:02

4 Answers 4

3

Using most simple for loop :

let list1 = [4, 2, 3, 1];
let list = [
  { id: 1, type: 2 },
  { id: 2, type: 4 },
  { id: 3, type: 2 },
  { id: 4, type: 1 },
  { id: 5, type: 2 },
  { id: 6, type: 3 }
];
let orderedList = [];
for (let i = 0; i < list1.length; i++) {
  for (let j = 0; j < list.length; j++) {
    if (list1[i] == list[j].type) {
      orderedList.push(list[j]);
    }
  }
}
console.log(orderedList);

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! Looks like a lot of looping through the array, though.
I've added a JSPerf comparison in my question. It looks like your solution is by far the most efficient.
@AndréReichelt happy to know it worked for you :)
3

You can use flatMap and filter for this.

Keep in mind, that there needs to be every possible type in the desiredOrder array, otherwise some items will be lost.

const desiredOrder = [4, 2, 3, 1];

let list = [
  {id: 1, type: 2},
  {id: 2, type: 4},
  {id: 3, type: 2},
  {id: 4, type: 1},
  {id: 5, type: 2},
  {id: 6, type: 3}
];

// Run through all types in the order array
// => flat map will turn all the array results of the filter method into a list of elements
//       eg: [...filterResultArray1, ...filterResultArray2, ]
let result = desiredOrder.flatMap(type =>
  // Get all elements that match the type as an array 
  list.filter(l => l.type == type)
);

console.log(result)

4 Comments

Thank you for your idea. How would it compare to Nina Scholz' solution?
@AndréReichelt I don't know about performance. I do like my solution more, because I personally think it is more readable, but I guess for that everyone needs to decide himself. I just don't like the sort() function in general, as I find it hard to read.
I see your point. MDN states, that flatMap is still an experimental technology without any support in Microsoft Edge yet. I have to consider that.
I've added a JSPerf comparison in my question.
3

Using the index value in the order list, of the type property of the object you can order your sort.

To make a stable sort I have used the index of occurence of the elements in the list so if there is a tie for the sort order of type I will use the original index:

const order = [4, 2, 3, 1];
const list = [
  {id: 1, type: 2},
  {id: 2, type: 4},
  {id: 3, type: 2},
  {id: 4, type: 1},
  {id: 5, type: 2},
  {id: 6, type: 3}
];

//returns a new sorted list
const sortedList = list
      .map((o, idx) => ({idx, ...o}))
      .sort(({type:type1, idx: idx1}, 
             {type:type2, idx: idx2}) => {
             // use the index if there is a tie
           return order.indexOf(type1) - order.indexOf(type2) || idx1 - idx2;
      })
      .map(({idx, ...o}) => o);
console.log(sortedList);

5 Comments

Just one little nitpick: your second map() can be simplified to .map(({ idx, ...o }) => o). I personally think the sort() would just be more readable overall if you didn't use parameter destructuring for it and just did a.type, b.type, a.idx, b.idx but that's more opinion, different strokes for different folks, etc.
@PatrickRoberts That's a wonderful suggestion! Thank you!
This one looks pretty bulky compared to some other solutions. Does it have some (hidden) advantages?
@AndréReichelt In my solution I did 2 things, 1. I copied the original array so sorting would not modify your original array that's why u see [...list] 2. Since you wanted a stable sort I had to know the index when there would be a tie. To do that I had to use map once for adding the index of the object as a property and once for removing the index after the sort is done using it.
Thank you for your further explanations. I've added a JSPerf comparison in my question.
3

You could sort

  • by the index of typeOrder, for the over all sorting
  • by the index of mainOrder, for maintaining the original order by taking the object.

var list = [{ id: 1, type: 2 }, { id: 2, type: 4 }, { id: 3, type: 2 }, { id: 4, type: 1 }, { id: 5, type: 2 }, { id: 6, type: 3 }],
    typeOrder = [4, 2, 3, 1],
    mainOrder = list.slice();

list.sort((a, b) =>
    typeOrder.indexOf(a.type) - typeOrder.indexOf(b.type) ||
    mainOrder.indexOf(a) - mainOrder.indexOf(b)
);

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

A slightly differnet approach by using a Map

var list = [{ id: 1, type: 2 }, { id: 2, type: 4 }, { id: 3, type: 2 }, { id: 4, type: 1 }, { id: 5, type: 2 }, { id: 6, type: 3 }],
    typeOrder = [4, 2, 3, 1]
    result = Array
        .from(
            list
                .reduce(
                    (m, o) => m.set(o.type, [...(m.get(o.type) || []), o]),
                    typeOrder.reduce((m, t) => m.set(t, []), new Map)
                )
                .values()
        )
        .flat();

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

6 Comments

@PatrickRoberts, thank you for the hint. now it store the orrder and mainatins the original order.
Do I understand it correctly, that your solution requires the whole array to be copied once? Is that a problem?
no, it just takes the references to the objects in a new array. it is used for getting the indices, later.
Ah, I see. It's not that the array contains a lot of elements, but the objects can be rather large. They contain a list of sub entries each.
it does not matter.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.