下划线: 基于多个属性的 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}]
];

Sorting these by the roomNumber attribute i would use the following code:

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?

102433 次浏览

您可以在迭代器中连接您想要排序的属性:

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

或者类似的东西。

注意: 因为要将数字属性 room Number 转换为字符串,所以如果房间号大于10,则必须执行某些操作。否则11会在2之前到来。你可以用前导零填充来解决这个问题,比如01而不是1。

这里有一个我在这些情况下经常使用的技巧: 以这样一种方式组合属性,结果将是可排序的:

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

Of course, 这个 will sort your array in place. If you want a sorted copy (like _.sortBy would give you), clone the array first:

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

无聊之余,我还为此编写了一个通用解决方案(按任意数量的键进行排序) : 看看

sortBy表示它是一个稳定的排序算法,因此您应该能够首先按照第二个属性进行排序,然后再按照第一个属性进行排序,如下所示:

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

当第二个 sortBy发现约翰和丽莎有相同的房间号码,它将保持他们的顺序发现他们,第一个 sortBy设置为“丽莎,约翰”。

顺便说一句,你的病人初始化程序有点奇怪,不是吗? 为什么不使用 将这个变量初始化为 this-作为一个真正的对象数组呢? 你可以使用 压扁(),而不是使用单个对象的数组,这可能是输入错误的问题) :

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

我对清单进行了不同的分类,并将 Kiko 添加到丽莎的床上; 只是为了好玩,看看会做什么改变... ..。

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

so my answer is : use an array in your callback function 这与 Dan Tao的答案非常相似,我只是忘记了连接(可能是因为我删除了惟一条目的数组数组:)
Using your data structure, then it would be :

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

测试一下会很有意思。

我知道我迟到了,但是我想为那些需要一个更干净、更快捷的解决方案的人补充这一点,那些人已经建议过了。可以将 sortBy 调用按最不重要的属性的顺序链接到最重要的属性。在下面的代码中,我从称为 病人的原始数组中,在 房间号中创建了一个新的按 姓名排序的患者数组。

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

这些答案都不适合作为在排序中使用多个字段的通用方法。上述所有方法都是低效的,因为它们要么需要对数组进行多次排序(在一个足够大的列表中,这会大大降低工作速度) ,要么会产生大量垃圾对象,VM 需要清理这些垃圾对象(并最终降低程序运行速度)。

这是一个快速、高效、容易实现反向排序的解决方案,可以与 underscorelodash一起使用,也可以直接与 Array.sort一起使用

The most important part is the compositeComparator method, which takes an array of comparator functions and returns a new composite comparator function.

