Gạch dưới: sortBy () dựa trên nhiều thuộc tính


115

Tôi đang cố gắng 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. Tức là nếu thuộc tính thứ nhất giống nhau giữa hai đối tượng thì nên sử dụng thuộc tính thứ hai để comapare hai đối tượng. Ví dụ, hãy xem xét các mảng sau:

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

Sắp xếp chúng theo roomNumberthuộc tính tôi sẽ sử dụng đoạn mã sau:

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

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

Câu trả lời:


250

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

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

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


12
Có một bài viết trên blog mở rộng về điều này và bao gồm thông tin tốt về việc sắp xếp các thuộc tính tăng dần và giảm dần.
Alex C

4
Một giải pháp đơn giản hơn để sắp xếp chuỗi có thể được tìm thấy ở đây . Công bằng mà nói, có vẻ như bài viết trên blog đã được viết sau khi những câu trả lời này được đưa ra, nhưng nó đã giúp tôi tìm ra điều này sau khi cố gắng sử dụng mã trong câu trả lời ở trên và thất bại.
Mike Devenney

1
Bạn có chắc bệnh nhân [0] .name và bệnh nhân [1] .roomNumber nên có chỉ số ở đó? bệnh nhân không phải là một mảng ...
StinkyCat

Bộ [0]chỉ mục được yêu cầu bởi vì trong ví dụ ban đầu, patientslà một mảng các mảng. Đây cũng là lý do tại sao "giải pháp đơn giản hơn" trong bài đăng trên blog được đề cập trong một bình luận khác sẽ không hoạt động ở đây.
Rory MacLeod

1
@ac_fire Đây là một kho lưu trữ liên kết đã chết: archive.is/tiatQ
lustig

52

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

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

Tuy nhiên, như tôi đã nói, đó là khá hack. Để làm điều này đúng cách, có lẽ bạn muốn sử dụng phương thức JavaScript cốt lõisort :

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, điều này sẽ sắp xếp mảng của bạn tại chỗ. Nếu bạn muốn một bản sao được sắp xếp (như _.sortBysẽ cung cấp cho bạn), hãy sao chép mảng trước:

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

Vì 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 khóa tùy ý nào) cho việc này: hãy xem .


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 cái thứ hai vì các thuộc tính của tôi có thể là cả chuỗi và số. Vì vậy, dường như không có một cách đơn giản để sắp xếp các mảng?
Christian R

3
Tại sao không return [patient[0].roomNumber, patient[0].name];đủ mà không có join?
Csaba Toth

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 thông qua máy chủ proxy của chúng tôi). Bạn có thể vui lòng gửi nó ở đây?
Zev Spitz

Ngoài ra, như thế nào comparegiá trị xử lý mà không phải là những giá trị nguyên thủy - undefined, nullhoặc đối tượng đồng bằng?
Zev Spitz

FYI hack này chỉ hoạt động nếu bạn đảm bảo rằng độ dài str của mỗi giá trị là giống nhau cho tất cả các mục trong mảng.
miex

32

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

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

4
Mặc dù bạn đến muộn nhưng bạn vẫn đúng :) Cảm ơn!
Allan Jikamu

3
Đẹp, rất sạch sẽ.
Jason Turan

11

btw trình khởi tạo của bạn cho bệnh nhân là một chút 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 các đối tượng thực sự - bạn có thể thực hiện nó bằng cách sử dụng _.flatten () chứ không phải là một mảng các mảng của một đối tượng, có thể đó là vấn đề 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 nhau 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}
]

Vì vậy, câu trả lời của tôi là: sử dụng một mảng trong hàm gọi lại của bạn, nó khá giống với câu trả lời của Dan Tao , tôi chỉ quên tham gia (có thể vì tôi đã xóa mảng của mảng 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à một tải thử nghiệm sẽ rất thú vị ...


Nghiêm túc mà nói, đó là câu trả lời
Radek Duchoň

7

Không có câu trả lời nào trong số này là lý tưởng cho phương pháp mục đích chung để sử dụng nhiều trường trong một loại. Tất cả các cách tiếp cận ở 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 danh sách đủ lớn có thể làm chậm mọi thứ) hoặc chúng tạo ra một lượng lớn các đối tượng rác mà VM sẽ cần dọn dẹp (và cuối cùng là làm chậm chương trình xuống).

Đây là một giải pháp nhanh chóng, hiệu quả, dễ dàng cho phép sắp xếp ngược và có thể được sử dụng với underscorehoặc lodash, hoặc trực tiếp vớiArray.sort

Phần quan trọng nhất là compositeComparatorphương thức, lấy một mảng các hàm so sánh và trả về một hàm so sánh hỗn 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. Các naturalSortchức năng sẽ tạo ra một so sánh được đưa ra một lĩnh vực cụ thể. Viết một bộ so sánh để sắp xếp ngược cũng là chuyện nhỏ.

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

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

Tiếp theo, bạn cần tạo bộ so sánh tổng hợp. Ví dụ của chúng tôi, nó sẽ trông như thế này:

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à không đáng kể và không ảnh hưởng đến hiệu suất của loại.

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ề như 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à vì nó cho phép sắp xếp nhanh trên một số trường tùy ý, không tạo ra nhiều rác hoặc thực hiện nối chuỗi bên trong sắp xếp và có thể dễ dàng sử dụng để một số cột được sắp xếp ngược trong khi các cột theo thứ tự sử dụng sắp xếp



2

Có lẽ bây giờ underscore.js hoặc chỉ các công cụ Javascript khác với khi các câu trả lời này được viết, nhưng tôi đã có thể giải quyết điều này bằng cá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 thực tế, vui lòng xem fiddle này: https://jsfiddle.net/mikeular/xothy3u91/


2

Chỉ cần trả về một mảng các thuộc tính bạn muốn sắp xếp với:

Cú pháp ES6

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

Cú pháp ES5

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 của việc chuyển đổi một số thành một chuỗi.


1

Bạn có thể nối các thuộc tính bạn muốn sắp xếp 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 phòng thuộc tính sốSố thành chuỗi, bạn sẽ phải làm 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 với các số 0 đứng đầu để giải quyết vấn đề, tức là 01 thay vì 1.


1

Tôi nghĩ rằng bạn nên sử dụng tốt hơn _.orderBythay vì sortBy:

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

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

1
Không có orderBy trong gạch dưới.
AfroMogli

1
_.orderByhoạt động, nhưng nó là một phương thức của thư viện lodash, không phải gạch dưới: 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ể phù hợp với OP.
Mike K

0

Nếu bạn tình cờ sử dụng Angular, bạn có thể sử dụng bộ lọc số của nó trong tệp html thay vì thêm bất kỳ trình xử lý JS hoặc CSS nào. Ví dụ:

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

Trong ví dụ đó, nếu val = 1234567, nó sẽ được hiển thị dưới dạng

  No fractions: 1,234,567

Ví dụ và hướng dẫn thêm tại: https://docs.angularjs.org/api/ng/filter/number

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.