<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ekil_like.log</title>
        <link>https://velog.io/</link>
        <description>좋아하는 일을 잘함으로써 먹고살고 싶은 프론트엔드 개발자입니다.</description>
        <lastBuildDate>Sun, 26 Apr 2026 22:47:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ekil_like.log</title>
            <url>https://velog.velcdn.com/images/ekil_like/profile/59e92673-a5df-4a12-8980-cbacd859337c/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ekil_like.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ekil_like" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[코테] 카펫 (완전탐색)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%B9%B4%ED%8E%AB-%EC%99%84%EC%A0%84%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%B9%B4%ED%8E%AB-%EC%99%84%EC%A0%84%ED%83%90%EC%83%89</guid>
            <pubDate>Sun, 26 Apr 2026 22:47:09 GMT</pubDate>
            <description><![CDATA[<h2 id="카펫-완전탐색">카펫 (완전탐색)</h2>
<blockquote>
<p>2026.4.25. (작성일 2026.4.27)</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42842">https://school.programmers.co.kr/learn/courses/30/lessons/42842</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<h4 id="약수-구하기">약수 구하기</h4>
<ul>
<li>약수 구하기는 보통 Lv. 0, Lv. 1에서 그 자체만 나오는 개념 같음. 여기서는 그걸 풀이의 일부로 이용함!</li>
<li>방법은 여러가지가 있겠으나 세가지를 정리해둔다.</li>
</ul>
<ol>
<li><p><code>for</code>문이나 <code>while</code>문을 돌려, 나누었을 때 나머지가 0이면 배열에 추가하는 방법 
(가장 직관적. 그러나 모든 수에 대해 반복하므로 다소 비효율적)</p>
</li>
<li><p>약수는 <code>1 * 자기자신</code>, <code>2 * 자기자신 / 2</code>, ... 와 같이 진행됨(물론 <code>자기자신 / 2</code>가 자연수여야 포함됨). 즉, <code>자기자신</code>을 제외하면 약수의 최댓값은 <code>자기자신 / 2</code>이므로, 그 값까지만 반복문을 돌려 약수를 구하고 최종 결과 배열에 <code>자기자신</code>을 추가로 넣어줘도 됨. =&gt; 반복문 횟수가 방법 1에 비해 절반으로 줄어듦</p>
</li>
<li><p>제곱근 이용 (<code>Math.sqrt()</code> 메서드)
 <code>n</code>의 약수는 <code>루트 n</code>을 기준으로 <strong>대칭적</strong>임. 
 (예) 12의 약수 = 1, 2, 3, 4, 6, 12 = 루트 12(약 3.46)를 기준으로 [1, 12], [2, 6], [3, 4]와 같이 쌍을 이루며 존재함. (쌍으로 곱해서 12가 되는 숫자들이니까 당연)
 =&gt; <code>루트 n</code>까지만 반복문을 돌리되, 나누었을 때 나머지가 0이면 그 숫자와 쌍을 이루는 수까지 한번에 결과 배열에 넣어줌. 실행 후 결과 배열에 중복이 없도록 변경 한번 해주기.</p>
</li>
</ol>
<p><a href="https://velog.io/@woody_/JS-%EC%95%BD%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">이 글</a> 과 구글 검색 (키워드: javascript 약수 구하기) 결과를 참고했다.</p>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(brown, yellow) {
    // 노란 사각형의 가로를 x, 세로를 y라고 하면,
    // 1. yellow = x * y
    // 2. (brown - 4) / 2 = x + y (모서리를 뺀 가로, 세로)
    // 3. 문제 제한사항에 따라, x &gt;= y

    // 곱해서 yellow가 되는 모든 두 자연수에 대해, 위 2, 3을 만족하는 x, y를 구하고,
    // [x + 2, y + 2]를 반환한다.

    // 1. yellow의 약수 구하기
    // n의 약수는 루트 n을 기준으로 대칭적임을 이용,
    // 반복할 횟수를 효율적으로 줄여 약수를 구함
    let x = yellow;
    let y = 1;

    for (let i = 0; i &lt;= Math.sqrt(yellow); i++) {
        // 약수인지 확인
        if (yellow % i === 0) {
            // x &gt;= y를 만족하도록 i 자신과, 대칭되는 쌍인 (yellow / i)를 할당함
            x = yellow / i;
            y = i;

            // 2. (brown - 4) / 2가 x + y인 경우를 찾으면 즉시 종료
            if (x + y === (brown - 4) / 2) {
                break;
            }
        }
    }

    // 카펫의 가로, 세로 길이는 yellow의 가로(x), 세로(y) 길이에 모서리 길이를 더한 값
    return [x + 2, y + 2];
}</code></pre>
<p>예제를 그림으로 그려보며 접근했다. 그려보니 바깥 테두리는 모서리 4개가 있고, 그 안의 개수는 노란색 타일의 가로, 세로 길이와 가로, 세로가 서로 같음이 보였다. <code>노란색의 가로 * 세로</code> = <code>2 * (노란색 가로 + 세로)</code> = <code>갈색 타일 수 - 4</code>, 이것을 만족하는 가로, 세로 숫자의 쌍 1개가 존재함.</p>
<p>이렇게 정리했다.</p>
<pre><code>노란 사각형의 가로를 x, 세로를 y라 할 때, 아래 세 조건을 만족하는 x, y를 구하기

1. (brown - 4) / 2 = x + y
2. yellow = x * y
3. x &gt;= y

x, y를 구했다면 answer = [x + 2, y + 2]</code></pre><p>처음엔 미지수가 2개이고 수식도 2개면 방정식으로 답 구할 수 있는 건가 하면서 풀어보려 했는데 좀 복잡해져서 안 풀리길래 멈추고 다시 내가 쓴 걸 봤다.
=&gt; 곱해서 <code>yellow</code>가 되는 두 자연수에 대하여, 조건 1, 3을 만족하는 것을 구해야겠다.</p>
<p>=&gt; 곱해서 <code>yellow</code>가 되는 자연수는 어떻게 구하나? =&gt; <strong>약수 구하기</strong></p>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(brown, yellow) {
    // 노란 사각형의 가로를 x, 세로를 y라고 하면,
    // 1. yellow = x * y
    // 2. (brown - 4) / 2 = x + y (모서리를 뺀 가로, 세로)
    // 3. 문제 제한사항에 따라, x &gt;= y

    // 곱해서 yellow가 되는 모든 두 자연수에 대해, 위 2, 3을 만족하는 x, y를 구하고,
    // [x + 2, y + 2]를 반환한다.

    // 1. yellow의 약수 구하기
    // n의 약수는 루트 n을 기준으로 대칭적임을 이용,
    // 반복할 횟수를 효율적으로 줄여 약수를 구함
    let x = yellow;
    let y = 1;

    for (let i = 1; i &lt;= Math.sqrt(yellow); i++) { // ✅ 0부터 시작하면 NaN
        // 약수인지 확인
        if (yellow % i === 0) {
            // x &gt;= y를 만족하도록 i 자신과, 대칭되는 쌍인 (yellow / i)를 할당함
            x = yellow / i;
            y = i;

            // 2. (brown - 4) / 2가 x + y인 경우를 찾으면 즉시 종료
            if (x + y === (brown - 4) / 2) {
                break;
            }
        }
    }

    // 카펫의 가로, 세로 길이는 yellow의 가로(x), 세로(y) 길이에 모서리 길이를 더한 값
    return [x + 2, y + 2];
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<ul>
<li>반복문을 0부터 시작하면, <code>yellow % 0 = NaN</code>이라 의미없는 연산. 0부터 시작할 이유가 없음. 1부터 시작으로 변경</li>
<li>1 ~ <code>Math.sqrt(yellow)</code>까지 오름차순(<code>i++</code>) 탐색도 가능하고, <code>Math.floor(Math.sqrt(yellow))</code> ~ 1까지 내림차순(<code>i--</code>) 탐색도 가능함. <em>문제 테스트 케이스에선 두번째 방법이 전반적으로 조금 더 빠르게 실행되는데, 카펫의 모양이 <code>1 * n</code>, <code>2 * n / 2</code> 이런 것보다 서로 비슷한 숫자인 경우가 좀더 많은 게 아닐까 싶다.</em></li>
</ul>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ul>
<li>약수 구하는 법!</li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li>약수 구하기</li>
<li><code>Set</code>의 <code>add</code>. 약수 자체를 구하려 했을 때 중복 제거 과정을 추가하지 않으려고 <code>Set</code>으로 썼는데, <code>add</code>를 해도 최종 결과물에 <code>add</code>한 값들이 들어가지 않았다. 이상해서 다시 배열로 바꿨다.</li>
<li>위와 같은 접근법에서 <code>Set</code>을 배열로 만들려고 <code>Array.from()</code> 같은 배열 생성 방법도 찾아봤다.</li>
<li>쓰면서 이상해서 지금 풀이에 Set 정의랑 add 넣어서 다시 테스트해봤는데 잘 들어간다. 왜 이상했을까..? for문 안에서 매번 새로운 Set을 만들었으려나.. 근데 반복문 밖에서 Set을 로그 찍을 수 없었을텐데.. 실행했을 때 계속 <code>{}</code>가 찍혔던 게 기억난다. </li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ul>
<li>약수는 <code>Math.sqrt()</code>까지 반복문 돌려서 0으로 나누어떨어지는 자연수의 쌍을 구하면 됨!</li>
<li>그리고 Set, add 썼을 때 이상하게 빈 값으로 출력된다면, add 직후에 현재 Set의 상태를 로깅하면서 풀어보자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 파일명 정렬 (정렬)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%ED%8C%8C%EC%9D%BC%EB%AA%85-%EC%A0%95%EB%A0%AC-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%ED%8C%8C%EC%9D%BC%EB%AA%85-%EC%A0%95%EB%A0%AC-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Thu, 23 Apr 2026 23:11:31 GMT</pubDate>
            <description><![CDATA[<h2 id="파일명-정렬-정렬">파일명 정렬 (정렬)</h2>
<blockquote>
<p>2026.4.22, 2026.4.23 (작성일: 2026.4.23, 2026.4.24)</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/17686">https://school.programmers.co.kr/learn/courses/30/lessons/17686</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li>정규표현식<ul>
<li><code>/\D/</code>는 숫자가 아닌 것, <code>/\d/</code>는 숫자인 것 (<code>/[0-9]/</code>와 같음)</li>
<li><code>+</code>를 붙이면 연속된 값을 찾음</li>
<li><code>match(정규식)</code> 리턴 값은 <code>[일치하는 문자열, index: 일치하는 첫번째 문자열 인덱스, input: 검사한 문자열 원문, groups: 그룹핑 결과]</code></li>
<li>위 결과에서 <code>일치하는 문자열</code>을 사용하고 싶으면, <code>[0]</code>으로 접근 가능. <code>match</code>의 리턴 값을 백틱으로 감싼 <code>${}</code> 안에 넣어도 뽑히는데, 길이가 1인 배열이라 우연히 동작하는 것임..!! <em>(실행하다 <code>[0]</code>으로 접근했을 때 오류가 나서 저렇게 작성한 거였는데 다시 해보니 오류가 나지 않는다. 뭔가 다른 문법 오류가 있었나보다.)</em></li>
</ul>
</li>
</ul>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(files) {
    // 1. 파일명을 [HEAD, NUMBER, TAIL]로 분리한다.
    const slicedFiles = files.map(f =&gt; {
        // 정규표현식으로 연속된 숫자 값과 첫번째 인덱스를 구한다.
        const number = f.match(/\d+/);
        const NUMBER = `${number}`; // ✅ number[0]이 더 정확함
        const numberIndex = number.index;
        const tailIndex = numberIndex + NUMBER.length;

        // HEAD: 처음으로 숫자가 나오기 전까지
        const HEAD = f.substring(0, numberIndex);
        const TAIL = f.substring(tailIndex);

        // return [HEAD, NUMBER, TAIL];
        return { head: HEAD, number: NUMBER, tail: TAIL, fileName: f };
    });

    // 2. 기준에 맞게 정렬 시작
    const sorted = slicedFiles.sort((a, b) =&gt; {
        // 2-1. HEAD를 기준으로 사전 순 정렬 (대소문자 구분 없이)    
        if (a.head.toLowerCase() === b.head.toLowerCase()) {
            // 2-2. NUMBER를 숫자 순 정렬하되, 같은 값이면 순서를 유지 (0 리턴)
            return Number(a.number) === Number(b.number) ? 0 : Number(a.number) - Number(b.number);
        } else {
            return a.head.localeCompare(b.head);
        }
    });

    return sorted.map(s =&gt; s.fileName);
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<h4 id="정규표현식을-바꾸지-않는-선에서-개선한-풀이">정규표현식을 바꾸지 않는 선에서 개선한 풀이</h4>
<pre><code class="language-javascript">function solution(files) {
    // 1. 파일명을 { head, number, fileName } 구조로 분해한다.
    const destructured = files.map(f =&gt; {
        // 정규표현식으로 연속된 숫자 값과 첫번째 인덱스를 구한다.
        const number = f.match(/\d+/);

        // 정렬할 때 number는 숫자로 비교하므로 숫자로 변환해준다.
        const NUMBER = parseInt(`${number}`); 

        // head는 처음으로 숫자가 나오기 전까지 자른 문자열.
        // 정렬할 때 대소문자 구별 없으니 모두 소문자로 변환해준다.
        const numberIndex = number.index;
        const HEAD = f.substring(0, numberIndex).toLowerCase();

        return { head: HEAD, number: NUMBER, fileName: f };
    });

    // 2. 기준에 맞게 정렬 시작
    const sorted = destructured.sort((a, b) =&gt; {
        // head 사전 순 정렬
        if (a.head &gt; b.head) return 1;
        if (a.head &lt; b.head) return -1;

        // head가 같으면, number 숫자 순 정렬
        if (a.number &gt; b.number) return 1;
        if (a.number &lt; b.number) return -1;

        // head, number가 같으면 현재 순서 유지
        return 0;
    });

    return sorted.map(s =&gt; s.fileName);
}</code></pre>
<h4 id="정규표현식-고급-문법을-사용하도록-개선한-풀이">정규표현식 고급 문법을 사용하도록 개선한 풀이</h4>
<pre><code class="language-javascript">function solution(files) {
    // 1. 파일명을 { head, number, fileName } 구조로 분해한다.
    const destructured = files.map(f =&gt; {
        // 파일명 조건을 정규표현식으로 표현한다.
        const regex = /^([a-zA-Z-\. ]+)([0-9]+)(.*)$/;
        // const regex = /([a-zA-Z-\. ]+)([0-9]+)/;
        const [fileName, head, number] = f.match(regex);

        // head는 처음으로 숫자가 나오기 전까지 자른 문자열.
        // 정렬할 때 대소문자 구별 없으니 모두 소문자로 변환해준다.
        // 정렬할 때 number는 숫자로 비교하므로 숫자로 변환해준다.
        return { head: head.toLowerCase(), number: parseInt(number), fileName };
    });

    // 2. 기준에 맞게 정렬 시작
    const sorted = destructured.sort((a, b) =&gt; {
        // head 사전 순 정렬
        if (a.head &gt; b.head) return 1;
        if (a.head &lt; b.head) return -1;

        // head가 같으면, number 숫자 순 정렬
        if (a.number &gt; b.number) return 1;
        if (a.number &lt; b.number) return -1;

        // head, number가 같으면 현재 순서 유지
        return 0;
    });

    return sorted.map(s =&gt; s.fileName);
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<h4 id="1번-개선안">1번 개선안</h4>
<ul>
<li>정렬 기준을 보면 TAIL부는 고려하지 않으니, 구하지 않는다.</li>
<li>파일명을 원하는 구조로 가공할 때, 정렬할 때 사용하는 형태로 리턴한다. (HEAD는 소문자로 통일, NUMBER는 숫자로 변환)</li>
<li>정렬할 때 <code>localeCompare</code> 메서드 대신 단순 비교 연산자를 이용한다. (코드가 더 명확하게 보임)</li>
</ul>
<h4 id="2번-개선안">2번 개선안</h4>
<ul>
<li>문제의 &#39;파일명&#39; 규칙을 정규표현식으로 옮기면: <code>const regex = /^([a-zA-Z-\. ]+)([0-9]+)(.*)$/;</code><ul>
<li><code>^</code>: 문자열 시작</li>
<li><code>$</code>: 문자열 종료</li>
<li>위 두개를 넣지 않으면 시작/종료가 아닌 지점에 대해서도 검색할테니 명시하는 게 정확할 듯</li>
<li>영어 대소문자, 숫자 같은 표현은 범위를 <code>-</code> 와 함께 적어주면 되고, 여러 조건은 그냥 나열하면 되네. (<code>a-zA-Z</code> 같이)</li>
<li>문제에서 파일명에 허용되는 다른 문자열로 <code>-</code>, <code>.</code>, <code></code>(공백)을 말했으니 그것도 넣어준다.</li>
<li><code>()</code>는 그룹화 패턴</li>
<li><code>+</code>는 연속된 문자열을 찾는 패턴</li>
<li><code>.</code>은 아무 문자열이나!</li>
<li><code>*</code>은 앞에 온 것이 0번 이상 반복되는 패턴</li>
<li><code>.*</code>은 임의의 문자열이 0번 이상 반복, 즉 아무 문자열이나 상관 없음 (빈 문자열도 OK)</li>
<li>대소문자나 특수문자로 구성된 연속된 문자열 덩어리, 숫자로 구성된 연속된 문자열 덩어리, 아무거로나 구성된 문자열 덩어리로 쪼개는 것이다!</li>
</ul>
</li>
<li><code>&#39;img2.JPG&#39;.match(regex)</code> 결과는 아래와 같다.</li>
</ul>
<pre><code>[
  &#39;img2.JPG&#39;,
  &#39;img&#39;,
  &#39;2&#39;,
  &#39;.JPG&#39;,
  index: 0,
  input: &#39;img2.JPG&#39;,
  groups: undefined
]</code></pre><ul>
<li>아웃풋에서 적절히 꺼내서 쓰면 된다. 구조 분해 할당 가능하다.</li>
</ul>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<pre><code>// 작성했던 풀이 노트
1. 주어진 문자열을 [HEAD, NUMBER, TAIL]로 쪼갠다.
   HEAD와 NUMBER를 구별할 방법 -&gt; 처음으로 숫자가 나오는 지점에서 자르기
2. HEAD를 기준으로 오름차순 정렬한다.
3. HEAD 기준, &#39;순서가 같은 것들&#39; = &#39;모두 소문자로 변환 시 &quot;같은 문자열&quot;인 것들&#39;에 대해서만 NUMBER를 비교한다.
   NUMBER를 정수로 변환하고, 그 값을 오름차순 정렬한다.
   만약 정수로 변환한 값이 서로 같다면 정렬하지 않는다!</code></pre><ul>
<li>정렬보단 &quot;문자열 쪼개기&quot;에서 좀 막혔다. 처음으로 숫자가 나오는 지점을 어떻게 판별하지?</li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<p>문자열을 쪼갤 방법을 많이 찾아봤다.</p>
<ul>
<li><code>split</code>: seperator 인자로 문자열이나 <strong>정규표현식</strong>을 받음, 다만 문자열을 수정함 =&gt; 얕은 복사본에 대해 숫자를 찾는 정규표현식 담아 <code>split</code>을 실행하고, 잘린 것의 맨뒤를 제거하면 HEAD...</li>
<li><code>substring</code>은 문자열을 반환함 (원본 수정 안하는 메서드)</li>
<li>Regex의 <code>exec()</code>은 만족하는 첫요소를 반환함 =&gt; 이걸 이용하고 인덱스를 찾을 수야 있겠지.. 근데 더 좋은 방법이 있을 것 같다.</li>
<li><code>match</code>는 어떤 형태의 값을 리턴하는지 콘솔 로깅 여러번 해보니, 작성한 정규식을 만족하는 값, index, input, groups 키와 값을 배열로 뱉고 있었다. 신기한 발견</li>
<li>정규표현식 문법을 이해하는 데는 <a href="https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EC%A0%95%EA%B7%9C%EC%8B%9D-RegExp-%EB%88%84%EA%B5%AC%EB%82%98-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%89%BD%EA%B2%8C-%EC%A0%95%EB%A6%AC">이 글</a>이 큰 도움이 되었다.</li>
</ul>
<p><code>sort</code>의 인자로 넘기는 콜백에서 0을 리턴하면 <strong>순서를 변경하지 않는다.</strong></p>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ul>
<li>늘 숙제 같던 정규표현식을 그래도 이번에 좀 들여다봤다. 사실 이번 문제는 AI한테 힌트 요청 안하고 검색만 적극 활용해서 풀었다. 검색이랑 프로그래머스에 있는 다른 사람의 풀이 보면서 개선작업! 그래서 비슷하게 정규식을 이용하는 게 가장 효율적일 때 전보다 덜 막막하게 풀어볼 수 있을 듯!!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[잡코리아 DEV CON] 참여 후기]]></title>
            <link>https://velog.io/@ekil_like/%EC%9E%A1%EC%BD%94%EB%A6%AC%EC%95%84-DEV-CON-%EC%B0%B8%EC%97%AC-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@ekil_like/%EC%9E%A1%EC%BD%94%EB%A6%AC%EC%95%84-DEV-CON-%EC%B0%B8%EC%97%AC-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 23 Apr 2026 13:37:19 GMT</pubDate>
            <description><![CDATA[<p>작년 10월 16일 2025 원티드 온라인 AX 컨퍼런스에서 배휘동님 발표를 인상깊게 들었고, 11월 22일 개취뽀 4회 모임에서 딩코딩코님 발표도 잘 들었는데, 이번 잡코리아 DEV CON에도 연사 라인업에 있으셔서 들어보려고 신청을 했다.</p>
<p>오후 7시부터 10시까지 예정이었는데, 첫 한시간에 구성된 세션들이 가장 유익했다. 당장 막막했던 이력서 정리의 방향성을 잡는 데 도움이 많이 되었다. 시장은 문제를 정의하고 집요하게 파고들어 해결하는 &#39;개발자&#39;를 원하는구나. 파고들었던 경험, 실패했던 경험, 트레이드 오프를 고민한 경험 등을 묻는 건 &#39;그런 사람인지&#39; 알고 싶어서 그런거였구나. 개발자로 살아가고 싶다면 그런 사람이 되어야 함을 느꼈다.</p>
<p>이력서나 면접에서는 결국 <strong>&quot;나는 어떤 문제를 해결한 사람인지&quot;</strong> 드러낼 수 있어야 한다.</p>
<p>그러기 위해선 <strong>내가 어떤 사람인지, 문제를 어떤 관점으로 접근하고, 어떤 방향으로 해결방법을 찾는지를 알아야 한다.</strong></p>
<p>저 내용을 듣고 세션을 계속 들으면서, 면접이나 세션이나 비슷하다 느낀 게, 자신이 고민해본 프레임워크나 관점, 경험을 공유하는 사람에게 더 관심이 갔다.</p>
<p>&#39;구현&#39;은 누구나 할 수 있는 세상이 되었으니 그것보다 문제 정의, &#39;왜&#39;에 대한 깊은 고민이 중요해진 것 같다. 그런 경험에서 얻은 자신만의 관점이 쌓인다면 금상첨화고.</p>
<p><strong>결국 돌고돌아 깊.생. 깊게 생각하는 것이 중요하다.</strong></p>
<p>AI가 발전할수록 스스로 생각하고 판단하는 힘을 기르기 어려워지고 있다. 사유하고, 판단하는 것은 에너지가 드는 어려운 일인데, AI한테 묻거나 시키면 금방 그럴듯한 답을 주니까.</p>
<p>오늘 연사님 중에 재밌는 비유를 많이 든 분이 있었다. 이야기를 들어보면, 세상을 이해할 때 무엇과 무엇이 닮아있다는 관점으로 바라봤기에 가능했던 것 같다. 집을 청소하듯, 코드도 인프라도 청소해야 한다고 말했다. 무언갈 만드는 것 말고 그걸 치우고 정리하는 것은 인간의 <strong>의지</strong>가 필요하다고. (물론 청소의 과정에선 AI를 적극 활용!)</p>
<p>또 다른 분은 농부에 비유하셨는데, 농부는 트랙터와 맞서 싸우지 않고 그걸 활용한다. 다만 과거엔 농부가 100명이었다면 지금은 그중 98명은 다른 일을 하고 있다. 지금의 &#39;개발자&#39;들이 미래에 어떤 일을 하게 될진 모르겠지만 AI를 이용해 <strong>문제를 해결하는 힘</strong>을 기르자고 말해주셨다.</p>
<hr>
<h3 id="오늘-배운-것-정리">오늘 배운 것 정리</h3>
<p>개발자로 일하고 싶다면 개발자가 하는 일에 관심과 애정을 가지고 깊게 파고드는 것이 유리하고,
내가 해결하려는 문제에 대해서도 나의 커리어에 대해서도 또 이 판(개발판)이 돌아가는 흐름에 대해서도 깊게 고민하며 관점을 길러가는 것이 나의 무기가 될 수 있다.</p>
<hr>
<p>여담 (이것 또한 인상적이었던 이야기여서 기록해둔다)</p>
<p>지금을 아마존으로 대표되는 유통산업, 유튜브로 대표되는 방송산업을 이어 클로드 등으로 대표되는 지식산업의 시대로 정의해본다면, </p>
<p>아마존과 유튜브가 누구나 생산자가 될 수 있게 열려있던 것처럼 지금 시대도 누구나 지식/개발을 이용해 생산자/공급자가 될 수 있게 열려있다.</p>
<p>유통산업은 물건도 구하고 재고관리, 마케팅, 배송인프라 구축 등 장벽이 좀 높았고
유튜브는 자신만의 매력이나 특색있는 컨텐츠가 필요해 조금의 장벽이 있었다면
개발! 이녀석은 그래도 내가 벌써 3년 이상 하고 있지 않은가?
소비자에만 머무르지 않을 좋은 기회다. 이 엄청난 시기와 타이밍에 감사하면서 시류를 잘 타보자.!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] H-Index (정렬)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-H-Index-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-H-Index-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Tue, 21 Apr 2026 22:08:55 GMT</pubDate>
            <description><![CDATA[<h2 id="h-index-정렬">H-Index (정렬)</h2>
<blockquote>
<p>2026.4.20, 2026.4.21 (작성일: 2026.4.22)</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42747">https://school.programmers.co.kr/learn/courses/30/lessons/42747</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li>문제에서 차용된 H-Index라는 개념을 어떻게 구할지 접근하는 것 자체</li>
<li>내가 작성한 조건의 엣지케이스가 언제 발생하는지 떠올리는 것</li>
</ul>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(citations) {
    const sorted = citations.sort((a, b) =&gt; b - a);
    const answer = sorted.findIndex((e, i) =&gt; e &lt; i + 1);

    // 마지막 e도 i + 1보다 크거나 같으면 index = -1
    // 모든 논문이 전체 논문 개수 이상으로 인용되었으므로, hIndex = 전체 논문 개수
    return answer === - 1 ? citations.length : answer;
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(citations) {
    const sorted = citations.sort((a, b) =&gt; b - a);
    const answer = sorted.filter((e, i) =&gt; e &gt;= i + 1).length;

    return answer;
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<ul>
<li>내 풀이는 조건을 만족하지 않는 첫 번째 순간을 찾으므로, 조건 충족 즉시 멈춘다. 그러나 <code>findIndex</code> 결과가 <code>-1</code>인 경우에 대한 예외 처리가 필요</li>
<li>개선된 풀이는 제출 후 다른 사람의 풀이를 보다가 발견한 건데, 조건을 만족하는 것의 개수를 세는 방식으로 항상 전체를 순회한다. 데이터가 엄청 크다면 비효율적일 수도 있겠지만 예외 처리가 필요 없어서 코드가 간결하다.</li>
<li>그래서 개선된 풀이가 모든 상황에서 &quot;개선된&quot; 풀이라고 볼 순 없지만, 반례를 찾느라 조금 헤맸던 걸 생각하면 코딩테스트에선 &#39;개선&#39;이라 볼 수 있을지도..</li>
</ul>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ul>
<li><p>H-Index를 어떻게 구할 것인가?
  처음엔 주어진 인용 횟수의 평균을 구하고 그것이 H-Index일 수 있는지 체크하고, 아니라면 더 작은 쪽 혹은 더 큰쪽으로 다시 평균을 구해 동일한 비교를 계속하는 방식을 생각했다. 그런데 그런 식으론 구할 수 없었다!
  잘 모르겠어서 문제에 첨부된 <a href="https://en.wikipedia.org/wiki/H-index">H-Index 위키백과 링크</a>에 들어가 찬찬히 읽었다. 아래 설명을 읽고 아하 했다.</p>
<blockquote>
<p>공식적으로, f가 각 출판물에 대한 인용 횟수에 해당하는 함수라고 할 때, h-index를 다음과 같이 계산합니다: 먼저 f의 값을 가장 큰 값부터 가장 작은 값으로 정렬합니다. 그런 다음, f가 위치(h라고 부르는 이 위치)보다 크거나 같은 마지막 위치를 찾습니다. 예를 들어, 5편의 출판물 A, B, C, D, E가 각각 10, 8, 5, 4, 3번의 인용을 받았다면, h-index는 4입니다. 왜냐하면 4번째 출판물이 4번의 인용을 받았고, 5번째는 3번만 인용을 받았기 때문입니다. 반면에, 같은 출판물이 25, 8, 5, 3, 3번의 인용을 받았다면, 인덱스는 3입니다. 즉, 3번째 위치입니다. 왜냐하면 4번째 논문이 겨우 3번만 인용을 받았기 때문입니다.</p>
</blockquote>
<p>  주어진 <code>citations</code> 배열을 내림차순으로 정렬하고, <code>element</code>와 <code>index</code>를 비교하면 되겠구나.
  그 비교는 <code>element &gt;= index + 1</code>을 만족하는 순간까지이니까,
  그것을 만족하지 않는 첫번째 순간의 <code>index</code>를 찾으면 되겠다, 생각했다. <em>(만족하지 않는 첫번째 <code>index</code>의 직전 <code>element</code>의 <code>index + 1</code> = 만족하지 않는 첫번째 <code>element</code>의 <code>index - 1 + 1</code>)</em></p>
</li>
<li><p>다른 말로 정리하면, 배열 속 요소 <code>element</code>가 자신의 <code>index + 1</code>보다 크거나 같은 케이스 중 가장 큰 <code>index + 1</code>이 H-Index인 것이다.</p>
</li>
<li><p>그렇게 했는데 1개 케이스에서 실패했다. 이번에도 반례인가! 하면서 이전 문제에서와 같이 모든 요소가 0인 경우를 고민했으나 이번엔 그게 반례가 아니었다.</p>
</li>
<li><p>이럴 땐 내가 쓴 코드를 자세히 뜯어보는 것도 힌트가 될 수 있음을 깨달았다. 딱 봐도 예외 케이스가 생길 부분이 어디인가? <code>findIndex</code>에서 <code>-1</code>을 반환하는 경우다. 그런 상황은 언제 발생하나? 배열의 모든 요소가 <code>e &lt; i +1</code>을 만족하지 않을 때, 즉 모든 <code>element</code>가 자신의 <code>index + 1</code>보다 크거나 같을 때이다.
  예를 들어 <code>citations = [1, 2, 3, 4]</code>라면, H-Index는 -1이 아니라 4가 되어야 한다. 그리고 그것은 전체 논문의 개수 = <code>citations.length</code>와 같다.</p>
</li>
<li><p>발상을 조금 전환하면, 배열을 내림차순 정렬한 뒤 <code>element</code>가 자신의 <code>index + 1</code>보다 크거나 같은 요소만 남기고 그것의 <code>length</code>를 구하면 그게 H-Index와 같다.</p>
</li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li><code>findIndex</code>가 받는 콜백함수에서 <code>index</code>도 인자에 있는지 체크했다.</li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ul>
<li>H-Index 위키백과를 읽으면서 접근법을 알아낸 걸 볼 때, 가능하면 다양한 예시를 접해야 접근법이 명확해지는데, 문제 이해도가 낮을 땐 정확한 예시를 떠올리기 어렵다. 이건 여러 문제를 풀어보며 능력치를 길러보자.</li>
<li>엣지케이스, 반례를 찾을 땐 문제의 제약 조건 + 내가 작성한 코드를 같이 뜯어보자. 특히 의심되는 지점을 좀더 파보자.</li>
<li>찾기 어렵다면 엣지케이스를 고려하지 않아도 되는 접근법이 있을지 생각을 전환해보는 것도 방법이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 가장 큰 수 (정렬)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EA%B0%80%EC%9E%A5-%ED%81%B0-%EC%88%98-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EA%B0%80%EC%9E%A5-%ED%81%B0-%EC%88%98-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Sat, 18 Apr 2026 08:43:05 GMT</pubDate>
            <description><![CDATA[<h2 id="가장-큰-수-정렬">가장 큰 수 (정렬)</h2>
<blockquote>
<p>2026.4.17. &amp; 2026.4.18.</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42746">https://school.programmers.co.kr/learn/courses/30/lessons/42746</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li>기본 <code>sort()</code>로는 엣지케이스가 존재하는 상황에서 직접 정렬 로직 구현하기
  [3, 30, 34]가 주어질 때, 일반적인 내림차순 정렬하면 [34, 30, 3]이 되어 &#39;34303&#39;을 리턴하게 되는데, &#39;34330&#39;이 더 큰 숫자임!</li>
<li>위와 같은 비교를 위해 <code>sort()</code>에 커스텀 콜백을 넘기는데, 문자열로 만들어 비교하는 방법을 선택했음</li>
<li>주의할 점: 내림차순 정렬할 때 <code>sort((a, b) =&gt; b - a)</code> 이렇게 많이 쓰다보니, 콜백에서 비교하고 <code>a : b</code> 를 리턴하게 했는데, 그렇게 하면 정확한 정렬이 안됨! 왜? <strong><code>sort</code>는 콜백 반환값이 음수/양수/0 중 무엇인지만 판단</strong>하기 때문임. <strong>3과 30을 비교해서 30을 리턴하든 3을 리턴하든 똑같이 양수이므로 <code>(a, b)</code> 중 <code>b</code>가 앞에 배치된다.</strong> 문제에서 주어지는 배열은 0 또는 양의 정수로 구성되므로 결국 어떤 경우든 항상 0 또는 양수를 리턴하니 정렬이 제대로 안되는 것이다~~!! =&gt; 명확하게 음수/양수/0 케이스로 구별하여 리턴해야 한다.</li>
<li>엣지케이스 (테스트 케이스 11번) 떠올리기: 문제 조건에 따르면 모든 요소가 0일 수도 있음. 그럴 땐 &#39;000&#39; 같은 형태가 아닌 &#39;0&#39;을 리턴하도록 처리가 필요함.</li>
</ul>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(numbers) {
    if (numbers.every(n =&gt; n === 0)) return &#39;0&#39;;

    const answer = [...numbers].sort((a, b) =&gt; `${a}${b}` &lt; `${b}${a}` ? 1 : -1);

    return answer.join(&#39;&#39;);
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(numbers) {
    const answer = [...numbers].sort((a, b) =&gt; `${a}${b}` &lt; `${b}${a}` ? 1 : -1).join(&#39;&#39;);

    return answer[0] === &#39;0&#39; ? &#39;0&#39; : answer;
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<p><strong>배열의 모든 요소가 0인 경우를 체크하는 방법의 차이</strong></p>
<ul>
<li>얼리 리턴이 가독성도 좋고 더 효율적이라 생각해서 코드 제일 앞에서 그 경우를 체크하게 했다. 그런데 다른 사람의 풀이를 보니까 연산 수행 후 제일 앞 요소가 &#39;0&#39;인지를 체크하고 있었다.</li>
<li>정렬 후 제일 앞 요소가 &#39;0&#39;이라면 당연히 그 뒤의 모든 값은 0이겠지.</li>
<li>근데 모든 요소가 0일 때는 <code>sort</code> 연산을 하지 않는 게 더 효율적이지 않을까? 하며 얼리 리턴을 고집해봤다. 대신 모든 요소를 순회하지 않게, <code>some()</code>으로 변경하고, 0이 아닌 요소가 하나라도 발견되면 기존대로 answer을 구하는 연산을 수행하게 하고, 아니면 0을 리턴하게도 작성해봤다.</li>
<li>3가지 케이스를 모두 실행하고 실행 속도를 비교했다.</li>
</ul>
<p><strong>비교 결과</strong>
A. [얼리 리턴] <code>every</code>를 사용한 경우 - 전반적인 연산 속도가 가장 오래 걸림. 
B. <code>some</code>을 사용해 조건을 체크한 경우 - 모든 요소가 0인 케이스 연산만 C보다 0.02ms 빠르고, 그 외의 속도는 C가 근소하게 빠름.
C. 조건 없이 코드를 실행하고, 마지막에 제일 첫 요소가 &#39;0&#39;인지 체크한 경우 - 전반적으로 가장 빠름!</p>
<p>왜 이런 결과가 나왔는지 생각해봤다.
얼리 리턴, 조건 체크를 추가하면, 어떤 인풋이 들어오든 if문을 한번 실행한다. 그것을 만족하는 케이스가 많다면 더 효율적일 수도 있다. 그러나 이번 문제에서는 11번 테스트 케이스를 제외하고는 그런 경우가 없다.
이 접근법은 (<code>every</code>를 쓰든 <code>some</code>을 쓰든), 한가지 경우를 위해 <strong>모든 다른 경우에 불필요한 if문과 비교 연산이 실행</strong>되는 것이다. =&gt; 오히려 이것이 비효율!</p>
<p>정렬 연산 수행 후 그 문자열의 제일 앞 요소가 &#39;0&#39;과 같은지 비교하는 것이 오히려 가벼운 것인가보다.</p>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ul>
<li>어떻게 접근할 것인가? 일반적인 정렬은 안된다. 문제에서 설명한 접근법대로 문자열을 구성하고 그것을 비교하는 함수를 <code>sort</code>의 콜백으로 넘기자. 까지는 그래도 금방 생각해냈는데, 거기서 콜백이 리턴하는 값을 1, -1이 아닌 그 숫자 자체로 작성하는 바람에 오답을..</li>
<li>그래서 그 접근법이 아닌가 하고 다른 식으로도 고민했다.</li>
<li>극단적으로 <code>&quot;b&quot; - &quot;a&quot;</code> 이런 것도 적어봤음. 참고로 저걸 실행하면 <code>NaN</code>임..</li>
<li><code>sort</code> 콜백 구성할 때, 서로 같은 값이면 0을 리턴하게 3가지로 나누어 작성했던 기억이 나서 그것도 추가해봄. 그러다가 아, 비교 후 1, -1을 리턴해서 정렬이 이루어지도록 해야하지? 떠올랐다.</li>
<li>수정 후 11번 테케만 계속 실패해서 힌트 요청함. 나는 맨 처음 값이 0인 경우에서 실패하는 걸까? 라고만 생각했는데, 비교 연산을 하는데 0이 맨 앞에 놓일 순 없지!라고 생각하고 넘겼었음.</li>
<li>힌트: <code>numbers = [0, 0, 0] 일 때 이 코드의 결과는 무엇인가?</code></li>
<li>아하리 모든 게 0일 때는 &#39;0&#39;이 나와야 하는구나 -&gt; 얼리 리턴 처리 </li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li>문자열 비교는 <code>localeCompare</code> (그냥 <code>compare</code>는 존재하지 않음)</li>
<li>배열의 length = 1 일때 sort는 어떻게 동작하는가? =&gt; 정렬을 수행하지 않고 리스트를 그대로 반환한다고 함</li>
<li>string from array: 주어진 배열을 정렬했으니 이제 그대로 문자열로 구성하면 되는데, 그런 메서드가 분명 존재하겠지? 라는 생각으로 찾음. <code>join(&#39;&#39;)</code>하면 배열의 모든 요소를 빈 문자열로 연결해 하나의 문자열이 구성됨</li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ol>
<li><code>sort</code>의 콜백을 구성할 때는 -1, 0, 1을 일관되게 리턴하게 하자. <code>sort</code>는 콜백 반환값이 <strong>음수</strong>인지 <strong>양수</strong>인지 <strong>0</strong>인지<strong>만</strong> 판단하기 때문!!</li>
<li>발생 확률이 극히 낮은 엣지케이스를 대비할 때는 얼리 리턴 코드가 오히려 비효율적일 수 있다. 모든 정상 케이스에서도 그 조건문을 한번 체크해야 하니까.</li>
<li>문자열의 제일 앞 요소에 대한 단순 비교 연산은 가볍다.</li>
<li>엣지케이스가 떠오르지 않을 땐 문제의 제약 조건들을 다시 살펴보고, <code>모든 요소가 0</code> 같은 경우도 의심해보자.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[5월 목표 정리]]></title>
            <link>https://velog.io/@ekil_like/5%EC%9B%94-%EB%AA%A9%ED%91%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@ekil_like/5%EC%9B%94-%EB%AA%A9%ED%91%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 16 Apr 2026 04:48:21 GMT</pubDate>
            <description><![CDATA[<p>5월 목표:
&quot;매일 1시간 &lt;자바스크립트 딥다이브&gt;의 실행 컨텍스트·클로저·비동기·this 챕터를 읽고 (10<del>20분), 각 개념마다 실행 순서 또는 동작을 콘솔에서 직접 예측·검증해 익힌 뒤 (20</del>30분) 이해한 내용을 블로그에 정리한다 (20분) &quot;
(적어둔 시간은 가이드일뿐 완벽히 지킬 필요는 없다. 1시간 안에 적절히 배분할 것)</p>
<p>구체적인 순서는</p>
<ol>
<li><p>실행 컨텍스트 &amp; 스코프 &amp; 클로저 - 가장 먼저, 깊고 정확하게 이해해야 하는 개념. 리액트의 useEffect, useCallback, useMemo 동작 원리의 근거가 됨</p>
</li>
<li><p>이벤트 루프 &amp; 비동기 처리 (Promise, async/await) - 핵심적으로 이해해야 하는 것은 &quot;태스크 큐와 마이크로태스크 큐의 차이&quot;, &quot;실행 순서 예측&quot;. useEffect 안에서 비동기를 다룰 때나 타이밍 버그 발생 시 원인 추적에 필요한 개념</p>
</li>
<li><p>this 바인딩 - 이벤트 핸들러, 콜백, 화살표 함수에서 this가 다르게 동작하는지 설명할 수 있도록 개념 확실히 잡는 것이 목표</p>
</li>
</ol>
<p>각 단계별 예측, 검증의 구체적인 방법: </p>
<ul>
<li><p>책의 예제 코드를 실행 -&gt; 변수 하나를 바꾸거나 순서를 뒤집어서 예상과 다른 결과를 의도적으로 만들어보는 등의 변형 시도 -&gt; 필요 시 추가 자료 구글링 (너무 오래 끌지 않도록 주의)</p>
</li>
<li><p>실제 며칠 해보면서 적절한 프로세스 찾기</p>
</li>
</ul>
<p>주의할 점</p>
<ol>
<li><p>블로그 글은 &#39;내가 이해한 걸 내 말로 덤프하는 것&#39;이라는 기준으로 쓸 것. 너무 잘쓰려는 욕심에 블로그 정리에 과도한 시간을 투자하면 글쓰기 부담으로 루틴이 깨질 수 있음을 명심</p>
</li>
<li><p>1, 2, 3단계 넘어가는 기준: &quot;코드를 보고 동작을 예측하고, 틀렸을 때 왜 틀렸는지 말로 설명할 수 있으면 다음 단계로 넘어간다.&quot; - 어차피 중요한 개념은 자주 마주치기 때문에 처음부터 완벽히 이해하고 넘어가지 않아도 된다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 이후 계획 정리]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%9D%B4%ED%9B%84-%EA%B3%84%ED%9A%8D-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%9D%B4%ED%9B%84-%EA%B3%84%ED%9A%8D-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 15 Apr 2026 22:44:32 GMT</pubDate>
            <description><![CDATA[<h3 id="현재-상태-진단">현재 상태 진단</h3>
<p>잘하고 있는 것들:</p>
<ul>
<li>힌트를 받으면 빠르게 연결해서 풀어냄</li>
<li>코드 품질이 회차마다 눈에 띄게 좋아지고 있음</li>
<li>콘솔 로깅으로 스스로 버그 잡는 습관 형성됨</li>
<li>다른 사람 풀이 보고 더 나은 방식을 흡수하는 능력이 있음</li>
</ul>
<p>아직 걸리는 것들:</p>
<ul>
<li>새로운 패턴(단조 스택 등)을 처음 만났을 때 방향을 잡는 데 시간이 걸림</li>
<li>주식가격 문제가 3일 걸렸고, 프로세스 문제도 이틀 걸림</li>
<li>문제를 보고 “어떤 자료구조/패턴을 쓸지” 스스로 결정하는 부분이 아직 약함</li>
</ul>
<h3 id="다음-문제-풀이-유형-선택">다음 문제 풀이 유형 선택</h3>
<p>원래 계획은 BFS/DFS였는데 잘할 수 있을지 고민
개념 진입장벽이 높은 편이라 다른 문제들 풀며 감 좀 더 잡고 넘어가자.</p>
<ol>
<li><p>정렬 유형 (Level 2, 정렬 - 3문제 있음)
새로운 개념보다 &quot;어떻게 정렬 기준을 설계하는가&quot;를 연습</p>
</li>
<li><p>완전탐색/그리디 유형 (Level 2, 완전탐색 - 5문제 있음)
BFS/DFS의 &#39;모든 경우를 탐색한다&#39;는 감각을 재귀 없이 먼저 익힐 수 있음. 워밍업으로 적절.
재귀, 백트래킹 등 개념이 포함될 수 있는데, 막혀도 30분은 혼자 고민해보기.
핵심은 <strong>&quot;모든 경우를 어떻게 나열할 것인가&quot;</strong>를 스스로 설계하는 연습!</p>
</li>
<li><p>BFS/DFS
위 두가지를 하고 진입</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 다리를 지나는 트럭 (스택/큐)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EB%8B%A4%EB%A6%AC%EB%A5%BC-%EC%A7%80%EB%82%98%EB%8A%94-%ED%8A%B8%EB%9F%AD-%EC%8A%A4%ED%83%9D%ED%81%90</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EB%8B%A4%EB%A6%AC%EB%A5%BC-%EC%A7%80%EB%82%98%EB%8A%94-%ED%8A%B8%EB%9F%AD-%EC%8A%A4%ED%83%9D%ED%81%90</guid>
            <pubDate>Wed, 15 Apr 2026 22:39:02 GMT</pubDate>
            <description><![CDATA[<h2 id="다리를-지나는-트럭-스택큐">다리를 지나는 트럭 (스택/큐)</h2>
<blockquote>
<p>2026.4.14, 2026.4.15 (작성일: 2026.4.16)</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42583">https://school.programmers.co.kr/learn/courses/30/lessons/42583</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li>&#39;일차선 다리&#39; = &quot;큐&quot;: 선입선출의 구조</li>
<li>다리에 올라가있는 트럭의 무게의 합 = <code>reduce</code> 돌리지 않아도, 트럭이 올라가고 내려가는 처리를 해줄 때 무게 변수에 더하고 빼주면 관리 가능</li>
<li>다리에서 내려가는 것부터 처리, 그후 올라올 수 있는지 체크</li>
<li>트럭이 다리에 올라갈 수 있는 조건 = 현재 무게 <strong>+ 올리려는 트럭의 무게</strong> &lt;= 수용 가능한 무게</li>
<li>트럭을 다리에서 내려야 하는 순간을 계산할 때, &#39;트럭이 올라온 시간(초)&#39;를 알고 있어도 계산 가능하지만, &#39;트럭이 내려가야 하는 시간(초)&#39;를 저장하면 코드가 더 깔끔해짐
  트레이싱 해보면 트럭이 내려가야 하는 순간 = <code>queue</code>에 속한 시간이 <code>bridge_length</code>만큼일 때이므로, <code>현재 시간 = 다리에 올라온 시간 + bridge_length</code> 가 <code>true</code>이면 내려가야 함</li>
<li>위 접근법과 이어지는데, <code>seconds</code> 증가 처리 위치는 저장하는 시간의 개념에 따라 달라짐<ol>
<li>&quot;진입 시점&quot;을 저장할 경우 -&gt; 나가야 하는지 조건을 체크하기 위해 이 &#39;진입 시점&#39; 값을 이용해 <code>duration</code>, 거리를 <strong>계산</strong>해야 함. 트럭이 0초에 올라온건지 1초에 올라온건지 생각해보면 됨..<ol start="2">
<li>&quot;나갈 시점&quot;을 저장할 경우 -&gt; 나가야 하는지 조건을 체크할 때 반복문 안의 <code>seconds</code>와 저장했던 &#39;나갈 시점&#39;(<code>seconds + bridge_length</code>)이 &quot;같은지&quot; 단순 비교만 하면 됨 (<strong>지금 시간이 내가 나가야 할 시간과 같은가</strong>)</li>
</ol>
</li>
</ol>
</li>
<li>시간 효율성: 테스트 케이스에 다리가 수용 가능한 무게가 100kg인데 트럭은 10kg 1개만 존재하는 경우가 있음. <code>shift</code>, <code>push</code>만 처리하는 반복문 구현 시 이 케이스는 트럭 1개가 다리에 올라가있고 다른 변화는 없는 상태로 루프를 100번 돌고 <code>seconds</code>를 0부터 101까지 증가시키는 작업만 수행함.
  이런 경우 현재 올라가있는 트럭이 내려가는 시간으로 <code>seconds</code>를 증가시켜 그 다음 순간의 루프를 실행할 수 있도록 개선해 효율성 확보 가능</li>
</ul>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(bridge_length, weight, truck_weights) {
    let seconds = 0;
    let currentWeight = 0;
    const queue = []; // [무게, 진입한 시점의 초][]

    // 두 배열이 모두 비어있을 때까지 진행
    while (queue.length &gt; 0 || truck_weights.length &gt; 0) {
        // seconds 증가 // ⚠️ &quot;진입한 시점의 초&quot;를 저장해야 해서 시간을 먼저 증가시켜줘야 아래 로직이 올바르게 동작함
        seconds += 1;

        // shift 조건 체크
        if (queue.length &gt; 0) {
            const duration = seconds - queue[0][1];
            if (duration === bridge_length) {
                const item = queue.shift();
                currentWeight -= item[0];
            }
        }

        // push 조건 체크
        if (currentWeight + truck_weights[0] &lt;= weight) {
            queue.push([truck_weights[0], seconds]);
            const item = truck_weights.shift();
            currentWeight += item;
        }
    }

    return seconds;
}</code></pre>
<ol>
<li>이전에 다른 문제 풀이 시도 때처럼 인덱스를 따로 저장하는 식으로 접근하려 하지 않고, <code>queue</code> 배열에 트럭의 무게와 시간을 함께 저장하려 한 것!! 장족의 발전이다. <em>처음엔 객체 형태로 저장하려 했는데 키 없이 값만 넣어서 <code>{무게, 시간}</code>으로 쓰고 문법 오류를 만났다. 고민하다가 배열을 선택했다..</em></li>
<li><code>seconds</code> 같은 변수는 루프 안 모든 작업 처리 후 마지막에 증가시키는 것이 관행이지 않은가? 나도 그렇게 작성했었는데, 트럭이 머무는 시간이 부정확하게 계산됐다.
=&gt; 첫번째 트럭은 0초가 아닌 1초에 올라왔는데 0초에 올라온 것으로 저장되어서 계산이 이상해진 것
&quot;진입한 시점의 초&quot;를 저장하기로 했기 때문에, 루프 안의 계산을 실행하기 전에 <code>seconds</code>를 먼저 증가시켜줘야 현재 시간을 의도대로 저장할 수 있었다.</li>
<li>그 외에는, <code>currentWeight</code> (다리 위 트럭 무게의 합)를 변수로 관리하는 것, queue에서 트럭이 내려가야 할때(먼저)와 올라갈 수 있을 때(내려간 뒤)를 순서대로 체크하고 처리해주는 것. 사실 무게의 합이 필요하다 = <code>reduce</code> 써야지, 다른 방법이 있나? .. 딱히 없군! 생각하고 push 조건 체크하는 if문에서 reduce를 썼는데, 매 순회마다 queue의 모든 요소를 순회하는 메서드를 쓰는 격이라 시간 초과로 실패했었다. 어떻게 접근할지 고민하던 중 창밖 도로를 지나가는 차들이 접근 방식 변경에 도움을 주었다. (올라오고 내려가는 것을 처리할 때 무게 변수도 같이 관리해주면 되는구나!)</li>
</ol>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(bridge_length, weight, truck_weights) {
    // &#39;다리&#39;를 모방한 큐. [트럭 무게, 다리에서 나가야 하는 시간][] // ✅ 저장할 개념을 변경함
    const queue = [];
    let seconds = 0;
    let weightOnBridge = 0; // ✅ 더 직관적인 네이밍이라서 차용

    // 다리를 건너는 트럭, 대기 트럭이 모두 0일 때까지 다음 루프 반복
    while (queue.length &gt; 0 || truck_weights.length &gt; 0) {
        // 1. 현재 시간이 다리 위 첫번째 트럭의 나갈 시간과 같다면,
        //    첫번째 트럭을 나가게 하고, 다리 위 무게에서 뺌  // ✅ 코테에서도 가독성 좋은 주석을 작성할 수 있구나 느껴서 차용
        if (queue[0] &amp;&amp; seconds === queue[0][1]) { // ✅ queue를 [[0, 0]]으로 초기화해주면 queue[0]이 존재하는지 체크할 필요가 없지만, 저 초기화가 어떤 의미인지 와닿지 않아서 조건문 안에서 체크하도록 작성
            weightOnBridge -= queue.shift()[0]; // ✅ 배열 요소의 첫번째 값이 &#39;무게&#39;임
        }

        // 2. 대기 트럭이 올라와도 다리가 견딜 수 있는 무게라면,
        //    대기 트럭을 올리고, 다리 위 무게에 더함
        if (weightOnBridge + truck_weights[0] &lt;= weight) {
            queue.push([truck_weights[0], seconds + bridge_length]); // ✅ 트럭이 내려갈 시점을 저장하는 방식
            weightOnBridge += truck_weights.shift();
        } else { // ✅ 이 부분이 실행 시간 단축의 키 - 없어도 통과하지만, 실무였으면 퍼포먼스 향상에 큰 도움이 됐을 코드..
            // 3. 대기 트럭이 못 올라온다 = 다리 위 트럭이 나가줘야 함
            //    다리 위 첫번째 트럭이 나갈 수 있는 시간으로 점프하여 효율성 확보
            if (queue[0]) { // ✅ 마찬가지로 queue가 비어있지 않아야 아래 코드가 안전히 처리될 수 있음
                seconds = queue[0][1] - 1; // 루프 밖에서 1초 증가시키므로 -1 처리 // ✅
            }
        }

        // 4. 모든 작업 완료 후 seconds 증가
        seconds ++; // ✅ 트럭이 내려갈 시점을 저장하므로 시간을 마지막에 증가시키는 것이 맞음
    }

    return seconds;
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<ol>
<li>저장할 개념 변경: 올라온 시점 -&gt; 내려갈 시점</li>
<li>네이밍과 주석 - &#39;다른 사람의 풀이&#39; 보고 사실 좀 많이 놀랐음. (아래에 첨부) 코테는 혼자 보는 거라 생각하고 문제 푸는 것에만 집중하면 된다고 생각했는데.. 단적으로 나는 주석을 <code>두 배열이 모두 비어있을 때까지 진행</code>이라 썼음. 그건 코드 보면 알 수 있는 내용인데 굳이 적은 이유는 풀면서 내 사고를 명확히 하려는 의도. 그런데 그 풀이에서는 동일한 부분에 <code>대기 트럭, 다리를 건너는 트럭이 모두 0일 때 까지 다음 루프 반복</code>이라 적었음. 문제에서 주어진 상황과 코드 설명을 적절히 작성한 것 같아 놀랐다. 다만 나는 아직은 주석 작성에 더 신경을 쓰기보단 문제 풀이, 접근법에 더 집중할 때가 맞다. 그래도 가능하면 이런 식으로 적어볼까 싶다! (협업할 때도 이런 주석이 코드 파악에 도움이 되니까 언제 코드를 쓰든 그런 습관을 들이도록 연습하려는 의도다.)</li>
<li>좀더 간략해진 것은 당연한 것이고 - 나는 처음 풀때는 의도적으로 변수들을 더 선언하는 편이다. 그 변수 이름에 의도를 반영해두는 편.. 그리고 통과하면 불필요한 변수 정의를 줄여나가는 식으로 하는데, 뭐가 더 나을지는 아직 고민이다.</li>
<li>시간 단축을 위한 점프 부분. 생각도 못한 지점이다. 문제 풀이 + 통과만을 목적으로 했는데, 이런 식으로 실행 시간을 줄일 방법을 생각해낼 수도 있구나 신기했다. 그 의도까지 적혀있는데 뭔가 완벽한 코딩테스트 응시자의 전형을 본 것 같아 굉장히 인상깊었다. (이 코드 바탕으로 이야기 나눌 때 저렇게 말할 것 같고, 문제 출제자가 너무 만족할 답변일 것 같단 생각..)</li>
</ol>
<p><img src="https://velog.velcdn.com/images/ekil_like/post/e6c9744d-b7b5-4b79-b152-cb86234c2879/image.png" alt="다른 사람의 풀이 이미지"></p>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<p>위에 다 적어놔서 반복이니 간략히만 정리한다.</p>
<ul>
<li>다리 위 무게의 합을 구할 방법</li>
<li>push 조건을 정확히 체크할 방법</li>
<li>queue에 무게와 함께 저장할 시간을 어떤 걸로 할 것인가 &amp; 나갈 시점 체크를 어떻게 할 것인가 =&gt; seconds를 언제 증가시켜야 하는가로 이어지는 결정</li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<p>검색은 딱히 안했음</p>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ul>
<li>트럭 무게와 진입 시간을 묶어서 저장하기로 생각해낸 것처럼, 쌍으로 존재하면 더 도움이 되는 경우인지 체크해보자</li>
<li>seconds 증가 코드의 위치를 변경했을 때처럼 나의 직관과 맞지 않는 테스트 결과가 나올 땐, 접근 방식, 내가 저장하고 사용하는 변수의 개념을 다른 걸로 변경해볼 순 없을지 고민해보자</li>
<li>반복문 안에서 추가로 조건 체크를 할 때는 배열의 모든 요소를 순회하는 메서드 대신 더 단순한 방법은 없을지 고민하자</li>
<li>주어진 문제 풀이 플러스 알파로 문제의 조건 자체에서 실행 시간을 단축시킬 방법이 있을지 고민해보는 것도 좋겠다</li>
<li>주석과 네이밍도 좀더 신경써보자</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 주식가격 (스택/큐)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%A3%BC%EC%8B%9D%EA%B0%80%EA%B2%A9-%EC%8A%A4%ED%83%9D%ED%81%90</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%A3%BC%EC%8B%9D%EA%B0%80%EA%B2%A9-%EC%8A%A4%ED%83%9D%ED%81%90</guid>
            <pubDate>Sun, 12 Apr 2026 22:52:03 GMT</pubDate>
            <description><![CDATA[<h2 id="주식가격-스택큐">주식가격 (스택/큐)</h2>
<blockquote>
<p>2026.4.5., 2026.4.12., 2026.4.13.</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42584">https://school.programmers.co.kr/learn/courses/30/lessons/42584</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<p><strong>단조 스택 (Monotonic Stack)</strong></p>
<blockquote>
<p>아직 답을 못 구한(=조건을 미충족한) 것들을 <strong>스택</strong>에 쌓아두다가, 조건이 충족되는 순간 꺼내면서 답을 계산한다</p>
</blockquote>
<p><em>단조 - 스택 안의 값이 항상 단조롭게 증가하거나 감소하는 상태를 유지한다는 의미에서 붙음</em>
=&gt; 항상 오름차순/내림차순으로 추가되는데, 그 순서가 깨지는 순간이 되면, 그 깨뜨리는 요소가 들어오기 전까지 스택에 쌓인 것들을 꺼내며 처리함</p>
<p><strong>활용하면 좋을 문제 패턴</strong></p>
<blockquote>
<p>각 요소에 대해 왼쪽/오른쪽에서 <strong>처음으로</strong> 나(현재 값)보다 크거나 작은 것을 구하라.</p>
</blockquote>
<pre><code>- 오늘 이후 주가가 처음으로 떨어지는 날
- 각 건물에서 오른쪽으로 처음 보이는 더 큰 건물
- 온도가 처음으로 오르는 날까지의 기간</code></pre><ul>
<li>각 요소를 스택에 한번 push, 한번 pop</li>
</ul>
<blockquote>
<p><strong>이미 답이 나온 요소는 다시 볼 필요가 없다</strong> 는 특성을 이용한 것
(이미 가격이 떨어진 순간이 나타났다? 그 요소는 이제 더이상 고려하지 않아도 된다는 뜻)</p>
</blockquote>
<p><strong>이번 문제 이해해보기</strong></p>
<p>&quot;가격이 떨어지지 않은 기간은 몇 초?&quot;
= &quot;<strong>가격이 처음으로 떨어진 초까지의 거리</strong>를 구하라!&quot;</p>
<p>stack을 이용해 (아직) 가격이 떨어지지 않은 것의 인덱스만 저장하고,
새로운 가격과 비교함 (stack에 늦게 들어온 요소부터)
처음으로 자신보다 낮은 가격이 나오는 시점은 가격의 index별로 몇 초 후인지 별도로 저장</p>
<h3 id="내-풀이">내 풀이</h3>
<p>너무 삽질을 많이 해서 뭘 적어야 할지.. 처음엔 스택 활용할 생각도 못하고 큐 적용(?)해서 테스트케이스만 겨우 통과 &gt; 힌트 받고 스택 사용해서 테스트 케이스만 겨우 통과(^^..) &gt; 전혀 이해하지 못함</p>
<pre><code class="language-javascript">function solution(prices) {
    const answer = [];

    while (prices.length &gt; 0) {
        const price = prices.shift();
        const lessIdx = prices.findIndex(p =&gt; p &lt; price);

        if (lessIdx === -1) {
            answer.push(prices.length);
        } else {
            answer.push(lessIdx + 1);
        }
    }

    return answer;
}</code></pre>
<pre><code class="language-javascript">function solution(prices) {
    const answer = new Array(prices.length).fill(0);
    const stack = [0];
    let duration = 0;

    for (let i = 1; i &lt; prices.length; i++) {
        let j = stack.length - 1;
        while (prices[stack[j]] &gt; prices[i]) {
            duration = i - stack[j];
            const index = stack.pop();
            answer[index] = duration;
        }

        stack.push(i);
    }

    for (let index of stack) {
        const d = prices.length - 1 - index;
        answer[index] = d;
    }

    return answer;
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(prices) {
    const answer = new Array(prices.length).fill(0);
    const stack = [];

    for (let i = 0; i &lt; prices.length; i++) {
        while (stack.length &gt; 0 &amp;&amp; prices[stack[stack.length - 1]] &gt; prices[i]) {
            const index = stack.pop();
            answer[index] = i - index; // 거리 저장
        }
        stack.push(i);
    }

    while (stack.length &gt; 0) {
        // 끝까지 가격 떨어지지 않은 요소 거리 계산
        const index = stack.pop();
        answer[index] = prices.length - 1 - index;
    }

    return answer;
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<ul>
<li>스택 개념을 사용해 어떻게 활용하는가 그 관점</li>
</ul>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ul>
<li>&#39;가격이 떨어지지 않은 기간&#39;의 정의가 도대체 무엇이란 말인가? 이 말의 의미를 이해하는 데 한참 걸림 (예를 들어 테스트 케이스인 [1, 2, 3, 2, 3]에서 마지막에 1을 추가한 [1, 2, 3, 2, 3, 1]이라면 <code>prices[2]</code> -&gt; <code>prices[3]</code>으로 떨어지고 맨 뒤에 1이 나오니까 또 떨어졌다고 봐야 하는가? 하는 질문을 할 정도로.. 배열 자체만 보면 떨어진 게 맞으니까)</li>
<li>그리고 또 하나의 해프닝 발생 - 처음 <code>answer</code> Array를 초기화할 때 값이 없다는 의미로 <code>-1</code>로 채워서 테스트해봤는데 효율성 테스트 1개에서 시간 초과로 실패하는 것임. 0으로 초기화하나 -1로 초기화하나 사실 그 값을 활용하는 부분은 없어서 이게 왜 문제가 되는지 의문이었음.
마지막 <code>return</code>문에서 <code>answer</code>에 <code>map</code>을 돌려서 값이 -1일 경우 0을 리턴하고 아니면 값 자체를 리턴하도록 했더니 해당 케이스(효율성 4번)에서 6ms로 빠르게 통과, 다른 케이스(효율성 2번)에서 또 타임아웃 실패함. 이 부분은 빡빡한 케이스에 <code>map</code> 한번 더 돌려서 실패한 것으로 보아야 할지..
어쨌든 -1로 초기화하면 실패, 0으로 초기화하면 통과하는 이상한 지점이 있다... (캐시 비우고 다시 제출해봐도 동일한 결과)
=&gt; 원인은 정확히 파악 불가 (효율성 케이스는 로그도 찍지 못하니까)
다만 duration은 최솟값이 0이므로 의미상으로도 fill(0)이 맞는 초기값.</li>
<li>값이 없다는 의미로 -1이 의미상 더 적절하다고도 생각했는데,* <code>fill(-1)</code>처럼 &quot;없음&quot;을 표현하려면 이 문제에선 결국 후처리가 필요하고 (-1이 왜남는것임),
그 후처리(<code>map</code> 순회)가 오히려 복잡도를 높이므로 <code>fill(0)</code>을 쓰자.</li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li>처음에 아예 감 못잡았을 땐 reduce도 찾고 Map 자료 구조도 찾고 이것저것 찾아봤다 .. . </li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ul>
<li>비슷한 문제란 무엇인가 - 값이 &quot;처음으로&quot; &quot;떨어진/올라간/작은/큰&quot; 지점까지의 &quot;거리&quot;를 구하는 문제. 여기서의 &quot;거리&quot;는 지금의 &#39;초&#39;처럼 다소 추상적인 거리일 수도 있음.</li>
<li>일단 비슷한 문제인지 감을 잡아야 적용할 수 있을 것 같고 (그것부터가 지금의 나에겐 장벽임)
신호 키워드: &quot;처음으로&quot;, &quot;다음&quot;, &quot;거리/기간&quot; 이 세 단어가 같이 보이면 단조 스택을 일단 의심해보기</li>
<li>감 잡고 나면 <code>Array</code> 를 주어진 배열 길이만큼 초기화해두고 거기에 답을 저장(그래야 정확한 순서가 보장된 답을 구할 수 있으니)한다 생각하고,</li>
<li>stack에 아직 조건을 만족하지 않은 녀석들의 index를 0부터 집어넣고,</li>
<li>조건을 만족한다면 stack에서 <code>pop</code>하고, <code>Array</code>의 해당 index 값을 문제에 맞게 계산하여 저장, 조건을 만족하지 않을 때까지 반복해야 한다.</li>
<li>해당 반복문이 끝나면 이제 이번 인덱스도 stack에 넣어 다음 값이 비교군으로 사용할 수 있도록 한다.</li>
<li>이 반복문도 끝나면, 아직 stack에 남아있는 값들은 조건을 만족하지 않은 채 끝까지 버틴 값들이므로, 문제에 맞게 별도로 거리를 계산해주는데, 이때도 stack에서 하나씩 <code>pop</code>하면서 처리해주면 명확하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 프로세스 (스택/큐)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%8A%A4%ED%83%9D%ED%81%90</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%8A%A4%ED%83%9D%ED%81%90</guid>
            <pubDate>Sun, 05 Apr 2026 07:55:40 GMT</pubDate>
            <description><![CDATA[<h2 id="프로세스-스택큐">프로세스 (스택/큐)</h2>
<blockquote>
<p>2026.4.1. &amp; 2026.4.5.</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42587">https://school.programmers.co.kr/learn/courses/30/lessons/42587</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<p><strong>큐에서 &quot;처리 못하면 뒤로 보낸다&quot;는 동작의 구현 방법 접근법!</strong></p>
<p>순회하면서 인덱스로 값을 찾지 않고, 원본 배열을 직접 수정하며 앞에서부터 꺼내고 맨뒤에 넣으며 처리하는 방식
=&gt; 프로세스를 추적하기 위해 요소가 수정되는 배열이 필요한데, 원본 자체를 수정할 경우 index가 맞지 않게 됨. 결국 location과의 비교도 불가능해짐.</p>
<ol>
<li><p><code>shift()</code>, <code>push()</code>를 사용해 index 없이 처리하기</p>
</li>
<li><p><code>for ... of</code> 루프와 <code>while</code> 루프의 차이</p>
</li>
</ol>
<ul>
<li><p><code>for ... of Arr</code>: <code>Arr</code>가 변경되어도 순회 대상에 반영 X, 처음 판별한 값으로 고정됨 =&gt; <code>push()</code>로 뒤에 추가한 요소가 순회에 포함되지 않음</p>
</li>
<li><p>코드를 읽을 때 이터레이터(다음에 어떤 요소를 줄지 기억하는 객체)를 한번 만들고 그 만든 걸 계속 참조하는 구조임. 루프 안에서 배열이 변경되어도 <code>for ... of</code> 부분을 재평가하지 <strong>않음</strong>.</p>
</li>
<li><p><code>while</code>: 매 반복마다 조건을 새로 평가 =&gt; <code>push()</code>로 뒤에 추가한 요소가 순회에 포함됨</p>
</li>
<li><p>반복할 코드를 실행하고, 반복문을 더 실행할지 말지를 알기 위해선 <code>while</code> 뒤의 조건을 확인해야 하는 구조니까 그런 듯!!</p>
</li>
<li><p>(추가) 일반 <code>for</code>문은 <code>while</code>처럼 매 반복마다 조건을 재평가함! (다만 이 문제에선 <code>while</code>이 더 적절함)</p>
</li>
<li><p><code>for</code>문: 조건을 매번 배열에서 직접 읽음, 변경된 사항이 반영됨</p>
</li>
<li><p><code>for ... of</code>: 시작할 때 순회 계획을 미리 만들어두고, 그걸 따라감, 변경된 사항이 반영되지 않음</p>
</li>
</ul>
<pre><code class="language-javascript">for (let i = 0; i &lt; arr.length; i++)
//                  ↑ 매 반복마다 arr.length를 그 시점에 읽음</code></pre>
<pre><code class="language-javascript">for (let item of arr)
// 시작 시점에 이터레이터 생성 → &quot;1번째, 2번째, 3번째 줄게&quot;가 확정됨
// 이후 arr가 바뀌어도 이터레이터는 모름</code></pre>
<p>=&gt; <strong>배열 자체</strong>를 보는 게 <strong>아니라</strong> 다음에 어떤 요소를 실행할지 미리 계획한 객체여서, 계획이 세워진 후에 배열이 바뀌어도 계획이 바뀌지 않음</p>
<ul>
<li>이 문제에서 <code>for</code>문보다 <code>while</code>이 적절한 이유: <code>i</code>를 증가시키지 않고 순회해야 함. <code>shift</code>로 요소를 제거하면 length가 줄어들면서 <code>i</code>는 증가하니까 모든 요소를 순회하지 못함. <code>for</code>문에서 <code>shift</code>를 사용하려면 <code>i</code>를 증가시키지 않고 고정해야 함 = <code>while</code>과 같음
=&gt; <code>shift</code>처럼 배열의 길이를 변경하면서 순회할 때는 <code>while</code>이 가장 적합함</li>
<li><code>for</code>문에서 <code>push</code> 사용 시에는 <code>i</code>와 <code>arr.length</code>가 같은 속도로 증가 =&gt; 무한루프 위험 있음</li>
<li>이번 문제 풀이에선 <code>while</code>, <code>push</code>, <code>shift</code>의 조합이어서 length 변화 없이 모든 요소 순회 가능. 종료 조건이 논리적으로 보장되어야 안전한데, 이 문제에서는 우선순위 최고값이 반드시 존재하므로 종료가 보장됨!</li>
</ul>
<ol start="3">
<li><code>location</code> 추적 방법: <code>priorites</code> 배열에서 값과 <code>index</code>를 함께 묶어 재가공한 배열 만들어 사용</li>
</ol>
<ul>
<li><code>[index, value]</code>의 배열로 구성해도 되고, <code>{priority, index}</code> 같은 객체로 구성해도 무방</li>
</ul>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(priorities, location) {
    const array = priorities.map((p, idx) =&gt; [idx, p]);
    let count = 0;

    while (array.length &gt; 0) {
        const item = array.shift(); // 실행할 요소 꺼내기
        if (array.length === 0) { // ⚠️ 불필요한 코드임
            // 마지막 남은 요소면 체크 없이 처리 후 끝냄
            count += 1;
            return count;
        }

        // 대기 중인 프로세스 우선순위 체크
        const cannotProceed = array.findIndex(arr =&gt; item[1] &lt; arr[1]) !== -1;
        if (cannotProceed) {
            array.push(item); // 맨뒤에 다시 넣기
        } else {
            // 프로세스 실행
            count += 1;
            if (item[0] === location) {
                return count;
            }
        }
    }
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(priorities, location) {
    const array = priorities.map((p, idx) =&gt; [idx, p]);
    let count = 0;

    while (array.length &gt; 0) {
        const item = array.shift(); // 실행할 요소 꺼내기
        // ✅ length가 0일 경우 아래 cannotProceed가 true일 수 없음. 별도 처리 불필요!

        // 대기 중인 프로세스 우선순위 체크
        // ✅ 가독성을 고려해 some으로 변경
        const cannotProceed = array.some(arr =&gt; item[1] &lt; arr[1]);
        if (cannotProceed) {
            array.push(item); // 맨뒤에 다시 넣기
        } else {
            // 프로세스 실행
            count += 1;
            if (item[0] === location) {
                return count;
            }
        }
    }
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<ul>
<li><code>array</code>의 마지막 요소를 꺼냈을 때, <code>array.push(item)</code>을 실행해 무한루프에 빠질 것을 대비(?)해 얼리 리턴 코드를 넣은 거였는데, 생각해보면 그런 상황은 발생할 수 없음. 불필요한 분기를 없앰.</li>
<li><code>findIndex</code>나 <code>some</code> 둘 다 조건 충족하면 즉시 종료하므로 효율은 같은데, 체크하려는 것이 index가 아니라 조건을 만족하는 값이 <strong>존재하는가</strong> 이므로, <code>some</code>이 의미에 더 부합함.</li>
</ul>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ol>
<li><p>원본 배열 자체를 수정하고 순회하면서 <code>index</code>를 변수로 두어 따로 관리하려 했음 =&gt; 원본 배열의 길이가 줄어드는데 <code>index</code>는 계속 증가, <code>priorites[index]</code>가 <code>undefined</code>가 되면서 비교 로직이 부정확해짐</p>
</li>
<li><p><strong>그럼 그렇게 안하면서 원래 위치를 특정할 방법이 도대체 무엇인가???</strong> (location 추적 방식)
=&gt; 위에서 짚은 대로 값과 원래 인덱스를 함께 저장한 데이터를 정의해두고 사용
=&gt; <code>shift()</code>로 꺼내도 원래 위치가 함께 있는 구조 (<code>[[0, 2], [1, 1], [2, 3]]</code>: <code>[원래인덱스, 우선순위]</code> 형태)</p>
</li>
<li><p><code>for ... of</code>로 사용했을 때 원본 배열 수정이 반영되지 않는 현상
=&gt; 반복 대상인 배열이 반복문 안에서 수정되고 그것이 반영되어야 한다면, 매 반복 실행 시마다 조건을 다시 체크하는 <code>while</code>을 사용</p>
</li>
<li><p>index를 특정하려는 생각 버리기!
=&gt; 큐에서는 실행할 요소를 특정할 때 배열에서 index로 값을 판단하지 않아도, <code>shift()</code>로 꺼낸 맨 첫번째 요소가 바로 &quot;현재 처리할 요소&quot;임!!</p>
</li>
</ol>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li><code>splice</code>를 찾았는데, <code>shift</code>와 <code>push</code>만 이용하면 되는 거였음</li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ol>
<li>주어진 데이터의 &#39;위치&#39;를 알아야 하고 요소가 unique하지 않다면 =&gt; 처음 주어진 값에서 <code>값</code>과 <code>위치</code>를 같이 저장한 형태로 재가공해 사용하기</li>
<li>큐 문제라면 실행할 배열의 첫번째 요소부터 처리하면 되니까 <code>shift</code>를 사용하고, 문제 상황에 맞게 <code>push</code>도 사용하기. 반대로 스택 문제라면 <code>pop</code>을 사용해보면 되지 않을까?</li>
<li><code>for...of</code>문은 순회 시작 시 이터레이터를 한 번 생성하므로, 이후 배열이 변경되어도 반영되지 않음. 변경된 배열을 순회해야 한다면 매 반복마다 조건을 재평가하는 <code>while</code>을 사용할 것.</li>
</ol>
<p>휴..어려웠다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 기능개발 (스택/큐)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EA%B8%B0%EB%8A%A5%EA%B0%9C%EB%B0%9C-%EC%8A%A4%ED%83%9D%ED%81%90</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EA%B8%B0%EB%8A%A5%EA%B0%9C%EB%B0%9C-%EC%8A%A4%ED%83%9D%ED%81%90</guid>
            <pubDate>Tue, 31 Mar 2026 15:00:21 GMT</pubDate>
            <description><![CDATA[<h2 id="기능개발-스택큐">기능개발 (스택/큐)</h2>
<blockquote>
<p>2026.3.31.</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42586">https://school.programmers.co.kr/learn/courses/30/lessons/42586</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li>선입선출의 큐 자료구조<em>: 배포 순서상 뒤에 있는 기능은 먼저 개발되더라도 앞에 있는 기능 배포할 때 함께 배포해야 한다는 컨셉</em></li>
</ul>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(progresses, speeds) {
    const answer = [];

    const que = progresses.map((p, index) =&gt; {
        const days = Math.ceil((100 - p) / (speeds[index]));
        return days;
    })

    let count = 1;
    let target = que[0];

    for (let i = 1; i &lt; que.length; i++) {
        if (target &lt; que[i]) {
            // 배포 불가능한 경우
            answer.push(count);
            target = que[i];
            count = 1;
        } else {
            // 배포 가능한 경우
            count += 1;
        }
    }

    answer.push(count);
    return answer;
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<ul>
<li><code>map</code> 메서드 안에서 변수 선언 및 return 문 없이 화살표 함수로 구현하는 것 정도? 근데 <code>days</code> 라는 개념을 명확히 해두고 싶어서 일부러 변수를 썼어서 개선하지 않음.</li>
</ul>
<h3 id="핵심-차이">핵심 차이</h3>
<p>패스!</p>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ul>
<li>배포까지 필요한 날짜 수 구하는 건 간단한 사칙연산이라 금방 규칙을 구했다.</li>
<li>처음 작성한 코드는 2가지 문제점이 있는데,</li>
</ul>
<pre><code class="language-javascript">function solution(progresses, speeds) {
    const answer = [];

    const que = progresses.map((p, index) =&gt; {
        const days = Math.ceil((100 - p) / (speeds[index]));
        return days;
    })

    let count = 1;

    for (let i = 0; i &lt; que.length; i++) {
        // console.log(que[i], que[i+1], que[i] &lt; que[i+1]);

        // ⚠️ que[i]와 que[i + 1]을 비교하게 작성함
        if (i === que.length - 1 || que[i] &lt; que[i + 1]) {
            console.log(i, true)
            answer.push(count);
            count = 0; // ⚠️ count를 1이 아닌 0으로 초기화함
        } else {
            console.log(i, false)
            count += 1;
        }
    }

    return answer;
}</code></pre>
<ul>
<li>위 코드에서 <code>count</code>를 <code>1</code>로 초기화하도록 수정하면 문제에서 제시된 예시 케이스는 모두 통과한다.</li>
<li>반복문 안에서 <strong>자기자신과 그 다음 요소를 비교</strong>하도록 로직을 짰더니 문제의 예시 케이스에서만 성공하고 제출하니 우수수 틀렸다!</li>
<li>이럴 때 엣지케이스를 생각해내는 능력을 기르고 싶다. 클로드의 도움을 받아 [7, 3, 5] 케이스에 대해 더 자세히 손으로 적어보며 고민했다.</li>
</ul>
<pre><code>첫번째 인자 7 =&gt; count = 1
두번째 인자 3 =&gt; 7과 비교 =&gt; 7보다 작다 =&gt; count = 2
세번째 인자 5 =&gt; 7과 비교 =&gt; 7보다 작다 =&gt; count = 3</code></pre><ul>
<li>이 예시에서 알 수 있는 점: 바로 앞/뒤 요소끼리 비교가 아니라 특정한 대상과 비교해야 한다! =&gt; 비교 대상을 저장할 변수가 필요하다.</li>
<li>예시가 짧아서 문제 속 [5, 10, 1, 1, 20, 1] 케이스에 대해서도 같은 방식으로 접근해봤다.</li>
</ul>
<pre><code>첫번째 인자 5 =&gt; count = 1
두번째 인자 10 =&gt; 5와 비교 =&gt; 5보다 크다 =&gt; 5 먼저 나가: answer.push(count), 10을 새로운 비교 대상으로 저장, count 1로 초기화
세번째 인자 1 =&gt; 10과 비교 =&gt; 10보다 작다 =&gt; count = 2
네번째 인자 1 =&gt; 10과 비교 =&gt; 10보다 작다 =&gt; count = 3
다섯번째 인자 20 =&gt; 10과 비교 =&gt; 10보다 크다 =&gt; answer.push(count), 20을 새로운 비교 대상으로 저장, count 1로 초기화
여섯번째 인자 1 =&gt; 20과 비교 =&gt; 20보다 작다 =&gt; count = 2

더이상 인자가 없음 =&gt; answer.push(count), 종료!</code></pre><ul>
<li>플로우가 명확히 정리되어 그대로 코드로 옮겼다.</li>
</ul>
<pre><code class="language-javascript">function solution(progresses, speeds) {
    const answer = [];

    const que = progresses.map((p, index) =&gt; {
        const days = Math.ceil((100 - p) / (speeds[index]));
        return days;
    })

    let count = 1;
    let target = que[0];
    // ✅ target 초기값을 null로도 해봤는데, 그럴 필요가 없었다. 제일 첫 요소로 시작해서 큰 값으로 계속 바꿔주면 되는 것!

    // ✅ 제일 첫 요소(i=0 케이스)는 target으로 할당, count 1로 초기화 외에 하는 게 없어서 반복문 루프를 돌릴 필요가 없었다
    // 그래서 i = 1부터 시작하도록 변경
    // &lt; 연산자라서 length - 1까지가 아닌 것도 체크
    for (let i = 1; i &lt; que.length; i++) {
        // ✅ 인접 요소가 아닌 특정 target 값과 비교
        if (target &lt; que[i]) {
            // 배포 불가능한 경우
            answer.push(count);
            target = que[i]; // ✅ 새로운 값으로 업데이트
            count = 1;
        } else {
            // 배포 가능한 경우
            count += 1;
        }
    }

    // ✅ 루프가 끝나면 그때까지 누적된 count도 answer 배열에 포함해야 함!
    answer.push(count);
    return answer;
}</code></pre>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li><p><code>Math.ceil()</code></p>
<ul>
<li>항상 숫자에 바로 적용하려 한다. (문자열, 배열 메서드 쓰듯이..) 이번에도 오류와 마주하고 아, <code>Math</code> 메서드였지..깨닫기</li>
<li>올림, 반올림, 버림 메서드들은 항상 이름이 헷갈린다</li>
<li>올림: <code>Math.ceil</code></li>
<li>반올림: <code>Math.round</code></li>
<li>버림: <code>Math.floor</code></li>
</ul>
</li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ol>
<li>순서가 중요한 문제라면 큐(선입선출) 떠올리기: 앞에 있는 것이 처리되어야 뒤에 있는 것도 처리 가능한 구조</li>
<li>인접 요소끼리 비교했는데 틀렸을 경우: 비교 기준이 계속 바뀌는지 체크, 바뀌지 않는 동안은 고정된 target과 비교해야 함</li>
<li><strong>반복문 밖에서 마지막 케이스를 answer 배열에 push하는 것 잊지 말기!</strong>: 루프가 끝나면 마지막 그룹이 answer에 안 담긴 상태임<ul>
<li>&quot;그룹을 만들면서 순회할 때&quot;, 루프 안에서 순회 중일 때는 조건을 만족하면 현재 그룹을 저장하고 새 그룹을 시작하니 문제 없는데, 순회가 끝나 루프에서 나왔을 때 바로 <code>return answer</code> 처리하면 마지막 그룹은 포함되지 않은 상태임을 기억할 것</li>
<li>루프 끝난 뒤 마지막 처리가 필요한지 체크하는 습관 들이기</li>
</ul>
</li>
<li>엣지케이스 의심될 때: [7, 3, 5] 같은 <strong>단순한</strong> 예시 떠올려보고 손으로 차근히 접근해보기</li>
</ol>
<h3 id="비고-다른-접근법">(비고) 다른 접근법</h3>
<ul>
<li>선입선출의 자료구조가 잘 활용된 것인지 애매해서 (for문을 돌리니까 순차적으로 앞에서부터 처리하긴 하지만!) 좀더 고민해봤다.</li>
<li>배열의 앞에서부터 요소를 제거하는 <code>shift</code> 메서드와 내가 잘 안 쓰는 <code>while</code>문을 사용한 풀이법을 적어둔다.</li>
</ul>
<pre><code class="language-javascript">function solution(progresses, speeds) {
    const answer = [];

    const que = progresses.map((p, index) =&gt; {
        const days = Math.ceil((100 - p) / (speeds[index]));
        return days;
    })


    while (que.length &gt; 0) {
        const target = que.shift();
        let count = 1;

        // target보다 작거나 같은 것들을 앞에서부터 꺼내기
        while (que.length &gt; 0 &amp;&amp; que[0] &lt;= target) {
            que.shift();
            count++;
        }

        answer.push(count);
    }

    return answer;
}</code></pre>
<ul>
<li>원본 배열을 수정해버리는 <code>shift</code>는 혹시 모를 버그에 대비해 거의 안쓰긴 했다. (예를 들어 같은 배열을 다른 곳에서도 쓰고 있는데 <code>shift</code>로 다 꺼내버리면 그 다음에 참조하는 곳에서 빈 배열을 보게 되니까)</li>
<li>코테 수준에선 문제되지 않을테니 다시 한번 눈도장!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 올바른 괄호 (스택/큐)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8-%EC%8A%A4%ED%83%9D%ED%81%90</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8-%EC%8A%A4%ED%83%9D%ED%81%90</guid>
            <pubDate>Mon, 30 Mar 2026 00:47:54 GMT</pubDate>
            <description><![CDATA[<h2 id="올바른-괄호-스택큐">올바른 괄호 (스택/큐)</h2>
<blockquote>
<p>2026.3.30.</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12909">https://school.programmers.co.kr/learn/courses/30/lessons/12909</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li>후입선출의 스택 자료구조</li>
<li>배열 속 요소를 활용할 필요성이 있는지 체크, 필요 없을 경우 개수만 카운트하도록 단순화 가능</li>
</ul>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(s){
    const start = s.indexOf(&#39;(&#39;);
    if (start !== 0) {
        return false;
    }

    let answer = 0;

    for (let str of s) {
        answer = str === &#39;(&#39; ?  answer + 1 : answer - 1;
        if (answer &lt; 0) {
            return false;
        }
    }

    if (answer !== 0) {
        return false;
    }

    return true;
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(s){
    let answer = 0;

    for (let str of s) {
        answer += str === &#39;(&#39; ? 1 : -1;
        if (answer &lt; 0) {
            return false;
        }
    }

    return answer === 0 ? true : false;
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<ul>
<li>for 문 안에서 0 미만으로 떨어지면 바로 리턴하므로 기존의 얼리 리턴이 무의미함</li>
<li>불필요한 if문을 없애고 삼항연산자 활용</li>
</ul>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ul>
<li>스택 안에 괄호든 count든 쌓는다는 개념을 떠올리지 못하고, 스택 활용하는 건 맞는데 그걸 어떻게 로직으로 옮길지 방향을 잡지 못했음</li>
<li>인덱스를 찾아서 배열을 slice하는 함수를 만들고 그걸 반복시켜야 하나.. 생각함 (지금까지 쌓인 &#39;(&#39; 개수와 &#39;)&#39;가 일치하지 않는 걸 체크하려는 시도. 근데 이제 로직이 명확하지 않은)</li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li>인덱스 찾는다고 <code>findIndex</code> 메서드 썼는데 문법 오류가 났다! <code>findIndex</code>에서는 인자로 값이 아닌 함수를 받아야 하기 때문. (<strong>판별 함수를 만족하는 배열의 첫 번째 요소에 대한 인덱스를 반환</strong>함)</li>
<li>내가 원했던 메서는 <code>indexOf</code> (값을 인자로 받음)</li>
<li><code>slice</code> 메서드도 찾아봤다. start, end까지 자르되 end는 exclusive하게 자른 얕은 복사의 새 배열을 리턴</li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ol>
<li>후입선출의 자료 구조가 필요한가?</li>
<li>배열에 담은 요소 자체를 활용하는가? -&gt; 순회하며 요소 자체를 담고, 문제 요구사항을 체크</li>
<li>아니라면, 1 또는 -1을 더해주어 count 값을 이용해서 문제 요구사항을 체크하는 접근법을 떠올려보기</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 해시 문제 유형 정리]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%ED%95%B4%EC%8B%9C-%EB%AC%B8%EC%A0%9C-%EC%9C%A0%ED%98%95-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%ED%95%B4%EC%8B%9C-%EB%AC%B8%EC%A0%9C-%EC%9C%A0%ED%98%95-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 29 Mar 2026 14:24:10 GMT</pubDate>
            <description><![CDATA[<p>이번 주에는 프로그래머스에서 Lv2, JavaScript, &#39;해시&#39; 유형 필터링 걸어서 나온 문제 3개를 모두 풀었다.
공통 유형의 문제들을 풀어보면 그 개념이 감이 올 것으로 예상했는데 그렇지 않아서 따로 정리해보려 한다.</p>
<h2 id="해시-유형의-공통점">해시 유형의 공통점</h2>
<blockquote>
<p>이 값이 존재하는가/몇 개인가 를 빠르게 찾아야 할 때, &#39;배열&#39;보다 &#39;해시&#39;를 쓰는 것이 효율적이다!</p>
</blockquote>
<h3 id="왜-maphash를-쓰는가">왜 Map/Hash를 쓰는가</h3>
<ul>
<li><p><strong>배열</strong>로도 &#39;이 값이 존재하는가&#39;, &#39;몇 개인가&#39;를 찾을 수 있다.</p>
</li>
<li><p>최악의 경우 배열의 모든 요소를 순회해야만 답을 구할 수 있다는 약점이 있을 뿐..</p>
</li>
<li><p>이것을 시간복잡도로 표현하면, O(n)</p>
</li>
<li><p>반면 Map을 사용하면, <strong>키로 바로 접근</strong>하므로, O(1)의 시간복잡도를 갖는다.</p>
</li>
</ul>
<pre><code class="language-javascript">// 배열
arr.includes(&quot;target&quot;); // 최악의 경우 전체 순회

// Map
map.has(&quot;target&quot;); // 내부 해시값을 이용해 바로 접근</code></pre>
<ul>
<li>단, 해시 충돌(다른 키가 같은 저장 위치로 계산되는 경우)이 발생하면 최악의 경우 O(n)이 될 수 있음. 코딩테스트 레벨에서는 그런 상황은 없다는 가정 하에 해시 자료구조가 O(1)이라고 전제하는 것임.</li>
</ul>
<h4 id="해시가-o1인-이유">해시가 O(1)인 이유</h4>
<ul>
<li><p>해시 자료 구조에서는 <strong>키</strong>를 넣었을 때 <strong>해시 함수</strong>가 저장 위치(인덱스)를 계산해줌</p>
</li>
<li><p>데이터 인풋할 때도, 데이터를 찾을 때도 해시 함수가 그 저장 위치를 이용하므로 데이터를 순회할 필요가 없는 구조</p>
</li>
<li><p>JavaScript에서는 Map, Object(<code>{}</code>)가 내부적으로 해시 구조를 사용함.</p>
<ul>
<li>다만 코테에서는 순서 보장, 순회 편의성 등을 고려해 Object보단 Map 사용이 편리함</li>
<li>Map의 <code>get</code>, <code>set</code> 메서드에서 내부적으로 해시 함수를 사용</li>
</ul>
</li>
</ul>
<h3 id="해시-유형인지-감-잡기">해시 유형인지 감 잡기</h3>
<p>아래와 같은 요구사항을 마주하면 Map을 우선 떠올려보자.</p>
<ul>
<li>&quot;중복을 체크해야 함&quot;</li>
<li>&quot;그룹별로 개수를 세야 함&quot;</li>
<li>&quot;어떤 값이 존재하는지 (빠르게 - 당연) 확인해야 함&quot;</li>
</ul>
<p>=&gt; <strong>key로 value를 빠르게 찾는 구조를 만들어야 하는가?</strong> 를 생각해보면 됨</p>
<p>다만 전화번호 문제처럼 정렬로 더 깔끔하게 풀리는 경우도 있긴 함에 유의</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 테이블 해시 함수 (해시)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%ED%85%8C%EC%9D%B4%EB%B8%94-%ED%95%B4%EC%8B%9C-%ED%95%A8%EC%88%98-%ED%95%B4%EC%8B%9C</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%ED%85%8C%EC%9D%B4%EB%B8%94-%ED%95%B4%EC%8B%9C-%ED%95%A8%EC%88%98-%ED%95%B4%EC%8B%9C</guid>
            <pubDate>Sun, 29 Mar 2026 14:02:20 GMT</pubDate>
            <description><![CDATA[<h2 id="테이블-해시-함수-해시">테이블 해시 함수 (해시)</h2>
<blockquote>
<p>2026.3.29.</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/147354?language=javascript">https://school.programmers.co.kr/learn/courses/30/lessons/147354?language=javascript</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li>테이블 개념 (row, column으로 구성된 이차원 배열에 대한 개념적 이해)</li>
<li>코드 내에서 접근할 때의 index(0-based)와 문제에서 말하는 col번째(1-based)의 차이 구별: 이전에 다른 문제들에서 접했던 개념이고, 코드 추가할 때마다 어떤 값이 나오는지 계속 console 로깅하면서 왜 정렬이 의도대로 안되는지 고민해보다 알아챔</li>
<li>JavaScript <code>%</code> 연산자, <code>^</code> 연산자 - <code>^</code> 연산자는 살면서 쓸일이 없었어서 처음 써봤다. 문제에 냅다 &#39;bitwise XOR&#39; 연산을 수행하래서 그게 뭔데.. 하면서 개념 찾아보니 이진수로 변환해서 두 수를 자릿수별로 비교하는데, 값이 같으면 0을 다르면 1을 리턴하게 하고 그 결괏값을 리턴하라는 것이었다. . (결괏값도 이진법으로 적혀있는데 문제에서 반환하라는 값은 십진법 베이스여서 다시 역산이 필요)
클로드에게 이거 직접 연산하는 게 맞는지 물어보니 <code>^</code> 연산자를 쓰면 된다고 힌트를 줘서 사용했다.</li>
</ul>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(data, col, row_begin, row_end) {
    // 정렬부터
    const sorted = data.sort((a, b) =&gt; a[col - 1] === b[col - 1] ? b[0] - a[0] : a[col - 1] - b[col - 1]);

    let result = 0;

    // S_i 구하기
    // row_begin ~ row_end 빼먹는 것 없이 실행
    for (let i = row_begin - 1; i &lt; row_end; i++) {
        const row = sorted[i];
        const sum = row.reduce((acc, cur) =&gt; acc + cur % (i + 1), 0);

        result = result ^ sum;
    }

    return result;
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(data, col, row_begin, row_end) {
    // 정렬부터
    const sorted = [...data].sort((a, b) =&gt; a[col - 1] === b[col - 1] ? b[0] - a[0] : a[col - 1] - b[col - 1]);

    let result = 0;

    // S_i 구하기
    // row_begin ~ row_end 빼먹는 것 없이 실행
    for (let i = row_begin - 1; i &lt; row_end; i++) {
        const row = sorted[i];
        const sum = row.reduce((acc, cur) =&gt; acc + cur % (i + 1), 0);

        result = result ^ sum;
    }

    return result;
}</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<ul>
<li>이미 깔끔하다고.. ㅎㅎㅎ</li>
<li><code>sort()</code>는 원본 배열을 변경하므로 spread 연산자를 사용해 새 배열을 생성하고 그것을 정렬하는 것이 안전하다.</li>
</ul>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ul>
<li>bitwise XOR 개념을 몰라서</li>
<li>오히려 근데 문제가 논리적으로 로직으로 옮기기만 하면 되는 형태여서 고민할 부분이 적었다.</li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li>오늘도 <code>reduce</code> 문서 다시보기~ 세번째 인자로 <code>currentIndex</code>를 받을 수 있음을 체크하고, 반복문 안에서 <code>reduce</code> 호출할 때 그 값도 사용하려 했는데, <code>cur</code> 값이 이미 해당 row의 컬럼값이 찍히고 있었다. 그래서 인덱스는 필요가 없었음. 이미 row에 대해서 반복을 돌리고 있어서 그런 거네.</li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<p>반복문 인덱스(0-based)와 문제의 행 번호(1-based)가 다를때</p>
<ul>
<li>i로 배열 접근, i+1로 계산에 사용</li>
<li>console.log로 값 찍어가며 확인하는 습관이 도움됨</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 전화번호 목록 (해시)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%EB%AA%A9%EB%A1%9D-%ED%95%B4%EC%8B%9C</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%EB%AA%A9%EB%A1%9D-%ED%95%B4%EC%8B%9C</guid>
            <pubDate>Fri, 27 Mar 2026 00:43:28 GMT</pubDate>
            <description><![CDATA[<h2 id="전화번호-목록-해시">전화번호 목록 (해시)</h2>
<blockquote>
<p>2026.3.27.</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42577">https://school.programmers.co.kr/learn/courses/30/lessons/42577</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(phone_book) {
    const sorted = phone_book.sort();

    for (let i = 0; i &lt; sorted.length; i++) {
        if (i === sorted.length -1 ) return true;

        if (sorted[i].length &lt; sorted[i + 1].length) {
            if (sorted[i + 1].startsWith(sorted[i])) {
                return false;
            } else continue;
        } else {
            if (sorted[i].startsWith(sorted[i + 1])) {
                return false;
            } else continue;
        }
    }
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(phoneBook) {
    const sorted = phoneBook.sort();

    for (let i = 0; i &lt; sorted.length - 1; i++) {
        if (sorted[i + 1].startsWith(sorted[i])) {
            return false;
        }
    }
    return true;
}</code></pre>
<ul>
<li>for문 내부 if문 다음에 <code>continue</code>를 넣었었는데, 지금 코드에선 어차피 루프 내 코드의 마지막 줄이라 건너뛰고 다음 루프로 넘어가라고 따로 지시할 필요 없음! 어차피 그렇게 동작할 것이기 때문.</li>
</ul>
<h3 id="핵심-차이">핵심 차이</h3>
<ol>
<li><code>sort</code>로 정렬하면 길이도 작은순으로 정렬돼서 <code>sorted[i]</code>와 <code>sorted[i+1]</code>의 길이를 비교할 필요가 없음. 먼저 놓인 요소가 항상 뒤에 놓인 요소보다 길이가 짧을 것으로 가정해도 무방함.
(문자열 비교로 인해 길이가 앞이 더 긴 경우에는 논리적으로 접두어가 될 수 없으므로 고려하지 않아도 된다)</li>
<li><code>sorted.length -1</code> 까지 반복문을 돌리면 얼리 리턴 코드가 없어도 동일하게 동작함</li>
</ol>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ol>
<li><p>해시가 뭐지? 어떻게 활용하라는 의도일까?
이 문제에서 해시를 적용하는 방향을 찾지 못함
배열, 문자열 메서드는 좀 아니까 고민하다보니 풀렸음</p>
</li>
<li><p>정렬할 때 길이를 먼저 비교해야 할까, 알파벳을 먼저 비교해야 할까 고민했는데, 그럴 필요 없었던 게 <code>sort</code>는 내부적으로 둘 다 비교해서 정렬함.</p>
</li>
</ol>
<p><strong><code>sort()</code></strong> 기본 정렬: <strong>사전순 정렬</strong> - 문자 하나씩 앞에서부터 비교 &amp; 길이가 짧은 게 앞에 놓임</p>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li>배열 <code>sort()</code> 메서드, 문자열 <code>includes()</code>, <code>startsWith()</code> 메서드</li>
<li>정렬할 때 문자열이라 숫자로 변환해야 하나 고민했는데, 동일한 문자열로 시작하는지만 체크하면 되는거라 그부분은 고려할 필요가 없으니 단순하게 <code>sort</code>만 사용함</li>
<li>처음엔 <code>includes</code>를 쓰려 했는데 (지난번 <code>for ... in</code>, <code>for ... of</code>처럼 계속 Dart랑 JavaScript 중에 다트 문법이 먼저 생각나는 이슈로 <code>contains</code>부터 떠올리긴 함) 좀더 생각해보니 어디에든 포함하는 걸 체크하는 게 아니라 &#39;접두&#39;어인지 체크하는 거라서, <code>startsWith</code> &amp;&amp; <code>정렬</code>을 떠올림</li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ol>
<li>&#39;접두어/포함 관계 체크&#39; 문제가 나오면 정렬 먼저 떠올리기: 정렬하면 관계 있는 것들이 인접하게 붙음</li>
<li>정렬 후 인접한 두 요소만 비교하면 되므로, <code>for i &lt; length - 1</code> + <code>startsWith</code> 패턴 사용</li>
<li><code>sort()</code> 기본 정렬은 사전순이어서 접두어 관계 체크엔 충분. 길이를 따로 비교할 필요 없음</li>
</ol>
<hr>
<p>특이점. 힌트없이 풀었다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코테 준비하기]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%A4%80%EB%B9%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%A4%80%EB%B9%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 26 Mar 2026 13:50:43 GMT</pubDate>
            <description><![CDATA[<ol>
<li>매일 1시간 루틴</li>
</ol>
<pre><code>5분 - 어제 푼 문제 핵심 개념 훑기
40분 - 새 문제 풀기
15분 - 블로그 기록 (템플릿 활용)

다만, 40분/15분은 가이드라인, not 목표</code></pre><ol start="2">
<li>주의할 점</li>
</ol>
<pre><code>- 30분 넘게 막히면 미련 없이 클로드에게 현재까지 풀이 상황 공유해 힌트 받고 풀어나가기 ⭐️

- 그래도 안되면 답 보기

- 직접 풀었다 -&gt; 답 공유하고 개선점 피드백 받고 더 나은 코드 이해하기
- 못 풀었다 -&gt; 답 공유받고 이해하기

- 다시 돌아가 직접 모범 답안까지 도달하기

- 풀이 후 기록은 반드시 당일 안에 - 미루면 영원히 안씀</code></pre><ol start="3">
<li>3주동안 풀 문제 유형</li>
</ol>
<pre><code>이번주 - 해시 마무리: 남은 해시 문제 다 풀기
다음주 - 스택/큐
그다음주~ - BFS/DFS: 막혀도 정상이니까 당황하지 말기</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[코테] 의상 (해시)]]></title>
            <link>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%9D%98%EC%83%81-%ED%95%B4%EC%8B%9C</link>
            <guid>https://velog.io/@ekil_like/%EC%BD%94%ED%85%8C-%EC%9D%98%EC%83%81-%ED%95%B4%EC%8B%9C</guid>
            <pubDate>Thu, 26 Mar 2026 13:39:31 GMT</pubDate>
            <description><![CDATA[<h2 id="의상-해시">의상 (해시)</h2>
<blockquote>
<p>2026.3.26.</p>
</blockquote>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42578">https://school.programmers.co.kr/learn/courses/30/lessons/42578</a></p>
</blockquote>
<h3 id="핵심-개념">핵심 개념</h3>
<p>각 카테고리의 아이템마다 &quot;선택함 / 선택하지 않음&quot; 두 가지 경우의 수
-&gt; (카테고리별 아이템 수 + 1)을 전부 곱하고, 아무것도 안입은 것은 제외하기 위해 -1</p>
<h3 id="내-풀이">내 풀이</h3>
<pre><code class="language-javascript">function solution(clothes) {
    const map = new Map();

    for (let cloth of clothes) {
        if (!map.has(cloth[1])) {
            map.set(cloth[1], [cloth[0]]);
        } else {
            const arr = map.get(cloth[1]);
            map.set(cloth[1], [...arr, cloth[0]])
        }
    }

    const arr = Array.from(map.values());

    const answer = arr.reduce((acc, cur) =&gt; {
        const available = cur.length + 1;
        return acc * available;
    }, 1) - 1;

    return answer;
}</code></pre>
<h3 id="개선된-풀이">개선된 풀이</h3>
<pre><code class="language-javascript">function solution(clothes) {
    const map = new Map();

    for (let cloth of clothes) {
        map.set(cloth[1], (map.get(cloth[1]) || 0) + 1);
    }

    const answer = [...map.values()].reduce((acc, cur) =&gt; acc * (cur + 1), 1) - 1;
    return answer;
}
</code></pre>
<h3 id="핵심-차이">핵심 차이</h3>
<p>아이템 이름은 사용하지 않고, 타입별 아이템 개수만 필요하므로, 개수만 저장하도록 구현 = 로직이 단순해짐</p>
<h3 id="막혔던-포인트">막혔던 포인트</h3>
<ul>
<li>경우의 수를 구할 개념을 잡지 못했음 (&#39;선택함 / 선택안함&#39; 두 경우뿐이라는 사실)</li>
<li>2차원 배열을 어떻게 재구성할지 몰랐음</li>
</ul>
<h3 id="풀면서-찾은-개념">풀면서 찾은 개념</h3>
<ul>
<li>내가 원했던 자료 구조는 Set이 아닌 Map이었음. <code>has</code>, <code>get</code>, <code>set</code> 메서드가 존재하는 자료 구조!</li>
<li><code>for ... in</code> 이 아닌, <code>for ... of</code> 를 써야 함</li>
<li><code>reduce</code> 함수 파라미터는 <code>acc, cur</code> 순서이고, 화살표 함수로 작성하지 않았으면 <code>return</code> 잊지 말고, 초기값 설정 잊지말자. 이 문제는 &#39;곱하기&#39;를 해야 하는데, 초기값을 관성적으로 0으로 설정했다가 계속 -1이 출력되는 해프닝이 있었음.</li>
</ul>
<h3 id="다음에-비슷한-문제-만나면">다음에 비슷한 문제 만나면</h3>
<ul>
<li>카테고리별 그룹화가 필요하면 Map 먼저 떠올리기</li>
<li>경우의 수 조합 문제면 &#39;선택 / 미선택&#39; 관점으로 접근해보기</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[서평] 안티프래질 프런트엔드]]></title>
            <link>https://velog.io/@ekil_like/%EC%84%9C%ED%8F%89-%EC%95%88%ED%8B%B0%ED%94%84%EB%9E%98%EC%A7%88-%ED%94%84%EB%9F%B0%ED%8A%B8%EC%97%94%EB%93%9C</link>
            <guid>https://velog.io/@ekil_like/%EC%84%9C%ED%8F%89-%EC%95%88%ED%8B%B0%ED%94%84%EB%9E%98%EC%A7%88-%ED%94%84%EB%9F%B0%ED%8A%B8%EC%97%94%EB%93%9C</guid>
            <pubDate>Sun, 16 Nov 2025 23:35:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ekil_like/post/db5e7df2-d86c-4044-8776-9a2793951d94/image.png" alt="안티프래질 프런트엔드 책 표지"></p>
<h2 id="개요-이-책에-관하여">개요: 이 책에 관하여</h2>
<ul>
<li><strong>총평</strong>
  지금의 나에겐 유용하고 꽤 맘에 드는 책이다. 좀 더 본질적인 차원의 지식을 다루며, 처음 접한 개념도 이해하고 기억할 수 있도록 적혀있기 때문. 추천 독자에 자신이 해당된다 생각한다면 읽어볼 것을 추천한다!</li>
<li><strong>추천 독자</strong>
  원론적인 공부는 잠시 제쳐두고 일단 웹 개발을 하고 있는 주니어 프론트엔드 개발자 (중에서도 특히 성장을 위한 지식을 쌓고 싶다는 생각을 하고 있으면 더 추천!)</li>
<li><strong>이 책의 좋은 점</strong><ol>
<li>다루는 주제: 본질적인 지식 (ex. &#39;useEffect&#39;의 구체적인 사용법이 아니라, React가 해결하려는 &#39;문제&#39;가 무엇이고 &#39;어떤 방법으로&#39; 그 문제를 해결하고 있는지를 알려줌)</li>
<li>서술 방식: 중요한 개념은 설명 과정에서 정의를 여러 번 반복하여 서술함으로써 그 정의를 이해하고 머릿속에 넣게 도와줌</li>
</ol>
</li>
<li><strong>아쉬운 점</strong>
  화질이 안 좋아 글자 식별이 잘 안되는 이미지들이 좀 있었음 (내용을 바탕으로 유추 가능한 것이 대부분이긴 했다)
  <img src="https://velog.velcdn.com/images/ekil_like/post/cff5c668-a810-42cd-83b1-196059fb4ca1/image.png" alt="화질 떨어지는 이미지 예시"></li>
</ul>
<h2 id="들어가며-지식과-실력을-갖춘-엔지니어에-대한-갈망">들어가며: 지식과 실력을 갖춘 엔지니어에 대한 갈망</h2>
<p>AI가 놀라운 속도로 발전하고 일상에 침투하며, 내가 개발하는 방식도 바뀌었다. </p>
<p>처음 인턴으로 일하던 시절에는 프레임워크의 기본 메서드도 헷갈려서 선배들이 작업한 소스코드를 들추고 구글링하며 사용법을 익혔다. 그러다 깃허브 코파일럿을 사용하며 머릿속으로 어렴풋이 생각하던 &#39;다음 코드&#39;를 코파일럿이 추천해주면 &#39;오..! 어떻게 알았지?&#39; 하며 Tab 키를 눌러 적용하며 코드를 작성했다.</p>
<p>우리 개발팀은 그후 Windsurf를 꽤 오랜 기간 사용했는데, Cursor가 더 똑똑하다는 후기들이 자주 들려 병행하다 Cursor로 갈아탔다. 그러다 또 다음 시류에 몸을 맡기며 지금은 클로드코드를 사용 중이다. Cursor에서 클로드코드를 사용하다보니 코드 추천 기능이 체감상 일주일이면 끝이 나는데, 거진 3년을 그 기능을 사용하며 일해왔다보니, 자동 코드 추천 없이 코드를 짜는 게 막막했다.</p>
<p>단순하게는 변수명에 오타가 들어갈 수 있다는 것. 이 부분을 아예 신경쓰지 않고 편리하게 일하고 있었구나! 깨달았다. 크리티컬하게는 논리적인 로직을 구현하는 과정에 있어서, AI가 추천해준 로직을 거의 그대로 쓰고 있었나..?! 하는 충격적인 깨달음이 있었다. 자동 추천 기능을 빼앗긴(?) 경험은 주니어인 나에게 중요한 터닝포인트가 되었다. 사용하는 언어의 기본적인 문법을 잘 구사하고 적재적소에 메서드를 활용해 효율적으로 기능을 구현하는 능력을 길러야겠다,라는 목표가 아주 선명해졌다.</p>
<p>말하자면 나는 탄탄한 기본기를 갖춘 소프트웨어 엔지니어가 되고 싶어졌다.</p>
<p>언어와 프레임워크, 그리고 운영체제와 컴퓨터에 대한 지식을 바탕으로 문제를 해결하는 실력을 갖추고 싶다. 처음 공부하던 시절 구매했던 JavaScript Deep Dive를 다시 들춰보며 공부를 시작했고, 로직을 짤 때도 AI 의존도를 이전보다 줄이고자 노력했다.</p>
<p>그러던 중 김상철, 『안티프래질 프런트엔드』 (비제이퍼블릭, 2025)라는 책을 어디선가 접했고, 나중에 읽어봐야지 하고 있었다. 그런데 운명적으로(?) 며칠 뒤 해당 도서의 서평단 모집 이벤트를 알게 되어 냅다 신청했고, 감사하게도 이렇게 읽어볼 수 있게 되었다. ☺️</p>
<h2 id="책-맛보기">책 맛보기</h2>
<p>이 책의 큰 목차들은 다음과 같다.</p>
<ul>
<li>변하는 것과 변하지 않는 것 (안티프래질에 대해 이야기한다.)</li>
<li>브라우저 (&#39;브라우저&#39;의 개념과 화면 렌더링 과정을 구체적으로 다룬다.)</li>
<li>리액트</li>
<li>Next.js</li>
<li>인프라 구조</li>
</ul>
<p>각 챕터에서는 대주제를 <strong>구체적으로</strong> 다루며, 아마도 의도적으로 <strong>반복적으로</strong> 언급한다. 비슷하지만 다른 표현 또는 같은 표현으로, 설명을 덧붙이며 반복적으로 설명하기에, 읽을수록 개념이 정립된다. 이 점이 나는 굉장히 마음에 들었고 감사했다.</p>
<h2 id="안티프래질">안티프래질</h2>
<p><del>이 책의 치명적인 약점은, 제목을 보고 책을 읽기 시작하면 머릿속에서 ANTIFRAGILE 노래가 재생된다는 점이다..</del></p>
<p>책 제목의 &#39;안티프래질&#39;은 &#39;충격을 가하면 오히려 단단해진다&#39;는 의미를 갖는다. 이 얼마나 멋진 단어인가.. 처음엔 음 그렇구나 정도로 받아들였는데, 책을 반 이상 읽은 지금은 이 단어의 의미가 더 크게 와닿는다.</p>
<p>내가 이해한 식으로 말해보자면,
<strong>많은 변화를 거치면서도 살아남은 개념들</strong>을 잘 알아두면, 새로운 충격적인 변화가 나타났을 때 그 기본 지식들을 바탕으로 <strong>더 잘 이해하고 응용하는 능력</strong>을 갖출 수 있다는 의미로 해석된다.</p>
<p>프론트엔드 생태계는 원래도 새로운 기술이 빠르게 등장하고 사라지는 필드였는데, AI의 등장으로 이제는 프론트엔드 분야에만 한정할 것이 아니라 매일 사용하는 IDE, 개발 에이전트도 새로운 것이 출시되고 자주 업데이트된다. 앞서 이야기했듯 개발 자체에서 AI의 도움을 많이 받고 있었기에, 이 도구들을 어떻게 현명하게 활용할 것인가에 좀 더 관심이 있었던 것 같다. 그런데 이 책을 읽으며, 그런 도구를 잘 활용하는 방법론도 중요하지만, 근본적인 지식을 갖추는 게 더 시급함을 느꼈다.</p>
<p>이 책에선 다음과 같은 지식을 얻을 수 있다.</p>
<ul>
<li>사용자가 url을 입력하면 어떤 일이 벌어져 우리 웹페이지에 접속할 수 있는 것인지</li>
<li>내가 개발한 소스코드를 사용자에게 제공하는 브라우저, 이 브라우저는 어떤 과정을 통해 코드를 웹의 형태로 바꿔주는 것인지</li>
<li>리액트가 정확히 어떤 기능까지를 포함하며, 어떤 문제를 풀기 위해 등장한 것인지</li>
</ul>
<p>몰라도 웹 개발을 할 수 있으나, 좋은 개발자가 되기 위해 반드시 알아야 할 것들. (위에 나열한 것 외에도 Next.js, 인프라에 대한 내용도 다룬다!!) 저 과정들을 알아야 무언가 문제가 발생했을 때 그 원인을 더 정확히 파악하고, 개선 방법을 도출할 수 있을 것이다.</p>
<p>어렴풋이 알던 개념들과 몰랐던 개념들이 함께 있었는데, 머릿속에서 개념들이 잘 정리되고 맞추어지는 느낌이었다. 한번에 다 흡수하진 못했으니 반복해서 읽어야 정말 내것이 되겠지만!</p>
<h2 id="책을-읽은-후에-이어지는-과제들">책을 읽은 후에 이어지는 과제들</h2>
<p>읽으면서 추가로 조사해보고 싶은 주제들도 많았다. 정말 많은 페이지에 인덱스를 붙이고 메모하며 읽었다. 그중 두가지만 대표적으로 말해보자면 다음과 같다.</p>
<h3 id="번들러-등-리액트가-아닌-것">번들러 등 리액트가 아닌 것</h3>
<p>예를 들어 &#39;리액트와 리액트가 아닌 것&#39; 챕터에서 번들러에 대한 설명이 짧게 나오는데, 웹팩, 롤업 같은 게 번들러였다. 부끄럽지만 나는 이것들의 이름만 들어봤지 정확히 어떤 역할을 하는지도 모른채 개발을 하고 있었다. 리액트 프로젝트를 셋업하면, 리액트로 웹을 개발하고 배포하는 과정을 편하게 하기 위해 번들러를 포함한 다양한 기능들이 이미 세팅되어 있다. 리액트와 그밖의 어떤 것들이 모여 웹 개발을 가능하게 해주는 건지 관심을 갖지 않았었는데, 이 챕터를 계기로 리액트 외의 것들도 알아봐야겠단 생각을 했다.</p>
<h3 id="리액트의-철학-공식문서">리액트의 철학, 공식문서</h3>
<p><img src="https://velog.velcdn.com/images/ekil_like/post/56cc81dd-8016-40da-be0e-4f417dd10982/image.png" alt="&#39;웹과 네이티브 라이브러리&#39; 정의에 대한 설명 (리액트)"></p>
<p>웹 개발할 땐 리액트, 앱 개발할 땐 리액트 네이티브를 쓴다는 사실만 알고, 각 프레임워크로 어떻게 개발하는지에 관심을 갖고 있었는데, &#39;리액트 네이티브&#39;가 왜 &#39;리액트 네이티브&#39;인지도 이 책 덕분에 알았다. iOS, Android 같은 &#39;네이티브&#39;에서 UI를 그리는 리액트 프레임워크였기 때문이다..</p>
<p>리액트 공식 문서의 &quot;웹 및 네이티브 사용자 인터페이스를 위한 라이브러리&quot;라는 설명도 드디어 이해했고, 리액트가 UI 라이브러리였음을 알았다. 이것도 모르고 매일 리액트를 쓰고 있었다니..</p>
<h3 id="앞으로의-공부-계획">앞으로의 공부 계획</h3>
<p>그래서 이 책을 활용해 앞으로 어떻게 할 것이냐면, 우선 책에서 다룬 핵심 주제를 설명할 수 있을 정도로 머릿속에 넣고 싶다. 읽으면서 주제 정리하는 챕터가 나오면 잠시 덮고 한번 먼저 말로 설명해보기도 했는데, 분명 이해한 것 같았는데 떠오르지 않거나 설명히 막혔기 때문이다.</p>
<p>그리고 남겨둔 인덱스와 메모를 다시 보며 추가로 조사/정리하고 싶었던 내용을 짚어보면 좋겠다. 목표를 세웠음에도 지속적으로 실천하는 게 참 어렵지만.. 나를 위해 해냈으면 좋겠다. 화이팅..!</p>
<p>끝으로 이런 양질의 도서를 읽을 기회를 제공해주신 <a href="https://www.instagram.com/bjpublic_official?igsh=MXBlYWY4cnowaDh3dQ==">@비제이퍼블릭 출판사</a>에 감사드린다. 책을 잘 써주신 <a href="https://www.linkedin.com/in/sckimynwa/">@김상철 개발자님</a>에게도 깊은 감사를 드린다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[팀네이버 컨퍼런스 DAN25] 연결의 진화, 경험의 확장 방문 후기]]></title>
            <link>https://velog.io/@ekil_like/%ED%8C%80%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-DAN25-%EC%97%B0%EA%B2%B0%EC%9D%98-%EC%A7%84%ED%99%94-%EA%B2%BD%ED%97%98%EC%9D%98-%ED%99%95%EC%9E%A5-%EB%B0%A9%EB%AC%B8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@ekil_like/%ED%8C%80%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-DAN25-%EC%97%B0%EA%B2%B0%EC%9D%98-%EC%A7%84%ED%99%94-%EA%B2%BD%ED%97%98%EC%9D%98-%ED%99%95%EC%9E%A5-%EB%B0%A9%EB%AC%B8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 09 Nov 2025 12:09:40 GMT</pubDate>
            <description><![CDATA[<p>이번 주에는 공교롭게도 코엑스에 세번째 방문했는데, 이번엔 팀네이버 컨퍼런스 DAN25 방문이 목적이었다. 작년에는 클릭을 늦게 해서 신청에 실패해 오프라인 방문은 못했는데, 올해는 꼭 가보겠단 마인드로 재빠르게 신청해 다녀올 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/ekil_like/post/dd31677e-426c-425c-b909-9efd8e3a4333/image.jpeg" alt="DAN25 현장 사진"></p>
<p>11월 6일 목요일, 7일 금요일 양일 간 진행했는데, 첫날인 목요일은 온라인으로 키노트만 들었다.</p>
<h2 id="키노트-후기">키노트 후기</h2>
<p>키노트는 꽤 흡인력이 있었다. 최수연 CEO님, 김범준 COO님, 이종민 광고 사업 부문장님, 이재후 네이버앱 서비스 부문장님, 김유원 네이버 클라우드 CEO님이 차례로 진행하셨다.</p>
<p>들으면서 바로 느껴진 인상적이었던 부분은, 작년 DAN24에서 약속했던 내용들을 먼저 짚고 그것을 올해까지 잘 지켜왔는지, 그리고 그것을 바탕으로 하여 올해는 좀 더 나아가서 (컨퍼런스 및 네이버에서) 어떤 주제를 다룰 것인지를 이야기한 것이었다. 이런 식으로 자신들이 한 말을 한번 내뱉고 끝이 아니라 약속을 지키는 기업이라는 신뢰의 메시지를 주려고 한 것 같은데, 의도가 보이든 어떻든 좋은 자세 같다.</p>
<p>키노트의 주요 키워드는 SK 써밋에서와 유사했다. <strong>Agentic AI, 소버린 AI</strong>, ProActive 등.</p>
<p>네이버만의 특징은, 검색, 뉴스, 쇼핑, 뮤직 등 네이버가 이미 갖춰둔 많은 서비스 경로를 통해 획득한 &quot;사용자 정보&quot;(비대화형 컨텍스트)를 바탕으로 더 개인화된 경험을 제공할 에이전트를 개발하려 한다는 것이었다.</p>
<p>이것이 네이버의 큰 경쟁력이자 무기라는 생각이 크게 들었다. 이미 갖추어진 서비스가 많고, 각 서비스들에 어느 정도의 사용자들이 있다는 것. 팀네이버는 그러한 데이터를 잘 활용할 방법을 모색하여 <strong>지금까지 없었던 사용자 경험을 제공할 수 있는 것</strong>이다.</p>
<p>예를 들어 이미 네이버 앱으로 접속하면 뜨는 피드는 몇번 사용해보면 알 수 있듯이 내가 검색하거나 클릭해본 정보를 바탕으로 맞춤 컨텐츠를 제공한다. 사용자로서는 이게 편할 때도 있고 좀 꺼림칙할 때도 솔직히 있었으나, personA 세션에서 공유한 내용처럼 뉴스 기사를 보고 있을 때 내가 평소 관심 있는 주제와 정보 수집 패턴을 바탕으로 관심 있을 법한 추가 정보를 제공하는 캔버스는 꽤나 유용해 보였다. (자세한 내용은 아래에)</p>
<p>추후 출시될 쇼핑 에이전트에 대해서도 언급했는데 이것도 흥미로웠다. 예를 들어 사용자가 온라인으로 쇼핑을 할 때 주문한 제품의 배송이 오래 걸릴 경우, 사용자의 위치 근처에서 픽업 가능한 매장이 있는지 찾아서 사용자에게 &#39;픽업하고 싶은지&#39; &#39;먼저&#39; 물어보고, 사용자가 원한다고 답하면 픽업 예약까지 잡아주는 식의 경험을 제공할 거라고 했다. 실제로 써보면 어떨지 꽤 궁금하다.</p>
<p>개인화된 광고를 띄워줄수록 더 의미있는 광고, 사용자가 효능감을 느낄 광고를 제시할 수 있다는 점도 공감이 됐다. 사실 광고가 뜨지 않는 게 가장 사용자로선 만족스러운 방향이지만, 기왕 뜰 거면 나에게 필요한 정보를 보여주는 것도 나쁘진 않단 인식을 요즘 인스타그램을 통해서 하고 있었기 때문이다. 인스타그램에서는 누가봐도 너무나 광고인 광고가 많이 떠서 불쾌한 적도 있었지만, 이런 컨퍼런스 정보나 내가 정말 몰랐으나 필요한 제품 광고 같은 게 뜨기도 해서 유용하기도 했다. 그 뒤로는 광고를 마냥 넘기기보단 어느 정도 주의깊게 보게 되더라고..</p>
<p>또 네이버에는 소버린 AI의 일종으로 한국은행과 한국수력원자력에 안전하고 신뢰할 수 있는 AI를 제공하고 있다고 했다. 이런 국가 차원의 AI를 개발하는 데 일조할 수 있단 것도 꽤 재밌고 보람있지 않을까 하는 생각이 불쑥 들었다. 심지어 사기업인 네이버에서 일하는데 이런 일을 할 수 있다니..!! 지금 연구하고 계신 분들이 부럽기도 하고 그랬다.</p>
<p>로보틱스를 개발하는 피지컬 AI 분야 연구도 하고 있는데, 방위 산업과 농업 산업 등에 활용 중이라고 했다. 네이버에서 이런 분야까지 관여하고 있는지 처음 알았다. 두 산업 분야 모두 잘 활용하면 큰 도움이 되지 않을까? 희생되는 사람을 줄일 수 있고, 부족한 농업 일손 문제를 해결하는 데 도움이 될 것 같아 앞으로의 소식이 기대된다.</p>
<p>다양한 사례들을 이야기하며, 기술의 진정한 가치는 &quot;연결&quot; (사람과 사람을 연결하는..)에 있다는 믿음을 바탕으로 네이버는 &#39;모두를 위한 기술&#39;을 추구한다는 결론으로 키노트는 마무리되었다. 평소 네이버 검색 엔진과 지도, 네이버 페이 정도를 사용하는 사용자로서 네이버가 이런 가치를 중시하는 줄은 정말 몰랐다. 이후에 들은 세션에서도 좀 사람다운 기업이란 느낌을 받기도 해서 신기했다. 어쨌든 모든 기업들은 다 목적을 가지고 존재할텐데, 기술을 수단으로하여 인간적인 목적을 달성하고자 한다는 게 좋게 보였다.</p>
<h2 id="세션-후기">세션 후기</h2>
<p>나는 아래의 세션들을 들었다.</p>
<ul>
<li><a href="#%EB%A1%9C%EA%B7%B8-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%84%B8%EC%85%98">하루 수백억 건을 처리하는 똑똑한 로그 파이프라인 만들기: 비용, 성능, 안정성 삼박자</a></li>
<li><a href="#%ED%8E%98%EB%A5%B4%EC%86%8C%EB%82%98-%EC%84%B8%EC%85%98">네이버 PersonA - 지금 나를 이해하는 AI: LLM 기반 사용자 메모리 구축과 실시간 사용자 로그 반영 시스템 구현</a></li>
<li>What’s In Our Infra: 대규모 트래픽을 수용하기까지</li>
<li><a href="#%EC%BC%80%ED%94%8C%EB%9F%AC-%EC%B1%97-%EC%84%B8%EC%85%98">대화형 AI로 바꾼 데이터 협업: 데이터 활용의 장벽을 없애다</a></li>
<li><a href="#%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%A7%88%EC%9D%98%EC%9D%91%EB%8B%B5-%EC%84%B8%EC%85%98">네이버에게 묻고, 네이버가 답하다.</a></li>
</ul>
<p>세션 하나당 시간이 sk보다 길어서 더 자세한 배경이나 구현 방식 설명을 들을 수 있어 잘 모르는 개념이 있는 발표여도 맥락을 따라갈 수 있어 좋았다. FE, BE와 직접 연계된 주제는 없었지만, AI 주제뿐만 아니라 DevOps 주제들도 있어 조금은 다양한 느낌이었다. 키노트에서 발표한 내용을 각 세션을 통해 구체적으로 들을 수 있어 하나의 유기적인 컨텐츠라는 느낌을 받았다. 특히 검색이나 쇼핑 경험의 개인화가 궁금했는데, personA 세션에서 그걸 구현한 실무자들의 발표를 들을 수 있었다. 네이버의 다양한 서비스들에서 수집한 사용자의 행동 로그를 네이버만의 메모리로 축적하는 방식이 인상적이었다. 네이버 검색 시스템뿐만 아니라 음악, 영상, 쇼핑, 카페 등 이미 구축된 다양한 기반이 있다는 게 앞으로 AI를 활용해 더 개인화된 경험을 제공할 수 있는 네이버의 큰 무기라는 게 새삼 놀라웠다.</p>
<p>또 네이버 컨퍼런스의 장점은 세션 전에 미리 발표 장표를 모두 제공해서 구체적으로 어떤 내용을 다룰지 미리 알고 가장 관심 있는 주제를 선택할 수 있단 점이었다. 미리 장표를 읽어본 덕에 이해하기도 더 수월했다. (세션 자료는 <a href="https://dan.naver.com/25/sessions#DAY2">홈페이지</a>에서 각 주제를 클릭하면 pdf 형태로 모두 다운받을 수 있다.)</p>
<h3 id="로그-파이프라인-세션">로그 파이프라인 세션</h3>
<p>네이버의 수많은 서비스들에서 사용자의 행동 관련한 로그, 그리고 또 다른 로그들도 있겠지? 그런 전사적 차원의 로그를 수집하는 체계를 개선한 이야기였다. 기존 체계의 문제점 네가지와 그것을 어떻게 극복했는지를 구체적으로 공유해주셨다.</p>
<p>가장 이해되고 기억에 남는 건 아래 두가지다.</p>
<ol>
<li><p>낮/새벽 트래픽의 차이 (-낮/새벽을 임의로 구분하는 게 아니라, 트래픽이 많은/적은 시간을 구별)</p>
<p> 피크 시간 트래픽을 기준(주로 낮시간)으로 넉넉한 규모의 클러스터를 마련해두었는데, 이는 트래픽이 적은 시간에는 당연히 리소스가 많이 남아 비용 비효율적이다. 문제 해결을 위해 <strong>피크 시간 트래픽 중 일부(실시간으로 처리하지 않아도 괜찮은 데이터)를 피크가 아닌 시간대에 처리할 수 있도록 변경</strong>했다. </p>
<p> &#39;처리 중단&#39;을 허용하는지 여부를 저장할 옵션을 추가해, 클러스터가 BackPressure 상태가 될 경우 해당 옵션 값에 따라 다르게 처리하는 식이다. 처리 중단을 허용한 데이터일 경우, 백프레셔가 활성화된 상태에서는 후에 처리하도록 별도 토픽에 쌓아두었다고 한다. (백프레셔 상태는 뒷단의 컴포넌트별 부하를 바탕으로 판단하며, 후 처리를 위해 별도로 쌓아둔 데이터가 시간 안에 처리되지 못할 경우엔 담당자/담당팀?에게 연락이 가는 방식으로 체계를 구축했다고 이해했다.)</p>
</li>
<li><p>모든 로그를 공평하게 처리해 장애 발생 등 비상시에 중요한 로그와 덜 중요한 로그가 모두 지연되는 문제 -&gt; 중요도에 따라 우선순위 구별</p>
<p> 클러스터가 Mayday 상태가 될 경우, 우선순위별로 데이터를 차등 처리하도록 조치. Mayday 상태가 해제되면 기존과 동일하게 처리.</p>
</li>
</ol>
<p>DB 스키마를 설계할 때나 이런 체계를 구축할 때, 적은 추가로 핵심적인 문제를 해결할 수 있도록 하는 것이 중요한 역량임을 느끼고 있었는데, 이 세션을 들으면서 또 그 생각이 떠올랐다. 이런 클러스터 등의 개념은 잘 몰라서 잘못 이해한 걸 수도 있겠지만..</p>
<h3 id="페르소나-세션">페르소나 세션</h3>
<p>클릭과 같은 사용자의 행동 로그를 의도와 맥락이 담긴 정보로 해석해서, 사용자를 더 깊이 이해한 PersonA를 정의한다는 것이 흥미로웠다. 또 단순히 사용자가 관심 갖는 주제를 파악하는 것뿐만 아니라 사용자가 정보를 어떻게 연결하고 파고들어 탐색하는지 그 패턴까지 분석해 활용한다는 것이 재밌었다.</p>
<p>사용자의 행동이 담긴 Raw Data를 바탕으로 ShortTerm Memory, LongTerm Memory를 구성한다. 이 과정에서는 사용자의 액션을 수집하고 메모리화하기 위한 자체 플랫폼인 Dexter를 활용한다. 그렇게 수집한 메모리를 활용해서 PEP(Personalized Exploration Partner)라는 AI 에이전트 시스템이 결론적으로 사용자에게 개인화된 정보를 제공하는 플로우인 것 같다. 이 에이전트는 사용자의 명시적 요청 없이도 상황과 맥락을 파악해 프로-액티브하게,, 선제적으로 제안하고 추천하는 에이전트다.</p>
<p>사용자가 뉴스 기사를 보고 있는 상황에서 이 에이전트는 다음과 같은 역할을 한다.</p>
<p align="center">
  <img src="https://velog.velcdn.com/images/ekil_like/post/cb6053f0-81fa-402a-ade5-5291ece88531/image.png">
  발표 자료에서 캡처!
</p>


<ul>
<li>우선 컨텐츠(뉴스 기사)의 주요 엔터티를 추출하고, 네이버의 &#39;연관 검색어 DB&#39;와 메인스트림 페르소나(미리 정의해놓은 대표적인 페르소나로 이해함. 이 부분은 개인별 메모리 활용으로 나아갈 예정)를 활용해 &quot;연관 주제&quot;를 생성한다.</li>
<li>연관 주제를 쿼리로 문서를 검색하고(RAG), 주제별 대표 컨텐츠 및 보조 컨텐츠를 작성한다.</li>
<li>대표/보조 컨텐츠를 바탕으로 연관 주제별로 요약 정보창을 생성한다.</li>
<li>현재 컨텐츠의 연관 주제와 사용자 메모리 간 유사도 매트릭스를 내부 알고리즘을 바탕으로 계산하고, 인기도 등을 활용해 최종 스코어를 계산해 최종적으로 사용자에게 보여줄 컨텐츠를 정한다. 이 정보는 사용자가 보고 있는 화면에 &#39;캔버스&#39;라는 이름의 UI를 통해 노출된다.</li>
</ul>
<p>나는 이렇게 이미 가지고 있는 데이터를 잘!! 활용해서 뭔가 유의미한 인사이트를 제공하는 게 너무 재밌다. 이런 플로우를 만들기까지 막히는 부분도 많으셨겠지만 재밌었을 것 같다.. 앞으로 네이버를 쓰면서 이런 AI 피처들이 나올 때 더 관심을 갖고 보게 될 것 같다.</p>
<h3 id="케플러-챗-세션">케플러 챗 세션</h3>
<p>이 세션의 내용이 더 머리에 잘 들어오고 기억에 남는 이유는 나의 현재 문제와도 닿아있기 때문이리라.. </p>
<p>회사에서 영업팀이 실제 사용자 데이터를 확인하고 그걸 활용해서 전략을 짜고 싶단 요청을 했었다. 그것만을 위해서 새로운 관리자 서비스를 개발하거나 하는 건 우리 인력 수준에서 불가능한 일이었다. (리소스 낭비..!) 그래서 어떻게 해결할 수 있을까 고민했었는데, 두가지 안을 냈었다.</p>
<p>첫번째는 사용 중인 Supabase의 SQL Editor에서 영업팀이 원하는 데이터를 뽑아낼 SQL 쿼리문을 작성하고 개발팀에서 직접 (매주..) 실행하고, 쿼리 결과 데이터를 뽑아 공유 드라이브에 업로드하는 방식이었다. (실제로 3주 정도 했다..)</p>
<p>이 방식의 장점은 아무런 체계를 구축할 필요 없이 즉각적으로 적용할 수 있단 것이다. 단점은 영업팀이 원하는 데이터가 변경/추가될 경우 새로운 쿼리문을 작성해야 한다는 것, 그리고 더 심각한 것은 매주 이를 실행해서 공유해야 한다는 것이다. (물론 이 부분은 이 체계를 계속 유지할 거라면 자동화할 방법을 찾으려 했었다.) 게다가 실시간으로 정보 확인하기도 어렵다.</p>
<p>두번째는 데이터 시각화 툴 Grafana를 이용하는 것이다. Grafana는 다양한 DB와의 연동이 쉽고 자유롭다. Supabase도 조금만 살펴보면 금방 연동할 수 있었다! 위에서 작성한 SQL 쿼리문을 추가하고 적절한 시각화 방식(막대 그래프나 원 그래프, 표 등 데이터 특성에 맞게)만 선택해 저장 및 공유하면, 영업팀에서 실시간 정보를 확인할 수 있으며 개발팀에서는 추가적인 공수가 들지 않는다. 다른 데이터를 원한다는 요청이 들어올 경우에만 쿼리를 새로 작성해서 추가해주면 된다. 그래서 지금은 이 체계를 사용 중이다.</p>
<p>이 경험이 이 세션을 더 집중해서 듣게 만들었다. 네이버와 같이 더 큰 규모의 회사는 직원/부서별 역할이 명확히 나누어져 있어서 그런지 많은 부서에서 데이터 조회 요청을 많이 하나보다. 이 문제를 해결하기 위해 해당 팀에서는 원래는 내가 지금 Grafana를 활용하는 것과 비슷하게 대시보드 형식의 &#39;케플러 스튜디오&#39;를 먼저 개발했다고 한다. 그러나 SQL이 익숙하지 않은 팀원들에게는 여전히 데이터 활용에서 장벽이 있었다. 그래서 대화를 기반으로 하여 사용자가 자율적으로 데이터를 뽑아낼 수 있는 방식으로 전환한 것이 &#39;케플러 챗&#39;이다.</p>
<p>우리가 흔히 알고 있는 챗 인터페이스를 활용한 것이다. 그래서 어떻게 활용하는 건지 상상하기 쉬웠다. 사용자가 입력한 자연어를 기반으로 SQL 쿼리를 생성하고, 원하면 실행까지 해주고, 쿼리 결과를 차트로 시각화하고 인사이트까지 제공하도록 개발했다고 한다. (와웅!) 이 피처 자체도 너무나 매력적이었다.</p>
<p>구체적으로 어떻게 구현했으며 어떻게 발전해 왔는지(&amp; 앞으로 어떻게 발전시킬 것인지)를 세션을 통해 소개해주셨다.</p>
<p>인상적인 부분은, 컨플루언스 연동을 통해 사내 문서를 임베딩하여 내부 약어에 대한 맥락적인 이해를 지원한 것이었다. 우리만해도 기능의 용어를 변경하면서 테이블명이 이전 기능 이름을 아직도 사용하고 있는데, 이런 사정을 담은 내부 문서 없이는 제대로된 쿼리가 불가능할 것이다. 이런 맥락 데이터를 제공할 방법을 찾았다는 게 오호! 포인트였다.</p>
<p>쿼리의 실행은 Trino를 사용했다고 한다. Trino는 처음 들어서 좀 더 살펴보려 한다.</p>
<p>또 대기업 모먼트를 느낀 부분은, 당연히 사용자별로 데이터 접근 권한의 수준이 다를 것인데, 인증 시스템을 구현하여 쿼리하는 데이터의 수준도 그 사용자별 권한을 바탕으로 가능하게 구현했다는 부분이었다. 또 그런 접근 기록을 자동으로 로그가 남게 해서 투명성도 확보했다고 한다.</p>
<p>피처 자체만 봤을 때도 큰 효익이 느껴질 것 같은데, 이를 구현하는 과정에서 이런 디테일들까지 신경썼다는 점이 매력적이어서 재밌게 들었다. 네이버 웹툰 서비스 팀에서 시작했다는데, 전사 차원의 표준화까지를 목표로 한다고 한다. 도전적인 과제겠지만 재밌고 뿌듯할 것 같다.</p>
<p>PersonA 세션에서 다룬 내용이 네이버 &#39;사용자&#39;의 데이터를 잘 활용하는 창의적인 방법이라면, 케플러 챗 세션에서 다룬 내용은 네이버 구성원, 팀원들이 네이버가 가진 데이터를 잘 활용할 수 있도록 돕는 창의적인 방법이었다. 그 과정에서 컨플루언스와 같은 기존의 데이터도 야무지게 활용한 것이 인상적이었다. 나도 역량이 된다면 이런 걸 개발해보고 싶기도 하고, 우리도 이런 케플러 챗이 있다면 좋겠단 생각에 부럽기도 했다. (아직 사용자도 팀원도 적어서 &#39;제대로&#39; 활용하려면 갈길이 멀겠지만..)</p>
<h3 id="네이버-질의응답-세션">네이버 질의응답 세션</h3>
<p>네이버를 대표하는 사람들은 아니고, 컨퍼런스 기간동안 세션을 진행한 세명의 연사들이 나와서 메인 홀에서 사람들이 적어둔 질문, 그리고 실시간으로 올라오는 질문들에 답해주는 세션이었다. 영상으로 공유되지 않을 세션이라고 해서 들어보았고, 재밌는 시간이었다. 이 세션장의 청중들은 이미 네이버 구성원들이 꽤 있는 걸로 보였다. 그래서 자기들만 아는 이야기를 하기도 했지만, 뭔가 그 분위기, 유대감이 좋게 보였다.</p>
<p>네이버는 뭐 부문장 같은 직책의 구성원에게도 &#39;~님&#39; 호칭을 쓰나보다. 굉장히 수평적인 문화임을 강조했고, 뭐 구체적인 내용이지만 상해보험 같은 것도 해주고,, 아침 점심 저녁도 주고,, 한다고 한다.</p>
<p>아쉬운 점은 사옥이 정자역에서 도보 15분 거리라는 점..이라고.. ㅋㅋㅋㅋㅋ 정말 부럽다! 아 주차 자리도 좀 부족하다곤 했다.</p>
<p>팀네이버 팀원들이 원하는 신입 인재상은 &#39;적극성&#39;이 있는 사람. 진짜 자기가 그 분야에 관심이 많아서 새로운 소식, 정보도 많이 알고, 이것저것 써보고 들여다본 경험이 있으며, 적극적으로 먼저 자기가 무엇을 할지 또는 무엇을 모르는지 등을 이야기하는 사람이면 좋겠다고 한다.</p>
<p>인턴을 받아본 경험에 비추어 보았을 때, 맞아, 저런 신입이 들어오면 예뻐보이고 같이 일할 맛이 날 것 같다.</p>
<p>세션을 들으며, 그리고 듣고 나서, &quot;진짜&quot;가 되어야겠단 생각을 했다.</p>
<p>이력서를 어떻게 쓰고 포트폴리오를 어떻게 구성하고, 그런 것도 나를 드러내는 중요한 역량임은 분명하다. 그러나, 그런 도구적인 측면은 좀 부수적인 역량이랄까. 결국 같이 일할 사람을 뽑는다는 목적 하에서는 바로 일에 투입해도 잘 녹아들어 자신에게 기대되는 퍼포먼스를 낼 수 있는 사람을 선별해야 하기 때문에, 그게 가능한 진짜 엔지니어가 되는 것이 가장 중요한 것이다.</p>
<p>이렇게 또 당장 무엇을 해야 하고 뭘 중요시 여겨야 하는지 깨달음을 얻고 돌아온 유익한 시간이었다. </p>
<p>여담으로는, 타이밍이 좋아서 코엑스 앞에서 고메잇강남 2? 라는 행사도 하고 있었는데, 거기서 먹은 김치전이 참 맛있었다. 요거트월드도 후후..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK AI SUMMIT 2025] AI NOW & NEXT 방문 후기]]></title>
            <link>https://velog.io/@ekil_like/SK-AI-SUMMIT-2025-AI-NOW-NEXT-%EB%B0%A9%EB%AC%B8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@ekil_like/SK-AI-SUMMIT-2025-AI-NOW-NEXT-%EB%B0%A9%EB%AC%B8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Tue, 04 Nov 2025 14:32:00 GMT</pubDate>
            <description><![CDATA[<p>11월 3일 4일 양일간 코엑스에서 열린 <strong>SK AI SUMMIT 2025 &quot;AI NOW &amp; NEXT&quot;</strong>에 방문해 키노트, 세션을 듣고 왔다. 감사하게도 회사에서 동료 개발자분들과 함께 다녀올 수 있게 해주었다.</p>
<p>그랜드볼룸 1층~3층을 사용하고, 온오프라인으로 약 3만 5천명이 신청한(출처: 최태원 회장님 키노트) 큰 행사여서 신선한 경험이었다.</p>
<p><img src="https://velog.velcdn.com/images/ekil_like/post/3486218e-32f6-4f17-8b96-19ff7e07ed86/image.jpeg" alt="SK AI SUMMIT 로고"></p>
<p>궁금했던 키노트와 세션을 들으며 내용을 이해하고 소화하려 노력했다. 제목부터 AI가 들어간 만큼, AI 산업과 지식 분야의 다양한 이야기를 들을 수 있었다.</p>
<hr>
<p>나의 참가 목표는 다음과 같았다.</p>
<ol>
<li>AI 동향 파악</li>
<li>큰 기업 실무자들의 발표로부터 인사이트 얻기</li>
<li>실무나 공부에 적용할 기술 접하기</li>
</ol>
<hr>
<p><img src="https://velog.velcdn.com/images/ekil_like/post/62f0405b-ae33-4501-849d-57798cd91fc8/image.jpeg" alt="엔비디아 키노트 현장">
<img src="https://velog.velcdn.com/images/ekil_like/post/0054f688-bf1f-46fb-b263-1eb364c3c95f/image.jpeg" alt="카카오 정신아 대표 키노트 현장"></p>
<hr>
<p> 우선 목표였던 AI 동향 파악의 경우, 현재 AgenticAI가 정말 큰 화두이며, 소버린 AI라는 국가적 차원의 AI 기반 마련에도 많은 관심이 기울어있음을 피부로 느낄 수 있었다. AWS 온라인 교육을 통해 Agentic AI를 개발하는 것의 기본 개념을 배웠던 것이 세션들을 이해하는 데 도움이 되었다. 다양한 툴/LLM/AI를 융합해 잘 오케스트레이션 해서 다중 도메인의 작업을 처리하는 Agentic AI를 개발하는 것..! 그리고 그러한 Agentic AI는 지금까지의 다른 인터페이스나 생성형 AI와 달리 더욱 능동적이고, 사용자가 질문하기 전에 먼저 제안할 줄 아는 개념이라는 것. 이것이 내가 이해한 핵심이다.</p>
<p> 큰 기업의 실무자들의 발표를 들으면서는, 발표를 잘하는 역량 역시 개인차가 심한 부분임을 느꼈고, 기술적 깊이가 아무리 깊어도 청중이 무엇을 기대하는지 파악하고 발표를 준비해야 함을 느꼈다. 또 발표자는 혼자 발표 시간동안 발화하는데, 말은 생각의 창이라고 하던가,, 그 사람의 인간성이 생각보다 보이는 게 신기했다. 여러 발표자를 보다보니 서로 비교가 되어 더 드러나 보였나보다. 기술적으로 뛰어나고 말을 잘하는 것만큼, 그 사람의 인간성도 중요해보였다. 사람을 존중하는 가치관을 가지고 기술을 대하는 것과 같은.</p>
<p> 그외에는, SK라는 기업의 존재 목적은 ‘구성원들의 행복’이라는 장표를 봤는데, 그래서 하이닉스도 충성도가 높은 것인가,, 저게 정말이라면 나도 들어가고 싶은 기업이다, 생각했다. 회장님 키노트에 따르면 SK는 ‘효율성’에 초점을 맞추어, 효율적인 AI를 제공하고자 한다고 한다. AI 기술 개발에 매우 진심인 기업 분위기를 여러 세션을 통해 느낄 수 있었다. 이 기업의 미래가 어떻게 될지도 궁금해졌다.</p>
<p> 안타깝게도 내가 당장 실무/공부에 적용할 기술을 접하진 못했다. 다만, 카카오의 playMCP를 살펴보았는데 개념 자체는 좋으나 아직 실제로 활용해 나만의 툴을 만들 정도는 아닌 것 같았고, MCP의 등장 배경과 그 개념을 좀 제대로 정리하고 싶어졌고, playMCP를 통하지 않고 직접 여러 툴을 MCP로 연결해서 나만의 무언가를 만들고 싶어졌다. 클로드에서는 클로드 스킬을 꼭 써보라 했으니, 나중에 볼 영상에 넣어둔 클로드 스킬 영상을 시청하고 실제 써볼 방법을 모색해봐야겠다.</p>
<hr>
<p> 진로에 대해서는.. 지금 AI가 붐이라고 해서 그 유행을 좇아 AI 산업에서 내가 (지금 당장부터) 기여할 수 있도록 “공부”를 할 건 아닌 것 같다. 애초에 이 분야에 내가 현재 쌓은 지식이나 타고난 역량이 부족한 듯하고, 개발자를 선택한 것도 이렇게 붐이 일어서가 아니라 내가 하고 싶은 일이었기 때문이기에, 이 선택을 좀 더 밀고나가고자 한다. 당장에는 AI 개발이 더 밝은 미래로 이어질 것처럼 보여 조바심이 나기도 하겠지만, 변하지 않을 기술과 개념을 습득하는 데 집중하고, 내가 하고 싶은 일을 꾸준히 할 수 있도록 하는 기반을 마련해야겠다. 보상이 많으면 좋겠지만 보상이 적더라도 하고 싶은 일을 뚝심있게 해서 전문성을 갖추어 그 가치를 인정받는 사람이 되고 싶다.</p>
<p><img src="https://velog.velcdn.com/images/ekil_like/post/183d096f-2b31-42d0-bb53-287edd9f9b8b/image.jpeg" alt="SK로고"></p>
]]></description>
        </item>
    </channel>
</rss>