어쩌다보니..

혼자 공부할 때 useEffect 언마운트에서 가볍게 스냅샷으로 작동하는 것 같다며 마지막에 언급하고 끝났었다.

그러나 스냅샷보다 훨씬 중요한 무언가가 있었다..

 

바로 클로저였다.

퀴즈

let count = 1;
function makeCounter() {
  count = 0;

  return function() {
    return ++count;
  };
}
count = 10;

let counter = makeCounter();
console.log(counter()); // <-- 뭘까??

정답을 모른다면(글을 읽고 있는 다른 사람이든, 미래의 나든) 밑으로 쭉 내려서 자바스크립트 공식문서를 읽는 편이 좋을듯 하다. 클로저 개념은 필수다!!

일단 퀴즈의 정답은 1이다.

 

나도 오늘 막 배웠는데, useEffect의 언마운트에서 왜 기묘한 일이 발생하는지 이제야 이해할 수 있었다.

 

이전의 포스팅은 나만 볼 수 있으니, 여기에 좀 복붙을 해오자면,

 

특이한 점

import { useEffect, useState } from "react";
const StarRating = ({starCount}) => {
  const [starClickedNum, setStarClickedNum] = useState(0);
  const stars = [];

  // !! 여기부터 중요 !!
	console.log(starClickedNum);
	useEffect(() => {
	  const currentTimetoString = new Date().toLocaleString();
	  return () => {
	    console.log(starClickedNum);
	    console.log(currentTimetoString);
	    console.log(new Date().toLocaleString());
	  }
	}, [starClickedNum])
  // !! 여기까지 중요 !!

  for (let i = 0; i < starCount; i++) {
    stars.push(<img src="/assets/blueStar.png" alt="star" className="w-4 h-4" />);
  }

  return (
    <div className="flex">
    {stars.map((star, index) => (
      <div onClick={() =>setStarClickedNum(prev => prev + 1)} key={index}>{star}</div>
    ))}
    </div>
  )
}

export default StarRating;

starClickedNum이 바뀌었을 때 첫 번째 줄의 starClickedNum은 업데이트 된 값으로,

클린업 함수 내부의 starClickedNum은 업데이트 이전의 값으로 출력된다.

이런식으로 오히려 값이 다시 작아진다.

useEffect 내부만의 스냅샷이 아닌, 전체 코드의 스냅샷을 기준으로 하는듯 하다.

참고로, new Date()의 경우는 실제 실행 시간을 기준으로 출력된다.

결과적으로는 일단 위에서부터 출력하고, dependency 배열 내부의 값이 바뀌기 이전의 스냅샷을 기반으로 클린업 함수를 실행한다.

이렇게 대충 혼자 이해하고 넘어갔었다.

 

결론..

내가 이해한 내용이 대충은 맞다. 그래도 원리는 제대로 모르고 억지로 이해했으니 클로저와 렉시컬 환경을 아는 시점에서 다시 작성해보겠다.

 

예시로 나와있는 클린업 함수 내부에서 starClickedNum을 찾는 과정은 이렇다.

return에 있는 익명함수 → useEffect 내부 → 컴포넌트 전체 (useState에 의해 정의된 starClickedNum이 있음(찾음)) → !! 출력 !!

위의 모든 과정은 리랜더링 이전에 있던 렉시컬 환경에서 이뤄진다.

 

따라서 리랜더링되며 출력된 starClickedNum엔 새로운 렉시컬 환경의 업데이트 된 값이,

클린업 함수 내부에서 출력되는 값은 starClickedNum이 업데이트 되기 전 렉시컬 환경의 값이 들어가 있는 것이다..

 

클린업 함수도 결국엔 함수니까, 숨김 프로퍼티 [[Environment]] 가 있고, 이를 사용해서 자신이 만들어진 환경으로 간다. 그리고 변수에 접근한다.

+

