Подчеркиваем: sortBy() на основе нескольких атрибутов
Я пытаюсь отсортировать массив с объектами на основе нескольких атрибутов. Т. е. если первый атрибут одинаков между двумя объектами, второй атрибут должен использоваться для объединения двух объектов. Например, рассмотрим следующий массив:
var patients = [
[{name: 'John', roomNumber: 1, bedNumber: 1}],
[{name: 'Lisa', roomNumber: 1, bedNumber: 2}],
[{name: 'Chris', roomNumber: 2, bedNumber: 1}],
[{name: 'Omar', roomNumber: 3, bedNumber: 1}]
];
сортировка по roomNumber
атрибут я бы использовал следующий код:
var sortedArray = _.sortBy(patients, function(patient) {
return patient[0].roomNumber;
});
Это прекрасно работает, но как мне поступить, чтобы "Джон" и "Лиза" были отсортированы должным образом?
10 ответов:
sortBy
говорит, что это стабильный алгоритм сортировки, поэтому вы должны иметь возможность сначала сортировать по второму свойству, а затем снова Сортировать по первому свойству, например:var sortedArray = _(patients).chain().sortBy(function(patient) { return patient[0].name; }).sortBy(function(patient) { return patient[0].roomNumber; }).value();
когда вторая
sortBy
считает, что Джон и Лиза имеют тот же номер, он будет держать их в порядке, он их нашел, что первыйsortBy
установите значение "Лиза, Джон".
вот хитрый трюк, который я иногда использую в этих случаях: объедините свойства таким образом, чтобы результат был сортируемым:
var sortedArray = _.sortBy(patients, function(patient) { return [patient[0].roomNumber, patient[0].name].join("_"); });
однако, как я уже сказал, Это довольно суховато. Чтобы сделать это правильно, вы, вероятно, захотите использовать ядро JavaScript
sort
метод:patients.sort(function(x, y) { var roomX = x[0].roomNumber; var roomY = y[0].roomNumber; if (roomX !== roomY) { return compare(roomX, roomY); } return compare(x[0].name, y[0].name); }); // General comparison function for convenience function compare(x, y) { if (x === y) { return 0; } return x > y ? 1 : -1; }
конечно, этой сортирует массив на месте. Если вы хотите отсортированную копию (например
_.sortBy
даст вам), клонировать массив первый:function sortOutOfPlace(sequence, sorter) { var copy = _.clone(sequence); copy.sort(sorter); return copy; }
от скуки я просто написал общее решение (для сортировки по произвольному количеству ключей) для этого:посмотреть.
Я знаю, что опаздываю на вечеринку, но я хотел добавить это для тех, кто нуждается в чистом и быстром решении, которое те уже предложили. Вы можете связывать вызовы sortBy в порядке наименее важного свойства с наиболее важным свойством. В приведенном ниже коде я создаю новый массив пациентов, отсортированных по имя внутри RoomNumber из исходного массива называется больных.
var sortedPatients = _.chain(patients) .sortBy('Name') .sortBy('RoomNumber') .value();
Кстати, Ваш инициализатор для пациентов-это немного странно, не так ли? почему бы тебе не инициализируйте эту переменную как это-как истинный массив объектов-вы можете сделать это с помощью _.сплющить () и не как массив массивов одного объекта, может быть, это опечатка вопрос):
var patients = [ {name: 'Omar', roomNumber: 3, bedNumber: 1}, {name: 'John', roomNumber: 1, bedNumber: 1}, {name: 'Chris', roomNumber: 2, bedNumber: 1}, {name: 'Lisa', roomNumber: 1, bedNumber: 2}, {name: 'Kiko', roomNumber: 1, bedNumber: 2} ];
я отсортировал список по-другому и добавил Кико в кровать Лизы; просто для удовольствия и посмотреть, какие изменения будут сделаны...
var sorted = _(patients).sortBy( function(patient){ return [patient.roomNumber, patient.bedNumber, patient.name]; });
проверьте сортировку, и вы увидите это
[ {bedNumber: 1, name: "John", roomNumber: 1}, {bedNumber: 2, name: "Kiko", roomNumber: 1}, {bedNumber: 2, name: "Lisa", roomNumber: 1}, {bedNumber: 1, name: "Chris", roomNumber: 2}, {bedNumber: 1, name: "Omar", roomNumber: 3} ]
так что мой ответ : использовать массив в функции обратного вызова это очень похоже на Дэн Таоответ, я просто забыл присоединиться (может быть, потому что я удалил массив массивов уникального элемента :))
используя структуру данных, то это будет выглядеть так :var sorted = _(patients).chain() .flatten() .sortBy( function(patient){ return [patient.roomNumber, patient.bedNumber, patient.name]; }) .value();
и тестовая загрузка была бы интересной...
ни один из этих ответов не идеален в качестве метода общего назначения для использования нескольких полей в сортировке. Все вышеперечисленные подходы неэффективны, поскольку они либо требуют сортировки массива несколько раз (что в достаточно большом списке может сильно замедлить работу), либо они генерируют огромное количество объектов мусора, которые VM необходимо будет очистить (и в конечном итоге замедлить работу программы).
вот решение, которое является быстрым, эффективным, легко позволяет обратную сортировку, и может быть используется с
underscore
илиlodash
, или непосредственно сArray.sort
наиболее важной частью является
compositeComparator
метод, который принимает массив функций компаратора и возвращает новую составную функцию компаратора./** * Chains a comparator function to another comparator * and returns the result of the first comparator, unless * the first comparator returns 0, in which case the * result of the second comparator is used. */ function makeChainedComparator(first, next) { return function(a, b) { var result = first(a, b); if (result !== 0) return result; return next(a, b); } } /** * Given an array of comparators, returns a new comparator with * descending priority such that * the next comparator will only be used if the precending on returned * 0 (ie, found the two objects to be equal) * * Allows multiple sorts to be used simply. For example, * sort by column a, then sort by column b, then sort by column c */ function compositeComparator(comparators) { return comparators.reduceRight(function(memo, comparator) { return makeChainedComparator(comparator, memo); }); }
Вам также понадобится функция компаратора для сравнения поля, которые вы хотите отсортировать. Элемент
naturalSort
функция создаст компаратор с учетом конкретного поля. Написание компаратора для обратной сортировки тоже тривиально.function naturalSort(field) { return function(a, b) { var c1 = a[field]; var c2 = b[field]; if (c1 > c2) return 1; if (c1 < c2) return -1; return 0; } }
(все код до сих пор является многоразовым и может храниться в служебном модуле, например)
Далее необходимо создать составной компаратор. Для нашего примера это будет выглядеть так:
var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]);
это будет сортировать по номеру комнаты, а затем по имени. Добавление дополнительных критериев сортировки является тривиальным и не влияет на производительность сортировки.
var patients = [ {name: 'John', roomNumber: 3, bedNumber: 1}, {name: 'Omar', roomNumber: 2, bedNumber: 1}, {name: 'Lisa', roomNumber: 2, bedNumber: 2}, {name: 'Chris', roomNumber: 1, bedNumber: 1}, ]; // Sort using the composite patients.sort(cmp); console.log(patients);
возвращает следующий элемент
[ { name: 'Chris', roomNumber: 1, bedNumber: 1 }, { name: 'Lisa', roomNumber: 2, bedNumber: 2 }, { name: 'Omar', roomNumber: 2, bedNumber: 1 }, { name: 'John', roomNumber: 3, bedNumber: 1 } ]
причина, по которой я предпочитаю этот метод, заключается в том, что он позволяет быстро сортировка по произвольному количеству полей не создает много мусора или выполняет конкатенацию строк внутри сортировки и может легко использоваться, чтобы некоторые столбцы были отсортированы в обратном порядке, а столбцы порядка используют естественную сортировку.
Простой Пример: от http://janetriley.net/2014/12/sort-on-multiple-keys-with-underscores-sortby.html (любезно предоставлено @MikeDevenney)
код
var FullySortedArray = _.sortBy(( _.sortBy(array, 'second')), 'first');
С Данными
var FullySortedArray = _.sortBy(( _.sortBy(patients, 'roomNumber')), 'name');
возможно, подчеркнуть.JS или просто JavaScript-движки теперь отличаются от тех, когда эти ответы были написаны, но я смог решить это, просто вернув массив ключей сортировки.
var input = []; for (var i = 0; i < 20; ++i) { input.push({ a: Math.round(100 * Math.random()), b: Math.round(3 * Math.random()) }) } var output = _.sortBy(input, function(o) { return [o.b, o.a]; }); // output is now sorted by b ascending, a ascending
в действии, пожалуйста, смотрите эту скрипку:https://jsfiddle.net/mikeular/xenu3u91/
вы можете объединить свойства, которые вы хотите отсортировать в итераторе:
return [patient[0].roomNumber,patient[0].name].join('|');
или что-то эквивалентное.
Примечание: поскольку вы преобразуете числовой атрибут roomNumber в строку, вам придется что-то сделать, если у вас есть номера комнат > 10. В противном случае 11 до 2. Вы можете заполнить с ведущими нулями, чтобы решить проблему, т. е. 01 вместо 1.
Я думаю, что вам лучше использовать
_.orderBy
вместоsortBy
:_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])
просто возвращает массив свойств вы хотите разобраться с:
синтаксис ES6
var sortedArray = _.sortBy(patients, patient => [patient[0].name, patient[1].roomNumber])
синтаксис ES5
var sortedArray = _.sortBy(patients, function(patient) { return [patient[0].name, patient[1].roomNumber] })
это не имеет никаких побочных эффектов преобразования числа в строку.