<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>오프로드 탐험기</title>
    <link>https://cloer.tistory.com/</link>
    <description>끝 없는 비포장도로</description>
    <language>ko</language>
    <pubDate>Mon, 3 Aug 2026 10:44:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>thecloer</managingEditor>
    <image>
      <title>오프로드 탐험기</title>
      <url>https://tistory1.daumcdn.net/tistory/4927743/attach/d23c5eb581034850a9e06856b9be51e1</url>
      <link>https://cloer.tistory.com</link>
    </image>
    <item>
      <title>[LeetCode - 15] 3Sum - C++</title>
      <link>https://cloer.tistory.com/284</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an integer array nums, return all the triplets &lt;code&gt;[nums[i], nums[j], nums[k]]&lt;/code&gt; such that &lt;code&gt;i != j&lt;/code&gt;, &lt;code&gt;i != k&lt;/code&gt;, and &lt;code&gt;j != k&lt;/code&gt;, and &lt;code&gt;nums[i] + nums[j] + nums[k] == 0&lt;/code&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Notice that the solution set must not contain duplicate triplets.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]
Explanation: 
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.
The distinct triplets are [-1,0,1] and [-1,-1,2].
Notice that the order of the output and the order of the triplets does not matter.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;Input: nums = [0,1,1]
Output: []
Explanation: The only possible triplet does not sum up to 0.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [0,0,0]
Output: [[0,0,0]]
Explanation: The only possible triplet sums up to 0.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;3 &amp;lt;= nums.length &amp;lt;= 3000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-10&lt;sup&gt;5&lt;/sup&gt; &amp;lt;= nums[i] &amp;lt;= 10&lt;sup&gt;5&lt;/sup&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1737265983775&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
public:
    vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; threeSum(vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        sort(nums.begin(), nums.end());
        vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; ans;

        for(int i = 0, end = nums.size() - 2; i &amp;lt; end; i++) {
            if(nums[i] &amp;gt; 0) break;
            if(i &amp;gt; 0 &amp;amp;&amp;amp; nums[i] == nums[i - 1]) continue; // 중복 쌍 제거

            int j = i + 1, k = nums.size() - 1;
            while(j &amp;lt; k) {
                int sum = nums[i] + nums[j] + nums[k];
                if(sum &amp;lt; 0) j++;
                else if (sum &amp;gt; 0) k--;
                else {
                    ans.push_back({ nums[i], nums[j], nums[k] });

                    j++;
                    k--;
                    while(j &amp;lt; k &amp;amp;&amp;amp; nums[j] == nums[j - 1]) j++; // 중복 쌍 제거
                }
            }
        }
        return ans;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`i`를 `0 ~ nums.size() - 2`까지 순회하며 `i + 1 ~ nums.size() - 1`사이에서 투포인터로 &lt;a href=&quot;https://blog.thecloer.com/283&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Two Sum 2 문제&lt;/a&gt;를 푼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복되는 쌍을 제거하는 일이 까다로울 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬한 후 같은 숫자에 대해서는 `i`와 투 포인터의 `j`를 끝까지 밀어 중복 쌍을 카운트하지 않도록한다.&lt;/p&gt;</description>
      <category>Problem Solving/LeetCode</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/284</guid>
      <comments>https://cloer.tistory.com/284#entry284comment</comments>
      <pubDate>Sun, 19 Jan 2025 14:56:49 +0900</pubDate>
    </item>
    <item>
      <title>[LeetCode - 167] Two Sum II - Input Array Is Sorted - C++</title>
      <link>https://cloer.tistory.com/283</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a &lt;b&gt;1-indexed&lt;/b&gt; array of integers &lt;code&gt;numbers&lt;/code&gt; that is already &lt;b&gt;&lt;i&gt;sorted in non-decreasing order&lt;/i&gt;&lt;/b&gt;, find two numbers such that they add up to a specific &lt;code&gt;target&lt;/code&gt; number. Let these two numbers be &lt;code&gt;numbers[index&lt;sub&gt;1&lt;/sub&gt;]&lt;/code&gt; and &lt;code&gt;numbers[index&lt;sub&gt;2&lt;/sub&gt;]&lt;/code&gt; where &lt;code&gt;1 &amp;lt;= index&lt;sub&gt;1&lt;/sub&gt; &amp;lt; index&lt;sub&gt;2&lt;/sub&gt; &amp;lt;= numbers.length&lt;/code&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return&lt;i&gt; the indices of the two numbers, &lt;/i&gt;&lt;code&gt;index&lt;sub&gt;1&lt;/sub&gt;&lt;/code&gt;&lt;i&gt; and &lt;/i&gt;&lt;code&gt;index&lt;sub&gt;2&lt;/sub&gt;&lt;/code&gt;&lt;i&gt;, &lt;b&gt;added by one&lt;/b&gt; as an integer array &lt;/i&gt;&lt;code&gt;[index&lt;sub&gt;1&lt;/sub&gt;, index&lt;sub&gt;2&lt;/sub&gt;]&lt;/code&gt;&lt;i&gt; of length 2.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The tests are generated such that there is &lt;b&gt;exactly one solution&lt;/b&gt;. You &lt;b&gt;may not&lt;/b&gt; use the same element twice.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Your solution must use only constant extra space.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Explanation: The sum of 2 and 7 is 9. Therefore, index1 = 1, index2 = 2. We return [1, 2].
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: numbers = [2,3,4], target = 6
Output: [1,3]
Explanation: The sum of 2 and 4 is 6. Therefore index1 = 1, index2 = 3. We return [1, 3].
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: numbers = [-1,0], target = -1
Output: [1,2]
Explanation: The sum of -1 and 0 is -1. Therefore index1 = 1, index2 = 2. We return [1, 2].
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;2 &amp;lt;= numbers.length &amp;lt;= 3 * 10&lt;sup&gt;4&lt;/sup&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-1000 &amp;lt;= numbers[i] &amp;lt;= 1000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numbers&lt;/code&gt; is sorted in &lt;b&gt;non-decreasing order&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-1000 &amp;lt;= target &amp;lt;= 1000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The tests are generated such that there is &lt;b&gt;exactly one solution&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1737265873812&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
public:
    vector&amp;lt;int&amp;gt; twoSum(vector&amp;lt;int&amp;gt;&amp;amp; numbers, int target) {
        int l = 0, r = numbers.size() - 1;
        while(l &amp;lt; r) {
            int sum = numbers[l] + numbers[r];
            if(sum == target) return { l + 1, r + 1 };
            if(sum &amp;lt; target) l++;
            else r--;
        }
        return { 0, 0 };
    }
};&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Problem Solving/LeetCode</category>
      <category>투포인터</category>
      <category>투포인터 문제</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/283</guid>
      <comments>https://cloer.tistory.com/283#entry283comment</comments>
      <pubDate>Sun, 19 Jan 2025 14:51:33 +0900</pubDate>
    </item>
    <item>
      <title>[LeetCode - 125] Valid Palindrome - C++</title>
      <link>https://cloer.tistory.com/282</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A phrase is a &lt;b&gt;palindrome&lt;/b&gt; if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a string &lt;code&gt;s&lt;/code&gt;, return &lt;code&gt;true&lt;/code&gt;&lt;i&gt; if it is a &lt;b&gt;palindrome&lt;/b&gt;, or &lt;/i&gt;&lt;code&gt;false&lt;/code&gt;&lt;i&gt; otherwise&lt;/i&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;Input: s = &quot;A man, a plan, a canal: Panama&quot;
