이상한 자바스크립트의 sort
자바스크립트 개발자라면, 당연히 알고있는 이상함이다.
그래서 "자바스크립트 숫자 정렬"과 같은 키워드로 인터넷을 검색해보면,
// 오름차순 정렬을 하고 싶으세요?
arr.sort((a,b)=>a-b);
// 내림차순 정렬을 하고 싶으세요?
arr.sort((a,b)=>b-a);
이렇게 딱 답이 나와있다.
우리는 평소처럼 숫자 정렬에서는 a-b는 오름차순, b-a는 내림차순이구나! 하고 써먹고 끝낸다.
이것만 알아도 사실 전혀 문제는 없다.
그러나 한번쯤은 이유도 궁금해해보자. 꽤나 배울 것이 많다.
이번 포스팅에서는, 왜 저렇게 이상한 함수를 넣어줘야 하는지를 함께 알아보도록 하자.
Sort의 근원지 MDN를 탐색하자.
자바스크립트 개발자의 필수덕목 MDN이다.
가장 먼저 보이는 글이다.
친절하게도 [1,30,4,21,100000]을 정렬하면, 여러분 마음대로 되지 않을겁니다. 라는 예시도 보여준다.
이 가장 먼저 보이는 글에 이유가 담겨있다.
"기본 정렬 순서는 문자열의 유니코드 포인트를 따릅니다."
기본적으로 배열 정렬시 원소들을 "문자열" 취급하여 "유니코드"를 따른다고 한다.
숫자를 정렬하고 있으므로, 유니코드 표에서 숫자를 찾아보자.
1 = "U+0031"
2 = "U+0032"
...
9 = "U+0039"
유니코드에 따르면, 1이 2보다 먼저 등장한다.
그러므로 정렬시 1->2->3... 순으로 오름차순 정렬되는 것이 당연하다.
그렇다면 [1, 2, 10]을 생각해보자.
1 = "U+0031"
2 = "U+0032"
10 = "U+0031U+0030"
숫자를 유니코드로 변환하면, 10의 문자열이 2보다 앞선다.
그래서 "기본 정렬 순서는 문자열의 유니코드 포인트를 따릅니다." 에 따르면 10이 먼저오게 되는 것이다.
MDN에서 다음 문구를 읽어도 동일한 내용을 볼 수 있다.
sort의 매개변수로 넣어주는 compareFunction은 Optional이다.
생략 시 "각 문자의 유니코드 값"에 따라 정렬된다.
심지어 설명에 더 친절하게 적혀있다.
"바나나"가 "체리"앞에 오고, 80이 9보다 먼저오는 예시를 들어줬다.
심지어는 "숫자는 문자열로 변환되기 때문에!" 라고 이유를 한번 더 알려준다.
이렇게 MDN을 정독하면 메서드 및 api들의 사용법 뿐 아니라, 의문도 해결할 수 있다.
sort((a,b)=>a-b)는 왜 숫자 오름차순 정렬일까?
(a,b)=>a-b가 바로 compareFunction이다.
사실 더 설명할 것도 없이 MDN을 더 읽어보면 답이 나와있다.
compareFunction를 통해 sort에 어떤식으로 원소들을 정렬할 것인지의 기준을 알려주는 것이다.
리턴값 < 0 이라면, a가 b보다 낮은 색인(앞선 인덱스)로 정렬된다.
리턴값 = 0 이라면, 정렬을 수행하지 않는다.
리턴값 > 0 이라면, a가 b보다 큰 색인(뒤 인덱스)로 정렬된다.
리턴값 = a-b이므로
리턴값이 음수라면, a<b이다. 그러므로 a가 b보다 앞서야한다.
리턴값이 0이라면, a=b이다. 그러므로 정렬이 수행되지 않아도 된다.
리턴값이 양수라면, a>b이다. 그러므로 b가 a보다 앞서야한다.
그래서 (a,b)=>a-b 라는 compareFunction이 숫자 오름차순의 기능을 수행하게 되는 것이다.
마찬가지로 (a,b)=>b-a를 대입해보면, 왜 내림차순 정렬되는지 이해할 수 있다.
자주 등장하는 정렬 사례 소개
마지막으로 자주 등장하는 사례 1가지만 살펴보자.
const list = [
{ name: 'Tom', amount: 5000000 },
{ name: 'Paul', amount: 320000 },
{ name: 'Mark', amount: 80000 },
{ name: 'Jane', amount: 5000000 },
{ name: 'Nela', amount: 5100000 },
];
은행의 입장에서, VIP 고객의 명단을 관리한다고 가정하자.
예금액을 기준으로 내림차순, 예금액이 같다면 이름순으로 오름차순 정렬하라는 지시가 내려졌다.
바로 떠오르고 해결까지 했다면 글을 닫아도 된다. ㅎㅎ.
이처럼 조금만 어려워져도 막막한 상황이 많다.
우선 아래 스텝을 차근차근 따라와보자.
function compareFunction(a, b) {
if (a.amount === b.amount) {
return a.name - b.name;
}
return b.amount - a.amount;
}
// sort에 인자로 넣어줄 compareFunction를 따로 분리했다.
// 이 코드는 금액이 같을 때 name을 오름차순으로 먼저 분기시키고,
// 나머지 경우에 대해서는 내림차순 정렬을 수행한다.
해치웠나?
잘 정렬된 것 같아 보였으나, Tom과 Jane이 "오름차순"에 위배된다.
a-b가 오름차순 정렬이 맞는데, 왜 안될까?
뭔가 이상하면 부딪혀봐야지. 직접 빼보자.
NaN이 나온다.
당연하다. 문자끼리 빼는데, 연산을 수행할 수 없으니 NaN(Not a Number)이 나와야지.
그렇지만 지시는 수행해야 할텐데 어떻게 해결하면 좋을까?
대소를 비교하는 방법 중, 빼는 방법은 숫자를 비교하는 방법이다.
문자를 비교할 때는 부등호(>, < 등)을 이용한다.
function compareFunction(a, b) {
if (a.amount === b.amount) {
return a.name > b.name ? 1 : (a.name < b.name ? -1 : 0);
}
return b.amount - a.amount;
}
콘솔에서의 결과를 바탕으로 왜 각각 1, -1, 0을 리턴해줬을까? 정도는 그림을 그려보자.
이 비교함수는
드디어 원하는 결과를 반환해준다.
짧은 후기
이처럼 "어떤 방법"을 아는 것은 좋지만, "이유"를 아는 것이 무척 중요하다.
오름차순, 내림차순 "방법"을 알지만, 한가지 조건이 추가되면 손을 못쓰는 상황이 발생할수도 있으니 말이다.
(실제로 이렇게 "같을 때는 오름차순으로 정렬해주세요!" 문제가 코딩테스트에도 종종 등장한다.)
이 포스팅을 접하는 동료분들은 꼭 평소 궁금증이 해결되었길 바란다.
문제가 해결이 되지 않을 때나 이유를 모른다면, 한번 쯤 그 이유를 알아보려 해보자.
보통 나에게 어려운 문제는 다른 동료들도 똑같이 겪는 문제이며, 사실 "공식문서"에 정리되어 있는 경우가 많다.
사실 이렇게 적어둔 필자도 FM대로 공식문서만을 참고하는 것은 아니고 이곳 저곳의 레퍼런스를 참고한다ㅎㅎ,,
그러나, 결국 "공식"문서가 왜 공식이겠는가?
그 개념의 가장 믿을만한 지식이며, 많은 고민들을 제안하고 나누며 해결하고자 하는 곳이 공식문서다.
어렵거나 답이 나오지 않는 문제를 겪으면, 공식문서에 가서 차근차근 살펴보는 것을 추천한다. 👍👍
'JavaScript > theory' 카테고리의 다른 글
[Array] 일반적인 배열과 자바스크립트의 배열 알아보기 (0) | 2022.10.13 |
---|---|
엄격모드. "use strict" (0) | 2022.07.20 |
이벤트 위임, 버블링, 캡쳐링 (Event Delegation, bubbling, capturing) (0) | 2022.07.11 |
[함수형] 커링 Currying을 배워보자. (0) | 2022.06.01 |
[자바스크립트] 마이크로 태스크 큐의 비동기 작업 처리와 렌더링 시점을 알아보자. (1) | 2022.04.14 |