스터디 기록 14

유아현·2022년 12월 7일
0

Study

목록 보기
15/27
post-thumbnail

오늘의 스터디 문제 목록

신규 아이디 추천

카카오에 입사한 신입 개발자 네오는 "카카오계정개발팀"에 배치되어, 카카오 서비스에 가입하는 유저들의 아이디를 생성하는 업무를 담당하게 되었습니다. "네오"에게 주어진 첫 업무는 새로 가입하는 유저들이 카카오 아이디 규칙에 맞지 않는 아이디를 입력했을 때, 입력된 아이디와 유사하면서 규칙에 맞는 아이디를 추천해주는 프로그램을 개발하는 것입니다.
다음은 카카오 아이디의 규칙입니다.

아이디의 길이는 3자 이상 15자 이하여야 합니다.
아이디는 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.) 문자만 사용할 수 있습니다.
단, 마침표(.)는 처음과 끝에 사용할 수 없으며 또한 연속으로 사용할 수 없습니다.
"네오"는 다음과 같이 7단계의 순차적인 처리 과정을 통해 신규 유저가 입력한 아이디가 카카오 아이디 규칙에 맞는지 
검사하고 규칙에 맞지 않은 경우 규칙에 맞는 새로운 아이디를 추천해 주려고 합니다.
신규 유저가 입력한 아이디가 new_id 라고 한다면,
1단계 new_id의 모든 대문자를 대응되는 소문자로 치환합니다.
2단계 new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거합니다.
3단계 new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.
4단계 new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.
5단계 new_id가 빈 문자열이라면, new_id에 "a"를 대입합니다.
6단계 new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다.
     만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.
7단계 new_id의 길이가 2자 이하라면, new_id의 마지막문자를 new_id의 길이가 3이 될때까지 반복해서 끝에 붙입니다.
function solution(new_id) {
  //1단계, 모든 대문자를 대응된느 소문자로 치환
  new_id = new_id.toLowerCase();
  //2단계, 알파벳 소문자, 숫자, 빼기, 밑줄, 마침표를 제외한 모든 문자 제거
  new_id = new_id
    .replaceAll(/[^a-z0-9-_.]/g, "")
    // 3단계, new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.
    .replaceAll(/[.]{2,}/g, ".")
    // 4단계, new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.
    .replaceAll(/^[.]|[.]$/g, "");
  // 5단계, new_id가 빈 문자열이라면, new_id에 "a"를 대입합니다.
  if (new_id === "") new_id = "a";
  // 6단계, new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다.
  if (new_id.length > 15) {
    new_id = new_id.slice(0, 15);
    // 만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.
    new_id = new_id.replaceAll(/[.]$/g, "");
  }

  // 7단계, new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 
  // 3이 될 때까지 반복해서 끝에 붙입니다.
  if (new_id < 3) {
  }

  for (let i = new_id.length; i < 3; i++) {
    new_id += new_id[new_id.length - 1];
  }

  return new_id;
}

solution("ABCd12348efgh@i&*jklmn.p");

처음에 문제 지문이 너무 길어서 문제 자체를 이해할 수 없을 거라고 생각했지만 생각보다 내용만 장황하고 괜찮았던 문제였다 보자마자 이건 다 정규식을 갈겨야겠군... 싶어서 정규식을 찾아서 단계별로 순차적으로 풀어 주었다.

function stage1(id) {
  return id.toLowerCase();
}

function stage2(id) {
  const result = [];
  for (const el of id) {
    if (/[a-z\d\-\_\.]/.test(el)) result.push(el);
  }
  return result;
}

function stage3(idArr) {
  const result = [];
  for (const el of idArr) {
    if (result.at(-1) === "." && el === ".") continue;
    result.push(el);
  }
  return result;
}

function stage4(idArr) {
  if (idArr.at(0) === ".") idArr.shift();
  if (idArr.at(-1) === ".") idArr.pop();
  return idArr;
}

function stage5(idArr) {
  return idArr.length === 0 ? ["a"] : idArr;
}

function stage6(idArr) {
  if (idArr.length >= 16) {
    idArr.splice(15);
    if (idArr.at(-1) === ".") idArr.splice(-1);
  }
  return idArr;
}

function stage7(idArr) {
  if (idArr.length <= 2) {
    let arr = [...idArr];
    for (let i = idArr.length; i < 3; i++) {
      arr[i] = idArr.at(-1);
    }
    return arr.join("");
  }
  return idArr.join("");
}

function solution(new_id) {
  return stage7(stage6(stage5(stage4(stage3(stage2(stage1(new_id)))))));
}
function solution(new_id) {
    const level1 = new_id.toLowerCase();
    return returnValue(level1, [setLevel2,setLevel3,setLevel4,setLevel5,setLevel6,setLevel7])
}

function returnValue(str, arr){
    for(const func of arr){
        str = func(str);
    }
    return str;
}

