<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>채피지의 통나무</title>
        <link>https://velog.io/</link>
        <description>학부생</description>
        <lastBuildDate>Mon, 15 Sep 2025 09:27:06 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. 채피지의 통나무. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/chae-jpg" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[BOJ 12789 - 도키도키 간식드리미]]></title>
            <link>https://velog.io/@chae-jpg/BOJ-12789-%EB%8F%84%ED%82%A4%EB%8F%84%ED%82%A4-%EA%B0%84%EC%8B%9D%EB%93%9C%EB%A6%AC%EB%AF%B8</link>
            <guid>https://velog.io/@chae-jpg/BOJ-12789-%EB%8F%84%ED%82%A4%EB%8F%84%ED%82%A4-%EA%B0%84%EC%8B%9D%EB%93%9C%EB%A6%AC%EB%AF%B8</guid>
            <pubDate>Mon, 15 Sep 2025 09:27:06 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-cpp">// 도키도키 간식드리미 - 실버 3

#include &lt;bits/stdc++.h&gt;

using namespace std;

string solve(int n, vector&lt;int&gt;&amp; v) {

    stack&lt;int&gt; small, large;
    small.push(0);
    large.push(n+1);
    for (int i = 0; i &lt; n; i++) {
        int j = v[i];

        if (j == small.top() + 1) {
            small.push(j);

            while (!large.empty() &amp;&amp; large.top() == small.top() + 1) {
                small.push(large.top());
                large.pop();
            }

        } else {
            if (large.top() &lt; j) return &quot;Sad&quot;;
            large.push(j);
        }
    }
    return &quot;Nice&quot;;
}

int main() {
    int n;
    vector&lt;int&gt; v;
    cin &gt;&gt; n;
    v.assign(n, 0);

    for (int i = 0; i &lt; n; i++) {
        cin &gt;&gt; v[i];
    }

    cout &lt;&lt; solve(n, v);
}</code></pre>
<p>급해서 간단히 쓰고 감; </p>
<p>아이디어 자체는 간단함. 1, 2, 3 ... 처럼 오름차순으로 보내야 하는 곳이 있고, 그 곳에 값들을 보내기 위해 다른 공간에 값들을 차곡차곡 쌓으면 되는 것임.</p>
<p>근데 생각할 점은</p>
<p>1) 그 값을 쌓는 공간에는 LIFO가 적용됨 -&gt; 스택 사용
2) 다른 공간에 값을 차곡차곡 쌓긴 할 건데, 스택이니까 쌓인 순서대로만 나가야 됨. 근데? 보내야 하는 곳은 오름차순을 요구함! -&gt; 스택에도 <strong>큰 수부터 차례대로</strong> 쌓여야 함. 이게 포인트</p>
<p>나는 근데 이거 보고 스택이다 스택!! 하고 신나서 당장 스택 2개를 질러버림,,, 사실 스택 하나여도 풀 수 있죠? 나가는 공간은 어떤 값들이 어떻게 나갔는지를 보관해야 할 필요는 없기 때문에 그냥 인덱스로만 관리해도 무방함ㅎㅎ</p>
<p>아무튼 간단하고 재미있는 스택 문제였다... 지문이 좀 이상해서 질문 게시판을 참조해야 되기는 했지만 </p>
<blockquote>
<p>어떤 개념을 쓰는 문제라고 해서 그 개념 쓸 생각에만 매몰되지 말자!</p>
</blockquote>
<p>라는 좋은 경험을 얻었다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 11 - 핸즈온 머신러닝 2장 - part 5]]></title>
            <link>https://velog.io/@chae-jpg/Day-11-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-part-5</link>
            <guid>https://velog.io/@chae-jpg/Day-11-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-part-5</guid>
            <pubDate>Tue, 22 Jul 2025 12:54:51 GMT</pubDate>
            <description><![CDATA[<h3 id="데이터-정제">데이터 정제</h3>
<p>먼저, <code>total_bedrooms</code> 특성에 값이 없는 경우가 있는데, 이를 수정해보자. 방법에는 다음과 같이 세 가지가 있다.</p>
<blockquote>
<ol>
<li>해당 구역을 제거하기</li>
<li>전체 특성을 삭제하기</li>
<li><strong>대체</strong> : 누락된 값을 어떤 값으로 채우기</li>
</ol>
</blockquote>
<p>판다스의 <code>dropana()</code>, <code>drop()</code>, <code>fillna()</code> 메서드로 이런 작업을 간단하게 처리할 수 있다.</p>
<pre><code class="language-python"># housing.dropna(subset=[&quot;total_bedrooms&quot;], inplace=True)  

# housing.drop(&quot;total_bedrooms&quot;, axis=1, inplace=True)  

median = housing[&quot;total_bedrooms&quot;].median()  
housing[&quot;total_bedrooms&quot;].fillna(median, inplace=True)</code></pre>
<p>여기까지는 저번 파트에서 다루었던 내용이다.</p>
<hr>
<p>세 번째 옵션이 누락된 값을 중간값으로 채우는 코드이다.
위와 같은 동작을 사이킷런의 <code>SimpleImputer</code> 클래스를 통해 할 수 있다. 이 클래스는 각 특성의 중간값을 저장하고 있어 유용하다. 또한, 훈련 세트뿐만 아니라 검증 세트와 테스트 세트 그리고 모델에 주입될 새로운 데이터에 있는 누락된 값을 대체할 수 있다.</p>
<p>다음과 같이 누락된 값을 특성의 중간값으로 대체하도록 지정하여 SimpleImputer의 객체를 생성한다.</p>
<pre><code class="language-python">from sklearn.impute import SimpleImputer  

imputer = SimpleImputer(strategy=&quot;median&quot;)</code></pre>
<p>중간값은 수치형 특성에서만 계산될 수 있으므로 수치 특성만 가진 데이터 복사본을 생성한다.</p>
<pre><code class="language-python">housing_num = housing.select_dtypes(include=[np.number])</code></pre>
<p>이후, imputer 객체의 <code>fit()</code> 메서드를 사용해 훈련 데이터에 적용할 수 있다.</p>
<pre><code class="language-python">imputer.fit(housing_num)</code></pre>
<p>imputer은 각 특성의 중간값을 계산하여 그 결과를 객체의 <code>statistics_</code> 속성에 저장한다. 
이후, 학습된 impuer 객체를 사용해 훈련 세트에서 누락된 값을 학습된 중간값으로 바꿀 수 있다.</p>
<pre><code class="language-python">X = imputer.transform(housing_num)</code></pre>
<p>누락된 값을 평균이나 가장 자주 등장하는 값, 상수로 바꾸는 것도 가능하다. 뒤 두 가지 방법은 수치가 아닌 데이터도 지원한다.</p>
<p>사이킷런 변환기는 판다스 데이터프레임이 입력되더라도 넘파이 배열이나 사이파이 희소 행렬을 출력한다. 따라서 <code>imputer.transform(housing_num)</code> 의 출력 또한 넘파이 배열이다.</p>
<pre><code class="language-python">type(X)

&gt;&gt; numpy.ndarray</code></pre>
<p>현재 상태에서는 열 이름도 인덱스도 없기 때문에, 이를 데이터프레임으로 감싸서 열 이름과 인덱스를 복원할 수 있다.</p>
<pre><code class="language-python">housing_tr = pd.DataFrame(X, columns=housing_num.columns,  
                          index=housing_num.index)</code></pre>
<h3 id="텍스트와-범주형-특성-다루기">텍스트와 범주형 특성 다루기</h3>
<p>위에서는 수치형 특성만을 다뤘고, 이제는 텍스트 특성을 살펴보자.</p>
<pre><code class="language-python">housing_cat = housing[[&quot;ocean_proximity&quot;]]  
housing_cat.head(8)

      ocean_proximity
13096        NEAR BAY
14973       &lt;1H OCEAN
3785           INLAND
14689          INLAND
20507      NEAR OCEAN
1286           INLAND
18078       &lt;1H OCEAN
4396         NEAR BAY</code></pre>
<pre><code class="language-python">housing_cat.value_counts()

ocean_proximity
&lt;1H OCEAN          7274
INLAND             5301
NEAR OCEAN         2089
NEAR BAY           1846
ISLAND                2
Name: count, dtype: int64</code></pre>
<p>이를 통해, 이 특성은 범주형 특성임을 알 수 있다. </p>
<p>이 카테고리를 텍스트에서 숫자로 변환하기 위해 사이킷런의 <code>OrdinalEncoder</code>을 사용한다.</p>
<pre><code class="language-python">from sklearn.preprocessing import OrdinalEncoder  

ordinal_encoder = OrdinalEncoder()  
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)</code></pre>
<p>인코딩된 몇 개의 값을 확인하면 다음과 같다.</p>
<pre><code class="language-python">housing_cat_encoded[:8]

array([[3.],
       [0.],
       [1.],
       [1.],
       [4.],
       [1.],
       [0.],
       [3.]])</code></pre>
<p><code>categories_</code> 인스턴스 변수를 사용해 카테고리 리스트를 얻을 수 있다. 범주형 특성마다 1D 카테고리 배열을 담은 리스트가 반환된다.</p>
<pre><code class="language-python">ordinal_encoder.categories_

[array([&#39;&lt;1H OCEAN&#39;, &#39;INLAND&#39;, &#39;ISLAND&#39;, &#39;NEAR BAY&#39;, &#39;NEAR OCEAN&#39;],
       dtype=object)]</code></pre>
<p>이런 표현 방식의 문제는 머신러닝 알고리즘이 가까이 있는 두 값을 떨어져 있는 두 값보다 더 비슷하다고 생각한다는 점이다. (숫자 상으로 비슷하면, 실제로는 전혀 다른 특징을 가진 값이어도 비슷하게 인식하게 된다.)</p>
<p>이러한 문제는 카테고리별 이진 특성을 만들어 해결한다. 카테고리가 <code>&lt;1H OCEAN</code>일 때 한 특성이 1이고, 카테고리가 <code>INLAND</code>일 때 다른 한 특성이 1이 되는 식이다.
한 특성만 1이고 나머지는 0이므로 이를 <strong>원-핫 인코딩</strong>이라고 부른다. 새로운 특성을 <strong>더미</strong> 특성이라고도 부른다. 사이킷런은 범주 값을 원-핫 벡터로 바꾸기 위한 <code>OneHotEncoder</code> 클래스를 제공한다.</p>
<pre><code class="language-python">from sklearn.preprocessing import OneHotEncoder  

cat_encoder = OneHotEncoder()  
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)</code></pre>
<p><code>OneHotEncoder</code>의 출력은 넘파이 배열이 아니라 사이파이 희소 행렬이다. </p>
<pre><code class="language-python">housing_cat_1hot

&lt;16512x5 sparse matrix of type &#39;&lt;class &#39;numpy.float64&#39;&gt;&#39;
    with 16512 stored elements in Compressed Sparse Row format&gt;</code></pre>
<p>희소 행렬은 0이 대부분인 행렬을 매우 효율적으로 표현한다. 내부적으로 0이 아닌 값과 그 위치만 저장한다. 이는 많은 메모리를 절약하고 계산 속도를 높여주는 효과를 가진다. <a href="https://docs.scipy.org/doc/scipy/tutorial/sparse.html">공식 documentation</a></p>
<p>대부분 희소 행렬을 보통의 2D 배열처럼 사용할 수 있지만, 넘파이 배열로 바꾸려면 <code>toarray()</code> 메서드를 호출해야 한다.</p>
<pre><code class="language-python">housing_cat_1hot.toarray()

array([[0., 0., 0., 1., 0.],
       [1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0.],
       ...,
       [0., 0., 0., 0., 1.],
       [1., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1.]])</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 10 - 핸즈온 머신러닝 2장 - part 4]]></title>
            <link>https://velog.io/@chae-jpg/Day-10-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-part-4</link>
            <guid>https://velog.io/@chae-jpg/Day-10-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-part-4</guid>
            <pubDate>Sun, 20 Jul 2025 12:53:00 GMT</pubDate>
            <description><![CDATA[<p>2장이 지나치게 길어지고 있다.
사실 분량이 많은 것도 맞기는 하다...
최대한 빨리 마무리하고 뒷내용으로 넘어가도록... 노력하겠다.</p>
<hr>
<h3 id="상관관계-조사하기">상관관계 조사하기</h3>
<p>모든 특성 간의 <strong>표준 상관계수</strong>를 <code>corr()</code> 메서드를 사용해 쉽게 계산할 수 있다.</p>
<pre><code class="language-python">corr_matrix[&quot;median_house_value&quot;].sort_values(ascending=False)</code></pre>
<pre><code class="language-text">median_house_value    1.000000
median_income         0.688380
total_rooms           0.137455
housing_median_age    0.102175
households            0.071426
total_bedrooms        0.054635
population           -0.020153
longitude            -0.050859
latitude             -0.139584
Name: median_house_value, dtype: float64</code></pre>
<p>상관관계의 범위는 -1부터 1까지로, 1에 가까우면 강한 양의 상관관계, -1에 가까우면 강한 음의 상관관계를 가진다는 뜻이다.</p>
<p>판다스의 <code>scatter_matrix</code> 함수를 통해 숫자형 특성 간 산점도를 그려보는 것 또한 가능하다.</p>
<pre><code class="language-python">from pandas.plotting import scatter_matrix  

attributes = [&quot;median_house_value&quot;, &quot;median_income&quot;, &quot;total_rooms&quot;,   
&quot;housing_median_age&quot;]  
scatter_matrix(housing[attributes], figsize=(12, 8))  
plt.show()</code></pre>
<p><img src="https://i.imgur.com/Ou17vi8.png" alt=""></p>
<p>이 그림을 보았을 때, 중간 주택 가격을 예측하는 데 중간 소득이 가장 유용해보인다는 것을 발견할 수 있다. 이 산점도를 확대해보자.</p>
<p><img src="https://i.imgur.com/srlcADH.png" alt=""></p>
<p>이 그래프를 통해 알 수 있는 사실은 다음과 같다.</p>
<ol>
<li>상관관계가 매우 강하다.<ul>
<li>위쪽으로 향하는 경향이 보인다.</li>
<li>포인트들이 너무 많이 퍼져 있지는 않다.</li>
</ul>
</li>
<li>가격의 한계값이 수평선으로 잘 보이는데, 이러한 형태가 다른 곳에서도 더 나타나므로 이상한 형태를 알고리즘이 학습하지 않도록 구역을 제거할 수 있다.</li>
</ol>
<h3 id="특성-조합으로-실험하기">특성 조합으로 실험하기</h3>
<p>특정 구역의 가구 당 방 개수가 몇 개인지, 방 당 침실 개수가 몇 개인지 등 여러 특성을 조합하여 새로운 특성을 만들 수 있다.</p>
<pre><code class="language-python">housing[&quot;rooms_per_house&quot;] = housing[&quot;total_rooms&quot;] / housing[&quot;population&quot;]  
housing[&quot;bedrooms_ratio&quot;] = housing[&quot;total_bedrooms&quot;] / housing[&quot;total_rooms&quot;]  
housing[&quot;population_per_house&quot;] = housing[&quot;population&quot;] / housing[&quot;households&quot;]</code></pre>
<pre><code class="language-text">median_house_value      1.000000
median_income           0.688380
rooms_per_house         0.202050
total_rooms             0.137455
housing_median_age      0.102175
households              0.071426
total_bedrooms          0.054635
population             -0.020153
population_per_house   -0.038224
longitude              -0.050859
latitude               -0.139584
bedrooms_ratio         -0.256397
Name: median_house_value, dtype: float64</code></pre>
<p>기존의 다른 특성들에 비해 <code>bedrooms_ratio</code>, <code>rooms_per_house</code>  특성이 중간 주택 가격과의 상관관계가 높게 나타나는 것을 확인할 수 있다.</p>
<h2 id="머신러닝-알고리즘을-위한-데이터-준비">머신러닝 알고리즘을 위한 데이터 준비</h2>
<p>머신러닝 알고리즘을 위해 데이터를 준비하는 과정은 함수를 만들어 자동화해야 하는데, 그 이유는 다음과 같다.</p>
<ul>
<li>어떤 데이터셋에 대해서도 데이터 변환을 손쉽게 반복할 수 있음.</li>
<li>향후 프로젝트에 재사용 가능한 변환 라이브러리를 점진적으로 구축할 수 있음.</li>
<li>실제 시스템에서 알고리즘에 새 데이터를 주입하기 전에 이 함수를 사용해 변환할 수 있음.</li>
<li>여러 가지 데이터 변환을 쉽게 시도해볼 수 있고 어떤 조합이 가장 좋은지 확인하는 데 편리함.</li>
</ul>
<h3 id="데이터-정제">데이터 정제</h3>
<p>먼저, <code>total_bedrooms</code> 특성에 값이 없는 경우가 있는데, 이를 수정해보자. 방법에는 다음과 같이 세 가지가 있다.</p>
<blockquote>
<ol>
<li>해당 구역을 제거하기</li>
<li>전체 특성을 삭제하기</li>
<li><strong>대체</strong> : 누락된 갓을 어떤 값으로 채우기</li>
</ol>
</blockquote>
<p>판다스의 <code>dropana()</code>, <code>drop()</code>, <code>fillna()</code> 메서드로 이런 작업을 간단하게 처리할 수 있다.</p>
<pre><code class="language-python"># housing.dropna(subset=[&quot;total_bedrooms&quot;], inplace=True)  

# housing.drop(&quot;total_bedrooms&quot;, axis=1, inplace=True)  

median = housing[&quot;total_bedrooms&quot;].median()  
housing[&quot;total_bedrooms&quot;].fillna(median, inplace=True)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 9. 핸즈온 머신러닝 - 2장 - part 3]]></title>
            <link>https://velog.io/@chae-jpg/Day-9.-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-part-3</link>
            <guid>https://velog.io/@chae-jpg/Day-9.-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-part-3</guid>
            <pubDate>Fri, 18 Jul 2025 12:49:20 GMT</pubDate>
            <description><![CDATA[<p>사이킷런의 <code>sklearn.model_selection</code> 패키지 안에는 여러 가지 분할기 클래스를 제공한다. 모든 분할기는 또한 훈련과 테스트 분할에 대한 반복자를 반환하는 <code>split()</code> 메소드를 가지고 있다.</p>
<p>이 코드에서는 <code>StratifiedShuffleSplit</code>을 사용해 10개의 다른 계층 분할을 생성한다.</p>
<pre><code class="language-python">from sklearn.model_selection import StratifiedShuffleSplit

splitter = StratifiedShuffleSplit(n_splits=10, test_size=0.2, random_state=42)
strat_splits = []
for train_index, test_index in splitter.split(housing, housing[&quot;income_cat&quot;]):
    strat_train_set_n = housing.iloc[train_index]
    strat_test_set_n = housing.iloc[test_index]
    strat_splits.append([strat_train_set_n, strat_test_set_n])</code></pre>
<p>첫 번째 분할을 다음과 같이 사용할 수 있다.</p>
<pre><code class="language-python">strat_train_set, strat_test_set = strat_splits[0]</code></pre>
<p>다음과 같은 방식으로 계층적 샘플링을 코드 한 줄로 불러오는 것도 가능하다.</p>
<pre><code class="language-python">strat_train_set, strat_test_set = train_test_split(housing, test_size=0.2, stratify=housing[&quot;income_cat&quot;], random_state=42)</code></pre>
<h3 id="데이터-이해를-위한-탐색과-시각화">데이터 이해를 위한 탐색과 시각화</h3>
<p>먼저, 다음과 같은 코드를 통해 지리 정보를 산점도로 만들어 시각화해보자.</p>
<pre><code class="language-python">housing.plot(kind=&quot;scatter&quot;, x=&quot;longitude&quot;, y=&quot;latitude&quot;, grid=True)
plt.xlabel(&quot;경도&quot;)
plt.ylabel(&quot;위도&quot;)
plt.show()</code></pre>
<p><img src="https://i.imgur.com/68dJHTL.png" alt=""></p>
<p>이 그래프에서, 데이터 포인트가 밀접된 영역을 확인해보자.</p>
<pre><code class="language-python">housing.plot(kind=&quot;scatter&quot;, x=&quot;longitude&quot;, y=&quot;latitude&quot;, grid=True, alpha=0.2)
plt.xlabel(&quot;logitude&quot;)
plt.ylabel(&quot;latitude&quot;)
plt.show()</code></pre>
<p><img src="https://i.imgur.com/SlD9new.png" alt=""></p>
<p>다음으로는 구역의 인구와 주택가격까지 그림에 나타내보자.</p>
<pre><code class="language-python">housing.plot(kind=&quot;scatter&quot;, x=&quot;longitude&quot;, y=&quot;latitude&quot;, grid=True,
             s=housing[&quot;population&quot;] / 100, label=&quot;population&quot;,
             c=&quot;median_house_value&quot;, cmap=&quot;jet&quot;, colorbar=True,
             legend=True, figsize=(10, 7))
plt.show()</code></pre>
<p><img src="https://i.imgur.com/Czh9CEq.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 8. 핸즈온 머신러닝 - 2장 - part 2]]></title>
            <link>https://velog.io/@chae-jpg/Day-8.-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-part-2</link>
            <guid>https://velog.io/@chae-jpg/Day-8.-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-part-2</guid>
            <pubDate>Thu, 17 Jul 2025 09:13:08 GMT</pubDate>
            <description><![CDATA[<p><code>describe</code> 메서드는 숫자형 특성의 요약 정보를 보여준다.</p>
<p><img src="https://i.imgur.com/XAR1pUi.png" alt=""></p>
<p><code>hist()</code> 메서드는 모든 숫자형 형태에 대한 히스토그램을 출력할 수 있다.
<img src="https://i.imgur.com/r711ouE.png" alt=""></p>
<p>이를 통해 알 수 있는 사실은 다음과 같다. </p>
<ul>
<li>중간 소득이 US 달러로 표시되어 있지 않다. 따라서, 그 단위를 통일하는 것이 필요하다.</li>
<li>중간 주택 연도와 중간 주택 가격 그래프의 오른쪽 값이 심하게 높아지면서 그래프가 끝나는 것으로 보아 최댓값과 최솟값이 한정되어 있음을 알 수 있다.</li>
<li>특성들의 스케일이 많이 다르다.</li>
<li>많은 히스토그램에서 오른쪽 꼬리가 더 길다. 이러한 형태는 일부 머신러닝 알고리즘에서 패턴을 찾기 어렵게 만든다.</li>
</ul>
<h3 id="테스트-세트-만들기">테스트 세트 만들기</h3>
<ul>
<li><strong>데이터 스누핑</strong> 편향 : 테스트 세트로 일반화 오차를 추정하여 매우 낙관적인 추정이 나오게 되는 것</li>
</ul>
<p>다음과 같은 코드로 랜덤으로 테스트 세트를 만들 수 있다.</p>
<pre><code class="language-python">import numpy as np

def shuffle_and_split_data(data, test_ratio):
shuffled_indices = np.random.permutation(len(data)) # 괄호 안 숫자 범위로 생성된 배열을 무작위로 섞음.
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]
return data.iloc[train_indices], data.iloc[test_indices]</code></pre>
<p>하지만, <strong>프로그램을 실행할 때마다 다른 테스트 세트가 생성된다</strong>는 문제점이 존재한다. 이를 해결하기 위해서는,</p>
<ol>
<li>처음 실행에서 테스트 세트를 저장하고 다음번 실행에서 불러들이기</li>
<li>항상 같은 난수 인덱스가 생성되도록 <code>np.random.permutation()</code>을 호출하기 전에 난수 발생기의 초깃값을 지정하기</li>
</ol>
<p>하지만 위 방법 또한 데이터셋이 업데이트된 경우 문제가 생긴다. 따라서, 샘플의 식별자를 사용하여 테스트 세트로 보낼지 말지를 결정할 수 있다.</p>
<pre><code class="language-python">from zlib import crc32

def is_id_in_test_set(identifier, test_ratio):
return crc32(np.int64(identifier).tobytes()) &lt; test_ratio * 2 ** 32

def split_data_with_id_hash(data, test_ratio, id_column):
ids = data[id_column]
in_test_set = ids.apply(lambda id_: is_id_in_test_set(id_, test_ratio))
return data.loc[~in_test_set], data.loc[in_test_set]</code></pre>
<p>위 방법을 통해 나눈 훈련 / 테스트 세트를 보면 다음과 같다.</p>
<p><img src="https://i.imgur.com/gTLpeMA.png" alt=""></p>
<hr>
<p>사이킷런을 사용하면 위 과정을 간단히 구현할 수 있다. 가장 간단한 함수는 <code>train_test_split</code>으로, <em>난수 초깃값을 설정하는 기능</em>과 <em>행의 개수가 같은 여러 개의 데이터셋을 넘겨 동일한 인덱스를 기반으로 나누는 기능</em>이 존재한다. </p>
<pre><code class="language-python">from sklearn.model_selection import train_test_split

train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)</code></pre>
<hr>
<p>앞서 본 샘플링은 <strong>순수한 랜덤 샘플링</strong>이다. 데이터셋이 충분히 크다면 괜찮지만, 그렇지 않다면 샘플링 편향이 생길 가능성이 크다. 이를 위해 <strong>계층적 샘플링</strong>이 존재한다.</p>
<ul>
<li><strong>계층적 샘플링</strong> : 전체를 <em>계층</em>이라는 동질의 그룹으로 나누고, 테스트 세트가 전체를 대표할 수 있도록 각 계층에서 올바른 수의 샘플을 추출하는 것.</li>
</ul>
<p>이를 위해 전체를 여러 개의 계층으로 나눈 뒤, 계층의 비율대로 전체에서 샘플링하는 것이 가능하다.</p>
<pre><code class="language-python">housing[&quot;income_cat&quot;] = pd.cut(housing[&quot;median_income&quot;],
                               bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
                               labels=[1, 2, 3, 4, 5])</code></pre>
<p><a href="https://pandas.pydata.org/docs/reference/api/pandas.cut.html">pandas.cut reference</a>
<code>pandas.cut</code> 함수는 특정 1차원 배열을 <code>bins</code>에 따라 해당 개수, 또는 해당 구간에 따라 분할한 뒤, 각 값들에 대해 해당하는 구간의 <code>labels</code>의 값을 부여한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 7. 핸즈온 머신러닝 - 2장 - part 1]]></title>
            <link>https://velog.io/@chae-jpg/Day-7.-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5</link>
            <guid>https://velog.io/@chae-jpg/Day-7.-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-2%EC%9E%A5</guid>
            <pubDate>Mon, 14 Jul 2025 12:08:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>2장에서는 캘리포니아 주택 가격 데이터셋을 활용해 머신러닝 프로젝트를 처음부터 끝까지 직접 진행해보면서 배우는 것을 목표로 한다.</p>
