일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 생활코딩
- 프로그래머스
- UX
- 자바스크립트
- 회고
- 프로토타입
- html
- 운영체제
- redux
- 자료구조
- useState
- 코드스테이츠
- REST_API
- Next.js
- 스택
- 30daysdowoonchallenge
- 백준
- 해시테이블
- javascript
- vercel
- React
- 카카오
- web
- level1
- UI
- 큐
- mysemester
- Til
- superstarjypnation
- CSS
- Today
- Total
데굴데굴
<JavaScript> 정렬의 비교 함수는 어떻게 동작하는가 본문
궁금증이 피어오르게 된 문제 -> 백준 10825 - 국영수
const fs = require("fs");
const filePath = process.platform === "linux" ? "/dev/stdin" : "./test.txt";
let input = fs.readFileSync(filePath).toString().trim().split("\n");
const N = Number(input.shift());
const scores = input.map((el) =>
el.split(" ").map((v, idx) => {
if (idx > 0) v = Number(v);
return v;
})
);
let names = [];
function sortAlphabetically(a, b) {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
scores.sort((a, b) => {
if (a[1] === b[1]) {
if (a[2] === b[2]) {
if (a[3] === b[3]) {
return sortAlphabetically(a[0], b[0]);
}
return b[3] - a[3];
}
return a[2] - b[2];
}
return b[1] - a[1];
});
scores.forEach((el) => {
names.push(el[0]);
});
console.log(names.join("\n"));
문제에 주어진 정렬 조건에 맞게 정렬 함수를 작성해주면 되는 문제이다.
마지막 조건 4번을 보면 학생들의 국영수 점수가 모두 같을 경우에는 사전 순으로 앞에 오는 학생을 먼저 출력하도록 지시하고 있다.
이 문제를 처음 풀 때 정렬 함수에 대한 이해가 부족했던 터라 숫자를 오름차순으로 정렬하는 것처럼 return a[0] - b[0]
로 작성하면 문자열도 알아서 사전 순으로 정렬될 것이라 생각했는데 아니었다. 위처럼 알파벳 순으로 정렬해주는 함수를 따로 써줘야 통과할 수 있었다.
(알파벳 순 정렬 함수 참고: https://stackoverflow.com/questions/6712034/sort-array-by-firstname-alphabetically-in-javascript)
자바스크립트에서 문자열로만 이루어진 배열을 정렬할 때 정렬 함수에 아무것도 전달하지 않으면 알아서 알파벳 순으로 정렬해준다.
대문자와 소문자가 섞여 있으면 대문자가 먼저 정렬된다.
const students = ['Donghyuk', 'Sangkeun', 'Sunyoung', 'nsj', 'Wonseob', 'Sanghyun', 'Sei', 'Kangsoo', 'Haebin', 'Junkyu', 'Soong', 'Taewhan']
console.log(students.sort());
// [ 'Donghyuk', 'Haebin', 'Junkyu', 'Kangsoo', 'Sanghyun', 'Sangkeun', 'Sei', 'Soong', 'Sunyoung', 'Taewhan', 'Wonseob', 'nsj' ]
내가 처음에 문제에 접근했던 방식처럼 여기에 숫자를 오름차순으로 정렬하는 비교 함수를 써보면 요소가 전혀 정렬되지 않고 원본 배열이 그대로 리턴되는 신기한 광경을 볼 수 있다.
const students = ['Donghyuk', 'Sangkeun', 'Sunyoung', 'nsj', 'Wonseob', 'Sanghyun', 'Sei', 'Kangsoo', 'Haebin', 'Junkyu', 'Soong', 'Taewhan']
console.log(students.sort((a, b) => a - b));
// 하나도 정렬되지 않았다.
// [ 'Donghyuk', 'Sangkeun', 'Sunyoung', 'nsj', 'Wonseob', 'Sanghyun', 'Sei', 'Kangsoo', 'Haebin', 'Junkyu', 'Soong', 'Taewhan' ]
단순히 알파벳 순으로 정렬하면 되는 문제라면 .sort()
로 해결이 가능하다. 하지만 위 문제처럼 직접 작성한 정렬 함수의 일부로서 알파벳 순으로 정렬하는 로직이 추가적으로 필요한 경우에는 정렬 함수에 인수로 전달되는 비교 함수의 동작 방식을 제대로 알고 있어야 한다.
자바스크립트 정렬 비교 함수의 동작 방식
자바스크립트 정렬 함수는 아래처럼 두 가지 방식으로 사용할 수 있다.
sort()
sort(compareFn)
비교 함수는 정렬의 순서를 결정하는 함수이다.
비교 함수의 두 인자는 보통 간단하게 a
와 b
로 설정하며 아래의 조건에 맞게 0을 리턴하거나 0보다 큰 값, 0보다 작은 값을 리턴하도록 작성한다.
a
가b
보다 큰 경우에는 0보다 큰 값을 리턴한다.- 정렬 순서:
[b, a]
- 정렬 순서:
a
와b
가 같은 경우에는 0을 리턴한다.- 정렬 순서: 원본 배열의
a
와b
순서를 그대로 따른다.
- 정렬 순서: 원본 배열의
a
가b
보다 작은 경우에는 0보다 작은 값을 리턴한다.- 정렬 순서:
[a, b]
- 정렬 순서:
비교 함수가 생략되면 배열의 요소는 자동으로 문자열로 변환되며 유니코드 코드 포인트 순서에 따라 정렬된다.
const arr = [1, 10, 2, 3, 4];
arr.sort();
// 기대한 결과: [1, 2, 3, 4, 10]
// 실제 결과: [1, 10, 2, 3, 4]
문자열 10
의 유니코드 코드 포인트: U+0031 U+0030
문자열 2
의 유니코드 코드 포인트: U+0032
-> 문자열 10
이 더 앞서기 때문에 기대완 달리 10이 더 앞으로 오게 된다.
또 자바스크립트 정렬 함수는 원본 배열을 변경한다.
참고
How does sort function work in JavaScript, along with compare function
As already asked: how does sort function work in JavaScript, along with the compare function? If I have an array, and I do array.sort(compare) now it was written in the book that if the compare fun...
stackoverflow.com
Array.prototype.sort() - JavaScript | MDN
The sort() method sorts the elements of an array in place and returns the reference to the same array, now sorted. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units val
developer.mozilla.org
숫자 정렬하기
// 오름차순 정렬
const arr = [1, 30, 5];
arr.sort((a, b) => a - b);
// 결과: [ 1, 5, 30 ]
비교 함수의 동작 방식을 대략적으로 따라가보면 이렇게 된다.
- 첫 번째로
1
과30
을 비교한다. 둘의 차는-29
로 0보다 작다. 따라서1
이 더 앞으로 오게 된다. 1
과5
를 비교한다. 둘의 차는-4
로 0보다 작다. 마찬가지로 1이 더 앞으로 오게 된다.30
과5
를 비교한다. 둘의 차는25
로 0보다 크다.5
가 더 앞으로 오게 된다.
=>[1, 5, 30]
문자열 알파벳 순으로 정렬하기
const students = ['Donghyuk', 'Sangkeun', 'Sunyoung', 'nsj', 'Wonseob', 'Sanghyun', 'Sei', 'Kangsoo', 'Haebin', 'Junkyu', 'Soong', 'Taewhan'];
function sortAlphabetically(a, b) {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
students.sort(sortAlphabetically)
console.log(students);
'Donghyuk'
과 'Sangkeun'
이를 비교해보면 아래와 같다.D
의 유니코드 코드 포인트: U+0044
S
의 유니코드 코드 포인트: U+0053
=> D가 더 앞서기 때문에 동혁이가 더 앞으로 온다.
'Sangkeun'
이와 'Sanghyun'
을 살펴보겠다.
둘 다 Sang
까지는 똑같기 때문에 본격적인 비교는 keun
과 hyun
에서 시작된다.k
의 유니코드 코드 포인트: U+006B
h
의 유니코드 코드 포인트: U+0068
유니코드 코드 포인트는 아래 순서대로 표기하기 때문에 h
가 더 앞서서 상현이가 더 먼저 오게 된다.1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 0
소문자 a
= U+0061
소문자 j
= U+006A
소문자 p
= U+0070
소문자 z
= U+007A
문자열을 비교해주는 localeCompare()
를 이용할 수도 있다. 하지만 localeCompare()
에서는 기본적으로 대소문자 상관없이 알파벳 순으로 정렬해주기 때문에 이 점에 유의해야 한다. (조건을 직접 작성해주면 대문자 우선 정렬이 가능하다고 한다.)
const students = ['Donghyuk', 'Sangkeun', 'Sunyoung', 'nsj', 'Wonseob', 'Sanghyun', 'Sei', 'Kangsoo', 'Haebin', 'Junkyu', 'Soong', 'Taewhan']
console.log(students.sort((a, b) => a.localeCompare(b)));
// [ 'Donghyuk', 'Haebin', 'Junkyu', 'Kangsoo', 'nsj', 'Sanghyun', 'Sangkeun', 'Sei', 'Soong', 'Sunyoung', 'Taewhan', 'Wonseob' ]
왜 정렬되지 않았을까?
const students = ['Donghyuk', 'Sangkeun', 'Sunyoung', 'nsj', 'Wonseob', 'Sanghyun', 'Sei', 'Kangsoo', 'Haebin', 'Junkyu', 'Soong', 'Taewhan']
console.log(students.sort((a, b) => a - b));
// 하나도 정렬되지 않았다.
// [ 'Donghyuk', 'Sangkeun', 'Sunyoung', 'nsj', 'Wonseob', 'Sanghyun', 'Sei', 'Kangsoo', 'Haebin', 'Junkyu', 'Soong', 'Taewhan' ]
왜 여기서는 정렬되지 않았을까?
정렬 함수가 어떻게 동작하고 있는지 알아보기 위해 콘솔에 찍어보았다.
students.sort((a, b) => {
console.log(a - b);
return a - b
})
// <11번 출력됨> NaN
// students.length => 12
길이 12의 배열에서 11번의 비교를 하는데 전부 NaN
이 출력되었다.
어째서 NaN
이 출력되었는고 하니, 문자열을 숫자 취급하여 연산을 수행하려 했기 때문이다. (이렇게 단순한 원인이었다니..)
비교 함수에서 NaN
은 0으로 간주한다.
비교 함수에서 0을 리턴하게 되면 두 값이 같은 것으로 취급되어 기존 배열의 순서를 그대로 따라가게 되는데, 그래서 위 코드에서는 아무것도 정렬되지 않은 채 원본 배열이 그대로 리턴되었던 것이다.
그 동안 정렬 함수를 기계적으로 외워서 써왔었는데 이 문제를 계기로 자바스크립트의 정렬 방식을 더 깊게 이해할 수 있었다.
다음에 또 헷갈리게 되면 다시 글을 읽어보아야겠다.
'Programming > JavaScript' 카테고리의 다른 글
<JavaScript> 스코프 (1) | 2023.11.11 |
---|---|
<JavaScript> parseInt()와 Number()는 어떤 차이가 있을까? (0) | 2023.03.14 |
<JavaScript> e.stopPropagation()과 e.preventDefault() (0) | 2023.02.16 |
<JavaScript> 프로토타입 체인 (0) | 2022.09.22 |
<JavaScript> 프로토타입 (0) | 2022.09.21 |