// 소문자, 숫자, -, _, 마침표 조건으로 걸러준다.
// (code > 47 && code < 58) // numeric(0-9)
// (code > 96 && code < 123) // lower alpha (a-z)
// '-'.charCodeAt === 45
// '_'.charCodeAt === 98
// '.'.charCodeAt === 46
function setLevel2(str){
    let newStr = "";
    for(let i = 0 ; i < str.length ; i++){
        const code = str.charCodeAt(i);
        if((code > 47 && code < 58) ||
          (code > 96 && code < 123) ||
           code === 45 ||
           code === 95 ||
           code === 46           
          ) 
        {
            newStr += str[i];
        }
    }
    return newStr;
}
// for문을 돌며 이전 값이 .인경우 추가하지 않는다.
function setLevel3(str){
    let newStr = '';
    let prev = '';
    for(let i = 0; i < str.length ; i++){
        if(prev === '.' && str[i] === '.'){
            continue;
        }
        newStr += str[i];
        prev = str[i];
    }
    return newStr;
}
// pointer를 앞과 뒤에 2개를 두고
// .이 아닌것을 만날때까지 접근
// 앞과 뒤의 인덱스로 splice를 통해
// 점을 제거
// startsWith('.')
// endsWith('.') 이런 메서드도 있음
function setLevel4(str){
    let start = 0;
    let end = str.length -1;
    while(start <= end){
        if(!(str[start] === '.') && !(str[end] === '.')) break;
        if(str[start] === '.') start++;
        if(str[end] === '.') end--;
    }
    return str.slice(start, end + 1);
}
// length가 0이라면 a를 반환
function setLevel5(str){
    if(!str.length) return 'a';
    return str;
}
// slice로 15개까지 나오게 함. slice도 O(n)이라서 그냥 for문으로 돌렸다.
// 근데 똑같을 듯
// 마지막 인덱스의 값이 .이라면
// 점을 지워주는 lever4를 실행
function setLevel6(str){
    let newStr = "";
    for(let i = 0 ; i < str.length ; i++){
        if(i > 14) break;
        newStr += str[i];
    }
    if(newStr.at(-1) === '.'){
        return setLevel4(newStr);
    }
    return newStr;
}
// 길이가 2 이하라면 3이 될때까지 padEnd를 사용;
function setLevel7(str){
    if(str.length <= 2){
        return str.padEnd(3, str.at(-1));
    }
    return str;
}

나는 정규식으로 풀었지만 민혁, 효근님의 코드를 보면 각 단계별로 함수를 따로 만들어 주셔서 작성하셨다... 난 저럴 정성이 부족했는데,,, 무조건 간편하게 풀어야겠다는 생각으로 정규식을 갈겼지만 이런 방법으로도 풀 줄 알아야 된다고 생각한다. 그리고 효근님께서 의사코드로 적어 주신 부분에 startsWith, endsWith 메서드가 있다는 것을 알게 되었다!

String.prototype.startsWith()

  • 특정 문자열로 시작하는지 확인하여 true, false로 반환
구문 [startsWith(searchString)]
- searchString = 시작 위치로 탐색할 문자열으로 정규표현식이 올 수 없다.

구문 [startsWith(searchString, position)]
- position = 탐색 위치를 지정할 수 있다. 기본값은 0이다.

const str1 = 'Saturday night plans';
console.log(str1.startsWith('Sat'));
// expected output: true
// str1의 첫 문자가 Sat로 시작하므로 true를 반환한다.

console.log(str1.startsWith('Sat', 3));
// expected output: false
// 3번 째의 위치에서 Sat로 시작하지 않으므로 false를 반환한다.

String.prototype.endsWith()

  • 특정 문자열로 끝나는지 확인하여 true, false로 반환
구문 [str.endsWith(searchString[, length])]
- searchString = 특정 문자열로 끝나는지를 찾기 원하는 문자열
- length = 찾고자 하는 문자열의 길이의 값, 기본값은 문자열 전체의 길이

var str = 'To be, or not to be, that is the question.';

console.log(str.endsWith('question.')); // true
// str이 question.으로 끝나고 있으므로 true
console.log(str.endsWith('to be'));     // false
// str은 to be로 끝나지 않고 있으므로 false
console.log(str.endsWith('to be', 19)); // true
// 19번째의 위치에서 to be로 끝나고 있으므로 true

기사단원의 무기

function solution(number, limit, power) {
  //? 1. 각 number의 약수의 개수를 먼저 구함
  //? 2. 약수의 개수가 limit를 초과할 경우 power로 대체
  //? 3. 모두 더해서 return

  let knight = [];
  let divisorCount = 0;
  //? 1번부터 number번까지의 기사 무기 쇼핑 시작
  for (let i = 1; i <= number; i++) {
    divisorCount = 1;
    //? 기사들은 자신의 번호까지 약수 개수만큼의 공격력을 구매할 수 있다!
    //! 시간 초과로 약수 절반까지만 구하고 +1 해주는 방식 사용
    for (let d = 1; d <= i / 2; d++) {
      if (i % d === 0) divisorCount++;
    }

    //! 근데 divisorCount가 limit보다 크면 power로 대체
    divisorCount > limit ? (divisorCount = power) : divisorCount;
    knight.push(divisorCount);
    console.log(divisorCount);
  }
  console.log(knight);
  //? 공격력 1당 1kg로 철 필요, 모든 공격력 더해버리기
  knight = knight.reduce((a, b) => a + b);
  console.log(knight);

  return knight;
}

