Alt çizgi: birden çok özniteliğe göre sortBy ()


115

Nesneli bir diziyi birden çok özniteliğe göre sıralamaya çalışıyorum. Yani, ilk öznitelik iki nesne arasında aynıysa, iki nesneyi karşılaştırmak için ikinci bir öznitelik kullanılmalıdır. Örneğin, aşağıdaki diziyi düşünün:

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

Bunları roomNumberözniteliğe göre sıralamak için aşağıdaki kodu kullanacağım:

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

Bu iyi çalışıyor, ancak "John" ve "Lisa" nın düzgün sıralanması için nasıl ilerleyebilirim?

Yanıtlar:


250

sortBy bunun kararlı bir sıralama algoritması olduğunu söylüyor, bu nedenle önce ikinci mülkünüze göre sıralayabilmeniz, ardından ilk mülkünüze göre yeniden sıralayabilmeniz gerekir, örneğin:

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

İkincisi sortBy, John ve Lisa'nın aynı oda numarasına sahip olduğunu bulduğunda, onları buldukları sırada tutacaktır, bu da ilki sortBy"Lisa, John" olarak ayarlanmıştır.


12
Bu konuda genişleyen ve artan ve azalan özelliklerin sıralanması hakkında iyi bilgiler içeren bir blog yazısı var.
Alex C

4
Zincirleme sınıflandırmaya daha basit bir çözüm burada bulunabilir . Adil olmak gerekirse, blog yazısı bu cevaplar verildikten sonra yazılmış gibi görünüyor, ancak yukarıdaki cevaptaki kodu kullanmaya çalışıp başarısız olduktan sonra bunu anlamama yardımcı oldu.
Mike Devenney

1
Hasta [0] .name ve hasta [1] .roomNumber'ın oradaki dizine sahip olması gerektiğinden emin misiniz? hasta bir dizi değil ...
StinkyCat

[0]Orijinal örnekte, çünkü dizinleyici gereklidir patientsdizilerin bir dizidir. Bu aynı zamanda başka bir yorumda bahsedilen blog gönderisindeki "daha basit çözüm" burada çalışmayacağının nedenidir.
Rory MacLeod

1
@ac_fire İşte artık ölü bağlantının bir arşivi: archive.is/tiatQ
lustig

52

İşte bazen bu durumlarda kullandığım hile: özellikleri, sonucun sıralanabileceği şekilde birleştirin:

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

Ancak, dediğim gibi, bu oldukça zordur. Bunu doğru bir şekilde yapmak için muhtemelen temel JavaScript sortyöntemini kullanmak isteyeceksiniz :

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

Elbette, bu dizinizi yerinde sıralayacaktır. Sıralı bir kopya istiyorsanız ( _.sortBysize vereceği gibi ), önce diziyi klonlayın:

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

Can sıkıntısından, bunun için de genel bir çözüm (herhangi bir rastgele sayıda anahtara göre sıralamak için) yazdım: bir göz atın .


Bu çözüm için çok teşekkürler, ikincisini kullanmaya başladım, çünkü niteliklerim hem dizeler hem de sayılar olabilir. Yani dizileri sıralamak için basit bir yerel yol yok mu?
Christian R

3
return [patient[0].roomNumber, patient[0].name];Olmadan neden yeterli değil join?
Csaba Toth

1
Genel çözümünüzün bağlantısı bozuk görünüyor (veya belki de ona proxy sunucumuz aracılığıyla erişemiyorum). Lütfen buraya postalar mısınız?
Zev Spitz

Ayrıca, nasıl compare- sap ilkel değerleri değildir değerleri undefined, nullveya düz nesneleri?
Zev Spitz

Bilginize bu hack, yalnızca her bir değerin str uzunluğunun dizideki tüm öğeler için aynı olmasını sağlarsanız çalışır.
miex

32

Partiye geç kaldığımı biliyorum, ancak bunu daha önce önerdikleri daha temiz ve daha hızlı bir çözüme ihtiyaç duyanlar için eklemek istedim. SortBy çağrılarını en az önemli özelliğe göre en önemli özelliğe zincirleyebilirsiniz. Kodunda ben sıralama kriteri hastaların yeni bir dizi oluşturmak aşağıda adı içinde roomnumber orijinal dizi denir gelen hastalar .

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

4
Geç kalmanıza rağmen hala haklısınız :) Teşekkürler!
Allan Jikamu

3
Güzel, çok temiz.
Jason Turan

11

btw hastalar için başlatıcınız biraz tuhaf değil mi? neden bu değişkeni şu şekilde başlatmıyorsunuz -gerçek bir nesne dizisi olarak -bunu _.flatten () kullanarak yapabilirsiniz ve tek nesneden oluşan bir dizi dizisi olarak değil, belki yazım hatası sorunudur):

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