Output: true
Explanation: &quot;amanaplanacanalpanama&quot; is a palindrome.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;Input: s = &quot;race a car&quot;
Output: false
Explanation: &quot;raceacar&quot; is not a palindrome.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;Input: s = &quot; &quot;
Output: true
Explanation: s is an empty string &quot;&quot; after removing non-alphanumeric characters.
Since an empty string reads the same forward and backward, it is a palindrome.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= s.length &amp;lt;= 2 * 10&lt;sup&gt;5&lt;/sup&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; consists only of printable ASCII characters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1737265678116&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
public:
    bool isPalindrome(string s) {
        int l = 0, r = s.size() - 1;
        while(l &amp;lt; r) {
            while(l &amp;lt; r &amp;amp;&amp;amp; !isAlphanumeric(s[l])) l++;
            while(l &amp;lt; r &amp;amp;&amp;amp; !isAlphanumeric(s[r])) r--;
            if(tolower(s[l]) != tolower(s[r])) return false;
            l++; r--;
        }
        return true;
    }

    bool isAlphanumeric(char c) {
        return (
            'A' &amp;lt;= c &amp;amp;&amp;amp; c &amp;lt;= 'Z' ||
            'a' &amp;lt;= c &amp;amp;&amp;amp; c &amp;lt;= 'z' ||
            '0' &amp;lt;= c &amp;amp;&amp;amp; c &amp;lt;= '9'
        );
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투포인터가 양쪽에서 가운데로 모이며 non-alphanumeric 문자는 무시한다.&lt;/p&gt;</description>
      <category>Problem Solving/LeetCode</category>
      <category>투포인터</category>
      <category>투포인터 문제</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/282</guid>
      <comments>https://cloer.tistory.com/282#entry282comment</comments>
      <pubDate>Sun, 19 Jan 2025 14:49:25 +0900</pubDate>
    </item>
    <item>
      <title>[TS] 타입이론과 고급 타입 추론</title>
      <link>https://cloer.tistory.com/281</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브에 공개된 네이버 사내 기술 교류 행사인 NAVER ENGINEERING DAY 2024(5월)에서 발표되었던 세션을 보고 타입스크립트의 타입 시스템에 대한 이해가 깊어졌습니다. 해당 발표 영상을 보고 추후 다시 찾아볼 수 있도록 정리한 내용입니다. 본문에는 제 나름 대로의 해석이 추가되어 오해의 여지가 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표 영상: &lt;a href=&quot;https://youtu.be/xesy1i67OWI?feature=shared&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;infer, never만 보면 두려워지는 당신을 위한 고급 TypeScript&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입이론과 TypeScript&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러셀의 역설(Russell's Paradox)의 발견으로 수학의 근간인 집합론이 흔들리며&amp;nbsp;함수에 대한 고찰도 이루어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 집합 사이의 관계로만 생각했던 함수에 대한 고찰을 시작은 람다 대수(Lambda Calculus)의 고안을 이끌었고 이를 고도화하던 중 타입의 개념이 도입되었다. 관련 내용은 해당 문서에 상세히 나와있다. &lt;a href=&quot;https://plato.stanford.edu/entries/type-theory/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://plato.stanford.edu/entries/type-theory/&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀랍게도 TypeScript는 1.8까지 공식문서가 존재했으나 Microsoft가 문서 유지보수를 포기하며 tsc의 구현이 곧 TypesScript의 스펙이 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/o12OK/btsKIEKSo6S/pzd7vw2xTrg7Sk1Uvygpv1/TypeScript%20Language%20Specification%201.8.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;TypeScript Language Specification 1.8.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;1.62MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 영상은 tsc를 블랙박스로 간주하고 수학적 일관성과 실험을 근거로 서술한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기초 타입 추론&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;슈퍼타입과 서브타입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상에서는 타입을 `&lt;b&gt;어떤 &lt;u&gt;심벌(Symbol, ≒ 변수명)&lt;/u&gt;에 엮인(binded) 메모리 공간에 존재할 수 있는 &lt;u&gt;값(value)&lt;/u&gt;의 집합과 그 값들이 가질 수 있는 &lt;u&gt;성질(properties)&lt;/u&gt;&lt;/b&gt;`로 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`3.141592 : number` 이와 같은 표기는 `값 3.141592는 타입 number에 &lt;u&gt;&lt;b&gt;속한다.&lt;/b&gt;&lt;/u&gt;`로 이해할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\{\text{x}:\text{number};\thinspace\text{y}?:\text{string} \}\lesssim\{\text{x}:\text{number}\}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 $\{\text{x}:\text{number};\thinspace\text{y}?:\text{string}\}$은 타입 $\{\text{x}:\text{number}\}$에 &lt;u&gt;&lt;b&gt;속한다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 $\{\text{x}:\text{number};\thinspace\text{y}?:\text{string} \}$은 타입 $\{\text{x}:\text{number}\}$의 &lt;b&gt;&lt;u&gt;서브타입&lt;/u&gt;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 $\{\text{x}:\text{number}\}$은 타입 $\{\text{x}:\text{number};\thinspace\text{y}?:\text{string}\}$의 &lt;u&gt;&lt;b&gt;슈퍼타입&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`타입 B가 가지는 모든 속성을 타입 A가 가지면, 타입 A는 B의 서브타입이다.`&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`&lt;u&gt;&lt;b&gt;서브타입은 슈퍼타입의 모든 속성을 갖는다.&lt;/b&gt;&lt;/u&gt;`&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타입 간의 관계와 대입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입은 4가지 관계를 가질 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$\text{a} \gtrsim \text{b}$: a가 b의 슈퍼타입이다. ( $\text{number} \gtrsim 42$ )&lt;/li&gt;
&lt;li&gt;$\text{a} \lesssim \text{b}$: a가 b의 서브타입이다. ( $\text{string} \lesssim \text{string \textbar&amp;nbsp; number}$ )&lt;/li&gt;
&lt;li&gt;$\text{a} \simeq \text{b}$: a와 b가 서로 서브타입이다. ( $\{\text{x}?:\text{number}\}\simeq \{\text{x}:\text{number \textbar&amp;nbsp; \text{undefined}}\}$ )&lt;/li&gt;
&lt;li&gt;$\text{a} \not\simeq \text{b}$: 서로 관계가 없다. ( $\text{number} \not\simeq \{\text{x}:\text{string}\}$ )&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 타입을 큰 타입에 대입할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`&lt;u&gt;&lt;b&gt;서브타입을 슈퍼타입에 대입할 수 있다.&lt;/b&gt;&lt;/u&gt;`&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원시 타입(Primitive Type)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;boolean, number, string, symbol, null, undefined&lt;/li&gt;
&lt;li&gt;공리적으로 정의&lt;/li&gt;
&lt;li&gt;상호 무관계&lt;/li&gt;
&lt;li&gt;null을 제외한 모든 원시 타입은 `typeof`연산 결과 해당 타입의 이름이 나온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리터럴 타입(Literal Type)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 타입에 속한 값 하나만으로 구성하는 타입&lt;/li&gt;
&lt;li&gt;본래 타입의 서브타입으로 간주&lt;/li&gt;
&lt;li&gt;as const 키워드로 특정 값을 리터럴 타입으로 선언할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1731514308912&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const x = 6; // x: number (원시 타입으로 추론)
const y = 6 as const; // y: 6 (리터럴)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체 타입(Object Type)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입 L과 R에서 타입 L의 모든 속성 P에 대하여 $\text{L[P]}\gtrsim \text{R[P]}$이면 $\text{L}\gtrsim\text{R}$이다.&lt;/li&gt;
&lt;li&gt;이때 객체의 타입은 속성의 타입에 대해 `공변적(Covariant)`이라 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열/튜플 타입(Array/Typle Type)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체와 동일하다. 키가 number로 고정되어 있다는 점만 다르다.&lt;/li&gt;
&lt;li&gt;튜플은 length가 상수 리터럴 타입으로 고정되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\text{a}\lesssim\text{b}\Longrightarrow\text{A[ ]}\lesssim\text{B[ ]}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수 타입(Function Type)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반환형과 인자형의 대입 조건을 모두 만족해야 한다.&lt;/li&gt;
&lt;li&gt;반환형에 대해서는 `공변적(Covariant)`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$\text{A}\lesssim\text{B}\Longrightarrow\text{X}\mapsto\text{A}\lesssim\text{X}\mapsto\text{B}$&lt;/li&gt;
&lt;li&gt;인자형이 같은 두 함수에서 A가 B의 서브타입이라면 A를 반환하는 함수가 B를 반환하는 함수의 서브타입이다.&lt;/li&gt;
&lt;li&gt;함수의 타입 관계는 반환형의 타입관계와 같은 방향이다. (공변적이다.)&lt;/li&gt;
&lt;li&gt;반환값은 rvalue로 사용되기 때문에 함수의 타입과 반환형의 타입은 공변적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인자형에 대해서는 `반변적(Contravariant)`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$\text{A}\lesssim\text{B}\Longrightarrow\text{A}\mapsto\text{X}\gtrsim\text{B}\mapsto\text{X}$&lt;/li&gt;
&lt;li&gt;반환형이 같은 두 함수에서 A가 B의 서브타입이라면 A를 인자로 갖는 함수는 B를 인자로 갖는 함수의 슈퍼타입이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인자의 수가 불일치하는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인자가 적은 함수를 인자가 많은 함수에 대입할 수 있다.&lt;/li&gt;
&lt;li&gt;인자가 적은 함수가 서브타입, 인자가 많은 함수가 슈퍼타입이 된다.&lt;/li&gt;
&lt;li&gt;인터페이스(슈퍼타입, 인자 많은 함수)로 들어오는 넘치는 인자는 구현체(서브타입, 인자 적은 함수)에서 버리면 그만이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특수 타입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Javascript에는 존재하지 않으나 TypeScript에서 타입상으로만 존재하는 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;never, unknown, any, void&lt;/li&gt;
&lt;li&gt;never &amp;amp; unknown
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$\forall_T:\text{never}\lesssim\text{T}\lesssim\text{unknown}$&lt;/li&gt;
&lt;li&gt;`never`는 가장 좁은 타입이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 타입의 서브타입이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;공집합과 비슷하다.&lt;/li&gt;
&lt;li&gt;어떤 타입도 대입할 수 없다.&lt;/li&gt;
&lt;li&gt;타입 에러를 고의적으로 발생시킬 때에도 사용(제네릭에서 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`unknown`은 가장 넓은 타입이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 타입의 슈퍼타입이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;any, unknown을 제외한 어떠한 곳에도 대입이 불가능하다.&lt;/li&gt;
&lt;li&gt;모든 값을 받을 수는 있지만 대입할 수는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;any
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$\forall_{\text{T}\not=\text{never}}:\text{T}\simeq\text{any},\,\text{never}\lesssim\text{any}$&lt;/li&gt;
&lt;li&gt;never를 제외한 모든 타입과 서로 서브타입이다.&lt;/li&gt;
&lt;li&gt;never를 제외한 모든 타입에 대입 가능하며 모든 타입을 받을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1731516756341&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const thisIsNever: never = undefined; // ERROR: never는 모든 타입의 서브타입이다.
const thisIsUnknown: unknown = 0; // unknown은 모든 타입의 슈퍼타입이다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;퀴즈&lt;/h3&gt;
&lt;pre id=&quot;code_1731517151990&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다음 중 이론 상 가장 넓은 함수의 타입은? (슈퍼타입은?)
type A = (...args: unknown[]) =&amp;gt; unknow;
type B = (...args: never[]) =&amp;gt; unknow;
type C = (...args: any[]) =&amp;gt; any;
type D = (...args: void[]) =&amp;gt; never;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`B`는 모든 함수의 슈퍼타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 타입은 반환형과 공변적, 인자형과 반변적이므로 반환형은 넓고, 인자형은 좁을수록 함수의 타입은 넓어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환형이 가장 넓은 타입인`unknown`이고 인자형이 가장 좁은 타입인 `never`인 `B`가 가장 넓은 함수 타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고급 타입 추론&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타입 검사(Type Checking)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 전체/부분 심벌에 대한 대입, 연산, 참조가 가능성을 확인하는 과정이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 심벌이 제약 조건(타입)을 만족하는가?&lt;/li&gt;
&lt;li&gt;어떤 코드 맥락에서, 심벌이 가질 수 있는 타입은 어떤 것인가(type guard에 의한 분기)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 검사의 구현은 &lt;a href=&quot;https://en.wikipedia.org/wiki/Constraint_satisfaction_problem&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;제약조건을 갖는 만족 가능성 문제(Constrained Satisfaction Problem, CSP)&lt;/a&gt;와 동치다. 하지만 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%B6%A9%EC%A1%B1_%EA%B0%80%EB%8A%A5%EC%84%B1_%EB%AC%B8%EC%A0%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SAT 문제(Satisfiability Problem)&lt;/a&gt;는 NP-완전 문제로 이론상 지수시간의 시간복잡도를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsc는 SAT solver 혹은 CSP solver를 구현하지 않고 Greedy 알고리즘을 사용하는 것으로 추정된다. 실제 SAT solver가 아니기 때문에 추론 깊이의 한계가 있고 복잡하고 깊이가 깊은 타입을 구현할 경우 tsc에서 타입 추론을 정확하게 수행하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제네릭(Generic)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제네릭은 타입에 대한 함수이자 관계 그 자체다.&lt;/li&gt;
&lt;li&gt;1차 논리만 서술 가능하다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉 고차 함수는 허용하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;명시적 타입 전달(Explicit Type Argument Passing)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제네릭 타입 인자에 직접 타입을 기술
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) `useState&amp;lt;{ x?: number}&amp;gt;({})`&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실행 흐름 상에서 명시적 타입 전달이 된 이후부터는 전달된 타입으로 추론&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;타입 인자 추론(Type Argument Inference)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입 선언 안 한 경우, 제네릭 타입의 인자를 생략한 경우, infer 문을 사용한 경우&lt;/li&gt;
&lt;li&gt;실행 흐름상에서 분석된 정보를 바탕으로 타입 인자를 추론&lt;/li&gt;
&lt;li&gt;최대한 비관적이고 보수적으로 분석
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 작은 타입으로 추론한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Greedy로 추론하기 때문에 완벽하게 추론되지 않는 경우가 있다.&lt;/li&gt;
&lt;li&gt;라이브러리 제작자가 자주 고려해야 할 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수 시그니쳐 오버로딩에서 순서에 민감하다.&lt;/li&gt;
&lt;li&gt;가장 위부터 만족하는 함수 시그니쳐를 찾고 찾았다면 그 이후 시그니쳐는 확인하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조건부 타입(Conditional Type)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 타입이 다른 타입의 서브타입인지 확인&lt;/li&gt;
&lt;li&gt;그 여부에 따라서 다른 타입으로 변환&lt;/li&gt;
&lt;li&gt;생각보다 Greedy 하고 일관성 없음 (왜 안되는지 모를 때가 많음)&lt;/li&gt;
&lt;li&gt;`type HasName&amp;lt;T&amp;gt; = T extends { name: string }? true : false`&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;응용문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 `flattenObject` 함수의 타입을 작성해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://www.typescriptlang.org/play/?#code/PQKhCgAIUgDeAuBnKMDGB7AdkhkCWWADgK4IBckA3qpHXQIaVYkC2ARgKYBOA3LfUjtKuboQDm-aIMhoRCMVnEBtALpSYggCaUa0mfS07qAgzKPGWAGysazdAL537F4ySxbOAM0Kctz+idTOk5dYJlOUMhRCQDHOMgvSnYMDCtOBiw7B1p4WFRgcEwcPEJSPABeE0ZKAAYAGighSgByACEWxro5SGUWgGEW1S7IYz1DMabtI2YSGxHHBdGLSndPHyw-EYcRqPGASEjWgFEWpp2mpMgFEk5wIPBQGFzEFGli3EgMMnKw-XomJAWBweAlhNEFLFwj0Yko1Akrik0hksuFXKsPN5fP40TMgXNbOEjhDFJJaDlpHkCkVsJ9vghypAql4rAwEAhOFgAPLsABWnDQCAAFGUyABKfjgKXSmXSp65foYIgATx5-MFSFggBSmyCAXBrAC0zkEAAwuAUPHACLjkEAH7WAAGbADodkEArz2AGoHABqrOtNgBdxwAnTQA6Wimi3OwA6q5BACNrkEADD2AF9GPZBACKjgBmmvWGgOQQAeNYAb9sALQ2QQALY27IIAGmsAPzWAH07IIBu0cALaOAF57AAnjfughQQyqInEgipVaoFCAAPAAVSCcAAeHI8SC+fJ7AD4mdVegBpAhYSAAa04yowXkg-dUlH7ygXqnukvl0kAiJOASpnjSbIIBYwcAjIOADXG4IAkGpvFs2ADcePbACmzgAGHbAgAuq5AwGAJ9N94PuWgADk4ALWO+rQsaAL01gANY1BOqABUNgAHNWBkGPi+npApwP7cP+AGAD-NeYFiW5YVjBkCAN1dQaIU24Atm2kAAGL4FYHLcG0yoAHLYEJnC4H4ABqDBWLcA6zlUTSDiOY5aBOGBToKUyQAA-DuQ6jpyamQO4q5YBgADuWBqHQwDAJGUY3i+gA6s4AgwOACDjkCABFDgA+y6agAe4yZWBmZZ1mqIAPOP2tGN6QIAATVOtpdB6YO9ipXZDkfpAgBoNTqj6AALjmU2vagAYPYAGmuJZAzAkTwqUyOlV7JpAr4WjlUEvu+qbfr+kCAaBj6QIAtQOADgTkCADtDgAjzdp+68LVaX2QagAANfGSbNdluUPgVHXmladqQGVUoce2IlYGJElaN2gqCdJsmcAOBmqepmkIAp86Hsua4bluO57txvH8YJx2nRyWjXXJB5HtOJ5SmeMB5MgtCHZAgPicDF0IFdMm3AASuJcyVNUmgArMIJ8OE4KwmS-zdPIpLwmiVWkTN9Dpcc3DcBg3AAIREgzoKCCzbMc9zVOJMkqTpJk2QvPkbGI8jZ1oxjN040geNzvLqNPUrcmHV9orPZKspG48YC0J5sAa34ivKqDnCwIAC6OADiD62xjBgAzYzBDGAFKjcbUTqtGVgxjGABGrrGwwRmVdWRPVAX10GQO79aNiAzatu2PF8TwAOiSjfgAArs223AtgO9SQAuL1NEuKlGRO66btu-baclh6qPdteTuqeD2Olr6xgaN7+QxgCqa-5FUt0e7fjkFIVWTZzP2X3GUBSPY-2HpS6zbNvextFnqAD7tgAuXVBFqACKrgAMi4Ag50ut7WqsWYvPcFvZg7w5saJo1B-H4+Z9XyxjYyEoJvZ+tVX4DyInGQAE01HxPpAC+19b73zoI-JmICDDpQXEtfsL5HzvyTGhHUgAWbtGv-A6ackY5wVk9O6Ndp4aS7i9cYb1CC-UzgJYSlDgYFyVDwEu-Yy71y+v2acP1wbHgeDDOArwEbkMtudJ6Ks1ZVD0ITGo+ISZghplCEWMJIRwnUOERE4sURS0pPAakctOFWwUbjPi6srHyK7r2XW259bTkNsbGUkiPSAAiewAK6N7V2ubORaMHaO0GkNEMDUB4-x6oAHNnQJJ0gLkDO-0OEnVzlobhRcWywFNE7OBV8CyAADewAIb2ABnOyAgAHGsAB319pgKAB+J4+gAcFrDqnTiqSs7CUyaXculc6DV0MtPQRjdm47lblPYy9Cewv0Xv3FMJoh6QFHuPcZk9aHGVMuZOebdIBgIWR6GBe9Fmr1IevYipE0G2TmcvE0hzj6ABKF8s8CzkP3LlcuqNzox4M-ncmB8Cb6QC9nfABghH4fOuU1eZN537QMec8v+SDKoXL5hCvZ9lME7hfA8paH8CGQGIaQ9i5CADKAALGSVhLJA2sU45SQyplPUYU0ZhK5OnsJpVoXpIydwiP3K3KGJsIBmNgPDaQiNyWUupZktGii7HKPpgTVKeIVHPxWPiGwCRBBBBFtMPEawsSbBxCLHVqihx-DNYIYkFMEimsgBSWG5jZakopTYaVVCu5yvxpKt1FlOVo2cWnPWxAyDuM8UbSRsB+wYAAJJYH4kgHs+BsCwAKoAAnHIGAAoxzK-lz6AE5BwAGoM3kipAQAAz2lVGg890dyw7JOkAARh9HAelD1ICZGVLpSAQoAD6+4xRMlnF+DA+AtDIqjjLVRUbJkTnbdqSAgBbVdcpAQAHDMekAAPd9pF1wB7X2gdkAh0jtgCWwCrTk6CBvpUwADhOABca4pMdICADHR2MsAd07n7RUQdw6tCwAtIAFB77ROkAJVjLpAAxNZAQAGC3HwzdmgKO57aQEADstgAKrsAC6dgAQGsgIAVJ7ACac7GQAgBOAeKl6SApSIw4YtARwA2D0gdPcEQAEGOlkACctVZ9JVFwPgbgShIAAB91FcCfoAH3HIBoWCIBIsxYdSvopjxvjPB32fpHZAXFSYhRSb0eIeT+6v39t46p3tsnuCaYPVoft3oz20AAExNp9DZ6dXb9OEC8DVElRmv3BD01opQrmR06a7a+4E-HvMmfoL2KodBX0ub3cZ4I5ZymAEcJgsC1ACl45AQAieMFRJeWQAfDNxn3sEF9+nIsfq04e59HmSQSCC758rAW5NRe07AF8vkjSABDx2M4HAAMdZAdywFnaQH8nWHUxVS14fXfaElOoKbFgMy+QACrWAEXJwAlquBVq0-AAZBVpQZnqRynsrAW2modSvkIttaDx64mkMRgdmhDLHoMLnAeHlu5TymxFWKmAV3MbiR9VSv1MqbGq3lUq6YFrlV4msISE1CR0RBXWNibIMnVVWqiDa8k0sLHkIOz991mtPW2Pxtd7Hf2PU9kDW2YN5Rpxhuhnt6NcaE1JpTemrNOb81FtNCW8tlbq3bcRnT+NPBE2CmTVgeSc4hRKTs+2tF6Ut2rqRXpV9-YgsgPSjeu9poY4WkAK2LoEzv2kAGzdgV+zlhN-1us9pWqAAtVwAGEMvn1yRsj2GKOAdecglF3Amj9o2ROV9jnnMq-RZAB3RvIC26a35bagEXTHwGy+YbD4S0DZ1I7QAMH0c-IuBljubC2c9KrRwQekSVTQ95KCVrrfsAFUsAWW4AwIgbZHGk5bR3aZgoXr84Z8L7AvZCcV5x7S5vVPqeRukeKl1UqLLV9r-Xxvsr8dziRxMUHLhwcEltdD9VBqNh+ARxtpfIQUfqdMY6id7T2xE+n3Xhvg-BRernJfmv1+5-UJccuSn0py+T64qydknIA0t50JMr2IZIk6Chi4baP4z435N7gHCLQyvan7vbXAT6+o-5shjjz6A74whLUIH5trEz8aaKbaUxmq6K0wGIiyTC6rL7A6zTqoQ5aqBBMF0Aw7b7w7hB2pWor5mDWrH6cEIhizIiSzkizj764g8EGAMHr4CG4j6qYg77GowAOCQDiEix8Gkgn5SJn7EqcRE7oF-7cgA5KKQD6G-6YGv5BquIhrPThpeL2T9jkIyT4AMATgtCszszcAAAikQRA1ez+t+CALQsgHGaAcwDA3AVgHa3A3gPAnIaA4kBAyAnAVgXgPoQoFmAALAAKwABsYouh7YHhHMPhnAfhWAARsBfYgBjK92ikdAnegujOouaKsykAAAzE2iWP1mztpMUd4b4QYRYU4lculBZgALSdGQD1rjGeBEAIBkqQCACTy4ACMLgAKmuQDNI3jwbWigSAA144AAO1eGJaqxaxFU12rRoC9kExVmkA0SCy0xsxZRCxyx6xFUdARO-q1Cwiz8Yx4xjadx144Ct4f4cSFUs4Bg2kkMdAs4ZCnE-RpRRAQx-+3xdmbez0c4TQuBdKEJlx6UAJHogAH92vGAAxa1sY+E0Btgib4f4bPoEWLlvGMU2vcdsZALsZAIcccfaKcXYfYZAIAD0D86gAD6OAANA3WC+DBEKUsQtOWB6K7hyQcZAIADerPkN4hRkAOMYR3ASAIuAAEikUXGLvUfpD7p3DMpcQvF2nLh6DBJUpKdKaegUYXpAIiciUYTiR8n8VMTMXMS8acZsayeyZyScW8WCqXnCe2IiVfnSVUTdq2uif0juLGgLtqc0b2BafzPZFMd0TngWtpJqSQNqXqQaTwL2OcV9kgOmRmegtceMbcSyZrj6c8YsTyfYJ8f9h6YybWQCQ2SCWCQYNCfQIObCeqa6eYSiXSmicAcadiUPhmfiU2kSaSeSQ+JSS6TSU-jGQAbiartccyUCQsjsfsUcSGWcUKtLMgYjGORgROT2PflULOeAfgYCKtsQajjop5ioJQWatQZarQfgavpYDITQfEOEHqm4AoRwVDjzHQQYBodomalwfQEYsIaiNIA4GIRQqAbjqTvgSqmBRMEBZqrIVQVvpBUatkJhY+X2DZj6FRQ4gGrRdCafhjpxNeYYVgSYexcMaTm-m4h4uGulIABdNgAOEOAAR64VLtM6AWJ6LaTFIBBaIAL7jXWgAvkPjSAC6HWBIAACTiEiMWMaQVgRpr0S4LCT2oiAqKh++EiiB2hl55C+lNg95sFBBBmb5-BH5JBdMIsKFEsaFP5ZFcOFFchswxF6hR+mhaOIqrF7YDlVgTlsVvY3Ft54BfFNhVO0oXg7g3eK4LIN57pzeU5DCQo9C+4ZcMR2BlA0uyiDgYolACVSV+VcBVOJg+wXgHMXaHweA9c709C-a4wdA+w+A24Qob+9Cyg9cbcFQU1kALQ6JwRa2G2Y1E1qhG2nMxVfI41G4bchAuAmQCRX0AAgmzAwMqGKH1e8ZAK1eOY1cKEtVtWVfjhKBdSoSkYmvOAYPsOVXjJtcqJNZ3D9ZQWYBSIENpDEQgIWSuF9XxPwDkEAA&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TS Playground&lt;/a&gt;에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1731820371209&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function flattenObject(obj: any, result: any = {}): any {
	for (const key in obj) {
    	if (typeof obj[key] === 'object' &amp;amp;&amp;amp; obj[key] &amp;amp;&amp;amp; !(obj[key] instanceof Array)) {
        	flattenObject(obj[key], result);
        } else {
        	result[key] = obj[key];
        }
    }
    return result;
}