</blockquote>
<h2 id="큰-그림-보기">큰 그림 보기</h2>
<h3 id="문제-정의">문제 정의</h3>
<p>풀고자 하는 문제가 무엇인지 먼저 정의하는 것이 필요함. 그렇게 하여 문제 상황을 정확히 파악하고, 해당 문제에 적합한 시스템을 설계하게 됨. 
지금 문제는 레이블된 훈련 샘플이 있고, 여러 특성을 통해 한 개의 값을 예측하므로 <strong>지도 학습, 단변량 회귀</strong> 문제가 됨.</p>
<h3 id="성능-측정-지표-선택">성능 측정 지표 선택</h3>
<p>회귀 문제에서는 일반적으로 평균 제곱근 오차, <code>RMSE</code>를 사용한다. 
$RMSE(X, h) = \sqrt{\frac{1}{m} \sum_{i=1}^{n} (h(x^{(i)}) - y^{(i)})^2}$</p>
<p>하지만, <strong>이상치로 보이는 구역이 많은 경우</strong>, 평균 절대 오차, <code>MAE</code>를 사용하기도 한다.
$MAE(X, h) = \frac{1}{m}\sum_{i=1}^{m}|h(x^{(i)})-y^{(i)}|$</p>
<p>둘 모두 예측값의 벡터와 타깃값의 벡터 사이의 거리를 재는 방식이다. 거리 측정에는 여러 방식을 사용할 수 있다.</p>
<ul>
<li><strong>유클리드 노름</strong> : <code>RMSE</code> 계산과 같다. $l_2$ 노름이라고도 부른다.</li>
<li><strong>맨해튼 노름</strong> : 절댓값의 합을 계산한다. $l_1$ 노름이라고도 부른다.</li>
<li>$l_k$ 노름 = $(|v_0|^k + |v_1|^k + ... + |v_n|^k)^{\frac{1}{k}}$</li>
</ul>
<h3 id="가정-검사">가정 검사</h3>
<p>시스템에서 요구로 출력을 하고 있는 출력이 현재 개발하고자 하는 모델의 출력과 일치한지 (ex: 가정에서는 수치형을 출력하고자 하는데 범주형을 요구한다던가) 확인해야한다.</p>
<h2 id="데이터-가져오기">데이터 가져오기</h2>
<h3 id="데이터-다운로드">데이터 다운로드</h3>
<p>아래 코드를 실행하면 데이터를 다운로드 할 수 있다.</p>
<pre><code class="language-python">from pathlib import Path
import pandas as pd
import tarfile
import urllib.request

def load_housing_data():
    tarball_path = Path(&quot;datasets/housing.tgz&quot;)
    if not tarball_path.is_file():
        Path(&quot;datasets&quot;).mkdir(parents=True, exist_ok=True)
        url = &quot;https://github.com/ageron/data/raw/main/housing.tgz&quot;
        urllib.request.urlretrieve(url, tarball_path)
        with tarfile.open(tarball_path) as housing_tarball:
            housing_tarball.extractall(path=&quot;datasets&quot;)
    return pd.read_csv(&quot;datasets/housing/housing.csv&quot;)

housing = load_housing_data()</code></pre>
<ol>
<li><code>tarball_path</code>에 있는 파일이 있는지 확인한다.
1-1. 만약 파일이 없다면, <code>datasets</code> 폴더를 만들고 그 폴더에 <code>tgz</code> 파일을 다운로드받은 뒤, 압축 해제를 한다.</li>
<li>파일을 pandas DataFrame으로 읽어온 결과를 리턴한다.</li>
</ol>
<h3 id="데이터-구조-훑어보기">데이터 구조 훑어보기</h3>
<p><code>head()</code> 메서드를 사용하면 데이터의 첫 다섯 행을 읽어올 수 있다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/c1c752c8-baad-4353-b685-d6ce992c5b6c/image.png" alt=""></p>
<p><code>info()</code> 메서드는 데이터에 관한 간략한 설명을 보여준다.
특히, 전체 행 수, 각 특성의 데이터 타입, 널이 아닌 값의 개수를 확인하는 데 유용하다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/69e622c6-e4ad-455c-bb34-8bca5374f218/image.png" alt=""></p>
<p>다음과 같은 정보를 알 수 있다.</p>
<ul>
<li>전체 샘플 개수는 20640개이다.</li>
<li><code>total_bedrooms</code>는 20433개만 널값이 아니다. 즉, 나머지 207개는 이 특성을 가지고 있지 않다.</li>
<li><code>ocean_proximity</code> 필드를 제외한 모든 특성이 숫자형이다.</li>
</ul>
<hr>
<p><code>value_counts()</code> 메서드는 어떤 카테고리가 있고, 각 카테고리마다 얼마나 많은 구역이 있는지 확인할 수 있다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/d765883d-39d3-44a6-a553-9916172afc19/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 6. 핸즈온 머신러닝 - 1장]]></title>
            <link>https://velog.io/@chae-jpg/Day-6.-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-1%EC%9E%A5</link>
            <guid>https://velog.io/@chae-jpg/Day-6.-%ED%95%B8%EC%A6%88%EC%98%A8-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-1%EC%9E%A5</guid>
            <pubDate>Sat, 12 Jul 2025 01:27:23 GMT</pubDate>
            <description><![CDATA[<h2 id="1장---한눈에-보는-머신러닝">1장 - 한눈에 보는 머신러닝</h2>
<h3 id="머신러닝이란">머신러닝이란?</h3>
<ul>
<li><p>명시적인 프로그래밍 없이 컴퓨터가 학습하는 능력을 갖추게 하는 연구 분야</p>
</li>
<li><p><strong>훈련 세트</strong> : 시스템이 학습하는 데 사용하는 샘플</p>
</li>
<li><p><strong>훈련 사례 (샘플)</strong> : 각각의 훈련 데이터</p>
</li>
<li><p><strong>모델</strong> : 학습하고 예측을 만드는 부분</p>
</li>
<li><p><strong>정확도</strong> : 성능 측정 지표 중 하나, 분류 작업에 많이 사용됨</p>
</li>
</ul>
<h3 id="머신러닝을-사용하는-이유">머신러닝을 사용하는 이유</h3>
<ul>
<li><strong>기존의 프로그램에 비해 유지보수가 편리</strong> : 복잡한 규칙이 필요하지 않음, 자동화 기능을 갖춤</li>
<li><strong>전통적인 방식으로는 너무 복잡하거나 알려진 알고리즘이 없는 문제를 해결 가능</strong> : ex) 음성 인식</li>
<li><strong>데이터 마이닝</strong> : 대용량의 데이터를 분석하여 숨겨진 패턴을 발견하는 것. 이처럼 머신러닝을 통해 새로운 지식을 배울 수 있음.</li>
</ul>
<h3 id="머신러닝-시스템의-종류">머신러닝 시스템의 종류</h3>
<p>다양한 범주로 나눌 수 있음.</p>
<h4 id="훈련-지도-방식">훈련 지도 방식</h4>
<p>학습하는 동안의 지도 형태나 정보량에 따라 분류하는 방법.</p>
<ul>
<li><strong>지도 학습 ** : 훈련 데이터에 **레이블</strong>이라는 원하는 답이 포함되는 방법. ex) 분류, 회귀</li>
<li><strong>비지도 학습</strong> : 레이블이 없어 시스템이 아무런 도움 없이 학습해야 하는 방법.</li>
<li><strong>준지도 학습</strong> : 레이블이 일부만 있는 데이터를 다루는 학습 방법.</li>
<li><strong>자기 지도 학습</strong> : 레이블이 전혀 없는 데이터셋에서 레이블이 완전히 부여된 데이터셋을 생성하는 방법.</li>
<li><strong>강화 학습</strong> : <strong>에이전트(학습하는 시스템)</strong>이 환경을 관찰하여 행동을 실행하고 그 결과로 <strong>보상</strong> 또는 <strong>벌점</strong>을 얻는 방식. 시간이 지나면서 가장 큰 보상을 얻기 위해 <strong>정책</strong>이라고 하는 최상의 전략을 스스로 학습함.</li>
</ul>
<h4 id="배치-학습과-온라인-학습">배치 학습과 온라인 학습</h4>
<h5 id="배치-학습">배치 학습</h5>
<ul>
<li>가용한 데이터를 모두 사용해 훈련시키는 방법.</li>
<li><strong>오프라인 학습</strong> : 시스템을 오프라인에서 훈련시킨 뒤 학습한 것을 적용하기만 함.</li>
<li>문제점 : 세상은 진화하는데 모델은 바뀌지 않고 그대로임. -&gt; <strong>모델 부패, 데이터 드리프트</strong></li>
</ul>
<h5 id="온라인-학습">온라인 학습</h5>
<ul>
<li>데이터를 순차적으로 한 개씩 또는 <strong>미니배치</strong>라고 부르는 작은 묶음 단위로 주입.</li>
<li><strong>학습률</strong> : 변화하는 데이터에 얼마나 빠르게 적응할지를 결정하는 파라미터.</li>
</ul>
<h4 id="사례-기반-학습과-모델-기반-학습">사례 기반 학습과 모델 기반 학습</h4>
<p>어떻게 일반화되는가에 따라 머신러닝 시스템을 분류함.</p>
<ul>
<li><strong>사례 기반 학습</strong> : 시스템이 훈련 샘플을 기억함으로써 학습함.</li>
<li><strong>모델 기반 학습</strong> : 샘플들의 모델을 만들어 예측에 사용.</li>
</ul>
<h3 id="머신러닝의-주요-도전-과제">머신러닝의 주요 도전 과제</h3>
<p>나쁜 모델과 나쁜 데이터의 사례로 나누어짐.</p>
<h4 id="충분하지-않은-양의-훈련-데이터">충분하지 않은 양의 훈련 데이터</h4>
<ul>
<li>대부분의 머신러닝 알고리즘이 잘 작동하려면 데이터가 아주 많아야 함.</li>
</ul>
<h4 id="대표성-없는-훈련-데이터">대표성 없는 훈련 데이터</h4>
<ul>
<li>일반화가 잘 되려면 훈련 데이터가 일반화하고 싶은 새로운 사례를 잘 대표하는 것이 중요.</li>
<li><strong>샘플링 잡음</strong> : 우연에 의한 대표성 없는 데이터, 샘플이 작을 경우 생김.</li>
<li><strong>샘플링 편향</strong> : 대표성을 띠지 못하는 샘플을 의미.</li>
</ul>
<h4 id="낮은-품질의-데이터">낮은 품질의 데이터</h4>
<ul>
<li>훈련 오류가 오류, 이상치, 잡음으로 가득하다면 데이터 정제를 위해 많은 시간을 투자해야 함.</li>
</ul>
<h4 id="관련-없는-특성">관련 없는 특성</h4>
<ul>
<li><p>훈련 데이터에 관련 없는 특성이 적고 관련 있는 특성이 충분해야 시스템이 학습할 수 있음.</p>
</li>
<li><p>훈련에 사용할 좋은 특성을 찾는 것을 <strong>특성 공학</strong>이라고 함.</p>
<ul>
<li><strong>특성 선택</strong> : 가지고 있는 특성 중 훈련에 가장 유용한 특성을 선택.</li>
<li><strong>특성 추출</strong> : 특성을 결합하여 더 유용한 특성을 만듦.</li>
<li><strong>데이터 수집</strong> : 새로운 데이터를 수집해 새 특성을 만듦.</li>
</ul>
</li>
</ul>
<h4 id="훈련-데이터-과대적합">훈련 데이터 과대적합</h4>
<ul>
<li><strong>과대적합</strong> : 모델이 훈련 데이터에는 너무 잘 맞지만 일반성이 떨어지는 경우</li>
<li>훈련 데이터의 양과 잡음에 비해 모델이 너무 복잡할 때 일어남.</li>
<li><strong>규제</strong>를 통해 문제를 해결할 수 있음. </li>
</ul>
<h4 id="훈련-데이터-과소적합">훈련 데이터 과소적합</h4>
<ul>
<li>모델이 너무 단순해서 데이터의 내제된 구조를 학습하지 못할 때 일어남.</li>
</ul>
<h3 id="테스트와-검증">테스트와 검증</h3>
<ul>
<li><p>훈련 데이터를 <strong>훈련 세트</strong>와 <strong>테스트 세트</strong> 두 개로 나누어 검증을 진행함.</p>
<ul>
<li><strong>일반화 오차</strong> : 새로운 샘플에 대한 오차 비율, 테스트 세트에서 모델을 평가함으로써 이 값에 대한 추정값을 얻음.</li>
</ul>
</li>
</ul>
<h4 id="하이퍼파라미터-튜닝과-모델-선택">하이퍼파라미터 튜닝과 모델 선택</h4>
<ul>
<li><p>하이퍼파라미터 튜닝 과정에서 <strong>테스트 세트에 최적화된 모델</strong>이 만들어져 새로운 데이터에 잘 작동하지 않게 될 수 있음.</p>
</li>
<li><p>-&gt; <strong>홀드아웃 검증</strong>을 사용.</p>
<ul>
<li>훈련 세트의 일부를 떼어내어 여러 후보 모델을 평가하고 가장 좋은 하나를 선택하는 방법.</li>
<li><strong>검증 세트</strong> : 새로운 홀드아웃 세트</li>
<li><strong>교차 검증</strong> : 검증 세트마다 나머지 데이터에서 훈련한 모델을 해당 검증 세트에서 평가하는 방법. 훨씬 정확한 성능을 측정할 수 있지만, 훈련 시간이 검증 세트의 개수에 비례해 늘어남.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 5. 점프 투 파이썬 - 파이썬 날아오르기]]></title>
            <link>https://velog.io/@chae-jpg/Day-5.-%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%82%A0%EC%95%84%EC%98%A4%EB%A5%B4%EA%B8%B0</link>
            <guid>https://velog.io/@chae-jpg/Day-5.-%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%82%A0%EC%95%84%EC%98%A4%EB%A5%B4%EA%B8%B0</guid>
            <pubDate>Thu, 10 Jul 2025 08:35:19 GMT</pubDate>
            <description><![CDATA[<h2 id="클로저와-데코레이터">클로저와 데코레이터</h2>
<h3 id="클로저란">클로저란?</h3>
<ul>
<li>함수 안에 내부 함수를 구현하고 그 내부 함수를 리턴하는 함수</li>
</ul>
<p>ex) 클래스를 통한 구현 (클로저 X)</p>
<pre><code class="language-python">class Mul:
    def __init__(self, m):
        self.m = m

    def __call__(self, n):
        return self.m * n

if __name__ == &quot;__main__&quot;:
    mul3 = Mul(3)
    mul5 = Mul(5)

    print(mul3(10)) # 30 출력
    print(mul5(10)) # 50 출력</code></pre>
<ul>
<li><code>mul3 = Mul(3)</code>에서 생성자를 통해 함수 내부의 m이 3으로 지정.</li>
<li><code>mul3(10)</code>을 통해 <code>__call__</code> 메소드가 호출됨. </li>
</ul>
<p>ex) 함수 안에 함수를 구현하는 방법 (클로저)</p>
<pre><code>def mul(m):
    def wrapper(n):
        return m * n
    return wrapper

if __name__ == &quot;__main__&quot;:
    mul3 = mul(3)
    mul5 = mul(5)

    print(mul3(10)) # 30 출력
    print(mul5(10)) # 50 출력</code></pre><ul>
<li>위와 같은 기능을 구현하고 있지만, <strong>함수가 함수를 리턴</strong>하는 식으로 구현.</li>
</ul>
<h3 id="데코레이터란">데코레이터란?</h3>
<ul>
<li>기존 함수를 바꾸지 않고 기능을 추가할 수 있게 만드는 클로저</li>
</ul>
<p>ex)</p>
<pre><code class="language-python">import time

def elasped(original_func):
    def wrapper():
        start = time.time()
        result = original_func()
        end = time.time()
        print(&quot;함수 수행시간: %f 초&quot; % (end - start))
        return result
    return wrapper

def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)

decorated_myfunc = elasped(myfunc)
decorated_myfunc()</code></pre>
<blockquote>
<p><strong>나의 의문</strong> : 여기서 굳이 wrapper을 써서 클로저로 구현해야 하는 이유는? 사실 wrapper 아래 코드를 바로 함수에서 실행해도 작동은 동일할 텐데?</p>
</blockquote>
<blockquote>
<p><strong>이유</strong> : <code>elasped</code>를 <strong>데코레이터</strong>로 만들기 위해서. 아래의 데코레이터로써의 기능을 사용하기 위해서는 <em>클로저로 구현</em>해야 할 필요가 있음.</p>
</blockquote>
<ul>
<li>다음과 같이 @ 문자를 이용해 함수 위에 적용하여 사용할 수 있음.</li>
</ul>
<pre><code class="language-python">@elasped
def myfunc():
    print(&quot;함수가 실행됩니다.&quot;)

myfunc() # 데코레이터가 위에 바로 붙었으므로 별도의 선언이 더 이상 필요하지 않음.</code></pre>
<ul>
<li>데코레이터와 기존 함수의 인자 개수가 일치하지 않을 경우 에러가 남</li>
<li><blockquote>
<p>어떤 인자를 받든 작동하도록 <code>*args</code>와 <code>**kwargs</code>를 사용.</p>
</blockquote>
</li>
</ul>
<pre><code class="language-python">def elasped(original_func):
    def wrapper(*args, **kwargs): 
    ...</code></pre>
<blockquote>
<p><strong>나의 의문</strong> : 꼭 <code>*args</code>랑 <code>**kwargs</code>를 둘 다 써야 하나?</p>
</blockquote>
<blockquote>
<p><strong>해답</strong> : 꼭 그럴 필요는 없는데, 지금처럼 유연하게 어떤 인자가 들어와도 받을 수 있게 처리하는 것이 목적이라면 둘 다 써야 함. 지금의 경우에는 <code>*args</code>가 빠질 경우에는 문제가 생김. 위치 인자가 <code>original_func</code>로 들어오고 있고, <code>*args</code>가 그걸 받는 역할을 하는 인자이기 때문.</p>
</blockquote>
<h2 id="이터레이터와-제너레이터">이터레이터와 제너레이터</h2>
<h3 id="이터레이터란">이터레이터란?</h3>
<ul>
<li><code>이터러블</code> : 리스트와 같이 반복 구문에 적용할 수 잇는 객체</li>
<li><code>이터레이터</code> : <code>next</code> 함수 호출 시 계속 그다음 값을 리턴하는 객체</li>
</ul>
<p>ex) 리스트를 이터레이터로 변환하고 next를 사용해 값 가져오기</p>
<pre><code class="language-python">a = [1, 2, 3]
ia = iter(a) # 모든 이터러블은 iter 함수를 통해 이터레이터로 변환 가능.

next(ia)
# 1
next(ia)
# 2
next(ia)
# 3
next(ia)
# StopIteration 예외 : 더 리턴할 값이 없으므로</code></pre>
<p>ex) for을 사용해 이터레이터의 값 가져오기</p>
<pre><code class="language-python">a = [1, 2, 3]
ia = iter(ia)
for i in ia:
    print(i)
    # 1 2 3 출력
for i in ia:
    print(i)
    # 아무것도 출력 X, for이나 next로 값을 한 번 읽으면 다시 읽을 수 없는 이터레이터의 특징.</code></pre>
<h3 id="이터레이터-만들기">이터레이터 만들기</h3>
<ul>
<li>class로 이터레이터를 만들 수 있음.</li>
<li><code>__iter__</code>과 <code>__next__</code> 라는 2개의 메서드를 구현.</li>
</ul>
<pre><code class="language-python">class MyIterator:
    def __init__(self, data):
        self.data = data
        self.position = 0

    def __iter(self):
        return self

    def __next__(self):
        if self.position &gt;= len(self.data):
            raise StopIteration
        result = self.data[self.position]
        self.position += 1
        return result

