Coding (164)

05
29

ㅇ리소스 누수를 막아봅시다

1. useMemo?

  • input이나 state 값이 바뀌는 경우 react는 컴포넌트들을 리렌더링 한다. 
  • 이때, 변화가 없는 값을 계산하는 함수일 경우 다시 호출 될 필요가 없다.
  • 이럴 때, 최적화를 위해 사용되는 것이 useMemo이다.
  • 진정한 뜻은 'memoization'에서 온 것으로, 메모리에 이전 값을 저장해서 변경이 없는 값이면 불필요하게 계산을 하지 않게 된다. 

2. 예시

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [num, setNum] = useState(100);
  const [value, setValue] = useState("");

  const getNum = () => {
    console.log("num : ", num);
    return num;
  };

  const item = getNum();

  return (
    <div className="App">
      <h1>num : {item}</h1>
      <button onClick={() => setNum((prev) => ++prev)}>Add num</button>
      <h1>value : {value}</h1>
      <input onChange={(e) => setValue(e.target.value)} />
    </div>
  );
}
  • 매우 간단한 코드이다. 당근, input의 state인 value가 바뀌면 전혀 관계없는 num를 알려주는 getNum도 호출되므로, num은 바뀌지 않았지만 불필요하게 함수가 호출된다.

리소스가 줄줄센다

  • 하지만 useMemo를 사용해서 바꿔줄 수 있다.
import "./styles.css";
import { useState, useMemo } from "react";

export default function App() {
  const [num, setNum] = useState(100);
  const [value, setValue] = useState("");

  const getNum = () => {
    console.log("num : ", num);
    return num;
  };

  const item = useMemo(() => getNum(), [num]);

  return (
    <div className="App">
      <h1>num : {item}</h1>
      <button onClick={() => setNum((prev) => ++prev)}>Add num</button>
      <h1>value : {value}</h1>
      <input onChange={(e) => setValue(e.target.value)} />
    </div>
  );
}
  • useEffect를 사용할 때 처럼, num이 바뀔 때만 getNum을 호출하자. 그럼 value가 바뀔 때는 getNum은 호출되지 않는다.

땜빵전문

3. 실전팁

  • 각종 상황에 사용할 수 있지만, 가장 유용하다고 생각하는 부분은 아래 코드처럼 불필요한 컴포넌트 렌더링 자체를 피할 수 있다.
  • 슬라이드 메뉴, 파일을 넘나들지만 state로 관리되면 안되는 데이터들도 useMemo로 처리할 수 있다.
import { useMemo  } from 'react';

function Analysis() {
  const { path } = useParams();

  const content = useMemo(() => {
    switch (path) {
      case 'home':
        return <Home />;
      case 'login':
        return <Login />;
      case 'intro':
        return <Intro />;
      case 'Info':
        return <Info />;
      default:
        return null;
    }
  }, [path]);

  return (
    <div>{content}</div>
  );
}

export default App;

4. 주의사항

  • useMemo의 사용이 항상 옳지는 않다. 리렌더링을 방지하는 용도로 사용하는 것이 아니라, '리소스'를 절약해 주는 것에 의의가 있다.
  • 복잡한 함수가 아닐경우, 함수를 한 번 더 호출하는 것이 나은 경우도 있다. useMemo를 남발하면 코드 유지보수가 어려워진다.
  • 그리고 결국 메모리를 사용하는 작업이고, 결과가 바뀔 때마다 계산을 시도는 하기 때문에 너무 변동이 많은 곳에 사용하면 성능이 되려 떨어지게 될 수 도 있다.
COMMENT
 
02
21

1. 문제 설명

 

코딩테스트 연습 - 짝지어 제거하기

짝지어 제거하기는, 알파벳 소문자로 이루어진 문자열을 가지고 시작합니다. 먼저 문자열에서 같은 알파벳이 2개 붙어 있는 짝을 찾습니다. 그다음, 그 둘을 제거한 뒤, 앞뒤로 문자열을 이어 붙

programmers.co.kr

짝지어 제거하기는, 알파벳 소문자로 이루어진 문자열을 가지고 시작합니다. 먼저 문자열에서 같은 알파벳이 2개 붙어 있는 짝을 찾습니다. 그다음, 그 둘을 제거한 뒤, 앞뒤로 문자열을 이어 붙입니다. 이 과정을 반복해서 문자열을 모두 제거한다면 짝지어 제거하기가 종료됩니다. 문자열 S가 주어졌을 때, 짝지어 제거하기를 성공적으로 수행할 수 있는지 반환하는 함수를 완성해 주세요. 성공적으로 수행할 수 있으면 1을, 아닐 경우 0을 리턴해주면 됩니다.

예를 들어, 문자열 S = baabaa 라면

b aa baa → bb aa → aa 

의 순서로 문자열을 모두 제거할 수 있으므로 1을 반환합니다.

 

2. 제한 사항

  • 문자열의 길이 : 1,000,000이하의 자연수
  • 문자열은 모두 소문자로 이루어져 있습니다.

 

3. 입출력 예제

s result
baabaa 1
cdcd 0

 