/**
 * ```ts
 * const input: {
 *     a: number;
 *     b: string;
 *     c: string[];
 *     d: {
 *         dd: {
 *             ddd: null;
 *         };
 *         dddd: undefined;
 *     };
 *     e: {
 *         ee: string;
 *     };
 *     f: boolean;
 * }
 * ```
 */
const input = {
  a: 0,
  b: 'B',
  c: ['C'],
  d: {
    dd: {
      ddd: null,
    },
    dddd: undefined,
  },
  e: {
  	ee: 'E'
  },
  f: true
};

/** 
 * ```ts
 * const output: {
 *     a: number;
 *     b: string;
 *     c: string[];
 *     f: boolean;
 *     dddd: undefined;
 *     ddd: null;
 *     ee: string;
 * }
 * ```
 */
const output = flattenObject(input);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;step 1. 이미 평탄화된 속성과 중첩 객체 속성 분리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 depth가 1인 속성들(`a, b, c, d, e, f`)를 살펴보자. depth가 1인 속성 중에서 이미 평탄화가 되어있는 속성인 `a, b, c, f`는 그대로 출력하고 배열이 아닌 객체 타입을 값으로 갖는 속성인 `d, e` 재귀적으로 평탄화하면 된다. 이 둘을 분류하는 타입을 작성해  보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이미 평탄화된 속성 추출하기 1 (값을 never로 추론)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평탄화된 속성을 추출하는 방법으로 추출하지 않을 속성들의 타입을 `never`로 만들어 제거하는 방법을 생각해 볼 수 있다. 하지만 이 경우 &lt;u&gt;키는 지워지지 않고 키의 값이 never로 추론돼 원하는 결과를 얻지 못한다&lt;/u&gt;.&lt;/p&gt;
&lt;pre id=&quot;code_1731822759314&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * `CopyObjects`는 객체 타입을 그대로 복사하는 타입이다.
 * 타입을 복사한 후 배열이 아닌 객체 타입을 걸러내 원하는 결과를 얻어보자.
 */
