<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hwan_lee.log</title>
        <link>https://velog.io/</link>
        <description>늙고병듦.</description>
        <lastBuildDate>Tue, 02 Sep 2025 01:31:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hwan_lee.log</title>
            <url>https://velog.velcdn.com/images/hwan_lee/profile/36660471-9c14-4c73-a92b-798ccf0383e5/image.jfif</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hwan_lee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hwan_lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Python Websockets 오류해결 - BaseEventLoop.create_connection() got an unexpected keyword argument 'extra_headers']]></title>
            <link>https://velog.io/@hwan_lee/Python-Websockets-%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-BaseEventLoop.createconnection-got-an-unexpected-keyword-argument-extraheaders</link>
            <guid>https://velog.io/@hwan_lee/Python-Websockets-%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-BaseEventLoop.createconnection-got-an-unexpected-keyword-argument-extraheaders</guid>
            <pubDate>Tue, 02 Sep 2025 01:31:02 GMT</pubDate>
            <description><![CDATA[<h1 id="평화롭게-파이썬-웹소켓-튜닝하던중에-문제가-발생">평화롭게 파이썬 웹소켓 튜닝하던중에 문제가 발생</h1>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/ead488c1-11fa-4440-9d9d-43b215a1fe84/image.png" alt=""></p>
<h1 id="직감적으로-절대-코드-문제는-아니고-바로-구글링">직감적으로 절대 코드 문제는 아니고 바로 구글링</h1>
<blockquote>
<p><a href="https://github.com/pytr-org/pytr/issues/167">https://github.com/pytr-org/pytr/issues/167</a></p>
</blockquote>
<h1 id="나처럼-비슷한-증상이-나타나는-사람들이-많다는-것을-확인">나처럼 비슷한 증상이 나타나는 사람들이 많다는 것을 확인</h1>
<pre><code>Hi @timherz86
Same error, I solved doing this:

`pip uninstall websockets

pip install websockets==10.1`

With this version I dont have errors. Hope this helps you.</code></pre><p>이렇게 해결했다고 함.</p>
<blockquote>
<p>나도 이렇게 하니 해결됨
<del>깃허브와 스택 오버플로우에는 없는 오류가 없다</del></p>
</blockquote>
<h1 id="결론">결론</h1>
<ul>
<li><ol>
<li><code>pip uninstall websockets</code></li>
</ol>
</li>
<li><ol start="2">
<li><code>pip install websockets==10.1</code></li>
</ol>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[어셈블리로 웹 만들기]]></title>
            <link>https://velog.io/@hwan_lee/%EC%96%B4%EC%85%88%EB%B8%94%EB%A6%AC%EB%A1%9C-%EC%9B%B9-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hwan_lee/%EC%96%B4%EC%85%88%EB%B8%94%EB%A6%AC%EB%A1%9C-%EC%9B%B9-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 23 Jul 2025 13:16:19 GMT</pubDate>
            <description><![CDATA[<hr>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/21f5ab1a-fcab-4f12-8481-aa60ce893064/image.png" alt=""></p>
<h2 id="webassembly-왜-필요할까요-🤔">WebAssembly, 왜 필요할까요? 🤔</h2>
<p>여러분, 웹이 언제부터 이렇게 진화했죠? 이제 그냥 정보만 띄우는 화면이 아니잖아요. 복잡한 프로그램이 돌아가는 <strong>플랫폼</strong>이 됐단 말이죠.</p>
<p>근데 웹의 메인 언어인 <strong>JavaScript</strong>가 말입니다, 좀 한계가 있었어요. 특히 막 3D 게임 돌리고, 영상 편집하고, 사진 보정 같은 <strong>고성능 작업</strong>에선 속도가 발목을 잡았단 말이죠? 답답해 죽는 줄 알았어요.</p>
<p>그래서 **WebAssembly(Wasm)**가 짠! 하고 등장한 겁니다. 얘가 뭐냐면, 웹 브라우저에서 돌아가는 <strong>새로운 형태의 코드</strong>예요. 자바스크립트랑 같이 손잡고 일하면서, 고성능 작업은 얘가 처리하는 거죠. 덕분에 웹 앱 성능이 그냥 <strong>날아다니게</strong> 된 겁니다!</p>
<hr>
<h2 id="webassembly-어떤-점이-매력적일까요-✨">WebAssembly, 어떤 점이 매력적일까요? ✨</h2>
<h3 id="1-엄청난-속도-🚀">1. 엄청난 속도! 🚀</h3>
<p>Wasm은 말이죠, 브라우저에서 거의 <strong>네이티브 앱 수준</strong>으로 쌩쌩 달려요. 이미 컴파일된 <strong>바이너리 파일</strong>이라 읽고 실행하는 데 시간이 적게 들고, 컴퓨터 자원도 효율적으로 쓴단 말이죠. 복잡한 계산이나 막 그래픽 처리 같은 거 할 때 얘가 <strong>핵심</strong>입니다!</p>
<h3 id="2-다양한-언어-지원-🌐">2. 다양한 언어 지원! 🌐</h3>
<p>이게 진짜 대박인데, C, C++, Rust, Go 같은 <strong>여러 프로그래밍 언어</strong>로 짠 코드를 Wasm으로 바꿔버릴 수 있어요. 그러니까 기존에 만들어진 <strong>고성능 라이브러리나 프로그램</strong>들을 웹에서 그대로 쓸 수 있게 된 거죠. 웹 개발의 문이 <strong>활짝 열리는</strong> 순간입니다!</p>
<h3 id="3-보안성-🔒">3. 보안성! 🔒</h3>
<p>Wasm은 <strong>샌드박스</strong>라는 안전한 울타리 안에서만 돌아가요. 외부에서 이상한 공격 들어와도 시스템을 <strong>딱! 보호</strong>해주는 거죠. 웹의 <strong>개방성</strong>이랑 <strong>보안</strong>이라는 두 마리 토끼를 동시에 잡는 겁니다! 기가 막히죠?</p>
<h3 id="4-경량화-📦">4. 경량화! 📦</h3>
<p>Wasm 파일은 바이너리 형태라서 텍스트로 된 자바스크립트 파일보다 <strong>크기가 훨씬 작아요</strong>. 이게 뭐냐면, 웹 앱이 <strong>더 빨리 로딩</strong>된다는 뜻입니다. 기다리는 시간 줄어드니 사용자들도 행복해지겠죠?</p>
<hr>
<h2 id="webassembly-어디에-활용될까요-💡">WebAssembly, 어디에 활용될까요? 💡</h2>
<ul>
<li><strong>고성능 게임:</strong> 웹에서 3D 게임 엔진 돌리고 막 복잡한 물리 시뮬레이션까지 가능해요. 게임이 훨씬 <strong>생동감</strong> 있어지는 거죠.</li>
<li><strong>그래픽 및 멀티미디어 처리:</strong> 웹에서 이미지 편집, 비디오 인코딩/디코딩 같은 <strong>무거운 작업</strong>도 이제 척척 해낼 수 있습니다.</li>
<li><strong>CAD/CAM 및 과학 시뮬레이션:</strong> 막 복잡한 계산 필요한 전문 소프트웨어도 웹에서 돌릴 수 있게 됐단 말이죠. <strong>가능성이 무궁무진</strong>합니다.</li>
<li><strong>블록체인:</strong> 분산 앱(dApp)의 <strong>성능을 팍팍 올려주는</strong> 데도 기여할 수 있어요.</li>
</ul>
<hr>
<h2 id="webassembly-간단-실습-c에서-wasm으로-🧑💻">WebAssembly 간단 실습: C에서 Wasm으로! 🧑‍💻</h2>
<p>자자, 이제 말로만 듣던 WebAssembly를 <strong>직접 손으로 만져볼 시간</strong>입니다! 가장 쉬운 예제로 C 언어로 짠 함수를 WebAssembly로 바꿔서 웹에서 실행해볼 거예요.</p>
<blockquote>
<p>급한분들은 위한 실습 레파지스토리: <a href="https://github.com/HwanLee-0321/webAssembly">실습 레퍼지스토리</a></p>
</blockquote>
<p><strong>준비물:</strong></p>
<ul>
<li>손</li>
<li><strong>이상적이 환경은 리눅스라고 합니다. 윈도우에서 하실 분들은 오류좀 겪을지도 모르겠네요</strong></li>
<li>Node.js (npm 포함)</li>
<li>Emscripten (C/C++ 코드를 WebAssembly로 바꿔주는 마법 도구)</li>
</ul>
<h3 id="1단계-emscripten-설치">1단계: Emscripten 설치</h3>
<p>Emscripten은 Wasm 개발의 <strong>치트키</strong> 같은 겁니다. 아래 명령어로 설치할 수 있어요.</p>
<p>설치하는 데 시간이 좀 걸릴 수도 있으니, 뭐 맛있는 거라도 하나 물고 기다리세요.</p>
<blockquote>
<p><a href="https://github.com/emscripten-core/emsdk.git">emsdk GitHub 사이트</a>
<img src="https://velog.velcdn.com/images/hwan_lee/post/e271aef2-001d-4c2b-ac69-958d8a8680c6/image.png" alt=""></p>
</blockquote>
<pre><code class="language-bash">git clone https://github.com/emscripten-core/emsdk.git  # gitHub 레파지스토리 클론
cd emsdk  # emsdk 디렉토리로 이동
emsdk install latest  # emsdk 설치 1
emsdk activate latest  # emsdk 설치 2
.\emsdk_env.bat   # 설치한 emsdk 컴파일러 실행</code></pre>
<h3 id="2단계-c-코드-작성-addc">2단계: C 코드 작성 (add.c)</h3>
<p>아주 간단한 덧셈 함수를 C 언어로 짜볼 겁니다. <code>add.c</code> 파일을 만들고 아래 코드를 복붙하세요.
파일 경로는 emsdk 디렉토리의 동위 디렉토리 입니다.</p>
<p>그래도 말귀를 못 알아듣는 우리 코딩 새싹이이들을 위해서 굳이 설명을 더 해주자면</p>
<p><code>C://A/B/emsdk</code> 자 이렇게 여러분들의 emsdk 디렉토리가 있다고 칩시다. 
<code>C://A/B/add.c</code> 그럼 이렇게 emsdk 디렉토리와 <strong>같은</strong>위치에 있어야합니다.</p>
<blockquote>
<p>굳이 상관없긴한데 파일 구조 관리가 이게 더 편하니 그냥 따라하세요. 반박시 님말이 최고. 너 튜링상.</p>
</blockquote>
<p>그리고 아까 생성했던 add.c에 뭐 이런 함수 두개를 적어봅니다.</p>
<pre><code class="language-c">// add.c
#include &lt;stdio.h&gt;

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}</code></pre>
<p>이 코드는 두 숫자를 더하거나 곱하는, 뭐 아주 평범한 함수 두 개예요.
이것도 이해못하시면 이 글을 왜 읽는거죠?</p>
<p>빠져나가서 유튜브에 C언어 기초 강의라도 듣고 오세요.</p>
<h3 id="3단계-c-코드를-webassembly로-컴파일">3단계: C 코드를 WebAssembly로 컴파일</h3>
<p>이제 Emscripten을 써서 <code>add.c</code> 파일을 WebAssembly 모듈이랑 자바스크립트 래퍼 파일로 바꿔줄 거예요.
자 자세한 원리는 생각하지 마시고.</p>
<pre><code class="language-bash">emcc add.c -o add.js -s EXPORTED_FUNCTIONS=&quot;[&#39;_add&#39;, &#39;_multiply&#39;]&quot; -s EXPORT_ES6=1 -s WASM=1</code></pre>
<p><code>emcc add.c</code>: <code>add.c</code> 파일을 <strong>컴파일</strong>합니다.
<code>-o add.js</code>: 출력 파일 이름을 <code>add.js</code>로 정해주는 거예요. 이 파일이 나중에 WebAssembly 모듈을 불러오고 써먹을 수 있게 해줍니다.
<code>-s EXPORTED_FUNCTIONS=&quot;[&#39;_add&#39;, &#39;_multiply&#39;]&quot;</code>: C 코드에 있는 <code>add</code>랑 <code>multiply</code> 함수를 자바스크립트에서 <strong>호출할 수 있게 내보내는</strong> 겁니다. 함수 이름 앞에 언더바(<code>_</code>) 붙이는 거 <strong>잊지 마세요</strong>!
<code>-s EXPORT_ES6=1</code>: ES6 모듈 형식으로 내보내는 옵션입니다.
<code>-s WASM=1</code>: 이걸 붙여줘야 WebAssembly로 컴파일하라고 명령하는 거예요.</p>
<p>이 명령어 뙇 실행하면 <code>add.js</code>랑 <code>add.wasm</code> 파일 두 개가 생길 겁니다.</p>
<blockquote>
<p>정상 적용된 모습이고요.
참고하실 분들은 해주세요.
(LICENSE 파일이랑 README.md파일을 없어도 무관합니다)
<img src="https://velog.velcdn.com/images/hwan_lee/post/ea61a6a2-b4e3-4f04-b929-038f5e17771b/image.png" alt=""></p>
</blockquote>
<h3 id="4단계-웹-페이지에서-webassembly-사용-indexhtml">4단계: 웹 페이지에서 WebAssembly 사용 (index.html)</h3>
<p>이제 이 WebAssembly 모듈을 웹 페이지에서 불러와서 돌려봐야겠죠? <code>index.html</code> 파일을 만들고 아래 내용을 복붙하세요.</p>
<blockquote>
<p>최종 파일 구조
<img src="https://velog.velcdn.com/images/hwan_lee/post/3322b41f-820e-4382-808e-52222f776938/image.png" alt=""></p>
</blockquote>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;WebAssembly Test&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;WebAssembly Add Example&lt;/h1&gt;
    &lt;p&gt;Result of add(5, 3): &lt;span id=&quot;addResult&quot;&gt;&lt;/span&gt;&lt;/p&gt;
    &lt;p&gt;Result of multiply(5, 3): &lt;span id=&quot;multiplyResult&quot;&gt;&lt;/span&gt;&lt;/p&gt;

    &lt;script type=&quot;module&quot;&gt;
        // add.js 파일이 WebAssembly 모듈을 로드하고 초기화하는 역할을 해요.
        // Module 객체는 Emscripten이 만들어주는 전역 객체고요.
        // 우리는 Module._add랑 Module._multiply를 통해 C 함수에 접근하는 겁니다.
        import Module from &#39;./add.js&#39;;

        Module.onRuntimeInitialized = () =&gt; {
            const addResult = Module._add(5, 3);
            document.getElementById(&#39;addResult&#39;).textContent = addResult;
            console.log(&quot;5 + 3 =&quot;, addResult);

            const multiplyResult = Module._multiply(5, 3);
            document.getElementById(&#39;multiplyResult&#39;).textContent = multiplyResult;
            console.log(&quot;5 * 3 =&quot;, multiplyResult);
        };
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>이 HTML 파일은 <code>add.js</code>를 모듈로 가져와 쓰는 거예요. <code>Module.onRuntimeInitialized</code>는 Wasm 모듈이 준비되면 실행되는 부분입니다. 여기에서 C 함수인 <code>_add</code>와 <code>_multiply</code>를 호출해서 그 결과를 웹 페이지에 띄워줄 겁니다.</p>
<h3 id="5단계-웹-서버-실행-및-확인">5단계: 웹 서버 실행 및 확인</h3>
<p>웹 페이지를 열려면 우리 컴퓨터에 웹 서버가 돌아가고 있어야 해요. 간단하게 Node.js의 <code>http-server</code>를 쓸 수 있습니다.</p>
<pre><code class="language-bash">npm install -g http-server
http-server</code></pre>
<p>명령어 실행하고 브라우저에서 <code>http://localhost:8080</code> (아니면 화면에 뜨는 다른 주소)으로 들어가 보세요. <code>add(5, 3)</code>이랑 <code>multiply(5, 3)</code> 결과가 페이지에 <strong>뙇!</strong> 하고 나타나는 걸 확인할 수 있을 겁니다! 개발자 도구(F12) 콘솔에서도 결과 확인 가능해요.</p>
<hr>
<h2 id="마치며-🌟">마치며... 🌟</h2>
<p>지금까지 한 모든 뻘짓들을 저의 <a href="https://github.com/HwanLee-0321/webAssembly">깃허브</a>에 올려놨으니 알아서 참고하실 분들은 하세요.</p>
<p>WebAssembly는 웹의 가능성을 <strong>한 단계 더 업그레이드</strong> 시키는 엄청난 기술입니다. 단순히 속도만 빨라지는 게 아니라, 웹 앱의 <strong>복잡한 기능</strong>들을 구현하게 해주는 <strong>핵심 중의 핵심 기술</strong>이 될 거예요.</p>
<p>이번 간단한 실습으로 WebAssembly가 어떻게 돌아가는지 조금이나마 감을 잡으셨기를 바랍니다. 개발자 여러분들도 Wasm에 관심 가지고 <strong>새로운 웹 개발 세상</strong>을 한번 탐험해 보시는 건 어떨까요? <strong>진짜 후회 안 할 겁니다!</strong></p>
<p>혹시 WebAssembly에 대해 더 궁금한 점이 있다면, 뭐든지 물어보세요!
대답은... 나도 모르면 안 해줄거임 ㅅㄱ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[
구글이 쓰는 10배빠른 데이터 전송법]]></title>
            <link>https://velog.io/@hwan_lee/%EA%B5%AC%EA%B8%80%EC%9D%B4-%EC%93%B0%EB%8A%94-10%EB%B0%B0%EB%B9%A0%EB%A5%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%84%EC%86%A1%EB%B2%95</link>
            <guid>https://velog.io/@hwan_lee/%EA%B5%AC%EA%B8%80%EC%9D%B4-%EC%93%B0%EB%8A%94-10%EB%B0%B0%EB%B9%A0%EB%A5%B8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%84%EC%86%A1%EB%B2%95</guid>
            <pubDate>Mon, 14 Jul 2025 11:31:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hwan_lee/post/69d41b33-3453-4440-8cc3-492c4dfdc96c/image.png" alt=""></p>
<h2 id="깃허브의-trending을-확인하며">깃허브의 Trending을 확인하며</h2>
<p>깃허브의 수많은 레포지스토리 중 가장 인기 있는 레파지스토리는 무엇일까?</p>
<blockquote>
<p><a href="https://github.com/trending">GitHub트렌딩사이트</a></p>
</blockquote>
<p>다음 링크를 타고 넘어가면 깃허브의 최대 이슈 레파지스토리를 확인 가능하다. </p>
<blockquote>
<p>★개수가 기본 2000이 넘어가는 중</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/c4ae51e2-f8d5-4546-8c8b-c12c7f84599d/image.png" alt=""></p>
<h3 id="이게-사람이-할-짓인가-싶을-정도로-레파지스토리-하나하나가-살인적인-용량을-자랑한다">이게 사람이 할 짓인가 싶을 정도로 레파지스토리 하나하나가 살인적인 용량을 자랑한다.</h3>
<p>거의 대부분 구글이 만든 대형 프로젝트 아키텍쳐들이다.</p>
<h2 id="오늘은-그중에서-맨-상단-protobuf에-대해서-알아보자">오늘은 그중에서 맨 상단 protobuf에 대해서 알아보자</h2>
<blockquote>
<p><a href="https://github.com/protocolbuffers/protobuf">Protobuf GitHub사이트</a></p>
</blockquote>
<h3 id="이런-대형-프로젝트-레파지스토리의-정체를-알기위한-꿀팁">이런 대형 프로젝트 레파지스토리의 정체를 알기위한 꿀팁</h3>
<h4 id="바로-readme파일-읽어보자">바로 README파일 읽어보자!</h4>
<p>다른 이유가 아니라,
README 파일은 개발자들이 머리를 싸매고 이 프로젝트를 이해시키기위해서 발악한 결과물이다.</p>
<p>영어고 읽기 싫지만 몇번 읽어보면 적응된다.</p>
<p>그래서 정리하자면</p>
<blockquote>
<p>마이크로서비스 아키텍처(MSA)나 gRPC(Google Remote Procedure Call) 통신에서 &#39;실제&#39;쓰고 있는 코드라고 합니다.</p>
</blockquote>
<h2 id="이런-게-왜-필요한데">이런 게 왜 필요한데?</h2>
<p>대부분 컴퓨터 비전에서 </p>
<blockquote>
<p>왜 이렇게까지 해야하는가?</p>
</blockquote>
<p>처럼 필요성의 질문에는 무조건</p>
<blockquote>
<p>효율성, 편의성, 성능 때문에</p>
</blockquote>
<p>이라고 답을 하면 맞는다.</p>
<p>따라서 이 프로젝트도 그런 맥락이다.</p>
<p>이제 중요한 개념설명들을 하기전에 원치 않는 분들이 99%라고 생각합니다.</p>
<p>이제 그런 걸 기대하는 씹덕프사님들께 욕망해소의 링크를 첨부해드립니다.
<img src="https://velog.velcdn.com/images/hwan_lee/post/f8cc8d66-d413-4ae5-9fab-464b47c7e83a/image.png" alt=""></p>
<blockquote>
<p><a href="https://protobuf.dev/overview/">★☆ㅣ9@gold_pr@t●_u출_♥♡</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/6b6b2a55-2d1a-42e7-8efb-021ebc0148c8/image.png" alt="">
<del>유니짱 다이스키</del></p>
<h2 id="프로토콜-버퍼protocol-buffers-완벽-가이드-기본-개념부터-고급-아키텍처까지">프로토콜 버퍼(Protocol Buffers) 완벽 가이드: 기본 개념부터 고급 아키텍처까지</h2>
<p>현대 데이터 처리 방식은 주로 json 과 XML방식이 많이 채택되는 모습이다.</p>
<blockquote>
<p>출처: <a href="https://moldstud.com/articles/p-what-are-the-latest-trends-in-xml-development">https://moldstud.com/articles/p-what-are-the-latest-trends-in-xml-development</a></p>
</blockquote>
<p><del>와! 매우 과학적인 스택오버플로우!</del></p>
<p>그러면 프로토콜 버퍼는 몇위정도 채택률을 가질까?</p>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/1244fe00-fc74-4529-b3a9-5b8e8e5aa643/image.png" alt=""></p>
<blockquote>
<p>30분동안 아무리 찾아도 데이터 protobuf의 점유율을 조사한 결과는 없어서 그냥 Gemini돌리고 간단히 사실 유무 체크 하는 &quot;대답 재확인&quot;기능을 돌렸다. 빨간색 표시는 믿지 않는게 좋고 대충 초록색이면 신뢰가능한 정보라는 뜻. 따라서 대충 4~5위정도 Protobuf가 시장점유율을 가진다고 보면된다.</p>
</blockquote>
<p><del>와! 매우과학적인 Gemini !!</del></p>
<h3 id="1-기본-개념-프로토콜-버퍼란-무엇인가">1. 기본 개념: 프로토콜 버퍼란 무엇인가?</h3>
<p>프로토콜 버퍼(이하 Protobuf)는 구조화된 데이터를 저장하거나 교환하기 위한 형식(Format)이자 규칙(Protocol)이다. 텍스트 기반인 JSON이나 XML과 달리, Protobuf는 데이터를 *바이너리(Binary) 형식으로 직렬화하여 크기가 작고 처리 속도가 빠르다는 장점을 가진다.</p>
<blockquote>
<p>[바이너리 형식(파일)]: 0과 1로만 이루어진 이진법으로 표현된 데이터 또는 파일.</p>
</blockquote>
<p>텍스트는 문자와 숫자로 이루어져 있으며, 아스키 코드나 유니코드와 같은 문자 인코딩 방식을 사용하여 표현됩니다. </p>
<p>따라서 인코딩을 한 번 거치는 텍스트보다 당연히 0과 1로 이루어진 파일이 빠를 수 밖에 없겠죠. </p>
<p>이해가 안되신다면 
→ Python보다 C언어가 빠르고
→ C언어보다 assemble언어가 빠르고 
→ assemble보다 
→ 기계어가 빠르다</p>
<p>는처럼 생각하면 됩니다.
<img src="https://velog.velcdn.com/images/hwan_lee/post/6edccb9e-094f-45a4-8867-28cc2c910964/image.png" alt=""></p>
<blockquote>
<p>위에 사진처럼 바이너리는 읽기/쓰기 모두 문자/숫자간에 통신이 문제가 없는 반면에, 기존 텍스트 방식 json이나 XML은 읽기쓰기에 제약이 있는 모습이다.</p>
</blockquote>
<p><strong>주요 이점:</strong></p>
<ul>
<li><p><strong>성능:</strong> 바이너리 인코딩을 통해 데이터 크기와 파싱 속도를 획기적으로 개선합니다.</p>
</li>
<li><p><strong>엄격한 스키마:</strong> 데이터 구조를 미리 정의하므로 데이터 타입 불일치로 인한 런타임 오류를 방지합니다.</p>
</li>
<li><p><strong>호환성:</strong> 스키마에 필드를 추가하거나 변경하더라도 하위 및 상위 호환성을 유지하는 명확한 규칙을 제공하여 시스템의 유연한 진화를 지원합니다.</p>
<p>한마디로 개쩐다는 것</p>
</li>
</ul>
<h3 id="2-핵심-워크플로우-proto-파일과-코드-생성">2. 핵심 워크플로우: <code>.proto</code> 파일과 코드 생성</h3>
<p>Protobuf의 모든 것은 <code>.proto</code> 파일의 작성에서 시작됩니다. 다음은 간단한 주소록을 정의하는 예시입니다.</p>
<pre><code class="language-proto">// addressbook.proto
syntax = &quot;proto3&quot;;

package tutorial;

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}</code></pre>
<hr>
<h3 id="protobuf-객체-스키마">Protobuf 객체 스키마</h3>
<table>
<thead>
<tr>
<th align="left">객체 (Message)</th>
<th align="left">필드 (Field)</th>
<th align="left">타입 (Type)</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Person</strong></td>
<td align="left"><code>name</code></td>
<td align="left"><code>string</code></td>
<td align="left">사람의 이름</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"><code>id</code></td>
<td align="left"><code>int32</code></td>
<td align="left">고유 식별자</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"><code>email</code></td>
<td align="left"><code>string</code></td>
<td align="left">이메일 주소</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"><code>phones</code></td>
<td align="left"><code>repeated</code></td>
<td align="left">전화번호 목록 (PhoneNumber)</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left"><strong>PhoneNumber</strong></td>
<td align="left"><code>number</code></td>
<td align="left"><code>string</code></td>
<td align="left">실제 전화번호</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"><code>type</code></td>
<td align="left"><code>enum</code></td>
<td align="left">전화번호 타입</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"><strong>(Enum)</strong></td>
<td align="left"><code>MOBILE</code></td>
<td align="left">휴대폰</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"></td>
<td align="left"><code>HOME</code></td>
<td align="left">집 전화</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"></td>
<td align="left"><code>WORK</code></td>
<td align="left">직장 전화</td>
</tr>
<tr>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
<td align="left"></td>
</tr>
<tr>
<td align="left"><strong>AddressBook</strong></td>
<td align="left"><code>people</code></td>
<td align="left"><code>repeated</code></td>
<td align="left">주소록에 포함된 사람 목록</td>
</tr>
</tbody></table>
<hr>
<p>이후 <code>.proto</code> 파일을 <code>protoc</code> 컴파일러로 처리하면 됩니다.</p>
<style>
    details {
      border: 1px solid #aaa;
      border-radius: 4px;
      padding: 0.5em 0.5em 0;
      margin: 1em 0;
      cursor: pointer;
    }

    summary {
      font-weight: bold;
      font-size: 20px;
      margin: -0.5em -0.5em 0;
      padding: 0.5em;
    }

    details[open] {
      padding: 0.5em;
    }

    details[open] summary {
      border-bottom: 1px solid #aaa;
      margin-bottom: 0.5em;
    }

    pre {
      background-color: #f4f4f4;
      border: 1px solid #ddd;
      border-radius: 3px;
      padding: 10px;
      white-space: pre-wrap;
      word-wrap: break-word;
    }

    code {
      font-family: monospace;
      padding: 2px 4px;
      border-radius: 3px;
    }
</style>

<details>
  <summary>자세한 컴파일 절차 (토글 버튼)</summary>
  <div>
    <p>컴파일을 위해서는 먼저 Protocol Buffers 컴파일러인 <code>protoc</code>가 설치되어 있어야 합니다. <code>protoc</code>는 <code>.proto</code> 파일을 특정 프로그래밍 언어에 맞는 데이터 접근 클래스로 변환하는 역할을 합니다.</p>
    <p>일반적인 컴파일 명령어 형식은 다음과 같습니다.</p>
    <pre>protoc -I=$SRC_DIR --&lt;lang&gt;_out=$DST_DIR $SRC_DIR/addressbook.proto</pre>
    <ul>
      <li><code>$SRC_DIR</code>: <code>.proto</code> 파일이 있는 소스 디렉터리 (예: <code>.</code>)</li>
      <li><code>$DST_DIR</code>: 생성된 코드를 저장할 목적지 디렉터리 (예: <code>.</code>)</li>
      <li><code>--&lt;lang&gt;_out</code>: 대상 프로그래밍 언어를 지정하는 옵션 (예: <code>--cpp_out</code>, <code>--python_out</code>, <code>--java_out</code>)</li>
    </ul>
    <h3>언어별 컴파일 방법</h3>
    <p>프로젝트의 <code>examples</code> 디렉터리 내의 <code>CMakeLists.txt</code>와 각 언어별 예제 파일들을 참고하여 주요 언어에 대한 컴파일 방법을 설명합니다.</p>

<pre><code>&lt;h4&gt;1. C++&lt;/h4&gt;
&lt;p&gt;C++ 코드를 생성하려면 &lt;code&gt;--cpp_out&lt;/code&gt; 옵션을 사용합니다.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;코드 생성:&lt;/strong&gt;&lt;br&gt;
    터미널에서 다음 명령어를 실행하여 C++ 헤더(&lt;code&gt;addressbook.pb.h&lt;/code&gt;) 및 소스(&lt;code&gt;addressbook.pb.cc&lt;/code&gt;) 파일을 생성합니다.
    &lt;pre&gt;protoc --cpp_out=. addressbook.proto&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;애플리케이션과 함께 컴파일:&lt;/strong&gt;&lt;br&gt;
    생성된 파일들을 애플리케이션 코드와 함께 컴파일합니다. &lt;code&gt;protobuf&lt;/code&gt; 라이브러리와 링크해야 합니다.
    &lt;pre&gt;g++ your_app.cc addressbook.pb.cc -o your_app `pkg-config --cflags --libs protobuf`&lt;/pre&gt;
    &lt;p&gt;또는&lt;/p&gt;
    &lt;pre&gt;g++ your_app.cc addressbook.pb.cc -o your_app -lprotobuf&lt;/pre&gt;
    &lt;p&gt;&lt;code&gt;examples/add_person.cc&lt;/code&gt;와 &lt;code&gt;examples/list_people.cc&lt;/code&gt; 파일에서 생성된 클래스를 어떻게 사용하는지 확인할 수 있습니다.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;2. Python&lt;/h4&gt;
&lt;p&gt;Python의 경우 &lt;code&gt;--python_out&lt;/code&gt; 옵션을 사용하여 Python 모듈을 생성합니다.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;코드 생성:&lt;/strong&gt;&lt;br&gt;
    다음 명령어를 실행하면 &lt;code&gt;addressbook_pb2.py&lt;/code&gt; 파일이 생성됩니다.
    &lt;pre&gt;protoc --python_out=. addressbook.proto&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;모듈 사용:&lt;/strong&gt;&lt;br&gt;
    생성된 &lt;code&gt;addressbook_pb2.py&lt;/code&gt; 파일을 다른 Python 스크립트에서 &lt;code&gt;import&lt;/code&gt;하여 사용합니다.
    &lt;pre&gt;import addressbook_pb2</code></pre><p>  person = addressbook_pb2.Person()
  person.id = 123
  person.name = &quot;John Doe&quot;
  person.email = &quot;<a href="mailto:jdoe@example.com">jdoe@example.com</a>&quot;</p>
<h1 id="">...</h1>
<p>  </pre>
        <p><code>examples/add_person.py</code>와 <code>examples/list_people.py</code>에서 사용 예시를 찾아볼 수 있습니다.</p>
      </li>
    </ol>
    <h4>3. Java</h4>
    <p>Java는 <code>--java_out</code> 옵션을 사용합니다. 일반적으로 Maven이나 Gradle 같은 빌드 도구를 통해 관리됩니다.</p>
    <ol>
      <li><strong>코드 생성:</strong><br>
        다음 명령어로 Java 소스 파일을 생성합니다.
        <pre>protoc --java_out=. addressbook.proto</pre>
        <p>이 명령어는 <code>tutorial/AddressBookProtos.java</code> 파일을 생성하며, 그 안에 <code>Person</code>, <code>AddressBook</code> 등의 클래스가 포함됩니다.</p>
      </li>
      <li><strong>프로젝트에 통합:</strong><br>
        생성된 Java 파일을 프로젝트의 소스 디렉터리로 옮기고, <code>protobuf-java</code> 라이브러리를 의존성에 추가하여 빌드합니다. <code>java/pom.xml</code> 파일에서 볼 수 있듯이 Maven 프로젝트의 경우 <code>pom.xml</code>에 의존성을 추가합니다.
        <pre>&lt;dependency&gt;
      &lt;groupId&gt;com.google.protobuf&lt;/groupId&gt;
      &lt;artifactId&gt;protobuf-java&lt;/artifactId&gt;
      &lt;version&gt;LATEST_VERSION&lt;/version&gt;
      &lt;/dependency&gt;
          </pre>
      </li>
    </ol>
  </div></p>
</details>

<hr>
<p>예를 들어, Python에서는 <code>person.name = &quot;홍길동&quot;</code>과 같이 객체의 속성에 접근하듯 데이터를 조작하고 직렬화할 수 있습니다.</p>
<h3 id="3-실무에서는-왜-쓸까">3. 실무에서는 왜 쓸까?</h3>
<p>실무 환경에서는 아마 다음과 같은 효과를 기대해볼 수 있겠네요.</p>
<ul>
<li><strong>성능 최적화</strong></li>
<li><strong>gRPC를 통한 언어 독립적 API</strong></li>
<li><strong>견고한 API 버전 관리</strong></li>
</ul>
<h3 id="마치며">마치며</h3>
<p><em>프로토버프쓰샘</em></p>
<h3 id="b포-프ro토bㅓ프">B포 프ro토Bㅓ프</h3>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/034880c4-9a6e-4e37-bbad-b25f11cef791/image.png" alt=""></p>
<h3 id="f터-p로to퍼f">F터 P로to퍼f</h3>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/e8c115a3-c698-4e06-87af-99b8c0543f27/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[조선대학교 에브리타임 강의실 버그 픽스]]></title>
            <link>https://velog.io/@hwan_lee/%EC%A1%B0%EC%84%A0%EB%8C%80%ED%95%99%EA%B5%90-%EC%97%90%EB%B8%8C%EB%A6%AC%ED%83%80%EC%9E%84-%EA%B0%95%EC%9D%98%EC%8B%A4-%EB%B2%84%EA%B7%B8-%ED%94%BD%EC%8A%A4</link>
            <guid>https://velog.io/@hwan_lee/%EC%A1%B0%EC%84%A0%EB%8C%80%ED%95%99%EA%B5%90-%EC%97%90%EB%B8%8C%EB%A6%AC%ED%83%80%EC%9E%84-%EA%B0%95%EC%9D%98%EC%8B%A4-%EB%B2%84%EA%B7%B8-%ED%94%BD%EC%8A%A4</guid>
            <pubDate>Wed, 05 Mar 2025 09:13:33 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하기-앞서">시작하기 앞서..</h1>
<blockquote>
<p>안녕하세요? 시간표 업데이트하는 사람입니다.
지난 에브리타임 시간표 3차 업데이트 이후(약 3/2)에 강의실이 4자리가 아닌 뒤에 끝 3자리만 표시되는 버그를 발견,이에 지속적인 에브리타임 측과 문의과 피드백을 받으면서 해결할려고 하는 중입니다. 학기초, 그것도 신입생/복학생이 들어어와서 해당 3자리만 표시되는 문제는 신입생/복학생 모두에게 혼선을 주고 있습니다. 이에 보다 더 나은 학교생활을 제공하기 위해서 저나 에브리타임이나 열심히 버그픽스중에 있습니다.</p>
</blockquote>
<p>현재 에브리타임 측과 문의해본 바, 회사 사정으로 자세한 원인은 알 수 없으나 html파일과 Javascript파일들을 분석해본 바 저의 소견으로는시간표 데이터들을 정상적으로 데이터베이스(db)로 저장되었으나, 해당 저장소를 다시 사용자에게 가져오는 API 혹은웹/앱 구동하는 자바스크립트가 잘못 된거 같습니다.</p>
<blockquote>
</blockquote>
<p>따라서 제가 버그없이 잘 구동되는 js파일을 따로 작성 에브리타임 측에 보냈습니다</p>
<blockquote>
</blockquote>
<p>일단 급한데로 인터넷 웹 브라우저(웨일, 마소엣지, 크롬, 브래이브, 파폭 등등) 에서라도 잘 작동되도록 하기 위해서 불러오는 JS파일을 뜯어고치는 방법을 저의 velog에 자세히 적어놓았습니다.</p>
<blockquote>
</blockquote>
<p>이동통신단말기(aka 스마트폰)로 구동되는 앱은 현재로서는 앱자체를 뜯어고쳐야 하는데 이렇게 되버리면 제가 문제가 나타는 모든 핸드폰에 깔려있는 앱을 수정해야해서 현실적으로 불가능합니다. 이점 양해부탁드립니다.</p>
<blockquote>
</blockquote>
<p>현재로서는 빠른 시일 내로 에브리타임 자체에서 문제가 해결되길 기다리는 수밖에 없습니다. 감사합니다.</p>
<h1 id="본격적인-시작사진-글자가-잘-안보이면-확대해서-봐주세요">본격적인 시작(사진 글자가 잘 안보이면 확대해서 봐주세요)</h1>
<h2 id="step1-tampermonkey-확장-프로그램-다운로드">STEP1. Tampermonkey 확장 프로그램 다운로드</h2>
<blockquote>
<p>다음 사이트에 들어가서 Tampermonkey 확장프로그램을 다운로드 받는다.
<a href="https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ko">https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ko</a>
해당 확장 프로그램은 웹에서 실행되는 자바스크립트 자체를 뜯어고치는 확장 프로그램입니다.
주로 광고차단, 웹해킹 등에서 사용되지만 오늘은 일단 급한데로 강의실4자리가 온전히 표기되도록 하는 스크립트만을 적었습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/b3e9795e-3575-42c0-980b-646bfd23924b/image.png" alt=""></p>
<h2 id="spet2-스크립트-편집기-들어가기">SPET2. 스크립트 편집기 들어가기</h2>
<blockquote>
<p>Tampermonkey 다운로드 후 <code>Tampermonkey 아이콘 클릭 -&gt; 대시보드</code> 를 클릭해서 다음과 같이 타고 들어가준다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/ede34b9c-bfbb-4e79-893f-8e2904184ee7/image.png" alt=""></p>
<blockquote>
<p>이후 <code>+</code>버튼을 눌러준다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/fff4078b-88b0-4740-9525-66dc2e221d25/image.png" alt=""></p>
<h2 id="step3-스크립트-복붙">STEP3. 스크립트 복붙</h2>
<blockquote>
<p>정상적으로 타고 들어갔다면 다음과 같은 창이 뜹니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/3633c5c2-6e7b-4e89-bc8b-edfe031f0861/image.png" alt=""></p>
<blockquote>
<p>이후 해당 검은박스에 있는 모든 내용을 삭제 후, 아래 JS를 복붙해준다.</p>
</blockquote>
<pre><code class="language-java">// ==UserScript==
// @name         에브리타임 강의실 정보 자동 수정
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  서버에서 받아온 XML의 강의실 정보를 올바르게 수정하여 적용
// @author       You
// @match        https://everytime.kr/timetable*
// @grant        none
// ==/UserScript==

(function() {
    &#39;use strict&#39;;

    // 📌 1️⃣ XMLHttpRequest 가로채기 (XML 응답 수정)
    function interceptXHR() {
        const open = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(method, url) {
            this.addEventListener(&quot;readystatechange&quot;, function() {
                if (this.readyState === 4 &amp;&amp; this.responseText) {
                    try {
                        let parser = new DOMParser();
                        let xmlDoc = parser.parseFromString(this.responseText, &quot;text/xml&quot;);

                        console.log(&quot;📌 서버 응답 감지 (XML)&quot;, xmlDoc);

                        // 모든 subject(과목) 태그를 가져옴
                        let subjects = xmlDoc.getElementsByTagName(&quot;subject&quot;);

                        for (let subject of subjects) {
                            let timeNode = subject.getElementsByTagName(&quot;time&quot;)[0]; // &lt;time&gt; 태그
                            let dataNodes = subject.getElementsByTagName(&quot;data&quot;);  // &lt;data&gt; 태그들

                            if (timeNode &amp;&amp; dataNodes.length &gt; 0) {
                                let timeValue = timeNode.getAttribute(&quot;value&quot;); // 강의 시간 정보
                                let placeList = extractPlaces(timeValue);

                                console.log(&quot;📌 추출된 강의실 목록:&quot;, placeList);

                                // &lt;data&gt; 태그 개수와 강의실 목록 개수가 다를 경우, 부족한 부분은 첫 번째 강의실로 채움
                                for (let i = 0; i &lt; dataNodes.length; i++) {
                                    let placeAttribute = dataNodes[i].getAttribute(&quot;place&quot;);

                                    if (placeAttribute) {
                                        let newPlace = placeList[i] || placeList[0]; // 인덱스 초과 시 첫 번째 강의실 사용
                                        console.log(`📌 강의실 수정: ${placeAttribute} → ${newPlace}`);
                                        dataNodes[i].setAttribute(&quot;place&quot;, newPlace); // 강의실 정보 업데이트
                                    }
                                }
                            }
                        }

                        // 수정된 XML을 다시 문자열로 변환
                        let serializer = new XMLSerializer();
                        let modifiedXML = serializer.serializeToString(xmlDoc);

                        // 서버 응답을 수정하여 반환
                        Object.defineProperty(this, &quot;responseText&quot;, { value: modifiedXML });

                    } catch (error) {
                        console.error(&quot;❌ XML 처리 중 오류 발생:&quot;, error);
                    }
                }
            });
            return open.apply(this, arguments);
        };
    }

    // 📌 2️⃣ Fetch API 가로채기 (XML 응답 수정)
    function interceptFetch() {
        const originalFetch = window.fetch;
        window.fetch = function(...args) {
            return originalFetch(...args).then(response =&gt; {
                return response.clone().text().then(text =&gt; {
                    let parser = new DOMParser();
                    let xmlDoc = parser.parseFromString(text, &quot;text/xml&quot;);

                    console.log(&quot;📌 Fetch 요청 감지 (XML)&quot;, xmlDoc);

                    let subjects = xmlDoc.getElementsByTagName(&quot;subject&quot;);

                    for (let subject of subjects) {
                        let timeNode = subject.getElementsByTagName(&quot;time&quot;)[0];
                        let dataNodes = subject.getElementsByTagName(&quot;data&quot;);

                        if (timeNode &amp;&amp; dataNodes.length &gt; 0) {
                            let timeValue = timeNode.getAttribute(&quot;value&quot;);
                            let placeList = extractPlaces(timeValue);

                            console.log(&quot;📌 추출된 강의실 목록:&quot;, placeList);

                            for (let i = 0; i &lt; dataNodes.length; i++) {
                                let placeAttribute = dataNodes[i].getAttribute(&quot;place&quot;);
                                if (placeAttribute) {
                                    let newPlace = placeList[i] || placeList[0];
                                    console.log(`📌 강의실 수정: ${placeAttribute} → ${newPlace}`);
                                    dataNodes[i].setAttribute(&quot;place&quot;, newPlace);
                                }
                            }
                        }
                    }

                    let serializer = new XMLSerializer();
                    let modifiedXML = serializer.serializeToString(xmlDoc);

                    return new Response(modifiedXML, {
                        status: response.status,
                        statusText: response.statusText,
                        headers: response.headers
                    });
                });
            });
        };
    }

    // 📌 3️⃣ 강의실 정보 추출 함수
    function extractPlaces(timeValue) {
        let matches = timeValue.match(/\(([^)]+)\)/g); // 괄호 안의 정보 추출
        if (!matches) return [];

        let placeList = [];
        matches.forEach(match =&gt; {
            let places = match.replace(/[()]/g, &quot;&quot;).split(&quot;,&quot;); // 괄호 제거 후 강의실 분리
            placeList.push(...places); // 여러 강의실을 리스트에 추가
        });

        return placeList;
    }

    // 📌 4️⃣ MutationObserver로 DOM 업데이트 감지
    function observeDOMChanges() {
        const observer = new MutationObserver(() =&gt; {
            console.log(&quot;📌 페이지 업데이트 감지 (강의실 수정)&quot;);
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // 📌 5️⃣ Tampermonkey 실행 시 초기화
    window.addEventListener(&#39;load&#39;, () =&gt; {
        interceptXHR();    // XMLHttpRequest 가로채기
        interceptFetch();  // Fetch API 가로채기
        observeDOMChanges(); // DOM 변경 감지 시작
    });

})();</code></pre>
<blockquote>
<p>정상적용하면 아래 화면과 같다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/d21c04ee-2ea8-4bd2-b69e-7e3f18000eec/image.png" alt=""></p>
<blockquote>
<p>이후 <code>[Ctrl + S]</code> 키를 눌러서 저장해준다.</p>
</blockquote>
<h2 id="step4-확인해보기">STEP4. 확인해보기</h2>
<blockquote>
<p>적용전 (예시 시간표 입니다)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/60578820-539f-4c82-9a49-07084f04e922/image.png" alt=""></p>
<blockquote>
<p>적용후
일부 3자리로 표현되던 강의실이 정상적으로 4자리 모두 출력되는 모습</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/20b2677e-45b1-46a3-9212-a0d1c1682e2e/image.png" alt=""></p>
<blockquote>
<p>Q: 어라 계속 3자리인데?
A1: f5(새로고침)한번 해주세요.
A2: 아래사진참고</p>
</blockquote>
<blockquote>
<p>사진과 같이 초록색바가 표시되어야합니다!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/4bd4ea36-8a35-4d65-84e6-eaa8485b0230/image.png" alt=""></p>
<blockquote>
<p>아래 사진과 같이 회색바면 클릭해서 초록색바로 바꿔주세요.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/32a13540-4c5d-4b49-ac04-22b9c803f8e4/image.png" alt=""></p>
<h1 id="주요-아이디어-설명">주요 아이디어 설명</h1>
<h3 id="이후-이어질-내용은-준주전공자들-기준으로-설명드립니다">(이후 이어질 내용은 준/주전공자들 기준으로 설명드립니다)</h3>
<h2 id="일단은-왜-시간표자료가-어긋날까">일단은 왜 시간표자료가 어긋날까?</h2>
<h2 id="🚨-1-서버에서-xml-데이터가-변형되는-과정에서-발생할-가능성">🚨 1. 서버에서 XML 데이터가 변형되는 과정에서 발생할 가능성</h2>
<ul>
<li><p>(1) 서버에서 강의실 정보를 잘못 저장하는 경우
서버에서 강의실 정보를 place=&quot;IT융합대학-312&quot;로 저장한 후 클라이언트(브라우저)에서 이를 요청하면, 클라이언트는 그대로 표시.
하지만 place=&quot;IT융합대학-3128&quot;로 저장되어야 하는데, 서버 응답에서 마지막 숫자가 잘려서 3자리(312)만 전송될 수 있음.</p>
</li>
<li><p>(2) DB 저장 과정에서 강의실 번호가 잘리는 경우
강의실 번호가 3자리와 4자리가 혼재된 데이터 구조를 가지고 있을 경우, 특정 프로세스에서 4자리가 3자리로 변환되는 버그 발생 가능.
예를 들어, VARCHAR(3)처럼 컬럼 길이가 3자리로 제한되어 있다면, &quot;3128&quot; → &quot;312&quot;처럼 잘릴 수 있음.</p>
</li>
</ul>
<h2 id="🚨-2-클라이언트브라우저에서-강의실-번호를-처리하는-과정에서-발생할-가능성">🚨 2. 클라이언트(브라우저)에서 강의실 번호를 처리하는 과정에서 발생할 가능성</h2>
<ul>
<li><p>(3) 브라우저에서 place 값을 렌더링할 때 잘리는 경우
XML 데이터를 받아와서 JavaScript로 파싱하는 과정에서 마지막 숫자가 잘리는 문제 발생 가능.
예를 들어, XMLSerializer()를 사용하거나 innerText를 읽어올 때 숫자가 truncate(잘림)될 가능성 있음.</p>
</li>
<li><p>(4) JavaScript 코드에서 place 값을 parseInt() 같은 방식으로 처리할 경우
강의실 번호가 숫자로만 이루어져 있다면, JavaScript가 이를 자동으로 숫자로 변환할 수도 있음.
&quot;5109&quot;를 parseInt()로 처리하면 &quot;510&quot;이 될 수 있음.</p>
</li>
<li><p>(5) HTML 태그 속성에서 place 값이 CSS에 의해 잘리는 경우
overflow: hidden 같은 CSS 속성 때문에 화면에 3자리만 보일 가능성도 있음.
브라우저 개발자 도구에서 실제 place 값이 3자리인지 확인해야 함.</p>
</li>
</ul>
<h2 id="🚨-3-서버에서-xml을-생성하는-과정에서-생길-가능성">🚨 3. 서버에서 XML을 생성하는 과정에서 생길 가능성</h2>
<ul>
<li>(6) XML 응답을 생성할 때 place 값을 잘못 조합하는 경우
<code>&lt;time&gt;</code> 태그와 <code>&lt;data&gt;</code> 태그에서 사용하는 강의실 정보가 다른 테이블에서 조합될 경우,</li>
</ul>
<p><code>&lt;time&gt;</code> 태그는 올바른 4자리 강의실 번호를 가져오고,
<code>&lt;data&gt;</code> 태그는 잘못된 3자리 강의실 번호를 가져오는 매칭 오류 발생 가능.
예를 들면, 다음과 같은 잘못된 SQL 조인이 실행될 수 있음:</p>
<pre><code class="language-sql">SELECT time_table.room, data_table.place 
FROM time_table
JOIN data_table ON time_table.class_id = data_table.class_id;</code></pre>
<h2 id="이후-현재-로컬에서-수정가능한것은-1번밖에-없다고-판단">이후 현재 로컬에서 수정가능한것은 1번밖에 없다고 판단</h2>
<h3 id="왜냐">왜냐</h3>
<ul>
<li>2번같은 경우는 브라우저 버그일 확률이 매우 높은데, 브라우저가 버그날 확률은.. 난 들어본 적이 없다</li>
<li>3번같은 경우는 로컬에서 해결할 수 있는 방법이 없다.</li>
</ul>
<h2 id="html과-js-삽질을-통해서-서버에서-xml을-불러오는-것은-확인">html과 Js 삽질을 통해서 서버에서 XML을 불러오는 것은 확인</h2>
<p><code>시간표나와있는 게시판으로 이동 - 개발자 도구 - network - 최하단 table xml 파일을 보면 다음과 같이 뜬다.</code>
<code>(당연히 아래는 예시 시간표다)</code></p>
<pre><code class="language-xml">&lt;response&gt;
  &lt;table id=&quot;53124329&quot; is_deleted=&quot;0&quot; name=&quot;2&quot; year=&quot;2025&quot; semester=&quot;1&quot; priv=&quot;0&quot; primary=&quot;0&quot; created_at=&quot;2025-03-05 09:09:45&quot; updated_at=&quot;2025-03-05 17:45:42&quot;&gt;
    &lt;subject id=&quot;7274959&quot;&gt;
      &lt;internal value=&quot;00071-01&quot;/&gt;
      &lt;name value=&quot;안보학&quot;/&gt;
      &lt;professor value=&quot;박주환&quot;/&gt;
      &lt;time value=&quot;월09:00~10:00(학군단-1101,학군단-1101), 목09:00~11:00(학군단-1101,학군단-1101)&quot;&gt;
        &lt;data day=&quot;0&quot; starttime=&quot;108&quot; endtime=&quot;120&quot; place=&quot;학군단-110&quot;/&gt;
        &lt;data day=&quot;3&quot; starttime=&quot;108&quot; endtime=&quot;132&quot; place=&quot;학군단-110&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;3&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7274961&quot;&gt;
      &lt;internal value=&quot;00089-02&quot;/&gt;
      &lt;name value=&quot;예술의이해&quot;/&gt;
      &lt;professor value=&quot;장민한,김미영,김현재&quot;/&gt;
      &lt;time value=&quot;화10:00~13:00(미술대학 교사-6109)&quot;&gt;
        &lt;data day=&quot;1&quot; starttime=&quot;120&quot; endtime=&quot;156&quot; place=&quot;미술대학 교사-6109&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;3&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7274966&quot;&gt;
      &lt;internal value=&quot;00121-01&quot;/&gt;
      &lt;name value=&quot;교육학개론&quot;/&gt;
      &lt;professor value=&quot;민형덕&quot;/&gt;
      &lt;time value=&quot;월14:00~16:00(제1자연과학관-5516)&quot;&gt;
        &lt;data day=&quot;0&quot; starttime=&quot;168&quot; endtime=&quot;192&quot; place=&quot;제1자연과학관-5516&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;2&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7274968&quot;&gt;
      &lt;internal value=&quot;00121-80&quot;/&gt;
      &lt;name value=&quot;교육학개론&quot;/&gt;
      &lt;professor value=&quot;고화정&quot;/&gt;
      &lt;time value=&quot;화18:00~20:00(사회과학·사범대학-3107)&quot;&gt;
        &lt;data day=&quot;1&quot; starttime=&quot;216&quot; endtime=&quot;240&quot; place=&quot;사회과학·사범대학-3107&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;2&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7274969&quot;&gt;
      &lt;internal value=&quot;00130-01&quot;/&gt;
      &lt;name value=&quot;일반생물학실험1&quot;/&gt;
      &lt;professor value=&quot;이한용&quot;/&gt;
      &lt;time value=&quot;목11:00~12:30(체육대학-9213)&quot;&gt;
        &lt;data day=&quot;3&quot; starttime=&quot;132&quot; endtime=&quot;150&quot; place=&quot;체육대학-9213&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;1&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7274971&quot;&gt;
      &lt;internal value=&quot;00130-03&quot;/&gt;
      &lt;name value=&quot;일반생물학실험1&quot;/&gt;
      &lt;professor value=&quot;이현화&quot;/&gt;
      &lt;time value=&quot;수16:00~17:30(체육대학-9213)&quot;&gt;
        &lt;data day=&quot;2&quot; starttime=&quot;192&quot; endtime=&quot;210&quot; place=&quot;체육대학-9213&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;1&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7274992&quot;&gt;
      &lt;internal value=&quot;00169-80&quot;/&gt;
      &lt;name value=&quot;경제원론1&quot;/&gt;
      &lt;professor value=&quot;조동민&quot;/&gt;
      &lt;time value=&quot;수18:00~21:00(법과대학 및 경상대학-5309)&quot;&gt;
        &lt;data day=&quot;2&quot; starttime=&quot;216&quot; endtime=&quot;252&quot; place=&quot;법과대학 및 경상대학-5309&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;3&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7275001&quot;&gt;
      &lt;internal value=&quot;00176-03&quot;/&gt;
      &lt;name value=&quot;현대인의건강관리&quot;/&gt;
      &lt;professor value=&quot;서영환&quot;/&gt;
      &lt;time value=&quot;수14:00~16:00(체육대학-6213,체육대학-6213), 목16:00~17:00(체육대학-6213,체육대학-6213)&quot;&gt;
        &lt;data day=&quot;2&quot; starttime=&quot;168&quot; endtime=&quot;192&quot; place=&quot;체육대학-621&quot;/&gt;
        &lt;data day=&quot;3&quot; starttime=&quot;192&quot; endtime=&quot;204&quot; place=&quot;체육대학-621&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;3&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7275022&quot;&gt;
      &lt;internal value=&quot;20035-03&quot;/&gt;
      &lt;name value=&quot;정치학&quot;/&gt;
      &lt;professor value=&quot;최선&quot;/&gt;
      &lt;time value=&quot;월12:00~14:00(사회과학·사범대학-1204,사회과학·사범대학-1204), 수11:00~12:00(사회과학·사범대학-1204,사회과학·사범대학-1204)&quot;&gt;
        &lt;data day=&quot;0&quot; starttime=&quot;144&quot; endtime=&quot;168&quot; place=&quot;사회과학·사범대학-120&quot;/&gt;
        &lt;data day=&quot;2&quot; starttime=&quot;132&quot; endtime=&quot;144&quot; place=&quot;사회과학·사범대학-120&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;3&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7275067&quot;&gt;
      &lt;internal value=&quot;20141-01&quot;/&gt;
      &lt;name value=&quot;서양사개론&quot;/&gt;
      &lt;professor value=&quot;주의돈&quot;/&gt;
      &lt;time value=&quot;화14:00~16:00(대학 본관-4272,대학 본관-4272), 목15:00~16:00(대학 본관-4272,대학 본관-4272)&quot;&gt;
        &lt;data day=&quot;1&quot; starttime=&quot;168&quot; endtime=&quot;192&quot; place=&quot;대학 본관-427&quot;/&gt;
        &lt;data day=&quot;3&quot; starttime=&quot;180&quot; endtime=&quot;192&quot; place=&quot;대학 본관-427&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;3&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
    &lt;subject id=&quot;7275087&quot;&gt;
      &lt;internal value=&quot;20268-02&quot;/&gt;
      &lt;name value=&quot;미시경제학&quot;/&gt;
      &lt;professor value=&quot;박성훈&quot;/&gt;
      &lt;time value=&quot;목14:00~15:00(법과대학 및 경상대학-4319,법과대학 및 경상대학-4319), 금10:00~12:00(법과대학 및 경상대학-4319,법과대학 및 경상대학-4319)&quot;&gt;
        &lt;data day=&quot;3&quot; starttime=&quot;168&quot; endtime=&quot;180&quot; place=&quot;법과대학 및 경상대학-431&quot;/&gt;
        &lt;data day=&quot;4&quot; starttime=&quot;120&quot; endtime=&quot;144&quot; place=&quot;법과대학 및 경상대학-431&quot;/&gt;
      &lt;/time&gt;
      &lt;place value=&quot;&quot;/&gt;
      &lt;credit value=&quot;3&quot;/&gt;
      &lt;closed value=&quot;0&quot;/&gt;
    &lt;/subject&gt;
  &lt;/table&gt;
&lt;/response&gt;</code></pre>
<blockquote>
<p>찾았죠?
XML이 문제네요.</p>
</blockquote>
<h3 id="다음-코드를-봐보자">다음 코드를 봐보자</h3>
<pre><code class="language-xml">      &lt;internal value=&quot;20268-02&quot;/&gt;
      &lt;name value=&quot;미시경제학&quot;/&gt;
      &lt;professor value=&quot;박성훈&quot;/&gt;
      &lt;time value=&quot;목14:00~15:00(법과대학 및 경상대학-4319,법과대학 및 경상대학-4319), 금10:00~12:00(법과대학 및 경상대학-4319,법과대학 및 경상대학-4319)&quot;&gt;
        &lt;data day=&quot;3&quot; starttime=&quot;168&quot; endtime=&quot;180&quot; place=&quot;법과대학 및 경상대학-431&quot;/&gt;
        &lt;data day=&quot;4&quot; starttime=&quot;120&quot; endtime=&quot;144&quot; place=&quot;법과대학 및 경상대학-431&quot;/&gt;</code></pre>
<blockquote>
<p><code>time value</code>값이 우리가 찾던 정상적인 시간과 건물이 있는 것을 확인했고
<code>place</code>값이 3자리로 잘리는 버그 확인.</p>
</blockquote>
<h3 id="이후-xmlhttprequest를-가로채서-수정을-가하는-코드를-짜면-위에-코드와-같이-짜진다">이후 XMLHttpRequest()를 가로채서, 수정을 가하는 코드를 짜면 위에 코드와 같이 짜진다.</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[매우 쉽게 설명하는 백준[1012] - 파이썬]]></title>
            <link>https://velog.io/@hwan_lee/%EB%A7%A4%EC%9A%B0-%EC%89%BD%EA%B2%8C-%EC%84%A4%EB%AA%85%ED%95%98%EB%8A%94-%EB%B0%B1%EC%A4%801012-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hwan_lee/%EB%A7%A4%EC%9A%B0-%EC%89%BD%EA%B2%8C-%EC%84%A4%EB%AA%85%ED%95%98%EB%8A%94-%EB%B0%B1%EC%A4%801012-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Mon, 10 Feb 2025 06:16:09 GMT</pubDate>
            <description><![CDATA[<h2 id="본격적으로-시작-전에">본격적으로 시작 전에..</h2>
<blockquote>
<p>이 문제는 DFS 또는 BFS의 매우 정석적인 문제입니다.
따라서 본 문제의 문제풀이 법을 그냥 외우다 시피 공부하는 것을 추천합니다.
기업 코테나, 기사급 자격증에 약방의 감초 처럼 등장하는 매우 스탠다드한 문제라고 할 수 있습니다.
반드시 반복숙달 하시길..</p>
</blockquote>
<h2 id="✔️문제">✔️문제</h2>
<blockquote>
<p>차세대 영농인 한나는 강원도 고랭지에서 유기농 배추를 재배하기로 하였다. 농약을 쓰지 않고 배추를 재배하려면 배추를 해충으로부터 보호하는 것이 중요하기 때문에, 한나는 해충 방지에 효과적인 배추흰지렁이를 구입하기로 결심한다. 이 지렁이는 배추근처에 서식하며 해충을 잡아 먹음으로써 배추를 보호한다. 특히, 어떤 배추에 배추흰지렁이가 한 마리라도 살고 있으면 이 지렁이는 인접한 다른 배추로 이동할 수 있어, 그 배추들 역시 해충으로부터 보호받을 수 있다. 한 배추의 상하좌우 네 방향에 다른 배추가 위치한 경우에 서로 인접해있는 것이다.
한나가 배추를 재배하는 땅은 고르지 못해서 배추를 군데군데 심어 놓았다. 배추들이 모여있는 곳에는 배추흰지렁이가 한 마리만 있으면 되므로 서로 인접해있는 배추들이 몇 군데에 퍼져있는지 조사하면 총 몇 마리의 지렁이가 필요한지 알 수 있다. 예를 들어 배추밭이 아래와 같이 구성되어 있으면 최소 5마리의 배추흰지렁이가 필요하다. 0은 배추가 심어져 있지 않은 땅이고, 1은 배추가 심어져 있는 땅을 나타낸다.</p>
</blockquote>
<table>
  <tr><td>1</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
  <tr><td>0</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
  <tr><td>0</td><td>0</td><td>0</td><td>0</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
  <tr><td>0</td><td>0</td><td>0</td><td>0</td><td>1</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
  <tr><td>0</td><td>0</td><td>1</td><td>1</td><td>0</td><td>0</td><td>0</td><td>1</td><td>1</td><td>1</td></tr>
  <tr><td>0</td><td>0</td><td>0</td><td>0</td><td>1</td><td>0</td><td>0</td><td>1</td><td>1</td><td>1</td></tr>
</table>

<hr>
<h2 id="⌨️입력">⌨️입력</h2>
<blockquote>
<p>입력의 첫 줄에는 테스트 케이스의 개수 T가 주어진다. 그 다음 줄부터 각각의 테스트 케이스에 대해 첫째 줄에는 배추를 심은 배추밭의 가로길이 M(1 ≤ M ≤ 50)과 세로길이 N(1 ≤ N ≤ 50), 그리고 배추가 심어져 있는 위치의 개수 K(1 ≤ K ≤ 2500)이 주어진다. 그 다음 K줄에는 배추의 위치 X(0 ≤ X ≤ M-1), Y(0 ≤ Y ≤ N-1)가 주어진다. 두 배추의 위치가 같은 경우는 없다.</p>
</blockquote>
<hr>
<h2 id="🖥️-출력">🖥️ 출력</h2>
<blockquote>
<p>각 테스트 케이스에 대해 필요한 최소의 배추흰지렁이 마리 수를 출력한다.</p>
</blockquote>
<hr>
<h2 id="분류">분류</h2>
<blockquote>
<p>DFS, BFS, 그래프이론, 그래프 탐색</p>
</blockquote>
<hr>
<h2 id="문제풀이-💡">문제풀이 💡</h2>
<pre><code class="language-python"># 재귀 호출의 최대 깊이를 설정 (기본적으로 파이썬의 재귀 깊이는 1000으로 제한됨)
# DFS의 깊이가 깊어질 수 있으므로 설정을 늘려줌
import sys
sys.setrecursionlimit(10**6)

# 입력을 빠르게 받기 위한 sys.stdin.readline 사용
input = sys.stdin.readline

# 방향 벡터 (상, 하, 좌, 우)
# 이를 통해 상하좌우로 이동 가능하도록 설정
dirR = [1, -1, 0, 0]  # 행(y) 방향 이동
dirC = [0, 0, 1, -1]  # 열(x) 방향 이동

# 깊이 우선 탐색(DFS) 함수
# 현재 위치 (y, x)를 기준으로 연결된 모든 영역을 탐색
def dfs(y, x):
    # 현재 위치를 방문했음을 표시
    visited[y][x] = True

    # 상하좌우 4방향을 탐색
    for dirIdx in range(4):
        newY = y + dirR[dirIdx]  # 새 행 좌표
        newX = x + dirC[dirIdx]  # 새 열 좌표

        # 새로운 좌표가 그래프 범위를 벗어나지 않는지 확인
        if 0 &lt;= newY &lt; N and 0 &lt;= newX &lt; M:
            # 배추가 심어져 있고 아직 방문하지 않은 곳이라면 DFS 재귀 호출
            if graph[newY][newX] and not visited[newY][newX]:
                dfs(newY, newX)

# 테스트 케이스 입력 (T: 테스트 케이스 개수)
T = int(input())

# 여러 개의 테스트 케이스 처리
for _ in range(T):
    # M: 가로(열) 크기, N: 세로(행) 크기, K: 배추가 심어진 위치 개수
    M, N, K = map(int, input().split())

    # 2차원 그래프를 초기화 (배추가 심어지지 않은 곳은 False로 설정)
    graph = [[False] * M for _ in range(N)]

    # 방문 여부를 저장할 배열 (모든 위치를 False로 초기화)
    visited = [[False] * M for _ in range(N)]

    # 배추가 심어진 위치 입력받아 그래프에 표시
    for _ in range(K):
        x, y = map(int, input().split())  # x가 열, y가 행 (좌표 순서 주의)
        graph[y][x] = True  # 배추가 심어진 위치를 True로 표시

    # DFS를 통해 연결된 배추 그룹의 개수를 세기 위한 변수
    answer = 0

    # 그래프를 탐색하면서 배추가 심어진 곳을 찾음
    for i in range(N):  # 행(세로) 탐색
        for j in range(M):  # 열(가로) 탐색
            # 배추가 있고 아직 방문하지 않은 곳이라면 새로운 DFS 탐색 시작
            if graph[i][j] and not visited[i][j]:
                dfs(i, j)  # DFS 실행
                answer += 1  # 한 번의 DFS가 끝나면 새로운 그룹이므로 개수 증가

    # 최종적으로 해당 테스트 케이스의 결과 출력
    print(answer)</code></pre>
<h2 id="🔍-코드-설명">🔍 코드 설명</h2>
<hr>
<h3 id="1-입력-최적화">1. 입력 최적화</h3>
<ul>
<li><p><code>sys.stdin.readline()</code>을 사용하여 입력 속도를 빠르게 처리.</p>
</li>
<li><p><code>sys.setrecursionlimit(10**6)</code>을 설정하여 DFS가 깊어질 때 스택 오버플로 방지.</p>
</li>
</ul>
<h3 id="2-방향-벡터-사용">2. 방향 벡터 사용</h3>
<ul>
<li><p><code>dirR</code>과 <code>dirC</code>를 활용하여 <strong>상(↑), 하(↓), 좌(←), 우(→)</strong>로 이동 가능하도록 설정.</p>
</li>
<li><p><code>newY = y + dirR[dirIdx]</code>, <code>newX = x + dirC[dirIdx]</code> 형태로 이동 좌표 계산.</p>
</li>
</ul>
<h3 id="3-dfs를-활용한-그래프-탐색">3. DFS를 활용한 그래프 탐색</h3>
<ul>
<li>방문한 배추 위치를 <code>visited[y][x] = True</code>로 처리.</li>
<li>상하좌우를 탐색하며 배추가 있고 방문하지 않은 경우 DFS를 재귀적으로 호출.</li>
</ul>
<h3 id="4-전체-그래프-탐색">4. 전체 그래프 탐색</h3>
<ul>
<li>2중 for문을 사용하여 모든 위치를 탐색.</li>
<li>방문하지 않은 배추를 찾으면 DFS 탐색을 시작하고, 연결된 모든 배추를 방문한 후 answer를 증가.</li>
</ul>
<h3 id="5-결과-출력">5. 결과 출력</h3>
<ul>
<li>DFS를 수행할 때마다 새로운 연결된 배추 그룹이 하나 증가하므로 answer 값을 출력.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[우리 할머니도 이해한다 - [백준]17626 파이썬]]></title>
            <link>https://velog.io/@hwan_lee/%EC%9A%B0%EB%A6%AC-%ED%95%A0%EB%A8%B8%EB%8B%88%EB%8F%84-%EC%9D%B4%ED%95%B4%ED%95%9C%EB%8B%A4-%EB%B0%B1%EC%A4%8017626-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hwan_lee/%EC%9A%B0%EB%A6%AC-%ED%95%A0%EB%A8%B8%EB%8B%88%EB%8F%84-%EC%9D%B4%ED%95%B4%ED%95%9C%EB%8B%A4-%EB%B0%B1%EC%A4%8017626-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Sat, 08 Feb 2025 06:56:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="✔️문제">✔️문제</h2>
<p>라그랑주는 1770년에 모든 자연수는 넷 혹은 그 이하의 제곱수의 합으로 표현할 수 있다고 증명하였다. 어떤 자연수는 복수의 방법으로 표현된다. 예를 들면, 26은 52과 12의 합이다; 또한 42 + 32 + 12으로 표현할 수도 있다. 역사적으로 암산의 명수들에게 공통적으로 주어지는 문제가 바로 자연수를 넷 혹은 그 이하의 제곱수 합으로 나타내라는 것이었다. 1900년대 초반에 한 암산가가 15663 = 1252 + 62 + 12 + 12라는 해를 구하는데 8초가 걸렸다는 보고가 있다. 좀 더 어려운 문제에 대해서는 56초가 걸렸다: 11339 = 1052 + 152 + 82 + 52.
자연수 n이 주어질 때, n을 최소 개수의 제곱수 합으로 표현하는 컴퓨터 프로그램을 작성하시오.</p>
</blockquote>
<hr>
<h2 id="⌨️입력">⌨️입력</h2>
<p>입력은 표준입력을 사용한다. 입력은 자연수 n을 포함하는 한 줄로 구성된다. 여기서, 1 ≤ n ≤ 50,000이다.</p>
<hr>
<h2 id="🖥️-출력">🖥️ 출력</h2>
<p>출력은 표준출력을 사용한다. 합이 n과 같게 되는 제곱수들의 최소 개수를 한 줄에 출력한다.</p>
<hr>
<h2 id="분류">분류</h2>
<p>다이나믹프로그래밍(DP), 일반수학(math)</p>
<hr>
<h2 id="✔️풀이">✔️풀이</h2>
<blockquote>
<p>자세한 풀이는 사진에서 설명합니다.
진짜 저만큼 자세히 설명해주는 블로그 몇개 없습니다.</p>
</blockquote>
<p>[빠른 복붙을 위한 전체 코드] <strong>pypy로 돌려야 합니다.</strong></p>
<pre><code class="language-python">n = int(input())
dp = [0, 1]
for i in range(2, n+1):
    min_ = 4
    j = 1
    while (j**2) &lt;= i:
        min_ = min(min_, dp[i-j**2])
        j += 1
    dp.append(min_ + 1)
print(dp[n])</code></pre>
<hr>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/c4979320-af78-4124-9f9f-106d38802b52/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/hwan_lee/post/573c6023-9a9a-401e-aab7-c440711abcde/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/hwan_lee/post/0cd99ab6-8a34-412b-9adf-d1f8c0a97eba/image.jpg" alt=""></p>
<blockquote>
<h2 id="결과">결과</h2>
<table>
<thead>
<tr>
<th align="left">시간</th>
<th align="center">메모리</th>
</tr>
</thead>
<tbody><tr>
<td align="left">120ms</td>
<td align="center">112088 KB</td>
</tr>
</tbody></table>
</blockquote>
<h2 id="👁️🗨️-코멘트">👁️‍🗨️ 코멘트</h2>
<h3 id="i-제법-어렵다-인정하자">i) 제법 어렵다. 인정하자.</h3>
<blockquote>
<p>???: 아 별로 안 어렵던데<del>~ ㅋㅋ? 
 ???: 이걸 못 푸노 ㅋㅋ</del>?</p>
</blockquote>
<p>  이런거에 휘둘리지 말자. 
  적어도 내가 느끼기에는 충분히 어려운 문제다.</p>
<h3 id="ii-이런-거는-발상문제다-못-풀어도-괜찮다">ii) 이런 거는.. 발상문제다. 못 풀어도 괜찮다.</h3>
<ul>
<li>DP에 익숙하지 않으면, 당연히 발상이 잘 안떠오르고, 규칙도 잘 모르는게 당연하다.
너무 위축되고, 자책하지 말고 차근차근 공부해 나가다 보면 
그냥 관성적으로 발상이 떠오른다. 
딱 거기까지만 힘내보자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 9461번 파도반 수열 파이썬]]></title>
            <link>https://velog.io/@hwan_lee/%EB%B0%B1%EC%A4%80-9461%EB%B2%88-%ED%8C%8C%EB%8F%84%EB%B0%98-%EC%88%98%EC%97%B4-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@hwan_lee/%EB%B0%B1%EC%A4%80-9461%EB%B2%88-%ED%8C%8C%EB%8F%84%EB%B0%98-%EC%88%98%EC%97%B4-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Wed, 29 Jan 2025 14:58:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="✔️문제">✔️문제</h2>
