Underscore:基于多个属性的sortBy()

我正在尝试使用基于多个属性的对象对数组进行sorting。 也就是说,如果两个对象之间的第一个属性是相同的,则应该使用第二个属性来匹配这两个对象。 例如,考虑下面的数组:

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属性sorting这些我会使用下面的代码:

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

这工作正常,但我如何继续,以便“约翰”和“丽莎”将被正确sorting?

sortBy说这是一个稳定的sortingalgorithm,所以你应该可以先sorting你的第二个属性,然后再按第一个属性sorting,如下所示:

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

当第二个sortBy发现John和Lisa具有相同的房间号码时,它们将保持它们发现它们的顺序,第一个sortBy设置为“Lisa,John”。

下面是我在这种情况下有时使用的一个诡计:将这些属性组合起来,使得结果是可sorting的:

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

当然, 将sorting你的arrays。 如果你想要一个有序的副本(比如_.sortBy会给你),首先克隆该数组:

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

出于无聊,我只是写了一个通用的解决scheme(以任意数量的键sorting), 看看 。

我知道我迟到了,但是我想为那些需要那些已经提出的更清洁,更快的解决scheme的人添加这个。 您可以按最重要的属性的顺序将sortBy调用链连接到最重要的属性。 在下面的代码中,我创build了一个名为“ 病人”的原始数组中RoomNumber中名称sorting的新数组。

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

顺便说一句,你的初始值为病人有点奇怪,不是吗? 你为什么不初始化这个variables,因为这是一个真正的对象数组,你可以使用_.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} ]; 

我把不同的列表分类,把Kikojoin到Lisa的床上。 只是为了好玩,看看会做些什么变化…

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

检查sorting,你会看到这一点

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

所以我的答案是: 在你的callback函数中使用一个数组,这与丹涛的回答非常相似,我只是忘记了连接(也许是因为我删除了唯一项数组:))
使用你的数据结构,那么它将是:

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

和一个testing负载将是有趣的…

这些答案都不是一个理想的作为使用多种领域的一种通用的方法。 上面所有的方法都是低效的,因为它们要么需要多次对数组进行sorting(在足够大的列表中可能会使事情减慢很多),要么产生大量垃圾对象,VM将需要清理(最终减慢该程序下降)。

这是一个快速,高效,易于反向sorting的解决scheme,可以与underscorelodash一起使用,或直接与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); }); } 

你还需要一个比较函数来比较你想要sorting的字段。 naturalSort函数将创build一个给定特定字段的比较器。 写反向sorting比较也是微不足道的。

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

(到目前为止所有的代码都是可重用的,例如可以保存在公用程序模块中)

接下来,您需要创build复合比较器。 对于我们的例子来说,它看起来像这样:

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

这将按房间号码sorting,然后是名称。 添加额外的sorting标准是微不足道的,不会影响sorting的性能。

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

我更喜欢这种方法的原因是,它允许在任意数量的字段上快速sorting,在sorting内不会产生大量的垃圾或执行string连接,并且可以很容易地使用,以便某些列反向sorting,而sorting列使用自然分类。

您可以在迭代器中连接要sorting的属性:

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

或者相当的东西。

注意:由于您正在将数字属性roomNumber转换为string,所以如果房间号大于10,则必须执行某些操作。否则,11将会在2之前。您可以使用前导零填充以解决问题,即01代替1。

也许underscore.js或只是J​​avascript引擎现在不同于这些答案的写入,但我能够通过返回一个sorting键数组来解决这个问题。

 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 [ob, oa]; }); // output is now sorted by b ascending, a ascending 

在行动中,请看这个小提琴: https : //jsfiddle.net/mikeular/xenu3u91/

我想你最好使用_.orderBy而不是sortBy

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