밑줄 : 여러 속성을 기반으로하는 sortBy ()


115

여러 속성을 기반으로하는 개체로 배열을 정렬하려고합니다. 즉, 첫 번째 특성이 두 개체간에 동일하면 두 개체를 비교하는 데 두 번째 특성을 사용해야합니다. 예를 들어, 다음 배열을 고려하십시오.

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'가 올바르게 정렬되도록 어떻게 진행합니까?

답변:


250

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"으로 설정된 순서대로 유지합니다 .


12
이에 대해 확장하고 오름차순 및 내림차순 속성 정렬에 대한 좋은 정보를 포함 하는 블로그 게시물 이 있습니다.
Alex C

4
체인 정렬에 대한 더 간단한 솔루션은 여기 에서 찾을 수 있습니다 . 공정하게 말하면 이러한 답변이 제공된 후 블로그 게시물이 작성된 것처럼 보이지만 위 답변의 코드를 사용하려고 시도했지만 실패한 후에 이것을 알아내는 데 도움이되었습니다.
Mike Devenney 2016

1
환자 [0] .name 및 환자 [1] .roomNumber에 색인이 있어야합니다. 환자 ... 배열이 아닌
StinkyCat

[0]원래의 실시 예에서, 인덱서는 때문에 필요한 patients배열의 배열이다. 이것이 다른 댓글에 언급 된 블로그 게시물의 "간단한 솔루션"이 여기서 작동하지 않는 이유이기도합니다.
Rory MacLeod

1
@ac_fire 여기 죽은 링크의 아카이브가 있습니다 : archive.is/tiatQ
lustig

52

다음은 이러한 경우에 가끔 사용하는 해키 트릭입니다. 결과를 정렬 할 수있는 방식으로 속성을 결합합니다.

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

그러나 내가 말했듯이 그것은 꽤 해키입니다. 이 작업을 제대로 수행하려면 실제로 핵심 JavaScript sort메서드를 사용 하고 싶을 것입니다 .

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 .


이 솔루션에 감사드립니다. 내 속성이 문자열과 숫자가 될 수 있기 때문에 두 번째 솔루션을 사용하게되었습니다. 그래서 배열을 정렬하는 간단한 기본 방법이없는 것 같습니까?
Christian R

3
return [patient[0].roomNumber, patient[0].name];없이는 왜 충분하지 join않습니까?
Csaba Toth 2014 년

1
일반 솔루션에 대한 링크가 끊어진 것 같습니다 (또는 프록시 서버를 통해 액세스 할 수 없습니다). 여기에 게시 해 주시겠습니까?
Zev Spitz

또한, 어떻게합니까 compare- 핸들 원시 값없는 값 undefined, null또는 일반 개체를?
Zev Spitz 16.

참고로이 해킹은 각 값의 str 길이가 배열의 모든 항목에 대해 동일하다는 것을 확인하는 경우에만 작동합니다.
miex

32

나는 내가 파티에 늦었다는 것을 알고 있지만 이미 제안한 더 깨끗하고 빠른 솔루션이 필요한 사람들을 위해 이것을 추가하고 싶었습니다. 가장 중요하지 않은 속성의 순서로 sortBy 호출을 가장 중요한 속성에 연결할 수 있습니다. 아래 코드에서는 원래 배열 인 RoomNumber 내 에서 Name 으로 정렬 된 새로운 환자 배열을 만듭니다. 환자 .

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

4
늦었지만 여전히 정확합니다. :) 감사합니다!
Allan Jikamu

3
아주 깨끗합니다.
Jason Turan

11

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

그래서 내 대답은 : 콜백 함수에서 배열을 사용하십시오. 이것은 Dan Tao 의 대답 과 매우 유사 합니다. 조인을 잊어 버렸습니다 (어쩌면 고유 항목의 배열 배열을 제거했기 때문에 :))
데이터 구조를 사용하여 다음과 같습니다.

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

그리고 테스트로드는 흥미로울 것입니다 ...


진심으로, 그 대답은
라덱 Duchoň

7

이러한 답변 중 어느 것도 여러 필드를 한꺼번에 사용하기위한 범용 방법으로 이상적이지는 않습니다. 위의 모든 접근 방식은 배열을 여러 번 정렬해야하거나 (충분한 목록에서 많은 속도를 늦출 수 있음) VM이 정리하는 데 필요한 엄청난 양의 가비지 개체를 생성하므로 비효율적입니다. 프로그램 다운).

다음은 빠르고 효율적이며 쉽게 역 정렬을 허용하고 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 } ]

내가이 방법을 선호하는 이유는 임의의 수의 필드에 대해 빠른 정렬이 가능하고 많은 쓰레기를 생성하지 않거나 정렬 내에서 문자열 연결을 수행하지 않으며 일부 열이 역순으로 정렬되고 정렬 열이 자연적으로 사용되도록 쉽게 사용할 수 있기 때문입니다. 종류.



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/


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

이것은 숫자를 문자열로 변환하는 부작용이 없습니다.


1

반복기에서 정렬하려는 속성을 연결할 수 있습니다.

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

또는 동등한 것.

참고 : 숫자 속성 roomNumber를 문자열로 변환하기 때문에 방 번호가 10보다 크면 무언가를해야합니다. 그렇지 않으면 11이 2보다 앞에 오게됩니다. 문제를 해결하기 위해 선행 0으로 채울 수 있습니다. 1.


1

_.orderBy대신 다음을 사용 하는 것이 좋습니다 sortBy.

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

4
orderBy가 밑줄에 있는지 확실합니까? 문서 나 .d.ts 파일에서 볼 수 없습니다.
Zachary Dow

1
밑줄에는 orderBy가 없습니다.
AfroMogli

1
_.orderBy작동하지만 밑줄이 아닌 lodash 라이브러리의 방법입니다. lodash.com/docs/4.17.4#orderBy lodash는 대부분 밑줄에 대한 드롭 인 대체이므로 OP에 적합 할 수 있습니다.
Mike K

0

Angular를 사용하는 경우 JS 또는 CSS 처리기를 추가하는 대신 html 파일에서 숫자 필터를 사용할 수 있습니다. 예를 들면 :

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

이 예에서 val = 1234567이면 다음과 같이 표시됩니다.

  No fractions: 1,234,567

https://docs.angularjs.org/api/ng/filter/number의 예제 및 추가 지침

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.