[프로그래머스] [3차] 파일명 정렬
이번 문제는 정규식과 match()메서드 조합할 때 무엇을 반환하는지,
sort()메서드에 잘 아시는 분들이면 쉽게 풀었을 것 같습니다.
저는 둘 다 부족해서 질문하기에 도움을 받아 풀었도 또다시 새로운 사실을 깨닫고 신기해하는 중입니다.😄
진짜 알고 있다는 생각했던 메서드에서 이런 기능이 있다니;;; 진짜 제가 많이 부족한가 봐요.😂
접근방법
이 문제는 단순하게 생각하면 정렬 문제입니다.
정렬 문제라는 말은 결국 sort()를 사용해서 풀어야 된다는 말입니다.
이게 단순히 숫자만 정렬하는 문제면 for문을 쓸 수도 있겠지만 숫자, 문자를 다 구분해서 해야 돼서 sort()를 필수로 써야 됩니다.
그러나 단순한 정렬 문제면 정말 쉽겠지만, 정렬 조건이 있습니다.
이 표처럼 파일명을 세 부분으로 나눈 후, 다음 기준에 따라 파일명을 정렬해야 됩니다.
- 파일명은 우선 HEAD 부분을 기준으로 사전 순으로 정렬한다. 이때, 문자열 비교 시 대소문자 구분을 하지 않는다. MUZI와 muzi, MuZi는 정렬 시에 같은 순서로 취급된다.
- 파일명의 HEAD 부분이 대소문자 차이 외에는 같을 경우, NUMBER의 숫자 순으로 정렬한다. 9 < 10 < 0011 < 012 < 13 < 014 순으로 정렬된다. 숫자 앞의 0은 무시되며, 012와 12는 정렬 시에 같은 같은 값으로 처리된다.
- 두 파일의 HEAD 부분과, NUMBER의 숫자도 같을 경우, 원래 입력에 주어진 순서를 유지한다. MUZI01.zip과 muzi1.png가 입력으로 들어오면, 정렬 후에도 입력 시 주어진 두 파일의 순서가 바뀌어서는 안 된다.
내용을 다시 보니까 제가 문제를 제대로 안 읽어서 삽질했네요....
애초부터 head, number만 필요하고 tail은 필요 없네요.
(이럴 거면 표에 쓰지도 말지)
위에 내용 토대로 정렬해야 되는 순서를 정리하자면
- 파일명을 head, number로 나눈다. (tail은 필요 없습니다.)
- head에서 대소문자 구분하지 않음으로 소문자로 다 통일하기
- number은 문자열이 아닌 숫자로 바꾸기
- 1순위로 head부터 사전 순으로 정렬한다.
- 만약 head가 같을 경우 number의 숫자 순으로 정렬한다.
이렇게 코드로 짜시면 될 것 같습니다.
해결방안
function solution(files) {
files.sort((a, b)=>{
let [matchA, matchB] = [a.match(/\d+/), b.match(/\d+/)];
let [headA, headB] = [a.slice(0, matchA.index).toLowerCase(), b.slice(0, matchB.index).toLowerCase()];
let [numberA, numberB] =[matchA[0]/1, matchB[0]/1];
if(headA < headB) return -1
else if(headA > headB) return 1
else if(numberA < numberB) return -1
else if(numberA > numberB) return 1
return 0
});
return files;
}
해결방법 순서
- 파일명을 head, number로 나눈다.
- 1순위로 head부터 사전 순으로 정렬한다.
- 만약 head가 같을 경우 number의 숫자 순으로 정렬한다.
1단계. 파일명을 head, number로 나눈다.
let [matchA, matchB] = [a.match(/\d+/), b.match(/\d+/)];
let [headA, headB] = [a.slice(0, matchA.index).toLowerCase(), b.slice(0, matchB.index).toLowerCase()];
let [numberA, numberB] =[matchA[0]/1, matchB[0]/1];
head, number을 어떻게 만들었다면
"img12.png"
- 파일명에서 숫자 부분을 찾는다. -> 12
- 숫자가 시작하는 index를 찾는다 -> 3 (1의 위치)
- 파일명의 0번부터 index위치까지 자르면 head가 된다 -> 파일명의 0부터 3번까지 img
- head는 소문자로 바꾸어 준다 -> 만약 head가 IMG이면 img로 바꾼다.
- 파일명에서 숫자 부분을 찾은 것을 number에 넣는다
- number은 문자열 임으로 숫자로 바꾼다.
이렇게 바꾸었습니다.
자세한 설명은 아래에서 하겠습니다.
destructuring 문법을 사용해주어 sort() 메서드에 인자로 들어온 a, b를 head, number로 바꾸어 주었습니다.
// destructuring 문법 간단한 예시
let [a,b] = [2,3]; // a=2, b=3
(1, 2) match()메서드와 정규식을 사용해서 문자열에서 숫자와 숫자 첫 번째 위치 찾기
let [matchA, matchB] = [a.match(/\d+/), b.match(/\d+/)];
파일명에 숫자가 하나만 있거나 연속으로 붙어 있는 부분이 한 부분이면 편하겠지만
- foo010bar020.zip
이런 경우도 존재하기 때문에 애매해집니다.
foo010bar020.zip같은 경우에는
- head : foo
- number : 010
이렇게 돼야 되는데
숫자가 010, 020이 있어서 숫자들 중에서 처음에만 중복된 걸 찾기 하기가 코드로 쓰면 길어지고 복잡해집니다.
이때 010만 추출하는 가장 쉬운 방법은 정규식을 잘 쓰는 겁니다.
이건 예시를 보시면 이해가 될 것 같아서 예시로 설명하겠습니다.
//match()메서드와 정규식을 사용할 때 예시
let num1 = /\d/g
let num2 = /\d/
let num3 = /\d+/
let str = 'foo010bar020.zip'
console.log(str.match(num1)) // [ '0', '1', '0', '0', '2', '0' ]
console.log(str.match(num2)) // [ '0', index: 3, input: 'foo010bar020.zip', groups: undefined ]
console.log(str.match(num3)) // [ '010', index: 3, input: 'foo010bar020.zip', groups: undefined ]
let match = str.match(num3)
console.log(match.index) // 3
정규식을 쓸 때 일반적으로 g를 사용해서 g를 사용 안 할 때를 까먹고 있었습니다.
- g : 글로벌 매치 (첫 번째 조건을 찾은 뒤 모든 조건을 다 검색합니다.)
- \d : 0-9(숫자만)
- + : 1번 이상 반복
위에 예제를 잘 보시면 //만으로 정규식을 짜고 match() 메서드에 집어넣으면
[선택된 값, index...]을 보실 수 있습니다.
여기서 index는 숫자가 처음으로 나오는 첫 위치를 표시해주니 이 값을 가지고 head와 number을 구하기가 쉬워집니다.
그럼 match() 메서드와 정규식으로 만든 [matchA, matchB]이 값을 통해
head, number을 만들면 됩니다.
(3, 4) [matchA, matchB] 값을 가지고 파일명의 head를 구하기
let [headA, headB] = [a.slice(0, matchA.index).toLowerCase(), b.slice(0, matchB.index).toLowerCase()];
[matchA, matchB]의 index값을 사용해서 head를 만들어 주었습니다.
head는 숫자가 나오기 전 앞 문자열 임으로 파일명[0]부터 match.index까지가 파일명입니다.
- a.slice(0, matchA.index)
- b.slice(0, matchB.index)
그래서 slice를 이용해서 0번째부터 match.index까지로 지정해주었습니다.
그리고 문제 설명에서 "문자열 비교 시 대소문자 구분을 하지 않는다."말이 있어
toLowerCase()를 써서 소문자로 바꿔주었습니다.
- a.slice(0, matchA.index).toLowerCase()
- b.slice(0, matchB.index).toLowerCase()
(5, 6) [matchA, matchB] 값을 가지고 파일명의 number를 구하기
let [numberA, numberB] =[matchA[0]/1, matchB[0]/1];
[matchA, matchB] 값의 0번째 원소를 이용해서 number를 만들어 주었습니다.
- matchA[0]
- matchB[0]
match[0]은 숫자가 아닌 문자열 임으로 "문자열과 숫자열의 사칙연산" 이용해서 숫자로 만들어 주었습니다.
- matchA[0]/1
- matchB[0]/1
2단계. 1순위로 head부터 사전 순으로 정렬한다.
3단계. 만약 head가 같을 경우 number의 숫자 순으로 정렬한다.
if(headA < headB) return -1
else if(headA > headB) return 1
else if(numberA < numberB) return -1
else if(numberA > numberB) return 1
return 0
2단계, 3단계는 같이 설명하겠습니다.
sort()를 특정한 기준으로 정렬을 하려면
sort((a,b)=>{
if(a>b) return -1
else if(a<b) return 1
return 0
})
이렇게 써야 됩니다.
그러나 이번 문제 head에서 정렬이 안되면 number로 정렬해야 됩니다.
이럴 경우에는 else if() 추가해서 작성해주면 됩니다.
그래서 2단계 3단계를 코드를 나누자면
2단계. 1순위로 head부터 사전 순으로 정렬한다.
- if(headA < headB) return -1
- else if(headA > headB) return 1
3단계. 만약 head가 같을 경우 number의 숫자 순으로 정렬한다.
- else if(numberA < numberB) return -1
- else if(numberA > numberB) return 1
이렇게 됩니다.
if(headA < headB) return -1
else if(headA > headB) return 1
else if(numberA < numberB) return -1
else if(numberA > numberB) return 1
return 0
이 코드를 다르게 아래 코드같이 써도 됩니다.
return headA < headB ? -1 : headA > headB ? 1 : numberA < numberB ? -1 : numberA > numberB ? 1 : 0
// 이렇게 보시면 쉽게 이해하실 수 있습니다.
return headA < headB ? -1 :
headA > headB ? 1 :
numberA < numberB ? -1 :
numberA > numberB ? 1 :
0
여기까지 프로그래머스 [3차] 파일명 정렬 해결방안에 대해 설명해보았습니다.