if __name__ == &quot;__main__&quot;:
    i = MyIterator([1, 2, 3])
    for item in i:
        print(item)</code></pre>
<ul>
<li><code>__iter__</code> : 이 메서드가 있는 클래스로 생성한 객체는 <strong>반복 가능한 객체</strong>가 됨. 반복 가능한 객체를 리턴해야 함.</li>
<li><code>__next__</code> : <code>__iter__</code>이 있는 클래스는 반드시 <strong><code>__next__</code>를 구현</strong>해야 함. 반복 가능한 객체의 값을 차례대로 반환하는 역할을 함.</li>
</ul>
<h3 id="제너레이터란">제너레이터란?</h3>
<ul>
<li>이터레이터를 생성해 주는 함수</li>
<li><code>next</code> 함수 호출 시 그 값을 차례대로 얻을 수 있음.</li>
<li>차례로 결과를 반환하기 위해 <code>yield</code> 키워드 사용</li>
</ul>
<pre><code class="language-python">def mygen():
    yield &#39;a&#39;
    yield &#39;b&#39;
    yield &#39;c&#39;

if __name__ == &quot;__main__&quot; : 
    g = mygen()
    next(g)
    # a
    next(g)
    # b
    next(g)
    # c
    next(g)
    # StopIteration 예외</code></pre>
<ul>
<li><code>yield</code>를 만날 시 해당 값을 리턴하되, 현제 상태를 그대로 기억한 채로 멈춤.</li>
</ul>
<h3 id="제너레이터-표현식">제너레이터 표현식</h3>
<pre><code class="language-python">def mygen():
    for i in range(1, 1000):
        result = i * i
        yield result

gen = mygen()

print(next(gen))
# 1
print(next(gen))
# 4
print(next(gen))
# 9</code></pre>
<p>다음과 같은 제너레이터를 아래와 같이 튜플 표현식으로 간단하게 만들 수 있음.</p>
<pre><code class="language-python">gen = (i * i for i in range(1, 1000))</code></pre>
<h3 id="제너레이터-활용하기">제너레이터 활용하기</h3>
<pre><code class="language-python">import time

def longtime_job():
    print(&quot;job start&quot;)
    time.sleep(1)  # 1초 지연
    return &quot;done&quot;</code></pre>
<p>다음과 같은 코드에서</p>
<p><code>list_job = [longtime_job() for i in range(5)]</code>와
<code>list_job = (longtime_job() for i in range(5))</code>를 사용 가능.</p>
<p>위 : <code>longtime_job()</code>을 5번 실행해서 그 결과로 리스트를 만듦. -&gt; 즉시 <code>longtime_job()</code> 5번 실행
아래 : 제너레이터 표현식. 즉 <code>next</code>로 <code>list_job</code>의 결과를 불러오면 그 때마다 실행되고 값을 리스트에 넣음. -&gt; next할 때마다 <code>longtime_job()</code> 실행</p>
<h2 id="파이썬-타입-어노테이션">파이썬 타입 어노테이션</h2>
<h3 id="동적-언어">동적 언어</h3>
<ul>
<li><code>동적 언어</code> : 프로그램 실행 중 변수의 타입을 바꿀 수 있는 언어. ex) 파이썬</li>
</ul>
<pre><code class="language-python">a = 1
a = &quot;1&quot; # 타입을 실행 중 스트링으로 바꿈 -&gt; 동적 언어</code></pre>
<ul>
<li>장점 : 유연한 코딩이 가능하므로 쉽고 빠른 개발 가능, 타입 체크를 위한 코드가 없으므로 깔끔한 소스 생성 가능</li>
<li>단점 : 프로젝트의 규모가 커질수록 타입을 잘못 사용해 버그가 생길 확률이 늘어남.</li>
</ul>
<h3 id="파이썬-타입-어노테이션-1">파이썬 타입 어노테이션</h3>
<ul>
<li>3.5버전부터 타입 어노테이션 기능이 도입됨.</li>
<li>타입에 대한 힌트를 알려주는 정도의 기능</li>
</ul>
<pre><code class="language-python"># a와 b의 타입 : int, 리턴값의 타입 : int
def add(a: int, b: int) -&gt; int:
    return a+b</code></pre>