type CopyObject&amp;lt;T extends object&amp;gt; = {
  [K in keyof T]: T[K]
};

/**
 * 중첩 타입 속성의 `값 타입을 never로 추론`해 해당 속성를 지운다.
 * 이 경우 속성는 남고 해당 속성의 타입이 never로 추론돼 원하는 결과를 얻지 못한다.
 */
type FilterByNonNestedValue&amp;lt;T&amp;gt; = 
  T extends object
    ? T extends unknown[]  // 배열 타입의 최상위 슈퍼타입인 unknown[]으로 배열 타입 검사
      ? T                  // 배열 타입을 갖는 속성은 타입을 그대로 반환
      : never              // 중첩 객체 값을 갖는 속성의 값 타입을 never로 추론해 속성 삭제 시도
    : T;                   // 객체가 아닌 값을 갖는 속성은 값 타입을 그대로 반환

type NonNestedObjectByValue&amp;lt;T extends object&amp;gt; = {
  [K in keyof T]: FilterByNonNestedValue&amp;lt;T[K]&amp;gt;
};

/**
 * ```ts
 * type NonNestedObjectByValueResult = {
 *     a: number;
 *     b: string;
 *     c: string[];
 *     d: never;    // Error!
 *     e: never;    // Error!
 *     f: boolean;
 * }
 * ```
 */
type NonNestedObjectByValueResult = NonNestedObjectByValue&amp;lt;typeof input&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이미 평탄화된 속성 추출하기 2 (속성을 never로 추론)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 `NonNestedObjectByValue`에서는 속성이 지워지지 않아 원하는 결과를 얻지 못했다. 값의 타입이 아닌 `속성의 타입을 never로 추론`해 속성을 지우면 원하는 결과를 얻을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1731822908112&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 위 `NonNestedObjectByValue`에서는 속성이 지워지지 않아 원하는 결과를 얻지 못했다.
 * `속성의 타입을 never로 추론`해 속성를 지워보자.
 */
type FilterByNonNestedProperty&amp;lt;T, K&amp;gt; = 
  K extends keyof T
    ? T[K] extends object         // 값이 객체 타입인지 확인
      ? T[K] extends unknown[]    // 값이 배열 타입인지 확인
        ? K                       // 값이 배열 타입이라면 속성을 필터링하지 않는다.
        : never                   // 값이 배열이 아닌 객체 타입이라면 속성을 필터링한다.
      : K                         // 값이 객체 타입이 아니라면 속성을 필터링하지 않는다.
    : never;                      // K가 T의 속성이 아닌 경우는 무시한다.

type NonNestedObject&amp;lt;T extends object&amp;gt; = {
  [K in FilterByNonNestedProperty&amp;lt;T, keyof T&amp;gt;]: T[K]
};