/**
* 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.

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

我更喜欢这种方法的原因是,它允许对任意数量的字段进行快速排序,不会产生大量垃圾或在排序中执行字符串串联,并且可以很容易地使用,这样一来,一些列可以进行反向排序,而顺序列使用自然排序。

我认为你最好使用 _.orderBy而不是 sortBy:

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

也许 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

实际操作中,请看这个小提琴: https://jsfiddle.net/mikeular/xenu3u91/

来自 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');

你只需要用 返回属性数组排序:

ES6 Syntax

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

这不会产生将数字转换为字符串的任何副作用。

如果您正好使用 Angular,您可以在 html 文件中使用它的数字过滤器,而不是添加任何 JS 或 CSS 处理程序。例如:

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

在这个示例中,如果 val = 1234567,它将显示为

  No fractions: 1,234,567

示例和进一步指导: Https://docs.angularjs.org/api/ng/filter/number

我不认为大多数的答案真的工作,当然没有工作,并使用单纯的下划线在同一时间。

This answer provides sorting for multiple columns, with the ability to reverse the sort order for some of them, all in one function.

它还逐步构建在最终代码的基础上,因此您可能希望获取最后的代码片段:


我使用了这个 只有两栏(首先按 a排序,然后按 b排序) :

var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
_.chain(array)
.groupBy(function(i){ return i.a;})
.map(function(g){ return _.chain(g).sortBy(function(i){ return i.b;}).value(); })
.sortBy(function(i){ return i[0].a;})
.flatten()
.value();

结果如下:

0: {a: 1, b: 0}
1: {a: 1, b: 1}
2: {a: 1, b: 3}
3: {a: 2, b: 2}

我相信这可以推广到两个以上的..。


另一个可能更快的版本:

var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
_.chain(array)
.sortBy(function(i){ return i.a;})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0].a !== i.a) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return _.chain(i).sortBy(function(j){ return j.b; }).value();})
.flatten()
.value();

还有一个参数化的版本:

var array = [{a:1, b:1}, {a:1, b:0}, {a:2, b:2}, {a:1, b:3}];
function multiColumnSort(array, columnNames) {
var col0 = columnNames[0],
col1 = columnNames[1];
return _.chain(array)
.sortBy(function(i){ return i[col0];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][col0] !== i[col0]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return _.chain(i).sortBy(function(j){ return j[col1]; }).value();})
.flatten()
.value();
}
multiColumnSort(array, ['a', 'b']);

还有一个参数化版本 对于任意数量的列(似乎可以通过第一次测试实现) :

var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if(!columnNames || !columnNames.length || array.length === 1) return array;
var col0 = columnNames[0];
if(columnNames.length == 1) return _.chain(array).sortBy(function(i){ return i[col0]; }).value();
    

return _.chain(array)
.sortBy(function(i){ return i[col0];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][col0] !== i[col0]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return multiColumnSort(i, _.rest(columnNames, 1));})
.flatten()
.value();
}
multiColumnSort(array, ['a', 'b', 'c']);

If you want to be able to 倒车 the column sorting too:

var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if(!columnNames || !columnNames.length || array.length === 1) return array;
var col = columnNames[0],
isString = !!col.toLocaleLowerCase,
colName = isString ? col : col.name,
reverse = isString ? false : col.reverse,
multiplyWith = reverse ? -1 : +1;
if(columnNames.length == 1) return _.chain(array).sortBy(function(i){ return multiplyWith * i[colName]; }).value();
    

return _.chain(array)
.sortBy(function(i){ return multiplyWith * i[colName];})
.reduce(function(prev, i){
var ix = prev.length - 1;
if(!prev[ix] || prev[ix][0][colName] !== i[colName]) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function(i){ return multiColumnSort(i, _.rest(columnNames, 1));})
.flatten()
.value();
}
multiColumnSort(array, ['a', {name:'b', reverse:true}, 'c']);

还支持职能:

var array = [{a:1, b:1, c:9}, {a:1, b:1, c:3}, {a:2, b:2, c:10}, {a:1, b:3, c:0}];
function multiColumnSort(array, columnNames) {
if (!columnNames || !columnNames.length || array.length === 1) return array;
var col = columnNames[0],
isString = !!col.toLocaleLowerCase,
isFun = typeof (col) === 'function',
colName = isString ? col : col.name,
reverse = isString || isFun ? false : col.reverse,
multiplyWith = reverse ? -1 : +1,
sortFunc = isFun ? col : function (i) { return multiplyWith * i[colName]; };


if (columnNames.length == 1) return _.chain(array).sortBy(sortFunc).value();


return _.chain(array)
.sortBy(sortFunc)
.reduce(function (prev, i) {
var ix = prev.length - 1;
if (!prev[ix] || (isFun ? sortFunc(prev[ix][0]) !== sortFunc(i) : prev[ix][0][colName] !== i[colName])) {
prev.push([]); ix++;
}
prev[ix].push(i);
return prev;
}, [])
.map(function (i) { return multiColumnSort(i, _.rest(columnNames, 1)); })
.flatten()
.value();
}
multiColumnSort(array, ['a', {name:'b', reverse:true}, function(i){ return -i.c; }]);