Listeyi farklı şekilde sıraladım ve Kiko'yu Lisa'nın yatağına ekledim; sadece eğlence için ve hangi değişikliklerin yapılacağını görün ...

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

sıralı olarak inceleyin ve bunu göreceksiniz

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

bu yüzden cevabım şu: geri arama fonksiyonunuzda bir dizi kullanın, bu Dan Tao'nun cevabına oldukça benzer , sadece birleştirmeyi unutuyorum (belki de benzersiz öğe dizilerini kaldırdığım için :))
Veri yapınızı kullanarak, sonra o olabilir :

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

ve bir test yükü ilginç olurdu ...


Cidden, cevap bu
Radek Duchoň

7

Bu yanıtların hiçbiri, bir sıralamada birden çok alanı kullanmak için genel amaçlı bir yöntem olarak ideal değildir. Yukarıdaki yaklaşımların tümü, diziyi birden çok kez sıralamayı gerektirdiğinden (yeterince büyük bir listede işleri çok yavaşlatabilir) veya sanal makinenin temizlemesi gereken (ve nihayetinde yavaşlatan) büyük miktarlarda çöp nesneleri ürettikleri için verimsizdir. program aşağı).

Burada, hızlı, verimli bir çözüm kolayca bulunuyor ters sıralama izin verir ve kullanılabilir underscoreveya lodashveya doğrudan ileArray.sort

En önemli kısım, compositeComparatorbir dizi karşılaştırıcı işlevi alan ve yeni bir bileşik karşılaştırma işlevi döndüren yöntemdir.

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

Ayrıca sıralamak istediğiniz alanları karşılaştırmak için bir karşılaştırma işlevine ihtiyacınız olacaktır. naturalSortİşlev belli saha verilen bir karşılaştırıcı yaratacaktır. Ters sıralama için bir karşılaştırıcı yazmak da önemsizdir.

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

(Şimdiye kadarki tüm kod yeniden kullanılabilir ve örneğin yardımcı program modülünde tutulabilir)

Ardından, bileşik karşılaştırıcı oluşturmanız gerekir. Örneğimiz için şöyle görünecektir:

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

Bu, oda numarasına ve ardından ada göre sıralanacaktır. Ek sıralama ölçütleri eklemek önemsizdir ve sıralamanın performansını etkilemez.

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

Aşağıdakileri döndürür

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

Bu yöntemi tercih etmemin nedeni, rasgele sayıda alanda hızlı sıralamaya izin vermesi, çok fazla çöp üretmemesi veya sıralama içinde dize birleştirme gerçekleştirmemesi ve sıra sütunları doğal kullanırken bazı sütunların ters sıralanması için kolayca kullanılabilmesidir. çeşit.



2

Belki de alt çizgi.js veya sadece Javascript motorları şu anda bu cevapların yazıldığından farklıdır, ancak bunu sadece sıralama anahtarlarından oluşan bir dizi döndürerek çözebildim.

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

Hareket halindeyken lütfen şu kemana bakın: https://jsfiddle.net/mikeular/xenu3u91/


2

Sadece özelliklerin bir dizi döndürür sizinle sıralamak istiyorum:

ES6 Sözdizimi

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

ES5 Sözdizimi

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

Bunun bir sayıyı dizeye dönüştürmenin herhangi bir yan etkisi yoktur.


1

Yineleyicide sıralamak istediğiniz özellikleri birleştirebilirsiniz:

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

veya eşdeğer bir şey.

NOT: roomNumber sayısal özniteliğini bir dizgeye dönüştürdüğünüz için, oda numaralarınız> 10 ise bir şeyler yapmanız gerekir. Aksi takdirde, 2'den önce 11 gelir. Problemi çözmek için baştaki sıfırlar ile doldurabilirsiniz, yani 1.


1

Bence bunun _.orderByyerine şunu kullanmalısın sortBy:

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

4
OrderBy'nin alt çizgide olduğundan emin misiniz? Bunu dokümanlar veya .d.ts dosyamda göremiyorum.
Zachary Dow

1
Alt çizgide orderBy yoktur.
AfroMogli

1
_.orderByçalışır, ancak bu, lodash kitaplığının bir yöntemidir, alt çizgi değil: lodash.com/docs/4.17.4#orderBy lodash, çoğunlukla altçizginin yerini alır, bu nedenle OP için uygun olabilir.
Mike K

0

Angular kullanıyorsanız, herhangi bir JS veya CSS işleyicisi eklemek yerine onun numara filtresini html dosyasında kullanabilirsiniz. Örneğin:

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

Bu örnekte, val = 1234567 ise, şu şekilde görüntülenecektir:

  No fractions: 1,234,567

Örnek ve daha fazla rehber: https://docs.angularjs.org/api/ng/filter/number

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.