ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [CodeKata] wecode 3주차 코드카타
    Algorithm 2020. 12. 28. 14:55
    반응형

    🤔 서론

    레플릿으로, 알고리즘을 1일 1문제를 푸는 세션이다. 시작하기에 앞서, 뜬금없지만 코드카타 'Kata' 의 어원이 궁금해졌다.

     

    먼저, Codekata의 뜻은 프로그래머가 연습과 반복을 통해 기술을 연마하도록 돕는 프로그래밍 연습을 의미한다.

    여기서 Kata는, 1999년 The Pragmatic Programmer 의 공동저자인 Dave Thomas 가 무술의 일어개념인 Kata로 칭하면서 통용되었다고


    👊 12.28(월) / 이중회문

    첫 문제라서 간단한 사칙연산을 예상했는데, 생각보다 어려운 문제가 튀어나왔다!! 🤯🤯🤯

     

    📜 문제

    twoSum함수에 숫자배열과 '특정 수'를 인자로 넘기면, 더해서 '특정 수'가 나오는 index를 배열에 담아 return해 주세요.

    • nums: 숫자 배열
    • target: 두 수를 더해서 나올 수 있는 합계
    • return: 두 수의 index를 가진 숫자 배열

    예를 들어, nums은 [4, 9, 11, 14] / target은 13 이면, nums[0] + nums[1] = 4 + 9 = 13 이죠?

    그러면 [0, 1]이 return 되어야 합니다.

     

    🧮 풀이

    const twoSum = (nums, target) => {
      // let answer;
      for (let i = 0 ; i < nums.length ; i++) {
        for (let j = i+1 ; j < nums.length ; j++) {
          if (nums[i] + nums[j] === target) {
            return [i, j];
            break;
          }
        }
      // if (answer) { break; }
      }
    }
    1. 먼저, nums 배열을 순회하는 인덱스값을 2가지로 설정한다. (i, j)
    2. i는 0부터 끝(<nums.length)까지, j는 중복을 피하기 위해 (i+1)부터 끝까지 순회한다. ([0,0], [0,1], [0,2], [0,3] -> [1,1], [1,2]...)
    3. 두 인덱스의 값의 합(nums[i] + nums[j]) 이 target 값과 일치하면, [i, j] 를 반환하고 break 문으로 반복문을 종료한다.

    🖇 리뷰

    이중회문과 조건문만으로 풀 수 있었기에, 첫 워밍업으로 적절하지 않았나 생각된다.

    나는 알고리즘의 핵심은 시간단축이라고 생각해서, 인덱스 중복을 피하고 조건문이 옳을 시 break 문도 추가했다.

     

    바깥 반복문도 break 하고자 처음에 answer 변수를 선언하고, 바깥에서 answer가 존재하면 연산이 끝났으니 break하는 방법도 시도했다. 하지만, 시간차이가 거의 없거나 오히려 길게 걸리기도 해서(아마 변수선언과 조건문 추가 영향일듯?) 삭제하였다.


    👊 12.29(화) / Number - String 변환

     

    📜 문제

    reverse 함수에 정수인 숫자를 인자로 받습니다. 그 숫자를 뒤집어서 return해주세요. (x: 숫자, return: 뒤집어진 숫자를 반환!)

     

    예를 들어,
    x: 1234 / return: 4321

    x: -1234 / return: -4321

    x: 1230 / return: 321

     

    🧮 풀이

    const reverse = x => {
      let arr = x.toString().split('').reverse();
      while (arr[0] === 0) {
        arr.shift();
      }
      if (arr[arr.length-1] === '-') {
        arr.unshift(arr.pop());
      }
      return Number(arr.join(''))
    }
    1. 먼저 숫자 x를 인자로 받아, 문자변환(toString), 글자분할 및 배열저장(split), 역순(reverse) 처리를 한 배열(arr)을 지정했다.
    2. 앞의 0을 빼기 위해, while 반복문으로 arr[0] 요소를 검사 및 shift() 처리했다.
    3. 음수였을 경우, 맨 뒤로간 '-' 를 pop() 한 뒤, 다시 이를 맨 앞으로 unshift() 처리했다.
    4. 모든 예외조건을 가공한 최종배열을 문자열로 묶고(join), 숫자로 변환(Number, ParseInt...)하여 최종값으로 반환했다.
    return parseFloat(x.toString().split('').reverse().join('')) * Math.sign(x)

    구글링으로 찾은 매우 좋은 풀이이다. 뒤집은 문자열을 parseFloat 하면 숫자변한 / 앞의 0 제거 / 뒤의 '-' 문자제거 모두 달성할 수 있다.

    Math.sign(x) 는, x(숫자)의 부호를 가져오는 메소드로, 음수처리 뿐만 아니라 0(-0)일 땐 0(-0)을 반환하므로 0 처리까지 가능하다.

     

    🖇 리뷰

    Number()에 비해 parseFloat()는 뒤쪽 문자열의 삭제 등, 숫자변환에 있어 더욱 효과적인 메소드임을 알았다. (앞의 문자열은 NaN 반환)

    또한, Math.sign 이라는 유용한 메소드도 알게 되었다. 음수처리 뿐만 아니라, 0을 깔끔하게 정리할 수 있으므로 알고리즘에 유용할 것 같다.


    👊 12.30(수) / String 메서드(length, substring, includes, split+join, reverse)

     

    📜 문제1

    String 형인 str 인자에서 중복되지 않은 알파벳으로 이루어진 제일 긴 단어의 길이를 반환해주세요.

    (str: 텍스트 / return: 중복되지 않은 알파벳 길이 (숫자 반환))

     

    예를 들어,
    str = "abcabcabc" / return 은 3 => 'abc' 가 제일 길기 때문

    str = "aaaaa" / return 은 1 => 'a' 가 제일 길기 때문

    str = "sttrg" / return 은 3 => 'trg' 가 제일 길기 때문

     

    🧮 풀이

    const getLengthOfStr = str => {
    
      const setLongLen = (txt) => {
        let result;
        if (!txt) {
          result = 0
        }
        else {
          let wordArr = [];
          let cutIdx = 0;
          for (let i = 0 ; i < txt.length ; i++) {
            const cutWord = txt.substring(cutIdx, i)
            if (cutWord.includes(txt[i])) {
              wordArr.push(cutWord)
              cutIdx = i;
            }
            else {
              if (i === txt.length - 1) {
                wordArr.push(txt.substring(cutIdx, txt.length));
                break;
              }
              continue;
            }
          }
          result = wordArr.map(ele => ele.length).reduce((a,b) => {
            return a > b ? a : b
          })
        }
        return result;
      }
      
      const revStr = str.split('').reverse().join('');
      return setLongLen(str) > setLongLen(revStr) ? setLongLen(str) : setLongLen(revStr)
    }
    1. 먼저, 가장 긴 단어의 길이를 반환하는 setLongLen() 함수를 만들었다. (이를 만든 이유는 반대 경우도 적용해야 했기 때문)
    2. setLongLen() 은 result를 반환. txt가 없으면 0, 있으면 최대 길이값을 계산하여 반환한다.
    3. string 분할은, 글자별로 순회하면서 이전 단어들과 중복될 경우 시작 인덱스에서 현재 인덱스까지 substring() 후 wordArr 배열 추가
    4. wordArr 배열에 추가됬다면, 시작 인덱스(cutIdx)를 현재 인덱스로 최신화해준다. 마지막 인덱스에선 배열추가 후 break로 종료
    5. 마지막으로, result 값은 이 단어배열의 길이들 중 최대값을 반환한다.
    6. 단어를 뒤집은 경우(revStr) 역시 생각을 했는데, 이는 아래와 같은 예제에서 오류가 발생해서이다.
    getLengthOfStr('abcdefghcijklmnop') => ['abcdefgh', 'cijklmnop'] => 최대값 : 9
    getLengthOfStr('ponmlkjichgfedcba') => ['ponmlkjichgfed', 'cba'] => 최대값 : 14

     

    🖇 리뷰

    개인적으로 매우 맘에 들었던 다른 풀이를 적어보았다.

    const getLengthOfStr = str => {
      let sliceStr = [];
      let result = 0;
      
      for (let i = 0 ; i < str.length ; i++) {
        if (sliceStr.indexOf(str[i]) === -1) {
          sliceStr.push(str[i]);
          if (result < sliceStr.length) {
            result = sliceStr.length
          }
        }
        else {
          sliceStr = sliceStr.slice(sliceStr.indexOf(str[i]) + 1)
          sliceStr.push(str[i])
        }
      }  
      return result;
    }
    
    getLengthOfStr('abcdefghcijklmnop')
    1. 변수 : sliceStr(str 글자들을 분해해서 조건부로 담는 배열), result(최대길이 갱신)
    2. str 문자열을 반복하며 각 글자에 대한 조건문을 적용
    3. 글자가 sliceStr에 없다면, 이를 새로 push 하고 기존 result와 sliceStr 중 긴 값으로 최신화 작업을 한다. (알고리즘)
    4. 글자에 sliceStr이 있다면, 해당 글자의 인덱스(indexOf)까지 잘라낸다.(slice) 이후, 해당 글자를 똑같이 push 한다.
    5. 주목해야 할 점은, indexOf를 사용했기 때문에 같은 글자가 두 번 반복되면 앞쪽 인덱스에서 잘라내기 때문에 최대길이가 나온다.
    6. 위처럼, 'abcdefghcijklmnop' 예외가 해결된다. 또한, 빈 String이 들어와도 result 초기값인 0이 반환되는 예외해결에 좋은 코드다.

     

    📜 문제2

    숫자인 num을 인자로 넘겨주면, 뒤집은 모양이 num과 똑같은지 여부를 반환해주세요.

    (num: 숫자 / return: true or false (뒤집은 모양이 num와 똑같은지 여부))

     

    예를 들어,
    num = 123 / return false => 뒤집은 모양이 321 이기 때문

    num = 1221 / return true => 뒤집은 모양이 1221 이기 때문

    num = -121 / return false => 뒤집은 모양이 121- 이기 때문

    num = 10 / return false => 뒤집은 모양이 01 이기 때문

     

    🧮 풀이

    const sameReverse = num => {
      const fwdNum = num.toString()
      const bwdNum = fwdNum.split('').reverse().join('')
    
      return fwdNum === bwdNum;
    }
    1. 우선, 변환도 해야 하고 자료형을 통일(String) 하기 위해, toString() 으로 바꾼 fwdNum 을 새로 만들었다.
    2. bwdNum은 이를 reverse() 처리했다. 이 둘을 비교한 boolean을 반환한다.

    🖇 리뷰

    2일차에 리뷰한, split+reverse+join 조합으로 쉽게 풀 수 있는 문제였다.

    숫자로 전환(parseFloat) 하지 않은 이유는 -121 === 121- / 10 === 01 경우처럼, 숫자 양식으로 비교되지 않았기에 별도 처리하진 않았다. 


    👊 12.31(목) / String 메서드, 순회 조합

     

    📜 문제

    strs은 단어가 담긴 배열입니다. 공통된 시작 단어(prefix)를 반환해주세요.

     

    예를 들어,
    strs = ['start', 'stair', 'step'] => return은 'st'

    strs = ['start', 'wework', 'today'] => return은 ''

     

    🧮 풀이

    const getPrefix = strs => {
      let prefix = '';
      if (strs.length !== 0) {
        for (let i = 0 ; i<strs[0].length ; i++) {
          const testStr = prefix + strs[0][i];
          const testArr = strs.map(str => str.includes(testStr) ? 1 : 0)
          if (!testArr.includes(0)) {
            prefix = testStr;
          }
        }
      }
      return prefix;
    }
    1. 변수 : prefix(공통단어, 처음은 ''), testStr(prefix에 strs[0] 한 글자씩 더한 값), testArr(strs 글자들의 testStr 포함여부 배열)
    2. 예외처리 : 빈 배열이 들어오지 않는 경우만 로직 진행. 빈 배열은 바로 '' 반환.
    3. strs 첫 글자를 기준으로 prefix 만듬. strs[0] 한 글자씩 더하면서 이것을 strs 단어들이 포함하는지 확인하는 testArr 제작
    4. testArr은 포함한다면 1, 포함하지 않으면 0이 담긴다. 0이 없다는 것은 공통문자란 뜻으로, prefix를 testStr로 갱신

    🖇 리뷰

    역시 다른 좋은 풀이를 적어보았다. 이는, prefix가 공통된 시작 단어란 점을 활용해서, strs[0] 뒤에서 한 글자씩 제거하는 로직이다.

    const getPrefix = (strs) => {
      let prefix = strs[0];    
      if(strs.length === 0){	 
        prefix = "";
      }
      for(let i=1; i<strs.length; i++){            
        while(strs[i].indexOf(prefix) !== 0){               
          prefix = prefix.substring(0, prefix.length-1)
        }
      }
      return prefix;
    }
    
    • prefix를 빈 문자열이 아닌, strs[0]으로 설정한다. 역시 예외처리는 필요한 것으로 보인다.
    • strs[1]부터 순회하며, prefix가 포함되지 않을때까지 prefix를 잘라낸다. (substring(0, prefix.length - 1)) 

    CodeKata 첫 주라, 문제가 쉬울줄 알았지만 생각보다 고민을 많이 해야하는 것들이었다.

    좋은 점은, 위코더 블로그들에서 다른 풀이들을 참고할 수 있고, 개선점을 타산지석하며 알고리즘 사고를 늘려가야겠다.

    반응형
Designed by Tistory.