Skip to content

Latest commit

 

History

History
160 lines (114 loc) · 6.73 KB

76.md

File metadata and controls

160 lines (114 loc) · 6.73 KB

파일명 정렬

필요한 부분에만 집중하다.

Try1

큰 로직은 이러하다.

  1. 파일명을 head, number, tail 세 부분으로 나눈다.
  2. sort 메소드를 이용하여 head와 number의 기준에 따라서 정렬한다.

사실 로직 자체는 굉장히 간단하다. 그래서 쉽게 풀줄 알았다. 그런데, 아주 사소한 부분에서 오류가 발생했다. 공백!! 문제에서 파일명의 구성은 영문 대소문자, 숫자, 공백(" "), 마침표("."), 빼기 부호("-") 로 되어 있다고 했다. 다른건 몰라도 항상 문제되는 부분은 공백이다. 특히 자바스크립트에서는 말이다. 공백은 falsy value 이기 때문에 불리언을 이용해 이를 체크하기는 매우 까다롭다. 또한 여기서 한가지 더 문제는 NaN을 이용한다는 점이다. 문자열에서 문자 한개가 숫자인지를 판단하기 위해선 Number로 형변환을 한 후 변환값이 NaN인지를 판단해야한다.

예를 들면, 이렇다

'5' → Number('5') → 5 : ⭕️
'A' → Number('A') → NaN : ❌
' ' → Number(' ') → 0 : ⭕️ ??? 🤔

위의 예시처럼 3가지의 경우수가 발생할 수 있다. 그런데 여기서 NaN은 NaN === NaN 이런 식으로 비교하지 못한다. (false가 나온다. 자바스크립트의 예외라고 해야하나.) 그래서 이때는 isNaN을 사용해서 그 값을 비교해야한다. 또한 여기서 세번째 예시인 빈 공백은 숫자로 강제형변환이 되면 0과 같다. 그래서 또 공백은 숫자로 인식하게 된다. 이런 부분을 잘 분기해줘야했다.

여기에 나중에 알게된 조건이 또 있었다. 숫자는 최대 5글자까지만 가능하다는 조건!!

로직이 쉬우면 뭔가 예외적인 조건들이 많아서 실수를 유발하게 하는것 같다. (개인적인 생각이지만, 이런걸 보면 로직을 잘 구현하는지를 보려는게 아니라 함정을 얼마나 잘 캐치하는지를 보려는것인가 하는 생각이 들기도 한다. 물론 그게 실력이라고 말한다면 할 말은 없다...🥲)

const newFiles = files.map((file, index) => {
  let head = '',
    number = '',
    tail = '';

  for (let i = 0; i < file.length; i++) {
    if (!number && (isNaN(Number(file[i])) || file[i] === ' ')) {
      head += file[i];
    } else if (
      head &&
      !isNaN(Number(file[i])) &&
      file[i] !== ' ' &&
      number.length < 5
    ) {
      number += file[i];
    } else if (head && number) {
      tail += file[i];
    }
  }

  return { index, head, number };
});

head, number, tail 구분 로직 (정렬하는 로직은 빠져 있음)

55~66점에서 벗어나질 못했다. 그래서 공백에 대한 조건을 잘못정한 것인지를 고민하게되었다. 숫자인지를 좀 더 명확하게 파악하기 위해서 정규표현식 혹은 유니코드를 사용하는 방법으로 코드를 바꿔보았다.

const newFiles = files.map((file, index) => {
  let head = '',
    number = '',
    tail = '';

  /**
   * 둘 중 한가지를 사용함
   **/

  const isNumber = (char) => /[0-9]/.test(char); // 정규표현식
  const isNumber = (char) => char.charCodeAt() >= 48 && char.charCodeAt() <= 57; // 유니코드를 이용한 방법

  for (let i = 0; i < file.length; i++) {
    if (!number && !isNumber(file[i])) {
      head += file[i];
    } else if (head && isNumber(file[i]) && number.length < 5) {
      number += file[i];
    } else if (head && number) {
      tail += file[i];
    }
  }

  return { index, head, number };
});

그럼에도 여전히 결과에 변함이 없었다. 도대체 어디서 잘못되었을까.

정렬하는 부분에서는 문제가 없었다. 여러가지 시도와 고민 끝이 문제는 분명히 문자열을 나누는 부분에서 잘못되었음을 확신했는데, 어디서 어떻게 잘못되는지를 알 수가 없었다.

Try2

고민을 하던 중 우연히 tail의 부분이 왜 필요하지에 대한 생각을 하게 되었다. tail은 아무것도 하지않는데 굳이 분류해서 더해주는 연산작업을 할 필요가 없었다. tail이 정렬에 필요한 부분도 아니고 말이다. 그래서 tail 연산 부분을 과감히(?) 삭제하였다. 그랬더니 모든 케이스가 통과되었다. 잉?? 어안이 벙벙했다. 이럴때 하는 말이 있지 않은가. 왜 되지?? 😅

전체 코드는 아래와 같다.

function solution(files) {
  const newFiles = files.map((file, index) => {
    let head = '', number = '',

    const isNumber = (char) => /[0-9]/.test(char);

    for (let i = 0; i < file.length; i++) {
      if(!number && !isNumber(file[i])) {
        head += file[i];
      } else if(head && isNumber(file[i]) && number.length < 5) {
        number += file[i]
      } else break
    }

    return {index, head, number};
  });



  newFiles.sort((a, b) => {
    const head1 = a.head.toUpperCase();
    const head2 = b.head.toUpperCase();

    if (head1 > head2) return 1;
    else if (head1 < head2) return -1;
    else {
      const number1 = Number(a.number);
      const number2 = Number(b.number);
      if (number1 > number2) return 1;
      else if (number1 < number2) return -1;
    }
  });

  return newFiles.map((file) => files[file.index]);
}

이런 좋은 실수를 하고 과정을 찬찬히 돌이켜 생각하보면서 어떤 상황이 발생할 수 있을지, 반례에 대해서 생각해보았다.

유레카!!

'naver123kkk12' 이런 경우가 있을 수 있었다. 수정 전 로직(Try1)으로 돌려면서 아래와 처럼 흘러간다.

  1. 일반적인 흐름
  • head : naver → head 끝
  • number : 123 → number 끝(이라고 생각)
  • tail : kkk → tail 합치는 중
  1. 이제부터가 문제!!

    1은 number or tail ??

  • number 조건 : head && isNumber(file[i]) && number.length < 5

  • tail 조건 : head && number

    위 조건대로 하면 1은 number 조건에도 부합한다. 그래서 number에 합쳐지게 된다.

결국 나는 tail에 문자열과 숫자가 같이 나오는 경우를 체크하지 못했던 것이다. 😭 이것 때문에 거의 두시간을 쓴 것 같다.

그런데 말입니다. 사실 다르게 생각해보면, tail을 굳이 체크할 필요가 있었냐 라는 생각이 든다. tail은 문자열 정렬을 하는데 있어서 아무런 영향을 미치지 않습니다. 그렇기 때문에 위와 같은 반례를 생각하지 않더라도 head와 number만을 구한 후 나머지에서는 break 를 했다면, 결국 내가 필요로하는 값이 집중했다면 더 빠르게 고민없이 문제를 해결할 수도 있지 않았을까 하는 생각도 든다.

이런 반례를 구해나가는 과정이 힘들지만 보람되고 의미있는 시간이였음에도 말이다. 우리의 시간은 소중하니까.