/**
 * ```ts
 * type NonNestedObjectResult = {
 *     a: number;
 *     b: string;
 *     c: string[];
 *     f: boolean;
 * }
 * ```
 */
type NonNestedObjectResult = NonNestedObject&amp;lt;typeof input&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;step 2. 중첩 객체 속성 타입 추출하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 반대로 위 `NonNestedObject`에서 삭제한 중첩 객체 속성을 추출해 보자. `FilterByNonNestedProperty`타입에서 필터링하는 부분만 거꾸로 해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1731824393471&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 이번엔 반대로 위 `NonNestedObject`에서 삭제한 중첩 객체 속성을 추출해 보자. 
 * `FilterByNonNestedProperty`타입에서 필터링하는 부분만 거꾸로 해주면 된다.
 */
type FilterByNested&amp;lt;T, K&amp;gt; = 
  K extends keyof T
    ? T[K] extends object       // 값이 객체 타입인지 확인
      ? T[K] extends unknown[]  // 값이 객체 타입이라면 배열 타입인지 확인한다.
        ? never                 // 값이 배열 타입이라면 키를 필터링한다.
        : K                     // 값이 배열이 아닌 객체 타입이라면 필터링하지 않는다.
      : never                   // 값이 객체 타입이 아니라면 키를 필터링한다.
    : never;                    // K가 T의 키가 아닌 경우는 무시한다.

type ShallowNestedObject&amp;lt;T extends object&amp;gt; = {
  [K in FilterByNested&amp;lt;T, keyof T&amp;gt;]: T[K]
};

/**
 * ```ts
 * type ShallowNestedObjectResult = {
 *     d: {
 *         dd: {
 *             ddd: null;
 *         };
 *         dddd: undefined;
 *     };
 *     e: {
 *         ee: string;
 *     };
 * }
 * ```
 */
type ShallowNestedObjectResult = ShallowNestedObject&amp;lt;typeof input&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;step 3. 중첩 객체 차원 낮추기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`ShallowNestedObject`는 중첩 객체를 값으로 갖는 속성들만 추출한 타입이다. 이 타입의 차원을 하나 낮춰보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`ToIntersection`타입이 이해가지 않을 수 있다. 유니온 타입을 인터섹션 타입으로 변환해 주는 타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 첫 번째 삼항 연산자는 `T extends any ? (_: T) =&amp;gt; void : never` 항상 참이므로 `T`를 인자로 받는 함수 타입을 반환한다. 바로 함수 타입을 만들지 않고 삼항연산자로 만드는 이유는 분배법칙을 이용하기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 직접 함수타입을 만든다면 `T = string | number`인 경우 `(_: string | number) =&amp;gt; void`와 같은 타입으로 추론된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 삼항 연산자를 사용하면 분배법칙이 적용되어 `((_: string) =&amp;gt; void) | ((_: number) =&amp;gt; void)`와 같은 타입으로 추론된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 함수의 유니온 형태로 만들어 슈퍼타입을 추론하면 두 함수의 인자 타입의 서브타입인 인터섹션 타입을 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732628067055&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// `Values`는 값의 타입을 유니온으로 추출한다.
type Values&amp;lt;T extends object&amp;gt; = T[keyof T];

/**
 * ```ts
 * type ValuesShallowNestedObjectResult = {
 *     dd: {
 *         ddd: null;
 *     };
 *     dddd: undefined;
 * } | {
 *     ee: string;
 * }
 * ```
 */
type ValuesShallowNestedObjectResult = Values&amp;lt;ShallowNestedObject&amp;lt;typeof input&amp;gt;&amp;gt;;


// `ToIntersection`은 유니온 타입을 인터섹션 타입으로 변환시키는 타입이다.
type ToIntersection&amp;lt;T&amp;gt; = (
  T extends any                   // 항상 참이다.
    ? (_: T) =&amp;gt; void              // 조건부 타입 추론을 통해 유니온으로 묶인 T를 T를 인자로 갖는 함수의 유니온으로 분배법칙을 적용한다.
    : never
  ) extends (_: infer S) =&amp;gt; void  // 유니온으로 묶인 함수의 슈퍼타입을 추론하면 인자의 반변성으로 인자는 서브타입으로 추론되어 인터섹션으로 변환된다.
    ? S
    : never;

type ShallowUnwrappedObject&amp;lt;T extends object&amp;gt; = ToIntersection&amp;lt;Values&amp;lt;ShallowNestedObject&amp;lt;T&amp;gt;&amp;gt;&amp;gt;;

/**
 * ```ts
 * type ShallowUnwrappedObjectResult = {
 *     dd: {
 *         ddd: null;
 *     };
 *     dddd: undefined;
 * } &amp;amp; {
 *     ee: string;
 * }
 * ```
 */
type ShallowUnwrappedObjectResult = ShallowUnwrappedObject&amp;lt;typeof input&amp;gt;


type ShallowFlattenObject&amp;lt;T extends object&amp;gt; = NonNestedObject&amp;lt;T&amp;gt; &amp;amp; ShallowUnwrappedObject&amp;lt;T&amp;gt;

/**
 * ```ts
 * type ShallowFlattenObjectResult = NonNestedObject&amp;lt;{
 *     a: number;
 *     b: string;
 *     c: string[];
 *     d: {
 *         dd: {
 *             ddd: null;
 *         };
 *         dddd: undefined;
 *     };
 *     e: {
 *         ee: string;
 *     };
 *     f: boolean;
 * }&amp;gt; &amp;amp; {
 *     dd: {
 *         ddd: null;
 *     };
 *     dddd: undefined;
 * } &amp;amp; {
 *     ee: string;
 * }
 * ```
 */
type ShallowFlattenObjectResult = ShallowFlattenObject&amp;lt;typeof input&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;step 4. 재귀적으로 차원 낮추기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위에서 만든 `ShallowFlattenObject`를 재귀적으로 적용해 `FlattenObject`를 완성해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아래와 같이 재귀적으로 작성하면 `type circulary references inself` 에러가 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1732629009912&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Type alias 'ErrorDeepUnrappedObject' circularly references itself.(2456)
type ErrorDeepUnrappedObject&amp;lt;T extends object&amp;gt; = 
  ToIntersection&amp;lt;                          // 3. 결과 인터섹션
    ErrorDeepFlattenObject&amp;lt;                // 2-3. 1-depth 평탄화 된 타입에 대해 재귀적으로 평탄화
      Values&amp;lt;                              // 2-2. 중첩 객체 타입 1-depth 평탄화
        ShallowNestedObject&amp;lt;T&amp;gt;             // 2-1. 중첩 객체 타입 추출
      &amp;gt;       
    &amp;gt;
  &amp;gt; 

type ErrorDeepFlattenObject&amp;lt;T extends object&amp;gt; = 
  NonNestedObject&amp;lt;T&amp;gt;                       // 1. 이미 평탄회된 속성
  &amp;amp; ErrorDeepUnrappedObject&amp;lt;T&amp;gt;             // 2. 중첩 객체 타입에 대해 재귀적으로 평탄화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 삼항연산자를 이용해 지연평가 시켜 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입도 tsc가 해석한다. 이때 위 오류가 발생한 코드에 삼항연산자를 적용하면 tsc의 런타임에 타입 평가가 지연되어 오류가 발생하지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1732631448680&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 삼항연산자의 지연평가를 이용한 재귀 헬퍼 타입
type RecursionHelper&amp;lt;T&amp;gt; = 
  T extends object                         // (항상 참이지만 지연평가된다.)
    ? DeepFlattenObject&amp;lt;T&amp;gt;                 // 2-3. 1-depth 평탄화 된 타입에 대해 재귀적으로 평탄화
    : never;

type DeepUnwrappedObject&amp;lt;T extends object&amp;gt; = 
  ToIntersection&amp;lt;                          // 3. 결과 인터섹션
    RecursionHelper&amp;lt;
      Values&amp;lt;                              // 2-2. 중첩 객체 타입 1-depth 평탄화
        ShallowNestedObject&amp;lt;T&amp;gt;             // 2-1. 중첩 객체 타입 추출
      &amp;gt;
    &amp;gt;
  &amp;gt; 

type DeepFlattenObject&amp;lt;T extends object&amp;gt; = 
  NonNestedObject&amp;lt;T&amp;gt;                       // 1. 이미 평탄회된 속성
  &amp;amp; DeepUnwrappedObject&amp;lt;T&amp;gt;                 // 2. 중첩 객체 타입에 대해 재귀적으로 평탄화

/**
 * ```ts
 * type DeepFlattenObjectResult = NonNestedObject&amp;lt;{
 *     a: number;
 *     b: string;
 *     c: string[];
 *     d: {
 *         dd: {
 *             ddd: null;
 *         };
 *         dddd: undefined;
 *     };
 *     e: {
 *         ee: string;
 *     };
 *     f: boolean;
 * }&amp;gt; &amp;amp; NonNestedObject&amp;lt;{
 *     dd: {
 *         ddd: null;
 *     };
 *     dddd: undefined;
 * }&amp;gt; &amp;amp; NonNestedObject&amp;lt;...&amp;gt; &amp;amp; NonNestedObject&amp;lt;...&amp;gt;
 * ```
 */
type DeepFlattenObjectResult = DeepFlattenObject&amp;lt;typeof input&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;step 5. 마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`DeepFlattenObject` 타입으로 원하는 결과를 얻었다. 하지만 위 타입은 디버깅이 어렵다. `Roll` 타입으로 tsc 트릭을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1732631731698&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 단순히 타입을 그대로 복사하는 타입이지만 타입 추론을 읽기 쉽도록 해준다.
type Roll&amp;lt;T&amp;gt; = {
  [K in keyof T]: T[K]
} &amp;amp; {};


