Level 2️⃣ - 파일명 정렬

2024. 4. 7. 21:28알고리즘/프로그래머스

https://school.programmers.co.kr/learn/courses/30/lessons/17686

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

🚀문제 해석

먼저 파일명을 HEAD, NUMBER 파트로 나눈다.

foo9.txt ➡ HEAD : foo, NUMBER : 9

foo010bar020.zip ➡ HEAD : foo, NUMBER : 010

사실 TAIL이라는 파트도 있지만 정렬할 때는 필요가 없기 때문에 구하지 않는다.

 

그리고 각각의 파일을 정렬을 하는데 HEAD를 기준으로 정렬을 하고 만약 HEAD가 같다면 NUMBER를 기준으로 정렬을 한다. 만일 HEAD와 NUMBER 모두 같다면 stable하게 정렬하면 된다.

 

function solution(files) {
    let container = {};
    files.forEach((file, idx)=>{
        container[file] = [];
        let head = "";
        let number = "";
        let flag = 0;
        for(const c of file){
            if(flag && Number.isNaN(Number(c))) break;
            if(c === ' ' || Number.isNaN(Number(c))) head += c;
            else {
                flag = 1;
                number += c;
            }
        }
        container[file].push(head.toUpperCase());
        container[file].push(Number(number));
    });
    files.sort((a, b)=>{
        if(container[a][0] === container[b][0]){
            if(container[a][1] > container[b][1]) return 1;
            else if(container[a][1] < container[b][1]) return -1;
            else return 0;
        }
        else if(container[a][0] < container[b][0]) return -1;
        else if(container[a][0] > container[b][0]) return 1;
    });
    return files;
}

 

오류가 났던 부분

공백과 빈 문자열은 Number.isNaN(Number(elem))을 적용했을 때 false가 나온다. 왜냐하면 Number로 포맷할 때 0이 되기 때문이다. 따라서 다음과 같이 조건을 추가하지 않는다면 HEAD는 공백을 읽지 않고 잘라버리기 때문에 버그가 발생한다.

예외처리할 때 자주 실수했던 부분이다. 항상 염두하자. 공백과 빈 문자열은 Number로 포맷팅 시 0이 된다.

if(c === ' ' || Number.isNaN(Number(c))) head += c;

 

 

Sort

그리고 오류가 난 부분은 아니지만 sort의 쓰임에 대해 더 깊게 공부해야겠다는 생각이 들었다.

단순하게 문자열을 정렬할 때는 sort에 compare 함수를 전달하지 않아도 사전순으로 정렬하기 때문에 아무 생각없이 사용했다.

하지만 이번처럼 외부에 저장된 값을 가지고 문자열을 정렬해야한다면 compare 함수가 필요하기 때문에 이럴때는 어떻게 정렬해야되는지 답답함을 느꼈다. 이번 기회에 제대로 공부하고 넘어가자.

arr.sort([compareFun])

우선 sort 함수의 기본형태이다.

compareFun은 정렬 순서를 정의하는 함수로 생략되면 배열의 요소들이 문자열로 취급되어 유니코드 포인트 순서대로 정렬된다.

흔히 말하는 사전순 정렬이 다음과 같다.

let arr = ['aab', 'a', 'A'];
arr.sort();
console.log(arr); // 'A', 'a', 'aab'

 

만약 compareFun을 생략하지 않는다면 compareFun은 두 개의 요소를 파라미터로 입력받게 되는데, 

이 함수가 리턴하는 값이 0보다 작을 경우, a가 b보다 앞에 오도록 정렬하고,

이 함수가 리턴하는 값이 0보다 클 경우, b가 a보다 앞에 오도록 정렬한다.

만약 0을 리턴하면 a와 b의 순서를 변경하지 않는다.

let arr = [3, 12, 1, 5, 7, 91, 14, 2, 5];

arr.sort((a, b) => {
  if (a > b) return 1;
  if (a < b) return -1;
  if (a === b) return 0;
});

console.log(arr);

 

그래서 흔히 짧게 줄여쓰던 표현이 사실은 위와 같은 compareFun 리턴값에 의해 정렬이 되는 것이다.

let arr = [3, 12, 1, 5, 7, 91, 14, 2, 5];

arr.sort((a, b) => a - b); // 오름차순

console.log(arr);

 

그렇다면 문자열을 사전순으로 정렬하는 것 말고 compareFun을 이용해서 내림차순으로 정렬해보자.

이때 문자열을 대소 비교를 통해 비교할 수 있다는 점에 주목하자.

const arr = ['banana', 'b', 'boy'];

arr.sort(function(a, b) {
  if(a < b) return 1;
  if(a > b) return -1;
  if(a === b) return 0;
});

 

 

<, > 연산자는 문자열의 아스키코드 값을 비교해서 결과를 리턴한다.

'a'의 아스키코드보다 'banana'의 'b'의 아스키코드가 크기 때문에 'a' < 'banana' 의 대소관계가 성립한다.

'a'의 아스키코드와 'apple'의 'a'의 아스키코드는 같다. 하지만 두번째 문자에서 'a'는 글자가 없으므로 0이고 'apple'은 'p'이기 때문에 'apple'의 아스키코드가 더 높다.

주의할 점은 아스키코드가 작은 값이 사전순으로는 먼저 나오는 값이다.

console.log('a' < 'banana');
console.log('a' < 'apple');
console.log('a' > 'b'); // false

 

이제 위의 과정을 이해했으면 HEAD 기준으로 문자열을 사전순으로 정렬하는 코드를 구현할 수 있다.

핵심을 정리하면 다음과 같다.

  • compareFun이 음수를 리턴하면 a를 b 앞에, 양수를 리턴하면 b를 a 앞에 정렬한다.
  • 문자열은 아스키코드를 기반으로 대소비교를 할 수 있다.
  • 아스키코드가 작은 값이 사전순으로 먼저오는 값이다.
    files.sort((a, b)=>{
        if(container[a][0] === container[b][0]){
            if(container[a][1] > container[b][1]) return 1;
            else if(container[a][1] < container[b][1]) return -1;
            else return 0;
        }
        else if(container[a][0] < container[b][0]) return -1;
        else if(container[a][0] > container[b][0]) return 1;
    });