2026.4.22, 2026.4.23 (작성일: 2026.4.23, 2026.4.24)
https://school.programmers.co.kr/learn/courses/30/lessons/17686
/\D/는 숫자가 아닌 것, /\d/는 숫자인 것 (/[0-9]/와 같음)+를 붙이면 연속된 값을 찾음match(정규식) 리턴 값은 [일치하는 문자열, index: 일치하는 첫번째 문자열 인덱스, input: 검사한 문자열 원문, groups: 그룹핑 결과]일치하는 문자열을 사용하고 싶으면, [0]으로 접근 가능. match의 리턴 값을 백틱으로 감싼 ${} 안에 넣어도 뽑히는데, 길이가 1인 배열이라 우연히 동작하는 것임..!! (실행하다 [0]으로 접근했을 때 오류가 나서 저렇게 작성한 거였는데 다시 해보니 오류가 나지 않는다. 뭔가 다른 문법 오류가 있었나보다.)function solution(files) {
// 1. 파일명을 [HEAD, NUMBER, TAIL]로 분리한다.
const slicedFiles = files.map(f => {
// 정규표현식으로 연속된 숫자 값과 첫번째 인덱스를 구한다.
const number = f.match(/\d+/);
const NUMBER = `${number}`; // ✅ number[0]이 더 정확함
const numberIndex = number.index;
const tailIndex = numberIndex + NUMBER.length;
// HEAD: 처음으로 숫자가 나오기 전까지
const HEAD = f.substring(0, numberIndex);
const TAIL = f.substring(tailIndex);
// return [HEAD, NUMBER, TAIL];
return { head: HEAD, number: NUMBER, tail: TAIL, fileName: f };
});
// 2. 기준에 맞게 정렬 시작
const sorted = slicedFiles.sort((a, b) => {
// 2-1. HEAD를 기준으로 사전 순 정렬 (대소문자 구분 없이)
if (a.head.toLowerCase() === b.head.toLowerCase()) {
// 2-2. NUMBER를 숫자 순 정렬하되, 같은 값이면 순서를 유지 (0 리턴)
return Number(a.number) === Number(b.number) ? 0 : Number(a.number) - Number(b.number);
} else {
return a.head.localeCompare(b.head);
}
});
return sorted.map(s => s.fileName);
}
function solution(files) {
// 1. 파일명을 { head, number, fileName } 구조로 분해한다.
const destructured = files.map(f => {
// 정규표현식으로 연속된 숫자 값과 첫번째 인덱스를 구한다.
const number = f.match(/\d+/);
// 정렬할 때 number는 숫자로 비교하므로 숫자로 변환해준다.
const NUMBER = parseInt(`${number}`);
// head는 처음으로 숫자가 나오기 전까지 자른 문자열.
// 정렬할 때 대소문자 구별 없으니 모두 소문자로 변환해준다.
const numberIndex = number.index;
const HEAD = f.substring(0, numberIndex).toLowerCase();
return { head: HEAD, number: NUMBER, fileName: f };
});
// 2. 기준에 맞게 정렬 시작
const sorted = destructured.sort((a, b) => {
// head 사전 순 정렬
if (a.head > b.head) return 1;
if (a.head < b.head) return -1;
// head가 같으면, number 숫자 순 정렬
if (a.number > b.number) return 1;
if (a.number < b.number) return -1;
// head, number가 같으면 현재 순서 유지
return 0;
});
return sorted.map(s => s.fileName);
}
function solution(files) {
// 1. 파일명을 { head, number, fileName } 구조로 분해한다.
const destructured = files.map(f => {
// 파일명 조건을 정규표현식으로 표현한다.
const regex = /^([a-zA-Z-\. ]+)([0-9]+)(.*)$/;
// const regex = /([a-zA-Z-\. ]+)([0-9]+)/;
const [fileName, head, number] = f.match(regex);
// head는 처음으로 숫자가 나오기 전까지 자른 문자열.
// 정렬할 때 대소문자 구별 없으니 모두 소문자로 변환해준다.
// 정렬할 때 number는 숫자로 비교하므로 숫자로 변환해준다.
return { head: head.toLowerCase(), number: parseInt(number), fileName };
});
// 2. 기준에 맞게 정렬 시작
const sorted = destructured.sort((a, b) => {
// head 사전 순 정렬
if (a.head > b.head) return 1;
if (a.head < b.head) return -1;
// head가 같으면, number 숫자 순 정렬
if (a.number > b.number) return 1;
if (a.number < b.number) return -1;
// head, number가 같으면 현재 순서 유지
return 0;
});
return sorted.map(s => s.fileName);
}
localeCompare 메서드 대신 단순 비교 연산자를 이용한다. (코드가 더 명확하게 보임)const regex = /^([a-zA-Z-\. ]+)([0-9]+)(.*)$/;^: 문자열 시작$: 문자열 종료- 와 함께 적어주면 되고, 여러 조건은 그냥 나열하면 되네. (a-zA-Z 같이)-, ., (공백)을 말했으니 그것도 넣어준다.()는 그룹화 패턴+는 연속된 문자열을 찾는 패턴.은 아무 문자열이나!*은 앞에 온 것이 0번 이상 반복되는 패턴.*은 임의의 문자열이 0번 이상 반복, 즉 아무 문자열이나 상관 없음 (빈 문자열도 OK)'img2.JPG'.match(regex) 결과는 아래와 같다.[
'img2.JPG',
'img',
'2',
'.JPG',
index: 0,
input: 'img2.JPG',
groups: undefined
]
// 작성했던 풀이 노트
1. 주어진 문자열을 [HEAD, NUMBER, TAIL]로 쪼갠다.
HEAD와 NUMBER를 구별할 방법 -> 처음으로 숫자가 나오는 지점에서 자르기
2. HEAD를 기준으로 오름차순 정렬한다.
3. HEAD 기준, '순서가 같은 것들' = '모두 소문자로 변환 시 "같은 문자열"인 것들'에 대해서만 NUMBER를 비교한다.
NUMBER를 정수로 변환하고, 그 값을 오름차순 정렬한다.
만약 정수로 변환한 값이 서로 같다면 정렬하지 않는다!
문자열을 쪼갤 방법을 많이 찾아봤다.
split: seperator 인자로 문자열이나 정규표현식을 받음, 다만 문자열을 수정함 => 얕은 복사본에 대해 숫자를 찾는 정규표현식 담아 split을 실행하고, 잘린 것의 맨뒤를 제거하면 HEAD...substring은 문자열을 반환함 (원본 수정 안하는 메서드)exec()은 만족하는 첫요소를 반환함 => 이걸 이용하고 인덱스를 찾을 수야 있겠지.. 근데 더 좋은 방법이 있을 것 같다.match는 어떤 형태의 값을 리턴하는지 콘솔 로깅 여러번 해보니, 작성한 정규식을 만족하는 값, index, input, groups 키와 값을 배열로 뱉고 있었다. 신기한 발견sort의 인자로 넘기는 콜백에서 0을 리턴하면 순서를 변경하지 않는다.