我正在尝试使用基于多个属性的对象对数组进行排序。即,如果两个对象之间的第一个属性相同,则应使用第二个属性来共同映射这两个对象。例如,请考虑以下数组:
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
属性对这些进行排序我将使用以下代码:
var sortedArray = _.sortBy(patients, function(patient) {
return patient[0].roomNumber;
});
这样做很好,但我该如何处理以便'John'和'Lisa'能够正确分类?
答案 0 :(得分:241)
sortBy
表示它是一种稳定的排序算法,因此您应该能够先按第二个属性排序,然后再按第一个属性排序,如下所示:
var sortedArray = _(patients).chain().sortBy(function(patient) {
return patient[0].name;
}).sortBy(function(patient) {
return patient[0].roomNumber;
}).value();
当第二个sortBy
发现John和Lisa具有相同的房间号时,它将按照它找到的顺序保留它们,第一个sortBy
设置为“Lisa,John”。
答案 1 :(得分:49)
这是我在这些情况下有时使用的一个hacky技巧:以可以排序结果的方式组合属性:
var sortedArray = _.sortBy(patients, function(patient) {
return [patient[0].roomNumber, patient[0].name].join("_");
});
但是,正如我所说的那样,这很糟糕。要正确执行此操作,您可能希望实际使用the core JavaScript sort
method:
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;
}
当然,这个会对您的数组进行排序。如果你想要一个排序的副本(比如_.sortBy
会给你),首先克隆数组:
function sortOutOfPlace(sequence, sorter) {
var copy = _.clone(sequence);
copy.sort(sorter);
return copy;
}
出于无聊,我刚刚写了一个通用解决方案(按任意数量的键排序):have a look。
答案 2 :(得分:28)
我知道我迟到了,但我想为那些需要更清洁,更快速解决方案的人添加这个。您可以按最不重要的属性将sortBy调用链接到最重要的属性。在下面的代码中,我创建了一个新的患者数组,其中名称在 RoomNumber 中从名为 patients 的原始数组中排序。
var sortedPatients = _.chain(patients)
.sortBy('Name')
.sortBy('RoomNumber')
.value();
答案 3 :(得分:10)
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添加到Lisa的床上;只是为了好玩,看看会做些什么改变......
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}
]
所以我的答案是:在回调函数中使用数组
这与 Dan Tao 的答案非常相似,我只是忘记了连接(也许是因为我删除了独特项目的数组数组:))
使用您的数据结构,然后是:
var sorted = _(patients).chain()
.flatten()
.sortBy( function(patient){
return [patient.roomNumber,
patient.bedNumber,
patient.name];
})
.value();
并且测试负载会很有趣......
答案 4 :(得分:5)
这些答案都不是理想的,因为它是在排序中使用多个字段的通用方法。上面的所有方法效率都很低,因为它们要么需要多次对数组进行排序(在足够大的列表中可能会使速度降低很多),或者它们会生成大量的垃圾对象,这些垃圾对象需要清理(最终会减慢)该计划下来)。
这是一个快速,高效,易于反向排序的解决方案,可以与underscore
或lodash
一起使用,也可以直接与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);
});
}
您还需要一个比较器功能来比较您希望排序的字段。 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;
}
}
(到目前为止所有代码都是可重用的,例如可以保存在实用程序模块中)
接下来,您需要创建复合比较器。对于我们的示例,它看起来像这样:
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 } ]
我更喜欢这种方法的原因是它允许在任意数量的字段上快速排序,不会产生大量垃圾或在排序中执行字符串连接,并且可以很容易地使用,以便在命令时对某些列进行反向排序列使用自然排序。
答案 5 :(得分:3)
简单示例(由@MikeDevenney提供)
<强>代码强>
var FullySortedArray = _.sortBy(( _.sortBy(array, 'second')), 'first');
使用您的数据
var FullySortedArray = _.sortBy(( _.sortBy(patients, 'roomNumber')), 'name');
答案 6 :(得分:2)
也许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/
答案 7 :(得分:2)
只需返回要排序的属性数组:
ES6语法
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]
})
这没有将数字转换为字符串的任何副作用。
答案 8 :(得分:1)
您可以在迭代器中连接要排序的属性:
return [patient[0].roomNumber,patient[0].name].join('|');
或类似的东西。
注意:由于您要将数字属性roomNumber转换为字符串,因此如果您有房间号&gt;则必须执行某些操作。 10.否则11将在2之前到来。您可以使用前导零填充来解决问题,即01而不是1.
答案 9 :(得分:0)
我认为您最好使用_.orderBy
代替sortBy
:
_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])
答案 10 :(得分:0)
如果碰巧正在使用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
答案 11 :(得分:0)
我不认为大多数答案真的有效,当然没有一个有效并且同时使用纯粹的下划线。
此答案提供了对多列的排序,并且能够在一个函数中反转其中一些列的排序顺序。
它还一步一步地构建在最终代码的基础上,因此您可能需要采用最后一个代码片段:
我已经将这个仅用于两列(首先按 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']);
如果您也希望能够反转列排序:
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; }]);