new Date().toLocaleString()은 변수가 아니니까 굳이 렉시컬 환경에서 어떤 값이었는지 알 필요가 없다. 그냥 함수를 실행시켜서 클린업 함수 실행 시점의 시간을 출력하기만 하면 된다.

 

가렵던 부분을 긁은 느낌이다.

참고

https://ko.javascript.info/closure

 

변수의 유효범위와 클로저

 

ko.javascript.info

 

intersection observer 이해하기 에서 이어지는 내용이다.

사진에서 보이듯이, 배열에 원소가 하나 들어갔다가 2개 들어갔다가.. 아주 난리다.

 

여러 대상을 관찰하는 경우에도 보통 entries 배열에는 하나의 요소만 들어있는 것이 정상이다.

 

이 동작의 이유는 !!!!!!!

관찰 + 교차되는 대상에 대해 IntersectionObserver 객체의 콜백 함수가 실행되기 때문이다!!!!!!

코드에서 각각 다른 대상 요소를 관찰하는 3개의 개별 IntersectionObserver 인스턴스를 사용하고 있다. 따라서 콜백 함수는 교차된 각 대상에 대해 한 번씩 호출된다.

결론: 콜백 함수는 관찰된 각 대상에 대해 개별적으로 호출되므로 entries에는 observer.observe를 통해서 구독해놓은 녀석들이 전부 보이는 것이 아니다. 그 중 교차되고 있는 녀석들만 확인할 수 있는 방식이다.

 

여기서 직접 코드를 굴려볼 수 있다.

 

Edit fiddle - JSFiddle - Code Playground

 

jsfiddle.net

 

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

브루트포스로 풀었다.

사용할 곡괭이 딱 10개만 정해서 백트래킹으로..

 

다른 사람들 풀이 보니까 그리디가 있긴 했는데, '이렇게 명백한 백트래킹에선 빠르게 백트래킹 구현하는게 낫지 않나?' 싶다.

백트래킹 코드야 워낙에 정형화 되어있으니, 손가락만 빠르다면 그리디보다 더 효과적일 것 같다.. ㅋㅋ

 

이것만 떡하니 올리긴 뭐해서 챗지피티한테 코드 설명만 부탁해서 첨부하겠다~

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

vector<int> allPicks;
vector<string> allMinerals;
int arr[10];
int brr[10];
bool isused[10];
int picksSum;
int tiredSum = 99999999;

void func(int k) {
    if(k == picksSum) {
        int tempTiredSum = 0;
        for(int i = 0; i < allMinerals.size(); i++) {
            if(brr[i / 5] == 0) {
                break;
            }
            if(allMinerals[i][0] == 's') tempTiredSum += 1;
            else {
                if(brr[i / 5] == 3 && allMinerals[i][0] == 'd') tempTiredSum += 25;   
                if(brr[i / 5] == 3 && allMinerals[i][0] == 'i') tempTiredSum += 5;   
                if(brr[i / 5] == 2 && allMinerals[i][0] == 'd') tempTiredSum += 5;   
                if(brr[i / 5] == 2 && allMinerals[i][0] == 'i') tempTiredSum += 1;   
                if(brr[i / 5] == 1 && allMinerals[i][0] == 'd') tempTiredSum += 1;   
                if(brr[i / 5] == 1 && allMinerals[i][0] == 'i') tempTiredSum += 1;   
            }
        }
        tiredSum = min(tiredSum, tempTiredSum);
        return;
    }
    
    for(int i = 0; i < picksSum; i++) {
        if(!isused[i]) {
            brr[k] = arr[i];
            isused[i] = 1;
            func(k + 1);
            isused[i] = 0;
        }
    }
}

int solution(vector<int> picks, vector<string> minerals) {
    allPicks = picks;
    allMinerals = minerals;
    int coun = 0;
    picksSum = min(10, picks[0] + picks[1] + picks[2]);
    for(int i = 0; i < 3; i++) {
        while(picks[i]) {
            arr[coun++] = i + 1;
            picks[i]--;
            if(coun == 10) break;
        }
        if(coun == 10) break;
    }
    
    func(0);
    
    return tiredSum;
}

