Garis bawah: sortBy () berdasarkan beberapa atribut


115

Saya mencoba mengurutkan array dengan objek berdasarkan beberapa atribut. Yaitu jika atribut pertama adalah sama antara dua objek, atribut kedua harus digunakan untuk membandingkan kedua objek. Misalnya, perhatikan larik berikut:

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

Menyortir ini berdasarkan roomNumberatribut saya akan menggunakan kode berikut:

var sortedArray = _.sortBy(patients, function(patient) {
    return patient[0].roomNumber;
});

Ini berfungsi dengan baik, tetapi bagaimana saya melanjutkan sehingga 'John' dan 'Lisa' akan diurutkan dengan benar?

Jawaban:


250

sortBy mengatakan bahwa ini adalah algoritme pengurutan yang stabil sehingga Anda harus dapat mengurutkan berdasarkan properti kedua Anda terlebih dahulu, lalu mengurutkan lagi berdasarkan properti pertama Anda, seperti ini:

var sortedArray = _(patients).chain().sortBy(function(patient) {
    return patient[0].name;
}).sortBy(function(patient) {
    return patient[0].roomNumber;
}).value();

Ketika yang kedua sortBymenemukan bahwa John dan Lisa memiliki nomor kamar yang sama, itu akan membuat mereka tetap dalam urutan yang ditemukannya, yang pertama sortBydisetel ke "Lisa, John".


12
Ada entri blog yang menjelaskan hal ini dan menyertakan informasi bagus tentang pengurutan properti naik dan turun.
Alex C

4
Solusi yang lebih sederhana untuk jenis rantai dapat ditemukan di sini . Agar adil, sepertinya postingan blog ditulis setelah jawaban ini diberikan, tetapi ini membantu saya mengetahuinya setelah mencoba menggunakan kode dalam jawaban di atas dan gagal.
Mike Devenney

1
Anda yakin pasien [0] .name dan patient [1] .roomNumber harus memiliki indeks di sana? pasien bukanlah sebuah array ...
StinkyCat

The [0]pengindeks diperlukan karena dalam contoh asli, patientsadalah array dari array. Ini juga mengapa "solusi yang lebih sederhana" di entri blog yang disebutkan di komentar lain tidak berfungsi di sini.
Rory MacLeod

1
@ac_fire Berikut adalah arsip dari tautan yang sekarang sudah mati: archive.is/tiatQ
lustig

52

Berikut adalah trik hacky yang terkadang saya gunakan dalam kasus ini: gabungkan properti sedemikian rupa sehingga hasilnya dapat diurutkan:

var sortedArray = _.sortBy(patients, function(patient) {
  return [patient[0].roomNumber, patient[0].name].join("_");
});

Namun, seperti yang saya katakan, itu cukup hacky. Untuk melakukan ini dengan benar Anda mungkin ingin benar-benar menggunakan inti JavaScript sortmetode :

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;
}

Tentu saja, ini akan mengurutkan array Anda pada tempatnya. Jika Anda ingin salinan yang diurutkan (seperti yang _.sortByakan Anda berikan), klon larik terlebih dahulu:

function sortOutOfPlace(sequence, sorter) {
  var copy = _.clone(sequence);
  copy.sort(sorter);
  return copy;
}

Karena bosan, saya baru saja menulis solusi umum (untuk mengurutkan berdasarkan jumlah kunci yang berubah-ubah) untuk ini juga: lihatlah .


Terima kasih banyak untuk solusi ini yang akhirnya menggunakan yang kedua karena atribut saya bisa berupa string dan angka. Jadi sepertinya tidak ada cara asli yang sederhana untuk mengurutkan array?
Christian R

3
Mengapa tidak return [patient[0].roomNumber, patient[0].name];cukup tanpa itu join?
Csaba Toth

1
Tautan ke solusi umum Anda tampaknya rusak (atau mungkin saya tidak dapat mengaksesnya melalui server proxy kami). Bisakah Anda mempostingnya di sini?
Zev Spitz

Juga, bagaimana comparemenangani nilai yang bukan nilai primitif - undefined, nullatau objek biasa?
Zev Spitz

FYI hack ini hanya berfungsi jika Anda memastikan bahwa panjang str setiap nilai adalah sama untuk semua item dalam array.
miex

32

Saya tahu saya terlambat ke pesta, tetapi saya ingin menambahkan ini untuk mereka yang membutuhkan solusi yang lebih bersih dan lebih cepat yang telah disarankan. Anda dapat menyambung panggilan sortBy dalam urutan dari properti yang paling tidak penting ke properti yang paling penting. Dalam kode di bawah ini saya membuat array baru pasien yang diurutkan berdasarkan Nama dalam RoomNumber dari array asli yang disebut pasien .

var sortedPatients = _.chain(patients)
  .sortBy('Name')
  .sortBy('RoomNumber')
  .value();

4
Meskipun kamu telat kamu tetap benar :) Terima kasih!
Allan Jikamu

3
Bagus, bersih sekali.
Jason Turan

11

Btw penginisialisasi Anda untuk pasien agak aneh, bukan? mengapa Anda tidak menginisialisasi variabel ini sebagai ini -sebagai array objek yang sebenarnya -Anda dapat melakukannya menggunakan _.flatten () dan bukan sebagai array array objek tunggal, mungkin itu masalah kesalahan ketik):

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

Aku mengurutkan daftar itu secara berbeda dan menambahkan Kiko ke tempat tidur Lisa; hanya untuk bersenang-senang dan lihat perubahan apa yang akan dilakukan ...