4. 나의 접근 방식

  • 처음에는 split으로 모든 문자를 나누어 보았다.
  • 이후, 가장 처음 문자를 보고 두번째 문자와 비교해서 같으면 shift 두 번으로 빼버리려고 했다. 완전히 실패.
실패한 코드
function solution(s) {
    let splitStr = s.split('');

    for(let i = 0; i < splitStr.length; i++) {
        let first = splitStr[i];
        if(first === splitStr[i + 1]) {
            splitStr.shift();
            splitStr.shift();
        }
        else {
            // 맞지 않으면 다시 뒤로 넣어준다.
            splitStr.push(first);
        }
    }
    
    // 한번 이상 검사할 수 없기 때문에 무조건 0이 나온다.
    if(splitStr.length === 0) return 1;
    else return 0;
}​
  • 그런대 잘 생각해 보니 앞에서부터 할 필요가 없이, 스택처럼 쌓아서 뒤부터 보고 연속으로 같은 문자가 들어오면 뒤를 빼버리면 될 것이라고 생각했다.

 

4. 결과

function solution(s) {
    // 스텍으로 쓸 빈 배열을 만든다.
    let arr = [];
    
    for(let i = 0; i < s.length; i++) {
        // 각 요소들을 넣어주면서
        arr.push(s[i]);
        // 뒤부터 들어온 스텍에 똑같은 문자가 연속해서 들어 온다면,
        if(arr[arr.length - 1] === arr[arr.length - 2]) {
            // 두개를 빼 버린다.
            arr.pop();
            arr.pop();
        }
    }
    
    // 반복문을 다 돌고 나서, 스텍이 비어 있다면 전부 제거가 된 것이다.
    if(arr.length === 0) return 1;
    // 만약, 겹치지 않았다면 스텍에는 요소가 남아 있을 것이므로, 0을 반환한다.
    else return 0;
}

 

5. 개선점이 있다면?

  • 위의 방법으로도 왠지 풀 수 있을 것 같다...! (물론, 쉽지도 않고 효율도 떨어질 것이다)
  • 다양한 방식의 문제를 접하고, 어떻게 풀어야 할 지 더 다양한 방법으로 생각하는 것이 중요할 것 같다.
  • 조금 실수가 있었었다. 위의 문제는 split을 쓰면 올바르게 답을 작성해도 시간 초과가 나와서 테스트를 실패한다. 굳이, split을 쓰지 않고, 문자열 상태 그대로 반복문으로 해결할 수 있는 부분인데, pop과 같은 배열에서 사용할 수 있는 함수를 사용하려고 바꿨던 것이다. 이런 식으로 필요 없는 함수의 사용을 줄여 효율성을 생각하는 것도 중요한 부분이 될 것이다.
COMMENT
 
02
20

1. 문제 설명

 

코딩테스트 연습 - 구명보트

