-1

Hello this is a rather complicated question.

I have an array of objects:

let animals = [
{
    "typ": "rats",
    "name": "RB 1",
},
{
    "typ": "mice",
    "name": "MB 1",
    
},
{
    "typ": "rats",
    "name": "RB 4",
    
},
{
    "typ": "rats",
    "name": "RB 2",
    
},
{
    "typ": "rats",
    "name": "RB 3",
},
{
    "typ": "mice",
    "name": "MB 2",

},
{
    "typ": "mice",
    "name": "MB 3",
}

this array can have up to a few hundred objects and several other animals too!!!

now i want to sort it so that i have all rats first and then all mice after that...

A second step would be to sort all rats and mice by name like RB 1 , RB2 , RB 3 ....so that i get an array like this

[
{
    "typ": "rats",
    "name": "RB 1",
},
{
    "typ": "rats",
    "name": "RB 2",
    
},
{
    "typ": "rats",
    "name": "RB 3",
},
{
    "typ": "rats",
    "name": "RB 4",
    
},
{
    "typ": "mice",
    "name": "MB 1",
    
},
{
    "typ": "mice",
    "name": "MB 2",

},
{
    "typ": "mice",
    "name": "MB 3",
}]

I get the initial array from a firestore collection with snapshotChanges() so the objects(documents in the firestore database) all come in in random order

Thanx everybody

edit:

@secan posted a working solution! thanx man

  • 4
    so, sort them once you have the response - as you haven't shown that part (how you get the data) then you wont get much help ... can I guess that the sort order is descending on type then ascending on name? – Jaromanda X Oct 16 '20 at 10:07
  • Have you attempted anything ? – Kunal Mukherjee Oct 16 '20 at 10:08
  • yes , my idea was to create an array for each animal type, put the animals in there according to type , sort those arrays my name (ascending) and then combine those several arrays into one array again. but i only know the basic javascript , not the extensions of the recent years so i thought there might be something more elegant/faster. I get the initial array from a firestore collection with snapshotChanges() so they all come in randomly – Alexander Mehler Oct 16 '20 at 10:23
  • 1
    Does this answer your question? [Javascript - sort array based on another array](https://stackoverflow.com/questions/13304543/javascript-sort-array-based-on-another-array) – Nick Oct 16 '20 at 10:41

3 Answers3

0

let animals = [{
    "typ": "rats",
    "name": "RB 1",
  },
  {
    "typ": "mice",
    "name": "MB 1",
  },
  {
    "typ": "rats",
    "name": "RB 4",
  },
  {
    "typ": "rats",
    "name": "RB 2",
  },
  {
    "typ": "rats",
    "name": "RB 3",
  },
  {
    "typ": "mice",
    "name": "MB 2",
  },
  {
    "typ": "mice",
    "name": "MB 3",
  },
  {
    "typ": "monkeys",
    "name": "MoB 2",
  },
  {
    "typ": "monkeys",
    "name": "MoB 1",
  }
];

let animalsPriority = ['monkeys', 'rats', 'mice'];

const sortedAnimals = animals.sort((a, b) => {
  return (
    (animalsPriority.indexOf(a.typ) < animalsPriority.indexOf(b.typ) && -1) ||
    (animalsPriority.indexOf(a.typ) > animalsPriority.indexOf(b.typ) && 1) ||
    (a.name < b.name && -1) ||
    (a.name > b.name && 1) ||
    0
  );
});

console.log(sortedAnimals);
secan
  • 2,622
  • 1
  • 7
  • 24
  • What if there is another animal type, say `monkeys`, which is supposed to sort after `mice`? – Nick Oct 16 '20 at 10:29
  • Note that your code does not produce the answer OP wants anyway. – Nick Oct 16 '20 at 10:29
  • @Nick, my understanding is that the OP wants the result "grouped by" type (`typ`) and sorted alphabetically within the same type. I interpreted that "arbitrary order" as "I do not really care if types are sorted A-Z or Z-A" but maybe I misunderstood. – secan Oct 16 '20 at 10:38
  • "now i want to sort it so that i have all rats first and then all mice after that..." – Nick Oct 16 '20 at 10:39
  • Yep, you are right; I am going to update the answer in order to take into account a really arbitrary sorting by type. Thanks for your feedback – secan Oct 16 '20 at 10:46
  • No worries - always prefer to get answers corrected rather than downvote... – Nick Oct 16 '20 at 10:47
  • works perfect!!! thanx a lot , my code would have been 10 times longer. good that i asked ! – Alexander Mehler Oct 16 '20 at 11:17
  • 1
    @AlexanderMehler ... for really big data one should be aware that the above approach does, for each comparison step, compute the animal (type) precedence twice again and again via `animalsPriority.indexOf(... .typ)`. There are *less expensive* ways like looking it up via an index/map which will be created once before sorting/comparing takes place. – Peter Seliger Oct 16 '20 at 12:01
  • @PeterSeliger, that is an interesting and good point; thank you very much for your comment. :) – secan Oct 16 '20 at 12:08
  • @PeterSeliger well i could condense the code to just (types.indexOf(a[1].typ) - types.indexOf(b[1].typ))|| (a[1].name < b[1].name && -1); but i guess secan ´s code also works on edge cases which im not aware of .. setting an index would require alot of management code becasue animals can be add or removed at any time – Alexander Mehler Oct 16 '20 at 17:49
  • @AlexanderMehler, you can (and you should) condense the `typ` sorting in `types.indexOf(a.typ) - types.indexOf(b.typ)` but in theory you could not condense the `name` sorting... unless you know for sure there will never be two object with the same `typ` and the same `name`. – secan Oct 16 '20 at 18:55
  • 1
    @AlexanderMehler ... go with whichever of the given approaches you feel most comfortable with (both do reliably cover the base requirements), but please don't try to shorten the comparison of animal names like within your last comment. It will mess up any sort result. – Peter Seliger Oct 16 '20 at 22:27
0

I wrote a small helper package that can be useful for multi-level sort: https://www.npmjs.com/package/ts-comparer-builder

Here's how you might use it:

const animals = [{
    "typ": "rats",
    "name": "RB 1",
  },
  {
    "typ": "mice",
    "name": "MB 1",

  },
  {
    "typ": "mice",
    "name": "MB 3",
  }
]

const { comparerBuilder } = window.tsComparerBuilder

const comparer = comparerBuilder()
    .sortKey(x => x.typ)
    .thenKeyDescending(x => x.name)
    .build();
    
const sortedAnimals = animals.sort(comparer);

console.log(sortedAnimals);
<script src="https://cdn.jsdelivr.net/npm/ts-comparer-builder"></script>
spender
  • 117,338
  • 33
  • 229
  • 351
0

This approach takes into account one of the OP's requirements ...

this array can have up to a few hundred objects and several other animals too!!!

... and therefore it transforms a precedence list into a precedence map right before the sorting/comparison task. Twice a lookup via an object key will be much faster than computing an animal's precedence via indexOf for each comparison step 4 times again and again.

The approach also makes use of localeCompare.

function localeCompare(a, b) {
  return (a.localeCompare && b.localeCompare)
    && a.localeCompare(b)
    || (((a < b) && -1) || ((a > b) && 1) || 0);
}

function compareAnimalsViaBoundPrecedenceMap(a, b) {
  const precedenceMap = this;
  return (
    (precedenceMap[a.type] - precedenceMap[b.type]) ||
    localeCompare(a.name, b.name)
  );
}
function sortAnimalsViaPrecedenceList(animalList, precedenceList) {
  // build a map for a faster precedence lookup
  const precedenceMap = precedenceList.reduce((map, key, idx) => {
    map[key] = idx;
    return map;
  }, {});

  // does return the mutated `animalList`.
  return animalList.sort(
    compareAnimalsViaBoundPrecedenceMap.bind(precedenceMap)
  );
}


const animalList = [{
  "type": "rats",
  "name": "RB 1",
}, {
  "type": "mice",
  "name": "MB 1",
}, {
  "type": "rats",
  "name": "RB 4",
}, {
  "type": "rats",
  "name": "RB 2",
}, {
  "type": "rats",
  "name": "RB 3",
}, {
  "type": "mice",
  "name": "MB 2",
}, {
  "type": "mice",
  "name": "MB 3",
}];
console.log(
  'sortAnimalsViaPrecedenceList(animalList, ["rats", "mice"]) :',
  sortAnimalsViaPrecedenceList(animalList, ["rats", "mice"])
);


const petList = [
  {'type': 'rats', 'name': 'James'},
  {'type': 'mice', 'name': 'John'},
  {'type': 'guinea pigs', 'name': 'Robert'},
  {'type': 'parrots', 'name': 'Michael'},
  {'type': 'rats', 'name': 'William'},
  {'type': 'mice', 'name': 'David'},
  {'type': 'guinea pigs', 'name': 'Richard'},
  {'type': 'parrots', 'name': 'Charles'},
  {'type': 'rats', 'name': 'Joseph'},
  {'type': 'mice', 'name': 'Thomas'},
  {'type': 'guinea pigs', 'name': 'Andrew'},
  {'type': 'parrots', 'name': 'George'}
];
console.log(
  'sortAnimalsViaPrecedenceList(petList, ["rats", "mice", "guinea pigs", "parrots"]) :',
  sortAnimalsViaPrecedenceList(petList, ["rats", "mice", "guinea pigs", "parrots"])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37