2013-05-07 60 views
90

我想用基于多个属性的对象对数组进行排序。即,如果两个对象之间的第一个属性相同,则应该使用第二个属性来共同匹配这两个对象。例如,请考虑以下数组:下划线:基于多个属性的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}] 
       ]; 

roomNumber属性我会用下面的代码排序这些:

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

这工作得很好,但我怎么继续让“约翰”和'丽莎'会被正确排序?

回答

215

sortBy说,这是一个稳定的排序算法,所以你应该能够通过你的第二个属性在前排序再由你的第一个属性进行排序,那么,这样的:

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

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

+11

有一个[博客文章](http://blog.falafel.com/nifty-underscore-tricks-sorting-by-multiple-properties-with-underscore/)扩展了这一点,并包括有关排序升序和降序属性。 – 2014-10-07 15:13:51

+8

+1:我认为这应该是被接受的答案:o) – Andrew 2014-10-21 04:05:32

+1

正是我所期待的。谢谢! – 2015-11-07 15:14:30

1

你可以串连要在迭代器作为排序依据的属性:

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

或等价的东西。

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

46

这里有一个哈克把戏我有时在这些情况下使用:以这样的方式,其结果将是可排序的组合属性:

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

+0

非常感谢这个解决方案使用第二个解决方案,因为我的属性可能是字符串和数字。因此,似乎没有简单的本地方式来排列数组? – 2013-05-11 11:00:31

+3

为什么不仅仅是'return [patient [0] .roomNumber,patient [0] .name];''没有'join'就足够了? – 2014-06-25 20:53:18

+1

您的一般解决方案的链接似乎已损坏(或者我无法通过我们的代理服务器访问它)。你能把它张贴在这里吗? – 2016-04-13 09:53:50

9

btw您的患者初始化程序有点奇怪,不是吗? 为什么你不要这个变量初始化为 - 这是一个真正的对象数组 - 你可以使用_.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} 
     ]; 

我按照不同的方式对列表进行排序,并将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} 
] 

所以我的答案是:在回调函数 这是相当类似丹道的答案使用数组,我只是忘了加入(也许是因为我删除了独特项目数组的阵列:))
使用你的数据结构,那么它将是:

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

和testload将是有趣......

17

我知道我迟到了,但我想添加这个对于那些有需要的清洁-ER和快速ER解决方案那些已经建议。您可以按重要性最低的属性的顺序将sortBy调用链接到最重要的属性。在下面的代码中,我创建了一个新的数组排序的名称RoomNumber从原始数组中调用患者

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

即使你迟到了,你仍然是正确的:)谢谢! – 2016-08-22 12:12:40

+0

不错,很干净。 – 2017-09-05 21:55:01

5

这些答案都不是理想的通用方法,用于在排序中使用多个字段。上述所有方法效率不高,因为它们要么需要多次对数组进行排序(在足够大的列表中可能会使事情减慢很多),或者它们会产生大量的虚拟机清理所需的垃圾对象(并且最终会减慢该程序下降)。

这里的一个解决方案,快速,高效,容易地允许反向排序,并且可以与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); 
    }); 
} 

你还需要一个比较器函数来比较你想排序的字段。 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 } ] 

我喜欢这种方法是,它允许快速排序的字段任意数量,不会产生大量的垃圾或执行排序中字符串连接的原因并且可以轻松地使用,以便某些列反向排序,而订单列使用自然排序。

-1

我想你最好使用_.orderBy代替sortBy

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

你确定orderBy是下划线吗?我无法在文档或我的.d.ts文件中看到它。 – 2016-10-27 23:16:47

+1

下划线中没有orderBy。 – AfroMogli 2017-03-21 14:14:04

+0

'_.orderBy'工作,但它是lodash库的一种方法,不是下划线:https://lodash.com/docs/4.17.4#orderBy lodash主要是下划线的替代替换,所以它可能适用于OP。 – 2017-08-24 04:38:07

1

也许underscore.js或只是J​​avaScript引擎是不同的,现在当这些答案写比,但我可以通过正好解决了这一返回排序键的数组。

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/

0

只是回报要使用排序属性数组:

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

这没有将数字转换为字符串的任何副作用。