<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>So0oomm_0.log</title>
        <link>https://velog.io/</link>
        <description>개인 기록용 블로그</description>
        <lastBuildDate>Fri, 23 May 2025 04:37:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. So0oomm_0.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/soooomm_00" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[그래프 문제, 왜 자꾸 나올까요?]]></title>
            <link>https://velog.io/@soooomm_00/graph-when-how</link>
            <guid>https://velog.io/@soooomm_00/graph-when-how</guid>
            <pubDate>Fri, 23 May 2025 04:37:30 GMT</pubDate>
            <description><![CDATA[<p><em>알고리즘 문제 풀이 스터디를 하며 만든 자료입니다.
개인 공부 기록용으로 올립니다.</em></p>
<hr>
<h2 id="graph-란">Graph 란?</h2>
<p>그래프는 <strong>정점(Vertex, 또는 노드(Node)라고도 함)</strong>과 이들을 연결하는 <strong>간선(Edge)</strong>로 구성된 비선형 <strong>자료구조</strong>
<img src="https://velog.velcdn.com/images/soooomm_00/post/34e2ddda-871c-4745-8037-5178c67ee2d2/image.png" alt="graph"></p>
<h2 id="graph-용어-정리">Graph 용어 정리</h2>
<p>간략한 그래프 용어 정리</p>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>정점(Vertex),<br> 노드(Node)</strong></td>
<td>그래프에서 간선이 만나는 지점<br> 하나의 점으로, 데이터 저장시 사용</td>
</tr>
<tr>
<td><strong>간선(Edges)</strong></td>
<td>노드 간의 관계 또는 연결을 나타내는 선</td>
</tr>
<tr>
<td><strong>차수(Degree)</strong></td>
<td>노드에 연결된 간선(Edge)의 개수<br>(방향 그래프에서는 in-degree, out-degree로 나뉨)</td>
</tr>
<tr>
<td><strong>가중치(Weight)</strong></td>
<td>간선에 부여된 값 (예: 거리, 비용 등), 특성을 나타냄</td>
</tr>
<tr>
<td><strong>사이클(Cycle)</strong></td>
<td>같은 노드으로 되돌아오는 경로(시작 노드와 끝 노드가 동일한 경로)</td>
</tr>
</tbody></table>
<p>예시</p>
<ul>
<li>정점 :  장소</li>
<li>간선 : 길</li>
<li>가중치 : 거리/요금</li>
</ul>
<h2 id="graph의-특징">Graph의 특징</h2>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/5f6c13b8-a06b-48ad-8b4d-ee50029efda7/image.png" alt=""></p>
<ul>
<li><strong>방향성</strong> :  방향(Directed) / 무방향(Undirected)</li>
<li><strong>사이클 유무</strong> : 사이클(Cyclic) / 비사이클(Acyclic)</li>
<li><strong>가중치 유무</strong> : 가중치(Weighted) / 무가중치(Unweighted)</li>
<li><strong>간선의 밀도</strong> : 밀집(Dense) / 희소(Sparse)</li>
</ul>
<h2 id="graph-구현-방법">Graph 구현 방법</h2>
<p>그래프를 표현하는 대표적인 방식은 아래 두 가지입니다.
<img src="https://velog.velcdn.com/images/soooomm_00/post/30f07bef-d663-4e74-86a7-ad636a34adae/image.png" alt="인접 리스트, 인접 행렬"></p>
<h3 id="1-인접-행렬-adjacency-matrix">1. 인접 행렬 (Adjacency matrix)</h3>
<p> <strong>관계를 2차원 배열</strong>로 표현</p>
<ul>
<li><strong>노드간 연결 여부를 빠르게 확인</strong> 가능</li>
<li>모든 노드의 관계를 행렬로 저장하므로 <strong>메모리 소비 큼</strong></li>
</ul>
<h3 id="2-인접-리스트-adjacency-list">2. 인접 리스트 (Adjacency List)</h3>
<p><strong>관계를 링크드 리스트</strong>로 표현</p>
<ul>
<li>배열 방에 노드를 나열, 연결되는 노드를 링크드 리스트로 쭉 나열</li>
<li><strong>메모리 효율적, 연결된 정점 탐색에 유리</strong></li>
</ul>
<blockquote>
<p>💡 <strong>언제 무엇을 사용할까?</strong>
간선(Edge)이 많은 밀집 그래프 → <strong>인접 행렬(Adjacency matrix)</strong>
간선(Edge)이 적은 희소 그래프 → <strong>인접 리스트(Adjacency List)</strong></p>
</blockquote>
<h2 id="graph-탐색-알고리즘">Graph 탐색 알고리즘</h2>
<p>대표적인 그래프 알고리즘 3가지</p>
<ul>
<li><strong>그래프 탐색 : DFS , BFS</strong></li>
<li><strong>최단 경로 : Dijkstra</strong></li>
</ul>
<h3 id="📌-dfsdepth-first-search-깊이-우선-탐색">📌 DFS(Depth-First Search, 깊이 우선 탐색)</h3>
<p><strong>가능한 깊게 탐색한 후</strong>, 더 이상 갈 곳이 없으면 <strong>이전 정점으로 되돌아가는 방식</strong>
다른 경로로 넘어가기전 해당 분기를 완벽하게 탐색 
⇒ <strong>세로 탐색</strong></p>
<ul>
<li>주로 <strong>스택(Stack)</strong> 또는 <strong>재귀 함수</strong>로 구현</li>
</ul>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/fedf658c-c330-447d-9890-28cffcfc10ec/image.gif" alt="DFS"></p>
<h4 id="✅-장점">✅ 장점</h4>
<ul>
<li>현 경로상의 노드만 기억 ⇒ <strong>메모리 효율 좋음</strong></li>
<li><strong>목표 노드가 깊은 위치에 있는 경우, 빠르게 도달 가능</strong></li>
</ul>
<h4 id="❌-단점">❌ 단점</h4>
<ul>
<li><strong>해가 없는 경로를 깊이 탐색할 수 있어 비효율적</strong></li>
<li><strong>최단 경로를 보장하지 않음</strong></li>
<li>깊이가 깊으면 <strong>스택 오버플로우 위험</strong><ul>
<li>⚠️ 깊이 제한 두는 방법으로 해결할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="📌-bfsbreadth-first-search-너비-우선-탐색">📌 BFS(Breadth-First Search, 너비 우선 탐색)</h3>
<p><strong>한 노드에서 시작해 인접한 노드부터 모두 탐색하고 이후 그 인접한 노드의 인접한 노드 반복…</strong> ⇒ 가로 탐색</p>
<ul>
<li>주로 <strong>큐(Queue)</strong> 사용해 구현</li>
</ul>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/fd0cc3a1-af36-4231-bc81-295b790fa7c3/image.gif" alt="BFS"></p>
<h4 id="✅-장점-1">✅ 장점</h4>
<ul>
<li><strong>최단 경로 탐색에 유리(특히 무가중치 그래프)</strong></li>
<li>경로가 무한히 깊어져도 <strong>반드시 해를 찾을 수 있음</strong></li>
<li><strong>노드 수가 적고 깊이가 앝은 해가 존재할 때 유리</strong></li>
</ul>
<h4 id="❌-단점-1">❌ 단점</h4>
<ul>
<li>탐색을 위한 큐 사용 → <strong>메모리 사용량 증가</strong></li>
</ul>
<h3 id="dfs-vs-bfs">DFS vs BFS</h3>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/515f4632-90fb-4c33-b682-69aab456ee01/image.gif" alt="DFS vs BFS"></p>
<table>
<thead>
<tr>
<th></th>
<th>DFS</th>
<th>BFS</th>
</tr>
</thead>
<tbody><tr>
<td><strong>탐색 방식</strong></td>
<td>깊이 우선<br>분기마다 깊게 탐색</td>
<td>너비 우선<br>인접 노드부터 탐색</td>
</tr>
<tr>
<td><strong>구현 방법</strong></td>
<td>스택, 재귀 함수</td>
<td>큐</td>
</tr>
<tr>
<td><strong>주 사용처</strong></td>
<td>미로 찾기, 사이클 탐지, 백트래킹</td>
<td>최단 거리 탐색, 전염병 확산 시뮬레이션</td>
</tr>
<tr>
<td><strong>메모리</strong></td>
<td>호출 깊이만큼 사용</td>
<td>큐 크기만큼 사용</td>
</tr>
<tr>
<td><strong>최단 경로 보장</strong></td>
<td>❌</td>
<td>⭕️</td>
</tr>
</tbody></table>
<h3 id="dfs와-bfs의-시간-복잡도"><strong>DFS와 BFS의 시간 복잡도</strong></h3>
<ul>
<li><strong>인접 리스트 : <code>O(N + E)</code></strong></li>
<li><strong>인접 행렬 : <code>O(N^2)</code></strong></li>
</ul>
<blockquote>
<p>N: 노드의 개수, E: 간선의 개수
→ 일반적으로 <strong>인접 리스트 방식이 더 효율적</strong></p>
</blockquote>
<p>두 방식 모두 조건 내의 모든 노드를 검색한다는 점에서 시간 복잡도는 동일
BUT 일반적으로 DFS를 재귀 함수로 구현한다는 점에서 DFS보다 BFS가 조금 더 빠르게 동작함</p>
<h3 id="📌-dijkstra-알고리즘-최단-경로-알고리즘">📌 Dijkstra 알고리즘 (최단 경로 알고리즘)</h3>
<p>그래프에서 <strong>한 노드에서 다른 모든 노드까지의 최단 경로</strong>를 구하는 알고리즘
→ 매번 최단 경로의 정점을 선택해 탐색을 반복하는 것</p>
<ul>
<li><strong>우선순위 큐 (Heap) 활용 :  <code>(O(E log V))</code></strong></li>
<li>BFS + DP 개념으로 이해하면 편함
<img src="https://velog.velcdn.com/images/soooomm_00/post/f042795f-8b24-4d4a-aa43-7241495b1cd3/image.gif" alt=""></li>
</ul>
<h4 id="✅-장점-2">✅ 장점</h4>
<ul>
<li>거리뿐만 아니라 <strong>실제 경로 추적도 쉽게 구현 가능</strong></li>
<li><strong>가중치가 있는 그래프에서 안정적인 최단 경로 탐색</strong>이 가능</li>
</ul>
<h4 id="❌-단점-2">❌ 단점</h4>
<ul>
<li>간선(Edge)이 많은 대규모 그래프에서 큐 연산이 많아져 속도가 느려질 수 있음</li>
<li><strong>그래프와 거리 정보를 저장해야 하므로 메모리 사용량이 많음</strong></li>
<li><strong>단일 최단 경로만 제공</strong>, 동일 거리의 경로가 여러 개 있어도 전부 찾지는 않음</li>
</ul>
<h3 id="실제-활용-사례">실제 활용 사례</h3>
<ul>
<li>SNS 친구 추천 → 그래프 탐색</li>
<li>지도앱 길찾기 → 다익스트라(Dijkstra)</li>
<li>게임 맵 탐색, AI 이동 경로 계산 → DFS / BFS</li>
</ul>
<h3 id="graph-알고리즘-선택-가이드">Graph 알고리즘 선택 가이드</h3>
<table>
<thead>
<tr>
<th>알고리즘</th>
<th>특징</th>
<th>주 사용처</th>
</tr>
</thead>
<tbody><tr>
<td><strong>DFS</strong></td>
<td>최대한 깊게 탐색</td>
<td>사이클 탐지, 경로 존재 여부</td>
</tr>
<tr>
<td><strong>BFS</strong></td>
<td>가까운 노드부터 탐색</td>
<td><strong>최단 거리 탐색</strong> (무가중치 그래프)</td>
</tr>
<tr>
<td><strong>Dijkstra</strong></td>
<td>가까운 거리부터 최단 거리 갱신</td>
<td>실시간 길찾기<br>(<strong>가중치 그래프의 최단 경로</strong> 탐색)</td>
</tr>
</tbody></table>
<h2 id="reference">Reference</h2>
<hr>
<ul>
<li><a href="https://www.youtube.com/watch?v=fVcKN42YXXI">Youtube - [자료구조 알고리즘] 그래프(Graph)에 대해서</a></li>
<li><a href="https://medium.com/@sarinyaswift/intro-to-the-graph-data-structure-a8277c6a2ad9">Intro To The Graph Data Structure</a></li>
<li><a href="https://racross1.medium.com/graph-data-structures-and-databases-33e8351f5679">Graph Data Structures and Databases</a></li>
<li><a href="https://www.geeksforgeeks.org/dsa/basic-properties-of-a-graph/">Basic Properties of a Graph</a></li>
<li><a href="https://yozm.wishket.com/magazine/detail/2411/">그래프 알고리즘 종류와 활용 방법</a></li>
<li><a href="https://sarah950716.tistory.com/12">[그래프] 인접 행렬과 인접 리스트</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Brute Force가 느린데 왜 쓸까? - 완전 탐색 이해하기]]></title>
            <link>https://velog.io/@soooomm_00/brute-force-basics</link>
            <guid>https://velog.io/@soooomm_00/brute-force-basics</guid>
            <pubDate>Tue, 06 May 2025 06:09:26 GMT</pubDate>
            <description><![CDATA[<p><em>알고리즘 문제 풀이 스터디를 하며 만든 자료입니다.
개인 공부 기록용으로 올립니다.</em></p>
<hr>
<h3 id="🗝️-비밀번호를-맞추는-가장-확실한-방법">🗝️ 비밀번호를 맞추는 가장 확실한 방법</h3>
<p>비밀번호가 4자리 숫자일 때, 단서가 전혀 없다면 어떻게 풀어야 할까요?
<strong>0000부터 9999까지 전부 입력해보는 수밖에 없습니다.</strong></p>
<p>이런 식으로 <strong>모든 경우의 수를 전부 시도</strong>하는 전략을
→  <strong>브루트 포스(Brute Force)</strong> 혹은 <strong>완전 탐색</strong>이라고 합니다.</p>
<h2 id="📌-브루트-포스brute-force란">📌 브루트 포스(Brute Force)란?</h2>
<pre><code class="language-javascript">// 0000 ~ 9999까지 모든 경우 탐색
for (let i = 0; i &lt; 10000; i++) {
  if (check(i)) {  // 어떤 조건을 만족하면
    console.log(`정답: ${i}`);
    break;
  }
}</code></pre>
<p><strong>브루트 포스(Brute Force, 완전 탐색)</strong>는 가능한 모든 경우의 수를 탐색하여 조건을 만족하는 결과를 찾아내는 <strong>문제해결 패러다임</strong>입니다.</p>
<blockquote>
<p>💡 <strong>Brute</strong>: 단순히, 순전히 / <strong>Force</strong>: 힘<br>→ 즉, 무식한 힘으로 가능한 모든 경우를 탐색하는 방법</p>
</blockquote>
<hr>
<h2 id="브루트-포스를-쓰는-이유">브루트 포스를 쓰는 이유</h2>
<h3 id="✅-장점">✅ 장점</h3>
<ul>
<li><strong>구현이 상대적으로 단순하고 직관적</strong></li>
<li><strong>정답이 존재하는 경우 반드시 찾게 됨</strong></li>
<li>복잡한 조건이 있어도 적용 가능</li>
</ul>
<h3 id="❌-단점">❌ 단점</h3>
<ul>
<li><strong>시간 복잡도에 매우 민감</strong></li>
<li><strong>입력 크기가 커질수록 실행 시간 급격히 증가</strong></li>
<li>메모리 사용량이 클 수 있음</li>
</ul>
<blockquote>
<p>⚠️ N = 20인 경우 가능한 경우의 수는 약 100만개(2²⁰)가 되어버림</p>
</blockquote>
<hr>
<h2 id="브루트-포스의-사용-조건">브루트 포스의 사용 조건</h2>
<ol>
<li><strong>문제의 정답 조건이 명확해야 함</strong> 
: 어떤 상태가 정답인지 판별할 수 있어야 함</li>
<li><strong>가능한 경우의 수가 제한적이어야 함</strong>
: 솔루션의 수가 무한하거나 너무 크면 비효율적</li>
</ol>
<h3 id="✅-언제-사용하면-좋을까">✅ 언제 사용하면 좋을까?</h3>
<ol>
<li><p><strong>입력 범위가 작은 경우</strong></p>
<ul>
<li>N ≤ 20인 경우: 2^N ≈ 100만개 정도</li>
<li>N ≤ 10인 경우: N! ≈ 300만개 정도</li>
</ul>
</li>
<li><p><strong>다른 알고리즘으로 해결하기 어려운 경우</strong></p>
<ul>
<li>복잡한 조건이 많아 수학적 공식화가 어려운 경우</li>
<li>문제의 패턴을 찾기 어려운 경우</li>
</ul>
</li>
<li><p><strong>확실한 정답이 필요한 경우</strong></p>
<ul>
<li>근사해가 아닌 정확한 답이 필요할 때</li>
</ul>
</li>
</ol>
<hr>
<h2 id="완전-탐색의-구현-방식">완전 탐색의 구현 방식</h2>
<table>
<thead>
<tr>
<th>탐색 방식</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>단순 반복문</strong></td>
<td>이중 반복문으로 모든 조합 탐색</td>
<td>Two Sum 문제</td>
</tr>
<tr>
<td><strong>부분집합 (Subset)</strong></td>
<td>포함/비포함 2가지 선택</td>
<td>비트마스크, 재귀</td>
</tr>
<tr>
<td><strong>순열 (Permutation)</strong></td>
<td>원소를 일렬로 배치</td>
<td>순열 생성</td>
</tr>
<tr>
<td><strong>조합 (Combination)</strong></td>
<td>r개를 선택</td>
<td>조합 문제</td>
</tr>
<tr>
<td><strong>DFS/BFS</strong></td>
<td>그래프 등에서의 완전 탐색</td>
<td>미로 탐색 등</td>
</tr>
</tbody></table>
<p>DFS, BFS, 백트래킹은 추후 자세히 설명</p>
<hr>
<h2 id="문제-해결-예시">문제 해결 예시</h2>
<h3 id="1-반복문-활용---two-sum">1. 반복문 활용 - Two Sum</h3>
<h4 id="🔗-leetcode---two-sum">🔗 <a href="https://leetcode.com/problems/two-sum">Leetcode - Two Sum</a></h4>
<p>배열 nums와 목표값 target이 주어졌을 때, 두 수의 합이 target과 같아지는 두 원소의 인덱스를 반환</p>
<p>예제 :
nums = [2,7,11,15], target = 9
→ [0,1]  (nums[0] + nums[1] = 2 + 7 = 9)</p>
<pre><code class="language-javascript">// 시간 복잡도 : O(N^2)
var twoSum = function(nums, target) {
    const n = nums.length;
    for(let i=0; i&lt;n-1; i++){
        for(let j=i+1; j&lt;n; j++){
            if(nums[i]+nums[j]===target) return [i,j]
        }
    }
   return []
};</code></pre>
<p>모든 두 숫자 조합을 검사 → 시간복잡도 O(N²)
정확하지만 느릴 수 있음</p>
<h3 id="재귀-활용---부분수열의-합">재귀 활용 - 부분수열의 합</h3>
<h4 id="🔗-백준---부분수열의-합">🔗 <a href="https://www.acmicpc.net/problem/1182">백준 - 부분수열의 합</a></h4>
<p>N개의 정수로 이루어진 수열이 있을 때, 크기가 양수인 부분수열 중에서 그 수열의 원소를 다 더한 값이 S가 되는 경우의 수를 반환</p>
<pre><code class="language-javascript">function solution(N,S,arr) {
  let count = 0;

  function dfs(index,sum){
    if(index === N){
      if(sum === S){
        count++;
      }
      return;
    }

    dfs(index + 1, sum + arr[index]); // 포함
    dfs(index + 1, sum);              // 비포함
  }
  dfs(0,0);

  // 크기가 양수인 부분 수열만 인정
  if (S === 0) count--;
  return count;
}
</code></pre>
<p>원소마다 포함/비포함(2가지 선택)
시간복잡도: O(2ⁿ)</p>
<hr>
<h2 id="마무리-요약">마무리 요약</h2>
<p><strong>브루트 포스는 모든 경우의 수를 하나하나 다 시도해보는 방법입니다. 경우의 수가 적고, 무조건 정답을 찾고 싶을 때 강력한 전략이 됩니다.</strong></p>
<p><strong>브루트 포스만으로 풀 수 있는 문제는 드물지만, 어떤 문제든 &#39;부분적 완전 탐색&#39;이 필요한 경우는 매우 많습니다.</strong>  </p>
<p>개인적으로 브루트 포스, 완전 탐색이라는 두 단어가 완전히 일치한 용어인가? 라는 질문이 들어서 시간이 걸렸던거 같습니다.</p>
<p>엄밀히 구분하는 일부 자료에서는 다음과 같이 의미상의 미세한 차이를 언급합니다.</p>
<table>
<thead>
<tr>
<th>용어</th>
<th>강조점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>완전 탐색</strong></td>
<td>모든 경우를 빠짐없이 찾는 &quot;탐색 과정&quot;에 중점</td>
</tr>
<tr>
<td><strong>브루트 포스</strong></td>
<td>정답을 무조건 찾기 위한 &quot;시도 자체&quot;에 중점</td>
</tr>
</tbody></table>
<p>하지만 실전에서는 두 용어를 구별해서 쓸 필요는 거의 없고, 동의어로 여겨진다고 합니다.</p>
<h2 id="reference">Reference</h2>
<hr>
<ul>
<li><a href="https://youtu.be/ZNa9-86uVEA?si=oIb1LGIGbBMi-xah">Youtube - 브루트 포스 완전 탐색 알고리즘 3분만에 이해하기</a></li>
<li><a href="https://velog.io/@limchard/Algorithm-%EC%A0%84%EC%B2%B4%ED%83%90%EC%83%89-%EB%B8%8C%EB%A3%A8%ED%8A%B8%ED%8F%AC%EC%8A%A4-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Brute-Force-Algorithm">전체탐색 : 브루트포스 알고리즘 (Brute Force Algorithm)</a></li>
<li><a href="https://devyoseph.tistory.com/76">알고리즘: 선형/비선형 구조와 브루트포스</a></li>
<li><a href="https://velog.io/@dlzagu/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B3%B5%EB%B6%80-%EC%99%84%EC%A0%80%ED%83%90%EC%83%89%EA%B8%B0%EB%B2%95-%EB%B9%84%ED%8A%B8%EB%A7%88%EC%8A%A4%ED%81%AC-%EC%88%9C%EC%97%B4-%EC%9E%AC%EA%B7%80">[알고리즘 공부] 완전탐색기법 - 비트마스크, 순열 ,재귀</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DP, 언제 쓰고 어떻게 짤까?]]></title>
            <link>https://velog.io/@soooomm_00/dp-when-how</link>
            <guid>https://velog.io/@soooomm_00/dp-when-how</guid>
            <pubDate>Thu, 17 Apr 2025 05:14:50 GMT</pubDate>
            <description><![CDATA[<hr>
<p><em>알고리즘 문제 풀이 스터디를 하며 만든 자료입니다.
개인 공부 기록용으로 올립니다.</em></p>
<h2 id="dpdynamic-programming이란">DP(Dynamic Programming)이란?</h2>
<p><strong>Dynamic Programming(동적 계획법)</strong>은 하나의 큰 문제를 여러 개의 작은 문제로 나누어서, 그 결과를 저장하여 다시 큰 문제를 해결할 때 사용하는 <strong>문제해결 패러다임</strong>입니다.</p>
<h3 id="❓-왜-dp가-필요할까">❓ 왜 DP가 필요할까?</h3>
<p>완전탐색, DFS, BFS로 해결할 수 있지만 <strong>경우의 수가 너무 많아 속도가 느려지는 문제</strong>들을 효율적으로 해결하기 위해 등장했습니다.</p>
<blockquote>
<p>💡 한 교수는 DP를 <strong>&quot;기억하기 알고리즘&quot;</strong>이라고 번역하기도 했습니다.</p>
</blockquote>
<hr>
<h2 id="dp의-원리와-조건">DP의 원리와 조건</h2>
<h3 id="📌-핵심-원리">📌 핵심 원리</h3>
<ul>
<li><strong>메모리를 사용해서 중복 연산을 줄이고, 중복 연산을 줄여서 수행 속도를 개선</strong><ul>
<li><strong>메모리 사용</strong> : 배열 or 자료구조를 만든다.</li>
<li><strong>중복 제거</strong> : 연산 결과를 배열에 담는다. 같은 연산을 또 하지 않음</li>
<li><strong>재사용</strong>: 겹치는 서브 문제는 한 번만 계산하고 결과를 재사용</li>
</ul>
</li>
</ul>
<h3 id="dp의-사용-조건">DP의 사용 조건</h3>
<ol>
<li><p><strong>최적 부분 구조 (Optimal Substructure)</strong></p>
<ul>
<li>큰 문제의 최적해가 작은 문제들의 최적해로 구성됨</li>
</ul>
</li>
<li><p><strong>중복 부분 문제 (Overlapping Subproblems)</strong></p>
<ul>
<li>같은 작은 문제들이 반복적으로 계산됨</li>
</ul>
</li>
</ol>
<hr>
<h2 id="dp-사용-시점-판별법">DP 사용 시점 판별법</h2>
<h3 id="✅-dp를-사용해야-하는-경우">✅ DP를 사용해야 하는 경우</h3>
<ol>
<li><p><strong>DFS/BFS로 풀 수 있지만 경우의 수가 너무 많은 문제</strong></p>
<ul>
<li>일반적으로 DFS/BFS로 처리 가능한 경우의 수: 약 500만개</li>
<li>그 이상이면 DP 고려</li>
</ul>
</li>
<li><p><strong>중복적인 연산이 많이 발생하는 경우</strong></p>
<ul>
<li>같은 하위 문제가 여러 번 계산됨</li>
<li>최적값을 구하는 과정에서 불필요한 계산 반복</li>
</ul>
</li>
</ol>
<h3 id="주요-문제-유형">주요 문제 유형</h3>
<ul>
<li><strong>최적화 문제</strong>: 최대값, 최소값을 구하는 문제</li>
<li><strong>경우의 수</strong>: 가능한 방법의 수를 구하는 문제</li>
<li><strong>경로 탐색</strong>: 최단 경로, 최적 경로 문제</li>
</ul>
<p><strong>예시</strong>: 가장 빨리 도착하는 경로의 소요 시간, 주식 매매 최적 타이밍</p>
<h3 id="💡-dp-문제-해결의-핵심-질문">💡 DP 문제 해결의 핵심 질문</h3>
<blockquote>
<p><strong>&quot;어떻게 해야 뒤로 돌아가지 않을 수 있을까?&quot;</strong></p>
</blockquote>
<hr>
<h2 id="구현-방식--top-down-vs-bottom-up">구현 방식 : Top-Down vs Bottom-Up</h2>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/e9df5c1a-d9a5-4134-b08a-2a8e871ea506/image.png" alt=""></p>
<table>
<thead>
<tr>
<th></th>
<th>Top-Down</th>
<th>Bottom-Up</th>
</tr>
</thead>
<tbody><tr>
<td><strong>구조</strong></td>
<td>recursive(재귀)</td>
<td>iterative(반복문)</td>
</tr>
<tr>
<td><strong>subproblem 결과 저장</strong></td>
<td>memoization</td>
<td>tabulation</td>
</tr>
<tr>
<td><strong>선호하는 경우</strong></td>
<td>subproblem의 일부만 계산해도 최종 솔루션을 구할 수 있을 때</td>
<td>모든 subproblem을 계산해야 최종 솔루션을 구할 수 있을때</td>
</tr>
</tbody></table>
<hr>
<h3 id="⬇️-top-down-memoization">⬇️ Top-Down (Memoization)</h3>
<p><strong>큰 문제를 해결하기 위해, 재귀적으로 작은 문제를 호출하며 해결</strong></p>
<ul>
<li>dp[n] 같은 목표상태에서 출발</li>
<li>필요한 하위 문제를 재귀적으로 호출</li>
<li>이미 계산된 값은 메모리에 저장되 있던 내역 꺼내서 활용해 중복 계산 방지</li>
</ul>
<p>→ <strong>큰 문제에서 작은 문제로(top-down) 내려가며 해결하는 방식</strong></p>
<blockquote>
<p>⚠️ <strong>주의사항</strong>: 함수 호출이 많아지면 호출 오버헤드, 스택 오버플로우 위험 있음</p>
</blockquote>
<h3 id="⬆️-bottom-up-tabulation">⬆️ Bottom-Up (Tabulation)</h3>
<p><strong>작은 규모의 문제부터 시작해 전체 큰 문제를 해결</strong></p>
<ul>
<li>dp[0] 같은 베이스부터 출발</li>
<li>반복문을 사용해 테이블을 채워나가는(table-filling) 방식</li>
<li>모든*하위 문제를 반복문으로 순차적으로 해결하면서 결과를 테이블에 저장해 중복 계산을 방지</li>
</ul>
<p>→ <strong>작은 문제를 해결하면서 점점 위로(bottom-up) 올라가는 방식 (주로 사용)</strong></p>
<p>💡 <strong>장점</strong>: 스택 오버플로우 걱정 없음, 경우에 따라 공간 최적화 가능
ex. 직전 값만 사용하는 경우 리스트가 아닌 변수 사용</p>
<blockquote>
<p><strong>근본적인 개념으로 &quot;결과값을 기억하고 재활용&quot; 한다는 측면에서는 Memoization, Tabulation이 크게 다르지 않음</strong></p>
</blockquote>
<hr>
<h2 id="실전-문제-해결-예시">실전 문제 해결 예시</h2>
<h3 id="top-down-예시-climbing-stairs">Top-Down 예시: Climbing Stairs</h3>
<h4 id="leetcode---climbing-stairs"><a href="https://leetcode.com/problems/climbing-stairs/description/">Leetcode - Climbing Stairs</a></h4>
<p>정상까지 오르는데 N번의 스텝이 필요한 계단에서 한번에 한 스텝 혹은 두 스텝을 오를 수 있을때 정상까지 올라가는 방법의 수는?</p>
<h4 id="문제-분석"><strong>문제 분석</strong></h4>
<ul>
<li>한번에 한스텝 혹은 두 스텝을 오를 수 있음</li>
<li>n = 6일때,이전 계단은 5층 or 4층 
→ 해당 값에서 시작해 계속 아래로 내려간다.</li>
<li>점화식: <code>climb(n) = climb(n-1) + climb(n-2)</code><h4 id="step-1-일반-재귀-비효율적">Step 1: 일반 재귀 (비효율적)</h4>
</li>
</ul>
<pre><code class="language-js">// 시간복잡도: O(2^n) - 매우 비효율적!
function climb(n){
    if(n&lt;=2) return n;
    return climb(n-1) + climb(n-2)
}</code></pre>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/b19e37f9-8c25-464f-afb5-151784f3d6ed/image.png" alt="재귀 호출 트리"></p>
<p><strong>문제점</strong> : 동일한 input에 대한 function call이 많은 것을 확인 할 수 있음
→ 메모해 뒀다가 이후 재사용하는 방식으로 변경</p>
<h4 id="step-2-top-down-dp-memoization">Step 2: Top-Down DP (Memoization)</h4>
<pre><code class="language-js">// 시간복잡도: O(n)
let memo = [];
function climb(n){
    if(n&lt;=2) return n;

    // 이미 계산된 값이 있으면 바로 반환
    if (memo[n] !== undefined) 
        return memo[n];

    // 없으면 계산 후 저장
    memo[n] = climb(n-1) + climb(n-2);
    return memo[n]
}</code></pre>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/920e310f-82e8-446a-aab0-49d34a556276/image.png" alt="Memoization 최적화"></p>
<p><strong>Top-Down of DP</strong></p>
<ul>
<li>recursive(재귀)</li>
<li>memoization(메모리에 저장)<ul>
<li>funcatioin call의 결과를 저장해서 이후에 같은 input에 대한 호출은 저장한 결과를 사용하는 것</li>
</ul>
</li>
</ul>
<hr>
<h3 id="bottom-up-예시-연속합">Bottom-Up 예시: 연속합</h3>
<h4 id="백준---연속합"><a href="https://www.acmicpc.net/problem/1912">백준 - 연속합</a></h4>
<p>n개 정수 수열에서 연속된 몇 개의 수를 선택해서 구할 수 있는 합 중 가장 큰 값은?
예시: [10, -4, 3, 1, 5, 6, -35, 12, 21, -1] -&gt; 33(12+21)</p>
<h4 id="문제-분석-1"><strong>문제 분석</strong></h4>
<p>arr[i] 까지 왔을때, 최대인 연속 합이 나오는 2가지</p>
<ol>
<li>arr[i] 자체만 선택</li>
<li>이전까지의 최대 연속합에 arr[i] 를 더하는 경우</li>
</ol>
<ul>
<li><strong>점화식</strong>: <code>dp[i] = Math.max(arr[i], dp[i-1] + arr[i])</code></li>
</ul>
<h4 id="step-1-기본-dp-구현">Step 1: 기본 DP 구현</h4>
<pre><code class="language-js">// 시간복잡도: O(n), 공간복잡도: O(n)
function solution(N, arr) {
  const dp = Array(N).fill(0);
  dp[0] = arr[0];
  let maxSum = dp[0];

  for (let i = 1; i &lt; N; i++) {
    dp[i] = Math.max(arr[i], dp[i - 1] + arr[i]);
    maxSum = Math.max(maxSum, dp[i]);
  }

  return maxSum;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/b93c1875-8b17-4692-90e8-6bc5953fd357/image.png" alt="DP 테이블 채우기"></p>
<p>잘 보면 결국 바로 직전의 값만 사용하는 것을 확인할 수 있음
→ 꼭 배열 형태로 사용할 필요가 없다!</p>
<h4 id="step-2-메모리-ㅊ최적화">Step 2: 메모리 ㅊ최적화</h4>
<p>dp 배열 없이 현재 상태만 저장해서 메모리 사용을 줄임</p>
<pre><code class="language-js">// 시간복잡도: O(n), 공간복잡도: O(1)
function solution2(N, arr) {
  let current = arr[0];
  let maxSum = arr[0];

  for (let i = 1; i &lt; N; i++) {
    current = Math.max(arr[i], current + arr[i]);
    maxSum = Math.max(maxSum, current);
  }

  return maxSum;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/4a9d700c-214e-47d0-8022-8d6ea84214df/image.png" alt="메모리 최적화"></p>
<h2 id="실제-활용-사례">실제 활용 사례</h2>
<ol>
<li>Google Maps의 경로 탐색
: 다양한 경로 중에서 최단 경로를 순차적으로 찾는 데 활용</li>
<li>네트워크 데이터 전송
: 송신자에서 여러 수신자에게 데이터를 순차적으로 전송할 때 최적화된 경로를 찾는 데 사용</li>
<li>문서 유사도 분석
: Google, Wikipedia, Quora 등의 검색 엔진에서 두 텍스트 문서 간의 유사성 정도를 측정하는 알고리즘에 활용</li>
<li>맞춤법 검사
: 편집 거리(Edit Distance) 알고리즘을 사용하여 오타를 찾고 올바른 단어를 제안하는 맞춤법 검사기에서 활용</li>
<li>데이터베이스 캐싱 시스템</li>
<li>Git 병합 (Git Merge)
: 문서 비교에서 최장 공통 부분수열(LCS) 알고리즘을 사용하는 대표적인 사례</li>
<li>유전 알고리즘
: 최적화 문제를 해결하는 유전 알고리즘의 핵심 연산에 활용</li>
</ol>
<h2 id="reference">Reference</h2>
<hr>
<ul>
<li><a href="https://www.youtube.com/watch?v=0bqfTzpWySY">YouTube - DP 동적계획 다이나믹 프로그래밍 알고리즘 설명 10분만에 이해하기</a></li>
<li><a href="https://www.youtube.com/watch?v=GtqHli8HIqk&amp;t=1685s">YouYube - 코딩테스트에서 많이 사용되는 dynamic programming(동적 계획법)의 개념</a></li>
<li><a href="https://hongjw1938.tistory.com/47">알고리즘 - Dynamic Programming(동적 계획법)</a></li>
<li><a href="https://stackoverflow.com/questions/62927013/application-of-dynamic-programming-in-real-world-programming">Application of dynamic programming in real world programming</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL Docker 컨테이너 설정과 DBeaver 연결 쉽게 따라하기!]]></title>
            <link>https://velog.io/@soooomm_00/mysql-docker-dbeaver-setup</link>
            <guid>https://velog.io/@soooomm_00/mysql-docker-dbeaver-setup</guid>
            <pubDate>Wed, 21 Aug 2024 07:14:27 GMT</pubDate>
            <description><![CDATA[<p>📌  <strong>목표</strong></p>
<ol>
<li>Docker를 이용해 MySQL 컨테이너 설치하기</li>
<li>DBeaver를 통해 MySQL에 접속할 수 있도록 설정하기</li>
</ol>
<p>개인적인 기록용으로 작성합니다.</p>
<h2 id="1-docker-컨테이너-관리-명령어">1. <strong>Docker 컨테이너 관리 명령어</strong></h2>
<pre><code class="language-sh"># MySQL Docker 컨테이너 중지
$ docker stop mysql-container

# MySQL Docker 컨테이너 시작
$ docker start mysql-container

# MySQL Docker 컨테이너 재시작
$ docker restart mysql-container</code></pre>
<h2 id="2-docker-및-mysql-설치">2. <strong>Docker 및 MySQL 설치</strong></h2>
<h3 id="2-1-docker-설치">2-1. Docker 설치</h3>
<ol>
<li><a href="https://www.docker.com/">Docker 공식 홈페이지</a>에서 운영체제에 맞는 버전 설치</li>
<li>설치 완료 후, 아래 명령어로 정상적으로 설치되었는지 확인</li>
</ol>
<pre><code class="language-sh">docker -v</code></pre>
<h3 id="2-2-mysql-docker-이미지-다운로드">2-2. MySQL Docker 이미지 다운로드</h3>
<pre><code class="language-sh"># 최신 버전 다운로드
$ docker pull mysql

# 특정 버전 다운로드 (예: 8.0 버전)
$ docker pull mysql:8.0</code></pre>
<h3 id="2-3-다운로드한-docker-이미지-확인">2-3. 다운로드한 Docker 이미지 확인</h3>
<pre><code class="language-sh">$ docker images</code></pre>
<h2 id="3-mysql-docker-컨테이너-생성-및-실행">3. <strong>MySQL Docker 컨테이너 생성 및 실행</strong></h2>
<pre><code class="language-sh">$ docker run --name mysql-container \
  -e MYSQL_ROOT_PASSWORD=&lt;your-password&gt; \
  -d -p 3306:3306 mysql:8.0</code></pre>
<blockquote>
<p>🔹 <code>&lt;your-password&gt;</code> 부분에 원하는 비밀번호를 입력하세요.</p>
</blockquote>
<h2 id="4-docker-컨테이너-상태-확인">4. <strong>Docker 컨테이너 상태 확인</strong></h2>
<pre><code class="language-sh"># 실행 중인 컨테이너 확인
$ docker ps -a</code></pre>
<h2 id="5-mysql-docker-컨테이너-접속">5. <strong>MySQL Docker 컨테이너 접속</strong></h2>
<pre><code class="language-sh"># 컨테이너 내부로 접속
$ docker exec -it mysql-container bash

# MySQL 접속
$ mysql -u root -p</code></pre>
<blockquote>
<p>🔹 <code>&#39;Enter password:&#39;</code> 프롬프트가 나타나면, 앞서 설정한 비밀번호를 입력하세요.</p>
</blockquote>
<h2 id="6-mysql-사용자-및-데이터베이스-설정">6. <strong>MySQL 사용자 및 데이터베이스 설정</strong></h2>
<pre><code class="language-sql">-- 새로운 사용자 생성
CREATE USER &#39;username&#39;@&#39;%&#39; IDENTIFIED BY &#39;your-password&#39;;

-- 사용자에게 모든 권한 부여
GRANT ALL PRIVILEGES ON *.* TO &#39;username&#39;@&#39;%&#39;;

-- 변경 사항 적용
FLUSH PRIVILEGES;</code></pre>
<blockquote>
<p>🔹 <code>@&#39;%&#39;</code>는 외부 접속을 허용하는 설정입니다.</p>
</blockquote>
<h2 id="7-dbeaver-연결-설정">7. <strong>DBeaver 연결 설정</strong></h2>
<h3 id="7-1-dbeaver-설치-및-mysql-연결">7-1. <strong>DBeaver 설치 및 MySQL 연결</strong></h3>
<ol>
<li><a href="https://dbeaver.io/">DBeaver 공식 사이트</a>에서 프로그램 다운로드 및 설치</li>
<li>새로운 MySQL 연결 생성 후, 아래 정보 입력<ul>
<li><strong>Host:</strong> <code>localhost</code></li>
<li><strong>Port:</strong> <code>3306</code></li>
<li><strong>Database:</strong> 원하는 데이터베이스 이름 (없으면 비워둠)</li>
<li><strong>Username:</strong> 앞서 생성한 <code>username</code></li>
<li><strong>Password:</strong> 설정한 비밀번호 입력
<img src="https://velog.velcdn.com/images/soooomm_00/post/a9aa9c88-f3dc-4b39-9cf1-d157f034bdc8/image.png" alt=""></li>
</ul>
</li>
</ol>
<h3 id="7-2-public-key-retrieval-is-not-allowed-오류-해결">7-2. <strong><code>Public Key Retrieval is not allowed</code> 오류 해결</strong></h3>
<ol>
<li><strong>Driver Properties</strong> 탭 이동</li>
<li><code>allowPublicKeyRetrieval</code> 옵션을 <code>true</code>로 설정</li>
</ol>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/c89eee1d-9c33-4db4-a9c1-820fe3895eb5/image.png" alt=""></p>
<hr>
<h2 id="🔗-참고-자료">🔗 <strong>참고 자료</strong></h2>
<ul>
<li><a href="https://poiemaweb.com/docker-mysql">PoiemaWeb - Docker MySQL</a></li>
<li><a href="https://velog.io/@jinny-l/Docker%EB%A1%9C-MySQL-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0MySQL-5.7-Mac-M1">Docker로 MySQL 설치하기 (MySQL 5.7) - Mac M1</a></li>
<li><a href="https://robomoan.medium.com/mysql-%EB%8F%84%EC%BB%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%84%A4%EC%B9%98-%ED%9B%84-dbeaver-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0-cf945454cf1e">MySQL 도커 컨테이너 설치 후 DBeaver 연결하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS Guard에서 multipart/form-data 처리하는 법 – 왜 request.body가 비어있을까?]]></title>
            <link>https://velog.io/@soooomm_00/nestjs-guard-multipart-formdata</link>
            <guid>https://velog.io/@soooomm_00/nestjs-guard-multipart-formdata</guid>
            <pubDate>Thu, 08 Aug 2024 15:33:22 GMT</pubDate>
            <description><![CDATA[<p>NestJS에서 권한을 세분화하여 관리하는 과정에서 <code>multipart/form-data</code> 형식의 요청을 처리하는 데 문제가 발생했습니다. 이 글에서는 해당 문제를 해결한 과정을 정리해보았습니다.</p>
<hr>
<h2 id="1-문제-상황">1. 문제 상황</h2>
<p>저희 애플리케이션은 다음과 같이 각 기능마다 다른 권한을 요구합니다.</p>
<ul>
<li><strong>매니저</strong>만 공지사항 등록 가능</li>
<li><strong>커뮤니티 유저</strong>만 커뮤니티 게시글 등록 가능</li>
<li><strong>일반 유저</strong>가 볼 수 있는 라이브 방송 기능 제공</li>
</ul>
<p>이처럼 기능별로 권한이 다르기 때문에, 요청마다 권한을 검증하는 과정이 필요했습니다.</p>
<h3 id="기존-문제점">기존 문제점</h3>
<ol>
<li><strong>권한 검사 로직 중복</strong>  <ul>
<li>서비스 레이어에서 개별적으로 검사하여 코드 중복 발생</li>
</ul>
</li>
<li><strong>보안 취약점</strong>  <ul>
<li>서비스 레이어까지 불필요한 요청이 도달하여 민감한 데이터 접근 가능</li>
</ul>
</li>
<li><strong>불필요한 리소스 소비</strong>  <ul>
<li>권한이 없는 요청도 내부 로직 실행 → 성능 저하 유발</li>
</ul>
</li>
<li><strong>권한 관리의 일관성 부족</strong>  <ul>
<li>일부는 Guard, 일부는 서비스에서 검사하여 유지보수 어려움</li>
</ul>
</li>
<li><strong>확장성과 유지보수성 저하</strong>  <ul>
<li>권한 정책 변경 시 여러 서비스 코드 수정 필요</li>
</ul>
</li>
</ol>
<p>처음에는 일반 유저만 가드를 적용하고, 커뮤니티 유저와 관련된 권한은 서비스 레이어에서 검사했습니다. 그러나 이 방식은 코드 중복을 유발했고, 권한이 없는 사용자가 서비스 로직까지 도달하여 보안 이슈가 발생할 가능성이 있었습니다.</p>
<hr>
<h2 id="2-해결-방법-guard-적용">2. 해결 방법: Guard 적용</h2>
<p>이러한 문제를 해결하기 위해 <strong>NestJS의 Guard 기능</strong>을 활용했습니다. Guard는 NestJS에서 제공하는 공식적인 인증 및 권한 관리 기능이고, <code>AuthGuard</code>와 함께 사용하면 사용자 인증을 쉽게 처리할 수 있다고 생각했습니다.</p>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/ab42584a-ca95-46d5-8463-89ca876fdb24/image.png" alt=""></p>
<h3 id="✅-guard-적용-예상-결과--결론">✅ Guard 적용 예상 결과 &amp; 결론</h3>
<ol>
<li><strong>중복된 권한 검사 로직 제거</strong>  <ul>
<li>Guard에서 권한 검사를 수행 → 서비스 레이어는 비즈니스 로직에만 집중 가능</li>
</ul>
</li>
<li><strong>보안 강화 및 불필요한 요청 차단</strong>  <ul>
<li>서비스 진입 전에 차단하여 민감한 데이터 보호 및 성능 최적화</li>
</ul>
</li>
<li><strong>일관된 권한 관리</strong>  <ul>
<li>모든 API에 동일한 방식으로 Guard 적용 → 접근 제어 정책 유지</li>
</ul>
</li>
<li><strong>유저 권한 정보 제공</strong>  <ul>
<li>Guard에서 매니저, 아티스트 등의 권한 정보를 서비스로 전달하여 중복 코드 감소</li>
</ul>
</li>
<li><strong>확장성과 유지보수성 개선</strong>  <ul>
<li>새로운 권한 정책 추가 시 Guard만 수정하면 적용 가능 → 서비스 코드 수정 최소화</li>
</ul>
</li>
</ol>
<p>➡ <strong>결론:</strong> Guard를 활용하여 서비스 로직을 간결하게 유지하면서 보안과 성능을 개선하고, 유지보수성을 향상시킬 수 있었습니다.</p>
<hr>
<h2 id="3-해결-방법-적용-후-발생한-문제">3. 해결 방법 적용 후 발생한 문제</h2>
<p>Guard를 적용하는 과정에서 또 다른 문제가 발생했습니다.</p>
<h3 id="❌-문제점-multipartform-data-요청-처리-이슈">❌ 문제점: <code>multipart/form-data</code> 요청 처리 이슈</h3>
<p>게시글을 등록하는 API에서는 JSON 형식의 게시글 정보와 함께 이미지 파일을 업로드할 수 있도록 <code>multipart/form-data</code>를 사용하고 있습니다.</p>
<p>하지만, <strong>Guard에서 <code>multipart/form-data</code> 형식의 <code>request.body</code> 값을 읽을 수 없는 문제가 발생</strong>했습니다.</p>
<h3 id="원인-분석">원인 분석</h3>
<p><code>multipart/form-data</code> 요청은 파일 인터셉터를 사용해 <strong>파일 업로드가 완료된 후</strong> <code>request.files</code>와 <code>request.body</code>로 분리됩니다. 이 분리 시점이 Guard보다 늦게 실행되어 <code>request.body</code>를 찾을 수 없었던 것 입니다.</p>
<hr>
<h2 id="4-해결-방법-미들웨어-적용">4. 해결 방법: 미들웨어 적용</h2>
<p>이 문제를 해결하기 위해 <strong>Guard 이전 단계에서 실행되는 미들웨어를 활용</strong>했습니다.
<img src="https://velog.velcdn.com/images/soooomm_00/post/310da2fa-7988-41af-b1c1-dc789c3735b5/image.png" alt=""></p>
<h3 id="✅-적용-방법">✅ 적용 방법</h3>
<ol>
<li><strong>Multer 미들웨어를 게시글 모듈에 적용</strong>하여 파일을 먼저 처리</li>
<li><strong>파일 업로드 완료 후 <code>files</code>와 <code>body</code>가 분리됨</strong></li>
<li><strong>Guard에서 분리된 <code>body</code> 값을 받아와 유저 권한 확인</strong></li>
</ol>
<p>이 방법을 통해 Guard에서도 <code>request.body</code> 값을 정상적으로 확인할 수 있었습니다.</p>
<hr>
<h2 id="5-결론-및-개인적인-생각">5. 결론 및 개인적인 생각</h2>
<p>이번 해결 방법은 완벽하지 않지만, NestJS의 라이프사이클을 고려하여 Guard와 <code>multipart/form-data</code> 요청을 함께 사용하는 방법을 고민해본 사례로 기록하고자 합니다.</p>
<p>혹시 같은 문제를 겪고 계신 분들에게 도움이 되었으면 좋겠습니다. 😊</p>
<hr>
<h3 id="📌-참고-자료">📌 참고 자료</h3>
<ul>
<li><a href="https://docs.nestjs.com/faq/request-lifecycle">NestJS 공식문서: Request Lifecycle</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NestJS] Custom Decorator + Guard로 세분화된 권한 체크하기]]></title>
            <link>https://velog.io/@soooomm_00/nestjs-custom-decorator-guard-role</link>
            <guid>https://velog.io/@soooomm_00/nestjs-custom-decorator-guard-role</guid>
            <pubDate>Tue, 23 Jul 2024 14:13:23 GMT</pubDate>
            <description><![CDATA[<h2 id="배경">배경</h2>
<p>현 서비스에는 권한이 계층형입니다.
  <strong>① 전역:</strong> 비회원 ↔ 회원(JWT)
  <strong>② 커뮤니티:</strong> 여러 커뮤니티 중 <strong>“요청된 커뮤니티에 가입된 멤버인지”</strong>
  <strong>③ 역할:</strong> 그 커뮤니티에서의 <strong>일반 유저 / 아티스트 / 매니저</strong>
컨트롤러에서 매번 <code>if–else</code>로 검사하면 중복·누락이 생기고, 정책이 코드 곳곳으로 흩어집니다.
그래서 <strong>권한은 선언, 검증은 가드에서</strong> 처리하도록 변경했습니다.</p>
<h2 id="현재-문제-→-개선-방안">현재 문제 → 개선 방안</h2>
<p><strong>문제</strong></p>
<ul>
<li>Service 내부에서 ② 커뮤니티 권한 검사 → <strong>코드 중복</strong></li>
<li>권한 이슈를 Service에서 예외로 처리하기보다 <strong>API 입구에서 차단</strong>하는 게 적절</li>
</ul>
<p><strong>개선 방안</strong></p>
<ul>
<li>컨트롤러에 <code>@UseGuards(JwtAuthGuard, CommunityUserGuard)</code> 표준화</li>
<li>가드에서 ② 커뮤니티 회원 + ③ 역할(ARTIST/MANAGER) 검사</li>
<li>Custom Decorator <code>@CommunityUserRoles(...)</code> 로 허용 role 선언</li>
</ul>
<h2 id="전체-흐름-요약">전체 흐름 요약</h2>
<pre><code>Controller (선언)
  └─ @CommunityUserRoles(…optional)  +  @UserInfo()  // 권한 역할 선언 + 유저 주입
      ↓
JwtAuthGuard (① 전역: 로그인/회원)
      ↓
CommunityUserGuard
  ├─ ② 커뮤니티 멤버인지 검사
  └─ ③ (필요시) 커뮤니티 역할(ARTIST/MANAGER) 검사</code></pre><hr>
<h2 id="구현">구현</h2>
<p>아래 코드는 흐름을 설명하기 위해 실제 구현한 코드를 간소화한 버전입니다.</p>
<h3 id="1-역할role-enum">1) 역할(Role) Enum</h3>
<pre><code class="language-ts">// community-user-role.type.ts
export enum CommunityUserRole {
  ARTIST  = &#39;ARTIST&#39;,
  MANAGER = &#39;MANAGER&#39;,
  // 필요시 다른 역할 확장
}
</code></pre>
<h3 id="2-custom-decorator--허용할-역할-선언">2) Custom Decorator — 허용할 역할 선언</h3>
<pre><code class="language-ts">// community-user-roles.decorator.ts

import { SetMetadata } from &#39;@nestjs/common&#39;;
import { CommunityUserRole } from &#39;./community-user-role.type&#39;;

export const COMMUNITY_ROLES_KEY = &#39;communityRoles&#39;;
export const CommunityUserRoles = (...roles: CommunityUserRole[]) =&gt;
  SetMetadata(COMMUNITY_ROLES_KEY, roles);</code></pre>
<p>위 Enum만 CommunityUser의 허용 role로 선언합니다.
<code>@CommunityUserRoles()</code> 내부에 들어간 값을 읽어 가드에서 실제 검증에 사용합니다.</p>
<h3 id="3-custom-decorator--userinfo로-유저-정보-받기">3) Custom Decorator — <code>UserInfo()</code>로 유저 정보 받기</h3>
<pre><code class="language-ts">// user-info.decorator.ts
import { ExecutionContext, createParamDecorator } from &#39;@nestjs/common&#39;;