/**
 * ```ts
 * type RollResult = {
 *     a: number;
 *     b: string;
 *     c: string[];
 *     f: boolean;
 *     dddd: undefined;
 *     ddd: null;
 *     ee: string;
 * }
 * ```
 */
type RollResult = Roll&amp;lt;DeepFlattenObject&amp;lt;typeof input&amp;gt;&amp;gt;


function flattenObject&amp;lt;T extends object&amp;gt;(obj: T, result: any = {}): Roll&amp;lt;DeepFlattenObject&amp;lt;T&amp;gt;&amp;gt; {
	for (const key in obj) {
    	if (typeof obj[key] === 'object' &amp;amp;&amp;amp; obj[key] &amp;amp;&amp;amp; !(obj[key] instanceof Array)) {
        	flattenObject(obj[key], result);
        } else {
        	result[key] = obj[key];
        }
    }
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;부록&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공변성(Covariant), 반공변성(Contravariance)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;두 타입간의 관계와 두 타입이 매핑된 타임간의 관계 사이의 관계&amp;rdquo;로 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계 사이의 관계라는 말이 와닿지 않을 수 있는데, 예시를 보면 이해가 쉽다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 타입간의 관계: $\text{과일} \gtrsim \text{사과}$ (과일은 사과의 슈퍼타입이다.)&lt;/li&gt;
&lt;li&gt;매핑함수: $\text{X} \Longrightarrow \text{X주스}$&lt;/li&gt;
&lt;li&gt;매핑된 타입간의 관계: $\text{과일주스} \gtrsim \text{사과주스}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 타입간의 관계와 매핑된 두 타입간의 관계가 같다. 따라서 매핑함수의 결과는 공변적이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://ko.m.wikipedia.org/wiki/공변성과_반공변성_(컴퓨터_과학)&quot;&gt;https://ko.m.wikipedia.org/wiki/공변성과_반공변성_(컴퓨터_과학)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;평탄화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평탄화(flatten)은 객체 혹은 배열의 depth를 줄이는 개념으로 이해할 수 있다. 집합으로 예를들면 최대 깊이가 2인 X를 평탄화해 깊이를 줄이면 Y와 같은 결과를 예상할 수 있다.&lt;br /&gt;X&amp;nbsp;=&amp;nbsp;{&amp;nbsp;a,&amp;nbsp;b,&amp;nbsp;{&amp;nbsp;c&amp;nbsp;},&amp;nbsp;d&amp;nbsp;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Y&amp;nbsp;=&amp;nbsp;{&amp;nbsp;a,&amp;nbsp;b,&amp;nbsp;c&amp;nbsp;,&amp;nbsp;d&amp;nbsp;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;대표적으로 javaScript의 `Array.prototype.flat()`이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Languages/JS∕TS</category>
      <category>circulary references inself</category>
      <category>extends</category>
      <category>infer</category>
      <category>ROLL</category>
      <category>tointersection</category>
      <category>삼항 연산자</category>
      <category>재귀 타입</category>
      <category>타입 이론</category>
      <category>타입 추론</category>
      <category>평탄화</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/281</guid>
      <comments>https://cloer.tistory.com/281#entry281comment</comments>
      <pubDate>Thu, 14 Nov 2024 02:06:23 +0900</pubDate>
    </item>
    <item>
      <title>[Build your own React] step0: Review</title>
      <link>https://cloer.tistory.com/280</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;시리즈에 대한 설명과 관련 글은 &lt;a href=&quot;https://cloer.tistory.com/279&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;에서 모아보실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step 0: Review&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 React.js(이하 React)의 내부 동작을 살펴보기 앞서 기본 개념에 대해 다룹니다.&amp;nbsp;&amp;nbsp;React, JSX, DOM 요소가 기본적으로 어떻게 상호작용하며 동작하는지 알고 있다면 건너뛰어도 좋습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729412256079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const element = &amp;lt;h1 title=&quot;foo&quot;&amp;gt;Hello&amp;lt;/h1&amp;gt;;
const container = document.getElementById(&quot;root&quot;);
ReactDOM.render(element, container);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위&amp;nbsp;세&amp;nbsp;줄의&amp;nbsp;코드로&amp;nbsp;작성된&amp;nbsp;React&amp;nbsp;앱으로&amp;nbsp;React,&amp;nbsp;JSX,&amp;nbsp;DOM에&amp;nbsp;대해&amp;nbsp;가볍게&amp;nbsp;살펴봅시다.&amp;nbsp;&lt;br /&gt;첫 번째 줄은 React 요소(element)를 정의하고 두 번째 줄은 DOM으로부터 컨테이너로 사용할 노드(node)를 가져옵니다. 마지막 줄은 React 요소를 컨테이너 노드에 그립니다(render). 이 간단한 React&amp;nbsp;앱에서&amp;nbsp;React에서&amp;nbsp;제공하는&amp;nbsp;기능을&amp;nbsp;vanilla&amp;nbsp;JS로&amp;nbsp;대체해&amp;nbsp;해당&amp;nbsp;기능을&amp;nbsp;조금&amp;nbsp;더&amp;nbsp;자세히&amp;nbsp;이해해&amp;nbsp;봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JSX and React element&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 요소를 정의하는 첫 번째 줄은 javascript 문법이 아닙니다. 이는 JSX 문법이며 JS 런타임에서 실행 가능하도록 Babel과 같은 빌드툴에 의해 JS로 변환됩니다. React를 Vanilla JS로 치환하기 전에 JSX를 JS로 변환해 봅시다. JSX를 JS로 변환하는 과정은 단순합니다. 코드를 파싱하여 태그 이름(`h1`), 속성(`title=&quot;foo&quot;`), 자식(`Hello`)을 추출하고 추출한 값을 인자로&amp;nbsp;&amp;nbsp;`createElement` 함수를 호출하면 됩니다. 이름, 속성, 값 등에 대해 validation과 같은 추가적인 과정이 있지만 글의 맥락과 맞지 않아 생략하겠습니다. JSX를 걷어낸 코드는 아래와 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729412500764&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const element = React.createElement(
  &quot;h1&quot;,
  { title: &quot;foo&quot; },
  &quot;Hello&quot;
);

const container = document.getElementById(&quot;root&quot;);
ReactDOM.render(element, container);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`React.createElement`는 React 요소를 반환합니다. 이렇게 생성된 React 요소는 아래와 같이 전달된 인자를 속성으로 갖는 객체입니다. &lt;a href=&quot;https://github.com/facebook/react/blob/f4cc45ce962adc9f307690e1d5cfa28a288418eb/packages/react/src/ReactElement.js#L111&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;실제로는 더 많은 속성을 갖지만&lt;/a&gt;&amp;nbsp;이 역시 글의 주제에서 벗어나 생략했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729412618654&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const element = {
  type: &quot;h1&quot;,
  props: {
    title: &quot;foo&quot;,
    children: &quot;Hello&quot;,
  },
};

const container = document.getElementById(&quot;root&quot;);
ReactDOM.render(element, container);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 React 요소 객체에서 `type` 속성은 `document.createElement`에 전달되는 `tagName`으로 사용됩니다. `document.createElement`를 래핑하는 `React.createElement`함수는 &lt;b&gt;Step VII&lt;/b&gt;에서 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`props`는 JSX 태그의 attributes에서 추출된 키-값 쌍을 가지며 `children`이라는 특별한 속성도 갖고 있습니다. 위 예시에서 `children`은 문자열 타입이지만 다른 React 요소로 이루어진 배열도 가능합니다. 따라서 `children`속성에 의해 React 요소들은 단방향 계층 구조로 연결되고 트리 구조를 갖습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Render&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 줄 또한 React에서 제공하는 함수입니다. `render`는 React가 DOM을 조작하는 부분입니다. 이 부분을 vanilla JS로 변경해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 DOM에 삽입할 DOM 노드를 생성해야 합니다. 그 뒤, 요소의 `props`를 생성된 노드에 추가하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt; (node와 element가 많이 혼용되어 사용되곤 합니다. 오해를 줄이기 위해 React 요소는 element, DOM 요소는 node를 사용하겠습니다.)&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729413902812&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const element = {
  type: &quot;h1&quot;,
  props: {
    title: &quot;foo&quot;,
    children: &quot;Hello&quot;,
  },
};

const container = document.getElementById(&quot;root&quot;);

const node = document.createElement(element.type);
node[&quot;title&quot;] = element.props.title;
// ... 생략 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 생성된 노드에 자식 노드들을 생성해야 합니다. 위 예시에서는 자식으로 문자열을 가지므로 text node를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729414066400&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const element = {
  type: &quot;h1&quot;,
  props: {
    title: &quot;foo&quot;,
    children: &quot;Hello&quot;,
  },
};

const container = document.getElementById(&quot;root&quot;);

const node = document.createElement(element.type);
node[&quot;title&quot;] = element.props.title;

const text = document.createTextNode(&quot;&quot;);
text[&quot;nodeValue&quot;] = element.props.children;
// ... 생략 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`innerText`대신 `textNode`를 사용한 이유는 나중에 모든 요소들에 대해 같은 방법으로 처리하기 위함입니다. 비슷한 이유로 `h1` 노드에서 `title`속성을 추가한 것과 같은 방식으로 `text` 노드에 `nodeValue`를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 `textNode`를 `node`에, `node`를 `container`에 추가(append)합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729414539729&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const element = {
  type: &quot;h1&quot;,
  props: {
    title: &quot;foo&quot;,
    children: &quot;Hello&quot;,
  },
};

const container = document.getElementById(&quot;root&quot;);

const node = document.createElement(element.type);
node[&quot;title&quot;] = element.props.title;

const text = document.createTextNode(&quot;&quot;);
text[&quot;nodeValue&quot;] = element.props.children;