<p> 오른쪽 그림과 같이 삼각형이 나선 모양으로 놓여져 있다. 첫 삼각형은 정삼각형으로 변의 길이는 1이다. 그 다음에는 다음과 같은 과정으로 정삼각형을 계속 추가한다. 나선에서 가장 긴 변의 길이를 k라 했을 때, 그 변에 길이가 k인 정삼각형을 추가한다.
파도반 수열 P(N)은 나선에 있는 정삼각형의 변의 길이이다. P(1)부터 P(10)까지 첫 10개 숫자는 1, 1, 1, 2, 2, 3, 4, 5, 7, 9이다.
N이 주어졌을 때, P(N)을 구하는 프로그램을 작성하시오.
<img src="https://www.acmicpc.net/upload/images/pandovan.png" alt="문제사진"></p>
</blockquote>
<hr>
<h2 id="⌨️입력">⌨️입력</h2>
<p>첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, N이 주어진다. (1 ≤ N ≤ 100)</p>
<hr>
<h2 id="🖥️-출력">🖥️ 출력</h2>
<p>각 테스트 케이스마다 P(N)을 출력한다.</p>
<hr>
<h2 id="분류">분류</h2>
<p>다이나믹프로그래밍(DP), 일반수학(math)</p>
<hr>
<h2 id="✔️풀이">✔️풀이</h2>
<blockquote>
<p>자세한 풀이는 사진을 통해서 설명하겠다.</p>
</blockquote>
<hr>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/54e3a79a-7dfa-429a-9675-ebb7c4999190/image.png" alt=""></p>
<hr>
<h2 id="🧑💻풀이-코드">🧑‍💻풀이 코드</h2>
<pre><code class="language-python">import sys
input = sys.stdin.readline
p = [0] * 101
p[0], p[1], p[2] = 1,1,1
for k in range(3,101,1):
    p[k] = p[k-2] + p[k-3]