export const UserInfo = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) =&gt; {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);
</code></pre>
<p>매번 <code>req.user</code> 를 사용해 직접 꺼내오지 않고 <code>@UserInfo</code> 데코레이터를 만들어 유저 정보를 가져올 수 있도록 했습니다.</p>
<ul>
<li>읽기 쉽고, 구조 변경 시 데코레이터 내부만 수정하면 된다.</li>
<li>user만 필요해도 매번 req 전체를 받아야 했음 -&gt; 필요한 정보만 가져오도록 변경</li>
</ul>
<p><strong>Before/After</strong></p>
<pre><code class="language-ts">// Before
@Post()
create(@Req() req: Request, @Body() dto: CreateDto) {
  const user = req.user; // 매번 이렇게 꺼냄
  ...
}

// After
@Post()
create(@UserInfo() user: PartialUser, @Body() dto: CreateDto) {
  ...
}</code></pre>
<h3 id="4-guard---역할-검사">4) Guard - 역할 검사</h3>
<pre><code class="language-ts">// community-user.guard.ts
@Injectable()
export class CommunityUserGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
      // ...
  ) {}

  async canActivate(ctx: ExecutionContext): Promise&lt;boolean&gt; {
    const req = ctx.switchToHttp().getRequest();

    // 1) ① 로그인 여부 확인
    const userId = req.user?.id;
    if (!userId) throw new UnauthorizedException();

    // ...

    // 2) ② 해당 커뮤니티 유저인지 검사
     const existedCommunityUser =
      await this.communityUserService.findByCommunityIdAndUserId(
        communityId,
        userId,
      );

    // ...

    // 3) @CommunityUserRoles 데코레이터 안 roles를 가져오는 역할
    // ex) @CommunityUserRoles(ARTIST,MANAGER) → roles [ARTIST,MANAGER]
    const roles: CommunityUserRole[] = this.reflector.get(
      context.getHandler(),
    );

    // 4) roles 데코레이터 없으면 : 멤버면 통과
    if (roles.length === 0) return true;

    // 5) ③ 역할 검사(지정된 roles 중 맞으면 통과)
    const communityUserid = existedCommunityUser.communityUserId;

    // ③ 커뮤니티 유저이면서 ARTIST roles를 가진 경우
    if (roles.includes(CommunityUserRole.ARTIST)) {
      // ...
    }

    // ③ 커뮤니티 유저이면서 MANAGER roles를 가진 경우
    if (roles.includes(CommunityUserRole.MANAGER)) {
      // ...
    }

    // ...

    throw new ForbiddenException(&#39;존재하지 않는 역할입니다.&#39;);
  }
}
</code></pre>
<h3 id="컨트롤러-적용-예시">컨트롤러 적용 예시</h3>
<pre><code class="language-ts">// 1) 해당 커뮤니티 “멤버면” 접근
@UseGuards(JwtAuthGuard, CommunityUserGuard)
@Post(&#39;:communityId/assets&#39;)
createAsset(
  @UserInfo() user: PartialUser, 
  @Body() dto: CreateAssetDto,
) { /* ... */ }

// 2) “아티스트 또는 매니저”만 접근
@CommunityUserRoles(CommunityUserRole.ARTIST, CommunityUserRole.MANAGER) 
@UseGuards(JwtAuthGuard, CommunityUserGuard)
@Post(&#39;:communityId/publish&#39;)
publish(
 @UserInfo() user: PartialUser, 
  @Body() dto: PublishDto,
) { /* ... */ }

// 3) “매니저만” 접근
@CommunityUserRoles(CommunityUserRole.MANAGER)
@UseGuards(JwtAuthGuard, CommunityUserGuard)
@Delete(&#39;:communityId/posts/:postId&#39;)
removePost(
  @Param(&#39;postId&#39;) postId: string,
  @UserInfo() user: PartialUser,
) { /* ... */ }</code></pre>
<hr>
<h2 id="마무리">마무리</h2>
<p>기존 서비스 로직에 있던 코드들을 Guard를 사용하는 식으로 변경하고 나니 개발한 사람 외에는 팀원들이 사용할 수도 없을거란 생각을 했고, 쉽게 이해/사용할 수 있도록 가이드문서가 필요하다는 생각을 했습니다. 위 내용과 많이 중복되기는 하지만, 혹시 가이드 문서가 필요하다고 생각하신 분들이 참고할 수 있도록 짧게 사진 넣습니다 🥹</p>
<div style="margin:0; padding:0; line-height:0; font-size:0;">
  <img src="https://velog.velcdn.com/images/soooomm_00/post/af958039-e995-4cb9-83e4-c0a5ae0898ba/image.png" style="display:block; margin:0; padding:0; width:100%; height:auto;" alt="">
  <img src="https://velog.velcdn.com/images/soooomm_00/post/5941dbe8-15d9-48c3-91c1-5da80b6629c8/image.png" style="display:block; margin:0; padding:0; width:100%; height:auto;" alt="">
  <img src="https://velog.velcdn.com/images/soooomm_00/post/5aefa61e-44ca-416c-8a83-2712b5fd544e/image.png" style="display:block; margin:0; padding:0; width:100%; height:auto;" alt="">
</div>


<h2 id="reference">Reference</h2>
<hr>
<p>코드의 대부분은 NestJS 공식 문서에 관련 내용이 적혀있습니다!
<a href="https://docs.nestjs.com/guards">Guards | NestJS - A progressive Node.js framework</a>
<a href="https://docs.nestjs.com/custom-decorators">Custom decorators | NestJS - A progressive Node.js framework</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테이블명은 단수형? 복수형?]]></title>
            <link>https://velog.io/@soooomm_00/%ED%85%8C%EC%9D%B4%EB%B8%94%EB%AA%85%EC%9D%80-%EB%8B%A8%EC%88%98%ED%98%95-%EB%B3%B5%EC%88%98%ED%98%95</link>
            <guid>https://velog.io/@soooomm_00/%ED%85%8C%EC%9D%B4%EB%B8%94%EB%AA%85%EC%9D%80-%EB%8B%A8%EC%88%98%ED%98%95-%EB%B3%B5%EC%88%98%ED%98%95</guid>
            <pubDate>Tue, 23 Jul 2024 14:04:43 GMT</pubDate>
            <description><![CDATA[<p><strong>테이블명은 단수형으로 작성해야 할까요, 복수형으로 작성해야 할까요?</strong>
테이블명 네이밍 컨벤션에 관한 논의는 항상 재미있는데요. 특히 테이블명을 단수형으로 할지 복수형으로 할지에 대해선 항상 의견이 갈리곤 합니다.</p>
<p>그럼 테이블명은 단수형으로 쓰는 게 좋을까요, 복수형으로 쓰는 게 좋을까요? 제 생각은 <strong><code>정답은 없지만 일관되게 사용하는 것이 중요하다입니다.</code></strong></p>
<p>이번 포스트에서는 단수형과 복수형에 대한 의견을 살펴보고, 개인적인 생각도 살짝 담아보았습니다.</p>
<h3 id="단수형-파">단수형 파</h3>
<h4 id="1-각-행레코드를-나타낸다">1. 각 행(레코드)를 나타낸다</h4>
<ul>
<li>테이블의 각 행이 <strong>하나의 엔티티</strong>를 나타내기 때문에 단수형이 더 직관적이다.</li>
<li>예를 들어, User 테이블은 개별 유저를 나타냄</li>
</ul>
<h4 id="2-편리하고-에러-발생-가능성이-줄어든다">2. 편리하고 에러 발생 가능성이 줄어든다</h4>
<ul>
<li>영어가 모국어가 아닌 프로그래머에게는 단수형이 더 쉽다. </li>
<li>복수형은 형태가 불규칙적일 수 있어 오타가 발생할 가능성이 있으며, 몇몇 복수형은 존재하지 않는다.</li>
<li>작성 시간이 줄고, 디스크 공간을 절약할 수 있다.</li>
<li>(유머) 키보드를 많이 치지 않아 키보드를 오래 사용할 수 있습니다.</li>
</ul>
<h3 id="복수형-파">복수형 파</h3>
<h4 id="1-집합값을-나타낸다">1. 집합값을 나타낸다</h4>
<ul>
<li>테이블이 여러 행을 포함하는 <strong>집합</strong>임을 나타내기 때문에 복수형이 더 자연스럽다. </li>
<li>예를 들어, Users 테이블은 모든 유저를 포함</li>
</ul>
<h4 id="2-직관적이며-변수명과-일관성을-유지할-수-있다">2. 직관적이며 변수명과 일관성을 유지할 수 있다</h4>
<ul>
<li>리스트나 배열을 나타낼 때 복수형을 사용하듯, 여러 레코드를 포함하는 테이블명도 복수형으로 작성하는 것이 변수명과 일관성을 유지하는 방법입니다.</li>
</ul>
<h3 id="개인적인-선호-단수형">개인적인 선호: 단수형</h3>
<p>정답은 없지만, 저는 단수형을 더 선호합니다.</p>
<h4 id="1-의미의-중복">1. 의미의 중복</h4>
<p>각 엔티티는 데이터베이스의 테이블을 나타내기 때문에, 복수형을 사용하면 의미가 중복된다는 느낌을 받을 수 있습니다. </p>
<p>예를 들어, User 엔티티는 User 테이블로 해석되어 이미 User에 여러 개의 데이터를 포함할 것임을 알고 있습니다. 그렇다면 굳이 테이블 이름에 복수형을 붙여서 다시 여러 개임을 나타낼 필요가 있을까요?</p>
<p>저에겐 다소 아래 처럼 느껴집니다.</p>
<ul>
<li>유저 + 여러개 + 여러개 </li>
</ul>
<p>양말을 넣은 서랍에 네임택을 붙인다고 생각했을때 보통 &#39;양말&#39;이라고 적지 &#39;양말들&#39;이라고 적지 않습니다. 저희는 이미 서랍에 &#39;양말들&#39;이 있을걸 알고 있기 때문인데요. 테이블 네이밍도 이와 비슷하다고 생각합니다.</p>
<h4 id="2-명확한-의미-전달">2. 명확한 의미 전달</h4>
<p><code>User</code>와 <code>Users</code> 테이블이 있다고 가정해봅시다.</p>
<ul>
<li><code>User table</code>: 유저의 집합</li>
<li><code>Users table</code>: 유저들의 집합</li>
</ul>
<p>복수형을 사용했을때를 보면 해당 테이블이 각 유저 데이터에 대한 테이블인지, 여러 유저를 묶어 놓은 유저 덩어리 데이터의 테이블인지 명확하게 전달되지 않습니다.</p>
<p>복수형을 사용했을 때, 의미가 명확하게 전달되지 않는다고 느껴지는 부분이 제가 복수형을 선호하지 않는 가장 큰 이유인 것 같습니다.</p>
<h3 id="다시-한번-결론">다시 한번 결론</h3>
<ul>
<li><strong>뭐가 옳다고 할 수는 없으나, 컨벤션을 정해 일관되게 사용하는 것이 중요하다.</strong></li>
<li><strong>단수형 / 복수형 섞어서 사용하지 말 것!</strong></li>
</ul>
<h4 id="참고-자료">참고 자료</h4>
<p><a href="https://stackoverflow.com/questions/338156/table-naming-dilemma-singular-vs-plural-names">table-naming-dilemma-singular-vs-plural-names</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스(Database) 관련 용어 정리]]></title>
            <link>https://velog.io/@soooomm_00/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4Database-%EC%9A%A9%EC%96%B4</link>
            <guid>https://velog.io/@soooomm_00/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4Database-%EC%9A%A9%EC%96%B4</guid>
            <pubDate>Thu, 08 Sep 2022 08:34:51 GMT</pubDate>
            <description><![CDATA[<hr>
<p>오늘은 관계형 데이터베이스에 관련된 용어를 알아보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/4a1034db-20dc-4e99-b9fb-869b547b4037/image.png" alt=""></p>
<h3 id="속성attribute">속성(attribute)</h3>
<ul>
<li>테이블에서 <code>열(column)</code> 을 의미합니다. </li>
<li>각각의 열은 <strong>이름</strong>과 <strong>타입</strong>을 가지고 있습니다.</li>
<li><code>필드(Field)</code> , <code>속성(attribute, 애트리뷰트)</code> , <code>열(column , 컬럼)</code> 등으로 불립니다.</li>
<li>위의 학생 릴레이션의 속성은 4개입니다.</li>
</ul>
<h3 id="튜플tuple">튜플(tuple)</h3>
<ul>
<li>릴레이션에서 <code>행(row)</code> 를 의미합니다.</li>
<li>행은 <strong>관계된 데이터의 묶음</strong> 입니다.</li>
<li><code>행(row)</code> , <code>튜플(tuple, 투플)</code> , <code>레코드(record)</code> 등으로 불립니다.</li>
<li>위의 학생 릴레이션의 튜플은 총 5개 입니다.</li>
</ul>
<h3 id="카디널리티cardianlity">카디널리티(Cardianlity)</h3>
<ul>
<li>한 릴레이션 안에 있는 <code>튜플(tuple)의 개수</code>이다.</li>
<li>유효한 릴레이션은 <strong>카디널리티 0을 가질 수 있습니다</strong>. 릴레이션을 만들었지만, 아직 데이터를 넣지 않은 것과 같은 경우 입니다.</li>
<li>릴레이션의 카디널리티는 시간이 지남에 따라 계속해서 변화합니다. 데이터를 추가하고 삭제함에 따라 튜플의 수가 변하기 때문입니다.</li>
<li>위 릴레이션의 튜플은 5개 이므로 카디널리티는 5 입니다.</li>
</ul>
<h3 id="차수degree">차수(degree)</h3>
<ul>
<li>한 릴레이션 안에 있는 <code>애트리뷰트(attribute)의 수</code> 입니다.</li>
<li>유효한 릴레이션의 <strong>최소 차수는 1</strong> 입니다. 즉 모든 <strong>릴레이션들은 한 개 이상의 속성(attribute, 애트리뷰트)를 가져야 합니다.</strong></li>
<li>릴레이션의 차수는 자주 바뀌지 않습니다. 차수가 바뀌기 위해서는 릴레이션의 구조를 자체를 바꿔야 하므로 잘 변경 되지 않습니다.</li>
<li>위 릴레이션의 속성의 수는 4개 이므로 차수는 4 입니다.</li>
</ul>
<h3 id="도메인domain">도메인(domain)</h3>
<ul>
<li>애트리뷰트가 <code>가질 수 있는 값들의 집합</code>을 도메인이라고 합니다.</li>
<li>하나의 도메인을 여러 속성에서 공유할 수도 있습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/dc939443-95da-4066-a769-82f736f5fb48/image.png" alt=""></p>
<ul>
<li>위 student_tbl 릴레이션의 도메인은 VARCHAR(4), VARCHAR(20), VARCHAR(100), DATE 등이 있습니다.</li>
</ul>
<hr>
<h3 id="참고">참고</h3>
<p><a href="http://www.tcpschool.com/mysql/mysql_intro_relationalDB">http://www.tcpschool.com/mysql/mysql_intro_relationalDB</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Markdown 사용법]]></title>
            <link>https://velog.io/@soooomm_00/Markdown-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@soooomm_00/Markdown-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Sat, 20 Aug 2022 07:46:16 GMT</pubDate>
            <description><![CDATA[<p>개발하다보면 깃허브 리드미, velog, 지금 사용하고 있는 옵시디언(obsidian) 등  마크다운(Markdown)을 많이 쓰게 됩니다.  Markdown 문법을 정리할 겸 블로그 글을 작성하게 되었습니다. 
<br>
<br>
<br>
<br></p>
<h3 id="제목heading">제목(Heading)</h3>
<p><code>h1</code>부터 <code>h6</code>까지 <code>#</code>를 붙여 사용 가능합니다.</p>
<!-- Heading -->
<pre><code class="language-Markdown"># Heading1

## Heading2     

### Heading3

#### Heading4

##### Heading5

###### Heading6</code></pre>
<br>

<p>결과) </p>
<h1 id="heading1">Heading1</h1>
<h2 id="heading2">Heading2</h2>
<h3 id="heading3">Heading3</h3>
<h4 id="heading4">Heading4</h4>
<h5 id="heading5">Heading5</h5>
<h6 id="heading6">Heading6</h6>
<p>일반 본문</p>
<hr>
<h3 id="강조text-attributes">강조(Text attributes)</h3>
<p>아래와 같이 텍스트에 강조 효과를 줄 수 있습니다.</p>
<!-- Text attributes -->
<pre><code class="language-markdown">
It is *wonderful* ==&gt; 이탤릭

It is **wonderful**  ==&gt; 굵게

It is ***wonderful*** ==&gt; 굵게 + 이탤릭

It is ~~wonderful~~ ==&gt; 취소선

It is &lt;u&gt;wonderful&lt;/u&gt; ==&gt; 밑줄</code></pre>
<br>
결과) 

<p>It is <strong>wonderful</strong>
It is <em>wonderful</em>
It is <del>wonderful</del>
It is <strong><em>wonderful</em></strong>
It is <u>wonderful</u></p>
<hr>
<h3 id="인용문quote">인용문(Quote)</h3>
<p><code>&gt;</code> 를 이용하며, 중첩해 사용하거나 안에서 다른 마크다운 요소를 쓸 수 있습니다.</p>
<!-- Quote-->
<pre><code class="language-Markdown">&gt; I&#39;m queen!!!!

&gt; I&#39;m queen!!!!
&gt;&gt; I&#39;m queen!!!!
&gt;&gt;&gt; I&#39;m queen!!!!

&gt; #### Heading4
* List1
&gt; &gt; * List 2
&gt;&gt;&gt; ##### CodeBlock
&gt;&gt;&gt;```
CodeBlock
&gt;&gt;&gt;```

이런 식으로 복잡하게 중첩해서 쓸 수도 있습니다.
</code></pre>
<br>
결과)

<blockquote>
<p>I&#39;m queen!!!!</p>
</blockquote>
<blockquote>
<p>I&#39;m queen1</p>
<blockquote>
<p>I&#39;m queen2</p>
<blockquote>
<p>I&#39;m queen3</p>
</blockquote>
</blockquote>
</blockquote>
<blockquote>
<h4 id="heading4-1">Heading4</h4>
</blockquote>
<ul>
<li>List1<blockquote>
<blockquote>
<ul>
<li>List 2<blockquote>
<h5 id="codeblock">CodeBlock</h5>
<pre><code>CodeBlock</code></pre></blockquote>
</li>
</ul>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="목록list">목록(List)</h3>
<ul>
<li><h4 id="순서-없는-목록bullet-list">순서 없는 목록(Bullet List)</h4>
<code>*</code> , <code>+</code> , <code>-</code> 를 이용하며, 혼합해서 사용하는 것도 가능합니다.</li>
</ul>
<!-- Bullet List -->

<pre><code>Fruits:
* apple
* pear
* grapes
* mango

vegetables:
+ lettuce
+ green onion
+ cabbage
+ mushroom

other fruits:
* watermelon
    + orange
        - plum</code></pre><p>결과)
<img src="https://velog.velcdn.com/images/soooomm_00/post/2ea31d40-d21a-4203-b47a-631cd334fdab/image.png" align="left">
<br>
결과물이 velog에서는 제대로 나오지 않아서 마크다운 파일을 하나 생성해서 나온 결과 사진을 첨부했습니다. </p>
<hr>
<ul>
<li><h4 id="순서-있는-목록numbered-list">순서 있는 목록(Numbered List)</h4>
순서 있는 목록은 숫자와 점을 이용해 나타낼 수 있습니다.</li>
</ul>
<!-- Numbered List -->
<pre><code>1. first
2. second
3. third</code></pre><ol>
<li>first</li>
<li>second</li>
<li>third</li>
</ol>
<p>번호의 순서가 틀리더라도 처음 시작한 숫자부터 내림차순으로 정의됩니다.</p>
<pre><code>2. first
6. second
1. third</code></pre><ol start="2">
<li>first</li>
<li>second</li>
<li>third<br>

</li>
</ol>
<p>맨 처음 시작 숫자가 <code>2</code>이기 때문에 <code>2</code> , <code>3</code> , <code>4</code>로 나타나는 걸 볼 수 있습니다.  </p>
<hr>
<h3 id="링크link">링크(Link)</h3>
<!-- link -->
<pre><code>Click [원하는 텍스트](링크)
This is [google](https://www.google.com/)</code></pre><p>Click <a href="%EB%A7%81%ED%81%AC">원하는 텍스트</a>
This is <a href="https://www.google.com/">google</a></p>
<pre><code>This is [google](https://www.google.com/ &quot;구글로 이동&quot;)</code></pre><p>위처럼 뒤에 설명을 붙이게 되면, 마우스를 가져다 댔을 때 아래처럼 뜨게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/soooomm_00/post/9c58c010-1934-4f34-9b37-27cc0d45deff/image.png" alt="">
This is <a href="https://www.google.com/" title="구글로 이동">google</a></p>
<hr>
<h3 id="이미지image">이미지(Image)</h3>
<p>이미지도 링크와 같은 방법으로 삽입할 수 있습니다.</p>
<!-- Image -->
<pre><code>![이미지 설명](이미지 링크)
</code></pre><p><img src="https://images.unsplash.com/photo-1560806887-1e4cd0b6cbd6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Nnx8YXBwbGV8ZW58MHx8MHx8&auto=format&fit=crop&w=500&q=60" alt="사과"></p>
<p>별도의 사이즈 조절 기능이 없기 때문에 이런 부분을 조정하고 싶다면 <code>&lt;img&gt;</code> 태그를 써야 합니다.</p>
<ul>
<li>사이즈 조절
<code>&lt;img src=&quot;이미지주소&quot; width=&quot;&quot; height=&quot;&quot;&gt;&lt;/img&gt;</code></li>
</ul>
<pre><code>예시)
&lt;img src=&quot;이미지주소&quot; width=&quot;800px&quot; height=&quot;500px&quot;&gt;&lt;/img&gt;
</code></pre><p><img src="https://images.unsplash.com/photo-1560806887-1e4cd0b6cbd6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Nnx8YXBwbGV8ZW58MHx8MHx8&auto=format&fit=crop&w=500&q=60" width="800px" height="500px"></img></p>
<hr>
<h3 id="테이블table">테이블(Table)</h3>
<p><code>|</code> 문자와 <code>-</code> 문자로 표를 만들 수 있습니다.</p>
<pre><code>|Header|Description|
|--|--|
|cell1|cell2|
|cell1|cell2|
|cell1|cell2|
|cell1|cell2|
</code></pre><!-- Table -->
<table>
<thead>
<tr>
<th>Header</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td>cell1</td>
<td>cell2</td>
</tr>
<tr>
<td>cell1</td>
<td>cell2</td>
</tr>
<tr>
<td>cell1</td>
<td>cell2</td>
</tr>
<tr>
<td>cell1</td>
<td>cell2</td>
</tr>
<tr>
<td><br></td>
<td></td>
</tr>
</tbody></table>
<!-- Table 정렬 -> 콜론 위치에 따라 정렬됨 -->
<!-- cell1은 중앙정렬, cell2는 오른쪽 정렬 -->

<p><code>:</code> 문자를 사용하면 정렬을 할 수 있습니다.
아래의 예제는 cell1은 중앙정렬, cell2는 우측정렬을 해봤습니다.</p>
<pre><code>|Header|Description|
|:--:|--:|
|cell1|cell2|
|cell1|cell2|
|cell1|cell2|
|cell1|cell2|
</code></pre><table>
<thead>
<tr>
<th align="center">Header</th>
<th align="right">Description</th>
</tr>
</thead>
<tbody><tr>
<td align="center">cell1</td>
<td align="right">cell2</td>
</tr>
<tr>
<td align="center">cell1</td>
<td align="right">cell2</td>
</tr>
<tr>
<td align="center">cell1</td>
<td align="right">cell2</td>
</tr>
<tr>
<td align="center">cell1</td>
<td align="right">cell2</td>
</tr>
</tbody></table>
<hr>
<h3 id="코드code">코드(Code)</h3>
<p>마크다운으로 코드를 삽입하는 방법에는 <code>인라인(Inline)</code> 과 <code>코드블럭(CodeBlock)</code> 으로 삽입하는 2가지 방법이 있습니다.
두 방법 다 <code>백틱(`)</code> 을 이용하여 사용할 수 있습니다.</p>
<!-- Code -->
<ul>
<li><h4 id="인라인inline-코드-삽입">인라인(Inline) 코드 삽입</h4>
</li>
</ul>
<pre><code>`console.log(&#39;inline&#39;)`
&lt;code&gt;console.log(&#39;inline&#39;)&lt;/code&gt;</code></pre><p><code>console.log(&#39;inline&#39;)</code>
<code>console.log('inline')</code>
<br></p>
<ul>
<li><h4 id="코드블럭code-block-삽입">코드블럭(Code block) 삽입</h4>
<code>백틱(`)</code> 3개를 위아래로 붙여 사용합니다.<br>

</li>
</ul>
<p>*<em>``` *</em>
console.log(&#39;block1&#39;)
console.log(&#39;block2&#39;)
console.log(&#39;block3&#39;)
*<em>``` *</em></p>
<pre><code>console.log(&#39;block1&#39;)
console.log(&#39;block2&#39;)
console.log(&#39;block3&#39;)</code></pre><br>

<p><strong>```</strong> 뒤에 사용하는 언어를 넣으면 <code>문법 강조(Syntax highlighting)</code> 가 가능합니다. 
<br></p>
<p><strong>```</strong> js
console.log(&#39;block1&#39;)
console.log(&#39;block2&#39;)
console.log(&#39;block3&#39;)
*<em>``` *</em></p>
<pre><code class="language-js">console.log(&#39;block1&#39;)
console.log(&#39;block2&#39;)
console.log(&#39;block3&#39;)</code></pre>
<hr>
<h3 id="줄바꿈">줄바꿈</h3>
<!-- 줄바꿈 -->
<p>줄 바꿈은 문장 마지막에서 3칸 이상을 띄어쓰기(<code></code>)해야 합니다.</p>
<pre><code>이 문장은 ___ 줄 바꿈 예시입니다.</code></pre><p>이 문장은
줄 바꿈 예시입니다.</p>
<hr>
<!-- Line -->
<h3 id="수평선">수평선</h3>
<p>아래 있는 줄은 모두 수평선을 만듭니다.</p>
<pre><code>***
---
----------- ----</code></pre><hr>
<hr>
<hr>
<br>
<br>
<br>

<h2 id="참고">참고</h2>
<p><a href="https://gist.github.com/ihoneymon/652be052a0727ad59601">https://gist.github.com/ihoneymon/652be052a0727ad59601</a></p>
]]></description>
        </item>
    </channel>
</rss>