2013-05-07 62 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] 
}) 

這沒有將數字轉換爲字符串的任何副作用。