Подчеркивание: sortBy() на основе нескольких атрибутов
Я пытаюсь сортировать массив с объектами на основе нескольких атрибутов. I. Если первый атрибут совпадает между двумя объектами, для совместного использования двух объектов должен использоваться второй атрибут. Например, рассмотрим следующий массив:
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;
});
Это прекрасно работает, но как я могу продолжить, чтобы "Джон" и "Лиза" были правильно отсортированы?
Ответы
Ответ 1
sortBy
говорит, что это стабильный алгоритм сортировки, поэтому вы должны иметь возможность сначала отсортировать по второму свойству, а затем снова отсортировать по первому свойству, например так:
var sortedArray = _(patients).chain().sortBy(function(patient) {
return patient[0].name;
}).sortBy(function(patient) {
return patient[0].roomNumber;
}).value();
Когда второй sortBy
обнаружит, что у Джона и Лизы одинаковый номер комнаты, он сохранит их в том порядке, в котором он их нашел, что для первого sortBy
установлено "Лиза, Джон".
Ответ 2
Вот хакерский трюк, который я иногда использую в этих случаях: объединим свойства таким образом, чтобы результат был отсортирован:
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;
}
Из скуки я просто написал общее решение (для сортировки любым произвольным числом ключей): посмотреть.
Ответ 3
Я знаю, что опаздываю на вечеринку, но я хотел добавить это для тех, кто нуждается в чистом и быстрейшем решении, которое уже было предложено. Вы можете связать вызовы sortBy в порядке наименее важного свойства с наиболее важным свойством. В приведенном ниже коде я создаю новый массив пациентов, отсортированных по имени в RoomNumber из исходного массива, называемого пациентами.
var sortedPatients = _.chain(patients)
.sortBy('Name')
.sortBy('RoomNumber')
.value();
Ответ 4
Кстати, ваш инициализатор для пациентов немного странный, не так ли?
почему бы вам не инициализировать эту переменную как это - как истинный массив объектов - вы можете сделать это с помощью _. flatten(), а не как массив массивов один объект, возможно, это опечатка):
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}
]
поэтому мой ответ: использовать массив в вашей функции обратного вызова
это очень похоже на ответ Dan Tao, я просто забыл о соединении (возможно, потому, что я удалил массив массивов уникального элемента:))
Используя вашу структуру данных, это будет:
var sorted = _(patients).chain()
.flatten()
.sortBy( function(patient){
return [patient.roomNumber,
patient.bedNumber,
patient.name];
})
.value();
и будет интересна тестовая загрузка...
Ответ 5
Ни один из этих ответов не идеален как метод общего назначения для использования нескольких полей в сортировке. Все вышеприведенные подходы неэффективны, поскольку они либо требуют сортировки массива несколько раз (что в достаточно большом списке может замедлить работу), либо они генерируют огромное количество объектов мусора, которые 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 } ]
Причина, по которой я предпочитаю этот метод, заключается в том, что он позволяет быструю сортировку по произвольному числу полей, не генерирует много мусора или не выполняет конкатенацию строк внутри сортировки и может быть легко использована, так что некоторые столбцы сортируются в обратном порядке, а порядок столбцы используют естественный вид.
Ответ 6
Простой пример из 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');
Ответ 7
Возможно, underscore.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/
Ответ 8
Вы можете объединить свойства, которые хотите отсортировать, в итераторе:
return [patient[0].roomNumber,patient[0].name].join('|');
или что-то подобное.
ПРИМЕЧАНИЕ. Поскольку вы преобразовываете числовой атрибут roomNumber в строку, вам нужно будет что-то сделать, если у вас есть номера номеров > 10. В противном случае 11 придет раньше 2. Вы можете использовать начальные нули для решения проблемы, т.е. 01 вместо 1.
Ответ 9
Я думаю, вам лучше использовать _.orderBy
вместо sortBy
:
_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])
Ответ 10
Просто верните массив свойств, с которыми вы хотите отсортировать:
Синтаксис 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]
})
Это не имеет никаких побочных эффектов от преобразования числа в строку.
Ответ 11
Если вы используете Angular, вы можете использовать его числовой фильтр в html файле вместо добавления каких-либо обработчиков JS или CSS. Например:
No fractions: <span>{{val | number:0}}</span><br>
В этом примере, если val = 1234567, он будет отображаться как
No fractions: 1,234,567
Пример и дальнейшие рекомендации по адресу: https://docs.angularjs.org/api/ng/filter/number.