for _ in range(int(input())):
     print(p[int(input())-1])</code></pre>
<blockquote>
<h2 id="결과">결과</h2>
<table>
<thead>
<tr>
<th align="left">시간</th>
<th align="center">메모리</th>
</tr>
</thead>
<tbody><tr>
<td align="left">32ms</td>
<td align="center">32412 KB</td>
</tr>
</tbody></table>
</blockquote>
<h2 id="👁️🗨️-코멘트">👁️‍🗨️ 코멘트</h2>
<h3 id="i-일단-문제-이름부터-보자">i) 일단 문제 이름부터 보자</h3>
<ul>
<li>문제이름부터가 『파도반 수열』이다.
고등학교를 나왔다면 적어도 &#39;수열&#39;이라는 것이 수학이라는  것을 알 것이다.
바로 뒤도 안 돌아보고 brrrrrrr 떨면서 아하! 먜쓰몌뤾!(mathmetics)<h3 id="ii-그림을-보자">ii) 그림을 보자</h3>
<ul>
<li>백준같은 문제가 그림을 왜 줄까?<blockquote>
<p>진지하게 고민해봐야 하는 것이다</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>적어도 문제에 도움이 될려면 된거지, 문제 푸는데 하등 도움이 안될거라면 왜 줬을까?
능지가 있다면 바로 그림을 보고</p>
<blockquote>
<p><em>아하 그림이 뭔가 규칙적이네? 수학이네? DP로구나! 점화식 땅땅땅!!</em></p>
</blockquote>
<p>이 판단이 빨리 서야한다.</p>
<h3 id="iii-쓰자">iii) 쓰자!</h3>
</li>
<li><p>풀이 사진을 보면 p(1)부터 p(10)까지 직접 쓴 게 보일 것이다.</p>
<p>왜 썼을까?</p>
<p>규칙 볼라고 ㅋ</p>
</li>
</ul>
<blockquote>
<p>쓰자! 제발 쓰자! 우리들의 머리는 생각보다 멍청하다. 쓰자.</p>
</blockquote>
<h3 id="iv-라이브러리">iv) 라이브러리</h3>
<ul>
<li>파이썬은 너무 느리다.
다들 아는 이야기이지만 너무 느리다.
다음과 같은 라이브러리를 불러오면 제법 빨라진다. 알아두자.</li>
</ul>
<pre><code class="language-python">import sys
input = sys.stdin.readline</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[조선대학교 에브리타임 시간표 업데이트]]></title>
            <link>https://velog.io/@hwan_lee/%EC%A1%B0%EC%84%A0%EB%8C%80%ED%95%99%EA%B5%90-%EC%8B%9C%EA%B0%84%ED%91%9C-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@hwan_lee/%EC%A1%B0%EC%84%A0%EB%8C%80%ED%95%99%EA%B5%90-%EC%8B%9C%EA%B0%84%ED%91%9C-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Sat, 02 Nov 2024 20:04:00 GMT</pubDate>
            <description><![CDATA[<h2 id="일단-늦어서-죄송합니다">일단 늦어서 죄송합니다.</h2>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/95fe0914-592a-4b7f-84fe-6dbea0138124/image.jfif" alt=""></p>
<blockquote>
<p>왜 늦음?</p>
<blockquote>
<p>바쁘더라고요..음.. 2학기 시작하기 전에 여행도 다니고하고, 마음좀 추스리고 개발에 전념하고 공부도 좀 하고 알바도 좀 하고 아무튼간에 빨리 만들고 싶었으나 2학기 시작하기 전에는 공부도 덜했고.. 똑똑긔로 변할려고 노력많이 했고요 늦은 감이 있겠으나 하해와 같은 마음으로 이해해 주싶사.. 앞으로는 빨리빨리 업데이트 해보도록 하겠습니다!</p>
</blockquote>
</blockquote>
<h2 id="그리고-함께-개발을-도와준-dice한테-감사한다">그리고 함께 개발을 도와준 DICE한테 감사한다.</h2>
<h2 id="번외-에브리타임-측에서-늦어서-시간표-업데이트-못해준다-하네요-ㅅㄱ링">번외) 에브리타임 측에서 늦어서 시간표 업데이트 못해준다 하네요~ ㅅㄱ링</h2>
<p><del>나만 시간 날린거니깐..괜찮아.. 암파인.. 링링링링링</del></p>
<br/>


<h2 id="조선대에-다니다-보면">조선대에 다니다 보면..</h2>
<p>한가지 의문점이 들것이다.</p>
<blockquote>
<p>왜 시간표 업데이트가 느리지..?</p>
</blockquote>
<blockquote>
<p>시간표 마법사가 안되는데..?</p>
</blockquote>
<p>그렇다. 우리는 지금까지 GOAT &#39;종이학&#39;님이 업데이트 해주셨으나, 지금은 잠정 은퇴아닌 일선에서 물러나셨다. <del>그동안 고마웠습니다.</del></p>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/97d3d3eb-6fa9-4aa8-aad0-6e0a240150cf/image.png" alt="">
이렇게 위에 사진과 같이 에브리타임 시간표가 업데이트가 되지 않았다면 직접추가를 이용해 시간표를 직접 넣어야한다. <del>ㅈㄹ불편</del></p>
<h2 id="그래서-일단-교무처-전산실에-문의">그래서 일단 교무처, 전산실에 문의..</h2>
<blockquote>
<p>나: 엑셀....그냥 통짜로 가공 안된거여도 괜찮아요.. 제공만 해주세요..
조선대: 응 보안상 문제로 못해줘~ ㅅㄱ.</p>
</blockquote>
<p>도대체 시간표가 무슨 문제를 일으킨다고 보안상 제공을 못해주는가..?? 엄... 아무튼 이렇게 몇번의 문의를 넣었다. 그러나 결국 돌아오는 대답은 항상 똑같았다.<del>개새끼들</del></p>
<p>아무튼 </p>
<blockquote>
<p>결국 이렇게 된거 내가 직접 시간표를 만들자! </p>
</blockquote>
<p><br/><br/></p>
<h2 id="개발속-난관">개발속 난관</h2>
<blockquote>
<p>그때 멈췄어야 했다..</p>
</blockquote>
<p>조선대학교 학생이라면** 종합정보시스템(aka. 종정시)<strong>라고 알 것이다. MsEdge(전 인터넷익스플로러)로 접속하는 학생관리시스템인데... 사실 이 사이트를 보고 **크롤링하기도 전에 여러가지 생각이 들었다...</strong></p>
<h3 id="이거-개-오래된-시스템-쓸거-같은데">이거... 개 오래된 시스템 쓸거 같은데..</h3>
<p><del>제발 내 감이 틀리길 빌었다</del>
<img src="https://velog.velcdn.com/images/hwan_lee/post/3670e276-b553-4113-92d1-224cc33e033e/image.png" alt=""></p>
<p>오오...오오........오ㅗㅗㅗㅗㅗㅗㅗ오오오오오오 무려 나는 2003년 혹은 그보다 더 오래되었을수도 있는 사이트를 크롤링하려던 거시였던 거시였던 거였던 거시였따. <del>나보다 더 늙은 코드다</del>아니다 다를까 JavaScript에서 eval문을 쓰고 있었다... <del>컴공 혹은 코딩을 조금이라도 할줄 안다면 이게 얼마나 파괴적인 문법인지 알 것이다.</del> 사용된 html버전이나 JS버전이 확인하기도 겁날 정도로 오래된 시스템이다.
<br/>
심지여 어떤 기능들은 아예 개발자도구조차 안 열어진다...<del>세상에상에</del> 이러면 내가 알기론 JavaScript나 HTML이 만들어지기도 전, 고대 언어로 개발되었을 확률이 높다. 이름도 없는 그냥 본인들만 아는 그런언어로 코딩했을수도 있었던 것이다. <del>그런다고 설마 어샘블리언어로 시스템을 짰겠어..?</del>한마디로 지들 ㅈ대로 개발했을 확률 99% 그리고 유지보수 안한 기간도 매우 길어보임.
<img src="https://velog.velcdn.com/images/hwan_lee/post/d81712de-9dfb-48ed-9dc3-857d04a18e39/image.jfif" alt=""></p>
<br/>

<h2 id="종정시는-깔끔하게-포기">종정시는 깔끔하게 포기</h2>
<p>도저히 크롤링 불가능하다고 판정. 다른 방법을 몰색하던중...우리 학교(조선대)는 종정시와 같이 제2 학생관리 웹으로 사용하는 시스템이 있었으니..!
<br/></p>
<h3 id="바로-lms시스템이다">바로 LMS시스템이다!</h3>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/ceccaf0c-07b5-40ff-ba8d-642d988fa2ec/image.webp" alt=""></p>
<p>lms상에서 강의계획서를 보면...</p>
<p><img src="https://velog.velcdn.com/images/hwan_lee/post/3570f93e-9396-44dc-910f-6c58fb787e07/image.png" alt=""></p>
<p>오오...!! 우리가 찾던 모든 시간표 정보가 나와있던 것이다..!! <del>EGC는 역시 켈리김. 켈리김 교수님 사랑해요</del>
<br/></p>
<h2 id="개발-시작">개발 시작</h2>
<blockquote>
<p>주요 개발 언어: python, bs4, by, selenium, pypy3</p>
</blockquote>
<p>언어는 일단 파이썬으로 정했다. C언어나 Java도 고민했는데 웹 크롤링은 처음이라서 나한테 제일 익숙한 파이썬으로 정했다.<br>그리고 여느 프로그램이 그렇듯 개발하고 대가리 깨지고를 무한전 반복. 하루를 꼴딱 세워서 개발하는 일이 있을까 싶었는데 이번에를 통해서 경험하게 되었다. 피곤했지만 나름 괜찮은 것 같았다.
<br/></p>
<h2 id="데이터-수집-및-가공">데이터 수집 및 가공</h2>
<p>일단 데이터 상으로 제일 다루기 만만한 json파일로 정보를 저장할 것이다.
<img src="https://velog.velcdn.com/images/hwan_lee/post/24c4cf91-22c9-4ad6-ba7b-37dc38db65f1/image.png" alt="">
<strong>json파일 저장완료..!! 이제 이걸 exel로 예쁘게 변환하면 된다.</strong>
<img src="https://velog.velcdn.com/images/hwan_lee/post/f0150ca3-14bb-4ab3-97d0-6c1bc98deba6/image.png" alt=""></p>
<blockquote>
<p>아따 고놈 참 이쁘네잉</p>
</blockquote>
<h2 id="마치면서">마치면서</h2>
<p>강의계획서는 개강하고 나서도 2주 정도는 계속 조금씩 데이터가 바뀐다. 그래서 변경된 데이터를, 다시 엑셀 파일을 보내줘야 한다.</p>
<p>교수도 장소도 시간도 바뀌고 이래버리면 정보에 문제가 생긴다. 심지어 과목명, 교수명까지 틀려버리면 에브리타임 내에 강의평가 데이터도 틀어져버린다. 데이터가 분리된다.</p>
<p>따라서 학생 여러분들의 끝없는 관심과 참여가 필요합니다..!! 앞으로도 지속적으로 업데이트를 할 것이며, 데이터가 잘못된게 있으면 문의 주십시요!!</p>
<p>[형식]
LMS상의 교과목명: 
교과코드:
분반:
기존 정보:
수정 요청 정보:</p>
<blockquote>
<p>제1관리자: 아직 유지보수기간은 아니기 때문에 비공개
제2관리자: 아직 본인한테 공개의사 안 물어봄</p>
</blockquote>
<h2 id="현황">현황</h2>
<blockquote>
<p><strong>개발: 20241025<del>20241103
2024년2학기시간표제출: 20241103
2024년2학기시간표승인: 기간에 지남에 따라 승인거부
유지 및 보수: 20241103</del>(현재진행)
업데이트여부: 진행X</strong></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>