이 코드는 주어진 광물을 캐기 위해 곡괭이를 조합하는 모든 경우의 수를 탐색하여 최소한의 피로도를 구하는 함수인 solution을 구현한 코드입니다.

 

코드에서 사용되는 변수들은 다음과 같습니다:

  • allPicks: 다이아몬드, 철, 돌 곡괭이의 개수를 담은 벡터
  • allMinerals: 광물들의 순서를 담은 문자열 벡터
  • arr: 모든 곡괭이 종류를 포함하는 배열
  • brr: 현재 선택한 곡괭이들을 저장하는 배열
  • isused: 곡괭이가 사용되었는지 여부를 나타내는 배열
  • picksSum: 곡괭이 종류의 총 개수를 저장하는 변수
  • tiredSum: 최소 피로도를 저장하는 변수

주요 함수:

  1. func(int k): 재귀적으로 모든 곡괭이를 선택하는 함수. k는 현재 선택한 곡괭이의 개수를 나타냅니다. 모든 곡괭이를 선택한 경우에는 해당 조합에 대한 피로도를 계산하고, 최소 피로도인 tiredSum을 갱신합니다.
  2. solution(vector<int> picks, vector<string> minerals): 주어진 광물을 캐기 위해 가능한 모든 곡괭이 조합을 구하여 최소한의 피로도를 반환하는 함수입니다. 입력으로 주어진 picks 벡터에서 다이아몬드, 철, 돌 곡괭이의 개수를 읽어와 arr 배열에 모든 곡괭이 종류를 저장합니다. 그리고 func(0)을 호출하여 모든 곡괭이 조합을 탐색하고 최소 피로도를 구합니다.

코드에서 func 함수를 통해 재귀적으로 모든 곡괭이 조합을 탐색하고, 각 조합에 대해 광물을 캐기 위한 피로도를 계산하고 최소 피로도를 구하게 됩니다. 이를 통해 모든 광물을 캐기 위한 최소한의 피로도를 반환합니다.

기분 낼 겸 오랜만에 프로그래머스.. 풀어봤다.

카카오 테크 캠퍼스 붙고나서 온전히 개발에만 몰두했기에....... 거의 5개월만인듯 하다..

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

간단히 그리디하게 풀 수 있는 문제다.

#include <string>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

bool compare(vector<int> &a, vector<int> &b) {
    if(a[0] != b[0]) {
        return a[0] < b[0];
    } else {
        return a[1] < b[1];
    }
}
int solution(vector<vector<int>> targets) {
    int answer = 0;
    sort(targets.begin(), targets.end(), compare);

    for(int i = 0; i < targets.size();) {
        int last = targets[i][1];

        while(i < targets.size() && last > targets[i][0]) {
            last = min(targets[i][1], last);
            i++;
        }
        answer++;
    }
    return answer;
}

먼저, 정렬을 해준다.

 

인덱스 i를 이용해서 targets 벡터를 하나씩 확인한다. 현재 미사일의 끝점을 last에 저장한다.

while로 현재 미사일의 끝점 last가 다음 미사일의 시작점보다 크면 계속해서 다음 미사일을 확인한다.

만약 현재 미사일의 끝점이 다음 미사일의 시작점보다 크다면, 두 미사일은 한 번에 요격 가능하다.

그래서 두 미사일 중 끝점이 더 작은 값을 last로 갱신한다. 이렇게 함으로써 현재 요격 가능한 영역에 포함되는 모든 미사일을 한 번에 요격할 수 있는 것이 보장된다. (그리디하게)

 

i를 증가시켜 다음 미사일로 넘어가고, while이 끝나면 answer를 1 증가시킨다.

 

모든 미사일들을 순회하면서 선택된 요격 미사일들의 개수인 answer를 반환한다.

이 값은 모든 폭격 미사일을 요격하기 위해 필요한 최소한의 요격 미사일 수이다..

 

 

+ Recent posts