var sorted = _(patients).sortBy( 
                    function(patient){
                       return [patient.roomNumber, patient.bedNumber, patient.name];
                    });

periksa diurutkan dan Anda akan melihat ini

[
{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}
]

jadi jawaban saya adalah: gunakan array dalam fungsi panggilan balik Anda ini sangat mirip dengan jawaban Dan Tao , saya hanya lupa bergabung (mungkin karena saya menghapus array array item unik :))
Menggunakan struktur data Anda, maka itu akan menjadi :

var sorted = _(patients).chain()
                        .flatten()
                        .sortBy( function(patient){
                              return [patient.roomNumber, 
                                     patient.bedNumber, 
                                     patient.name];
                        })
                        .value();

dan testload akan menarik ...


Serius, itulah jawabannya
Radek Duchoň

7

Tak satu pun dari jawaban ini yang ideal sebagai metode tujuan umum untuk menggunakan beberapa bidang dalam satu urutan. Semua pendekatan di atas tidak efisien karena memerlukan pengurutan beberapa kali (yang, pada daftar yang cukup besar dapat memperlambat banyak hal) atau menghasilkan sejumlah besar objek sampah yang perlu dibersihkan oleh VM (dan pada akhirnya memperlambat program down).

Berikut adalah solusi yang cepat, efisien, dengan mudah memungkinkan pengurutan terbalik, dan dapat digunakan dengan underscoreatau lodash, atau langsung denganArray.sort

Bagian terpenting adalah compositeComparatormetode, yang mengambil larik fungsi pembanding dan mengembalikan fungsi komparator komposit baru.

/**
 * 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);
  });
}

Anda juga memerlukan fungsi pembanding untuk membandingkan bidang yang ingin Anda sortir. The naturalSortfungsi akan membuat pembanding diberi bidang tertentu. Menulis pembanding untuk pengurutan terbalik juga sepele.

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;
  }
}

(Semua kode sejauh ini dapat digunakan kembali dan dapat disimpan dalam modul utilitas, misalnya)

Selanjutnya, Anda perlu membuat komparator komposit. Untuk contoh kita, akan terlihat seperti ini:

var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]);

Ini akan mengurutkan berdasarkan nomor kamar, diikuti dengan nama. Menambahkan kriteria pengurutan tambahan itu mudah dan tidak memengaruhi kinerja pengurutan.

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);

Mengembalikan yang berikut

[ { name: 'Chris', roomNumber: 1, bedNumber: 1 },
  { name: 'Lisa', roomNumber: 2, bedNumber: 2 },
  { name: 'Omar', roomNumber: 2, bedNumber: 1 },
  { name: 'John', roomNumber: 3, bedNumber: 1 } ]

Alasan saya lebih suka metode ini adalah karena metode ini memungkinkan pengurutan cepat pada jumlah bidang yang berubah-ubah, tidak menghasilkan banyak sampah atau melakukan penggabungan string di dalam pengurutan dan dapat dengan mudah digunakan sehingga beberapa kolom diurutkan terbalik sementara kolom urutan menggunakan alami menyortir.



2

Mungkin underscore.js atau hanya mesin Javascript yang berbeda sekarang dibandingkan saat jawaban ini ditulis, tetapi saya dapat menyelesaikannya hanya dengan mengembalikan array kunci sortir.

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

Beraksi, silakan lihat biola ini: https://jsfiddle.net/mikeular/xenu3u91/


2

Cukup kembalikan larik properti yang ingin Anda urutkan:

Sintaks ES6

var sortedArray = _.sortBy(patients, patient => [patient[0].name, patient[1].roomNumber])

Sintaks ES5

var sortedArray = _.sortBy(patients, function(patient) { 
    return [patient[0].name, patient[1].roomNumber]
})

Ini tidak memiliki efek samping apa pun untuk mengubah angka menjadi string.


1

Anda dapat menggabungkan properti yang ingin Anda urutkan di iterator:

return [patient[0].roomNumber,patient[0].name].join('|');

atau sesuatu yang setara.

CATATAN: Karena Anda mengubah atribut numerik roomNumber menjadi string, Anda harus melakukan sesuatu jika Anda memiliki nomor ruangan> 10. Jika tidak, 11 akan muncul sebelum 2. Anda dapat mengisi dengan nol di depan untuk menyelesaikan masalah, yaitu 01 daripada 1.


1

Saya pikir Anda lebih baik menggunakan _.orderBydaripada sortBy:

_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])

4
Apakah Anda yakin orderBy ada di garis bawah? Saya tidak bisa melihatnya di dokumen atau file .d.ts saya.
Zachary Dow

1
Tidak ada orderBy di garis bawah.
AfroMogli

1
_.orderByberfungsi, tetapi ini adalah metode pustaka lodash, bukan garis bawah: lodash.com/docs/4.17.4#orderBy lodash sebagian besar merupakan pengganti drop-in untuk garis bawah, jadi mungkin cocok untuk OP.
Mike K

0

Jika Anda kebetulan menggunakan Angular, Anda dapat menggunakan filter nomornya di file html daripada menambahkan penangan JS atau CSS. Sebagai contoh:

  No fractions: <span>{{val | number:0}}</span><br>

Dalam contoh itu, jika val = 1234567, itu akan ditampilkan sebagai

  No fractions: 1,234,567

Contoh dan panduan lebih lanjut di: https://docs.angularjs.org/api/ng/filter/number

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.