node.appendChild(text);
container.appendChild(node);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;이렇게 처음 세 줄의 React 앱을 React를 사용하지 않은 vanilla JS로 변환하며 JSX와 React 그리고 DOM이 어떻게 상호작용 하는지 간단하게 알아봤습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;이제 React에서 제공하는 함수들을 직접 제작하며 React를 이해해 봅시다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Frontend</category>
      <category>frontend</category>
      <category>react</category>
      <category>React.js</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/280</guid>
      <comments>https://cloer.tistory.com/280#entry280comment</comments>
      <pubDate>Sun, 20 Oct 2024 17:24:31 +0900</pubDate>
    </item>
    <item>
      <title>[Build your own React] README</title>
      <link>https://cloer.tistory.com/279</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Build your own React&lt;/b&gt; 시리즈는 React.js를 직접 구현하며 내부 구조와 동작 방식을 이해하는 과정을 정리한 글 모음입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rodrigo Pombo(Pomber)님의 &lt;a href=&quot;https://pomb.us/build-your-own-react/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Build your own React&lt;/a&gt;글을 따라가며 내용을 해석, 추가하여 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;step 0&lt;/b&gt;: Review&lt;/li&gt;
&lt;li&gt;&lt;b&gt;step I&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;: The `createElement` Function&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Step II&lt;/b&gt;: The `render` Function&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Step III&lt;/b&gt;: Concurrent Mode&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Step IV&lt;/b&gt;: Fibers&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Step V&lt;/b&gt;: Render and Commit Phases&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Step VI&lt;/b&gt;: Reconciliation&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Step VII&lt;/b&gt;: Function Components&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Step VIII&lt;/b&gt;: Hooks&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Frontend</category>
      <category>frontend</category>
      <category>react</category>
      <category>React.js</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/279</guid>
      <comments>https://cloer.tistory.com/279#entry279comment</comments>
      <pubDate>Sun, 20 Oct 2024 16:57:05 +0900</pubDate>
    </item>
    <item>
      <title>Slash 24</title>
      <link>https://cloer.tistory.com/278</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;올해도 토스 개발자 컨퍼런스 &lt;a href=&quot;https://toss.im/slash-24&quot; target=&quot;_self&quot;&gt;&lt;span&gt;slash24&lt;/span&gt;&lt;/a&gt;가 돌아왔습니다. 작년 slash23의 &lt;a href=&quot;https://youtu.be/3wxG1WLDONI?feature=shared&quot; target=&quot;_self&quot;&gt;&lt;span&gt;server-driven UI로 마지막 어드민만들기&lt;/span&gt;&lt;/a&gt;를 아주 감명깊게 봤던 기억이 있어 바로 응모했습니다. 올해에는 직접 볼 수 있기를ㅠ&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;1701&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vg90Z/btsJcK7z4Ld/yXLb4ZpZUJjOjnkFhKKeBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vg90Z/btsJcK7z4Ld/yXLb4ZpZUJjOjnkFhKKeBk/img.png&quot; data-alt=&quot;내 시간표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vg90Z/btsJcK7z4Ld/yXLb4ZpZUJjOjnkFhKKeBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvg90Z%2FbtsJcK7z4Ld%2FyXLb4ZpZUJjOjnkFhKKeBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1360&quot; height=&quot;1701&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;1701&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내 시간표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;</description>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/278</guid>
      <comments>https://cloer.tistory.com/278#entry278comment</comments>
      <pubDate>Fri, 23 Aug 2024 12:53:31 +0900</pubDate>
    </item>
    <item>
      <title>[LeetCode - 238] Products of Array Discluding Self - C++</title>
      <link>https://cloer.tistory.com/277</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Products of Array Discluding Self&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an integer array nums, return an array output where `output[i]` is the product of all the elements of nums except `nums[i]`.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Each product is guaranteed to fit in a 32-bit integer.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Follow-up: Could you solve it in  ( )&amp;nbsp;time without using the division operation?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Example 1:&lt;/p&gt;
