javascript 下划线:sortBy() 基于多个属性
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16426774/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Underscore: sortBy() based on multiple attributes
提问by Christian R
I am trying to sort an array with objects based on multiple attributes. I.e if the first attribute is the same between two objects a second attribute should be used to comapare the two objects. For example, consider the following array:
我正在尝试使用基于多个属性的对象对数组进行排序。即,如果两个对象之间的第一个属性相同,则应使用第二个属性来比较这两个对象。例如,考虑以下数组:
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}]
];
Sorting these by the roomNumber
attribute i would use the following code:
按roomNumber
属性对这些进行排序,我将使用以下代码:
var sortedArray = _.sortBy(patients, function(patient) {
return patient[0].roomNumber;
});
This works fine, but how do i proceed so that 'John' and 'Lisa' will be sorted properly?
这工作正常,但我该如何进行才能正确排序“约翰”和“丽莎”?
回答by Rory MacLeod
sortBy
says that it is a stable sort algorithm so you should be able to sort by your second property first, then sort again by your first property, like this:
sortBy
说它是一种稳定的排序算法,因此您应该能够先按第二个属性排序,然后再按第一个属性排序,如下所示:
var sortedArray = _(patients).chain().sortBy(function(patient) {
return patient[0].name;
}).sortBy(function(patient) {
return patient[0].roomNumber;
}).value();
When the second sortBy
finds that John and Lisa have the same room number it will keep them in the order it found them, which the first sortBy
set to "Lisa, John".
当第二个sortBy
发现 John 和 Lisa 具有相同的房间号时,它将按照找到它们的顺序保留他们,第一个sortBy
设置为“Lisa,John”。
回答by Dan Tao
Here's a hacky trick I sometimes use in these cases: combine the properties in such a way that the result will be sortable:
这是我有时在这些情况下使用的一个技巧:以结果可排序的方式组合属性:
var sortedArray = _.sortBy(patients, function(patient) {
return [patient[0].roomNumber, patient[0].name].join("_");
});
However, as I said, that's pretty hacky. To do this properly you'd probably want to actually use the core JavaScript sort
method:
然而,正如我所说,这很hacky。要正确执行此操作,您可能希望实际使用核心 JavaScriptsort
方法:
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;
}
Of course, thiswill sort your array in place. If you want a sorted copy (like _.sortBy
would give you), clone the array first:
当然,这会将您的数组排序到位。如果你想要一个排序的副本(就像_.sortBy
会给你的),首先克隆数组:
function sortOutOfPlace(sequence, sorter) {
var copy = _.clone(sequence);
copy.sort(sorter);
return copy;
}
Out of boredom, I just wrote a general solution (to sort by any arbitrary number of keys) for this as well: have a look.
出于无聊,我也为此编写了一个通用解决方案(按任意数量的键排序):看看.
回答by Mike Devenney
I know I'm late to the party, but I wanted to add this for those in need of a clean-er and quick-er solution that those already suggested. You can chain sortBy calls in order of least important property to most important property. In the code below I create a new array of patients sorted by Namewithin RoomNumberfrom the original array called patients.
我知道我迟到了,但我想为那些需要更干净、更快速的解决方案的人添加这个,这些解决方案已经建议了。您可以按照最不重要的属性到最重要的属性的顺序链接 sortBy 调用。在下面的代码创建的排序患者一个新的数组名称中RoomNumber从原来的阵列称为患者。
var sortedPatients = _.chain(patients)
.sortBy('Name')
.sortBy('RoomNumber')
.value();
回答by zobidafly
btw your initializer for patients is a bit weird, isn't it? why don't you initialize this variable as this -as a true array of objects-you can do it using _.flatten()and not as an array of arrays of single object, maybe it's typo issue):
顺便说一句,您的患者初始化程序有点奇怪,不是吗?为什么不将这个变量初始化为这个 - 作为一个真正的对象数组- 你可以使用_.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}
];
I sorted the list differently and add Kiko into Lisa's bed; just for fun and see what changes would be done...
我对清单进行了不同的排序,并将 Kiko 添加到 Lisa 的床上;只是为了好玩,看看会发生什么变化......
var sorted = _(patients).sortBy(
function(patient){
return [patient.roomNumber, patient.bedNumber, patient.name];
});
inspect sorted and you'll see this
检查排序,你会看到这个
[
{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}
]
so my answer is : use an array in your callback functionthis is quite similar to Dan Tao's answer, I just forget the join (maybe because I removed the array of arrays of unique item :))
Using your data structure, then it would be :
所以我的答案是:在你的回调函数中使用一个数组,这与Dan Tao的答案非常相似,我只是忘记了 join(可能是因为我删除了唯一项的数组数组 :))
使用你的数据结构,然后它将是 :
var sorted = _(patients).chain()
.flatten()
.sortBy( function(patient){
return [patient.roomNumber,
patient.bedNumber,
patient.name];
})
.value();
and a testload would be interesting...
测试负载会很有趣......
回答by Andrew Newdigate
None of these answers are ideal as a general purpose method for using multiple fields in a sort. All of the approaches above are inefficient as they either require sorting the array multiple times (which, on a large enough list could slow things down a lot) or they generate huge amounts of garbage objects that the VM will need to cleanup (and ultimately slowing the program down).
这些答案都不是在排序中使用多个字段的通用方法的理想选择。上述所有方法都是低效的,因为它们要么需要对数组进行多次排序(这在足够大的列表上可能会减慢很多速度),要么会生成大量的垃圾对象,VM 需要清理这些对象(并最终减慢速度)程序关闭)。
Here's a solution that is fast, efficient, easily allows reverse sorting, and can be used with underscore
or lodash
, or directly with Array.sort
这是一个快速、高效、轻松允许反向排序的解决方案,可以与underscore
或一起使用lodash
,或直接与Array.sort
The most important part is the compositeComparator
method, which takes an array of comparator functions and returns a new composite comparator function.
最重要的部分是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);
});
}
You'll also need a comparator function for comparing the fields you wish to sort on. The naturalSort
function will create a comparator given a particular field. Writing a comparator for reverse sorting is trivial too.
您还需要一个比较器函数来比较您希望排序的字段。该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;
}
}
(All the code so far is reusable and could be kept in utility module, for example)
(到目前为止,所有代码都是可重用的,例如可以保存在实用程序模块中)
Next, you need to create the composite comparator. For our example, it would look like this:
接下来,您需要创建复合比较器。对于我们的示例,它看起来像这样:
var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]);
This will sort by room number, followed by name. Adding additional sort criteria is trivial and does not affect the performance of the sort.
这将按房间号排序,然后是名称。添加额外的排序条件是微不足道的,不会影响排序的性能。
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);
Returns the following
返回以下内容
[ { name: 'Chris', roomNumber: 1, bedNumber: 1 },
{ name: 'Lisa', roomNumber: 2, bedNumber: 2 },
{ name: 'Omar', roomNumber: 2, bedNumber: 1 },
{ name: 'John', roomNumber: 3, bedNumber: 1 } ]
The reason I prefer this method is that it allows fast sorting on an arbitrary number of fields, does not generate a lot of garbage or perform string concatenation inside the sort and can easily be used so that some columns are reverse sorted while order columns use natural sort.
我更喜欢这种方法的原因是它允许对任意数量的字段进行快速排序,不会产生大量垃圾或在排序中执行字符串连接,并且可以轻松使用,以便某些列反向排序而顺序列使用自然种类。
回答by Kevin Danikowski
Simple Examplefrom http://janetriley.net/2014/12/sort-on-multiple-keys-with-underscores-sortby.html(courtesy of @MikeDevenney)
来自http://janetriley.net/2014/12/sort-on-multiple-keys-with-underscores-sortby.html 的简单示例(@MikeDevenney 提供)
Code
代码
var FullySortedArray = _.sortBy(( _.sortBy(array, 'second')), 'first');
With Your Data
使用您的数据
var FullySortedArray = _.sortBy(( _.sortBy(patients, 'roomNumber')), 'name');
回答by Lucky Soni
Just return an array of propertiesyou want to sort with:
只需返回要排序的属性数组:
ES6 Syntax
ES6 语法
var sortedArray = _.sortBy(patients, patient => [patient[0].name, patient[1].roomNumber])
ES5 Syntax
ES5 语法
var sortedArray = _.sortBy(patients, function(patient) {
return [patient[0].name, patient[1].roomNumber]
})
This does not have any side effects of converting a number to a string.
这对将数字转换为字符串没有任何副作用。
回答by Mike K
Perhaps underscore.js or just Javascript engines are different now than when these answers were written, but I was able to solve this by just returning an array of the sort keys.
也许 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
In action, please see this fiddle: https://jsfiddle.net/mikeular/xenu3u91/
在行动中,请看这个小提琴:https: //jsfiddle.net/mikeular/xenu3u91/
回答by ZhangYi
I think you'd better use _.orderBy
instead of sortBy
:
我认为你最好使用_.orderBy
而不是sortBy
:
_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])
回答by Mark Sherretta
You could concatenate the properties you want to sort by in the iterator:
您可以在迭代器中连接要排序的属性:
return [patient[0].roomNumber,patient[0].name].join('|');
or something equivalent.
或等价的东西。
NOTE: Since you are converting the numeric attribute roomNumber to a string, you would have to do something if you had room numbers > 10. Otherwise 11 will come before 2. You can pad with leading zeroes to solve the problem, i.e. 01 instead of 1.
注意:由于您将数字属性 roomNumber 转换为字符串,因此如果您的房间号 > 10,则必须执行某些操作。否则 11 将出现在 2 之前。您可以用前导零填充来解决问题,即 01 而不是1.