<h3 id="mypy">mypy</h3>
<ul>
<li>더 적극적으로 파이썬 어노테이션을 사용할 수 있도록 돕는 라이브러리</li>
<li><code>pip install mypy</code>를 통해 설치 후 사용</li>
<li>아래와 같이 터미널에서 실행 시 결과를 출력함. </li>
</ul>
<pre><code>C:\doit&gt;mypy typing_sample.py
typing_sample.py:5: error: Argument 2 to &quot;add&quot; has incompatible type &quot;float&quot;; expected &quot;int&quot;
Found 1 error in 1 file (checked 1 source file)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Day 4. 점프 투 파이썬 - 파이썬 날개 달기]]></title>
            <link>https://velog.io/@chae-jpg/Day-4.-%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%82%A0%EA%B0%9C-%EB%8B%AC%EA%B8%B0</link>
            <guid>https://velog.io/@chae-jpg/Day-4.-%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%82%A0%EA%B0%9C-%EB%8B%AC%EA%B8%B0</guid>
            <pubDate>Tue, 08 Jul 2025 09:41:05 GMT</pubDate>
            <description><![CDATA[<h2 id="클래스-class">클래스 (Class)</h2>
<p>클래스는 <strong>자료형을 직접 만들 수 있는 도구</strong>다.<br>파이썬은 기본적으로 <code>int</code>, <code>str</code> 같은 자료형을 제공하지만, 사용자가 직접 자료형(클래스)을 정의할 수 있다.</p>
<hr>
<h3 id="클래스-정의하기">클래스 정의하기</h3>
<pre><code class="language-python">class 클래스이름:
def init(self):
# 생성자 (객체가 생성될 때 자동 실행됨)
수행할 문장</code></pre>
<p>예시:</p>
<pre><code class="language-python">class FourCal:
def init(self, first, second):
self.first = first
self.second = second</code></pre>
<hr>
<h3 id="객체-생성하기">객체 생성하기</h3>
<pre><code class="language-python">a = FourCal(4, 2)</code></pre>
<hr>
<h3 id="클래스-내부-함수-메서드-정의">클래스 내부 함수 (메서드) 정의</h3>
<pre><code class="language-python">class FourCal:
def init(self, first, second):
self.first = first
self.second = second

def add(self):
    return self.first + self.second

def mul(self):
    return self.first * self.second

def sub(self):
    return self.first - self.second

def div(self):
    return self.first / self.second</code></pre>
<pre><code class="language-python">a = FourCal(4, 2)
a.add()  # 6
a.mul()  # 8
a.sub()  # 2
a.div()  # 2.0</code></pre>
<hr>
<h3 id="생성자-__init__-이해하기">생성자 (<code>__init__</code>) 이해하기</h3>
<p>객체가 생성될 때 자동 호출되어 초기값을 설정함.</p>
<pre><code class="language-python">class Sample:
def init(self, value):
self.value = value</code></pre>
<hr>
<h3 id="self의-의미">self의 의미</h3>
<ul>
<li>클래스 내 메서드는 <strong>반드시 첫 번째 인자로 <code>self</code>를 가짐</strong></li>
<li><code>self</code>는 생성된 객체 자기 자신을 의미함</li>
<li>객체 내 변수 접근: <code>self.변수</code></li>
</ul>
<hr>
<h3 id="클래스에-함수-추가하기">클래스에 함수 추가하기</h3>
<pre><code class="language-python">class MoreFourCal(FourCal):
def pow(self):
return self.first ** self.second



a = MoreFourCal(2, 3)
a.pow()  # 8</code></pre>
<hr>
<h3 id="상속">상속</h3>
<p>기존 클래스를 상속받아 새로운 클래스를 만들 수 있다.</p>
<pre><code class="language-python">class MoreFourCal(FourCal):
pass  # 기존 기능을 그대로 상속</code></pre>
<hr>
<h3 id="메서드-오버라이딩-재정의">메서드 오버라이딩 (재정의)</h3>
<p>부모 클래스의 메서드를 <strong>자식 클래스에서 다시 정의</strong>할 수 있다.</p>
<pre><code class="language-python">class SafeFourCal(FourCal):
def div(self):
if self.second == 0:
return 0
else:
return self.first / self.second



a = SafeFourCal(4, 0)
a.div()  # 0</code></pre>
<hr>
<h3 id="클래스-변수와-인스턴스-변수">클래스 변수와 인스턴스 변수</h3>
<ul>
<li><strong>클래스 변수</strong>: 클래스에 공유되는 변수</li>
<li><strong>인스턴스 변수</strong>: 객체마다 따로 존재<pre><code class="language-python">class Family:
lastname = “김”
</code></pre>
</li>
</ul>
<p>print(Family.lastname)  # 김</p>
<p>a = Family()
b = Family()
print(a.lastname)  # 김
print(b.lastname)  # 김</p>
<p>Family.lastname = “박”
print(a.lastname)  # 박
print(b.lastname)  # 박</p>
<pre><code>※ 클래스 변수는 클래스 자체 또는 모든 인스턴스에서 공유됨

## 모듈 (Module)

모듈이란 **함수, 변수, 클래스** 등을 모아 놓은 **파이썬 파일(.py)** 을 말한다.  
필요할 때 import하여 사용할 수 있다.

---
### 모듈 만들기

예: `mod1.py` 파일을 만든다.
```python
def add(a, b):
return a + b

def sub(a, b):
return a - b</code></pre><hr>
<h3 id="모듈-불러오기-import">모듈 불러오기 (import)</h3>
<pre><code class="language-python">import mod1
print(mod1.add(3, 4))  # 7
print(mod1.sub(4, 2))  # 2</code></pre>
<p>또는 <code>from</code>을 사용해 직접 함수만 가져올 수 있다.</p>
<pre><code class="language-python">from mod1 import add
add(3, 4)  # 7</code></pre>
<p>여러 함수 가져오기:</p>
<p><code>from mod1 import add, sub</code></p>
<p>전체 가져오기:</p>
<p><code>from mod1 import *  # 권장되지 않음</code></p>
<hr>
<h3 id="모듈-이름-확인하기">모듈 이름 확인하기</h3>
<p>모듈 내부에서 자신의 이름을 확인할 수 있다.</p>
<p><code>print(name)</code></p>
<p>직접 실행하면 <code>__main__</code>, import되면 파일명이 출력된다.</p>
<p>이를 활용해 테스트 코드를 작성할 수 있다:</p>
<pre><code class="language-python">def add(a, b):
return a + b

if name == “main”:
print(add(3, 4))</code></pre>
<hr>
<h3 id="클래스가-포함된-모듈">클래스가 포함된 모듈</h3>
<p>예: <code>mod2.py</code></p>
<pre><code class="language-python">class Calculator:
def init(self):
self.result = 0

def add(self, num):
    self.result += num
    return self.result</code></pre>
<p>사용 예:</p>
<pre><code class="language-python">from mod2 import Calculator

cal1 = Calculator()
print(cal1.add(3))  # 3
print(cal1.add(4))  # 7</code></pre>
<hr>
<h3 id="다른-위치에-있는-모듈-사용하기">다른 위치에 있는 모듈 사용하기</h3>
<ul>
<li><code>sys.path</code>에 디렉토리 추가<pre><code class="language-python">import sys
sys.path.append(”/사용할/모듈/디렉토리”)</code></pre>
</li>
<li>또는 환경 변수 <code>PYTHONPATH</code>에 디렉토리 등록</li>
</ul>
<hr>
<h3 id="표준-모듈-라이브러리">표준 모듈 (라이브러리)</h3>
<p>파이썬은 다양한 <strong>표준 모듈</strong>을 제공한다.</p>
<p>예:</p>
<pre><code class="language-python">import math
print(math.pi)         # 3.141592…
print(math.sqrt(16))   # 4.0

import random
print(random.randint(1, 10))  # 1~10 사이 임의의 정수</code></pre>
<hr>
<h3 id="외부-모듈">외부 모듈</h3>
<ul>
<li><code>pip</code>을 사용해 설치</li>
</ul>
<p><code>pip install 모듈이름</code></p>
<p>예:</p>
<p><code>pip install requests</code></p>
<p>사용 예:</p>
<pre><code class="language-python">import requests
res = requests.get(“https://example.com”)
print(res.status_code)</code></pre>
<h2 id="패키지-package">패키지 (Package)</h2>
<p>패키지는 <strong>모듈을 계층적으로 관리</strong>할 수 있게 해주는 <strong>디렉터리 구조</strong>다.<br>패키지란, <strong>여러 모듈을 담고 있는 폴더</strong>이다.</p>
<hr>
<h3 id="패키지-만들기">패키지 만들기</h3>
<p>다음과 같은 디렉터리 구조를 만든다.</p>
<pre><code>game/
├── init.py
├── sound/
│   ├── init.py
│   ├── echo.py
├── graphic/
│   ├── init.py
│   ├── render.py</code></pre><ul>
<li><code>__init__.py</code> 파일은 해당 디렉토리가 <strong>패키지임을 알리는 역할</strong></li>
<li>파이썬 3.3 이상에서는 없어도 되지만, 있는 게 좋다</li>
</ul>
<hr>
<h3 id="모듈-불러오기">모듈 불러오기</h3>
<pre><code class="language-python">import game.sound.echo
game.sound.echo.echo_test()</code></pre>
<p>또는 하위 모듈을 직접 import 할 수도 있다:</p>
<pre><code class="language-python">from game.sound import echo
echo.echo_test()</code></pre>
<p>또는 함수만 직접 import:</p>
<pre><code class="language-python">from game.sound.echo import echo_test
echo_test()</code></pre>
<hr>
<h3 id="상대-경로로-import-하기">상대 경로로 import 하기</h3>
<p><code>from . import echo</code> ← 현재 디렉터리 기준<br><code>from ..sound import echo</code> ← 부모 디렉터리 기준</p>
<blockquote>
<p>상대 경로 import는 <strong>모듈이 패키지 내부에서 실행될 때만 사용 가능</strong><br>직접 실행하면 <code>ImportError</code> 발생할 수 있음</p>
</blockquote>
<hr>
<h3 id="initpy-사용"><strong>init</strong>.py 사용</h3>
<p>패키지 디렉터리에 <code>__init__.py</code> 파일을 만들어<br>패키지를 초기화하거나, 외부에 노출할 모듈을 지정할 수 있다.</p>
<p>예:</p>
<pre><code class="language-python"># game/sound/__init__.py
__all__ = [&#39;echo&#39;]</code></pre>
<p>이렇게 하면 from game.sound import * 시 echo만 import 된다.</p>
<hr>
<p>예외 사항</p>
<p>import * 문법은 <strong>init</strong>.py에서 <strong>all</strong> 리스트로 허용된 것만 가져옴
이 외에는 사용을 권장하지 않음</p>
<hr>
<h3 id="패키지와-모듈-위치-설정">패키지와 모듈 위치 설정</h3>
<ul>
<li>현재 파이썬이 모듈을 찾는 경로는 sys.path에 저장되어 있음</li>
<li>필요하면 경로를 추가해야 함<pre><code class="language-python">import sys
sys.path.append(&quot;/my/custom/path&quot;)</code></pre>
</li>
</ul>
<h2 id="예외-처리-exception-handling">예외 처리 (Exception Handling)</h2>
<p>프로그래밍 중 <strong>오류가 발생할 수 있는 부분</strong>을 처리하여 프로그램이 중단되지 않도록 만드는 방법.</p>
<hr>
<h3 id="기본-구조">기본 구조</h3>
<pre><code class="language-python">try:
    실행할 코드
except 예외종류:
    예외 발생 시 실행할 코드</code></pre>
<p>예시:</p>
<pre><code class="language-python">try:
    4 / 0
except ZeroDivisionError:
    print(&quot;0으로 나눌 수 없습니다.&quot;)</code></pre>
<hr>
<h3 id="여러-개의-except-문">여러 개의 except 문</h3>
<pre><code class="language-python">try:
    a = [1, 2]
    print(a[3])
    4 / 0
except ZeroDivisionError:
    print(&quot;0으로 나눌 수 없습니다.&quot;)
except IndexError:
    print(&quot;인덱스 오류입니다.&quot;)</code></pre>
<p>→ 가장 먼저 발생한 예외만 처리된다.</p>
<hr>
<h3 id="하나의-except에서-여러-예외-처리">하나의 except에서 여러 예외 처리</h3>
<pre><code class="language-python">try:
    a = [1, 2]
    print(a[3])
except (ZeroDivisionError, IndexError):
    print(&quot;오류 발생!&quot;)</code></pre>
<hr>
<h3 id="예외-메시지-확인하기-as-사용">예외 메시지 확인하기 (<code>as</code> 사용)</h3>
<pre><code class="language-python">try:
    4 / 0
except ZeroDivisionError as e:
    print(e)</code></pre>
<p>출력: <code>division by zero</code></p>
<hr>
<h3 id="finally-문">finally 문</h3>
<p>예외 발생 여부와 관계없이 <strong>항상 실행되는 블록</strong></p>
<pre><code class="language-python">try:
    print(&quot;try 실행&quot;)
    1 / 0
except:
    print(&quot;except 실행&quot;)
finally:
    print(&quot;finally 실행&quot;)</code></pre>
<p>→ 출력:  </p>
<pre><code class="language-python">try 실행  
except 실행  
finally 실행</code></pre>
<hr>
<h3 id="예외-발생시키기-raise">예외 발생시키기 (<code>raise</code>)</h3>
<p>직접 예외를 발생시킬 수 있다.</p>
<pre><code class="language-python">def raise_error():
    raise NotImplementedError

raise_error()</code></pre>
<hr>
<h3 id="사용자-정의-예외-만들기">사용자 정의 예외 만들기</h3>
<pre><code class="language-python">class MyError(Exception):
    pass

def say_nick(nick):
    if nick == &quot;바보&quot;:
        raise MyError()
    print(nick)

try:
    say_nick(&quot;바보&quot;)
except MyError:
    print(&quot;사용자 정의 예외 발생!&quot;)</code></pre>
<hr>
<h3 id="예외-메시지-포함하기">예외 메시지 포함하기</h3>
<pre><code class="language-python">class MyError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

try:
    raise MyError(&quot;오류가 발생했습니다.&quot;)
except MyError as e:
    print(e)</code></pre>
<p>출력: <code>오류가 발생했습니다.</code></p>
<hr>
<h3 id="정리">정리</h3>
<ul>
<li><code>try ~ except</code>: 예외 발생 시 처리</li>
<li><code>except e</code>: 예외 메시지 확인</li>
<li><code>finally</code>: 무조건 실행</li>
<li><code>raise</code>: 예외 강제 발생</li>
<li>사용자 정의 예외: <code>Exception</code> 상속</li>
</ul>
<hr>
<h2 id="내장-함수-built-in-functions">내장 함수 (Built-in Functions)</h2>
<p>파이썬은 기본적으로 다양한 <strong>내장 함수</strong>를 제공한다.<br>외부 모듈 없이 언제든지 사용할 수 있다.</p>
<hr>
<h3 id="absx">abs(x)</h3>
<p>절댓값 반환</p>
<pre><code class="language-python">abs(-3)  # 3
abs(3.14)  # 3.14</code></pre>
<hr>
<h3 id="alliterable">all(iterable)</h3>
<p>모든 요소가 참이면 <code>True</code>, 하나라도 거짓이면 <code>False</code></p>
<pre><code class="language-python">all([1, 2, 3])  # True
all([1, 0, 3])  # False</code></pre>
<hr>
<h3 id="anyiterable">any(iterable)</h3>
<p>하나라도 참이면 <code>True</code>, 모두 거짓이면 <code>False</code></p>
<pre><code class="language-python">any([0, &quot;&quot;, None])  # False
any([0, &quot;&quot;, 1])  # True</code></pre>
<hr>
<h3 id="chri">chr(i)</h3>
<p>유니코드 정수 → 문자</p>
<pre><code class="language-python">chr(97)  # &#39;a&#39;</code></pre>
<hr>
<h3 id="dirobject">dir([object])</h3>
<p>객체가 가진 <strong>변수나 함수(속성)</strong> 목록 리턴</p>
<pre><code class="language-python">dir([1, 2, 3])  # list의 메서드 목록 출력</code></pre>
<hr>
<h3 id="divmoda-b">divmod(a, b)</h3>
<p><code>a</code>를 <code>b</code>로 나눈 <strong>몫과 나머지</strong>를 튜플로 반환</p>
<pre><code class="language-python">divmod(7, 3)  # (2, 1)</code></pre>
<hr>
<h3 id="enumerateiterable">enumerate(iterable)</h3>
<p>반복 가능한 자료형을 (index, 요소) 형태로 반환</p>
<pre><code class="language-python">for i, name in enumerate([&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]):
    print(i, name)</code></pre>
<hr>
<h3 id="evalexpression">eval(expression)</h3>
<p>문자열로 된 표현식을 <strong>실행한 결과 반환</strong></p>
<pre><code class="language-python">eval(&quot;1 + 2&quot;)  # 3
eval(&quot;&#39;hi&#39; + &#39;world&#39;&quot;)  # &#39;hiworld&#39;</code></pre>
<hr>
<h3 id="filterfunction-iterable">filter(function, iterable)</h3>
<p>함수 조건을 만족하는 요소만 필터링</p>
<pre><code class="language-python">def is_positive(x):
    return x &gt; 0

list(filter(is_positive, [1, -3, 2, 0, -5]))  # [1, 2]</code></pre>
<hr>
<h3 id="hexx">hex(x)</h3>
<p>정수를 16진수 문자열로 변환</p>
<pre><code class="language-python">hex(255)  # &#39;0xff&#39;</code></pre>
<hr>
<h3 id="idobject">id(object)</h3>
<p>객체의 고유 주소값 반환</p>
<pre><code class="language-python">a = 3
id(a)</code></pre>
<hr>
<h3 id="inputprompt">input([prompt])</h3>
<p>사용자 입력 받기 (문자열로 반환됨)</p>
<pre><code class="language-python">name = input(&quot;이름을 입력하세요: &quot;)</code></pre>
<hr>
<h3 id="intx--base">int(x [, base])</h3>
<p>문자열을 정수로 변환 (진수도 지정 가능)</p>
<pre><code class="language-python">int(&#39;3&#39;)       # 3
int(&#39;11&#39;, 2)   # 3</code></pre>
<hr>
<h3 id="isinstanceobject-class">isinstance(object, class)</h3>
<p>객체가 클래스의 인스턴스인지 검사</p>
<pre><code class="language-python">isinstance(3, int)  # True</code></pre>
<hr>
<h3 id="lens">len(s)</h3>
<p>길이 반환</p>
<pre><code class="language-python">len(&quot;python&quot;)  # 6</code></pre>
<hr>
<h3 id="listiterable">list([iterable])</h3>
<p>리스트로 변환</p>
<pre><code class="language-python">list(&quot;abc&quot;)  # [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]</code></pre>
<hr>
<h3 id="mapfunction-iterable">map(function, iterable)</h3>
<p>함수를 반복 가능한 객체의 요소에 적용</p>
<pre><code class="language-python">def square(x):
    return x * x

list(map(square, [1, 2, 3]))  # [1, 4, 9]</code></pre>
<hr>
<h3 id="maxiterable">max(iterable)</h3>
<p>최댓값 반환</p>
<pre><code class="language-python">max([1, 2, 3])  # 3</code></pre>
<hr>
<h3 id="miniterable">min(iterable)</h3>
<p>최솟값 반환</p>
<pre><code class="language-python">min([1, 2, 3])  # 1</code></pre>
<hr>
<h3 id="openfilename-mode">open(filename, mode)</h3>
<p>파일을 열어 파일 객체 반환</p>
<pre><code class="language-python">f = open(&quot;file.txt&quot;, &#39;r&#39;)</code></pre>
<hr>
<h3 id="ordc">ord(c)</h3>
<p>문자 → 유니코드 정수</p>
<pre><code class="language-python">ord(&#39;a&#39;)  # 97</code></pre>
<hr>
<h3 id="powx-y">pow(x, y)</h3>
<p><code>x ** y</code>와 같음</p>
<pre><code class="language-python">pow(2, 3)  # 8</code></pre>
<hr>
<h3 id="rangestart-stop-step">range([start], stop[, step])</h3>
<p>정수 시퀀스 생성</p>
<pre><code class="language-python">list(range(5))       # [0, 1, 2, 3, 4]
list(range(1, 10, 2))  # [1, 3, 5, 7, 9]</code></pre>
<hr>
<h3 id="roundnumber-ndigits">round(number[, ndigits])</h3>
<p>반올림</p>
<pre><code class="language-python">round(3.14159, 2)  # 3.14</code></pre>
<hr>
<h3 id="sortediterable-key-reverse">sorted(iterable[, key][, reverse])</h3>
<p>정렬된 리스트 반환 (원본 변경 X)</p>
<pre><code class="language-python">sorted([3, 1, 2])  # [1, 2, 3]
sorted(&quot;zero&quot;)     # [&#39;e&#39;, &#39;o&#39;, &#39;r&#39;, &#39;z&#39;]</code></pre>
<hr>
<h3 id="strobject">str(object)</h3>
<p>문자열로 변환</p>
<pre><code class="language-python">str(3)     # &#39;3&#39;
str(3.14)  # &#39;3.14&#39;</code></pre>
<hr>
<h3 id="sumiterable-start">sum(iterable[, start])</h3>
<p>합계 반환</p>
<pre><code class="language-python">sum([1, 2, 3])  # 6</code></pre>
<hr>
<h3 id="tupleiterable">tuple([iterable])</h3>
<p>튜플로 변환</p>
<pre><code class="language-python">tuple([1, 2, 3])  # (1, 2, 3)</code></pre>
<hr>
<h3 id="typeobject">type(object)</h3>
<p>객체의 자료형 반환</p>
<pre><code class="language-python">type(&quot;abc&quot;)  # &lt;class &#39;str&#39;&gt;</code></pre>
<hr>
<h3 id="zipiterables">zip(*iterables)</h3>
<p>여러 iterable을 묶어서 튜플의 리스트로 반환</p>
<pre><code class="language-python">list(zip([1, 2], [&#39;a&#39;, &#39;b&#39;]))  # [(1, &#39;a&#39;), (2, &#39;b&#39;)]</code></pre>
<hr>
<h2 id="표준-라이브러리">표준 라이브러리</h2>
<p>파이썬은 다양한 표준 라이브러리를 제공하며, import만 하면 바로 사용할 수 있다.</p>
<hr>
<h3 id="sys">sys</h3>
<p>시스템 관련 정보를 다루는 모듈</p>
<pre><code class="language-python">import sys

sys.argv        # 명령행 인수
sys.exit()      # 강제 종료
sys.path        # 모듈 검색 경로</code></pre>
<p>예:</p>
<pre><code class="language-python"># hello.py
import sys
print(sys.argv)</code></pre>
<p>명령어:<br><code>python hello.py arg1 arg2</code> → <code>[&#39;hello.py&#39;, &#39;arg1&#39;, &#39;arg2&#39;]</code></p>
<hr>
<h3 id="pickle">pickle</h3>
<p>파이썬 객체를 파일에 저장하거나 불러올 때 사용</p>
<pre><code class="language-python">import pickle

f = open(&quot;test.txt&quot;, &quot;wb&quot;)
data = {1: &#39;python&#39;, 2: &#39;you&#39;}
pickle.dump(data, f)
f.close()

# 불러오기
f = open(&quot;test.txt&quot;, &quot;rb&quot;)
data = pickle.load(f)
print(data)  # {1: &#39;python&#39;, 2: &#39;you&#39;}</code></pre>
<hr>
<h3 id="os">os</h3>
<p>운영체제와 상호작용할 수 있는 기능 제공</p>
<pre><code class="language-python">import os

os.environ        # 환경 변수
os.getcwd()       # 현재 디렉토리
os.chdir(&quot;path&quot;)  # 디렉토리 이동
os.system(&quot;dir&quot;)  # 시스템 명령 실행</code></pre>
<hr>
<h3 id="shutil">shutil</h3>
<p>파일 복사 관련 기능 제공</p>
<pre><code class="language-python">import shutil

shutil.copy(&quot;src.txt&quot;, &quot;dst.txt&quot;)</code></pre>
<hr>
<h3 id="glob">glob</h3>
<p>파일 이름 리스트를 얻을 때 사용 (와일드카드 사용 가능)</p>
<pre><code class="language-python">import glob

glob.glob(&quot;*.py&quot;)  # 현재 폴더의 모든 .py 파일 리스트</code></pre>
<hr>
<h3 id="tempfile">tempfile</h3>
<p>임시 파일과 디렉터리를 쉽게 만들 수 있음</p>
<pre><code class="language-python">import tempfile

f = tempfile.TemporaryFile()
f.write(b&quot;Hello&quot;)
f.seek(0)
print(f.read())
f.close()</code></pre>
<hr>
<h3 id="time">time</h3>
<p>시간 관련 함수 제공</p>
<pre><code class="language-python">import time

time.time()             # 현재 시간 (timestamp)
time.localtime()        # struct_time
time.asctime()          # 보기 좋은 문자열 시간
time.sleep(1)           # 1초 대기</code></pre>
<p>예: 1초 간격으로 5번 출력</p>
<pre><code class="language-python">for i in range(5):
    print(i)
    time.sleep(1)</code></pre>
<hr>
<h3 id="calendar">calendar</h3>
<p>달력 관련 기능 제공</p>
<pre><code class="language-python">import calendar

print(calendar.calendar(2025))
print(calendar.month(2025, 7))
calendar.isleap(2024)  # 윤년 여부</code></pre>
<hr>
<h3 id="random">random</h3>
<p>난수 생성 관련 함수 제공</p>
<pre><code class="language-python">import random

random.random()           # 0~1 사이 임의의 실수
random.randint(1, 10)     # 1~10 사이 정수
random.choice([&#39;a&#39;, &#39;b&#39;]) # 리스트에서 임의 선택
random.shuffle([1, 2, 3]) # 리스트 섞기</code></pre>
<hr>
<h3 id="webbrowser">webbrowser</h3>
<p>웹 브라우저를 열 수 있음</p>
<pre><code class="language-python">import webbrowser

webbrowser.open(&quot;https://www.google.com&quot;)</code></pre>
<h2 id="외부-라이브러리">외부 라이브러리</h2>
<p>파이썬 설치 시 기본으로 제공되지 않는 라이브러리를 외부 라이브러리라고 하며, 사용 전 <code>pip</code> 도구로 설치해야 한다.<br><code>pip</code>는 ‘<strong>핍</strong>’이라고 읽으며, 의존성 있는 패키지도 함께 설치해준다.</p>
<hr>
<h3 id="pip-사용법">pip 사용법</h3>
<ul>
<li><p>설치:  </p>
<pre><code class="language-python">pip install SomePackage</code></pre>
</li>
<li><p>제거:  </p>
<pre><code class="language-python">pip uninstall SomePackage</code></pre>
</li>
<li><p>특정 버전 설치:  </p>
<pre><code class="language-python">pip install SomePackage==1.0.4</code></pre>
</li>
<li><p>최신 버전 설치:  </p>
<pre><code class="language-python">pip install --upgrade SomePackage</code></pre>
</li>
<li><p>설치된 패키지 목록 보기:  </p>
<pre><code class="language-python">pip list</code></pre>
</li>
</ul>
<p>예시 출력:</p>
<pre><code class="language-python">Package         Version
--------------- -------
amqp            2.1.4
celery          3.1.0
requests        2.31.0
...</code></pre>
<hr>
<h3 id="faker">Faker</h3>
<h4 id="설치">설치</h4>
<pre><code class="language-python">pip install Faker</code></pre>
<h4 id="기본-사용">기본 사용</h4>
<pre><code class="language-python">from faker import Faker

fake = Faker()
fake.name()       # &#39;Matthew Estrada&#39; 등
fake.address()    # &#39;충청북도 수원시 잠실6길 ...&#39;</code></pre>
<ul>
<li><p>한국어 데이터 사용:</p>
<pre><code class="language-python">fake = Faker(&#39;ko-KR&#39;)
fake.name()  # &#39;김하은&#39;</code></pre>
</li>
<li><p>테스트용 데이터 30건 생성:</p>
<pre><code class="language-python">test_data = [(fake.name(), fake.address()) for i in range(30)]</code></pre>
</li>
</ul>
<h4 id="주요-메서드">주요 메서드</h4>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>fake.name()</td>
<td>이름</td>
</tr>
<tr>
<td>fake.address()</td>
<td>주소</td>
</tr>
<tr>
<td>fake.postcode()</td>
<td>우편번호</td>
</tr>
<tr>
<td>fake.country()</td>
<td>국가명</td>
</tr>
<tr>
<td>fake.company()</td>
<td>회사명</td>
</tr>
<tr>
<td>fake.job()</td>
<td>직업명</td>
</tr>
<tr>
<td>fake.phone_number()</td>
<td>전화번호</td>
</tr>
<tr>
<td>fake.email()</td>
<td>이메일</td>
</tr>
<tr>
<td>fake.user_name()</td>
<td>사용자명</td>
</tr>
<tr>
<td>fake.pyint(min, max)</td>
<td>임의의 정수</td>
</tr>
<tr>
<td>fake.ipv4_private()</td>
<td>IP 주소</td>
</tr>
<tr>
<td>fake.text()</td>
<td>문장</td>
</tr>
<tr>
<td>fake.color_name()</td>
<td>색상명</td>
</tr>
</tbody></table>
<hr>
<h3 id="sympy">sympy</h3>
<p>수학식 기호화 및 방정식 풀이용 외부 라이브러리</p>
<h4 id="설치-1">설치</h4>
<pre><code class="language-python">pip install sympy</code></pre>
<hr>
<h4 id="예제-일차방정식-풀이">예제: 일차방정식 풀이</h4>
<pre><code class="language-python">from fractions import Fraction
import sympy

x = sympy.symbols(&quot;x&quot;)  # 미지수 x
f = sympy.Eq(x * Fraction(&#39;2/5&#39;), 1760)  # x*(2/5) = 1760

result = sympy.solve(f)  # [4400]
remains = result[0] - 1760

print(f&quot;남은 돈은 {remains}원입니다.&quot;)  # 2640</code></pre>
<ul>
<li><code>sympy.symbols(&quot;x&quot;)</code>: x라는 미지수 기호 생성</li>
<li><code>sympy.Eq(a, b)</code>: a = b 형태 방정식 표현</li>
<li><code>sympy.solve()</code>: 방정식의 해 구하기</li>
<li><code>fractions.Fraction</code>: 유리수 정확히 표현</li>
</ul>
<hr>
<h4 id="예제-이차방정식-풀이">예제: 이차방정식 풀이</h4>
<pre><code class="language-python">import sympy

x = sympy.symbols(&quot;x&quot;)
f = sympy.Eq(x**2, 1)
sympy.solve(f)  # [-1, 1]</code></pre>
<hr>
<h4 id="예제-연립방정식-풀이">예제: 연립방정식 풀이</h4>
<pre><code class="language-python">import sympy

x, y = sympy.symbols(&quot;x y&quot;)
f1 = sympy.Eq(x + y, 10)
f2 = sympy.Eq(x - y, 4)
sympy.solve([f1, f2])  # {x: 7, y: 3}</code></pre>
<ul>
<li>미지수가 2개 이상인 경우 <code>dict</code> 형태로 반환됨</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 3. 점프 투 파이썬 - 파이썬의 입출력]]></title>
            <link>https://velog.io/@chae-jpg/Day-3.-%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%98-%EC%9E%85%EC%B6%9C%EB%A0%A5</link>
            <guid>https://velog.io/@chae-jpg/Day-3.-%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%98-%EC%9E%85%EC%B6%9C%EB%A0%A5</guid>
            <pubDate>Mon, 07 Jul 2025 12:16:26 GMT</pubDate>
            <description><![CDATA[<h2 id="함수">함수</h2>
<h3 id="파이썬-함수의-구조">파이썬 함수의 구조</h3>
<p>파이썬 함수의 구조는 다음과 같다.</p>
<pre><code class="language-python">def 함수_이름(매개변수):
    수행할_문장1
    수행할_문장2
    ...</code></pre>
<p>C, Java와 같은 언어처럼 리턴값과 매개변수의 타입을 명시하지 않아도 된다.</p>
<h3 id="여러-개의-입력값을-받는-함수-만들기">여러 개의 입력값을 받는 함수 만들기</h3>
<p>매개변수 이름 앞에 <code>*</code>를 붙이면 입력값을 모두 모아 튜플로 만들어준다.</p>
<pre><code class="language-python">&gt;&gt;&gt; def add_many(*args): 
...     result = 0 
...     for i in args: 
...         result = result + i   # *args에 입력받은 모든 값을 더한다.
...     return result 
</code></pre>
<p>*가 붙은 매개변수만 함수의 인자로 사용할 수 있는 것은 아니다. 다음과 같은 형태로도 사용할 수 있다.</p>
<pre><code class="language-python">&gt;&gt;&gt; def add_mul(choice, *args): 
...     if choice == &quot;add&quot;:   # 매개변수 choice에 &quot;add&quot;를 입력받았을 때
...         result = 0 
...         for i in args: 
...             result = result + i 
...     elif choice == &quot;mul&quot;:   # 매개변수 choice에 &quot;mul&quot;을 입력받았을 때
...         result = 1 
...         for i in args: 
...             result = result * i 
...     return result </code></pre>
<h3 id="키워드-매개변수-kwargs">키워드 매개변수, kwargs</h3>
<p>키워드 매개변수에는 이름 앞에 별 2개를 붙인다.
키워드 매개변수는 함수 내에서 딕셔너리가 되고, <code>Key=Value</code> 형태의 입력값이 딕셔너리에 저장된다.</p>
<pre><code class="language-python">&gt;&gt;&gt; def print_kwargs(**kwargs):
...     print(kwargs)


&gt;&gt;&gt; print_kwargs(a=1)
{&#39;a&#39;: 1}
&gt;&gt;&gt; print_kwargs(name=&#39;foo&#39;, age=3)
{&#39;age&#39;: 3, &#39;name&#39;: &#39;foo&#39;}
</code></pre>
<h3 id="매개변수에-초깃값-미리-설정하기">매개변수에 초깃값 미리 설정하기</h3>
<p>아래와 같이 매개변수 자체에 초깃값을 설정해 함수 호출 시 값을 전달하지 않고도 기존에 저장된 값을 불러오도록 할 수 있다.</p>
<pre><code class="language-python">def hello(name, age, man=True):
    print(&quot;f&#39;이름 : {name}, 나이 : {age}, 사람 여부 : {man}&#39;&quot;)

# 둘 모두 같은 결과를 출력
hello(&quot;이채원&quot;, 24)
hello(&quot;이채원&quot;, 24, True)</code></pre>
<h3 id="lambda-예약어">lambda 예약어</h3>
<p>def와 같은 역할을 하지만, def를 사용해야 할 정도로 복잡하지 않거나 def를 사용할 수 없는 곳에 사용한다.</p>
<p>다음과 같은 형태를 갖는다.</p>
<pre><code class="language-python">함수_이름 = lambda 매개변수1, 매개변수2, ... : 매개변수를_이용한_표현식

# 예
&gt;&gt;&gt; add = lambda a, b: a+b
&gt;&gt;&gt; result = add(3, 4)
&gt;&gt;&gt; print(result)
7</code></pre>
<h2 id="사용자-입출력">사용자 입출력</h2>
<h3 id="input-사용하기">input 사용하기</h3>
<p>input 함수를 사용하면 사용자가 키보드로 입력한 모든 것을 문자열로 저장할 수 있다.
프롬프트를 띄우고 싶을 경우에는 원하는 문자열을 매개변수로 넘겨준다.</p>
<pre><code class="language-python">&gt;&gt;&gt; number = input(&quot;숫자를 입력하세요: &quot;)
숫자를 입력하세요:</code></pre>
<h2 id="파일-읽고-쓰기">파일 읽고 쓰기</h2>
<h3 id="open-함수">open 함수</h3>
<p>아래의 방법으로 open 함수를 사용해 여러 모드로 파일을 열 수 있다.</p>
<pre><code class="language-python">파일_객체 = open(파일_이름, 파일_열기_모드)</code></pre>
<p>파일 열기 모드는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>파일 열기 모드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>r</td>
<td>읽기 모드 : 파일을 읽기만 할 때 사용</td>
</tr>
<tr>
<td>w</td>
<td>쓰기 모드 : 파일에 내용을 쓸 때 사용</td>
</tr>
<tr>
<td>a</td>
<td>추가 모드 : 파일의 마지막에 새로운 내용을 추가할 때 사용</td>
</tr>
</tbody></table>
<h3 id="파일-쓰기">파일 쓰기</h3>
<p>파일을 쓰기 위해서는 <code>write</code> 함수를 사용한다. 
예제는 다음과 같다.</p>
<pre><code class="language-python"># write_data.py
f = open(&quot;C:/doit/새파일.txt&quot;, &#39;w&#39;)
for i in range(1, 11):
    data = &quot;%d번째 줄입니다.\n&quot; % i
    f.write(data)
f.close()</code></pre>
<p>위 예제는 파일을 읽어올 때 <code>w</code> 모드로 읽어왔다.
위 방법으로 파일을 읽을 시에는 <strong>기존 파일의 내용이 전부 사라진다.</strong>
하지만 <code>a</code> 모드로 불러올 시, 기존 파일의 내용은 유지하면서 뒤에 덧붙여 새로운 내용을 쓸 수 있다.</p>
<h3 id="파일을-읽는-여러-가지-방법">파일을 읽는 여러 가지 방법</h3>
<h4 id="readline-함수-사용하기">readline 함수 사용하기</h4>
<p><code>readline</code> 함수는 파일의 내용을 <strong>한 줄씩</strong> 읽는 함수이다. 
다음과 같은 방법으로 파일의 모든 내용을 읽고 출력하는 프로그램을 작성할 수 있다.</p>
<pre><code class="language-python"># readline_all.py
f = open(&quot;C:/doit/새파일.txt&quot;, &#39;r&#39;)
while True:
    line = f.readline()
    if not line: break
    print(line)
f.close()</code></pre>
<h4 id="readlines-함수-사용하기">readlines 함수 사용하기</h4>
<p><code>readlines</code>는 파일의 <strong>모든 줄</strong>을 읽어 각각의 줄을 요소로 가지는 리스트를 리턴한다.</p>
<pre><code class="language-python"># readlines.py
f = open(&quot;C:/doit/새파일.txt&quot;, &#39;r&#39;)
lines = f.readlines()
for line in lines:
    print(line)
f.close()</code></pre>
<h4 id="read-함수-사용하기">read 함수 사용하기</h4>
<p><code>read</code>는 파일의 내용 전체를 문자열로 리턴한다.</p>
<pre><code class="language-python"># read.py
f = open(&quot;C:/doit/새파일.txt&quot;, &#39;r&#39;)
data = f.read()
print(data)
f.close()</code></pre>
<h4 id="파일-객체를-for문과-함께-사용하기">파일 객체를 for문과 함께 사용하기</h4>
<p>파일 객체와 for을 함께 사용하면 파일을 줄 단위로 읽을 수 있다.</p>
<pre><code class="language-python"># read_for.py
f = open(&quot;C:/doit/새파일.txt&quot;, &#39;r&#39;)
for line in f:
    print(line)
f.close()</code></pre>
<h3 id="with문과-함께-사용하기">with문과 함께 사용하기</h3>
<p>위의 예제에서는 파일을 연 뒤(<code>open()</code>) 닫는 과정(<code>f.close()</code>)를 반복했다. 
파일을 닫을 필요 없이 연 뒤 모든 코드가 끝나면 자동으로 닫히게 할 수 있는 방법이 있다. with문을 사용하면 가능하다. 예제는 아래와 같다.</p>
<pre><code class="language-python">with open(&quot;foo.txt&quot;, &quot;w&quot;) as f:
    f.write(&quot;Life is too short, you need python&quot;)</code></pre>
<h2 id="프로그램의-입출력">프로그램의 입출력</h2>
<h3 id="sys-모듈-사용하기">sys 모듈 사용하기</h3>
<p><code>sys</code> 모듈을 사용하면 프로그램을 실행할 때 인수를 전달하여 해당 인수를 불러오는 것이 가능하다. </p>
<pre><code class="language-python"># sys2.py
import sys
args = sys.argv[1:]
for i in args:
    print(i.upper(), end=&#39; &#39;)</code></pre>
<p>위 예시는 프롬프트 상에서 프로그램이 실행될 때 받았던 인수를 넘겨받은 뒤 해당 인수를 모두 대문자로 변환하여 한 줄에 출력하는 코드이다.</p>
<p>여기서 <code>sys.argv[1:]</code>은 함수 이름을 제외한 모든 인수이다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/340c4023-2134-414f-999b-09d6cfb10209/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 2. 점프 투 파이썬 - 조건문]]></title>
            <link>https://velog.io/@chae-jpg/Day-2.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A1%B0%EA%B1%B4%EB%AC%B8</link>
            <guid>https://velog.io/@chae-jpg/Day-2.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A1%B0%EA%B1%B4%EB%AC%B8</guid>
            <pubDate>Fri, 04 Jul 2025 03:09:10 GMT</pubDate>
            <description><![CDATA[<h2 id="조건문-if-문">조건문 (if 문)</h2>
<p><code>if</code> 문은 조건에 따라 실행 흐름을 제어하는 구문이다.</p>
<h3 id="기본-구조">기본 구조</h3>
<p>if 조건문:
실행할 문장1
실행할 문장2</p>
<ul>
<li><code>조건문</code>이 참(<code>True</code>)이면 들여쓴 문장들이 실행됨</li>
<li>들여쓰기는 <strong>공백 4칸</strong> 또는 <strong>Tab</strong>으로 통일해야 함</li>
</ul>
<h3 id="예시">예시</h3>
<p>money = True
if money:
print(“택시를 타고 가라”)  # 출력됨</p>
<h3 id="if--else-문">if ~ else 문</h3>
<p><code>else</code>는 조건이 <strong>거짓(False)</strong> 일 때 실행된다.</p>
<pre><code>money = False
if money:
print(“택시를 타고 가라”)
else:
print(“걸어가라”)  # 출력됨</code></pre><h3 id="if--elif--else-문">if ~ elif ~ else 문</h3>
<p>여러 조건 중 하나를 선택할 수 있다.</p>
<pre><code>money = 2000
card = True

if money &gt;= 3000:
print(“택시를 타고 가라”)
elif card:
print(“카드가 있으니 택시를 타고 가라”)  # 출력됨
else:
print(“걸어가라”)</code></pre><ul>
<li>조건을 <strong>위에서부터 차례로 검사</strong>하며,</li>
<li>첫 번째로 참인 조건이 실행되고, 나머지는 무시됨</li>
</ul>
<h3 id="조건에-사용되는-연산자">조건에 사용되는 연산자</h3>
<ul>
<li><strong>비교 연산자</strong> : <code>==</code>, <code>!=</code>, <code>&gt;</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&lt;=</code></li>
<li><strong>논리 연산자</strong> : <code>and</code>, <code>or</code>, <code>not</code></li>
</ul>
<p>예시:</p>
<pre><code>x = 3
y = 5

if x &lt; y and x != 0:
print(“조건 만족”)  # 출력됨</code></pre><h3 id="조건문에-in-not-in-사용">조건문에 <code>in</code>, <code>not in</code> 사용</h3>
<pre><code>pocket = [‘paper’, ‘cellphone’, ‘money’]

if ‘money’ in pocket:
print(“택시를 타고 가라”)
else:
print(“걸어가라”)



if ‘card’ not in pocket:
print(“카드가 없다”)</code></pre><h3 id="pass-키워드">pass 키워드</h3>
<p><code>if</code> 문에서 조건은 참이지만, <strong>아직 구현할 내용이 없을 때</strong> 사용한다.</p>
<pre><code>pocket = [‘paper’, ‘money’, ‘cellphone’]

if ‘money’ in pocket:
pass  # 아무것도 하지 않음
else:
print(“카드를 꺼내라”)</code></pre><h3 id="조건부-표현식">조건부 표현식</h3>
<p>한 줄로 조건문을 작성할 수 있다.</p>
<p>‘참일 때의 값’ if 조건 else ‘거짓일 때의 값’</p>
<p>예시:</p>
<pre><code>score = 70
message = “success” if score &gt;= 60 else “failure”
print(message)  # 출력: success</code></pre><p>좋아! 이번엔 위키독스 21번 문서 내용을 바탕으로,
while 문을 빠짐없이 정리한 마크다운 코드 블록이야. 그대로 복붙해서 사용하면 돼:</p>
<h2 id="반복문-while-문">반복문 (while 문)</h2>
<p><code>while</code> 문은 <strong>조건이 참인 동안</strong> 블록을 계속 반복 실행한다.</p>
<h3 id="기본-구조-1">기본 구조</h3>
<p>while 조건문:
반복 실행할 문장</p>
<p>조건문이 <code>True</code>인 동안 반복된다. 조건이 <code>False</code>가 되면 종료된다.</p>
<h3 id="예시-1부터-10까지-출력">예시: 1부터 10까지 출력</h3>
<pre><code>i = 1
while i &lt;= 10:
print(i)
i = i + 1</code></pre><h3 id="while-문에서-break-사용">while 문에서 break 사용</h3>
<p><code>break</code> 문은 반복문을 <strong>강제로 종료</strong>할 때 사용된다.</p>
<pre><code>coffee = 10
money = 300

while money:
print(“돈을 받았으니 커피를 줍니다.”)
coffee -= 1
print(f”남은 커피는 {coffee}개입니다.”)
if coffee == 0:
print(“커피가 다 떨어졌습니다. 판매를 중지합니다.”)
break</code></pre><h3 id="while-문에서-continue-사용">while 문에서 continue 사용</h3>
<p><code>continue</code> 문은 <strong>남은 문장을 건너뛰고 조건 검사로 바로 넘어감</strong>.</p>
<pre><code>a = 0
while a &lt; 10:
a += 1
if a % 2 == 0:
continue
print(a)  # 홀수만 출력됨</code></pre><h3 id="무한-루프-만들기">무한 루프 만들기</h3>
<p><code>while True</code>를 사용하면 무한 루프를 만들 수 있다. 반드시 <code>break</code> 등으로 종료 조건을 만들어야 함.</p>
<pre><code>while True:
user_input = input(“종료하려면 q를 입력하세요: “)
if user_input == ‘q’:
break</code></pre><h3 id="실습-예시-사용자에게-숫자를-입력받아-합계-출력">실습 예시: 사용자에게 숫자를 입력받아 합계 출력</h3>
<pre><code>total = 0
i = 1

while i &lt;= 1000:
if i % 3 == 0:
total += i
i += 1

print(total)  # 1부터 1000까지 3의 배수의 합</code></pre><h3 id="실습-예시-별-출력">실습 예시: 별 출력</h3>
<pre><code>i = 0
while i &lt; 5:
i += 1
print(’*’ * i)

# 출력

*

**

***

****

*****</code></pre><hr>
<h2 id="반복문-for-문">반복문 (for 문)</h2>
<p><code>for</code> 문은 <strong>시퀀스(리스트, 튜플, 문자열 등)</strong> 안의 요소를 하나씩 꺼내면서 반복 실행한다.</p>
<h3 id="기본-구조-2">기본 구조</h3>
<pre><code>for 변수 in 시퀀스:
실행할 문장</code></pre><h3 id="예시-리스트-요소-출력">예시: 리스트 요소 출력</h3>
<pre><code>test_list = [‘one’, ‘two’, ‘three’]
for i in test_list:
print(i)</code></pre><h3 id="예시-튜플을-이용한-for-문">예시: 튜플을 이용한 for 문</h3>
<pre><code>a = [(1, 2), (3, 4), (5, 6)]
for (first, last) in a:
print(first + last)</code></pre><h3 id="for-문과-range-함수">for 문과 range 함수</h3>
<p><code>range(n)</code>은 0부터 n-1까지의 숫자 시퀀스를 만든다.</p>
<pre><code>for i in range(5):
print(i)  # 0, 1, 2, 3, 4</code></pre><ul>
<li><code>range(start, end)</code> : start부터 end-1까지</li>
<li><code>range(start, end, step)</code> : step만큼 증가하며 반복<pre><code>for i in range(1, 10, 2):
print(i)  # 1, 3, 5, 7, 9</code></pre></li>
</ul>
<h3 id="예시-1부터-10까지-합-구하기">예시: 1부터 10까지 합 구하기</h3>
<pre><code>result = 0
for i in range(1, 11):
result += i

print(result)  # 55</code></pre><h3 id="for-문과-len-range-함께-사용">for 문과 len(), range() 함께 사용</h3>
<p>리스트의 인덱스로 접근할 때 사용</p>
<pre><code>marks = [90, 25, 67, 45, 80]

for i in range(len(marks)):
if marks[i] &gt;= 60:
print(f”{i+1}번 학생은 합격입니다.”)
else:
print(f”{i+1}번 학생은 불합격입니다.”)</code></pre><h3 id="continue-문">continue 문</h3>
<p>조건에 맞지 않는 경우 실행을 건너뛴다.</p>
<pre><code>marks = [90, 25, 67, 45, 80]

for i in range(len(marks)):
if marks[i] &lt; 60:
continue
print(f”{i+1}번 학생 축하합니다. 합격입니다.”)</code></pre><h3 id="for-문과-리스트-컴프리헨션">for 문과 리스트 컴프리헨션</h3>
<p>리스트를 간결하게 생성하는 방법</p>
<pre><code>a = [1, 2, 3, 4]
result = [num * 3 for num in a]
print(result)  # [3, 6, 9, 12]</code></pre><p>조건문도 함께 사용할 수 있다.</p>
<pre><code>result = [num * 3 for num in a if num % 2 == 0]
print(result)  # [6, 12]</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Day 1. 파이썬 문제 풀이]]></title>
            <link>https://velog.io/@chae-jpg/Day-1.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</link>
            <guid>https://velog.io/@chae-jpg/Day-1.-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</guid>
            <pubDate>Wed, 02 Jul 2025 12:43:55 GMT</pubDate>
            <description><![CDATA[<p>크게 어려운 문제를 풀려고 시작한 건 아니고... 문제를 많이 풀어보면서 파이썬 문법과 친해지려는 목적이 가장 크다.</p>
<p>그래서 문제도 알고리즘보다는 단순 구현에 집중된 문제를 주로 보려고 하고, 이 글에서도 파이썬의 어떤 요소를 사용해서 문제를 해결했는지를 설명하고자 한다.</p>
<p>그러면 시작</p>
<hr>
<h2 id="1920---수-찾기">1920 - 수 찾기</h2>
<p><a href="https://www.acmicpc.net/problem/1920">수 찾기</a></p>
<p>뭐 다양한 방법이 있을 수 있을 것 같은 매우 단순한 문제인데...
일단 브루트포스는 안 통한다. 대충 계산해봐도 백 억 번 연산을 해야 하기도 하고 무엇보다...
<strong>전에 C로 브루트포싱 했다가 시간초과났다. ㅋ</strong></p>
<p>그러면 바로 떠올릴 수 있는 건 크게 두 가지다.</p>
<ol>
<li>Set</li>
<li>Binary Search</li>
</ol>
<p>뭔가 알고리즘을 파고 싶으면 당연히 2를 고르겠지만 위에도 말했듯이 파이썬의 여러 문법에 적응하고자 하는 취지이기 때문에 1로 가보겠다. ㅎㅎ</p>
<pre><code class="language-python">n = input()
a = set(map(int, input().split()))

m = input()
b = map(int, input().split())

for i in b:
    if i in a :
        print(1)
    else:
        print(0)</code></pre>
<p>여기서 볼 건 크게 두 가지이다.</p>
<h3 id="1-input-방법">1. input 방법</h3>
<p>파이썬에서 한 줄을 통째로 input받고 이를 쪼개려면 split을 해야 한다.
그런데, split 함수를 사용하면 리스트에 들어가는 값들이 모두 문자열이 된다는 문제가 존재한다.</p>
<p>따라서 int 함수를 사용해서 리스트 안의 모든 원소들을 정수형으로 바꿔줘야 하는데, 이 과정에서 배열 전체에 함수를 적용할 수 있도록 하는 함수가 바로 map이다. 따라서 <code>map(int, input().split())</code>이라는 파이썬으로 백준을 풀어본 사람이라면 모두 본 적 있을 코드가 완성된다...ㅎ</p>
<p>거기에 한 술 더 떠서 a를 input 받을 때에는 map을 set 함수로 감싸주었다. set 함수는 list를 set으로 변환해주기 때문에, 원래 리스트였던 결과가 set으로 바뀌게 된다!</p>
<p>이걸 보니 자바스크립트 하면서 함수형 프로그래밍 어쩌구,, 때문에 너무나도 고통받았던 시기가 문득 떠오른다...^^ 요즘 언어들은 참... 외울 게 많은 것 같다.</p>
<h3 id="2-in-문법">2. in 문법</h3>
<p>점프 투 파이썬은 아무래도 일반인들을 주로 대상으로 한 책이기 때문에 좀... 빠진 내용이 많은 것 같다. <del>이 책 말고 딴 걸로 할 걸 그랬나 후회 중이다.</del></p>
<p>c++을 예시로 들자면, 거기서는 set의 원소를 찾는 데 find를 사용한다. 하지만! 파이썬은 in 연산자가 기본적으로 제공된다. 해당하는 원소가 그 컨테이너에 존재하면 True, 아니면 False를 리턴하는 아주 편리한 연산자이다.</p>
<p>따라서 그냥 바로 이렇게 찾아주면 된다.</p>
<pre><code>    if i in a :
        print(1)
    else:
        print(0)</code></pre><p>그래서 사실 꼭 여기서 set 안 써도 될 것 같고...? 근데 set 쓰는 게 더 빠를 것 같기도 하고 모르겠다. 여기 set은 어떻게 구현된 건지까지는 몰라서... 그 background를 봐야 알 듯</p>
<p>따라서 전체 코드는 다음과 같다.</p>
<pre><code>n = input()
a = set(map(int, input().split()))

m = input()
b = map(int, input().split())

for i in b:
    if i in a :
        print(1)
    else:
        print(0)</code></pre><hr>
<h2 id="2822---점수-계산">2822 - 점수 계산</h2>
<p><a href="https://www.acmicpc.net/problem/2822">점수 계산</a></p>
<p>얘는 소트다. 뭐... 간단하죠?
거기에 2차원 배열을 쓰까봤다.</p>
<p>이 문제를 처음 보고 c++이면 어떻게 풀었을까? 생각해봤다. 사실 비슷한 문제를 풀었었는데, 그 때 값과 인덱스를 pair로 묶어서 저장하고 리스트에 넣었던 기억이 떠올랐다.</p>
<p>그래서 똑같이 구현했는데, 대신 python은 pair 같은 자료형이 있을 필요 없이 그냥 리스트의 요소를 자유롭게 할 수 있기 때문에 리스트 안에 리스트를 넣는 식으로 해서 구현했다! 이런 점에서는 확실히 파이썬이 편하다.</p>
<p>거기에 정렬을 몇 번 했는데, 이건 별 거 없다. 알아둘 점은 크게 두 개다.</p>
<ol>
<li>sort 함수 사용법
list의 method로 sort 함수가 존재한다. </li>
</ol>
<p><strong>주의</strong>
리턴으로 None이 나온다. 이게 뭔 소리냐면, <strong>정렬 된 배열</strong>을 리턴하는 함수가 아니다!</p>
<pre><code>list = list.sort()</code></pre><p>이렇게 되면 list에 그냥 None이 대입돼 버린다.</p>
<pre><code>list.sort()</code></pre><p>이렇게 하면 별도의 대입 없이 바로 list가 정렬된 상태로 저장된다.</p>
<p>만약 역방향으로 정렬을 하고 싶다면, 다음과 같이 할 수 있다.</p>
<pre><code>list.sort(reverse=True)</code></pre><p>정렬의 경우, sorted 함수를 이용해서 다양한 요소를 기준으로 해서 정렬할 수도 있고, 아니면 아예 lambda를 가지고 완전 마음대로 커스텀도 가능하다. 이런 점 관련해서는 나중에 정리를 해... 보도록... 하겠다...^^ (언제가 될지는 모름)</p>
<p>그리고 사실 예상하기는 했는데, 이 경우처럼 2차원 배열을 딱히 아무 기준도 주지 않고 정렬하면 <strong>맨 앞 요소</strong>를 기준으로 해서 정렬된다! 참고할 것.</p>
<p>완성된 코드는 다음과 같다.</p>
<pre><code>l = []
for i in range(8):
    t = int(input())
    l.append([t, i+1])

l.sort(reverse=True)

s = 0
ans = []
for i in range(5):
    s += l[i][0]
    ans.append(l[i][1])

print(s)
ans.sort()
for i in ans:
    print(i, end=&#39; &#39;)
</code></pre><hr>
<p>소감은... 음
확실히 사람들이 코테 입문으로 파이썬을 많이 쓰는 이유를 알 것 같다. 정말 직관적이다. 그래서 코드짜기 편하다. 물론 적응이 되어야 그런 거긴 하다. 사실 난 아직도 c++이 조금 더 편한 것 같기는 하다. ㅋㅋㅋㅋㅜㅜㅜ 뭔가 c++ 뇌에서 파이썬으로 번역하는 듯한 느낌이 없지 않아 있다...</p>
<p>뭐 연습하다 보면 적응할 거라고 믿는다. 그러면 조금 더 어려운 문제도 풀 수 있겠지?</p>
<p>다음은 컨테이너 내장 함수 관련된 문제를 조금 더 보고 싶다. 빡센 정렬 문제도 좋고... 그렇게 해서 좀 적응되었다 싶으면 바로 골드 풀이로 가도록 하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 1. 점프 투 파이썬 - 자료형]]></title>
            <link>https://velog.io/@chae-jpg/Day-1.-%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%ED%98%95</link>
            <guid>https://velog.io/@chae-jpg/Day-1.-%EC%A0%90%ED%94%84-%ED%88%AC-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%ED%98%95</guid>
            <pubDate>Wed, 02 Jul 2025 12:25:30 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>파이썬에는 다음과 같은 자료형이 존재한다.</p>
<ul>
<li>숫자형</li>
<li>문자열</li>
<li>리스트</li>
<li>튜플</li>
<li>딕셔너리</li>
<li>집합</li>
<li>불</li>
</ul>
<p>여기서는 각각의 자료형에 대해서 설명한다.</p>
<hr>
<h2 id="숫자형">숫자형</h2>
<p>숫자형에는 다음과 같은 종류가 존재한다.</p>
<ul>
<li>정수형</li>
<li>실수형 : 다음과 같은 표현이 가능하다.<pre><code>&gt;&gt;&gt; a = 4.24E10
&gt;&gt;&gt; a = 4.24e-10</code></pre></li>
<li>8진수와 16진수 : 각각 다음과 같이 표현한다.<pre><code># 8진수
&gt;&gt;&gt; a = 0o177
&gt;&gt;&gt; print(a)
127
</code></pre></li>
</ul>
<h1 id="16진수">16진수</h1>
<blockquote>
<blockquote>
<blockquote>
<p>a = 0x8ff
b = 0xABC
print(b)
2748</p>
</blockquote>
</blockquote>
</blockquote>
<pre><code>
이러한 숫자형 자료형을 연산자를 통해 연산 가능하다. 연산자는 **산술 연산자**와 **복합 연산자**로 나뉜다. 

- 산술 연산자 : `+, -, *, /, ** (제곱), %, // (몫)`
- 복합 연산자 : `+=, -=, *=, /=, //=, %=, **=`

## 문자열

문자열은 `&#39;`, `&quot;`, `&#39;&#39;&#39;`, `&quot;&quot;&quot;`로 표현할 수 있다.
</code></pre><h1 id="한-줄-문자열">한 줄 문자열</h1>
<p>a = ‘Hello’
b = “Python”</p>
<h1 id="여러-줄-문자열">여러 줄 문자열</h1>
<p>c = ‘’‘여러 줄 문자열’’’
d = “”“여러 줄 문자열”””</p>
<pre><code>
이러한 문자열은 연산자를 통해 연산 가능하다. 연산자는 **연결/반복 연산자**와 **포매팅 방법**으로 나뉜다.

- 연결 연산자 : `+` (문자열 이어 붙이기)
- 반복 연산자 : `*` (문자열 반복)
- 문자열 길이 구하기 : `len(문자열)`
- 문자열 인덱싱 : `&quot;Python&quot;[0]` → `&#39;P&#39;`, `&quot;Python&quot;[-1]` → `&#39;n&#39;`
- 문자열 슬라이싱 : `&quot;Life is too short&quot;[0:4]` → `&#39;Life&#39;`

문자열 포매팅에는 다음과 같은 방법이 있다.

- % 포매팅
</code></pre><p>“I eat %d apples.” % 3</p>
<pre><code>
- format() 메서드
</code></pre><p>“I eat {} apples”.format(3)
“I ate {0} apples and {1} oranges”.format(3, 5)</p>
<pre><code>
- f-string (Python 3.6+)
</code></pre><p>name = “철수”
age = 20
f”My name is {name} and I am {age} years old.”</p>
<pre><code>
문자열 관련 함수는 다음과 같다.
</code></pre><p>“hobby”.count(‘b’)       # 2
“hobby”.find(‘b’)        # 2
“hobby”.index(‘b’)       # 2
“,”.join([‘a’,‘b’,‘c’])  # ‘a,b,c’
“hi”.upper()             # ‘HI’
“HI”.lower()             # ‘hi’
“   hi   “.strip()       # ‘hi’
“Life is short”.replace(“short”, “long”)  # ‘Life is long’
“a:b:c:d”.split(’:’)     # [‘a’, ‘b’, ‘c’, ‘d’]</p>
<pre><code>
## 리스트

리스트는 대괄호 `[]`로 감싸서 표현한다.
</code></pre><p>a = [1, 2, 3]
b = [‘a’, ‘b’, ‘c’]
c = [1, 2, [‘a’, ‘b’]]  # 리스트 안에 리스트</p>
<pre><code>
리스트는 다양한 연산과 메서드를 사용할 수 있다.

- 인덱싱 : `a[0]` → `1`, `c[2][0]` → `&#39;a&#39;`
- 슬라이싱 : `a[1:]` → `[2, 3]`
- 덧셈 : `[1, 2] + [3, 4]` → `[1, 2, 3, 4]`
- 반복 : `[1, 2] * 2` → `[1, 2, 1, 2]`
- 길이 구하기 : `len(a)` → `3`

리스트 값 수정/삭제도 가능하다.
</code></pre><p>a = [1, 2, 3]
a[2] = 4          # [1, 2, 4]
del a[1]          # [1, 4]</p>
<pre><code>
리스트 관련 함수는 다음과 같다.
</code></pre><p>a = [1, 3, 2]</p>
<p>a.append(4)       # [1, 3, 2, 4]
a.sort()          # [1, 2, 3, 4]
a.reverse()       # [4, 3, 2, 1]
a.index(3)        # 1
a.insert(1, 100)  # [4, 100, 3, 2, 1]
a.remove(100)     # [4, 3, 2, 1]
a.pop()           # [4, 3, 2]
a.count(2)        # 1
a.extend([5, 6])  # [4, 3, 2, 5, 6]</p>
<pre><code>
## 튜플

튜플은 리스트와 유사하지만 **수정, 삭제가 불가능한 자료형**이다. 소괄호 `()`를 사용한다.
</code></pre><p>t1 = (1, 2, 3)
t2 = (‘a’, ‘b’, ‘c’)
t3 = (1, 2, (‘a’, ‘b’))</p>
<pre><code>
요소가 1개인 튜플은 쉼표를 반드시 붙여야 한다.
</code></pre><p>t4 = (1,)     # 올바른 튜플
t5 = (1)      # 그냥 정수형</p>
<pre><code>
튜플도 인덱싱, 슬라이싱, 연산이 가능하다.

- 인덱싱 : `t1[0]` → `1`
- 슬라이싱 : `t1[1:]` → `(2, 3)`
- 덧셈 : `t1 + t2` → `(1, 2, 3, &#39;a&#39;, &#39;b&#39;, &#39;c&#39;)`
- 반복 : `t2 * 2` → `(&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;a&#39;, &#39;b&#39;, &#39;c&#39;)`
- 길이 구하기 : `len(t1)` → `3`

하지만, 요솟값을 변경할 수 없기 때문에 sort, insert, remove, pop과 같은 내장 함수는 존재하지 않는다.

## 딕셔너리

딕셔너리는 **키(key)와 값(value)** 쌍으로 데이터를 저장하는 자료형이다. 중괄호 `{}`를 사용하며, 각 쌍은 `:`로 구분한다.
</code></pre><p>a = { ‘name’: ‘철수’, ‘age’: 20 }
b = { 1: ‘one’, 2: ‘two’ }
c = { ‘a’: [1, 2, 3] }</p>
<pre><code>
### 딕셔너리 사용 방법

- 값 참조 : `a[&#39;name&#39;]` → `&#39;철수&#39;`
- 새로운 항목 추가 : `a[&#39;school&#39;] = &#39;ABC&#39;`
- 기존 값 수정 : `a[&#39;age&#39;] = 21`
- 항목 삭제 : `del a[&#39;name&#39;]`

### 딕셔너리 관련 함수
</code></pre><p>a = { ‘name’: ‘철수’, ‘age’: 20, ‘school’: ‘ABC’ }</p>
<p>a.keys()        # dict_keys([‘name’, ‘age’, ‘school’])
a.values()      # dict_values([‘철수’, 20, ‘ABC’])
a.items()       # dict_items([(‘name’, ‘철수’), (‘age’, 20), (‘school’, ‘ABC’)])
a.get(‘name’)   # ‘철수’
a.get(‘height’) # None (오류 발생 안 함)
‘name’ in a     # True
‘height’ in a   # False</p>
<p>a.clear()       # 모든 항목 삭제</p>
<pre><code>
※ `get()`은 존재하지 않는 키에 접근해도 오류가 나지 않음. 기본값을 줄 수도 있음:  
`a.get(&#39;height&#39;, &#39;없음&#39;)` → `&#39;없음&#39;`

## 집합 (Set)

집합은 **중복을 허용하지 않고**, **순서가 없는** 자료형이다. `set()` 함수나 중괄호 `{}`를 사용해 만든다.
</code></pre><p>s1 = set([1, 2, 3])
s2 = {1, 2, 3}
s3 = set(“hello”)  # 중복 제거됨 → {‘e’, ‘h’, ‘l’, ‘o’}</p>
<pre><code>
### 집합의 특징

- 중복 자동 제거
- 순서가 없어 인덱싱 불가 (리스트나 튜플로 변환 필요)

### 집합 연산
</code></pre><p>s1 = set([1, 2, 3, 4])
s2 = set([3, 4, 5, 6])</p>
<p>s1 &amp; s2    # 교집합 → {3, 4}
s1 | s2    # 합집합 → {1, 2, 3, 4, 5, 6}
s1 - s2    # 차집합 → {1, 2}
s1 ^ s2    # 대칭차집합 → {1, 2, 5, 6}</p>
<pre><code>
### 집합 관련 함수
</code></pre><p>s = set([1, 2, 3])
s.add(4)          # 원소 추가 → {1, 2, 3, 4}
s.update([5, 6])  # 여러 원소 추가 → {1, 2, 3, 4, 5, 6}
s.remove(3)       # 특정 원소 제거 → {1, 2, 4, 5, 6}</p>
<pre><code>
## 불

불 자료형은 `True`, `False` 두 가지 값만을 가진다. 첫 글자는 반드시 **대문자**로 써야 한다.
</code></pre><p>a = True
b = False</p>
<pre><code>
### 비교 연산의 결과

비교 연산자는 항상 불 값을 반환한다.
</code></pre><p>1 &lt; 2     # True
3 == 3    # True
1 != 2    # True
4 &gt; 7     # False</p>
<pre><code>
### 불 연산자

- `and` : 둘 다 참일 때만 참
- `or` : 둘 중 하나라도 참이면 참
- `not` : 참 → 거짓, 거짓 → 참
</code></pre><p>True and False     # False
True or False      # True
not True           # False
not False          # True</p>
<pre><code>
### 자료형의 참/거짓 판별

다음 값들은 **거짓(False)** 으로 취급된다.

- `None`
- `0` (정수형, 실수형 모두)
- `&#39;&#39;` (빈 문자열)
- `[]`, `{}`, `()` (빈 리스트, 딕셔너리, 튜플)

그 외의 값들은 모두 참이다.
</code></pre><p>bool(0)        # False
bool(””)       # False
bool([])       # False
bool(123)      # True
bool(“hello”)  # True</p>
<pre><code>
---

## 배운 점

- 문자열 선언, 포매팅이 이렇게 다양하게 가능하구나... 특히 포매팅은 f-string 방식을 통해서 하는 코드를 많이 봤는데, 사실 아직도 잘 못 외웠다ㅎ 이번 기회에 제대로 사용할 수 있도록 익혀야겠다.
- 튜플, 딕셔너리, 셋 - 리스트는 아는데, 저 셋에 대해서는 이름만 알고 어떻게 선언하는지, 어떤 특징을 가지고 있는지 잘 몰랐다. 이번에 알게 되어서 상황에 따라서 잘 사용할 수 있을 것 같다. (상수 -&gt; 튜플, map -&gt; 딕셔너리 같은 식으로)
- 자료형의 참, 거짓 관련 부분에서 파이썬에서는 이런 규칙을 가지고 있다는 것을 배웠다. 이 또한 문제를 풀 때 잘 활용할 수 있을 것 같다.
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Optuna를 이용한 하이퍼파라미터 튜닝]]></title>
            <link>https://velog.io/@chae-jpg/%EC%95%88%EB%85%95</link>
            <guid>https://velog.io/@chae-jpg/%EC%95%88%EB%85%95</guid>
            <pubDate>Mon, 19 May 2025 13:42:10 GMT</pubDate>
            <description><![CDATA[<p>저번 학기에 이어 이번 학기도 캡스톤디자인과창업프로젝트 과목을 수강하고 있다. 현재 우리 팀의 연구는 끝났지만, 연구 과정에서 사용한 툴 한 가지를 소개하고자 한다. 바로 Oputna이다.
우리는 이 툴을 우리가 개발한 Linciever-IO 모델의 성능을 최대한 끌어낼 수 있는 하이퍼파라미터를 찾기 위해 사용하였다.</p>
<h2 id="optuna란">Optuna란?</h2>
<p><a href="https://optuna.org/">공식 사이트</a>
Optuna는 최적화된 하이퍼파라미터를 찾아주는 프레임워크이다. 하이퍼파라미터 튜닝을 위해서는 Scikit-Learn에서 제공하는 GridSearchCV, RandomizedSearchCV와 같은 툴을 많이 사용한다. 하지만 이런 툴들은 그 알고리즘의 태생적 한계로 인해 튜닝에 있어 <strong>아주 긴 시간이 소요된다는 문제점</strong>이 존재한다. 
물론 Optuna 또한 매우 빠른 것은 아니다. 이는 하이퍼파라미터 튜닝이라는 기법 자체가 결국 모든 케이스에 대해 실행해보고 비교해야 한다는 특성을 가지고 있기 때문이다. 하지만, Optuna는 위의 <strong>두 기법들과 비교해서는 훨씬 더 빠르다.</strong> 그리고, Pytorch, TensorFlow, Keras와 같은 메이저한 딥러닝 프레임워크에서 모두 사용 가능하다는 점도 아주 큰 장점이다.</p>
<p><img src="https://velog.velcdn.com/images/chae-jpg/post/2cde973f-91d1-485e-88a5-8909e387ab75/image.png" alt="">
현재 Optuna의 공식 사이트는 위 세 가지 점을 장점으로 내세우고 있다. 참고하면 좋을 듯 하다.</p>
<h2 id="사용하기">사용하기</h2>
<p>Optuna의 사용을 위해서는 먼저 설치가 필요하다. 다른 패키지들과 같이 pip로 간단하게 설치할 수 있다.</p>
<pre><code>pip install optuna</code></pre><p>다음, Optuna의 하이퍼파라미터 튜닝은 objective 함수 안에서 이루어진다.
나는 튜닝을 위해 다음과 같은 함수를 만들었다.</p>
<pre><code class="language-python">def objective(trial):

    # 조정 원하는 파라미터 범위 선언
    depth = trial.suggest_int(&quot;depth&quot;, 2, 8)
    latent_dim = trial.suggest_int(&quot;latent_dim&quot;, 128, 512, step=64)
    num_latents = trial.suggest_int(&quot;num_latents&quot;, 64, 256, step=32)
    learning_rate = trial.suggest_loguniform(&quot;learning_rate&quot;, 1e-4, 1e-2)

    # 기기 및 모델, 옵티마이저, 손실함수 선언
    device = torch.device(&quot;cuda&quot;)
    model = PerceiverMNIST(depth=depth, latent_dim=latent_dim, num_latents=num_latents).to(device)
    optimizer = optim.Adam(model.parameters(), lr=learning_rate)
    criterion = nn.CrossEntropyLoss()

    # 훈련
    model.train()
    total_loss = 0
    for images, labels in trainloader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        logits = model(images)
        loss = criterion(logits, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()

    return total_loss / len(trainloader)
</code></pre>
<p>나머지 부분은 일반적인 Pytorch의 훈련 loop와 동일하지만, 주목할 부분은 <strong>위의 파라미터 범위를 선언한 부분</strong>이다.
앞서 말했듯, 하이퍼파라미터 튜닝을 위해서는 결국 일일히 파라미터를 설정해서 결과를 내는 것이 필요하다. 이때 내가 파라미터를 어디서부터 어디까지 테스트할 것인지 그 범위를 설정해야 한다. 이를 위 코드의 앞부분이 설정하는 것이다.</p>
<p>위 코드에서 사용된 함수는 다음과 같다.
<code>suggest_int</code> : 정수 범위를 정한다. 조정하고자 하는 파라미터의 이름과 시작, 끝을 인자로 준다. 만약 1 단위가 아니라 64, 32와 같은 단위로 숫자가 증가하도록 하고 싶다면 step=을 주면 된다.
<code>suggest_loguniform</code> : 연속된 숫자의 범위를 정한다. 다만 이 함수는 이후 버전에서 삭제된다고 한다. 대신 <code>suggest_float(..., log=True)</code>를 사용하라고 한다.</p>
<p>위 코드에서는 depth, latent_dim, num_latents, learning_rate 네 개의 파라미터에 대해 튜닝을 시도하였다. 범위를 선언한 뒤 해당 범위로 모델 및 학습률을 설정하고, 훈련을 시켰다.</p>
<p>이를 실제로 실행하기 위해서는 다음과 같이 study를 생성한 다음, objective 함수를 인자로 주어 optimize를 수행해야 한다.</p>
<pre><code class="language-python">import optuna

study = optuna.create_study(direction=&quot;minimize&quot;)
study.optimize(objective, n_trials=30)</code></pre>
<p>여기서 <code>create_study()</code>의 인자인 <code>direction=&quot;minimize&quot;</code>는 objective 함수가 return하는 값이 감소하는 방향으로 최적화를 진행하겠다는 것을 의미한다. 여기서 위의 함수는 loss를 return하고 있기 때문에, 당연히 감소하도록 최적화하는 것이 맞다.</p>
<p>이렇게 study를 생성한 후, <code>optimize</code> 함수로 최적화를 수행한다. objective 함수와 n_trials를 인자로 준다. n_trials는 전체 시행 횟수를 의미한다.</p>
<p>코드를 실행하면 다음과 같이 trial이 시행되며 그 때의 최적 value와 파라미터, 그리고 지금까지 가장 좋은 값이 나왔던 trial을 보여준다.</p>
<pre><code>[I 2025-03-06 07:07:39,857] Trial 0 finished with value: 0.26668225020182124 and parameters: {&#39;depth&#39;: 7, &#39;latent_dim&#39;: 192, &#39;num_latents&#39;: 64, &#39;learning_rate&#39;: 0.0005968324834796224}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:09:11,290] Trial 1 finished with value: 3.8591011294227306 and parameters: {&#39;depth&#39;: 7, &#39;latent_dim&#39;: 320, &#39;num_latents&#39;: 160, &#39;learning_rate&#39;: 0.005783782916666108}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:09:31,260] Trial 2 finished with value: 0.3522836375556616 and parameters: {&#39;depth&#39;: 2, &#39;latent_dim&#39;: 128, &#39;num_latents&#39;: 128, &#39;learning_rate&#39;: 0.0001962797474825985}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:11:02,492] Trial 3 finished with value: 4.282872916789039 and parameters: {&#39;depth&#39;: 7, &#39;latent_dim&#39;: 320, &#39;num_latents&#39;: 160, &#39;learning_rate&#39;: 0.0062681789918993406}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:12:53,767] Trial 4 finished with value: 1.0389763821782207 and parameters: {&#39;depth&#39;: 5, &#39;latent_dim&#39;: 384, &#39;num_latents&#39;: 224, &#39;learning_rate&#39;: 0.0031583777405563137}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:13:21,763] Trial 5 finished with value: 0.3259865055694731 and parameters: {&#39;depth&#39;: 3, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 96, &#39;learning_rate&#39;: 0.00013326825932084483}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:13:45,325] Trial 6 finished with value: 0.3730849254169444 and parameters: {&#39;depth&#39;: 4, &#39;latent_dim&#39;: 128, &#39;num_latents&#39;: 64, &#39;learning_rate&#39;: 0.0032579798046704542}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:18:05,079] Trial 7 finished with value: 8.307785088732553 and parameters: {&#39;depth&#39;: 8, &#39;latent_dim&#39;: 512, &#39;num_latents&#39;: 256, &#39;learning_rate&#39;: 0.005633941542665712}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:18:39,135] Trial 8 finished with value: 0.2756429120068198 and parameters: {&#39;depth&#39;: 8, &#39;latent_dim&#39;: 192, &#39;num_latents&#39;: 64, &#39;learning_rate&#39;: 0.000335277034681362}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:21:02,136] Trial 9 finished with value: 11.076749913791604 and parameters: {&#39;depth&#39;: 8, &#39;latent_dim&#39;: 384, &#39;num_latents&#39;: 192, &#39;learning_rate&#39;: 0.0063976046053722925}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:22:25,992] Trial 10 finished with value: 0.2677588141255224 and parameters: {&#39;depth&#39;: 6, &#39;latent_dim&#39;: 512, &#39;num_latents&#39;: 96, &#39;learning_rate&#39;: 0.0006167022115396635}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:23:49,837] Trial 11 finished with value: 0.2702346246228861 and parameters: {&#39;depth&#39;: 6, &#39;latent_dim&#39;: 512, &#39;num_latents&#39;: 96, &#39;learning_rate&#39;: 0.00077167476845985}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:25:02,303] Trial 12 finished with value: 0.27797159841304014 and parameters: {&#39;depth&#39;: 6, &#39;latent_dim&#39;: 448, &#39;num_latents&#39;: 96, &#39;learning_rate&#39;: 0.0008276816679701316}. Best is trial 0 with value: 0.26668225020182124.
[I 2025-03-06 07:25:31,583] Trial 13 finished with value: 0.25901897840483834 and parameters: {&#39;depth&#39;: 5, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 64, &#39;learning_rate&#39;: 0.0004322355545064367}. Best is trial 13 with value: 0.25901897840483834.
[I 2025-03-06 07:25:55,515] Trial 14 finished with value: 0.27312530935811463 and parameters: {&#39;depth&#39;: 4, &#39;latent_dim&#39;: 192, &#39;num_latents&#39;: 64, &#39;learning_rate&#39;: 0.0016535293914112447}. Best is trial 13 with value: 0.25901897840483834.
[I 2025-03-06 07:26:40,226] Trial 15 finished with value: 0.2554813757209159 and parameters: {&#39;depth&#39;: 5, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 128, &#39;learning_rate&#39;: 0.0004002663484281009}. Best is trial 15 with value: 0.2554813757209159.
[I 2025-03-06 07:27:24,980] Trial 16 finished with value: 0.25565995413786186 and parameters: {&#39;depth&#39;: 5, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 128, &#39;learning_rate&#39;: 0.0003776580179276335}. Best is trial 15 with value: 0.2554813757209159.
[I 2025-03-06 07:28:03,775] Trial 17 finished with value: 0.2654680540221237 and parameters: {&#39;depth&#39;: 4, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 128, &#39;learning_rate&#39;: 0.0002610171011079572}. Best is trial 15 with value: 0.2554813757209159.
[I 2025-03-06 07:29:10,768] Trial 18 finished with value: 0.3302811196665647 and parameters: {&#39;depth&#39;: 3, &#39;latent_dim&#39;: 384, &#39;num_latents&#39;: 192, &#39;learning_rate&#39;: 0.00010365658913629019}. Best is trial 15 with value: 0.2554813757209159.
[I 2025-03-06 07:29:55,467] Trial 19 finished with value: 0.2866133468489705 and parameters: {&#39;depth&#39;: 5, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 128, &#39;learning_rate&#39;: 0.0013988263583856568}. Best is trial 15 with value: 0.2554813757209159.
[I 2025-03-06 07:30:51,669] Trial 20 finished with value: 0.2745055094945119 and parameters: {&#39;depth&#39;: 3, &#39;latent_dim&#39;: 320, &#39;num_latents&#39;: 192, &#39;learning_rate&#39;: 0.00020538133772138157}. Best is trial 15 with value: 0.2554813757209159.
[I 2025-03-06 07:31:48,387] Trial 21 finished with value: 0.25277498057449677 and parameters: {&#39;depth&#39;: 5, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 160, &#39;learning_rate&#39;: 0.00041984666204054915}. Best is trial 21 with value: 0.25277498057449677.
[I 2025-03-06 07:32:35,559] Trial 22 finished with value: 0.26705718970100983 and parameters: {&#39;depth&#39;: 5, &#39;latent_dim&#39;: 192, &#39;num_latents&#39;: 160, &#39;learning_rate&#39;: 0.00041908016880449524}. Best is trial 21 with value: 0.25277498057449677.
[I 2025-03-06 07:33:22,563] Trial 23 finished with value: 0.27611159145244274 and parameters: {&#39;depth&#39;: 4, &#39;latent_dim&#39;: 320, &#39;num_latents&#39;: 128, &#39;learning_rate&#39;: 0.0012366012560476457}. Best is trial 21 with value: 0.25277498057449677.
[I 2025-03-06 07:34:13,026] Trial 24 finished with value: 0.260062302501479 and parameters: {&#39;depth&#39;: 6, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 128, &#39;learning_rate&#39;: 0.0003121371654371933}. Best is trial 21 with value: 0.25277498057449677.
[I 2025-03-06 07:35:00,120] Trial 25 finished with value: 0.2908944913239748 and parameters: {&#39;depth&#39;: 5, &#39;latent_dim&#39;: 192, &#39;num_latents&#39;: 160, &#39;learning_rate&#39;: 0.00017289120110738774}. Best is trial 21 with value: 0.25277498057449677.
[I 2025-03-06 07:35:48,746] Trial 26 finished with value: 0.25546947501992967 and parameters: {&#39;depth&#39;: 4, &#39;latent_dim&#39;: 256, &#39;num_latents&#39;: 160, &#39;learning_rate&#39;: 0.0004696972031772631}. Best is trial 21 with value: 0.25277498057449677.
[I 2025-03-06 07:36:30,426] Trial 27 finished with value: 0.266836045512449 and parameters: {&#39;depth&#39;: 4, &#39;latent_dim&#39;: 128, &#39;num_latents&#39;: 224, &#39;learning_rate&#39;: 0.0005274984910314277}. Best is trial 21 with value: 0.25277498057449677.
[I 2025-03-06 07:37:09,359] Trial 28 finished with value: 0.32742580597692017 and parameters: {&#39;depth&#39;: 2, &#39;latent_dim&#39;: 320, &#39;num_latents&#39;: 160, &#39;learning_rate&#39;: 0.0020888611036553547}. Best is trial 21 with value: 0.25277498057449677.
[I 2025-03-06 07:37:47,981] Trial 29 finished with value: 0.25377385729332086 and parameters: {&#39;depth&#39;: 3, &#39;latent_dim&#39;: 192, &#39;num_latents&#39;: 192, &#39;learning_rate&#39;: 0.0006198691658685506}. Best is trial 21 with value: 0.25277498057449677.</code></pre><p>study가 종료된 후에는 최적 파라미터와 최적 value가 저장된다.
<code>study.best_params</code>와 <code>study.best_value</code>로 각각 이 값을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/91f752e9-8eda-4307-9846-476ac42ac954/image.png" alt=""></p>
<p>따라서 위에서 얻은 최적 파라미터를 이용해 같은 모델로도 <strong>하이퍼파라미터를 조정하는 것</strong>만으로 더 좋은 결과를 얻을 수 있는 것이다.</p>
<h2 id="마무리">마무리</h2>
<p>이 글에서는 간단히 Optuna의 objective 함수를 설정하고, 이를 사용해 study를 실행하는 것까지만 다뤘지만, 실제로 Optuna의 기능은 이 외에도 더 많다.</p>
<p>Optuna를 더욱 강력하게 만들어주는 툴도 존재한다. Optuna Dashboard와 OptunaHub가 그것이다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/ecd29aa0-4ba0-4404-96f2-46a8160719fe/image.png" alt="">
Optuna Dashboard는 말 그대로 Optuna에서 수행한 결과를 시각화하여 확인할 수 있는 대시보드이다. 최적화 히스토리나 파라미터의 중요도와 같은 값들을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/e37db09c-9108-4441-bc0c-767f6fb9f39e/image.png" alt="">
OptunaHub는 유저들이 업로드한 feature를 자유롭게 사용할 수 있는 플랫폼이다. 유저들이 제작한 강력한 feature를 사용하여 튜닝 작업을 더욱 효과적으로 진행할 수 있다.</p>
<p>이렇게 이번 글에서는 Optuna의 대한 간단한 소개와 사용법, 그리고 연관된 툴까지 알아보았다. 이후에 더욱 다양한 기능들도 사용하여 연구에 큰 도움을 얻을 수 있으면 하는 바람이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FedCLIP 논문 리뷰 & 코드 실행 준비]]></title>
            <link>https://velog.io/@chae-jpg/FedCLIP-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-%EC%BD%94%EB%93%9C-%EC%8B%A4%ED%96%89-%EC%A4%80%EB%B9%84</link>
            <guid>https://velog.io/@chae-jpg/FedCLIP-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-%EC%BD%94%EB%93%9C-%EC%8B%A4%ED%96%89-%EC%A4%80%EB%B9%84</guid>
            <pubDate>Tue, 26 Nov 2024 14:57:45 GMT</pubDate>
            <description><![CDATA[<p>저는 이번 학기 캡스톤디자인을 통해 <strong>멀티모달 환경에서의 연합학습</strong>이라는 주제로 연구를 진행하고 있습니다.
연구에 도움이 될 만한 다른 연구를 찾아보는 중, fine-tuning을 위해서 엄청난 연산이 필요한 대규모 pre-trained 모델을 제한된 리소스로 학습을 진행해야 하는 연합학습 환경에 최적화시켜 적용하는 <strong>FedCLIP</strong> 연구를 발견하게 되었습니다. 
이미 학습된 좋은 모델을 사용하지 못하는 환경에 맞추어 모델을 최적화한다는 발상이 흥미로웠고, 그 코드도 모두 공개가 되어있어 직접 시연을 해볼 수 있는 상황이었기 때문에 따라서 이 주제로 글을 작성하게 되었습니다.</p>
<h2 id="clip이란">CLIP이란?</h2>
<p>FedCLIP에 대해 설명하기 위해서는 먼저 CLIP에 대해 간단히 알고 넘어갈 필요가 있습니다.
CLIP은 2021년 OpenAI에서 공개한 이미지-텍스트 멀티모달 모델로, 논문은 다음 링크에 공개되어 있습니다.
<a href="https://arxiv.org/abs/2103.00020">https://arxiv.org/abs/2103.00020</a>
CLIP은 contrastive learning을 통해 학습을 진행합니다. (이름도 그래서 Contrastive Language-Image Pre-training의 줄임말)
<img src="https://velog.velcdn.com/images/chae-jpg/post/d7ea25db-12bc-4094-ae18-d00b4ad153e0/image.png" alt="">
논문의 이미지를 가지고 왔습니다.
각각 설정한 text encoder, image encoder를 통해 텍스트와 이미지를 넘기면(논문에서는 이미지에는 resnet과 vision transformer, 텍스트에는 transformer을 사용했다고 합니다) 텍스트와 이미지를 각각 벡터로 변환해줍니다. 그리고 두 벡터 쌍의 dot product를 구하면 그림과 같이 다양한 pair가 나오게 될 텐데, 여기서 색칠이 되어있는 각 텍스트와 이미지에 대응되는 pair에 가중치를 주어 학습을 시키는 것이 CLIP의 핵심입니다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/02071d35-3916-4004-b19b-a8e0cbc018aa/image.png" alt="">
마찬가지로 논문에서 가지고 온 수도코드입니다.
보면 두 벡터의 닷 프로덕트를 구한 뒤 대응되는 pair의 cosine similarity는 크게, 그 외는 작게 만드는 것이 학습의 핵심입니다.</p>
<h2 id="fedclip이란">FedCLIP이란</h2>
<p>현재 CLIP은 text-image 분야에서 SOTA 모델로 알려져 있는데요, 이 모델을 연합학습 환경에도 적용할 수 있다면 정말 좋지 않을까요?
하지만 CLIP은 어마어마하게 거대한 규모의 데이터를 통해 학습한 모델이기 때문에, 그 파라미터 수도 정말 많고, 따라서 리소스가 제한되는 연합학습 환경에서는 도입하기가 어렵다는 문제점이 있습니다. 
따라서 FedCLIP은 CLIP을 연합학습 환경에 맞추어 미리 학습된 인코더는 그대로 사용하고, 대신 각 기기별로 어댑터를 학습해, 이를 통합하여 하나의 완성된 어댑터를 만드는 방식으로 이 문제를 해결하였습니다.
<img src="https://velog.velcdn.com/images/chae-jpg/post/4c97e090-57eb-4c50-9402-7247997376ae/image.png" alt="">
FedCLIP의 학습은 다음 과정으로 진행됩니다.</p>
<ol>
<li>인코더를 통과시켜 T와 I를 얻는다.</li>
<li>I를 어댑터에 통과시켜 I* 를 얻는다. 그리고 CLIP과 같이 같은 쌍의 cosine similarity는 가깝게, 반대는 멀게 어댑터를 학습시킨다.</li>
<li>얻은 어댑터를 서버로 전송한다.</li>
<li>어댑터의 평균을 구한 뒤 해당 어댑터를 각 기기로 전송한다.</li>
<li>이 과정을 반복한다.</li>
</ol>
<p>이 때 사용하는 어댑터는 어텐션을 기반으로 한 AttAI라는 어댑터로, 하나의 완전연결층, Tahn 활성화 함수, 하나의 완전 연결층, 그리고 소프트맥스로 이루어져 있습니다.
미리 학습된 인코더를 사용하여 이미 충분히 학습된 검증된 자원을 사용하면서, 인코더를 기기마다 학습시킨 뒤 통합하고 분배하여 튜닝한다는 점이 흥미롭습니다. 어댑터라는 개념의 도입을 통해 기기에서도 CLIP을 통한 학습을 가능하게 했다니, 정말 재미있지 않나요?</p>
<p>참고로 여기서는 image에 adapter를 달았지만, text에 다는 것도 가능하다고 언급되어 있습니다. 해당 방식으로는 어떤 결과가 나올지도 궁금해지는 부분입니다.</p>
<h2 id="코드-실행-준비">코드 실행 준비</h2>
<p>FedCLIP의 코드는 모두 깃헙을 통해 공개되어 있습니다.
<a href="https://github.com/microsoft/PersonalizedFL/tree/main/fedclip">https://github.com/microsoft/PersonalizedFL/tree/main/fedclip</a>
먼저, 파이썬 버전을 3.8.5로 맞춰줍니다.
기존에 설치되어 있는 파이썬을 삭제하고 싶지 않은 경우에는, virtualenv를 통해 가상환경을 설정하는 것이 가능합니다.</p>
<p>그 다음, 요구사항을 맞춰줍니다. 
다음과 같은 요구사항을 맞춰줄 필요가 있습니다. </p>
<pre><code>$ pip install ftfy regex tqdm
$ pip install git+https://github.com/openai/CLIP.git
$ pip install -r requirements.txt
$ pip install torch==1.10.1+cu111 torchvision==0.11.2+cu111 torchaudio==0.10.1 -f https://download.pytorch.org/whl/cu111/torch_stable.html</code></pre><p>이 때, +cu111은 cuda가 설치되어 있는 윈도우즈에서만 설치가 가능합니다.</p>
<p>위 요구사항을 모두 설치한 뒤, 데이터셋을 다운로드 받습니다.
사용한 데이터셋은 PACS로, 물체 인식에 사용되는 데이터셋입니다. 4개의 sub-dataset과 7개의 class를 포함하고 있습니다.</p>
<pre><code>wget https://wjdcloud.blob.core.windows.net/dataset/PACS.zip
unzip PACS.zip</code></pre><p>리눅스 환경에서는 다음 명령어를 통해 설치하고 압축 해제가 가능합니다.
다만 윈도우즈에는 wget 명령어가 존재하지 않기 때문에, 해당 링크로 바로 접속하면 다운로드가 가능합니다.</p>
<p>이후 아래의 네 명령어를 각각 수행하는 것을 통해 직접 FedCLIP을 실행해볼 수 있습니다!</p>
<pre><code>python methods/fed_at_clip.py --dataset pacs --mode FedAtImg --test_envs 0 --iters 200 --wk_iters 1 --lr 5e-05
python methods/fed_at_clip.py --dataset pacs --mode FedAtImg --test_envs 1 --iters 200 --wk_iters 1 --lr 5e-05
python methods/fed_at_clip.py --dataset pacs --mode FedAtImg --test_envs 2 --iters 200 --wk_iters 1 --lr 5e-05
python methods/fed_at_clip.py --dataset pacs --mode FedAtImg --test_envs 3 --iters 200 --wk_iters 1 --lr 5e-05</code></pre><hr>
<p>다음은 윈도우즈 환경에서 직접 FedCLIP을 실행하는 글을 가져오겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스택 문제 두 개]]></title>
            <link>https://velog.io/@chae-jpg/%EC%8A%A4%ED%83%9D-%EB%AC%B8%EC%A0%9C-%EB%91%90-%EA%B0%9C</link>
            <guid>https://velog.io/@chae-jpg/%EC%8A%A4%ED%83%9D-%EB%AC%B8%EC%A0%9C-%EB%91%90-%EA%B0%9C</guid>
            <pubDate>Sat, 02 Dec 2023 14:00:24 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/2493">탑</a>
<a href="https://www.acmicpc.net/problem/6198">옥상 정원 꾸미기</a></p>
<p>둘 다 골드 5</p>
<p>입력 데이터의 수가 많기 때문에 스택을 이용해서 효율적으로 풀어야 하는 문제들이다.
조금 다르긴 한데 두 문제 다 결은 비슷해서... 하나 풀면 다른 하나도 풀 만 하다.</p>
<hr>
<h2 id="탑">탑</h2>
<p>나보다 왼쪽에 있는 탑 중에서 가장 가까운 나보다 높은 탑의 인덱스를 구하는 문제이다.
왼쪽의 탑 중 가장 가까운 탑을 선택하기 때문에, 탐색은 오른쪽에서 왼쪽 순서로 진행한다.</p>
<p>먼저 맨 오른쪽 탑 인덱스를 넣어준다. 그리고 왼쪽으로 한 칸씩 이동하면서 스택에 인덱스를 넣어줄 건데, 이 때 한 번 검사를 해주어야 한다.
바로 들어오는 인덱스의 탑의 높이가 현재 스택 탑의 탑 높이(ㅋ) 보다 높은지 확인하는 거다. </p>
<p>그래서 만약 새로 들어오는 게 더 높으면, 스택 탑의 정답을 새로운 인덱스로 저장을 해주고, 팝을 해준다. 이걸 스택 탑의 높이가 새로운 인덱스의 높이보다 더 높을 때까지 반복해준다. 
(같은 경우도 팝을 해 주어야 한다. 그래도 문제 조건을 만족하니까)
이러면 스택의 탑에는 계속해서 높은 원소가 남게 되니까, 더 높은 값을 찾을 수 있다.</p>
<p>코드는 다음과 같다.</p>
<pre><code class="language-c++">//탑 - 골드 5

#include &lt;iostream&gt;
#include &lt;stack&gt;
#include &lt;vector&gt;

using namespace std;

int main() {
    int n;
    vector&lt;int&gt; h, ans;
    stack&lt;int&gt; s;
    cin &gt;&gt; n;
    h.assign(n+1, 0);
    ans.assign(n+1, 0);

    for (int i = 1; i &lt;= n; i++) {
        cin &gt;&gt; h[i];
    }
    s.push(n);

    for (int i = n-1; i &gt;= 1; i--) {
        while (!s.empty() &amp;&amp; h[i] &gt;= h[s.top()]) {
            ans[s.top()] = i;
            s.pop();
        }
        s.push(i);
    }
    for (int i = 1; i &lt;= n; i++) {
        cout &lt;&lt; ans[i] &lt;&lt; &#39; &#39;;
    }

}</code></pre>
<p>참고로 이거 풀었을 때 segfault 떠서 대체 왜...? 했는데 스택이 비었을 때를 생각 안해서 그런 거였다. (새로 들어온 인덱스가 높이가 짱짱 높아서 스택에 있던 모든 원소를 날려버린 후에 스택 탑을 확인하려고 하면 아무래도 오류가 나겠지...?)
그래서 그거 고치니까 해결됐다!</p>
<hr>
<h2 id="옥상-정원-꾸미기">옥상 정원 꾸미기</h2>
<p>탑 풀고 다음은 이거 풀어야지~ 하고 생각만 하고 미루다가 스터디에서 추천받아서 풀게 된 문제이다. 
탑 푼지 얼마 안 돼서 그나마 금방 풀었다ㅋㅋㅋ 아이디어가 거의 동일하기 때문에 뭐...</p>
<p>탑은 방향이 왼쪽이었다면 이건 오른쪽이고, 탑에서는 인덱스를 구하는 거였다면 이거는 나보다 낮은 탑의 개수를 구하는 문제이다.</p>
<p>이렇게 정리해보니까 더 비슷한 문제 같다. 아무래도... 그럴만도</p>
<p>마찬가지로 인덱스를 계속 넣어주는데, 나보다 높은 빌딩이 새로 들어오면 탑이 그 빌딩보다 높이가 높을 때까지 계속 팝해준다. 근데 여기서는 팝 해주면서 새로운 인덱스랑 나의 거리를 계산해서 정답에 더해준다. 그게 나랑 새로운 빌딩 사이에 존재하는 빌딩의 수니까~
그리고 또 여기서는 오른쪽 방향을 보고 있기 때문에 탐색을 왼쪽부터 오른쪽 방향으로 해주어야 한다. 따라서 처음에 0번 인덱스를 넣고, 1에서부터 n-1까지 탐색을 진행한다.</p>
<p>이 문제의 또 다른 차이점은 스택을 다 집어넣고 마지막에도 한 번 더 처리를 해주어야 한다는 점이다. 위 과정이 끝나면 스택에 들어있는 원소들은 높이가 낮은 순서에서 높은 순서로 정렬된 채로 들어가있는 상태이다. 근데 탑이었으면 그냥 그 높은 원소들은 답이 0이고 끝이었을텐데(나보다 높은 원소가 앞에 하나도 없으니까. 팝이 안 됐다는 건 그 뜻이다) 여기서는 나보다 낮은 빌딩의 개수를 세어주는 것이 목적이기 때문에 또 계산을 해 주어야 한다.</p>
<p>따라서 맨 위에 있는 가장 높이가 낮은 빌딩의 인덱스를 기억을 해주고, 한 번 팝 한 다음에 스택이 빌 때까지 계속 팝을 한다. 그 때 정답에 저장한 인덱스랑 탑과의 거리를 또 계산해서 정답에 저장해준다. 그러면 끝~</p>
<pre><code>// 옥상 정원 꾸미기 - 골드 5

#include &lt;bits/stdc++.h&gt;

using namespace std;

int n;
long long answer;
vector&lt;int&gt; h, ans;
stack&lt;int&gt; s;

void solve() {
    s.push(0);
    for (int i = 1; i &lt; n; i++) {
        //다음 빌딩이 높이가 더 높거나 같으면 -&gt; 더 높이가 높은 빌딩이 나올 때까지 pop, 확인할 수 있는 빌딩의 개수 계산 (두 인덱스 차 -1)
        while (!s.empty() &amp;&amp; h[s.top()] &lt;= h[i]) {
            answer += (i - s.top()) -1;
            s.pop();
        }
        s.push(i);
    }

    int t = s.top();
    s.pop();
    while (!s.empty()) {
        answer += (t - s.top());
        s.pop();
    }

}

int main() {
    cin &gt;&gt; n;
    h.assign(n, 0);
    ans.assign(n, 0);
    for (int i = 0; i &lt; n; i++) {
        cin &gt;&gt; h[i];
    }

    solve();
    cout &lt;&lt; answer;

}</code></pre><p>고수들이 뭔가 문제 풀면 이렇게 다 전역변수로 빼고 main 코드도 solve 함수 하나 만들어서 거기에다가 다 쓰길래 따라해봤다ㅋㅋㅋㅋㅋㅋㅋㅋ</p>
<p>근데 얘도 처음 풀었을 때는 틀렸다... answer 범위 int로 해서
빌딩이 8만개까지 가능한데, 이게 내림차순으로 정렬되어있다고 생각하면 1+2+...+79999가 정답이니까... 굳이 계산을 하자면 약 32억. 무조건 오버플로우.
그래서 long long으로 안전하게 선언을 해주면 된다~</p>
<hr>
<p>내가 스택 문제를 풀던 건 사실 이 문제를 해결하기 위해서였다.
<a href="https://www.acmicpc.net/problem/3015">오아시스 재결합</a>
얘도 결국 입력 엄청 크고 높이 물어보는 거니까 비슷하긴 한데 모든 순서쌍을 구해야 하는 게 좀... 골치 아프다...
그래도 언젠가는 풀 수 있겠지?</p>
<p>요즘 들어서 골드 4<del>5 정도는 그래도 넉넉잡아서 1시간이면 풀 수 있는 정도로 올라온 것 같다. ㅎㅎ
좀 더 노력해서 플래 문제 건들여볼 수 있을 정도로 실력을 키워봐야겠다</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[BOJ 2470 - 두 용액 (C++)]]></title>
            <link>https://velog.io/@chae-jpg/BOJ-2470-%EB%91%90-%EC%9A%A9%EC%95%A1-C</link>
            <guid>https://velog.io/@chae-jpg/BOJ-2470-%EB%91%90-%EC%9A%A9%EC%95%A1-C</guid>
            <pubDate>Sat, 25 Nov 2023 13:41:09 GMT</pubDate>
            <description><![CDATA[<p>이 문제는 사실 버전이 두 가지다.
<a href="https://www.acmicpc.net/problem/2470">두 용액</a>
<a href="https://www.acmicpc.net/problem/2467">용액</a></p>
<p>둘이 뭔가가 다르긴 했는데 지금은 기억이 잘 안 난다.
근데 아무튼 중요한 건 사실상 같은 문제라 하나 풀고도 두 문제를 맞춘 것과 같은 효과를 낼 수 있다는 것(!)
그것도 심지어 골드 5(!!!)
완전히 남는 장사일 수 밖에 없다.</p>
<p>풀이 자체는 간단히 투 포인터를 이용해서 할 수 있다.
먼저 모든 원소를 소트한 뒤, 맨 왼쪽과 오른쪽에 포인터를 세팅해둔다. 그러면서 한 칸 한 칸씩 포인터를 옮기면서 두 포인터가 가리키고 있는 원소들의 합을 구하는 것이다. 만약 그 합이 0보다 크다면, 양수의 크기를 줄여야 하므로 오른쪽 포인터를 이동시키고, 반대라면 음수의 크기를 키워야 하므로 왼쪽 포인터를 이동시킨다. (이것이 가능한 것은 앞에서 원소를 소트해뒀기 때문이다. 만약 정렬되어 있지 않다면 이 방법을 사용할 수 없다.</p>
<p>이 과정을 반복하면서 합을 계산하고, 그 합이 만약 0과 가장 가까웠던 값보다 더 가깝다면 최소값을 갱신하고, 해당 원소를 저장한다. 이 과정을 두 포인터가 교차될 때까지 반복한다.</p>
<hr>
<p>정답을 얻은 코드는 다음과 같다.</p>
<pre><code>//두 용액 - 골드 5

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;cmath&gt;

using namespace std;

int a_l, a_r, min_char = 2*1e9;

void findMin(const vector&lt;int&gt;&amp; arr) {
    int l = 0, r = arr.size() - 1;

    while (l &lt; r) {
        int sum = arr[l] + arr[r];
        if (abs(min_char) &gt; abs(sum)) {
            min_char = sum;
            a_l = arr[l];
            a_r = arr[r];
        }

        if (sum &gt; 0) {
            r--;
        } else {
            l++;
        }
    }
}

int main() {
    int n;
    vector&lt;int&gt; arr;
    cin &gt;&gt; n;
    arr.assign(n, 0);

    for (int i = 0; i &lt; n; i++) {
        cin &gt;&gt; arr[i];
    }
    sort (arr.begin(), arr.end());

    findMin(arr);

    cout &lt;&lt; a_l &lt;&lt; &#39; &#39; &lt;&lt;  a_r;

}</code></pre><p>이거랑은 별개로, 같은 문제인데 이번에는 용액 3개를 가지고 푸는 버전도 존재한다.
<a href="https://www.acmicpc.net/problem/2473">세 용액</a></p>
<p>아무래도 이 문서가 도움이 될 것 같다. 
<a href="https://www.geeksforgeeks.org/find-a-triplet-that-sum-to-a-given-value/">triplet sum</a></p>
<p>보면 세 가지 방법을 알려주는데, 하나는 브루트포싱이라 뭐... 의미가 없고, 하나는 투 포인터, 마지막은 해싱이다.</p>
<p>투 포인터 방법도 근데 뭐 크게 다른 건 없는 게, 왼쪽에 원소를 하나 고정해 둔 다음에 그 오른쪽 원소들을 위에서 투 포인터로 푼 것처럼 옮겨가면서 가운데에서 만날 때까지 하고, 그 다음 왼쪽 원소를 한 칸 오른쪽으로 옮겨서 또 하고, 또 왼쪽 원소를 오른쪽으로 옮기고... 의 반복이다. 원소가 n개라고 할 때, 왼쪽 원소를 옮기는 게 O(N)이고, 또 포인터 연산이 O(N) 걸리니까 시간복잡도는 O(N^2)가 될 것이다. 그래도 다항식 시간으로 끊기니까 다행인가...</p>
<p>세 번째 방법은 엄밀히 말하면 위 문제를 푸는 데 사용할 만한 방법은 아니고, 그냥 뭐... 위랑 대단하게 다를 건 없다. 차이라면 뭐 원소 찾느라 set 쓴다? 근데 이것도 이 문제에서는 0이 되는 케이스를 찾는 게 아니라 0이랑 가까운 케이스를 찾는 거라서... 일일히 또 탐색하면 n^3 되니까 걍 투 포인터 변형으로 푸는 게 나을 것 같다.</p>
<p>아무튼 그렇다. 이 문제도 한 번쯤 풀어봐야겠다. 대신 인풋 범위 보니까 2개까지는 21억 안 넘는데 3개부터는 넘을 수 있으니까 자료형 좀 주의하고.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[BOJ 1107 - 리모컨]]></title>
            <link>https://velog.io/@chae-jpg/BOJ-1107-%EB%A6%AC%EB%AA%A8%EC%BB%A8</link>
            <guid>https://velog.io/@chae-jpg/BOJ-1107-%EB%A6%AC%EB%AA%A8%EC%BB%A8</guid>
            <pubDate>Sun, 12 Nov 2023 12:34:26 GMT</pubDate>
            <description><![CDATA[<p>전부터 풀어야지 생각하고 어떤 알고리즘으로 풀지까지 다 생각을 해뒀던 문제인데 미루고 미루다가 드디어 풀게 되었다.</p>
<p>발상은 간단하다.
정말 0번 채널부터 해서 모든 채널을 일일히 확인해보는 것이다. 채널 번호를 하나 줄인 뒤, 그 번호에 고장난 버튼의 숫자가 들어있는지 확인, 들어있지 않다면 버튼을 누르는 횟수를 계산, 이 것을 채널 번호가 0이 될 때까지 반복한다. 반대로, 채널 번호를 위로 올린 뒤, 확인하고, 100만이 될 때까지 반복하면 위의 범위까지 모두 검사한 것이 된다.   </p>
<p>여기서 100만까지 검사를 하는 이유는, N의 범위가 50만까지이기 때문이다. 채널 번호가 50만일 경우, 그보다 작은 채널 번호 중에서 가장 차이가 많이 나는 것은 0번 채널이다. 이 때의 차이는 50만이다. 따라서 그만큼 차이가 나는 100만번 채널까지 검사를 해 주는 것이다. 그 이상이 되면 무슨 값이든 0일 때보다는 버튼을 누르는 횟수가 많아져서, 절대 정답이 될 수가 없다.</p>
<p>이걸 일일히 검사해도 될 것이라고 판단하게 된 것은 애초에 검사해야하는 채널의 범위가 0부터 100만으로 굉장히 작기 때문이다. 이 문제의 제한시간은 2초이고, c++ 기준 2초면 2억 개의 계산을 할 수 있기 때문에 한 채널을 검사할 때 2억 / 100만 = 200개의 연산을 할 수가 있는데, 딱 생각해봐도 그 정도 연산은 한 채널 검사에 필요하지 않는다. 따라서 시간은 널널하다고 바로 판단이 가능하다. 
시간 복잡도를 생각해보면, 언제나 100만 번 연산을 수행한다고 생각하면 될 것 같다. 채널 수에 비례해서 연산 수가 변화하는 게 아니라 언제나 고정이라 뭐라고 표기를 해야 할 지 애매하다...</p>
<pre><code>for (int i = 0; i &lt; l; i++) {
       if (broken.find(s[i] - &#39;0&#39;) != broken.end()) continue;
}
cnt = min(abs(t - n) + l, cnt);</code></pre><p>맨 처음에 코드를 짤 때 이렇게 썼는데, 내가 원하는 결과는 만약 고장난 버튼을 저장한 set broken에서 s[i]를 찾으면 break하고 다음 루프롤 돌아가는 것이었지만, s[i]을 찾아도 계속 find를 해서 당황했었다.
근데... 그게 당연하다... 무슨 break도 아니고 continue를 걸었으니 당연히 무시하고 다음 루프를 돌겠지...
그리고 여기서 continue를 걸어야 되는 루프는 이 루프가 아니라 얘를 감싸고 있는 채널 번호를 줄이거나 늘리는 루프이다. 따라서 저 부분은 만약 s[i]를 찾았을 시 flag를 true로 해준 뒤 break한 뒤, flag가 true일 경우 for을 감싼 루프를 continue해서 cnt를 계산하지 않도록 해주었다.</p>
<p>참고로 여기서 고장난 버튼이 채널 번호에 포함되어 있을 때 cnt를 계산해주지 않는 이유는, 그 채널에 접근하는 것이 불가능하기 때문이다. (당연)</p>
<pre><code>//리모컨 - 골드 5

#include &lt;iostream&gt;
#include &lt;set&gt;
#include &lt;cmath&gt;
#include &lt;string&gt;

using namespace std;

#define MAX_CH 500000

int main() {
    int n, m, t, cnt = 0;
    char temp;
    bool flag = false;
    set&lt;int&gt; broken;
    cin &gt;&gt; n &gt;&gt; m;

    cnt = abs(100-n);

    if (!m) {
        int l = to_string(n).length();
        cout &lt;&lt; min(l, cnt);
        return 0;
    }
    for (int i = 0; i &lt; m; i++) {
        cin &gt;&gt; t;
        broken.insert(t);
    }



    t = n;
    while (t &gt; 0) {
        t--;
        string s = to_string(t);
        int l = s.length();
        for (int i = 0; i &lt; l; i++) {
            if (broken.find(s[i] - &#39;0&#39;) != broken.end()) {
                flag = true;
                break;
            }
        }
        if (flag) {
            flag = false;
            continue;
        }
        cnt = min(abs(t - n) + l, cnt);
    }

    t = n;
    while (t &lt; MAX_CH * 2) {
        t++;
        string s = to_string(t);
        int l = s.length();
        for (int i = 0; i &lt; l; i++) {
            if (broken.find(s[i] - &#39;0&#39;) != broken.end()) {
                flag = true;
                break;
            }
        }
        if (flag) {
            flag = false;
            continue;
        }
        cnt = min(abs(t - n) + l, cnt);
    }
    cout &lt;&lt; cnt;
}</code></pre><p>그렇게 해서 이렇게 제출을 했는데... 틀렸다!</p>
<p>이유는 간단했다. 나는 채널 번호가 n이면 0<del>n-1, n+1</del>MAX_CH까지만 검사를 했지, 정작 n일 때 검사를 하지를 않았다. 근데 n일 때도 cnt가 최소가 되는 것이 가능하다. 뭐 채널 번호가 1234인데 고장난 버튼은 0567 이러면 그냥 1234 누르는 게 최소니까...</p>
<p>이런 멍청한 실수를 했다는 사실을 깨닫고 바로 수정했다. 숫자를 늘리는 루프 앞에서 t를 n-1로 해주었다. 이러면 루프에 들어갔을 때 t가 n이 되니까 n일 때도 검사를 하게 된다.</p>
<hr>
<p>결과적으로 정답을 얻은 코드는 다음과 같다. </p>
<pre><code>//리모컨 - 골드 5

#include &lt;iostream&gt;
#include &lt;set&gt;
#include &lt;cmath&gt;
#include &lt;string&gt;

using namespace std;

#define MAX_CH 500000

int main() {
    int n, m, t, cnt = 0;
    char temp;
    bool flag = false;
    set&lt;int&gt; broken;
    cin &gt;&gt; n &gt;&gt; m;

    cnt = abs(100-n);

    if (!m) {
        int l = to_string(n).length();
        cout &lt;&lt; min(l, cnt);
        return 0;
    }
    for (int i = 0; i &lt; m; i++) {
        cin &gt;&gt; t;
        broken.insert(t);
    }



    t = n;
    while (t &gt; 0) {
        t--;
        string s = to_string(t);
        int l = s.length();
        for (int i = 0; i &lt; l; i++) {
            if (broken.find(s[i] - &#39;0&#39;) != broken.end()) {
                flag = true;
                break;
            }
        }
        if (flag) {
            flag = false;
            continue;
        }
        cnt = min(abs(t - n) + l, cnt);
    }

    t = n-1;
    while (t &lt; MAX_CH * 2) {
        t++;
        string s = to_string(t);
        int l = s.length();
        for (int i = 0; i &lt; l; i++) {
            if (broken.find(s[i] - &#39;0&#39;) != broken.end()) {
                flag = true;
                break;
            }
        }
        if (flag) {
            flag = false;
            continue;
        }
        cnt = min(abs(t - n) + l, cnt);
    }
    cout &lt;&lt; cnt;
}</code></pre><p>계산을 얼추 해보고 브루트포스로도 충분히 풀린다는 점을 캐치하면 간단한 구현으로 풀 수 있는 간단한 문제였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[칸토어 집합이 깨달음을 주었다]]></title>
            <link>https://velog.io/@chae-jpg/%EC%B9%B8%ED%86%A0%EC%96%B4-%EC%A7%91%ED%95%A9%EC%9D%B4-%EA%B9%A8%EB%8B%AC%EC%9D%8C%EC%9D%84-%EC%A3%BC%EC%97%88%EB%8B%A4</link>
            <guid>https://velog.io/@chae-jpg/%EC%B9%B8%ED%86%A0%EC%96%B4-%EC%A7%91%ED%95%A9%EC%9D%B4-%EA%B9%A8%EB%8B%AC%EC%9D%8C%EC%9D%84-%EC%A3%BC%EC%97%88%EB%8B%A4</guid>
            <pubDate>Sat, 14 Oct 2023 14:34:26 GMT</pubDate>
            <description><![CDATA[<p>런닝 뛰고 샤워하고 맥주 한 잔 걸치면서 쓰는 ps 일기...</p>
<p>나는 내가 재귀를 정말 못한다고 생각했었음... 
그래서 재귀를 잘해지기 위해서 재귀 문제만 골라서 쭉 풀고 하던 시절도 있었음... (이건 글도 썼는데 이 블로그 말고 딴 데다 써서.. 나중에 완성되면 올림)</p>
<p>예를 들어서 정말 유명한 재귀 문제
<a href="https://www.acmicpc.net/problem/2447">별 찍기 10</a>
PS를 하는 사람이라면 모두 한 번쯤은 풀어봤을 그 유명한 문제임</p>
<p>전에 이 문제를 풀어보려고 했었는데... 마찬가지로 재귀를 더럽게 못했던 나는 좀 끄적끄적만 거리다가 완전히 엉망인 결과가 나와서 던졌던 기억이 있다
이유는 모르겠지만 코드가 안 남아있네 지웠나... 아무튼</p>
<hr>
<p>그리고 까마득하게 이 문제를 잊고 지내다가 오늘 백준 단계별에서 재귀를 풀다가 만난 이 문제
<a href="https://www.acmicpc.net/problem/4779">칸토어 집합</a></p>
<p>한 번 손대봐야겠다고 생각은 하고 있었어서 마침 오늘 만난 김에 해결해버려야겠다고 생각했다</p>
<pre><code>// 칸토어 집합 - 실버 3

#include &lt;iostream&gt;
#include &lt;cmath&gt;
#include &lt;algorithm&gt;

using namespace std;

void makeCantor(string&amp; str, int l, int r) {
    if (l &gt;= r) return;
    int len = (r - l) / 3 + 1;
    fill(str.begin() + l + len, str.begin() + r - len + 1, &#39; &#39;);
    makeCantor(str, l, l+len-1);
    makeCantor(str, r-len+1, r);
}

int main() {
    int n;
    while (cin &gt;&gt; n) {
        string answer (pow(3, n), &#39;-&#39;);
        makeCantor(answer, 0, pow(3, n) - 1);
        cout &lt;&lt; answer &lt;&lt; &#39;\n&#39;;
    }
}</code></pre><p>결과적으로는 매우 유익했던 것 같음 
string 생성자 중에 저렇게 변수명 (길이, 문자) 해서 자동으로 채워주는 게 있다는 것도 배웠고
fill 함수도 처음 써봤고... pow 함수도 써보고... 
그리고 이 문제를 어떻게 접근해야 될지를 되게 고민했었는데 이번에는 운좋게도 보자마자 떠올랐음.
길이를 삼등분을 해주고 그 만큼을 공백문자로 채워준 다음에, 그러면 양 옆 두 부분이 남으니까 그 부분을 또 삼등분을 하고... 남은 두 부분을 또 삼등분... 또... 이렇게 재귀적으로 나눠주면 됨!
전보다 재귀가 많이 익숙해진 기분이 듬... 재귀 특훈의 성과가 이렇게 보이는 것 같다</p>
<hr>
<p>그래서 이제는 별 찍기 문제를 풀었다는 말을... 해야겠지요...? 맥락상ㅋㅋㅋㅋ</p>
<p>그냥 이 문제는... 칸토어 집합을 2차원으로 확장한 버전이라고 할 수 있음. 딱 그게 전부임. ㅋㅋ
진짜 그냥 칸토어에서는 가로로 삼등분해서 공백 넣어준 거를 여기서는 가로 세로를 둘 다 삼등분해서 공백을 넣어주면... 그걸로 끝</p>
<p>근데 이제 여기서 sub-problem이 칸토어는 2개가 생기는데 여기는 8개가 생김... 이유는 생각해보면 알거라고 생각함^^
그래서 그게 좀 귀찮았음... 이건 뭔가 더 좋은 방법이 있지 않을까 싶은데 나중에 한 번 찾아봄ㅋㅋㅋ</p>
<pre><code>//별 찍기 - 10 - 골드 5

#include &lt;iostream&gt;
#include &lt;vector&gt;

using namespace std;

typedef pair&lt;int, int&gt; pi;

void makePattern(vector&lt;vector&lt;char&gt;&gt;&amp; arr, pi l, pi r) {
    int len = (r.first - l.first + 1) / 3;
    if (!len) return;

    for (int i = l.first + len; i &lt; r.first -len + 1; i++) {
        for (int j = l.second + len; j &lt; r.second -len + 1; j++) {
            arr[i][j] = &#39; &#39;;
        }
    }

    makePattern(arr, l, {l.first + len - 1, l.second + len - 1});
    makePattern(arr, {l.first + len, l.second}, {r.first - len, l.second + len - 1});
    makePattern(arr, {r.first - len +1, l.second}, {r.first, l.second + len - 1});

    makePattern(arr, {l.first, l.second + len}, {l.first + len - 1, r.second - len});
    makePattern(arr, {r.first - len +1, l.second + len}, {r.first, r.second - len});

    makePattern(arr, {l.first, r.second - len + 1}, {l.first + len - 1, r.second});
    makePattern(arr, {l.first + len, r.second-len+1}, {r.first - len, r.second});
    makePattern(arr, {r.first - len + 1, r.second - len + 1}, r);

}

int main() {
    int n;
    vector&lt;vector&lt;char&gt;&gt; arr;
    cin &gt;&gt; n;
    arr.assign(n, vector&lt;char&gt; (n, &#39;*&#39;));

    makePattern(arr, {0, 0}, {n-1, n-1});

    for (int i = 0; i &lt; n; i++) {
        for (int j = 0; j &lt; n; j++) {
            cout &lt;&lt; arr[i][j];
        }
        cout &lt;&lt; &#39;\n&#39;;
    }
}</code></pre><p>아무튼 전보다 발전한 거 같아서 매우 뿌듯했다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL 첫걸음 - 7, 8장]]></title>
            <link>https://velog.io/@chae-jpg/SQL-%EC%B2%AB%EA%B1%B8%EC%9D%8C-7-8%EC%9E%A5</link>
            <guid>https://velog.io/@chae-jpg/SQL-%EC%B2%AB%EA%B1%B8%EC%9D%8C-7-8%EC%9E%A5</guid>
            <pubDate>Sat, 14 Oct 2023 11:13:03 GMT</pubDate>
            <description><![CDATA[<h2 id="7장---복수의-테이블-다루기">7장 - 복수의 테이블 다루기</h2>
<h3 id="집합-연산">집합 연산</h3>
<p>MySQL에서는 각 테이블을 집합, 그리고 그 안의 데이터 하나하나를 집합의 원소로 간주할 수 있다. 
따라서 집합 연산을 통해 두 테이블 간의 연관관계를 이용하여 원하는 데이터를 추출하는 것이 가능하다.</p>
<ul>
<li>UNION
합집합을 구하기 위해서는 UNION 연산자를 사용한다.
예를 들어서, a 테이블과 b 테이블에서 합집합을 구하기 위해서는 다음과 같이 작성한다.<pre><code>SELECT * FROM a
UNION
SELECT * FROM b;</code></pre>이때, 세미콜론은 b 뒤에만 붙여주어야 한다. 두 개의 명령을 UNION으로 합치는 것이기 때문에, 중간에 명령이 끝나면 합집합 연산이 제대로 수행되지 않는다.</li>
</ul>
<p>정렬을 하기 위해 ORDER BY 구를 사용할 때는 두 번째 명렁에만 붙여준다. 이 때, 기준으로 정렬하고자 하는 열의 이름이 서로 다른 경우는 as를 사용하여 이름을 통일시킨 후 사용하면 된다.</p>
<ul>
<li>UNION ALL
UNION 연산의 결과는 DISTINCT를 적용시킨 것처럼 중복되는 결과는 제거된 채로 출력이 된다. 만약 중복되는 결과까지도 출력하고 싶다면, UNION ALL 연산자를 사용하면 된다.</li>
</ul>
<p>교집합과 차집합은 MySQL에서는 지원되지 않는다.</p>
<h3 id="테이블-결합">테이블 결합</h3>
<p>집합 연산은 세로 방향으로 데이터가 늘어나거나 줄어들지만, 테이블 결합은 가로 방향으로 데이터가 늘어난다.
이는 곱집합 연산을 생각하면 되는데, 만약 테이블 x와 y가 존재한다면 x의 요소에 y의 요소를 붙여서 새로운 집합을 생성하는 것이다.</p>
<ul>
<li><p>교차결합
SELECT 구에서 테이블을 두 개 지정하면 자동으로 이 둘은 곱집합으로 계산된다.</p>
<pre><code>SELECT * FROM A, B;</code></pre><p>이렇게 작성하면 A와 B의 곱집합, 즉 교차결합을 한 결과가 출력된다.
이 때의 결과는 UNION은 자료가 세로로 길어지지만, 교차결합은 자료가 가로로 길어진다는 점에서 차이가 있다.</p>
</li>
<li><p>내부결합
두 개의 테이블 뿐만 아니라 원하는 만큼 무한정의 테이블을 교차결합을 통해 계산할 수 있다. 단, 이러면 테이블이 너무 거대해지기 때문에 보통은 내부결합을 더 많이 사용한다.</p>
</li>
<li><p><em>기본키*</em> : 테이블에서 유일한 값을 가지는 열을 말한다. 예를 들어 상품코드와 같은 것이 기본키가 될 수 있다.</p>
</li>
</ul>
<p>내부결합을 하는 방법에는 두 가지가 있다. 하나는 교차집합을 한 뒤 WHERE을 사용하여 원하는 조합을 검색하는 방법이고, 다른 하나는 INNER JOIN 키워드를 사용하여 결합하는 것이다. 전자의 방법은 구식으로 취급되고, 최근은 INNER JOIN을 이용한 결합을 보통 사용한다.</p>
<pre><code>SELECT 상품.상품명, 재고수.재고수 FROM 상품, 재고수
    WHERE 상품.상품코드 = 재고수.상품코드
        AND 상품.상품분류 = &#39;식료품&#39;;</code></pre><p>이 코드는 구식 방법으로 상품과 재고수 테이블에서 둘의 상품코드가 같고, 상품분류가 식료품인 데이터를 상품명과 재고수 열만 출력하는 명령어이다.</p>
<p>같은 명령어를 INNER JOIN을 사용하여 출력하면 다음과 같다.</p>
<pre><code>SELECT 상품.상품명, 재고수.재고수
    FROM 상품 INNER JOIN 재고수
        ON 상품.상품코드 = 재고수.상품코드
    WHERE 상품.상품분류 = &#39;식료품&#39;;</code></pre><p>FROM에서 두 개의 테이블을 바로 넣어서 교차결합을 하는 것이 아니라, 두 테이블 사이에 INNER JOIN 명령어를 넣어준다. 그리고 결합조건을 뒤에 ON을 사용하여 지정한다.</p>
<ul>
<li><p>외부결합
외부결합 또한 교차결합으로 결합 조건을 지정하여 검색한다는 점은 같지만, 여기서는 <strong>어느 한 쪽에만 존재하는 데이터행을 어떻게 다룰지</strong>를 변경 가능하다는 점이 다르다.</p>
<p>만약 왼쪽에만 존재하는 데이터행을 사용하고 싶다면, LEFT JOIN 명령어를 사용하면 된다. 이 때, 오른쪽 테이블에는 존재하지 않는 데이터는 NULL로 표시되게 된다. 반대로 오른쪽에 있는 데이터를 기준으로 하기 위해서는 RIGHT JOIN 명령어를 사용한다.</p>
</li>
</ul>
<h3 id="관계형-모델">관계형 모델</h3>
<p> MySQL과 같은 관계형 데이터베이스는 관계형 모델을 기반으로 작성되었다. 따라서 관계형 모델에 대해 살펴보는 것이 관계형 데이터베이스를 더 깊게 이해하는 데 도움을 준다. 단, 둘에서 사용하는 용어는 일치하지 않는다.</p>
<ul>
<li>관계형 모델 
관계형 모델의 기본적인 요소는 릴레이션이다. 이 때, 릴레이션은 SQL의 <strong>테이블</strong>을 말한다.
각 릴레이션에는 속성이 존재하는데, 이는 SQL의 열과 같다. 그리고 튜플도 존재하는데, 이는 SQL의 행과 같다.</li>
<li>관계대수
릴레이션은 튜플의 집합이며, 릴레이션에 대한 연산이 집합에 대한 연산에 대응된다는 이론을 <strong>관계대수</strong>라고 한다. </li>
</ul>
<p>릴레이션에서의 연산을 SQL의 연산과 일대일 대응시킬 수 있다. 합집합은 UNION, 차집합은 EXCEPT, 교집합은 INTERSECT, 곱집합은 CROSS JOIN, 선택은 WHERE, 투영은 SELECT, 결합은 내부결합에 해당된다.</p>
<h2 id="8장---데이터베이스-설계">8장 - 데이터베이스 설계</h2>
<h3 id="데이터베이스-설계">데이터베이스 설계</h3>
<p>데이터베이스를 설계한다는 것은 데이터베이스의 스키마 내에 테이블, 인덱스, 뷰 등의 데이터베이스 객체를 정의하는 것을 말한다. 
데이터베이스를 설계할 때에는 다음과 같은 것들을 정의해야 한다.</p>
<ul>
<li>논리명과 물리명
열이나 테이블의 이름을 지어도 SQL 명령에는 제약이 존재하기 때문에 이름을 물리명과 논리명으로 나누는 경우가 존재한다. 논리명은 실제로 테이블을 부를 때 사용하는 이름이고, 물리명은 테이블을 작성할 때 사용하는  이름이다.</li>
<li>자료형
수치형 데이터를 저장하는 것이 필요할 때에는 수치형으로 지정하면 된다. 반대로 이름과 같이 알파벳이 들어가는 데이터를 저장할 때에는 문자열형을 사용하면 된다.</li>
<li>고정길이와 가변길이
만약 제조번호처럼 자리수가 고정되어 있는 데이터를 저장할 때에는 고정길이를 사용하면 된다. 반대로, 입력되는 문자열의 변동폭이 클 때에는 가변길이를 사용한다.<ul>
<li>기본키
상품코드처럼 분명한 기본키가 존재한다면 좋지만, 만약 그런 열이 존재하지 않는다면 자동증가 열을 사용하여 기본키로 지정할 수 있다. MySQL에서는 AUTO_INCREMENT를 지정하는 것으로 자동증가 열을 설정할 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="er-다이어그램">ER 다이어그램</h3>
<p>ER 다이어그램이란 엔티티 간의 관계를 나타내는 도식이다. UML의 클래스 다이어그램을 생각하면 이해하기 편하다. 실제로 표현하는 방식도 클래스 다이어그램과 유사하다.</p>
<h3 id="정규화">정규화</h3>
<p>정규화는 테이블을 올바른 형태로 변경하고 분할하는 것을 의미한다. 정규화에는 여러가지 단계가 존재하는데, 이 책에는 세 번째 단계까지 설명하고 있다.</p>
<h4 id="제1정규형">제1정규형</h4>
<p>관계형 데이터베이스의 테이블에는 하나의 셀에 하나의 값만 저장할 수 있다. 따라서 제1정규형에서는 먼저 반복되는 데이터를 세로 방향으로 늘리게 된다. 그 이후, 테이블에 중복되는 값이 존재하므로, 테이블을 분해하는 과정도 이루어진다. </p>
<p>따라서 제1정규형의 과정은 크게 테이블 분할과 기본키 지정으로 이루어져 있다고 말할 수 있다.</p>
<h4 id="제2정규형">제2정규형</h4>
<p>이후 정규화에서는 중복되는 부분을 찾아내어 테이블로 분할해 나가는데, 이 때 기본키에 의해 특정되는 열과 그렇지 않은 열로 나누는 과정을 제2정규형이라고 한다. 어려운 말로는, 함수종속성을 찾아내서 테이블을 분할하는 것을 제2정규화라고 한다.</p>
<h4 id="제3정규형">제3정규형</h4>
<p>기본키 이외의 부분에서 중복이 없는지를 조사한 후, 테이블을 분할하는 것을 제3정규형이라고 한다.</p>
<hr>
<p>이와 같이 정규화를 하는 이유는 <strong>&#39;하나의 데이터는 한 곳에 있어야 한다&#39;</strong>는 규칙을 지키기 위함이다.
데이터가 한 곳에 저장되어 있다면, 수정이 필요할 때에 그 부분만 수정하면 되므로 편리하다.</p>
<h3 id="트랜잭션">트랜잭션</h3>
<p>트랜잭션은, 깃을 사용할 때 커밋이 수정하는 족족 올라가는 것이 아니라 먼저 stage에 올려둔 다음에 한꺼번에 모아서 커밋하는... 그런 과정을 생각하면 된다. 
트랜잭션을 사용해 테이블을 수정하면 만약 데이터 수정 중 에러가 발생했을 시 롤백하여 모든 수정을 초기화 할 수 있어 편리하다.</p>
<p>MySQL에서 트랜젝션을 시작하기 위해서는 다음과 같은 명령을 사용하면 된다.</p>
<pre><code>START TRANSACTION</code></pre><p>이 명령을 사용하면 자동커밋이 꺼지고 이후 COMMIT이나 ROLLBACK 명령을 사용해야만 그 수정한 내용이 적용되거나 사라진다.</p>
]]></description>
        </item>
    </channel>
</rss>