<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>vel_cmb.log</title>
        <link>https://velog.io/</link>
        <description>당신을 한 줄로 소개해보세요</description>
        <lastBuildDate>Sun, 08 Dec 2024 07:06:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>vel_cmb.log</title>
            <url>https://velog.velcdn.com/images/vel_cmb/profile/9bc4d6c4-37f7-44ef-8891-86699ce4dd0d/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. vel_cmb.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/vel_cmb" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[워드 프로세서 필기 합격 수기]]></title>
            <link>https://velog.io/@vel_cmb/%EC%9B%8C%EB%93%9C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%84%9C-%ED%95%84%EA%B8%B0-%ED%95%A9%EA%B2%A9-%EC%88%98%EA%B8%B0</link>
            <guid>https://velog.io/@vel_cmb/%EC%9B%8C%EB%93%9C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%84%9C-%ED%95%84%EA%B8%B0-%ED%95%A9%EA%B2%A9-%EC%88%98%EA%B8%B0</guid>
            <pubDate>Sun, 08 Dec 2024 07:06:17 GMT</pubDate>
            <description><![CDATA[<p>공군 지원을 위해 가산점을 모으던 중, 뭔가 애매하게 부족한거 같아서 공인 자격증에 들어가는 워드 프로세서 자격증을 따기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/adfb4761-c6dd-4bf1-80cf-c1692e9a1487/image.png" alt=""></p>
<p>일단 모바일 코참 패스로 필기시험을 접수하고 공부를 시작하기로 했다.
<code>(비용 : 18,200원)</code></p>
<p>저번 겨울방학 때, YBM 인강을 끊어놓은게 있었는데 올해까진데다 마침 자격증 인강도 있길래 돈도 아낄겸 이걸 통해 공부했다.</p>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/7c68b2cd-3e58-4715-8c90-e2069069552c/image.png" alt=""></p>
<p>근데 이렇게 써놓으면 뭔가 열심히 공부한거 같지만 사실 어영부영 대충 동영상 듣다가,
전날 되어서야 뭔가 큰일난거 같아서 호다닥 이론 공부를 마무리했다.</p>
<p>그리고 문제은행식이니 이제 문제풀이를 해야해서, 저기서 준 강의자료의 족보도 풀어보고
강사님이 알려주신 사이트에서 여러 번 풀어보았다.</p>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/721761c2-0494-4e07-83f7-9475bae04dd3/image.png" alt=""></p>
<p>여기 다른 자격증 족보도 많던데 나중에 다른거 딸 때 쓰면 좋을 것 같았다.</p>
<p>그리고 기출 3개년 풀고, 혹시 몰라 1년치 정도만 더 풀고 시험을 치러갔다.
처음 대기실에 갔을 때, 정말 조용하고 다들 정리노트 열심히 보고 있어서 쫄았다.
다들 좋은 결과가 있었으면 좋겠다.</p>
<p>시험장 들어가니 진짜 OMR 카드 같은것도 아니고 only 컴퓨터로만 시험을 쳤다.
그리고 혹시 몰라 수험증 출력해서 갔는데 그냥 신분증만 들고 가면 됐다.</p>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/bf21fdcf-b537-4af4-9bcf-b226b0f1f881/image.png" alt="">
<code>합격 기준 : (각 과목당 40점 이상) and (전체 평균 점수 60점 이상)</code></p>
<p>결과는 다행히 합격했다. 문제 풀면서 1,3번만 연속으로 10개는 나온 것 같아서 뭔가 망했나 싶었는데 다행히 잘 찍었나보다.
다들 2과목이 어렵다는데 진짜 어렵더라...</p>
<p>사족이지만 필기가 바로 다음 날 결과가 나와서 좋다. 실기는 결과 나오기까지 2주 걸리는거 같은데.</p>
<p>마지막으로 시험장이 깨끗하고 좋아서 실기도 거기서 보고 싶었는데 찾아보니 거기 시험장이 지원을 안하는건지 아니면 이미 지원이 마감 됐는지 그 지역은 실기가 없더라...</p>
<p>이제 실기만 치면 되는데 어서 빨리 기말 끝나는 날짜에 신청을 해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[💾유니티 - JSON 파일로 게임 저장하기 1-3 (저장 파일 불러오기)]]></title>
            <link>https://velog.io/@vel_cmb/%EC%9C%A0%EB%8B%88%ED%8B%B0-JSON-%ED%8C%8C%EC%9D%BC%EB%A1%9C-%EA%B2%8C%EC%9E%84-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-1-3-%EC%A0%80%EC%9E%A5-%ED%8C%8C%EC%9D%BC-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@vel_cmb/%EC%9C%A0%EB%8B%88%ED%8B%B0-JSON-%ED%8C%8C%EC%9D%BC%EB%A1%9C-%EA%B2%8C%EC%9E%84-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-1-3-%EC%A0%80%EC%9E%A5-%ED%8C%8C%EC%9D%BC-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Mon, 02 Dec 2024 17:00:16 GMT</pubDate>
            <description><![CDATA[<p>저번에는 유니티에서 게임 데이터를 JSON 파일로 저장해봤다.
이번에는 저장한 JSON 파일을 유니티로 불러오는 작업을 해보자.</p>
<hr>
<h2 id="1-json-파일-역직렬화-deserialization">1. JSON 파일 역직렬화 (<code>Deserialization</code>)</h2>
<p>앞선 글에서 우리는 게임 데이터 객체를 JSON 형식으로 변환해봤다.
근데 이제 저장한 JSON 파일을 어떻게 불러오고, 그 파일의 내용을 어떻게 처리해야할까?</p>
<p>객체를 JSON 파일로 변환시켰던 것처럼, JSON 파일을 활용하기 위해선 이 데이터를 다시 객체로 변환시켜야한다. 이를 <strong>역직렬화<code>(Deserialization)</code></strong>라 한다.</p>
<p>다행히 우리가 썼던 <code>JsonUtility</code>에서 <code>JsonUtility.ToJson</code>메서드를 통해 간단히 객체를 직렬화 했던 것처럼 라이브러리 내부에서 <strong><code>JsonUtility.FromJson</code> 메서드를 사용해서 역직렬화 기능을 사용할 수 있다.</strong></p>
<p>그럼 이제 JSON 파일을 역직렬화하는 코드를 살펴보자.</p>
<hr>
<h2 id="2-저장된-파일-읽기">2. 저장된 파일 읽기</h2>
<pre><code class="language-csharp">string path = Path.Combine(Application.persistentDataPath, &quot;playerData.json&quot;);
if (File.Exists(path))
{
    string jsonData = File.ReadAllText(path);
}</code></pre>
<p>당연히 데이터를 불러오러면 파일을 먼저 읽어야 한다. 위 코드를 분석해보면</p>
<ul>
<li><code>Path.Combine</code>을 통해서 <code>path</code> 변수에 경로값을 할당해준다.</li>
<li><code>File.Exists</code>를 통해서 <code>path</code> 경로에 파일이 존재하는지 확인한다.</li>
<li>만약 존재한다면 <code>File.ReadAllText</code>로 파일 내의 모든 텍스트을 가져와서 <code>jsonData</code> 변수에 할당한다.</li>
</ul>
<p>자, 이러면 <code>jsonData</code>변수에 파일 내에 있던 모든 값이 저장이 됐다.
그럼 이제 이 텍스트를 어떻게 처리해야 데이터 객체로 변환시킬 수 있을까?</p>
<hr>
<h2 id="3-데이터-객체로-변환하기">3. 데이터 객체로 변환하기</h2>
<pre><code class="language-csharp">PlayerData playerData = JsonUtility.FromJson&lt;PlayerData&gt;(jsonData);</code></pre>
<p>이건 JSON 객체로 변환할 때 <code>JsonUtility.ToJson</code>을 사용한 것과 유사하게, <code>JsonUtility.FromJson</code> 메서드를 사용하면 된다.</p>
<ul>
<li><code>JsonUtility.FromJson</code>를 통해서 <code>jsonData</code>에 저장된 텍스트 데이터를  <code>PlayerData</code> 타입의 객체로 변환한 후, <code>playerData</code> 변수에 값을 저장해준다.</li>
</ul>
<p>이러면 데이터를 불러오는 기본 로직이 끝났다!</p>
<p>이제 위의 로직을 합쳐서 한 코드로 만들면 아래처럼 쓸 수 있다.</p>
<pre><code class="language-csharp">using System.IO;
using UnityEngine;

[System.Serializable]
public class PlayerData // 저장할 데이터가 명시된 클래스
{
    public string name;
    public int level;
    public string[] items;
}

// 데이터 로드와 관련된 메서드가 정의된 매니저 클래스
public class LoadManager : MonoBehaviour
{
    // JSON 파일에서 PlayerData를 불러와, 반환해주는 메서드
    public PlayerData LoadData()
    {
        string path = Path.Combine(Application.persistentDataPath, &quot;playerData.json&quot;);
        if (File.Exists(path))
        {
            string jsonData = File.ReadAllText(path);
            PlayerData data = JsonUtility.FromJson&lt;PlayerData&gt;(jsonData);
            Debug.Log(&quot;데이터 로딩 성공! // 플레이어 이름 : &quot; + data.name);
            return data;
        }
        else
        {
            Debug.LogWarning(&quot;세이브 파일을 찾을 수 없음!&quot;);
            return null;
        }
    }
}</code></pre>
<hr>
<p>이제 아래의 예제를 통해서<code>playerData.json</code> 파일을 저장 후, 불러와보자.</p>
<pre><code class="language-csharp">using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestJson : MonoBehaviour
{
    void Start()
    {
        PlayerData player = new PlayerData // 데이터 객체 생성
        {
            name = &quot;Player1&quot;,
            level = 5,
            items = new string[] { &quot;sword&quot;, &quot;shield&quot; }
        };

        SaveManager saveManager = new SaveManager(); // SaveManager 객체 생성
        saveManager.SaveData(player); // 데이터 저장

        LoadManager loadManager = new LoadManager(); // LoadManager 객체 생성
        PlayerData playerData = loadManager.LoadData(); // 데이터 불러오기
        printData(playerData); // 불러온 데이터 출력
    }

    void printData(PlayerData playerData)
    {
        Debug.Log(&quot;name : &quot; + playerData.name);
        Debug.Log(&quot;level : &quot; + playerData.level);
        foreach(string item in playerData.items)
            Debug.Log(&quot;item : &quot; + item);
    }
}</code></pre>
<ul>
<li>예제 코드</li>
</ul>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/0f1741fd-047a-4eee-b389-d21701677fdb/image.png" alt=""></p>
<ul>
<li>데이터가 성공적으로 불러와진 모습</li>
</ul>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/a64f9fc5-757f-4dee-bc73-aa26d06df9a8/image.png" alt=""></p>
<ul>
<li>불러온 데이터 객체에 저장되어 있는 값</li>
</ul>
<hr>
<p>이걸로 게임 저장 파일을 JSON 형식으로 저장하는 것에 이어서 불러오는 기능까지 구현해봤다.</p>
<p>위의 예시 코드를 좀 더 다듬은 코드를 첨부하고 글을 마치겠다.</p>
<ul>
<li><code>try-catch</code> 문을 사용해 예상치 못한 오류 예외처리</li>
<li><code>if</code>문 구문 약간 수정 (로직은 동일함)</li>
</ul>
<pre><code class="language-csharp">using System.IO;
using UnityEngine;

public class LoadManager : MonoBehaviour
{
    public PlayerData LoadData()
    {
        string path = Path.Combine(Application.persistentDataPath, &quot;playerData.json&quot;);
        if (!File.Exists(path))
        {
            Debug.LogWarning(&quot;세이브 파일을 찾을 수 없습니다. 디폴트 파일을 불러옵니다.&quot;);
            return new PlayerData { name = &quot;DefaultPlayer&quot;, level = 1, items = new string[] { } };
        }

        try
        {
            string jsonData = File.ReadAllText(path);
            PlayerData playerData = JsonUtility.FromJson&lt;PlayerData&gt;(jsonData);

            Debug.Log(&quot;데이터 로드 성공!&quot;);
            return playerData;
        }
        catch (System.Exception e)
        {
            Debug.LogError(&quot;JSON 파일을 불러오는데 오류 발생: &quot; + e.Message);
            return new PlayerData { name = &quot;DefaultPlayer&quot;, level = 1, items = new string[] { } };
        }

    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[💾유니티 - JSON 파일로 게임 저장하기 1-2 (저장 기능 만들어보기)]]></title>
            <link>https://velog.io/@vel_cmb/%EC%9C%A0%EB%8B%88%ED%8B%B0-JSON-%ED%8C%8C%EC%9D%BC%EB%A1%9C-%EA%B2%8C%EC%9E%84-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-1-2-%EC%A0%80%EC%9E%A5-%EA%B8%B0%EB%8A%A5-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@vel_cmb/%EC%9C%A0%EB%8B%88%ED%8B%B0-JSON-%ED%8C%8C%EC%9D%BC%EB%A1%9C-%EA%B2%8C%EC%9E%84-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-1-2-%EC%A0%80%EC%9E%A5-%EA%B8%B0%EB%8A%A5-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 25 Nov 2024 11:50:42 GMT</pubDate>
            <description><![CDATA[<p>지난 글에서 JSON 형식에 대해 알아보았다.
이번에는 본격적으로 JSON 파일을 이용해서 게임 저장 기능을 만들어보자. 코드만 참고하고 싶으면 맨 밑으로 내리면 된다.</p>
<hr>
<h2 id="1-json으로-저장할-데이터는">1. JSON으로 저장할 데이터는?</h2>
<p>일단 저장기능을 구현하기 전에, 우리는 무슨 데이터를 JSON으로 저장해야할까? 데이터야 여러가지가 있지만 보통 다음과 같은 예시가 있을 것이다.</p>
<blockquote>
<p>플레이어의 정보, 게임 설정, 현재까지의 진행 상황...</p>
</blockquote>
<p>이제 우린 이런 데이터들을 JSON 형식으로 저장해야한다. 앞선 글에서 나왔던 것처럼, JSON 파일은 키와 값 쌍의 형태로 이뤄져 있다는걸 기억하자. 위의 예시에서 언급된 데이터를 JSON으로 나타낸다면 다음과 같이 나타낼 수 있다.</p>
<pre><code class="language-JSON">{
  &quot;player&quot;: {
    &quot;name&quot;: &quot;Player1&quot;,
    &quot;level&quot;: 5,
    &quot;items&quot;: [&quot;sword&quot;, &quot;shield&quot;],
    &quot;currentStage&quot;: {
      &quot;id&quot;: 3,
      &quot;name&quot;: &quot;Forest of Shadows&quot;,
      &quot;progress&quot;: 45
    },
    &quot;clearedStages&quot;: [
      {
        &quot;id&quot;: 1,
        &quot;name&quot;: &quot;Plains of Beginning&quot;,
        &quot;time&quot;: &quot;00:12:34&quot;
      },
      {
        &quot;id&quot;: 2,
        &quot;name&quot;: &quot;Cave of Echoes&quot;,
        &quot;time&quot;: &quot;00:24:56&quot;
      }
    ]
  },
  &quot;settings&quot;: {
    &quot;volume&quot;: 70,
    &quot;difficulty&quot;: &quot;normal&quot;
  }
}</code></pre>
<p>이외에도 다양한 데이터들이 포함될 수도 있다.</p>
<hr>
<h2 id="2-json으로-데이터-저장하기">2. JSON으로 데이터 저장하기</h2>
<p>자, 어떻게 데이터를 JSON 형식으로 저장할지도 고민해봤으니 이제 JSON으로 데이터를 저장해보자.</p>
<p>먼저 우리는 파일을 저장할 위치를 정해야한다. 파일을 아무런 위치에다 저장해놓으면 나중에 찾기도 힘들고 관리하기도 버겁다.</p>
<p>이럴 때를 위해 유니티에서 제공하는 경로인 <code>Application.persistentDataPath</code> 가 있다.</p>
<p>이 프로퍼티는 각 플랫폼 별로 적절한 디렉터리를 제공하기에 번거롭게 경로를 일일히 정해줄 필요가 없다. </p>
<ul>
<li><strong>Windows</strong>: <code>C:\Users\&lt;사용자이름&gt;\AppData\LocalLow\&lt;회사이름&gt;\&lt;프로젝트이름&gt;</code></li>
<li><strong>macOS</strong>: <code>~/Library/Application Support/&lt;회사이름&gt;/&lt;프로젝트이름&gt;</code></li>
<li><strong>Android</strong>: <code>/data/data/&lt;패키지이름&gt;/files</code></li>
<li><strong>iOS</strong>: <code>&lt;앱 샌드박스 디렉토리&gt;/Documents</code></li>
</ul>
<blockquote>
<p>만약 실제 경로가 맞는지 확인하려면 아래의 코드를 실행시켜보면 된다.</p>
</blockquote>
<pre><code class="language-csharp">Debug.Log(&quot;Save Path: &quot; + Application.persistentDataPath);</code></pre>
<p>그리고 사용자가 앱을 삭제하지 않는 한 데이터는 <strong>영구적</strong>으로 유지되기에 사용자의 데이터를 저장하기에 적합하다.</p>
<p>다만 <strong>파일 변조가 가능</strong>하기에, 중요하거나 개인적인 정보를 암호화하지 않은 상태로 <code>Application.persistentDataPath</code> 에 저장하는 것은 추천되지 않는다.</p>
<h3 id="🤔-datapath--streamingassetspath">🤔 dataPath? // streamingAssetsPath?</h3>
<p><code>Application</code> 클래스의 프로퍼티를 살펴보면, 경로에 대한 여러 프로퍼티를 찾을 수 있다.</p>
<p>이중에서 몇 가지만 살펴보면</p>
<hr>
<ul>
<li><code>Application.dataPath</code><ul>
<li>에디터 상에선 <strong>유니티 프로젝트의 Assets 폴더 경로</strong>를,
빌드된 앱에선 <strong>앱의 Data 폴더 경로</strong>를 반환.</li>
<li><strong>읽기 전용</strong></li>
<li>이 프로퍼티를 통해 게임 데이터를 저장하면 안됨.</li>
</ul>
</li>
</ul>
<p><code>Asset</code> 폴더 안의 내용은 빌드 시, 특정 형식으로 패키징 되기에 경로 기반 접근이 <strong>불가능</strong>하다. 
 다만 <code>Resources</code> 폴더에 접근할 때는 아래 예시처럼 경로 기반이 아닌 <code>Resources.Load</code> 를 사용해 접근할 수 있다.
그리고 밑에서 얘기할 <code>StreamingAssets</code> 폴더의 경우에만 예외적으로 경로 기반 접근이 가능하다. </p>
<pre><code>// Resources/myFile.txt 파일을 읽기
TextAsset textAsset = Resources.Load&lt;TextAsset&gt;(&quot;myFile&quot;); // 확장자 제외</code></pre><hr>
<ul>
<li><code>Application.streamingAssetsPath</code><ul>
<li><strong>StreamingAssets 폴더의 경로</strong> 반환</li>
<li>StreamingAssets 폴더는 해당 내용 그대로 패키징 되기에 빌드 후에도 경로 기반 접근이 가능</li>
<li><strong>읽기 전용</strong></li>
</ul>
</li>
</ul>
<hr>
<p>이 외에도 임시적인 캐시 파일을 저장하는 경로인, <code>Application.temporaryCachePath</code></p>
<p>콘솔 로그에 대한 경로를 반환하는,
<code>Application.consoleLogPath</code> 가 있다.</p>
<h2 id="3-json-직렬화serialization">3. JSON 직렬화<code>(Serialization)</code></h2>
<p>자 이제 게임 데이터를 JSON으로 변환해보자. UNITY는 이런 직렬화 과정을 위해 <code>JsonUtility</code>를 기본적으로 제공하며, 이 외의 다양한 기능이 필요할 경우 <code>Newtonsoft</code>의 JSON 라이브러리를 사용할 수도 있다.</p>
<p>우리는 유니티에서 기본적으로 제공하는 <code>JsonUtility</code>를 통해서 직렬화를 해보자.</p>
<p>먼저 게임 데이터를 저장해주는 클래스를 만들자.</p>
<pre><code class="language-csharp">[System.Serializable] // 직렬화를 하겠다는 어트리뷰트(Attribute)
public class PlayerData
{
    public string name;
    public int level;
    public string[] items;
}</code></pre>
<p>이제 SaveManager 스크립트를 만들어주고 다음과 같이 적어주자.</p>
<pre><code class="language-csharp">using System.IO;
using UnityEngine;

public class SaveManager : MonoBehaviour
{
    public void SaveData(PlayerData data)
    {
        // 경로 설정
        string path = Path.Combine(Application.persistentDataPath, &quot;playerData.json&quot;);

        // JSON으로 직렬화
        string jsonData = JsonUtility.ToJson(data, true); // true는 읽기 쉽게 포맷팅
        Debug.Log(&quot;JSON Data : \n&quot; + jsonData);

        // 파일로 저장
        File.WriteAllText(path, jsonData);

        Debug.Log(&quot;Data saved to &quot; + path);
    }
}</code></pre>
<ul>
<li><code>Path.Combine</code> : 두 개 이상의 문자열을 매개변수로 받고 올바른 디렉터리 경로를 반환.</li>
<li><code>JsonUtility.ToJson</code> : 매개변수로 직렬화할 객체를 받고, 만약 두 번째 매개변수에 <code>true</code> 넣는다면 읽기 쉽게 변환.</li>
<li><code>File.WriteAllText</code> : 해당 경로에 텍스트 파일을 저장.</li>
</ul>
<p>이제 아래의 예제를 실행시켜보고 방금 얘기한 디렉터리를 살펴보면 파일이 생성된걸 확인할 수 있다.</p>
<pre><code class="language-csharp">using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestJson : MonoBehaviour
{
    void Start()
    {
        PlayerData player = new PlayerData // 데이터 객체 생성
        {
            name = &quot;Player1&quot;,
            level = 5,
            items = new string[] { &quot;sword&quot;, &quot;shield&quot; }
        };

        SaveManager saveManager = new SaveManager(); // SaveManager 객체 생성
        saveManager.SaveData(player); // 세이브 시작
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/fa7d5617-626e-4a76-b3ed-2a590af93ff8/image.png" alt=""></p>
<p>JSON 파일로 직렬화 된 모습</p>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/cab836f4-279d-4ff8-9881-05111ba45862/image.png" alt=""></p>
<p>파일이 저장된 위치</p>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/85f48cc3-9837-49b0-8b28-ce64dd5abd03/image.png" alt="">
<img src="https://velog.velcdn.com/images/vel_cmb/post/1fe29248-de21-4f95-9164-a988059020d3/image.png" alt=""></p>
<p>저장된 파일과 내용</p>
<hr>
<h2 id="4-오류-케이스-처리하기">4. 오류 케이스 처리하기</h2>
<p>이걸로 저장 기능을 구현했다! 하지만 이 코드는 파일 저장 과정에서 생길 수 있는 오류를 처리하지 못한다. 마지막으로 예외 처리 로직까지 짜보자.</p>
<hr>
<ol>
<li>파일 경로 체크<ul>
<li>만약 저장할 경로가 존재하지 않을 때 문제가 발생할 수 있다.</li>
<li>다음의 코드를 통해, 파일의 경로가 존재하는지 체크하고 만약 존재하지 않는다면 디렉터리를 생성하게 할 수 있다.<pre><code class="language-csharp">if (!Directory.Exists(Application.persistentDataPath))
{
Directory.CreateDirectory(Application.persistentDataPath);
}</code></pre>
</li>
</ul>
</li>
</ol>
<ul>
<li><code>Directory.CreateDirectory</code> : 매개변수로 넣은 경로를 생성</li>
</ul>
<hr>
<ol start="2">
<li>예외 처리<ul>
<li><code>try-catch</code> 문으로 저장 과정 중에 예상치 못하게 생기는 오류를 확인할 수 있다.</li>
</ul>
</li>
</ol>
<pre><code class="language-csharp">try
{
    File.WriteAllText(path, jsonData);
    Debug.Log(&quot;Data saved successfully.&quot;);
}
catch (Exception e)
{
    Debug.LogError(&quot;Failed to save data: &quot; + e.Message);
}</code></pre>
<hr>
<p>이 예외 처리를 포함한 전체적인 저장 코드는 다음과 같다.</p>
<pre><code class="language-csharp">public class SaveManager : MonoBehaviour
{
    public void SaveData(PlayerData data)
    {
        // 저장 경로 설정
        string path = Path.Combine(Application.persistentDataPath, &quot;playerData.json&quot;);

        try
        {
            // JSON 직렬화
            string jsonData = JsonUtility.ToJson(data, true);
            Debug.Log(&quot;JSON Data : \n&quot; + jsonData);

            // 경로 확인 및 생성
            if (!Directory.Exists(Application.persistentDataPath))
            {
                Directory.CreateDirectory(Application.persistentDataPath);
            }

            // JSON 데이터 저장
            File.WriteAllText(path, jsonData);
            Debug.Log(&quot;Data saved to &quot; + path);
        }
        catch (Exception e)
        {
            Debug.LogError(&quot;Failed to save data: &quot; + e.Message);
        }
    }
}</code></pre>
<hr>
<p>이렇게 해서 저장 기능을 구현해봤다.
다음 글에선 이 저장한 파일을 어떻게 불러와야 하는지 알아보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[💾유니티 - JSON 파일로 게임 저장하기 1-1 (JSON 파일이란?)]]></title>
            <link>https://velog.io/@vel_cmb/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-1-1-JSON</link>
            <guid>https://velog.io/@vel_cmb/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-1-1-JSON</guid>
            <pubDate>Sat, 16 Nov 2024 14:08:38 GMT</pubDate>
            <description><![CDATA[<p>게임을 하다보면 언젠간 게임을 종료시켜야 한다. 이럴 때 저장기능이 없다면 유저 입장에서 많이 난처할 것이다.
유니티에서 보통 저장 기능을 구현할 땐, <code>PlayerPrefs</code>, <code>JSON 파일</code> 등을 사용한다. 
이번엔 JSON 파일로 게임 저장 기능을 어떻게 구현할 수 있을지 알아보자.</p>
<hr>
<h2 id="json이란">JSON이란?</h2>
<p>저장 기능을 구현하기 전에 먼저 JSON에 대해 알아보자.
JSON으로 저장한다는데 JSON이 뭔지 모르면 뭔가 이상하지 않은가.</p>
<p>JSON은 <code>JavaScript Object Notation</code>의 약자로 데이터 저장과 교환을 수월하게 하기 위해 만들어진 양식이다. 기존에 쓰이던 <code>XML</code>에 비해 구조가 단순하고 가벼워 자주 사용된다.</p>
<h3 id="1-json의-구조">1. JSON의 구조</h3>
<p>JSON은 객체<code>(Object)</code>로 이루어져 있을 수도 있고, 배열<code>(Array)</code>로 이루어져 있을 수도 있다. 설명하기에 앞서 간단히 비교해보면,</p>
<hr>
<p><strong>객체</strong> </p>
<ul>
<li>중괄호 <code>{}</code> 로 감싸며, 키<code>(key)</code> - 값<code>(value)</code> 쌍으로 데이터를 저장한다.</li>
<li>다양한 속성의 데이터를 저장할 때 유용하다. 
ex) 플레이어 레벨, 진행 상황, 설정 정보 등</li>
</ul>
<p>기본적인 구조</p>
<pre><code>{ key : value }</code></pre><p>예시</p>
<pre><code>{
  &quot;도시&quot;: &quot;서울&quot;,
  &quot;인구&quot;: 10000000,
  &quot;지역&quot;: [&quot;은평구&quot;, &quot;영등포구&quot;, &quot;마포구&quot;]
}</code></pre><hr>
<p><strong>배열</strong></p>
<ul>
<li>대괄호 <code>[]</code>로 감싸며, 값<code>(value)</code>들의 목록을 저장한다.</li>
<li>순서가 존재하거나 비슷한 종류의 데이터를 저장할 때 유용하다.
ex) 아이템 리스트, 배열에 기반한 데이터 등</li>
</ul>
<p>기본적인 구조</p>
<pre><code>[value1, value2, value3]</code></pre><p>예시</p>
<pre><code>[
  {&quot;상품&quot;: &quot;노트북&quot;, &quot;가격&quot;: 1500000},
  {&quot;상품&quot;: &quot;스마트폰&quot;, &quot;가격&quot;: 800000},
  {&quot;상품&quot;: &quot;태블릿&quot;, &quot;가격&quot;: 600000}
]</code></pre><hr>
<p>다음 예시로 좀 더 자세히 알아보자.</p>
<pre><code>{
  &quot;이름&quot;: &quot;홍길동&quot;,
  &quot;나이&quot;: 30,
  &quot;주소&quot;: {
    &quot;도시&quot;: &quot;서울&quot;,
    &quot;구&quot;: &quot;강남구&quot;
  },
  &quot;취미&quot;: [&quot;독서&quot;, &quot;등산&quot;, &quot;음악 감상&quot;]
}</code></pre><ul>
<li>위의 예시는 JSON 객체로 <code>이름</code>, <code>나이</code>, <code>주소</code>, <code>취미</code>의 키<code>(key)</code>와 이에 해당하는 값들이 포함되어 있다. 각 데이터는 쉼표<code>,</code>로 구분된다.</li>
<li>여기서 <code>주소</code>는 값으로 또 다른 객체를 가지며, 그 객체는 <code>도시</code>, <code>구</code>의 키와 이에 해당하는 값이 저장되어 있다.</li>
<li>그리고 <code>취미</code>의 값으로는 배열이 할당되어 있다.</li>
</ul>
<p><strong>주소 키에 할당된 값(객체)</strong></p>
<pre><code>{
    &quot;도시&quot;: &quot;서울&quot;,
    &quot;구&quot;: &quot;강남구&quot;
}</code></pre><p><strong>취미 키에 할당된 값(배열)</strong></p>
<pre><code>[&quot;독서&quot;, &quot;등산&quot;, &quot;음악 감상&quot;]</code></pre><hr>
<p>갑자기 뭔가 복잡해 보이는 코드의 구조를 뜯어보니 뭔가 헷갈릴 수도 있다. 
그래서 JSON에서 사용 가능한 데이터 타입에 대해 알아보면서 복습해보자.</p>
<hr>
<h3 id="2-json에서-사용-가능한-데이터-타입">2. JSON에서 사용 가능한 데이터 타입</h3>
<p>JSON 내에서 표현할 수 있는 데이터 타입은 다음 6개가 있다.</p>
<ul>
<li><strong>문자열 (String)</strong>: <code>&quot;텍스트&quot;</code></li>
<li><strong>숫자 (Number)</strong>: 정수<code>ex) -1, 0, 1, 등...</code>나 실수<code>ex) 3.141592, 등...</code></li>
<li><strong>객체 (Object)</strong>: <code>{ &quot;키&quot;: &quot;값&quot; }</code></li>
<li><strong>배열 (Array)</strong>: <code>[값1, 값2, ...]</code></li>
<li><strong>불리언 (Boolean)</strong>: <code>true</code> 또는 <code>false</code></li>
<li><strong>널 (Null)</strong>: <code>null</code></li>
</ul>
<p>이를 보고 다음 예시와 함께 다시 자세히 이해해보자.</p>
<pre><code>{
  &quot;name&quot;: &quot;홍길동&quot;,                
  &quot;age&quot;: 28,                      
  &quot;height&quot;: 175.5,                
  &quot;isMember&quot;: true,               
  &quot;address&quot;: {                    
    &quot;city&quot;: &quot;서울&quot;,
    &quot;district&quot;: &quot;강남구&quot;,
    &quot;zipcode&quot;: null               
  },
  &quot;hobbies&quot;: [&quot;독서&quot;, &quot;등산&quot;, &quot;영화 감상&quot;]
}</code></pre><ol>
<li><strong>문자열 (String)</strong><ul>
<li>예시 필드: <code>&quot;name&quot;: &quot;홍길동&quot;</code>, <code>&quot;city&quot;: &quot;서울&quot;</code>, <code>&quot;district&quot;: &quot;강남구&quot;</code></li>
<li>텍스트 데이터를 저장하는 데 사용된다. 문자열은 항상 큰따옴표(&quot; &quot;)로 감싸서 써야한다.</li>
</ul>
</li>
<li><strong>숫자 (Number)</strong><ul>
<li>예시 필드: <code>&quot;age&quot;: 28</code>, <code>&quot;height&quot;: 175.5</code></li>
<li>정수와 실수, 모두를 지칭한다. JSON에서는 별도의 정수 타입과 실수 타입으로 구분하지 않는다.</li>
</ul>
</li>
<li><strong>객체 (Object)</strong><ul>
<li>예시 필드: <code>&quot;address&quot;: { ... }</code></li>
<li>중괄호<code>{}</code>로 감싸여 있으며, 키-값 쌍으로 구성된다. 복잡한 데이터를 구조화하는 데 사용된다.</li>
</ul>
</li>
<li><strong>배열 (Array)</strong><ul>
<li>예시 필드: <code>&quot;hobbies&quot;: [&quot;독서&quot;, &quot;등산&quot;, &quot;영화 감상&quot;]</code></li>
<li>대괄호<code>[]</code>로 감싸여 있으며, 값들의 순서가 있는 목록으로 구성된다. 동일한 타입의 데이터나 다양한 타입의 데이터를 포함할 수 있다.</li>
</ul>
</li>
<li><strong>불리언 (Boolean)</strong><ul>
<li>예시 필드: <code>&quot;isMember&quot;: true</code></li>
<li>참(<code>true</code>) 또는 거짓(<code>false</code>) 값을 가진다.</li>
</ul>
</li>
<li><strong>널 (Null)</strong><ul>
<li>예시 필드: <code>&quot;zipcode&quot;: null</code></li>
<li>값이 없음을 나타낸다. 데이터가 없거나 값이 아직 할당되지 않은 경우에 사용한다.</li>
</ul>
</li>
</ol>
<hr>
<p>데이터 타입을 활용하면 아래와 같이 복잡한 구조도 표현 가능하다.</p>
<pre><code>{
  &quot;player&quot;: {
    &quot;name&quot;: &quot;김철수&quot;,
    &quot;stats&quot;: {
      &quot;level&quot;: 15,
      &quot;experience&quot;: 2450.75,
      &quot;isPremium&quot;: false
    },
    &quot;inventory&quot;: [
      {
        &quot;item&quot;: &quot;검&quot;,
        &quot;quantity&quot;: 1,
        &quot;durability&quot;: 80.5
      },
      {
        &quot;item&quot;: &quot;방패&quot;,
        &quot;quantity&quot;: 1,
        &quot;durability&quot;: 60.0
      },
      {
        &quot;item&quot;: &quot;포션&quot;,
        &quot;quantity&quot;: 10,
        &quot;durability&quot;: null
      }
    ],
    &quot;achievements&quot;: null
  }
}</code></pre><ul>
<li><code>player</code> : 객체 타입, 플레이어에 대한 모든 정보를 포함.</li>
<li><code>name</code> : 문자열 타입, 플레이어의 이름.</li>
<li><code>stats</code> : 객체 타입, 플레이어의 레벨, 경험치, 프리미엄 회원 여부에 대한 정보를 포함.<ul>
<li><code>level</code>: 숫자 타입, 플레이어의 레벨.</li>
<li><code>experience</code>: 숫자 타입, 플레이어의 경험치.</li>
</ul>
</li>
<li><code>isPremium</code>: 불리언 타입, 프리미엄 회원 여부.</li>
<li><code>inventory</code>: 배열 타입, 플레이어의 인벤토리 아이템 목록.<ul>
<li>각 아이템은 객체 타입으로, <code>item(문자열)</code>, <code>quantity(숫자)</code>, <code>durability(숫자 또는 널)</code> 필드를 가짐.</li>
</ul>
</li>
<li><code>achievements</code>: 널 타입, 현재 플레이어가 달성한 업적이 없음을 의미.</li>
</ul>
<hr>
<h3 id="3-json을-쓰면서-주의해야-할-것">3. JSON을 쓰면서 주의해야 할 것</h3>
<ul>
<li><p>키와 문자열 값은 작은따옴표<code>&#39;&#39;</code>가 아닌 큰따옴표<code>&quot;&quot;</code>로 감싸야 한다.</p>
<pre><code>(X)    { &#39;name&#39;: &#39;홍길동&#39; }
(O)       { &quot;name&quot;: &quot;홍길동&quot; }</code></pre></li>
<li><p>각 요소의 구분은 쉼표<code>,</code>로 구분하며, 마지막 요소 뒤엔 쉼표를 사용하지 않는다.</p>
<pre><code>(X)
{
&quot;name&quot;: &quot;홍길동&quot;,
&quot;age&quot;: 30,
}
</code></pre></li>
</ul>
<p>(O)
{
  &quot;name&quot;: &quot;홍길동&quot;,
  &quot;age&quot;: 30
}</p>
<pre><code>
- **JSON 표준은 주석을 지원하지 않는다.**</code></pre><p>(X)
{
  &quot;name&quot;: &quot;홍길동&quot; // 사용자 이름
}
또는
{
  &quot;name&quot;: &quot;홍길동&quot; # 사용자 이름
}</p>
<p>(O)
{
  &quot;name&quot;: &quot;홍길동&quot;
}</p>
<p>```</p>
<hr>
<p>이렇게 해서 JSON에 대해 알아보았다.
쓰다보니 분량이 길어져서 유니티에서 JSON을 활용해 저장 기능을 만들어 보는 것은 다음 글에서 알아보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📌동적 계획법(Dynamic Programming, DP)]]></title>
            <link>https://velog.io/@vel_cmb/%EB%8F%99%EC%A0%81-%EA%B3%84%ED%9A%8D%EB%B2%95Dynamic-Programming-DP</link>
            <guid>https://velog.io/@vel_cmb/%EB%8F%99%EC%A0%81-%EA%B3%84%ED%9A%8D%EB%B2%95Dynamic-Programming-DP</guid>
            <pubDate>Wed, 13 Nov 2024 16:52:02 GMT</pubDate>
            <description><![CDATA[<h2 id="동적-계획법dynamic-programming">동적 계획법(Dynamic Programming)</h2>
<p>동적 계획법(Dynamic Programming)은 </p>
<ul>
<li><strong>문제의 최적해가 하위 문제(sub-problem)의 최적해로 정의되며</strong>, </li>
<li><strong>하위 문제가 중첩(overlapping)될 때</strong> </li>
</ul>
<p>사용할 수 있는 알고리즘 기법이다.</p>
<p>예시로, 피보나치 수열을 재귀와 DP로 구하는 방식을 비교해보자.</p>
<hr>
<h3 id="1-재귀">1. 재귀</h3>
<pre><code> import time

 #재귀 함수
 def Fibonacci(n):
     if n == 1 or n == 2:
        return 1
    else:
        return Fibonacci(n-1) + Fibonacci(n-2)

 n = 40

 startTime = time.time()
 result = Fibonacci(n)
 endTime = time.time()
 timeRecursive = endTime - startTime

 print(f&quot;Fibonacci({n}) 계산 결과&quot;)
 print(f&quot;값 : {result} / 실행 시간 {timeRecursive}초&quot;)</code></pre><ul>
<li>실행결과<pre><code>Fibonacci(40) 계산 결과
값 : 102334155 / 실행 시간 29.934786319732666초</code></pre></li>
<li>시간 복잡도 : <code>O(2^n)</code></li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/b32757d5-d113-48aa-92aa-d54ff2fae3e9/image.jpg" alt=""></p>
<p>이 재귀함수가 실행되는 과정을 살펴보면, <strong>동일한 과정을 반복</strong>한다는 것을 알 수 있다. 이로 인해 <strong>실행과정이 오래 걸리게 된다.</strong></p>
<p>그런데 이 과정을 잘 살펴보니 </p>
<ul>
<li>같은 하위 문제<code>(sub-problem)</code>가 중첩되고
<code>ex) Fibo(n-k)가 반복됨</code></li>
<li>최종적인 해가 하위 문제의 해로 정의된다는 것을 알 수 있다.
<code>ex) Fibo(n) = Fibo(n-1) + Fibo(n-2)</code></li>
</ul>
<p>즉 이 문제는 DP로 해결할 수 있다.
이제 피보나치  수열을 DP로 해결해보자.</p>
<hr>
<h3 id="2-동적-계획법">2. 동적 계획법</h3>
<pre><code class="language-python">import time

# 동적 계획법
def Fibonacci_DP(n):
    if n == 1 or n == 2:
        return 1

    fibo = [0 for _ in range(n+1)]

    fibo[1], fibo[2] = 1, 1

    for i in range(3, n+1):
        fibo[i] = fibo[i-1] + fibo[i-2]

    return fibo[n]


n = 10000

startTime = time.time()
result = Fibonacci_DP(n)
endTime = time.time()
timeDP = endTime - startTime

print(f&quot;Fibonacci({n}) 계산 결과&quot;)
print(f&quot;값 : {result} / 실행 시간 {timeDP}초&quot;)</code></pre>
<ul>
<li><p>실행결과</p>
<pre><code>Fibonacci(10000) 계산 결과
값 : 33644764876...(생략) / 실행 시간 0.00937342643737793초</code></pre></li>
<li><p>시간 복잡도 : <code>O(n)</code></p>
</li>
</ul>
<p>재귀와 DP의 실행 시간을 비교해보면, DP방식이 재귀방식보다 매우 빠른 것을 알 수 있다.</p>
<hr>
<h2 id="탑다운top-down과-바텀업bottom-up">탑다운(Top-Down)과 바텀업(Bottom-Up)</h2>
<p>방금 전 문제의 경우, 바텀업<code>(Bottom-Up)</code> 접근 방식을 통해 문제를 풀었다.
DP는 주로 <strong>탑다운<code>(Top-Down)</code></strong>과 <strong>바텀업<code>(Bottom-Up)</code></strong> 접근방식을 통해서 문제를 해결한다.</p>
<hr>
<h3 id="1-탑다운">1. 탑다운</h3>
<p>탑다운은 위에서부터 아래로, 즉 <strong>큰 문제에서 시작해서 필요한 하위문제의 해를 구해가는 방식</strong>이다.</p>
<p>탑다운 방식의 경우, 재귀와 메모제이션<code>(Memoization)</code>을 활용한다.</p>
<blockquote>
<p>메모제이션<code>(Memoization)</code> : 문제를 해결하는 과정에서 이미 계산한 결과를 저장해서 중복 계산을 방지하는 기법 중 하나</p>
</blockquote>
<ul>
<li>탑다운적 접근 방식</li>
<li>재귀 함수를 통해 구현된다.</li>
<li>문제를 해결하며 필요한 부분 문제만 계산되어 저장된다.</li>
</ul>
<p>앞서 풀어본 피보나치 수열을 탑다운 방식으로 구현하면 다음과 같이 구현할 수 있다.</p>
<pre><code class="language-python"># 동적 계획법 (탑다운)
def Fibonacci_DP(n, memo = {}):
    if n in memo:
        return memo[n]

    if n &lt;= 1:
        return n

    memo[n] = Fibonacci_DP(n-1, memo) + Fibonacci_DP(n-2, memo)

    return memo[n]

print(Fibonacci_DP(20)) # 출력 6765</code></pre>
<p><code>Fibonacci_DP</code> 함수를 통해서 위에서부터 재귀적으로 수열을 계산하면서, 계산된 결과는 <strong><code>memo</code> 딕셔너리에 저장하며 중복 계산을 피하고 있다.</strong></p>
<hr>
<h3 id="2-바텀업">2. 바텀업</h3>
<p>바텀업은 아래에서부터 위로, 즉 <strong>작은 하위문제에서 시작해 더 큰 문제의 해를 구성하는 방식</strong>이다.</p>
<p>바텀업 방식의 경우, 반복문과 타뷸레이션<code>(Tabulation)</code>을 활용한다.</p>
<blockquote>
<p>타뷸레이션<code>(Tabulation)</code> : 문제를 해결하는 과정에서 이미 계산한 결과를 저장해서 중복 계산을 방지하는 기법 중 하나</p>
</blockquote>
<ul>
<li>바텀업적 접근 방식</li>
<li>반복문을 통해 구현된다.</li>
<li>모든 부분 문제를 계산해서 작은 것부터 순차적으로 저장한다.</li>
</ul>
<p>앞서 구현한 코드를 다시 살펴보자.</p>
<pre><code class="language-python"># 동적 계획법 (바텀업)
def Fibonacci_DP(n):
    if n == 1 or n == 2:
        return 1

    fibo = [0 for _ in range(n+1)]

    fibo[1], fibo[2] = 1, 1

    for i in range(3, n+1):
        fibo[i] = fibo[i-1] + fibo[i-2]

    return fibo[n]

print(Fibonacci_DP(20)) # 출력 6765</code></pre>
<p><code>Fibonacci_DP</code> 함수 내에서 <code>fibo</code>리스트의 인덱스가 1과 2일 때 값을 1로 초기화한다. <code>(기저 조건(Base Case))</code>
그리고 <strong>반복문을 통해 3에서 n까지 순차적으로 수열을 계산하고 <code>fibo</code>리스트에 저장한다.</strong></p>
<hr>
<h3 id="정리">정리</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>탑다운(Top-Down)</th>
<th>바텀업(Bottom-Up)</th>
</tr>
</thead>
<tbody><tr>
<td>구현 방식</td>
<td>재귀와 메모이제이션</td>
<td>반복문과 타뷸레이션(혹은 테이블)</td>
</tr>
<tr>
<td>메모리 사용</td>
<td>재귀 호출 스택 + 캐시</td>
<td>테이블에 모든 부분 문제 결과 저장</td>
</tr>
<tr>
<td>코드 복잡도</td>
<td>재귀 함수 사용으로 코드가 간결할 수 있음</td>
<td>반복문과 테이블 초기화 등으로 다소 복잡할 수 있음</td>
</tr>
<tr>
<td>계산 순서</td>
<td>필요한 부분 문제부터 재귀적으로 계산</td>
<td>작은 문제부터 순차적으로 계산</td>
</tr>
</tbody></table>
<blockquote>
<p>테이블<code>(table)</code> : 데이터를 구조화하여 저장하고, 효율적으로 검색하거나 참조할 수 있도록 만든 데이터 구조로,
여기선 문제를 해결하기 위해 부분 문제의 해답을 저장하는 배열 또는 리스트를 의미한다.</p>
</blockquote>
<blockquote>
<p>캐시<code>(cache)</code> : 데이터나 계산 결과를 임시로 저장해 두었다가 필요할 때 빠르게 꺼내 쓰는 저장소를 의미한다.</p>
</blockquote>
<hr>
<h2 id="분할-정복divide-and-conquer과-동적-계획법dynamic-programming">분할 정복(Divide and Conquer)과 동적 계획법(Dynamic Programming)</h2>
<p><strong>문제를 하위문제로 쪼개고, 그 하위문제의 부분해를 통해서 전체 문제의 해를 구한다</strong>는 측면에서 분할 정복<code>(Divide and Conquer)</code>과 동적 계획법<code>(Dynamic Programming)</code>은 동일하게 보이기도 한다.</p>
<p>하지만 <strong>접근 방식</strong>과 <strong>적용되는 문제의 특성</strong>에서 둘은 몇 가지 중요한 차이점이 있다.</p>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/55fde3e8-2b92-40b6-8dbd-0e0a0402a9a0/image.jpg" alt="">
<del>divede 오타났다.</del></p>
<ul>
<li><p>분할 정복<code>(Divide and Conquer)</code></p>
<ul>
<li>하위 문제가 서로 독립적이며, 중복되지 않음</li>
<li>문제를 여러 하위 문제로 분할한 후, 부분해를 결합해서 전체해를 구한다.<blockquote>
<p>예시 : 병합 정렬<code>(Merge Sort)</code>, 퀵 정렬<code>(Quick Sort)</code>, 이진 탐색<code>(Binary Search)</code></p>
</blockquote>
</li>
</ul>
</li>
<li><p>동적 계획법<code>(Dynamic Programming)</code></p>
<ul>
<li>하위 문제가 중첩되며 여러 번 계산됨</li>
<li>하위 문제의 부분해를 저장해놨다가 재사용하며 전체해를 구한다.<blockquote>
<p>예시 : 피보나치 수열, LCS(최장 공통 부분 수열), 막대 자르기 문제</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h4 id="여담">여담</h4>
<p>동적 계획법의 이름을 보면 <strong>동적<code>(dynamic)</code></strong>이라는 말이 붙어있는데, 앞서 살펴본 것처럼, 사실 이 기법은 동적과는 거리가 멀다.
그런데도 이름이 이런 이유는, 동적 계획법의 고안자가 dynamic이란 이름이 멋져보여서 붙였기에 이런 이름으로 정해지게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 - None 객체]]></title>
            <link>https://velog.io/@vel_cmb/%ED%8C%8C%EC%9D%B4%EC%8D%AC-None</link>
            <guid>https://velog.io/@vel_cmb/%ED%8C%8C%EC%9D%B4%EC%8D%AC-None</guid>
            <pubDate>Thu, 31 Oct 2024 11:37:54 GMT</pubDate>
            <description><![CDATA[<p>파이썬을 쓰면 볼 수 있는 <code>None</code>은** 값이 없음**을 나타내는 특별한 객체입니다.</p>
<p>보통 <code>None</code>이 아무것도 없음을 나타내는 것처럼,
<code>None</code>은 <strong>함수가 값을 반환하지 않을 때</strong>, 또는 <strong>변수에 초기값을 지정하지 않을 때</strong> 사용됩니다.</p>
<hr>
<h2 id="특징">특징</h2>
<h3 id="1-none은-싱글톤-객체입니다">1. <code>None</code>은 싱글톤 객체입니다.</h3>
<p>즉, 프로그램 내에서 하나의 <code>None</code>객체만 존재합니다. 이는 다음과 같은 코드로 확인해 볼 수 있습니다.</p>
<ul>
<li><strong>코드</strong><pre><code class="language-python">a = None
b = None
print(f&quot;a is b : {a is b}&quot;)
print()
#다른 큰 수여도 상관 없음
c = 200000000000000000000000 * 9999999999999999999999999999999999999999999999999
d = 200000000000000000000000 * 9999999999999999999999999999999999999999999999999
print(f&quot;c is d : {c is d}&quot;)
print(f&quot;c == d : {c == d}&quot;)
print()
e = [1]
f = [1]
print(f&quot;e is f : {e is f}&quot;)
print(f&quot;e == f : {e == f}&quot;)</code></pre>
</li>
<li><strong>결과</strong><pre><code>a is b : True
</code></pre></li>
</ul>
<p>c is d : False
c == d : True</p>
<p>e is f : False
e == f : True</p>
<pre><code>앞서 얘기한거처럼 `Python`은 `None`을 하나의 **싱글톤 객체**로만 생성합니다.

같은 객체를 참조할 때 `True`를 반환하는 `is`연산자를 통해서 모**든 `None` 참조가 하나의 객체**를 가리킨다는 것을 알 수 있습니다.


### 🤔 왜 두 번째 케이스에 큰 수를 쓰나요?
- 파이썬은 `자주 사용되거나 크기가 작은 객체`, `불변 객체(immutable objects)`(`None`, `True`, `False` 등)를 **미리 저장해놨다가 필요할 때 재사용하는 캐싱 기법**을 사용하고 있습니다. 

- 예를 들어, 값이 작은 정수 같은 값은 프로그램에서 여러 번 사용될 가능성이 높습니다. 그래서 이 값이 다시 사용될 때마다 메모리를 새로 할당하는 것이 아닌 기존에 저장한 값을 재사용하는 것입니다.

- 하지만 `너무 큰 값`이거나, `가변 객체(mutable objects)`(`리스트`, `딕셔너리`, `집합` 등) 경우에는 **캐싱**해놓지 않습니다. 따라서 만약 두 번째 경우에서 값을 작은 값으로 하면 결과가 **`True`**로 바뀌게 됩니다.

- 코드
```python
c = 1000
d = 1000
print(f&quot;c is d : {c is d}&quot;)
print(f&quot;c == d : {c == d}\n&quot;)</code></pre><ul>
<li>결과<pre><code>c is d : True
c == d : True</code></pre></li>
</ul>
<h3 id="2-none은-nonetype이라는-고유한-타입을-가지고-있습니다">2. <code>None</code>은 <code>NoneType</code>이라는 고유한 타입을 가지고 있습니다.</h3>
<ul>
<li>코드<pre><code class="language-python">print(type(None))</code></pre>
</li>
<li>결과<pre><code>&lt;class &#39;NoneType&#39;&gt;</code></pre></li>
</ul>
<h3 id="3-none은-불리언-판별을-할-때-false로-평가됩니다">3. <code>None</code>은 불리언 판별을 할 때, <code>False</code>로 평가됩니다.</h3>
<ul>
<li>코드<pre><code class="language-python">if not None:
  print(&quot;None은 False 입니다.&quot;)</code></pre>
</li>
<li>결과<pre><code>None은 False 입니다.</code></pre></li>
</ul>
<hr>
<h2 id="none의-사용-예제">None의 사용 예제</h2>
<ol>
<li>반환값이 없는 함수의 반환값<pre><code class="language-python">def my_function():
 pass
</code></pre>
</li>
</ol>
<p>result = my_function()
print(result)  # 출력: None</p>
<pre><code>
2. 매개변수의 기본값 설정
```python
def greet(name=None):
    if name is None:
        print(&quot;Hello, Stranger!&quot;)
    else:
        print(f&quot;Hello, {name}!&quot;)

greet()          # 출력: Hello, Stranger!
greet(&quot;Alice&quot;)   # 출력: Hello, Alice!</code></pre><ol start="3">
<li>변수 초기화<pre><code class="language-python">data = None
if data is None: # 만약 data에 값이 없다면 값을 초기화
 print(&quot;데이터가 없습니다.&quot;)
 data = &quot;Initial_Value&quot;</code></pre>
</li>
</ol>
<hr>
<h2 id="none과-is"><code>None</code>과 <code>is</code></h2>
<p><code>None</code>을 비교 목적으로 사용할 때는 <code>==</code> 또는 <code>!=</code> 대신 <strong><code>is</code> 또는 <code>is not</code></strong>을 사용하는 것이 권장됩니다.</p>
<pre><code class="language-python">if var is None:
    print(&quot;변수가 None입니다.&quot;)</code></pre>
<p>이는 <code>None</code>이 <strong>싱글톤 객체</strong>이기 때문으로, 한 프로그램 내에서 하나의 객체만 존재하는 싱글톤 객체의 특성상 <strong>값을 비교하는 <code>==</code></strong> 연산자 보단 <strong>객체의 동일성을 검사하는 <code>is</code></strong> 연산자를 쓰는게 권장됩니다.</p>
<hr>
<h2 id="파이썬의-none과-다른-언어의-null">파이썬의 <code>None</code>과 다른 언어의 <code>null</code></h2>
<p>다른 프로그래밍 언어에도 <code>None</code>과 비슷한 <code>null</code>이라는 개념이 있습니다. 이 둘은 값이 없음을 의미하기도 하고, 실제로 둘은 비슷한 기능을 담당하고 있습니다. 다만 세부적으로 들어가면서 차이가 생깁니다.</p>
<table>
<thead>
<tr>
<th>언어</th>
<th>키워드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>파이썬</strong></td>
<td><code>None</code></td>
<td>값이 없음을 나타내는 특별한 객체입니다. <code>NoneType</code>의 유일한 인스턴스로, <code>is</code> 연산자를 사용해 비교합니다.</td>
</tr>
<tr>
<td><strong>자바</strong></td>
<td><code>null</code></td>
<td>객체가 아무런 값을 참조하지 않을 때 사용됩니다. 모든 참조형 변수에 할당될 수 있으며, <code>==</code> 연산자로 비교합니다.</td>
</tr>
<tr>
<td><strong>C#</strong></td>
<td><code>null</code></td>
<td>참조형 변수와 nullable 값형에 값이 없음을 나타냅니다. <code>==</code> 연산자를 사용해 비교합니다.</td>
</tr>
<tr>
<td><strong>C++</strong></td>
<td><code>nullptr</code> (C++11 이상), <code>NULL</code>, <code>0</code></td>
<td>포인터가 아무런 객체도 가리키지 않음을 나타냅니다. <code>nullptr</code>은 타입이 명확한 최신 표준입니다. <code>NULL</code>은 전통적인 매크로이며, <code>0</code>은 정수형 상수로도 사용됩니다.</td>
</tr>
<tr>
<td><strong>C</strong></td>
<td><code>NULL</code></td>
<td>포인터가 아무런 객체도 가리키지 않음을 나타내는 매크로입니다. 보통 <code>((void*)0)</code>으로 정의됩니다.</td>
</tr>
</tbody></table>
<h3 id="주요-차이점">주요 차이점</h3>
<ol>
<li><p><strong>타입 시스템 및 표현 방식</strong>:</p>
<ul>
<li><strong>파이썬</strong>: <code>None</code>은 <code>NoneType</code>의 유일한 객체으로, 모든 상황에서 동일하게 사용됩니다.</li>
<li><strong>자바, C#</strong>: <code>null</code>은 참조형에만 사용되며, 값형에서는 <code>nullable</code> 타입을 통해 사용됩니다.</li>
<li><strong>C++</strong>: <code>nullptr</code>은 C++11부터 도입된 명확한 포인터 리터럴이며, 이전에는 <code>NULL</code>이나 <code>0</code>을 사용했습니다.</li>
<li><strong>C</strong>: <code>NULL</code>은 전통적으로 <code>((void*)0)</code>으로 정의되며, 포인터와 관련된 상황에서 사용됩니다.</li>
</ul>
</li>
<li><p><strong>비교 방법</strong>:</p>
<ul>
<li><strong>파이썬</strong>: <code>is</code> 연산자를 사용하여 <code>None</code>과 비교합니다.<pre><code class="language-python">if 변수 is None:
    print(&quot;변수는 아무 값도 가지고 있지 않습니다.&quot;)</code></pre>
</li>
<li><strong>자바, C#, C++</strong>: <code>==</code> 연산자를 사용하여 <code>null</code> 또는 <code>nullptr</code>과 비교합니다.<pre><code class="language-java">if (변수 == null) {
    System.out.println(&quot;변수는 null입니다.&quot;);
}</code></pre>
<pre><code class="language-cpp">if (포인터 == nullptr) {
    std::cout &lt;&lt; &quot;포인터는 nullptr입니다.&quot; &lt;&lt; std::endl;
}</code></pre>
</li>
<li><strong>C</strong>: <code>==</code> 연산자를 사용하여 <code>NULL</code>과 비교합니다.<pre><code class="language-c">if (포인터 == NULL) {
    printf(&quot;포인터는 NULL입니다.\n&quot;);
}</code></pre>
</li>
</ul>
</li>
<li><p><strong>기본값 처리</strong>:</p>
<ul>
<li><strong>C++ 및 C</strong>: 포인터 초기화 시 <code>NULL</code>이나 <code>nullptr</code>을 명시적으로 할당하지 않으면, 포인터는 쓰레기 값을 가질 수 있습니다.</li>
</ul>
</li>
<li><p><strong>객체 지향 특성</strong>:</p>
<ul>
<li><strong>파이썬</strong>: <code>None</code>은 객체이므로, 모든 객체와 마찬가지로 메서드를 호출하거나 속성을 가질 수 없지만, 타입 체킹이 명확하게 가능합니다.</li>
<li><strong>자바, C#</strong>: <code>null</code>은 객체 참조가 없음을 나타내며, <code>null</code> 참조에 접근하면 <code>NullPointerException</code>(자바)이나 <code>NullReferenceException</code>(C#)이 발생합니다.</li>
<li><strong>C++ 및 C</strong>: <code>nullptr</code>이나 <code>NULL</code>은 단순히 메모리 주소 <code>0</code>을 나타내며, 이를 역참조하면 정의되지 않은 동작이 발생할 수 있습니다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 - 입력 처리하기]]></title>
            <link>https://velog.io/@vel_cmb/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%85%EB%A0%A5-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@vel_cmb/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%85%EB%A0%A5-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 30 Oct 2024 03:34:57 GMT</pubDate>
            <description><![CDATA[<p>다른 언어 쓰다가 파이썬 쓰려니 헷갈려서 글로 따로 정리해놨다.</p>
<hr>
<h2 id="입력-처리하기">입력 처리하기</h2>
<p>파이썬에서는 보통 <strong><code>input()</code></strong> 함수를 이용해서 입력을 처리한다.
이 함수는 사용자가 입력한 값을 항상 <strong>문자열(<code>str</code>)</strong>로 반환한다.</p>
<blockquote>
<p>예시</p>
</blockquote>
<pre><code class="language-python">name = input(&quot;이름을 입력하세요: &quot;)
print(f&quot;안녕하세요, {name}님!&quot;)</code></pre>
<p>매개변수로 받은 문자열을 화면 상에 출력한 후, 사용자가 입력한 값을 <code>name</code> 변수에 저장한다. 그 다음 <code>print</code> 함수를 통해 이름을 출력한다.</p>
<hr>
<h2 id="숫자로-입력받기">숫자로 입력받기</h2>
<p><code>input</code> 함수는 항상 문자열을 반환한다. 만약 숫자를 입력받아서 활용하고자 한다면 형 변환이 요구된다.</p>
<blockquote>
<p>예시1 (정수 입력)</p>
</blockquote>
<pre><code class="language-python">age = int(input(&quot;나이를 입력하세요: &quot;))
print(f&quot;제 나이는 {age}살 입니다.&quot;)</code></pre>
<p><code>int()</code> 함수를 통해 입력값을 정수로 변환시킬 수 있다.</p>
<blockquote>
<p>예시2 (실수 입력)</p>
</blockquote>
<pre><code class="language-python">height = float(input(&quot;키를 입력하세요 (cm): &quot;))
print(f&quot;당신의 키는 {height}cm입니다.&quot;)</code></pre>
<p>비슷하게 <code>float()</code> 함수를 통해 입력값을 실수로 변환시킬 수 있다.</p>
<hr>
<h2 id="여러-개의-값-입력받기">여러 개의 값 입력받기</h2>
<p>만약 사용자가 여러 개의 값을 입력한다면 어떻게 해야할까?
그럴 땐 <code>split()</code> 함수를 활용하면 된다.</p>
<blockquote>
<p>예시1 (여러 개의 문자열 입력받기)</p>
</blockquote>
<pre><code class="language-python">nameList = input(&quot;참여하는 사람들을 입력하세요 (공백으로 구분): &quot;).split()
print(f&quot;참여하는 사람: {nameList}&quot;)</code></pre>
<p>결과</p>
<pre><code>참여하는 사람들을 입력하세요 (공백으로 구분): 신희 난형 시우
참여하는 사람: [&#39;신희&#39;, &#39;난형&#39;, &#39;시우&#39;]</code></pre><p><code>split()</code> 함수는 입력된 문자열을 공백을 기준으로 나눠서 문자열 리스트를 반환한다.</p>
<p>그럼 여러 개의 데이터를 입력받은 다음 형 변환을 해야할 때는 어떻게 해야할까?</p>
<p>이럴 때를 위해 <code>map()</code> 함수가 있다.</p>
<p><code>map()</code> 함수는 <code>map(함수, 반복 가능한 객체)</code>  이런 구조를 가지고 있으며,
각 요소마다 함수를 적용하고 그 값이 포함된 <code>map</code>객체를 반환한다.</p>
<p><code>map</code>객체에 데이터가 저장되지만 사용자가 이 값을 직접 볼 수 없다. 
따라서 출력할 땐<code>list()</code> 등을 써서 출력해줘야 한다.</p>
<blockquote>
<p>예시2 (여러 개의 정수 입력받기)</p>
</blockquote>
<pre><code class="language-python">numList = map(int ,input(&quot;값들을 입력하세요 (공백으로 구분): &quot;).split())
print(f&quot;입력한 값: {list(numList)}&quot;)  # 데이터를 읽기 위해선 list()로 감싸줘야 함.</code></pre>
<p>결과</p>
<pre><code>값들을 입력하세요 (공백으로 구분): 1 5 3
입력한 값: [1, 5, 3]</code></pre><blockquote>
<p>예시3 (두 개의 정수 입력 받기)</p>
</blockquote>
<pre><code class="language-python">a, b = map(int, input(&quot;두 수를 입력하세요 (공백으로 구분): &quot;).split())
print(f&quot;두 수의 합: {a + b}&quot;)</code></pre>
<p>결과</p>
<pre><code>두 수를 입력하세요 (공백으로 구분): 3 5
두 수의 합: 8</code></pre><hr>
<h2 id="보다-빠르게-입력받기">보다 빠르게 입력받기</h2>
<p>대부분의 상황에선 편리적인 <code>input()</code> 함수를 사용해도 큰 문제는 없으나,
코딩 테스트처럼 처리 시간이 중요한 상황에서 <code>input()</code> 함수는 문제가 생길 수도 있다.
(예시 : 백준 BOJ 15552번)</p>
<p>이럴 때는 <code>sys 모듈</code>을 <code>import</code>한 후에 <code>sys.stdin.readline()</code> 함수를 사용하는 것으로 해결할 수 있다.</p>
<p><code>input()</code>함수 내부적으로 <code>sys.stdin.readline()</code>을 호출하기에 둘은 기능적으로 유사하지만 <code>input()</code>은 <code>sys.stdin.readline()</code>와 다르게</p>
<ol>
<li>개행 문자(<code>\n</code>)를 자동으로 제거한다.</li>
<li>프롬프트 문자열을 출력한다. (예시: <code>input(&quot;값을 입력해주세요&quot;)</code>)</li>
<li>예외 처리 같은 부가적인 로직이 포함한다.</li>
</ol>
<p>이로 인해 <code>input()</code>함수가 <code>sys.stdin.readline()</code>보다 느리다.
그렇기에 코딩 테스트 같이 많은 양의 데이터를 반복적으로 처리해야하는 케이스에선 <code>input()</code>보단 <code>sys.stdin.readline()</code>을 사용하는 것이 좋다.</p>
<hr>
<h3 id="sysstdinreadline"><code>sys.stdin.readline()</code></h3>
<p><code>sys.stdin.readline()</code>는 반환값에 개행 문자를 제거하지 않는다. 
따라서 입력값을 활용할 땐 이를 고려해야 한다.</p>
<blockquote>
<p>예시</p>
</blockquote>
<pre><code class="language-python">import sys
name = sys.stdin.readline().strip()     # 개행 문자 제거
print(f&quot;제 이름은 {name}입니다.&quot;)</code></pre>
<p>결과</p>
<pre><code>시우
제 이름은 시우입니다.</code></pre><blockquote>
<p>예시2</p>
</blockquote>
<pre><code class="language-python">import sys    
phoneList = sys.stdin.readline().split()
print(phoneList)</code></pre>
<p>결과</p>
<pre><code>S99 IPHONE99 G5
[&#39;S99&#39;, &#39;IPHONE99&#39;, &#39;G5&#39;]         #split()을 통해 개행 문자도 같이 제거</code></pre><hr>
<h2 id="2차원-배열-입력받기">2차원 배열 입력받기</h2>
<p>이제 코딩 테스트에서 자주 볼 수 있는 2차원 배열 입력 데이터를 파이썬으로 어떻게 처리해야 할지 알아보자</p>
<blockquote>
<p>입력값</p>
</blockquote>
<pre><code>3 5
6 8 2 6 2
3 2 3 4 6
6 7 3 3 2</code></pre><blockquote>
<p>예시1</p>
</blockquote>
<pre><code class="language-python">import sys
rows, cols = map(int, sys.stdin.readline().split())
matrix = []
for r in range(rows):
    matrix.append(list(map(int, sys.stdin.readline().split())))
print(matrix)</code></pre>
<p>결과</p>
<pre><code>[[6, 8, 2, 6, 2], [3, 2, 3, 4, 6], [6, 7, 3, 3, 2]]</code></pre><p>리스트 컴프리헨션을 사용하면 반복문을 대체할 수 있다.</p>
<blockquote>
<p>예시2</p>
</blockquote>
<pre><code class="language-python">import sys
n, m = map(int, sys.stdin.readline().split())
matrix = [list(map(int, sys.stdin.readline().split())) for _ in range(n)]
print(matrix)</code></pre>
<p>결과</p>
<pre><code>[[6, 8, 2, 6, 2], [3, 2, 3, 4, 6], [6, 7, 3, 3, 2]]</code></pre><p>만약 입력 케이스가 공백이나 쉼표<code>,</code>로 구분되어 있지 않다면 어떻게 받아야할까?
그럴 땐 split() 메서드 대신 strip() 메서드를 사용해주면 된다.</p>
<blockquote>
<p>입력</p>
</blockquote>
<pre><code>4 5
00100
01110
11111
10011</code></pre><blockquote>
<p>예시</p>
</blockquote>
<pre><code class="language-python">import sys
rows, cols = map(int, sys.stdin.readline().split())
matrix = []
for r in range(rows):
    matrix.append(list(map(int, sys.stdin.readline().strip())))
print(matrix)</code></pre>
<p>결과</p>
<pre><code>[[0, 0, 1, 0, 0], [0, 1, 1, 1, 0], [1, 1, 1, 1, 1], [1, 0, 0, 1, 1]]</code></pre><p>이 경우에도 동일하게 리스트 컴프리헨션을 사용 가능하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[5. 확률적 분석과 랜덤화 알고리즘 5. Probabilistic Analysis and Randomized
Algorithms]]></title>
            <link>https://velog.io/@vel_cmb/5.-%ED%99%95%EB%A5%A0%EC%A0%81-%EB%B6%84%EC%84%9D%EA%B3%BC-%EB%9E%9C%EB%8D%A4%ED%99%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-5.-Probabilistic-Analysis-and-RandomizedAlgorithms</link>
            <guid>https://velog.io/@vel_cmb/5.-%ED%99%95%EB%A5%A0%EC%A0%81-%EB%B6%84%EC%84%9D%EA%B3%BC-%EB%9E%9C%EB%8D%A4%ED%99%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-5.-Probabilistic-Analysis-and-RandomizedAlgorithms</guid>
            <pubDate>Tue, 08 Oct 2024 16:54:02 GMT</pubDate>
            <description><![CDATA[<h2 id="챕터-5의-목표">챕터 5의 목표</h2>
<blockquote>
<p>확률적 분석과 랜덤화 알고리즘의 차이에 대해 설명하기
인디케이터 랜덤 변수 테크닉 소개하기
랜덤화 알고리즘 분석의 다른 예시 소개하기 (추가 배열 없이 배열 섞기)</p>
</blockquote>
<h2 id="채용-문제-the-hiring-problem">채용 문제 (The Hiring Problem)</h2>
<blockquote>
<p>지배인은 에이전시를 통해서 직원을 고용하고자 합니다.
에이전시는 매일 한 명의 후보자를 보냅니다.
지배인은 각 후보자를 인터뷰하고 그 자리에서 고용 여부를 결정해야 합니다.
만약 새로운 사람이 고용되면, 현재 고용된 사람은 해고됩니다.
인터뷰 비용은 후보자 당 c_i / 고용 비용은 후보자 당 c_h 입니다.
고용 비용 c_h는 인터뷰 비용 c_i보다 크다고 가정합니다.</p>
</blockquote>
<p>이 문제의 목표는 항상 현재까지 면접 본 후보자 중 가장 뛰어난 사람을 고용하는 할 때 발생하는 비용을 계산하는 것 입니다.</p>
<h2 id="발생-비용-분석">발생 비용 분석</h2>
<p>만약 n명의 후보자 중에서 m명을 고용한다 가정하면, 총 비용은 <img src="https://velog.velcdn.com/images/vel_cmb/post/0503f7f4-d9f7-4b45-8784-9c5a1d0ce155/image.png" alt="">
입니다.
(c_i = 인터뷰 비용, c_h = 고용 비용)</p>
<p>최악의 케이스(WorstCase)에서 지배인은 n명을 모두 한번씩 고용하게 됩니다. 
(1명 인터뷰 하고  기존의 직원 해고 후 1명 고용)
이 때의 비용은 
<img src="https://velog.velcdn.com/images/vel_cmb/post/2e0f190e-afcb-4293-9e45-ad7deb9b83ed/image.png" alt="">
입니다.</p>
<h2 id="확률적-분석">확률적 분석</h2>
<p>보통, 후보자들이 나타나는 순서는 제어할 수 없기에 우리는 후보자들이 무작위 순서로 등장한다고 가정합니다.
각 후보자마다 순위(rank)를 부여합니다. rank(i) 는 1부터 n까지 범위에 대한 고유한 정수입니다.
이 순위들로 이루어진 리스트 <img src="https://velog.velcdn.com/images/vel_cmb/post/a276d9ca-77f2-4f8c-bbe2-f3011e33be07/image.png" alt="">
는 후보자들 <img src="https://velog.velcdn.com/images/vel_cmb/post/794db9b7-1cf8-45ea-a0c4-70899e070643/image.png" alt="">
 에 대한 순열로 볼 수 있습니다. 이 순위 리스트는 n!개가 존재할 수 있으며, 모든 순열은 동일한 확률입니다, 즉 이 순열은 균일한 무작위 순열(uniform random permutation)입니다. </p>
<h2 id="인디케이터-랜덤-변수">인디케이터 랜덤 변수</h2>
<p>인디케이터 랜덤 변수는 특정 사건(A)이 발생했는지의 여부를 나타내는 변수입니다.</p>
<p>인티케이터 랜덤 변수는 다음과 같이 정의됩니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/4487b2fa-2779-467a-a21f-6c67f97c40aa/image.png" alt="">
즉, 사건 A가 발생하면 1을, 발생하지 않으면 0을 반환하는 이진 변수입니다.</p>
<p>인티케이터 랜덤 변수는 확률을 기댓값으로 변환하는데 유용합니다.
어떤 사건이 발생할 확률을 구해야 될 때 인디케이터 변수를 사용해 사건의 기댓값을 계산할 수 있습니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/4d25bf7d-bd0f-4424-b472-88e7273d6568/image.png" alt=""></p>
<h3 id="lemma">Lemma</h3>
<p>주어진 사건 A에 대해, 인디케이터 랜덤 변수 I{A}의 기대값은 다음과 같습니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/5e9677ca-7a2a-4141-b28d-cecc8f01760e/image.png" alt="">
즉, 인디케이터 랜덤 변수의 기대값은 사건 A가 발생할 확률과 같습니다.
이를 증명하기 위한 과정은 다음과 같습니다:</p>
<p>사건 A의 보수(complement)인 /A 를 정의합니다.
기대값의 정의를 적용하면:
<img src="https://velog.velcdn.com/images/vel_cmb/post/cca0bad5-64f3-4eff-8e28-012043266338/image.png" alt="">
즉, 인디케이터 랜덤 변수의 기대값은 사건 A가 발생할 확률과 정확히 일치하게 됩니다.</p>
<h3 id="example">Example</h3>
<p>문제 - n번의 동전 던지기에서 앞면이 나오는 횟수의 기대값을 계산하기</p>
<p>먼저, X를 n번의 동전 던지기에서 앞면이 나온 횟수를 나타내는 확률 변수로 정의합니다.
그럼 X의 대한 기댓값은 다음과 같이 계산할 수 있습니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/236ff82a-1a26-4407-8f18-ffe2f936a2b3/image.png" alt="">
이 식은 이는 각 k(앞면의 개수)별로 해당 값이 나올 확률을 곱한 뒤 더하는 것을 의미합니다.</p>
<p>이 식은 인디케이터 랜덤 변수를 활용해 더 간단하게 만들 수 있습니다.
X_i를 정의하여, i번째 동전 던지기가 앞면이 나오는 사건을 나타내는 인디케이터 랜덤 변수로 설정합니다.<img src="https://velog.velcdn.com/images/vel_cmb/post/186b46a5-d780-41f6-85f3-2b1e4423c451/image.png" alt="">
즉, i-번째 동전 던지기에서 앞면이 나오면 X_i=1, 나오지 않으면 X_i=0입니다.</p>
<p>동전 던지기에서 앞면이 나오는 총 횟수 X는 각 동전 던지기에서 앞면이 나왔는지 여부를 나타내는 인디케이터 랜덤 변수들의 합으로 표현될 수 있습니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/92313650-4a20-4ee8-b75d-4cecc0c67775/image.png" alt="">
즉, 모든 n번의 동전 던지기에 대해 앞면이 나온 횟수는 각 인디케이터 변수 X_i들의 합입니다.</p>
<p>각 X_i의 기대값은 앞면이 나올 확률 Pr{H}와 같습니다. 즉, 균등한 동전을 가정할 때 <img src="https://velog.velcdn.com/images/vel_cmb/post/76b0b942-f735-4870-bbff-a4a16c397480/image.png" alt=""></p>
<p>그리고 앞면이 나오는 총 횟수 X의 기대값은 모든 X_i의 기대값의 합으로 계산됩니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/cb8a3430-fce8-4f6a-8d5e-aff454eab79c/image.png" alt="">
그러므로 n번의 동전 던지기에서 앞면이 나오는 기대값은 n/2로 계산됩니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/c2abb0ec-117d-424e-95e9-32b72df186b6/image.png" alt=""></p>
<p>참고로 <img src="https://velog.velcdn.com/images/vel_cmb/post/a9e2a332-4eb4-4c77-97c0-b445dfc6ffc9/image.png" alt="">
이게 가능한 이유는 기댓값의 선형성(Linearity of Expectation) 때문입니다.</p>
<h3 id="기댓값의-선형성">기댓값의 선형성</h3>
<blockquote>
<p>기대값의 선형성은 기대값을 구할 때, 랜덤 변수들의 합의 기대값이 랜덤 변수들의 기대값의 합과 같다는 원리입니다. 중요한 점은 각 랜덤 변수들 간의 독립 여부와 상관없이 이 법칙이 적용된다는 점입니다.</p>
</blockquote>
<p>즉, 기댓값을 구할 때 
<img src="https://velog.velcdn.com/images/vel_cmb/post/395a136c-2c12-4b94-bac6-0d2337af79c6/image.png" alt="">
이렇게 바꿔서 구할 수 있습니다.</p>
<h2 id="채용-문제-분석">채용 문제 분석</h2>
<p>이제 X_i를 다음과 같이 후보자가 고용되는 사건에 대한 인디케이터 랜덤 변수도 정의합니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/508718f3-0a55-4b9a-856a-eb812adfdf14/image.png" alt=""></p>
<p>이제 다음의 특성을 활용해 채용 문제를 분석할 수 있습니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/e81d4d5f-618d-4451-bcfd-0913327b5be2/image.png" alt=""></p>
<p>Pr{candidate i is hired} 즉, i번째 후보자가 고용될 확률은 i번째 후보자가 가장 뛰어날 확률로 볼 수 있습니다. 후보자들이 누가 가장 뛰어난 사람인지는 동일한 확률이기에 이는 1/i입니다. 이제 Lemma를 통해서 인디케이터 랜덤 변수 X_i의 기댓값이 1/i임을 알 수 있습니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/ad058e98-8dc6-41e8-92af-9d613b4fa6e0/image.png" alt=""></p>
<p>이제 고용 비용의 기대값, E{X}를 <img src="https://velog.velcdn.com/images/vel_cmb/post/55c94255-37f1-4f7f-9b41-5ad88d226dfa/image.png" alt="">
다음과 같이 계산할 수 있습니다.
(조화급수의 합은 ln(n)으로 근사 가능)
(O(1)은 상수 항으로 n에 비례하지 않는 작은 값임)</p>
<p>따라서 기대 고용 비용은 다음과 같이 계산됩니다.
<img src="https://velog.velcdn.com/images/vel_cmb/post/2b4c3d54-fd07-4631-8c02-cf9feabc928e/image.png" alt=""></p>
<p>이 O(c_h * ln(n)) 은 최악의 케이스에서 봤던 O(c_h * n)보다 낮은데, 이를 통해 무작위 순서로 후보자를 면접할 때 기대되는 비용을 낮출 수 있다는 것을 알 수 있습니다.</p>
<h2 id="랜덤화-알고리즘이란">랜덤화 알고리즘이란?</h2>
<p>랜덤화 알고리즘은 동작이 난수 생성기(random-number generator)에 의해 결정되는 알고리즘입니다. 즉, 실행될 때마다 다른 결과를 내놓을 수 있습니다.</p>
<p>보통 RANDOM(a,b)으로 표시하며 RANDOM(a,b)는 a부터 b까지의 범위에서 동일한 확률로 난수를 선택해 반환합니다.</p>
<p>현실에선 완전히 무작위한 난수를 생성할 수 없습니다. 따라서 결정론적 방법으로 난수가 생성되지만 통계적으로 무작위처럼 보이는 의사 난수 생성기(pseudorandom-number generator)를 사용합니다.</p>
<p>랜덤화 알고리즘은 알고리즘이 실행될 때마다 다른 경로를 따를 수 있기에, 입력이 최적화되지 않았거나 예측 불가능한 경우에 <strong>최악의 성능을 유발하지 않도록</strong> 도와줍니다.</p>
<h3 id="채용-문제에서-결정적-알고리즘과-랜덤화-알고리즘의-차이">채용 문제에서 결정적 알고리즘과 랜덤화 알고리즘의 차이</h3>
<ul>
<li><p><strong>결정적 알고리즘</strong>은 입력된 순서에 의존하여 고용 횟수가 결정되며, 이는 주어진 입력에서 항상 같은 결과를 산출합니다.</p>
</li>
<li><p><strong>랜덤화 알고리즘</strong>은 입력 분포를 가정하지 않고, 입력에 무작위성을 부여함으로써, 후보자의 순서가 알고리즘의 성능에 미치는 영향을 줄일 수 있습니다.</p>
</li>
</ul>
<h2 id="배열을-무작위로-섞기">배열을 무작위로 섞기</h2>
<h4 id="목표--균등한-무작위-순열uniform-random-permutation을-생성하기-단-배열의-모든-n개의-가능한-순열-중-하나가-동일한-확률로-선택되야-함">목표 : <strong>균등한 무작위 순열(uniform random permutation)</strong>을 생성하기. 단, 배열의 모든 n!개의 가능한 순열 중 하나가 동일한 확률로 선택되야 함.</h4>
<p>(여기선 각 요소 A[i]가 특정 위치로 이동할 확률이 1/n 임을 증명하는 것이 목표가 아님) 
(대신에 전체 순열이 균등하게 분포되는지에 초점을 맞춤)</p>
<h4 id="의사코드-psedocode">의사코드 (psedocode)</h4>
<p><img src="https://velog.velcdn.com/images/vel_cmb/post/77650e10-ee84-4808-a043-16c1d1241d40/image.png" alt=""></p>
<p>각 반복에서, 현재 처리 중인 A[i] 요소를 배열의 남은 부분에서 무작위로 선택된 요소와 교환합니다. 교환 후 A[i]는 다시 바뀌지 않으며, 그 자리에 고정됩니다.</p>
<h4 id="시간-복잡도time-complexity">시간 복잡도(Time Complexity)</h4>
<p>각 반복(iteration)은 <strong>상수 시간(O(1))</strong>에 수행됩니다.
n번의 반복이 있으므로, 전체 알고리즘의 시간 복잡도는 O(n)입니다.</p>
<h3 id="lemma-1">Lemma</h3>
<blockquote>
<p><strong>무작위 배열 순열(Randomly Permuting an Array)</strong>은 균등한 무작위 순열을 생성한다.</p>
</blockquote>
<h4 id="proof">Proof</h4>
<ol>
<li><p>루프 불변식 : i-번째 반복 직전까지의 부분 배열 A[1:i−1]에는 (i−1)-순열이 포함되어 있으며, 이 순열은 (n−i+1)!/n!의 확률로 발생합니다.</p>
</li>
<li><p>i=1 일 때, 0-순열(즉, 빈 순열)이 부분 배열 A[1:0]에 포함됩니다.
n!/n!=1이므로, 이 빈 배열은 100% 확률로 0-순열을 포함합니다.
즉, 배열이 비어 있고, 이 빈 배열은 확률 1로 0-순열을 포함하게 됩니다.</p>
</li>
<li><p>i-번째 반복 직전까지, 부분 배열 A[1:i−1]에 (i−1)-순열이 포함되어 있다고 가정합니다. 이 부분 배열은 (n−i+1)!/n!의 확률로 해당 순열을 포함합니다.
알고리즘의 i-번째 반복에서, 배열 A[i]와 무작위로 선택된 요소가 교환됩니다. 이로 인해 배열의 무작위성이 유지됩니다.</p>
</li>
<li><p>알고리즘이 n번 반복을 완료하고 종료될 때, 종료 시점에서는 배열 A[1:n]이 완전한 n-순열을 포함하며, 이 순열은 1/n!의 확률로 발생합니다.
따라서 모든 가능한 n-순열이 동일한 확률로 발생한다는 것을 보장합니다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[팩토리 패턴(Factory Pattern)]]></title>
            <link>https://velog.io/@vel_cmb/%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%8C%A8%ED%84%B4Factory-Pattern</link>
            <guid>https://velog.io/@vel_cmb/%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%8C%A8%ED%84%B4Factory-Pattern</guid>
            <pubDate>Mon, 23 Sep 2024 16:52:36 GMT</pubDate>
            <description><![CDATA[<h2 id="팩토리-패턴이란">팩토리 패턴이란?</h2>
<p>팩토리 패턴은 객체 생성 매커니즘을 별도의 클래스나 메서드로 분리하여, 객체생성을 담당하는 코드와 클라이언트 코드(객체를 사용하는 코드)를 분리하는 디자인 패턴입니다.
이 패턴을 통해 객체 생성 과정을 캡슐화 할 수 있으며 클라이언트 코드가 객체의 생성 방식에 의존하지 않게 되어 코드의 확장성과 유지보수성이 향상됩니다.</p>
<h2 id="팩토리-패턴의-사용-이유">팩토리 패턴의 사용 이유</h2>
<h4 id="1-객체-생성의-캡슐화">1. 객체 생성의 캡슐화</h4>
<p>객체 생성 매커니즘이 별도 클래스나 메서드로 분리되기에 코드 구조가 깔끔해집니다.</p>
<h4 id="2-확장성">2. 확장성</h4>
<p>새로운 객체가 추가할 때 클라이언트 코드를 수정하지 않고, 객체 생성을 담당하는 팩토리 클래스나 메서드를 확장하거나 수정하기만 하면 됩니다.</p>
<h4 id="3-의존성-감소">3. 의존성 감소</h4>
<p>클라이언트 코드가 구체적인 클래스에 의존하는 것이 아닌 추상 클래스나 인터페이스에 의존하기에 코드를 수정할 때 보다 용이해집니다.</p>
<h2 id="팩토리-패턴의-종류">팩토리 패턴의 종류</h2>
<p>팩토리 패턴은 주로 <code>심플 팩토리 패턴</code>, <code>팩토리 메서드 패턴</code>, <code>추상 팩토리 패턴</code>이 있습니다.</p>
<h3 id="심플-팩토리-패턴">심플 팩토리 패턴</h3>
<p>심플 팩토리 패턴은 <strong>객체 생성을 담당하는 클래스나 메서드를 따로 두는 것</strong>을 의미합니다.</p>
<p>예시 코드 :</p>
<pre><code class="language-csharp">// Animal 인터페이스 정의
public interface Animal
{
    void Speak();
}

// Cat 클래스 정의
public class Cat : Animal
{
    public void Speak()
    {
        Console.WriteLine(&quot;Meow&quot;);
    }
}

// Dog 클래스 정의
public class Dog : Animal
{
    public void Speak()
    {
        Console.WriteLine(&quot;Woof&quot;);
    }
}

// AnimalFactory 클래스 정의 (팩토리 패턴)
public class AnimalFactory
{
    // 동물을 생성하는 메서드
    public static Animal CreateAnimal(string animalType)
    {
        if (animalType == &quot;Cat&quot;)
        {
            return new Cat();
        }
        else if (animalType == &quot;Dog&quot;)
        {
            return new Dog();
        }
        else
        {
            throw new ArgumentException(&quot;Unknown animal type&quot;);
        }
    }
}

// 사용 예시 (클라이언트 코드)
class Program
{
    static void Main(string[] args)
    {
        // 팩토리 패턴을 이용한 객체 생성
        Animal cat = AnimalFactory.CreateAnimal(&quot;Cat&quot;);
        Animal dog = AnimalFactory.CreateAnimal(&quot;Dog&quot;);

        cat.Speak(); // 출력: Meow
        dog.Speak(); // 출력: Woof
    }
}</code></pre>
<p>위 코드를 살펴보면 <code>AnimalFactory</code> 클래스에서 <code>if-else 문</code>을 통해 클라이언트에서 사용할 동물 객체를 <code>CreateAnimal()</code> 메서드를 활용해 객체를 생성하고 있습니다.
이처럼 심플 팩토리는 생성 로직을 팩토리 클래스에 모아두는 것을 의미합니다.</p>
<p>다만 심플 팩토리 패턴은 만약 다른 클래스가 추가되었을 때 기존 코드를 수정해야합니다. 이는 확장에는 열려있으나 변경에는 닫혀있어야 하는 <code>OCP 원칙(Open Close Principle)</code>에 위배된다는 문제가 있습니다.</p>
<p>예를 들어, 또 다른 객체로 <code>Horse</code>나 <code>Monkey</code>가 추가 된다면 <code>AnimalFactory</code> 클래스를 수정해야만 합니다.</p>
<p>이는 프로젝트가 작을 때는 문제가 되지 않으나 프로젝트의 규모가 커지면서 문제가 발생할 수도 있습니다.</p>
<h3 id="팩토리-메서드-패턴">팩토리 메서드 패턴</h3>
<p>팩토리 메서드 패턴은 <strong>객체 생성 방식을 하위 클래스에서 결정하는 패턴</strong>입니다. 즉 객체를 생성하는 매커니즘은 부모 클래스에서 정의하되, 실제로 생성되는 객체의 종류는 자식 클래스에서 결정됩니다.</p>
<ul>
<li>부모 클래스 (또는 인터페이스) : 객체 생성 메서드를 정의</li>
<li>자식 클래스 : 실제 객체를 생성하는 로직을 구현 </li>
</ul>
<p>예시 코드 :</p>
<pre><code class="language-csharp">코드 복사
// Product 인터페이스 정의
public interface Product
{
    void Use();
}

// ConcreteProductA 클래스 정의
public class ConcreteProductA : Product
{
    public void Use()
    {
        Console.WriteLine(&quot;Using Product A&quot;);
    }
}

// ConcreteProductB 클래스 정의
public class ConcreteProductB : Product
{
    public void Use()
    {
        Console.WriteLine(&quot;Using Product B&quot;);
    }
}

// Creator 추상 클래스 (팩토리 메서드 패턴)
public abstract class Creator
{
    // 팩토리 메서드
    public abstract Product CreateProduct();

    // 생성된 제품을 사용하는 메서드
    public void UseProduct()
    {
        Product product = CreateProduct();
        product.Use();
    }
}

// ConcreteCreatorA 클래스 정의
public class ConcreteCreatorA : Creator
{
    public override Product CreateProduct()
    {
        return new ConcreteProductA();
    }
}

// ConcreteCreatorB 클래스 정의
public class ConcreteCreatorB : Creator
{
    public override Product CreateProduct()
    {
        return new ConcreteProductB();
    }
}

// 사용 예시
class Program
{
    static void Main(string[] args)
    {
        Creator creatorA = new ConcreteCreatorA();
        Creator creatorB = new ConcreteCreatorB();

        creatorA.UseProduct();  // 출력: Using Product A
        creatorB.UseProduct();  // 출력: Using Product B
    }
}</code></pre>
<p>위 코드를 살펴보면,
<code>Use()</code> 메서드를 정의하는 <code>Product</code> 인터페이스를 상속받는 <code>ConcreteProductA</code>와 <code>ConcreteProductB</code> 클래스에서 <code>Product</code> 인터페이스가 구현되어 있습니다.
<code>Creator</code> 추상 클래스는 <code>CreateProduct()</code>라는 팩토리 메서드를 추상 메서드로 정의하고, 하위 클래스 <code>CreateCreatorA</code> 그리고 <code>CreateCreatorB</code>에서 추상 메서드를 오버라이드 해줍니다.
그리고 실제로 <code>Product</code>객체를 생성할 때는 <code>CreateCreatorA</code> 또는 <code>CreateCreatorB</code> 클래스를 사용합니다.</p>
<p>이렇게 패턴을 작성함으로서, <strong>다른 <code>Product</code>객체가 추가된다 해도 <code>Creator</code> 추상 클래스의 수정없이 새로운 <code>Product</code>객체에 해당하는 <code>Creator</code> 클래스를 새로 작성하는 것으로 확장이 가능해집니다.</strong>
(ex: ProductC 추가 -&gt; CreateCreatorC 클래스 작성)</p>
<h3 id="추상-팩토리-패턴">추상 팩토리 패턴</h3>
<p>추상 팩토리 패턴은 <strong>연관된 객체들의 집합을 생성하는 인터페이스를 제공하는 패턴</strong>입니다. 팩토리 메서드 패턴에서 단일 객체를 생성하는 것과 달리, 여러 객체을 생성합니다.
여러 객체를 생성하는 메서드를 인터페이스를 통해 제공하고, 이를 상속받는 팩토리 클래스에서 그 메서드를 구현합니다.</p>
<ul>
<li>추상 팩토리 (인터페이스) : 서로 관련된 객체들을 생성하는 메서드를 정의</li>
<li>실질 팩토리 : 추상 팩토리를 구현해서 각 제품군의 객체들을 생성</li>
<li>추상 객체 (인터페이스) : 여러 객체들의 공통 인터페이스를 정의</li>
<li>객체 : 추상 객체에서 정의한 인터페이스를 구현한 클래스로, 팩토리에서 생성</li>
</ul>
<p>예시 코드 :</p>
<pre><code class="language-csharp">// Abstract ProductA
public interface AbstractProductA
{
    void UseA();
}

// Abstract ProductB
public interface AbstractProductB
{
    void UseB();
}

// Concrete ProductA1
public class ConcreteProductA1 : AbstractProductA
{
    public void UseA()
    {
        Console.WriteLine(&quot;Using Product A1&quot;);
    }
}

// Concrete ProductA2
public class ConcreteProductA2 : AbstractProductA
{
    public void UseA()
    {
        Console.WriteLine(&quot;Using Product A2&quot;);
    }
}

// Concrete ProductB1
public class ConcreteProductB1 : AbstractProductB
{
    public void UseB()
    {
        Console.WriteLine(&quot;Using Product B1&quot;);
    }
}

// Concrete ProductB2
public class ConcreteProductB2 : AbstractProductB
{
    public void UseB()
    {
        Console.WriteLine(&quot;Using Product B2&quot;);
    }
}

// Abstract Factory (객체 집합 생성 인터페이스)
public interface AbstractFactory
{
    AbstractProductA CreateProductA();
    AbstractProductB CreateProductB();
}

// Concrete Factory 1
public class ConcreteFactory1 : AbstractFactory
{
    public AbstractProductA CreateProductA()
    {
        return new ConcreteProductA1();
    }

    public AbstractProductB CreateProductB()
    {
        return new ConcreteProductB1();
    }
}

// Concrete Factory 2
public class ConcreteFactory2 : AbstractFactory
{
    public AbstractProductA CreateProductA()
    {
        return new ConcreteProductA2();
    }

    public AbstractProductB CreateProductB()
    {
        return new ConcreteProductB2();
    }
}

// 사용 예시
class Program
{
    static void Main(string[] args)
    {
        AbstractFactory factory1 = new ConcreteFactory1();
        AbstractFactory factory2 = new ConcreteFactory2();

        AbstractProductA productA1 = factory1.CreateProductA();
        AbstractProductB productB1 = factory1.CreateProductB();

        AbstractProductA productA2 = factory2.CreateProductA();
        AbstractProductB productB2 = factory2.CreateProductB();

        productA1.UseA(); // 출력: Using Product A1
        productB1.UseB(); // 출력: Using Product B1

        productA2.UseA(); // 출력: Using Product A2
        productB2.UseB(); // 출력: Using Product B2
    }
}</code></pre>
<p>위 코드를 살펴보면,
관련있는 객체를 묶어주는 <code>AbstractProductA</code>와 <code>AbstractProductB</code> 인터페이스가 있으며, <code>ConcreteProductA1</code>, <code>ConcreteProductA2</code> 클래스는 <code>AbstractProductA</code> 인터페이스를, <code>ConcreteProductB1</code>, <code>ConcreteProductB2</code> 클래스는 <code>AbstractProductB</code> 인터페이스를 상속받아서 인터페이스를 구현하고 있습니다.
그리고 <code>AbstractFactory</code> 인터페이스는 연관있는 객체들의 집합을 생성하는 메서드를 정의하고 있으며, 이 인터페이스를 상속받는 <code>ConcreteFactory1</code>과 <code>ConcreteFactory2</code> 클래스에서 각각 메서드를 정의하고 서로 다른 객체 집합을 생성합니다.</p>
<p>이렇게 패턴을 작성함으로서, <strong>객체 집합을 생성할 때, 한 팩토리에서 담당하게 설계할 수 있으며, 팩토리 메서드 패턴과 동일하게 OCP 원칙을 지키면서 코드를 작성할 수 있습니다.</strong></p>
<p>ex: </p>
<ul>
<li>몬스터를 소환한다 가정할 때, <code>OrcFactory</code> 팩토리 클래스에서 <code>Orc</code>, <code>BattleAxe</code>, <code>Leather Armor</code> 객체를 같이 생성. (각각 <code>AbstractFactory</code>, <code>Monster</code>, <code>Weapon</code>, <code>Armor</code> 인터페이스를 상속받음)</li>
<li>나중에 몬스터로 <code>Ghost</code>가 추가되어도, 새롭게 <code>GhostFactory</code> 클래스를 작성하고, 그 안에서 메서드를 오버라이드해서 <code>Ghost</code>, <code>GhostHand</code>, <code>WhiteCloth</code> 객체들을 생성</li>
</ul>
<p><br><br><br></p>
<p>마지막으로 제가 사용했었던 심플 팩토리 코드입니다. 그리 효율적이진 못하니 참고만 해주세요.</p>
<pre><code class="language-csharp">public class CharacterStatsInitFactory
{
    public static ArisuStats InitArisuStats()
    {
        return new ArisuStats
        {
            skillDamage = 50,
            skillSpeed = 30,
            manaCost = 50,
            skillCoolDown = 2f,
            playerSpeed = 5,
            playerDamage = 1,
            maxHealth = 10,
            maxMana = 100,
            manaRegenPerSecond = 20f
        };
    }

    public static MidoriStats InitMidoriStats()
    {
        return new MidoriStats
        {
            skillDamage = 10,
            skillSpeed = 30,
            manaCost = 30,
            skillCoolDown = 3f,
            detectionAngle = 120f,
            playerSpeed = 5,
            playerDamage = 1,
            maxHealth = 10,
            maxMana = 100,
            manaRegenPerSecond = 20f
        };
    }

    public static MomoiStats InitMomoiStats()
    {
        return new MomoiStats
        {
            skillDamage = 5,
            skillSpeed = 30,
            manaCost = 20,
            skillCoolDown = 3f,
            bulletCount = 10,
            spreadAngle = 90f,
            playerSpeed = 5,
            playerDamage = 1,
            maxHealth = 10,
            maxMana = 100,
            manaRegenPerSecond = 20f
        };
    }

    public static UZStats InitUZStats()
    {
        return new UZStats
        {
            skillDamage = 10,
            skillSpeed = 15,
            skillDuration = 4f,
            manaCost = 80,
            skillCoolDown = 10f,
            playerSpeed = 5,
            playerDamage = 1,
            maxHealth = 10,
            maxMana = 100,
            manaRegenPerSecond = 20f
        };
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴(Design Pattern)]]></title>
            <link>https://velog.io/@vel_cmb/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4Design-Pattern</link>
            <guid>https://velog.io/@vel_cmb/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4Design-Pattern</guid>
            <pubDate>Mon, 23 Sep 2024 10:50:40 GMT</pubDate>
            <description><![CDATA[<h2 id="디자인-패턴이란">디자인 패턴이란?</h2>
<p>객체 지향 프로그래밍 개발을 하면서 자주 발생하는 문제들을 해결하기 위해 사용되는 검증된 해결책의 모음입니다.
각 문제에 대한 정형화된 해결 방식을 제시하는 디자인 패턴을 사용하면 코드를 보다 유연하고 견고하게 작성할 수 있습니다.</p>
<h2 id="디자인-패턴의-목적">디자인 패턴의 목적</h2>
<h4 id="-개발자-간-의사소통의-편의성">-개발자 간 의사소통의 편의성</h4>
<blockquote>
<p>예를 들어, 서로 디자인 패턴을 알고 있는 상태에서 
&#39;여기선 객체를 만들어 반환하는 함수를 통해 초기화 과정을 외부로부터 숨겨놓고 반환타입을 제어하는게 좋겠네요.&#39; 보단 
&#39;여기선 팩토리 패턴을 사용하는게 좋겠네요.&#39; 가 서로 이해하는데 더 편리할 것이다.</p>
</blockquote>
<h4 id="-코드-간-유연성-향상">-코드 간 유연성 향상</h4>
<blockquote>
<p>디자인 패턴은 객체 간의 관계를 유연하게 관리할 수 있는 구조를 제공해줍니다. 따라서 객체 지향 프로그래밍 구조를 구현하는데 유용합니다.</p>
</blockquote>
<h2 id="디자인-패턴의-유형">디자인 패턴의 유형</h2>
<p>디자인 패턴은 크게 생성 패턴, 구조 패턴, 행위 패턴으로 구분됩니다.</p>
<h4 id="-생성-패턴">-생성 패턴</h4>
<p>객체 생성과 관련된 패턴으로, 객체 생성 과정을 추상화하거나 캡슐화시켜주는 매커니즘을 제공합니다.</p>
<h4 id="-구조-패턴">-구조 패턴</h4>
<p>객체나 클래스를 조합하여 더 큰 구조를 만들어 내는 패턴으로, 객체의 확장성을 높여줍니다.</p>
<h4 id="-행동-패턴">-행동 패턴</h4>
<p>객체나 클래스 간의 상호작용을 도와주는 패턴으로, 객체들 간 소통 방법이나 역할 할당을 도와줍니다.</p>
<h2 id="디자인-패턴의-주의점">디자인 패턴의 주의점</h2>
<p>디자인 패턴이 필요가 없는 상황에도 디자인 패턴을 적용해 코드가 더 복잡해질 수도 있다.
오히려 디자인 패턴을 사용하려고 하다가 빙 돌아가는 상황이 생길 수도 있으니 필요한 상황에 적재적소에 쓰는 것이 좋을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[오브젝트 풀링(Object Pooling)]]></title>
            <link>https://velog.io/@vel_cmb/%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%ED%92%80%EB%A7%81Object-Pooling</link>
            <guid>https://velog.io/@vel_cmb/%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%ED%92%80%EB%A7%81Object-Pooling</guid>
            <pubDate>Thu, 19 Sep 2024 10:09:48 GMT</pubDate>
            <description><![CDATA[<p>오브젝트 풀링은 게임 개발에서 성능 최적화를 위해 사용하는 중요한 기법 중 하나다. 
유니티에서 오브젝트를 생성하고 삭제할 때 흔히 <code>Instantiate</code>와 <code>Destroy</code> 메서드를 사용한다. 
그러나, 오브젝트가 많아질수록 이는 비효율적이다. 이때 오브젝트 풀링을 사용하면 더 효율적인 메모리 관리를 할 수 있다. 예를 들어, 총알이나 적 오브젝트와 같은 자주 생성되는 오브젝트에 유용하다.</p>
<h3 id="오브젝트-풀링의-동작-방식"><strong>오브젝트 풀링의 동작 방식</strong></h3>
<p><strong>1. 오브젝트 풀 생성(Create)</strong>
게임을 시작하면서 사용될 오브젝트들의 인스턴스를 필요한 만큼 미리 생성하고 풀에 저장한다.</p>
<p><strong>2. 오브젝트 요청(Call)</strong>
게임 중 오브젝트가 필요할 때마다 Instantiate를 사용하는 것이 아닌, 풀에서 오브젝트를 가져온다.</p>
<p><strong>3. 오브젝트 반환(Return)</strong>
다 사용한 오브젝트는 Destroy를 호출해 삭제시키는 것이 아니라, 풀로 반환하여 다음 요청 때 재사용할 준비를 한다.</p>
<p>그러면 이런 오브젝트 풀링은 왜 사용하는 걸까?</p>
<h3 id="오브젝트-풀링의-사용-이유"><strong>오브젝트 풀링의 사용 이유</strong></h3>
<p>*<em>1. 성능 최적화  *</em>
오브젝트를 매번 새로 생성하고 삭제할 때마다 메모리 할당과 해제 비용이 발생한다. 오브젝트 수가 많아질수록 이 비용이 커져서 게임 성능에 영향을 미칠 수 있다. 오브젝트 풀링을 사용하면 오브젝트를 미리 생성해 두고 재사용하기 때문에 이러한 성능 저하를 막을 수 있다.</p>
<p><strong>2. 가비지 컬렉터(GC) 호출 감소</strong><br><code>Destroy</code> 메서드를 호출하면 사용하지 않는 메모리를 해제하는 과정에서 가비지 컬렉터가 작동하게 된다. 이 가비지 컬렉션이 빈번하게 발생하면 게임이 일시적으로 멈추거나 렉이 걸릴 수 있다. 오브젝트 풀링을 사용하면 <code>Destroy</code> 호출을 줄여 가비지 컬렉션의 빈도를 낮출 수 있다.</p>
<p>아래는 내가 사용했던 오브젝트 풀링 코드이다.</p>
<pre><code class="language-csharp">using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class PoolManager : MonoBehaviour
{
    public static PoolManager Inst { get; private set; }

    // 프리펩을 보관할 변수
    public GameObject[] prefabs;

    // 풀 담당을 하는 리스트들
    List&lt;GameObject&gt;[] pools;

    private void Awake()
    {
        if (Inst == null)
        {
            Inst = this;
            DontDestroyOnLoad(gameObject); // 씬 전환 시에도 유지
        }
        else if (Inst != this)
        {
            Destroy(gameObject); // 기존 인스턴스가 있다면 새로운 것을 파괴
        }

        // pools의 길이를 프리펩의 길이 만큼 선언
        pools = new List&lt;GameObject&gt;[prefabs.Length];


        // 각 인덱스마다 프리펩을 담을 리스트 선언
        for (int index = 0; index &lt; pools.Length; index++)
            pools[index] = new List&lt;GameObject&gt;();
    }

    public GameObject GetObject(PoolObjectType poolObjectType)
    {
        GameObject select = null;
        int index = (int)poolObjectType;

        // 해당 인덱스의 게임 오브젝트 리스트 중 비활성화된 게임 오브젝트 선택 후 select에 할당
        foreach (GameObject prefab in pools[index]) 
        {
            if(!prefab.activeSelf)
            {
                select = prefab;
                select.SetActive(true);
                break;
            }
        }

        // 만약 다 사용 중이면 새롭게 생성 후 select에 할당
        if(!select)
        {
            select = Instantiate(prefabs[index], transform);
            pools[index].Add(select);
        }

        // 선택된 게임 오브젝트 반환
        return select;
    }

    public void ReturnObject(GameObject obj)
    {
        // 반환할 오브젝트를 비활성화하여 풀에 다시 넣음
        obj.SetActive(false);
    }
}
</code></pre>
<h3 id="코드-분석"><strong>코드 분석</strong></h3>
<p>코드를 하나씩 살펴보면,</p>
<pre><code class="language-csharp">    private void Awake()
    {
        //... 싱글톤 패턴 ...

        // pools의 길이를 프리펩의 길이 만큼 선언
        pools = new List&lt;GameObject&gt;[prefabs.Length];


        // 각 인덱스마다 프리펩을 담을 리스트 선언
        for (int index = 0; index &lt; pools.Length; index++)
            pools[index] = new List&lt;GameObject&gt;();
    }</code></pre>
<p><code>Awake</code> 메서드에서는 <code>pools</code>(GameObject List)를 <code>prefabs</code>(GameObject Array)의 길이만큼 선언해 준다. 
예를 들어 <code>prefabs</code>에 <code>BulletPrefab</code>, <code>EnemyPrefab</code>, <code>ItemPrefab</code>이 있다면, <code>pools</code>는 3개의 크기로 선언된다. 그리고 각 <code>index</code>마다 프리팹을 담을 리스트를 선언해 준다.</p>
<p>방금 예시로 든 <code>BulletPrefab</code>, <code>EnemyPrefab</code>, <code>ItemPrefab</code>에 대한 리스트가 각각 생성되며, 이는 일종의 2차원 리스트 구조라고 볼 수 있다.</p>
<pre><code class="language-csharp">    public GameObject GetObject(PoolObjectType poolObjectType)
    {
        GameObject select = null;
        int index = (int)poolObjectType;

        // 해당 인덱스의 게임 오브젝트 리스트 중 비활성화된 게임 오브젝트 선택 후 select에 할당
        foreach (GameObject prefab in pools[index]) 
        {
            if(!prefab.activeSelf)
            {
                select = prefab;
                select.SetActive(true);
                break;
            }
        }

        // 만약 다 사용 중이면 새롭게 생성 후 select에 할당
        if(!select)
        {
            select = Instantiate(prefabs[index], transform);
            pools[index].Add(select);
        }

        // 선택된 게임 오브젝트 반환
        return select;
    }</code></pre>
<p>그 다음에 <code>GetObject</code> 메서드가 <code>poolObjectType</code>을 매개변수로 받으면서 호출된다. 여기서 <code>poolObjectType</code>은 <code>enum</code>으로 인덱스 역할을 하며 다음과 같이 선언되어 있다.</p>
<pre><code class="language-csharp">public enum PoolObjectType
{
    Bullet,
    Enemy,
    Item
}</code></pre>
<p>그리고 <code>GameObject</code> 타입 변수, <code>select</code>를 선언해주고 매개변수로 받은 <code>poolObjectType</code>으로 <code>pools</code>에서 원하는 리스트가 있는 인덱스를 참조한다. 
그리고 그 리스트에서 활성화되지 않은 오브젝트가 있다면, <code>select</code>는 그 오브젝트를 참조하고, 오브젝트를 활성화한다.</p>
<p>만약 리스트를 다 돌았는데도 활성화되지 않은 오브젝트가 없다면 (즉 모든 오브젝트가 사용 중이거나 아예 없다면)
<code>Instantiate</code>를 호출하고 생성된 오브젝트를 <code>pools</code>의 리스트에 추가한다.</p>
<p>예를 들어, 만약 매개변수로 <code>Bullet</code>이 들어오면 <code>pools[0]</code>에 위치한 리스트를 순회하고 만약 사용 중이지 않은 오브젝트가 있다면 걔를 반환하고, 없다면 생성 후 <code>pools[0]</code>에 위치한 리스트에 추가한다.</p>
<p>그리고 <code>select</code>(선택한 오브젝트)를 반환한다.</p>
<pre><code class="language-csharp">    public void ReturnObject(GameObject obj)
    {
        // 반환할 오브젝트를 비활성화하여 풀에 다시 넣음
        obj.SetActive(false);
    }</code></pre>
<p><code>ReturnObject</code> 메서드에선 반환할 오브젝트를 매개변수로 받고
<code>obj.SetActive(false);</code>로 사용이 다 끝난 오브젝트를 비활성화 시켜준다.</p>
<p><code>int</code>형 <code>index</code> 대신 <code>enum</code>을 사용했다.
그리고 오브젝트를 반환하기 위한 목적으로<code>ReturnObject</code> 메서드를 정의했는데 내가 만든 게임에선 <code>obj.SetActive(false);</code>로 사용해도 별 문제가 없었다.</p>
<p>하지만 아래처럼 추가적인 로직이 필요할 경우, <code>ReturnObject</code> 메서드를 사용하는 것이 더 나을 것이다.</p>
<pre><code class="language-csharp">    public void ReturnObject(GameObject obj)
    {
        // 반환할 오브젝트를 비활성화하여 풀에 다시 넣음
        obj.SetActive(false);
        // 오브젝트의 계층 관리
        bullet.transform.SetParent(Instance.transform);

        // 이 외의 다른 로직들...
    }</code></pre>
<p>위의 <code>PoolManager</code> 클래스는 싱글톤 패턴도 적용되어 있는데 이는 다른 포스트에서 다뤄보도록 하겠다.</p>
<p>참고자료:
<a href="https://www.youtube.com/watch?v=A7mfPH8jyBE&amp;t=1155s">골드메탈 유튜브 강의</a>
<a href="https://glikmakesworld.tistory.com/m/13">티스토리 포스트</a>
<a href="https://wergia.tistory.com/203">베르의 프로그래밍 노트</a></p>
]]></description>
        </item>
    </channel>
</rss>