본문 바로가기

프로그래머스 - Lv.2 파일명 정렬 JS 본문

개발/algorithm

프로그래머스 - Lv.2 파일명 정렬 JS

자전하는명왕성 2023. 9. 30. 09:40

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

문제는 그 말마따나 정렬에 관한 문제다.

문자열이 주어졌을 때, 규칙에 따라 'head / number / tail' 로 나누고 정렬한 뒤 합치는 과정으로 진행했다.

문제가 길 뿐이지 단순할 것이라 생각했는데 생각보다 오래걸렸고, 덕분에 알게 된 사실도 있었다.

 

먼저 'head / number / tail'로 문자열을 매핑하는 과정에 대해 말한다.

const verifyNum = (s) => {
  const nums = '0123456789'
  return nums.includes(s)
}

 const mapping = files.map(str => {
  let [head,number,tail] = ['','','']
  let [headCheck, numberCheck] = [false, false]
  
  for(let i = 0 ; i < str.length ; i ++) {
     if(headCheck && numberCheck) tail += str[i]
     else if(!verifyNum(str[i])) {
         head += str[i]
         if(verifyNum(str[i+1])) headCheck = true
     }
     else if(verifyNum(str[i])) {
       number += str[i]
       if(!verifyNum(str[i+1])) numberCheck = true  
     }
   }
   return [head,number,tail]
 })

먼저 특이점이 있다면 숫자와 그외 문자를 구분하기 위해

isNaN 메서드를 사용하지 않고 직접 숫자인지 여부를 확인하는 함수를 만들었는데,

자바스크립트에서는 공백(' ')의 경우 isNaN(' ') 이 숫자와 같은 false를 반환하기 때문이었다. (처음 안 사실이다.)

 

이어서 'head / number / tail' 각 변수를 선언 후 반복문을 통해 채워나갔다.

이때 headCheck / numberCheck 이라는 변수도 함께 선언했는데

이는 각각 head와 number가 온전히 채워졌는지 확인하는 역할을 한다. 

 

예를 들어 'foo010bar020' 이라는 문자열이 존재한다고 할 때

head : foo / number : 010 / tail : bar020 가 아니라,

head : foobar / number 010020 이 되지 않게 하기 위함이다.

 

다음은 정렬하는 과정이다.

// 문자열 정렬을 위한 함수
const compareStr = (a,b) => {
  const [str1, str2] = [a,b].map(el => el.toLowerCase())
  return str1.localeCompare(str2)
}

// tail의 정렬을 위한 배열
// 여기서 인덱스 0, 1, 2는 각각 head, number, tail
const tails = [...mapping].map(el => el[2])

const result = mapping.sort((a,b)=> {
  if(compareStr(a[0],b[0])) return compareStr(a[0],b[0])
  else if(!compareStr(a[0],b[0])) return Number(a[1]) - Number(b[1])
  else if(Number(a[1]) === Number(b[1])) {
    return tails.indexOf(a[2]) - tails.indexOf(b[2]) 
  }
}).map(el => el.join(''))

 

먼저 head 부분 정렬을 위해 compareStr 함수를 만들어 사용했다.

그 이유로는 단순히 localeCompare를 정렬하는 경우,

'img', 'IMG' 를 정렬한다고 했을 때 소문자로 이루어진 'img'가 앞에 정렬되기 때문에

같은 형식으로 만들어 정렬해줄 필요가 있었다. (대소문자에 따라 정렬을 다르게 하지 않는다는 조건있음)

 

number 부분은 정렬은 단순히 문자열로 된 것을 숫자로 바꾸어 정렬하면 된다.

 

tail 부분은 '입력에 주어진 순서를 유지한다'는 조건 때문에

tail 부분만 가지고 있는 tails 배열을 앞에서 선언 후, 해당 인덱스에 따라 정렬하도록 했다.

 

전체 소스 코드

const verifyNum = (s) => {
  const nums = '0123456789'
  return nums.includes(s)
}

const compareStr = (a,b) => {
  const [str1, str2] = [a,b].map(el => el.toLowerCase())
  return str1.localeCompare(str2)
}

function solution(files) {
  const mapping = files.map(str => {
    let [head,number,tail] = ['','','']
    let [headCheck, numberCheck] = [false, false]
    
    for(let i = 0 ; i < str.length ; i ++) {
      if(headCheck && numberCheck) tail += str[i]
      else if(!verifyNum(str[i])) {
          head += str[i]
          if(verifyNum(str[i+1])) headCheck = true
      }
      else if(verifyNum(str[i])) {
        number += str[i]
        if(!verifyNum(str[i+1])) numberCheck = true  
      }
    }
    return [head,number,tail]
  })

  const tails = [...mapping].map(el => el[2])

  const result = mapping.sort((a,b)=> {
    if(compareStr(a[0],b[0])) return compareStr(a[0],b[0])
    else if(!compareStr(a[0],b[0])) return Number(a[1]) - Number(b[1])
    else if(Number(a[1]) === Number(b[1])) {
      return tails.indexOf(a[2]) - tails.indexOf(b[2]) 
    }
  }).map(el => el.join(''))

  return result
}

console.log(solution([
  "img12.png", 
  "img10.png", 
  "img02.png", 
  "img1.png", 
  "IMG01.GIF", 
  "img2.JPG"]))
Comments