2013-05-07 36 views
90

Tôi đang cố sắp xếp một mảng với các đối tượng dựa trên nhiều thuộc tính. I.e nếu thuộc tính đầu tiên là giống nhau giữa hai đối tượng, một thuộc tính thứ hai nên được sử dụng để comapare hai đối tượng. Ví dụ, hãy xem xét các mảng sau:Dấu gạch dưới: sortBy() dựa trên nhiều thuộc tính

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

Phân loại này bằng các roomNumber thuộc tính tôi sẽ sử dụng đoạn mã sau:

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

này hoạt động tốt, nhưng làm thế nào để tôi tiến hành để 'John' và 'Lisa' sẽ được sắp xếp đúng cách?

Trả lời

215

sortBy nói rằng nó là một thuật toán sắp xếp ổn định, do đó bạn sẽ có thể để sắp xếp theo bất động sản thứ hai của bạn đầu tiên, sau đó sắp xếp lại bằng cách sở hữu đầu tiên của bạn, như thế này:

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

Khi thứ hai sortBy phát hiện rằng John và Lisa có cùng số phòng, nó sẽ giữ chúng theo thứ tự tìm thấy chúng, trong đó sortBy đầu tiên được đặt thành "Lisa, John".

+11

Có một [bài đăng blog] (http://blog.falafel.com/nifty-underscore-tricks-sorting-by-multiple-properties-with-underscore/) mở rộng về điều này và bao gồm thông tin tốt về sắp xếp tăng dần và thuộc tính giảm dần. –

+8

+1: Tôi nghĩ rằng đây phải là câu trả lời được chấp nhận: o) – Andrew

+1

Chỉ là những gì tôi đang tìm kiếm. Cảm ơn bạn! –

1

Bạn có thể nối các thuộc tính bạn muốn sắp xếp bởi trong iterator:

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

hoặc một cái gì đó tương đương.

LƯU Ý: Vì bạn đang chuyển đổi thuộc tính số roomNumber thành chuỗi, bạn sẽ phải làm điều gì đó nếu bạn có số phòng> 10. Nếu không 11 sẽ đến trước 2. Bạn có thể đệm bằng số 0 để giải quyết vấn đề, tức là 01 thay vì 1.

46

Dưới đây là một thủ thuật hacky đôi khi tôi sử dụng trong những trường hợp: kết hợp các thuộc tính theo cách như vậy mà kết quả sẽ là sắp xếp được:

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

Tuy nhiên, như tôi đã nói, đó là khá hacky. Để làm điều này đúng cách bạn có thể muốn thực sự sử dụng 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; 
} 

Tất nhiên, này sẽ sắp xếp mảng cố định một chỗ. Nếu bạn muốn có một bản sao được sắp xếp (như _.sortBy sẽ cung cấp cho bạn), sao chép các mảng đầu tiên:

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

Out of nhàm chán, tôi chỉ viết một giải pháp chung (để sắp xếp theo bất kỳ số lượng tùy ý các phím) cho việc này cũng : have a look.

+0

Cảm ơn rất nhiều vì giải pháp này đã kết thúc bằng cách sử dụng giải pháp thứ hai vì thuộc tính của tôi có thể là cả chuỗi và số. Vì vậy, có vẻ như không phải là một cách bản địa đơn giản để sắp xếp mảng? –

+3

Tại sao không chỉ 'trả về [bệnh nhân [0]. RoomNumber, bệnh nhân [0] .name];' đủ mà không có 'tham gia'? –

+1

Liên kết đến giải pháp chung của bạn dường như bị hỏng (hoặc có lẽ tôi không thể truy cập nó thông qua máy chủ proxy của chúng tôi). Bạn có thể đăng nó ở đây không? –

9

btw trình khởi tạo của bạn cho bệnh nhân hơi lạ, phải không? tại sao bạn không khởi tạo biến này vì đây là một mảng thực của đối tượng-bạn có thể làm điều đó bằng cách sử dụng _.flatten() và không phải là mảng mảng của đối tượng đơn lẻ, có thể là lỗi chính tả):

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

Tôi đã sắp xếp danh sách khác và thêm Kiko vào giường của Lisa; chỉ để cho vui và xem những thay đổi sẽ được thực hiện ...

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

kiểm tra sắp xếp và bạn sẽ thấy điều này

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

nên câu trả lời của tôi là: sử dụng một mảng trong hàm callback này khá giống với câu trả lời Dan Tao 's, Tôi chỉ quên tham gia (có thể vì tôi đã xóa mảng mảng mục duy nhất :))
Sử dụng cấu trúc dữ liệu của bạn, sau đó sẽ là:

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

