Подчеркиваем: 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 98

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]
})

это не имеет никаких побочных эффектов преобразования числа в строку.