Home > Mobile >  Sort array of objects by multiple params
Sort array of objects by multiple params

Time:01-18

There's an object:

let obj = [
    {'id': 4, 'surname': 'white', 'is_curator': true},
    {'id': 7, 'surname': 'goodman', 'is_curator': false},
    {'id': 1, 'surname': 'black', 'is_curator': true},
    {'id': 9, 'surname': 'babbs'},
    {'id': 3, 'surname': 'smith', 'is_curator': true}
]

(I skipped is_curator field in one of the arrays intentionally)
and an array containing some of the object's ids, e.g.

let idsArr = [3, 1, 7]

I want to sort my object primarily to have all arrays which ids are included in the idsArr at the top, secondarily arrays with is_curator = true are sorted true to false, and then these arrays must be sorted by surname A to z, so the object looks like:

obj = [
    {'id': 1, 'surname': 'black', 'is_curator': true},
    {'id': 3, 'surname': 'smith', 'is_curator': true},
    {'id': 7, 'surname': 'goodman', 'is_curator': false},
    {'id': 4, 'surname': 'white', 'is_curator': true},
    {'id': 9, 'surname': 'babbs'},
]

I tried to use nested conditions in the array.sort function, tried to make a custom compare function but haven't got the required result.
I tried:

data.sort(function (a, b) {
    return idsArr.includes(b.id) - idsArr.includes(a.id)
    || b.is_curator - a.is_curator
    || b.surname.localeCompare(a.surname)
});

and something like

if (idsArr.includes(a.id) && !idsArr.includes(b.id) {
    if (a.is_curator && !b.is_curator) {
        return -1;
    } else if (!a.is_curator && b.is_curator) {
        return 1;
    }
} else if (!idsArr.includes(a.id) && idsArr.includes(b.id)) {
    return 1;
}

CodePudding user response:

Break the sort down into logical comparisons to short-circuit as early as possible.

  • Check for null
  • Check if the ID is present in the priority array
  • Find the logical difference between the is_curator field
    • Use nullish-coalescing (??) to default to false
  • Compare by surname

const people = [
  { id: 4 , surname: 'white'   , is_curator: true  },
  { id: 7 , surname: 'goodman' , is_curator: false },
  { id: 1 , surname: 'black'   , is_curator: true  },
  { id: 9 , surname: 'babbs'                       },
  { id: 3 , surname: 'smith'   , is_curator: true  }
];

const idsArr = [3, 1, 7];

const customSort = (items, idPriority) => {
  const ids = new Set(idPriority);
  return items.sort((a, b) => {
    if (!b) return -1;
    if (!a) return 1;
    if (ids.has(a.id) && !ids.has(b.id)) return -1;
    if (ids.has(b.id) && !ids.has(a.id)) return 1;
    let diff = (b.is_curator ?? false) - (a.is_curator ?? false);
    if (diff !== 0) return diff;
    return a.surname.localeCompare(b.surname);
  });
};

const sorted = customSort(people, idsArr);

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

CodePudding user response:

I suppose your intended object is this (correcting invalid syntax):

[
    {'id': 4, 'surname': 'white', 'is_curator': true},
    {'id': 7, 'surname': 'goodman', 'is_curator': false},
    {'id': 1, 'surname': 'black', 'is_curator': true},
    {'id': 9, 'surname': 'babbs'},
    {'id': 3, 'surname': 'smith', 'is_curator': true}
]

Your first attempt is almost right, but:

  • you need to cope with the non-existence of the is_curator property. For that, it is probably easiest to negate that property value -- which you did in the second attempt, but there you abandoned the simple subtraction pattern and will sometimes return undefined.

  • To sort by ascending surname, you should have the negation of what you tried. So swap the position of a and b in that expression: a.surname.localeCompare(b.surname).

Code:

obj.sort((a, b) => idsArr.includes(b.id) - idsArr.includes(a.id) 
                || !a.is_curator - !b.is_curator
                || a.surname.localeCompare(b.surname));
  • Related