solution(10, 3, 2);

의사코드에도 작성을 했듯이 각 기사들의 약수의 개수를 구해 이 개수가 limit보다 클 경우 power로 대체해서 약수들의 개수들을 모두 reduce를 통해 합계를 리턴해 주었다. 처음에는 약수를 구하는 부분에서 그냥 i까지 전부 돌게 했더니 테스트 케이스가 시간 초과로 막히게 되었다. 이를 해결하기 위해서 약수를 절반까지 구해 +1 하는 방식을 통해 시간 초과 문제를 해결할 수 있었다. 다른 분들의 코드를 보면서 약수를 구해서 시간 절약을 할 수 있는 방식들을 살펴 볼 수 있었다! 아래 코드는 약수를 구하는 부분만 뜯어서 온 것이다!

function getDivisorsCnt(n) {
  let cnt = 0;
  for (let i = 1; i < Math.sqrt(n); i++) {
    if (n % i === 0) cnt += 2;
  }
  if (Number.isInteger(Math.sqrt(n))) cnt++;
  return cnt;
}
function getDivisors(num){
  let divisors = 0;
  for(let i = 1 ; i <= Math.sqrt(num) ; i++){
      if(num % i === 0) {
          divisors++;
          if(num / i != i) divisors++;
      }
  }
  return divisors;
// 제곱근으로 범위를 줄임
function countDivisor(num){
  let count = 0;
  for(let i = Math.floor(Math.sqrt(num)) ; i > 0  ; i--){        
      if(num % i === 0){
          count++;   
          if(num / i !== i) count++;
      }  
  }
  return count;
}

문자열 나누기

function solution(s) {
  let x = 0;
  let xCnt = 0; // x가 나온 횟수
  let notXCnt = 0; // x가 아닌 다른 글자가 나온 횟수
  let result = [];
  for (let i = 0; i < s.length; i++) {
    // x와 같으면 x++ 아니면 not++
    s[x] === s[i] ? xCnt++ : notXCnt++;

    // 카운트가 같으면 x가 바뀜
    // x의 위치 = x + xCnt + notXCnt
    if (xCnt === notXCnt) {
      result.push(s.slice(x, x + xCnt + notXCnt));
      x += xCnt + notXCnt;
      xCnt = 0;
      notXCnt = 0;
    }
  }

  return result.join("").length < s.length ? result.length + 1 : result.length;
}

solution("abracadabra");

x가 나오는 횟수와 x가 아닌 문자가 나오는 횟수를 비교해 slice를 이용해 범위에 해당하는 문자열을 추출해 결과 배열에 push 해 주어 length를 구하는 방식으로 풀었다. 저렇게까지 복잡하게 할 필요는 없었을 것 같은데... 저 방법말고는 떠오르는 게 없었다 요번 문제를 통해서 slice 값을 주는 법에 대해서 많이 익힐 수 있어서 좋았당 ㅎㅎ

function solution(s) {
    let answer = 0;
    let fixedChar = "";
    let cnt1 = 0;
    let cnt2 = 0;

    // 문자를 for of 문으로 하나씩 확인한다.
    for (let c of s) {

      // 첫 문자를 알아야하기 때문에
      // 미리 선언해둔 fixedChar이 공백이면
      // 첫 문자를 fixedChar에 넣어준다.
      if (!fixedChar) fixedChar = c;

      // 첫 문자와 들어온 c가 같다면 cnt1를 증가시킨다.
      if(fixedChar === c) cnt1++;
      // 첫 문자와 다르다면 cnt2를 증가시킨다.
      else cnt2++;

      // cnt1과 cnt2의 숫자가 같아지면
      // answer를 1 증가시키고 나머지를 초기화 한다.
      if (cnt1 === cnt2) {
          answer++;
          cnt1 = 0;
          cnt2 = 0;
          fixedChar = "";
      }
    }

    // 반복문이 끝났는데 fixedChar이 남아있는 경우는
    // 잔여 문자가 남아있는 경우이므로 answer을 1 증가시킨다.
    if (fixedChar) answer++;

    return answer;
  }
function solution(s) {
  // s 순회
  // x: 단위 첫 글자
  // if x 개수 === x 외 개수 -> split++, x 갱신, xCnt/yCnt 초기화
  // split++ ; 분리된 문자열의 개수 = split + 1
  let split = 0;
  let x = s[0];
  let xCnt = 1;
  let yCnt = 0;
  for (let i = 1; i < s.length; i++) {
    if (xCnt === yCnt) {
      x = s[i];
      xCnt = 1;
      yCnt = 0;
      split++;
      continue;
    }
    if (x === s[i]) xCnt++;
    else yCnt++;
  }
  return ++split;
}

간결하고 깔끔해서 예쁜 동원님, 민혁님 코드... 이 방법과 같이 잘랐다는 의미로 split 변수에 ++만 해 주면 될 것을 나는 괜히 push를 하고 난리브루스를,,, 했던 것 조금 더 알고리즘을 간결하게 생각하는 법을 익혀야게따...

0개의 댓글