무인도에 갇힌 사람들을 구명보트를 이용하여 구출하려고 합니다. 구명보트는 작아서 한 번에 최대 2명씩 밖에 탈 수 없고, 무게 제한도 있습니다. 예를 들어, 사람들의 몸무게가 [70kg, 50kg, 80kg, 5

programmers.co.kr

무인도에 갇힌 사람들을 구명보트를 이용하여 구출하려고 합니다. 구명보트는 작아서 한 번에 최대 2명씩 밖에 탈 수 없고, 무게 제한도 있습니다.

예를 들어, 사람들의 몸무게가 [70kg, 50kg, 80kg, 50kg]이고 구명보트의 무게 제한이 100kg이라면 2번째 사람과 4번째 사람은 같이 탈 수 있지만 1번째 사람과 3번째 사람의 무게의 합은 150kg이므로 구명보트의 무게 제한을 초과하여 같이 탈 수 없습니다.

구명보트를 최대한 적게 사용하여 모든 사람을 구출하려고 합니다.

사람들의 몸무게를 담은 배열 people과 구명보트의 무게 제한 limit가 매개변수로 주어질 때, 모든 사람을 구출하기 위해 필요한 구명보트 개수의 최솟값을 return 하도록 solution 함수를 작성해주세요.

 

2. 제한 사항

  • 무인도에 갇힌 사람은 1명 이상 50,000명 이하입니다.
  • 각 사람의 몸무게는 40kg 이상 240kg 이하입니다.
  • 구명보트의 무게 제한은 40kg 이상 240kg 이하입니다.
  • 구명보트의 무게 제한은 항상 사람들의 몸무게 중 최댓값보다 크게 주어지므로 사람들을 구출할 수 없는 경우는 없습니다.

 

3. 입출력 예제

people limit return
[70, 50, 80, 50] 100 3
[70, 80, 50] 100 3

 

4. 나의 접근 방식

  • limit를 넘기지 않는 가장 큰 수 중에 2개는 people에서 가장 큰 수와 가장 작은 수를 더 하면 된다고 생각했다.
  • 정답은 나온다. 하지만 효율성 실패
실패한 코드
function solution(people, limit, count = 0) {
    // 가장 큰 수와 가장 작은 수를 구한다.
    let max = Math.max(...people);
    let min = Math.min(...people);

    // 제귀함수의 탈출 조건이다.
    if(people.length === 0) return count;

    // 둘을 더 했을때 limit보다 작으면 같이 제거한다.
    if(max + min <= limit) {
        count++;
        people.splice(people.indexOf(max), 1)
        people.splice(people.indexOf(min), 1)
    }

    // 보다 크다면 가장 큰 수만 제거해 준다.
    if(max + min > limit) {
        count++;
        people.splice(people.indexOf(max), 1)
    }

    return solution(people, limit, count)
}​
  • 대부분의 프로그래머스 문제들의 효율성 문제는 '정렬'이 안 된 상태로 계산해서 틀리는 경우가 많았다.
  • 그래서 일단 sort를 이용해 정렬을 해 놓고 생각해 보았다.
  • 로직은 맞는 것 같아서 이 방식대로 정렬한 후의 index를 이용해서 문제를 해결할 수 있었다.

 

4. 결과

function solution(people, limit) {
    let result = 0;
    // 정렬해 준다.
    people.sort((a,b) => b - a);

    // 각각의 index를 구해준다.
    // 당연히, 가장 앞이 가장 작은수, 가장 끝이 가장 큰 수가 된다.
    let minIndex = 0;
    let maxIndex = people.length - 1;

    // 가장 작은 수의 index가 가장 큰 수의 index를 넘길때 까지 반복하며,
    while(minIndex < maxIndex) {
        // 둘을 더 한 수를 limit와 비교한다.
        // 더 한 수가 limit보다 크면 minIndex만 늘려주고,
        let sum = people[minIndex] + people[maxIndex];
        if(sum > limit) minIndex++;
        // 넘지 않는 다면 가장 작은수와 가장 큰 수 둘다 보트에 탈 수 있으므로,
        // 같이 조절 해 준다.
        else {
            minIndex++;
            maxIndex--;
        }
        // 보트가 움직인 횟수를 더 해 준다.
        result++;
    }
    if(minIndex === maxIndex) result++;
    return result;
}
COMMENT
 
02
19

1. 문제 설명

 

코딩테스트 연습 - 모음사전

사전에 알파벳 모음 'A', 'E', 'I', 'O', 'U'만을 사용하여 만들 수 있는, 길이 5 이하의 모든 단어가 수록되어 있습니다. 사전에서 첫 번째 단어는 "A"이고, 그다음은 "AA"이며, 마지막 단어는 "UUUUU"입니

programmers.co.kr

사전에 알파벳 모음 'A', 'E', 'I', 'O', 'U'만을 사용하여 만들 수 있는, 길이 5 이하의 모든 단어가 수록되어 있습니다. 사전에서 첫 번째 단어는 "A"이고, 그다음은 "AA"이며, 마지막 단어는 "UUUUU"입니다.

단어 하나 word가 매개변수로 주어질 때, 이 단어가 사전에서 몇 번째 단어인지 return 하도록 solution 함수를 완성해주세요.

 

2. 제한 사항

  • word의 길이는 1 이상 5 이하입니다.
  • word는 알파벳 대문자 'A', 'E', 'I', 'O', 'U'로만 이루어져 있습니다.

 

3. 입출력 예제

word result
"AAAAE" 6
"AAAE" 10
"I" 1563
"EIO" 1189

 

4. 나의 접근 방식

  • 가장 청음에 나올 'A' 부터 입력받은 word까지 모든 경우의 수를 배열에 담는다.
  • 사전의 규칙처럼, 한 모음을 돌고 나면 가장 마지막 앞의 모음이 다음 모음으로 바뀌고 가장 끝의 모음이 다시 변경되면서 모든 모음을 넣는 규칙이 있다.
  • 이를 통해 모든 순서를 배열에 담을 수 있다.

 

5. 결과

function solution(word) {
    let result = [];
    let words = ["A", "E", "I", "O", "U"];
    
    // 깊이를 계산할 함수를 선언한다.
    const getWord = (cur, depth) => {
        // 길이가 6이라면
        if (depth === 6) return;
        // 나온 모음의 묶음을 result에 넣는다.
        result.push(cur);

        // 반복문을 돌면서
        for (let i = 0; i < words.length; i++) {
            // 나온 모음에 다음 모음을 추가하면서 깊이를 더 해 준다.
            getWord(cur + words[i], depth + 1);
        }
    };
    
    // 이 과정을 word에서 받은 모음까지의 모든 모음의 묶음을 구한다.
    words.map((word) => getWord(word, 1));
    // 만들어진 모든 순서에 indexOf로 해당하는 모음을 찾아서
    // 0번째 부터 이기 떄문에 + 1 한다.
    return result.indexOf(word) + 1;
}

 

6.  개선점이 있다면?

  • 모든 경우의 수를 구한다는 것이 효율성이 매우 떨어진다. 더 간단하게 패턴을 이용해 풀 수는 없을까?
  • DFS를 이용하여 푸는 방식이 있다는데 찾아볼 필요가 있다.
  • 자료구조에 대한 자유로운 사용이 선행 되어야 더 쉽게 적용하고 풀 수 있을 것이다.
COMMENT
 
1 2 3 4 5 6 7 8 ··· 41