và testload Sẽ rất thú vị ...

17

Tôi biết tôi là muộn để đảng, nhưng tôi muốn để thêm video này cho những người cần một-er sạch và giải pháp nhanh chóng-er mà những người đã đề xuất. Bạn có thể chuỗi các cuộc gọi sortBy theo thứ tự tài sản ít quan trọng nhất đối với tài sản quan trọng nhất. Trong mã bên dưới, tôi tạo một mảng bệnh nhân mới được sắp xếp theo Tên trong phạm vi RoomNumber từ mảng ban đầu được gọi là bệnh nhân.

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

Mặc dù bạn đến trễ bạn vẫn đúng :) Cảm ơn! –

+0

Đẹp, rất sạch sẽ. –

5

Không có câu trả lời nào là lý tưởng để sử dụng nhiều trường trong một sắp xếp. Tất cả các phương pháp trên đều không hiệu quả vì chúng yêu cầu sắp xếp mảng nhiều lần (trong đó, trên một danh sách đủ lớn có thể làm chậm nhiều thứ) hoặc chúng tạo ra một lượng lớn các đối tượng rác mà VM cần phải dọn dẹp (và cuối cùng làm chậm chương trình xuống).

Dưới đây là một giải pháp mà là nhanh chóng, hiệu quả, dễ dàng cho phép đảo ngược phân loại, và có thể được sử dụng với underscore hoặc lodash, hoặc trực tiếp với Array.sort

Phần quan trọng nhất là compositeComparator phương pháp, trong đó có một loạt các so sánh và trả về một hàm so sánh tổng hợp mới.

/** 
* 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); 
    }); 
} 

Bạn cũng sẽ cần một hàm so sánh để so sánh các trường bạn muốn sắp xếp. Hàm naturalSort sẽ tạo một trình so sánh cho một trường cụ thể. Viết một so sánh để phân loại ngược là tầm thường quá.

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

(Tất cả các mã cho đến nay là tái sử dụng và có thể được giữ trong mô-đun tiện ích, ví dụ)

Tiếp theo, bạn cần phải tạo ra các so sánh composite. Ví dụ của chúng tôi, nó sẽ giống như sau:

var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]); 

Điều này sẽ sắp xếp theo số phòng, theo sau là tên. Thêm tiêu chí sắp xếp bổ sung là tầm thường và không ảnh hưởng đến hiệu suất sắp xếp.

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

Trả về sau

[ { name: 'Chris', roomNumber: 1, bedNumber: 1 }, 
    { name: 'Lisa', roomNumber: 2, bedNumber: 2 }, 
    { name: 'Omar', roomNumber: 2, bedNumber: 1 }, 
    { name: 'John', roomNumber: 3, bedNumber: 1 } ] 

Lý do tôi thích phương pháp này là nó cho phép nhanh chóng sắp xếp trên một số tùy ý các lĩnh vực, không tạo ra rất nhiều rác hoặc thực hiện chuỗi nối bên trong loại và có thể dễ dàng được sử dụng để một số cột được sắp xếp ngược lại trong khi các cột thứ tự sử dụng sắp xếp tự nhiên.

-1

Tôi nghĩ rằng bạn nên sử dụng _.orderBy thay vì sortBy:

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

Bạn có chắc chắn orderBy đang ở dưới dấu gạch dưới không? Tôi không thể thấy trong tài liệu hoặc tệp .d.ts của tôi. –

+1

Không có đơn đặt hàngBằng gạch dưới. – AfroMogli

+0

'_.orderBy' hoạt động, nhưng nó là một phương pháp của thư viện lodash, không gạch dưới: https://lodash.com/docs/4.17.4#orderBy lodash chủ yếu là một thay thế thả xuống cho gạch dưới, vì vậy nó có thể thích hợp cho OP. –

1

lẽ underscore.js hay chỉ là động cơ Javascript Bây giờ khác hơn khi những câu trả lời được viết, nhưng tôi đã có thể giải quyết điều này bằng cách chỉ trả về một mảng các phím sắp xếp.

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 

Trong hành động, vui lòng xem fiddle này: https://jsfiddle.net/mikeular/xenu3u91/

0

Chỉ trở lại một mảng bất động sản bạn muốn sắp xếp với:

ES6 Cú pháp

var sortedArray = _.sortBy(patients, patient => [patient[0].name, patient[1].roomNumber]) 

ES5 Cú pháp

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

Điều này không có bất kỳ tác dụng phụ nào khi chuyển đổi một số thành chuỗi.

Các vấn đề liên quan