&lt;pre id=&quot;code_1717144211026&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Input: nums = [1,2,4,6]
Output: [48,24,12,8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Example 2:&lt;/p&gt;
&lt;pre id=&quot;code_1717144219536&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Input: nums = [-1,0,1,2,3]
Output: [0,-6,0,0,0]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Constraints:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`2 &amp;lt;= nums.length &amp;lt;= 1000`&lt;br /&gt;`-20 &amp;lt;= nums[i] &amp;lt;= 20`&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1717144237838&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
public:
    vector&amp;lt;int&amp;gt; productExceptSelf(vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        int n = nums.size();
        vector&amp;lt;int&amp;gt; ans(n, 1);

        for(int i=1; i&amp;lt;n; i++)
            ans[i] = ans[i-1] * nums[i-1];

        int acc = 1;
        for(int i=n-2; i&amp;gt;=0; i--) {
            acc *= nums[i+1];
            ans[i] *= acc;
        }

        return ans;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나눗셈을 사용하지 않고 선형시간에 풀라는 제약조건이 없다면 아주 쉬운 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 `nums`의 원소를 모두 곱한 후 `ans[i] = prod / nums[i]`로 답을 선형 시간에 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나누기를 사용하지 말라는 조건이 신선했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 아이디어는 `ans[k] = nums[0] * nums[1] * ... * nums[k-1] * nums[k+1] * ... * nums[n-1]`이므로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rightward로 누적곱과 leftward로 누적곱을 계산해 `k`의 양쪽의 누적곱을 곱해 `ans[k]`를 계산하는 아이디어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 두개의 누적곱을 계산하는 배열을 만들어 구현했다.&lt;/p&gt;
&lt;pre id=&quot;code_1717144489705&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
public:
    vector&amp;lt;int&amp;gt; productExceptSelf(vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        int n = nums.size();
        int leftward[n], rightward[n];
        rightward[0] = nums[0];
        leftward[n-1] = nums[n-1];

        for(int i=1; i&amp;lt;n; i++) {
            rightward[i] = rightward[i-1] * nums[i];
            leftward[n-1-i] = leftward[n-i] * nums[n-1-i];
        }

        vector&amp;lt;int&amp;gt; ans(n, 1);
        for(int i=0; i&amp;lt;n; i++) {
            if(i &amp;gt; 0) ans[i] *= rightward[i - 1];
            if(i &amp;lt; n-1) ans[i] *= leftward[i + 1];
        }
        return ans;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형 누적 계산이어서 추가적인 저장공간을 사용하지 않고 풀 수 있을 것 같다는 생각에 더 고민해봤고 `ans`만을 이용한 풀이를 생각했다.&lt;/p&gt;</description>
      <category>Problem Solving/LeetCode</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/277</guid>
      <comments>https://cloer.tistory.com/277#entry277comment</comments>
      <pubDate>Fri, 31 May 2024 17:38:44 +0900</pubDate>
    </item>
    <item>
      <title>[LeetCode - 271] encode and decode strings - C++</title>
      <link>https://cloer.tistory.com/276</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;분류: 문자열 /&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;271. String Encode and Decode&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Design an algorithm to encode a list of strings to a single string. The encoded string is then decoded back to the original list of strings.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Please implement encode and decode&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Example 1:&lt;/p&gt;
&lt;pre id=&quot;code_1717074586563&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Input: [&quot;neet&quot;,&quot;code&quot;,&quot;love&quot;,&quot;you&quot;]
Output: [&quot;neet&quot;,&quot;code&quot;,&quot;love&quot;,&quot;you&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Example 2:&lt;/p&gt;
&lt;pre id=&quot;code_1717074598688&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Input: [&quot;we&quot;,&quot;say&quot;,&quot;:&quot;,&quot;yes&quot;]
Output: [&quot;we&quot;,&quot;say&quot;,&quot;:&quot;,&quot;yes&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Constraints:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`0 &amp;lt;= strs.length &amp;lt; 100`&lt;br /&gt;`0 &amp;lt;= strs[i].length &amp;lt; 200`&lt;br /&gt;`strs[i]` contains only UTF-8 characters.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1717074664671&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
public:

    string encode(vector&amp;lt;string&amp;gt;&amp;amp; strs) {
        string ans = &quot;&quot;;
        for(const string &amp;amp;str:strs) 
            ans += to_string(str.size()) + &quot;:&quot; + str;
        return ans;
    }

    vector&amp;lt;string&amp;gt; decode(string s) {
        vector&amp;lt;string&amp;gt; ans;
        int i = 0, len = s.size();
        while(i &amp;lt; len) {
            int start = i;
            while(s[i] != ':') i++;
            int length = stoi(s.substr(start, i++ - start));
            ans.push_back(s.substr(i, length));
            i += length;
        }
        return ans;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개의 함수를 구현해 `output = decode(endcode(input))`일 때 `output == input`인 단방향 역원을 구현하는 점이 재밌는 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백준은 출력, 프로그래머스는 하나의 함수를 구현하는 형태의 문제지만 릿코드는 조금 더 다양한 문제가 있는것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`encode`를 구현하는 것은 아주 쉽다. 구분자(delimiter)를 끼워넣어 합치면 된다. 여기서 문제는 `decode`에서 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값은 모두 UTF-8 문자이므로 구분자가 포함된 입력이 들어올 경우&amp;nbsp;문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input = [&quot;a,b&quot;], delimiter = ','&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;output = [&quot;a&quot;, &quot;b&quot;]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분자를 입력값에서 등장할 확률이 매우 낮게 설정할 수도 있을 것이다. 예를들어 `&quot;ㄱ@#a%ㅂㅁ^!#$%ㅍ@$#!ㅏ#$;_!#$%(!&quot;` 와 같이 말이다. 아마 아주 긴 구분자를 사용한다면 테케는 통과할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이는 뭔가 찝찝한 느낌이 들것이다. 긴 구분자를 사용할 경우 인코딩 문자열이 아주 길어진다는 단점과 결국 확률에 의존해야한다는 단점이 있다. 어떻게 하면 UTF-8 문자를 이용한 구분자를 이용해 문자열로 인코딩하고 원본으로 디코딩 할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키는 단순히 같은 구분자를 사용하는 것이 아니라 구분자에 정보를 담는 방법이다. 인코딩하는 문자열의 숫자를 적고 그 뒤에 문자열을 붙인다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input = [ &quot;aaa&quot;, &quot;1234&quot; ]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;encoded = &quot;3:aaa4:123&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문자열의 길이와 문자열 사이에 `:`를 넣는 이유는 문자열이 숫자로 시작하는 경우 어디까지가 메타 데이터이고 어디서부터 데이터인지 알 수 없기 때문이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Problem Solving/LeetCode</category>
      <category>디코딩 문제</category>
      <category>문자열 문제</category>
      <category>인코딩 문제</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/276</guid>
      <comments>https://cloer.tistory.com/276#entry276comment</comments>
      <pubDate>Thu, 30 May 2024 22:24:34 +0900</pubDate>
    </item>
    <item>
      <title>[백준 - 17070] 파이프 옮기기 1 - C++</title>
      <link>https://cloer.tistory.com/275</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;분류: DP, DFS /&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17070&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문제 링크&lt;/a&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유현이가 새 집으로 이사했다. 새 집의 크기는 N&amp;times;N의 격자판으로 나타낼 수 있고, 1&amp;times;1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)로 나타낼 수 있다. 여기서 r은 행의 번호, c는 열의 번호이고, 행과 열의 번호는 1부터 시작한다. 각각의 칸은 빈 칸이거나 벽이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 집 수리를 위해서 파이프 하나를 옮기려고 한다. 파이프는 아래와 같은 형태이고, 2개의 연속된 칸을 차지하는 크기이다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;width: 138px; height: 70px;&quot; src=&quot;https://upload.acmicpc.net/3ceac594-87df-487d-9152-c532f7136e1e/-/preview/&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프는 회전시킬 수 있으며, 아래와 같이 3가지 방향이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;width: 427px; height: 136px;&quot; src=&quot;https://upload.acmicpc.net/b29efafa-dbae-4522-809c-76d5c184a231/-/preview/&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프는 매우 무겁기 때문에, 유현이는 파이프를 밀어서 이동시키려고 한다. 벽에는 새로운 벽지를 발랐기 때문에, 파이프가 벽을 긁으면 안 된다. 즉, 파이프는 항상 빈 칸만 차지해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프를 밀 수 있는 방향은 총 3가지가 있으며, &amp;rarr;, ↘, &amp;darr; 방향이다. 파이프는 밀면서 회전시킬 수 있다. 회전은 45도만 회전시킬 수 있으며, 미는 방향은 오른쪽, 아래, 또는 오른쪽 아래 대각선 방향이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프가 가로로 놓여진 경우에 가능한 이동 방법은 총 2가지, 세로로 놓여진 경우에는 2가지, 대각선 방향으로 놓여진 경우에는 3가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 파이프가 놓여진 방향에 따라서 이동할 수 있는 방법을 모두 나타낸 것이고, 꼭 빈 칸이어야 하는 곳은 색으로 표시되어져 있다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;width: 578px; height: 203px;&quot; src=&quot;https://upload.acmicpc.net/0f445b26-4e5b-4169-8a1a-89c9e115907e/-/preview/&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;가로&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;width: 579px; height: 203px;&quot; src=&quot;https://upload.acmicpc.net/045d071f-0ea2-4ab5-a8db-61c215e7e7b7/-/preview/&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;세로&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;width: 886px; height: 203px;&quot; src=&quot;https://upload.acmicpc.net/ace5e982-6a52-4982-b51d-6c33c6b742bf/-/preview/&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;대각선&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 처음에 파이프는 (1, 1)와 (1, 2)를 차지하고 있고, 방향은 가로이다. 파이프의 한쪽 끝을 (N, N)로 이동시키는 방법의 개수를 구해보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;입력&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째 줄에 집의 크기 N(3 &amp;le; N &amp;le; 16)이 주어진다. 둘째 줄부터 N개의 줄에는 집의 상태가 주어진다. 빈 칸은 0, 벽은 1로 주어진다. (1, 1)과 (1, 2)는 항상 빈 칸이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;출력&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째 줄에 파이프의 한쪽 끝을 (N, N)으로 이동시키는 방법의 수를 출력한다. 이동시킬 수 없는 경우에는 0을 출력한다. 방법의 수는 항상 1,000,000보다 작거나 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;- DFS&lt;/h3&gt;
&lt;pre id=&quot;code_1712481897017&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

int N, ans;
bool isWall[17][17];
int dx[] {0, 1, 1}; // direction -&amp;gt; delta position
int dy[] {1, 1, 0}; // direction -&amp;gt; delta position
vector&amp;lt;int&amp;gt; nxt[] {{0, 1}, {0, 1, 2}, {1, 2}}; // direction -&amp;gt; next directions
vector&amp;lt;int&amp;gt; check[] {{0}, {0, 1, 2}, {2}}; // next direction -&amp;gt; directions should be checked

inline bool isValid(int x, int y) {
    return x &amp;lt; N and y &amp;lt; N and not isWall[x][y];
}

void dfs(int dir, int x, int y) {
    if(x == N-1 and y == N-1) {
        ans++;
        return;
    }

    for(int nd:nxt[dir]) {
        int nx = x + dx[nd], ny = y + dy[nd];
        if(not isValid(nx, ny)) continue;
        
        bool pass = true;
        for(int d:check[nd]) {
            if(isValid(x + dx[d], y + dy[d])) continue;
            pass = false;
            break;
        }

        if(not pass) continue;
        dfs(nd, nx, ny);
    }
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    cin &amp;gt;&amp;gt; N;
    for(int i=0; i&amp;lt;N; i++)
        for(int j=0; j&amp;lt;N; j++)
            cin &amp;gt;&amp;gt; isWall[i][j];
    
    dfs(0, 0, 1);
    cout &amp;lt;&amp;lt; ans;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS로 완전탐색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가로 방향을 0, 대각선 방향을 1, 세로 방향을 2로 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`nxt[i]`는 `i`방향으로 이동해서 `(x, y)`왔을 때, `(x, y)`에서 이동할 수 있는 다음 방향을 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`check[i]`는 `i`방향으로 이동하기 위해 확인해야하는 방향을 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dfs(dir, x, y)`는 `dir` 방향으로 이동해 `(x, y)`에 도착했을 때 다음 이동을 DFS로 탐색하는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dfs()`를 살펴보면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;종료 조건: 목적지 `(N-1, N-1)` 도착할 경우 경로 수 1 증가(`ans++`)&lt;/li&gt;
&lt;li&gt;`(x, y)`로 들어온 방향(`dir`)에 따라 다음에 갈 수 있는 방향(`nxt[dir]`)으로 다음을 수행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다음 방향(`nd`)으로 갈 때, 확인해야 하는 방향(`check[nd]`)으로 모두 갈 수 있는 지 확인&lt;/li&gt;
&lt;li&gt;갈 수 있다면 해당 방향(`nd`)로 이동하고 재귀 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;- DP&lt;/h3&gt;
&lt;pre id=&quot;code_1712481969479&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int N;
bool isWall[17][17];
int way[17][17][3];

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    cin &amp;gt;&amp;gt; N;
    for(int i=1; i&amp;lt;=N; i++)
        for(int j=1; j&amp;lt;=N; j++)
            cin &amp;gt;&amp;gt; isWall[i][j];
    
    way[1][2][0] = 1;
    for(int x=1; x&amp;lt;=N; x++) {
        for(int y=1; y&amp;lt;=N; y++) {
            if(isWall[x][y]) continue;
            way[x][y][0] += way[x][y-1][0] + way[x][y-1][1];
            way[x][y][2] += way[x-1][y][1] + way[x-1][y][2];
            if(isWall[x-1][y] or isWall[x][y-1]) continue;
            way[x][y][1] += way[x-1][y-1][0] + way[x-1][y-1][1] + way[x-1][y-1][2];
        }
    }
    cout &amp;lt;&amp;lt; way[N][N][0] + way[N][N][1] + way[N][N][2];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 DFS로 풀었을 때 시간은 152ms. 보통 0ms ~ 20ms 사이의 시간이 나와야 마음이 편안해 지는데 152ms는 뭔가 느리다고 생각해 더 빠른 풀이를 더 생각해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS 탐색보다 빠른 완전탐색을 생각해보면 백트래킹, 분기한정 등을 이용한 프루닝을 통해 탐색 범위를 좁히는 방법(시간 복잡도는 거의 같다)들과&amp;nbsp;DP 정도가 떠올랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`way[x][y][dir]`은 `(x, y)`에서 `dir` 방향으로 들어오는 경로의 수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 `way[x][y][0] = way[x][y-1][0] + way[x][y-1][1]`의 의미는 가로 방향으로 `(x, y)`에 들어오는 경로(`way[x][y][0]`)는 가로방향으로 `(x, y-1)`에 도착한 경우(`way[x][y-1][0]`)와 대각선 방향으로 `(x, y-1)`에 도착한 경우(`way[x][y-1][1]`)의 합과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막의 `way[x][y][1]`의 경우 대각선으로 들어오는 경우를 의미하고 이 경우에는 추가적으로 주변에 벽이 있는지 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등호가 아닌 `+=`로 기존 값에 더하는 이유는 초기값 때문이다. 논리적으로는 등호가 알맞다. 하지만 등호로 구현할 경우 초기값인 `way[1][2][0]`이 덮어쓰여지지 않도록 추가적인 작업이 필요하다. 어차피 초기화된 값이 0이므로 추가적인 작업을 구현하지 않기 위해 `+=`로 구현했다.&lt;/p&gt;</description>
      <category>Problem Solving/BOJ</category>
      <category>dfs 문제</category>
      <category>dp 문제</category>
      <category>완전탐색 문제</category>
      <author>thecloer</author>
      <guid isPermaLink="true">https://cloer.tistory.com/275</guid>
      <comments>https://cloer.tistory.com/275#entry275comment</comments>
      <pubDate>Sun, 7 Apr 2024 18:45:54 +0900</pubDate>
    </item>
  </channel>
</rss>