알고리즘🅰/프로그래머스

[프로그래머스] [3차] 파일명 정렬

개발조각 2022. 4. 28. 20:53
728x90
반응형

이번 문제는 정규식과 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은 필요 없네요.

(이럴 거면 표에 쓰지도 말지)

 

 

위에 내용 토대로 정렬해야 되는 순서를 정리하자면

  1. 파일명을 head, number로 나눈다. (tail은 필요 없습니다.)
    • head에서 대소문자 구분하지 않음으로 소문자로 다 통일하기
    • number은 문자열이 아닌 숫자로 바꾸기
  2. 1순위로 head부터 사전 순으로 정렬한다.
  3. 만약 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;
}

해결방법 순서

  1. 파일명을 head, number로 나눈다.
  2. 1순위로 head부터 사전 순으로 정렬한다.
  3. 만약 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"

  1. 파일명에서 숫자 부분을 찾는다. -> 12
  2. 숫자가 시작하는 index를 찾는다 -> 3 (1의 위치)
  3. 파일명의 0번부터 index위치까지 자르면 head가 된다 -> 파일명의 0부터 3번까지 img
  4. head는 소문자로 바꾸어 준다 -> 만약 head가 IMG이면 img로 바꾼다.
  5. 파일명에서 숫자 부분을 찾은 것을 number에 넣는다 
  6. 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차] 파일명 정렬 해결방안에 대해 설명해보았습니다.

728x90
반응형