<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>파랑새</title>
    <link>https://hi-bluebird.tistory.com/</link>
    <description>contact</description>
    <language>ko</language>
    <pubDate>Wed, 1 Jul 2026 06:46:24 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>작지만 꾸준한 성장으로</managingEditor>
    <image>
      <title>파랑새</title>
      <url>https://tistory1.daumcdn.net/tistory/8372792/attach/8821e0fbef574f899c9a58865ef0f5db</url>
      <link>https://hi-bluebird.tistory.com</link>
    </image>
    <item>
      <title>[프로그래머스 LV.0] 등수 매기기 &amp;mdash; Python 정답 &amp;amp; 해설</title>
      <link>https://hi-bluebird.tistory.com/26</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 문제 URL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120882&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/120882&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782062689481&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120882&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ikfHj/dJMb8SpRDFv/NVAeNKknNBvJ1S4kbT0F4K/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/jrebj/dJMb8QevPdx/vVOCpjLU28QvHekwqyVxxK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120882&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120882&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ikfHj/dJMb8SpRDFv/NVAeNKknNBvJ1S4kbT0F4K/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/jrebj/dJMb8QevPdx/vVOCpjLU28QvHekwqyVxxK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 문제 요구사항 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어 점수와 수학 점수가 담긴 2차원 배열 score가 주어졌을 때, 각 학생의 평균 점수를 기준으로 등수를 매긴 결과를 반환하는 문제입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입력:&lt;/b&gt; 각 학생의 영어 점수와 수학 점수가 [영어, 수학] 형태로 주어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;처리:&lt;/b&gt; 학생별 평균 점수를 구한 뒤, 높은 평균 점수부터 등수를 매깁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값:&lt;/b&gt; 원래 학생 순서에 맞춰 각 학생의 등수를 담은 배열을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 1) 학생별 평균 점수 구하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;score의 각 원소에는 영어 점수와 수학 점수가 들어 있으므로, 두 점수를 더한 뒤 2로 나누어 평균 점수를 구합니다. 이렇게 구한 평균은 이후 등수를 매기는 기준이 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 2) 평균 점수를 내림차순으로 정렬하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등수는 높은 점수부터 1등이 되므로, 평균 점수 리스트를 내림차순으로 정렬한 별도 리스트를 만듭니다. 원본 평균 리스트는 학생의 기존 순서를 유지해야 하므로 따로 보관합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 3) index()로 등수 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬된 평균 리스트에서 현재 평균 점수가 처음 등장하는 위치를 찾고, 인덱스에 1을 더하면 등수가 됩니다. 같은 평균 점수가 여러 명에게 있으면 index()가 첫 번째 위치를 반환하므로 공동 등수 처리도 자연스럽게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 4. 정답 Python 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1782062625573&quot; class=&quot;properties&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(score):
    avg_list = []
    
    # 각 학생의 평균 점수를 구해서 리스트에 저장
    for s in score:
        eng = s[0]
        math = s[1]
        avg = (eng + math) / 2
        avg_list.append(avg)
        
    # 등수를 매기기 위해 내림차순 정렬된 새 리스트 생성
    sorted_avg = sorted(avg_list, reverse=True)
    
    answer = []
    # 원본 평균 리스트를 돌며 등수 매기기
    for avg in avg_list:
        rank = sorted_avg.index(avg) + 1
        answer.append(rank)
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 풀이하면서 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 학생별 평균을 구한 뒤 직접 등수를 비교해야 하나 생각했는데, 풀이를 정리하다 보니 평균 리스트를 내림차순으로 정렬해 두면 각 평균의 위치만으로 등수를 구할 수 있다는 점이 핵심이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 원본 순서를 유지하는 avg_list와 등수 기준이 되는 sorted_avg를 따로 만들고, sorted_avg.index(avg) + 1로 각 학생의 등수를 계산하는 방식으로 풀었습니다. 같은 평균 점수가 있는 경우에도 index()가 첫 번째 위치를 반환하기 때문에 공동 등수를 자연스럽게 처리할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 같은 흐름을 더 간결하게 표현하고 싶다면 평균 리스트를 리스트 컴프리헨션으로 만들고, 반환값도 리스트 컴프리헨션으로 구성할 수 있습니다. 예를 들어 변수명을 맞춰 avg = [(eng + math) / 2 for eng, math in score]처럼 평균을 만든 뒤, 정렬된 리스트에서 각 평균의 위치를 찾는 방식으로 줄여볼 수 있다는 점도 함께 생각해볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하신 점이나 포스팅에 대한 피드백이 있다면 댓글로 편하게 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 공감이나 댓글 한 줄 남겨주시면 포스팅을 이어가는 데 큰 힘이 됩니다.  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>코딩테스트/알고리즘</category>
      <category>Programmers</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>프로그래머스 lv.0</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/26</guid>
      <comments>https://hi-bluebird.tistory.com/26#entry26comment</comments>
      <pubDate>Mon, 22 Jun 2026 02:27:12 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 LV.1] 완주하지 못한 선수 &amp;mdash; Python 정답 &amp;amp; 해설</title>
      <link>https://hi-bluebird.tistory.com/25</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 문제 URL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42576&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782055106792&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AmIOS/dJMb83SsAn7/XdbGa1mEFXShQaaRKekYmk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/w14zk/dJMb82MMKcF/kKeGbCWTNBfQrt3LP3R4Jk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AmIOS/dJMb83SsAn7/XdbGa1mEFXShQaaRKekYmk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/w14zk/dJMb82MMKcF/kKeGbCWTNBfQrt3LP3R4Jk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 문제 요구사항 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마라톤에 참여한 선수들의 이름이 담긴 participant와 완주한 선수들의 이름이 담긴 completion이 주어졌을 때, 완주하지 못한 선수 한 명의 이름을 찾아 반환하는 문제입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입력:&lt;/b&gt; participant에는 전체 참가자 이름이, completion에는 완주한 선수 이름이 담겨 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조건:&lt;/b&gt; completion의 길이는 participant보다 1 작고, 참가자 중에는 동명이인이 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값:&lt;/b&gt; 완주하지 못한 선수의 이름을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 1) 이름별 등장 횟수 세기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에서는 같은 이름을 가진 참가자가 있을 수 있기 때문에 단순히 이름이 존재하는지만 확인하면 안 됩니다. 각 이름이 participant에 몇 번 등장했는지를 딕셔너리에 저장해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 2) 완주자 명단을 기준으로 카운트 줄이기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;participant를 순회하며 이름별 인원 수를 세어 둔 뒤, completion을 순회하면서 완주한 선수의 카운트를 하나씩 줄입니다. 이렇게 하면 완주한 사람은 0이 되고, 완주하지 못한 사람만 양수로 남게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 3) 남은 값이 있는 이름 반환하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 딕셔너리를 순회하면서 값이 0보다 큰 이름을 찾으면 됩니다. 문제에서 완주하지 못한 선수는 정확히 한 명이라고 했으므로, 해당 이름을 발견하는 즉시 반환할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 4. 정답 Python 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1782054850121&quot; class=&quot;applescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(participant, completion):
    person = {}
    
    for name in participant:
        person[name] = person.get(name, 0) + 1
        
    for name in completion:
        person[name] -= 1
        
    for name in person:
        if person[name] &amp;gt; 0:
            return name&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 풀이하면서 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 참가자 명단과 완주자 명단의 차이를 단순히 비교하면 된다고 생각할 수 있는데, 풀이를 정리하다 보니 같은 이름이 여러 번 등장할 수 있다는 조건이 핵심이라는 점을 떠올릴 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 participant를 먼저 순회하며 이름별 인원 수를 딕셔너리에 저장하고, completion을 순회하면서 완주한 사람 수를 하나씩 줄이는 방식으로 풀었습니다. 마지막에 값이 0보다 큰 이름이 완주하지 못한 선수이므로 바로 반환할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, collections.Counter를 사용하면 같은 아이디어를 더 짧게 표현할 수도 있다는 점을 이후에 생각해볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하신 점이나 포스팅에 대한 피드백이 있다면 댓글로 편하게 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 공감이나 댓글 한 줄 남겨주시면 포스팅을 이어가는 데 큰 힘이 됩니다.  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>코딩테스트/알고리즘</category>
      <category>Programmers</category>
      <category>Python</category>
      <category>python3</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>프로그래머스 lv.1</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/25</guid>
      <comments>https://hi-bluebird.tistory.com/25#entry25comment</comments>
      <pubDate>Mon, 22 Jun 2026 00:17:56 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 LV.0] 문자열 밀기 &amp;mdash; Python 정답 &amp;amp; 해설</title>
      <link>https://hi-bluebird.tistory.com/24</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 문제 URL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120921&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/120921&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 문제 요구사항 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 A와 B가 주어졌을 때, A를 오른쪽으로 몇 번 밀어야 B가 되는지 구하는 문제입니다. 만들 수 있다면 필요한 최소 횟수를 반환하고, 어떤 방식으로 밀어도 B가 될 수 없다면 -1을 반환해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입력:&lt;/b&gt; 길이가 같은 두 문자열 A, B가 주어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;처리:&lt;/b&gt; A를 오른쪽으로 회전시켜 B와 같아질 수 있는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값:&lt;/b&gt; 가능하면 최소 회전 횟수, 불가능하면 -1을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 1) 회전 문자열의 특징 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열을 오른쪽으로 밀어 만든 결과들은 원래 문자열의 순환된 형태입니다. 예를 들어 B를 두 번 이어 붙인 B + B 안에는 B에서 시작할 수 있는 모든 순환 형태가 연속된 부분 문자열로 들어 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 2) B + B에서 A의 위치 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A를 오른쪽으로 밀어 B를 만들 수 있다는 것은, 반대로 보면 A가 B + B 안에 부분 문자열로 존재한다는 뜻입니다. 이때 A가 처음 등장하는 위치가 A를 오른쪽으로 밀어야 하는 최소 횟수가 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 3) find()의 반환값 활용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python의 find()는 찾는 문자열이 있으면 시작 인덱스를 반환하고, 없으면 -1을 반환합니다. 문제에서 요구하는 반환값과 정확히 맞아떨어지기 때문에 별도의 조건문 없이 그대로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 4. 정답 Python 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1781875229333&quot; class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(A, B):
    # B를 두 번 더한 문자열에서 A의 시작 위치를 찾음
    return (B + B).find(A)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 풀이하면서 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 A를 한 칸씩 직접 밀면서 B와 같은지 확인하는 방식으로 풀 수 있다고 생각했습니다. 그런데 풀이를 정리하다 보니, 회전 문자열은 두 번 이어 붙인 문자열 안에서 부분 문자열로 확인할 수 있다는 점을 활용하면 훨씬 간단하게 풀 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 B + B에서 A가 처음 등장하는 위치를 찾는 방식으로 구현했습니다. find()가 문자열을 찾지 못하면 -1을 반환해 주기 때문에, 문제의 예외 처리까지 자연스럽게 해결되는 점이 깔끔하다고 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, deque를 이용해 직접 회전시키며 비교하는 방식으로도 접근할 수 있지만, 이번 문제에서는 문자열의 순환 구조를 이용하는 방식이 더 간결하다는 점을 이후에 생각해볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하신 점이나 포스팅에 대한 피드백이 있다면 댓글로 편하게 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 공감이나 댓글 한 줄 남겨주시면 포스팅을 이어가는 데 큰 힘이 됩니다.  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>코딩테스트/알고리즘</category>
      <category>Programmers</category>
      <category>Python</category>
      <category>python3</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>프로그래머스 lv.0</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/24</guid>
      <comments>https://hi-bluebird.tistory.com/24#entry24comment</comments>
      <pubDate>Fri, 19 Jun 2026 22:22:43 +0900</pubDate>
    </item>
    <item>
      <title>[Deep Learning] 경사 하강법과 Adam 옵티마이저 차이: 수식과 코드로 이해하는 장단점 비교</title>
      <link>https://hi-bluebird.tistory.com/23</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 먼저, 이 질문은 무엇을 묻고 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradient Descent(경사하강법), Adam(아담), Optimizer(옵티마이저) 같은 단어들은 모두 &quot;인공지능이 어떤 원리로 똑똑해지는가?&quot;라는 본질적인 문제를 해결하기 위해 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝에서 학습(Training)이란 모델이 입력 데이터를 보고 예측한 값과 실제 정답을 비교하여, 틀린 만큼 내부 설정을 고쳐나가며 정답률을 올리는 반복적인 과정 전체를 의미한다. 이 흐름을 이해하기 위해서는 핵심 구성 요소인 '손실 함수'와 '옵티마이저'의 역할을 명확히 정리해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예측의 기준이 되는 '손실 함수(Loss Function)'&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인공지능 모델은 스스로의 예측이 정확한지 판단하지 못한다. 모델이 낸 예측값과 실제 정답의 &lt;b&gt;차이와 불일치 정도를 정량적으로 계산하여 현재 모델이 얼마나 틀렸는지를 하나의 오차 수치로 알려주는 채점 기준표가 바로 손실 함수&lt;/b&gt;다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;손실 함수는 해결하려는 문제의 성격에 따라 크게 두 가지로 분류된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;회귀(Regression) 문제용 손실 함수 (연속된 숫자를 맞출 때)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MSE&lt;/b&gt; (Mean Squared Error, 평균제곱오차): 큰 오차를 더 강하게 반영하여 강조하고 싶을 때 적합하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MAE&lt;/b&gt; (Mean Absolute Error, 평균절대오차): 일반적인 오차의 크기를 평이하게 평가할 때 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분류(Classification) 문제용 손실 함수 (카테고리를 맞출 때)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이진 크로스 엔트로피&lt;/b&gt; (Binary Cross-Entropy): 정답 확률 분포가 두 가지 중 하나(예: Pass/Fail)일 때 정답과의 불일치를 측정한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;범주형 크로스 엔트로피&lt;/b&gt; (Categorical Cross-Entropy): 정답 확률 분포가 여러 개의 카테고리 중 하나일 때 정답과의 불일치를 측정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵티마이저에게 필요한 기울기를 계산하는 '역전파(Backpropagation)'&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;손실 함수가 오차를 계산하면 옵티마이저가 곧바로 가중치를 수정할 수 있을까? 그렇지 않다. 손실 함수는 단순히 &quot;현재 모델이 얼마나 틀렸는지&quot;를 하나의 수치로 알려줄 뿐이다. 하지만 어느 가중치가 이 오차에 얼마나 영향을 주었는지, 그리고 그 가중치를 어느 방향으로 조정해야 손실이 줄어드는지는 알려주지 않는다.&lt;/li&gt;
&lt;li&gt;이때 필요한 과정이 바로 역전파(Backpropagation)다. 역전파는 손실 함수에서 계산된 손실값을 기준으로, 모델의 출력층에서 입력층 방향으로 거꾸로 미분을 수행한다. 이 과정에서 각 파라미터, 즉 가중치와 편향이 손실에 얼마나 영향을 주었는지를 나타내는 기울기(Gradient)를 계산한다. 이 기울기는 옵티마이저가 파라미터를 업데이트하기 위해 참고하는 핵심 정보(설명서)가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설명서를 받아 실행하는 '옵티마이저(Optimizer)'&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실 함수는 단순히 &quot;현재 이만큼 틀렸다&quot;는 결과물인 손실 값만 제공할 뿐이다. 그렇다면 모델은 이 손실 값을 어떻게 줄여나갈 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 내부에 있는 가중치(Weight)와 편향(Bias)이라는 파라미터를 조정하면서 예측을 바꾼다. 이 가중치와 편향을 어떤 방향으로, 얼마나 크게 움직여야 가장 빠르고 안정적으로 오차의 최저점에 도달할 수 있을지 이동 법칙을 결정하고, 그 결과에 맞춰 모델 내부의 가중치와 편향을 '실제로 수정(업데이트)'하는 알고리즘이 바로 옵티마이저(Optimizer)다.&lt;/p&gt;
&lt;div style=&quot;background-color: #f7f7f7;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  핵심 요약&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;손실 함수:&lt;/b&gt; 현재 모델이 얼마나 틀렸는지 알려주는 정량적 &lt;b&gt;'기준'&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;옵티마이저:&lt;/b&gt; 그 오차를 줄이기 위해 모델의 파라미터를 어떻게 수정할지 결정하는 &lt;b&gt;'방법'&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 경사하강법(Gradient Descent): 손실을 줄이는 가장 기본적인 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 옵티마이저의 뿌리이자 가장 클래식한 알고리즘이 바로 경사하강법(Gradient Descent)이다. 앞서 1장에서 살펴본 파이프라인을 떠올려보면, 경사하강법은 &lt;b&gt;3단계 역전파(Backpropagation) 단계로부터 각 파라미터의 기울기(Gradient) 정보를 고스란히 넘겨받아 4단계 최적화(Optimization)를 수행하는 주체&lt;/b&gt;다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  경사하강법의 작동 범위와 메커니즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경사하강법이 정확히 어떤 데이터를 받고 무엇을 실행하는지 경계를 명확히 하면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전달받는 것 (Input):&lt;/b&gt; 역전파 알고리즘이 계산해낸 현재 파라미터 위치에서의 &lt;b&gt;기울기(Gradient)&lt;/b&gt; 정보&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수행하는 것 (Action):&lt;/b&gt; &lt;b&gt;동일한 학습률(Learning Rate) 기준 하에&lt;/b&gt;, 기울기가 가리키는 반대 방향(내리막길)으로 가중치와 편향 값을 직접 수정(업데이트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적으로 기울기(Gradient)는 함수 값이 가장 가파르게 '증가'하는 방향을 나타낸다. 따라서 우리의 목표인 손실 함수의 최저점을 찾으려면 기울기 값에 마이너스(-)를 붙여 반대 방향으로 파라미터를 이동시켜야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  수식과 코드의 1:1 직관적 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경사하강법의 수학적 공식과 이를 컴퓨터가 실행할 수 있도록 구현한 파이썬 코드를 매칭해 보면 그 구조를 아주 명확하게 이해할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 경사하강법 옵티마이저가 파라미터를 업데이트하는 식&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/49nD8/dJMcabduPyA/6WAqf2LtymjmK1Gb11z6jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/49nD8/dJMcabduPyA/6WAqf2LtymjmK1Gb11z6jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/49nD8/dJMcabduPyA/6WAqf2LtymjmK1Gb11z6jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F49nD8%2FdJMcabduPyA%2F6WAqf2LtymjmK1Gb11z6jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;68&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다음 파라미터 = 현재 파라미터 - 학습률 &amp;times; 현재 위치에서의 손실 함수 기울기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;theta; (Theta / theta)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델 내부의 가중치(Weight)나 편향(Bias)을 의미하는 파라미터.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nabla;L(&amp;theta;) (Gradient / gradients)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역전파 단계에서 계산되어 넘어온 &lt;b&gt;손실 함수의 기울기.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;현재 위치에서 손실이 가장 빠르게 증가하는 방향, 즉 오르막길 방향을 알려주는 지표다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;eta; (Eta / learning_rate)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 직접 설정하는 &lt;b&gt;학습률&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;파라미터를 한 번에 얼마나 크게 움직일지 결정하는 보폭의 기준 역할을 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 파이썬 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1781872946834&quot; class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import time

# [4단계: 최적화] 오직 기울기(gradient)만 넘겨받아 파라미터를 수정하는 경사하강법 함수
def gradient_descent(theta, learning_rate, gradient):
    &quot;&quot;&quot;
    &amp;theta;_next = &amp;theta;_current - (learning_rate * gradient)
    &quot;&quot;&quot;
    # 동일한 학습률 기준 하에, 넘어온 기울기 크기에 비례하여 내리막길(-) 방향으로 업데이트
    next_theta = theta - learning_rate * gradient
    return next_theta

# ==========================================
# 코랩 실습 시뮬레이션 환경 세팅
# ==========================================
current_theta = 10.0   # 시작 가중치 (산 꼭대기 위치)
learning_rate = 0.1    # 보폭 기준 값
total_steps = 3        # 시뮬레이션할 가상 루프 횟수

print(&quot;=&quot; * 50)
print(f&quot;  경사하강법(GD) 최적화 시뮬레이션을 시작합니다.&quot;)
print(f&quot;   - 시작 가중치(theta): {current_theta}&quot;)
print(f&quot;   - 고정 학습률(learning_rate): {learning_rate}&quot;)
print(&quot;=&quot; * 50)
time.sleep(0.5)

# 가상으로 역전파가 매 회차 다른 기울기를 계산해서 던져주는 루프
for step in range(1, total_steps + 1):
    
    # [3단계 역전파 가정] 최저점에 가까워질수록 기울기(경사)가 점점 완만해진다고 가정한 가상 데이터
    if step == 1:   calculated_gradient = 8.0   # 처음엔 매우 가파름
    elif step == 2: calculated_gradient = 5.0
    elif step == 3: calculated_gradient = 3.0
    
    print(f&quot;\n  [Iteration {step}]&quot;)
    print(f&quot;    [3단계 역전파 완료] 계산된 기울기(Gradient) = {calculated_gradient}&quot;)
    
    # [4단계 최적화] 경사하강법으로 가중치 실제 수정 및 갱신
    updated_theta = gradient_descent(current_theta, learning_rate, calculated_gradient)
    
    # 로그 출력으로 변화 확인
    print(f&quot;  ⚙️  [4단계 최적화 완료] 연산 식: {current_theta:.2f} - ({learning_rate} * {calculated_gradient})&quot;)
    print(f&quot;    [파라미터 갱신 결과] theta 상태 변경: {current_theta:.2f} ➡️ {updated_theta:.2f}&quot;)
    
    # 다음 회차를 위해 현재 가중치를 업데이트된 값으로 교체
    current_theta = updated_theta
    time.sleep(0.3)

print(&quot;\n&quot; + &quot;=&quot; * 50)
print(f&quot;  최종 시뮬레이션 종료! 정답 바닥에 안착한 theta: {current_theta:.2f}&quot;)
print(&quot;=&quot; * 50)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ Gradient Descent의 한계점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원리가 단순하고 직관적이라는 강력한 장점이 있지만, 실제 복잡한 딥러닝 모델의 손실 함수 지형에서는 다음과 같은 명확한 한계에 부딪힌다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고:&lt;br /&gt;&lt;a href=&quot;https://ompramod.medium.com/mastering-gradient-descent-optimizing-neural-networks-with-precision-e461e996633e&quot;&gt;Mastering Gradient Descent: Optimizing Neural Networks with Precision&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 로컬 미니마(Local Minima)와 안장점(Saddle Point)의 덫&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오직 현재 시점에 눈앞에 보이는 기울기 정보만 보고 발을 내딛기 때문에 지형의 함정에 쉽게 빠진다. 진짜 최저점(Global Minimum)으로 가는 길목에 있는 얕은 웅덩이(&lt;b&gt;Local Minima&lt;/b&gt;)를 만나거나, 말의 안장처럼 사방이 평평해져 기울기가 0이 되어버리는 평지(&lt;b&gt;안장점, Saddle Point&lt;/b&gt;)를 만나면 그곳이 정답인 줄 알고 학습을 멈춰버리는 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YzPCI/dJMcahkBydS/Gmp0cGrxK53BTALUUq0261/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YzPCI/dJMcahkBydS/Gmp0cGrxK53BTALUUq0261/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YzPCI/dJMcahkBydS/Gmp0cGrxK53BTALUUq0261/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYzPCI%2FdJMcahkBydS%2FGmp0cGrxK53BTALUUq0261%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1160&quot; height=&quot;522&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Adam: 과거 gradient를 기억하는 개선된 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Adam(Adaptive Moment Estimation, 적응적 모멘트 추정)은 현대 딥러닝 연구와 실무 환경에서 가장 범용적으로 선택되는 옵티마이저다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 경사하강법이 오직 현재 시점의 단일 기울기(Gradient) 정보만을 파라미터 업데이트에 반영하는 한계를 가졌다면, Adam은 &lt;b&gt;과거 계산된 기울기들의 통계적 방향성과 크기 정보를 지수 이동 평균(Exponential Moving Average) 형태로 누적하여 활용&lt;/b&gt;하는 알고리즘이다. 이를 위해 Adam은 기울기의 방향성을 제어하는 기능과 파라미터별 보폭을 조절하는 두 가지 핵심 메커니즘을 결합했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Adam의 작동 범위와 메커니즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Adam이 정확히 어떤 데이터를 받고 무엇을 실행하는지 경계를 명확히 하면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전달받는 것 (Input):&lt;/b&gt; 역전파 알고리즘이 계산해낸 현재 파라미터 위치에서의 기울기(Gradient) 정보&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수행하는 것 (Action):&lt;/b&gt; 과거 기울기들을 기반으로 계산된 '방향(1차 모멘트)'과 파라미터별 '맞춤형 보폭(2차 모멘트)'을 조합하여 가중치와 편향 값을 직접 수정(업데이트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적으로 단순히 현재의 경사면에만 순종하는 GD와 달리, Adam은 직전까지 내려오던 흐름을 유지하려는 관성을 연산에 개입시키고 지형의 가파른 정도에 따라 학습률을 스스로 격차를 두어 조절한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  수식과 코드의 1:1 직관적 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Adam의 수학적 공식과 이를 컴퓨터가 실행할 수 있도록 구현한 파이썬 코드를 매칭해 보면 파이토치(PyTorch) 내부에서 어떤 연산이 일어나는지 명확히 이해할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Adam 옵티마이저가 파라미터를 업데이트하는 식&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GmJfx/dJMcag0bH2I/APKEijFInap1h5KcHXqwAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GmJfx/dJMcag0bH2I/APKEijFInap1h5KcHXqwAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GmJfx/dJMcag0bH2I/APKEijFInap1h5KcHXqwAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGmJfx%2FdJMcag0bH2I%2FAPKEijFInap1h5KcHXqwAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;161&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&amp;theta; (Theta / theta):&lt;/b&gt; 모델 내부의 가중치(Weight)나 편향(Bias)을 의미하는 파라미터.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;g_t (Gradient / gradient):&lt;/b&gt; 역전파 단계에서 계산되어 넘어온 현재 시점(t)의 손실 함수 기울기.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;m_t / m_hat_t (1차 모멘트):&lt;/b&gt; 과거 기울기들의 지수 이동 평균. 업데이트의 '최종 방향(관성)'을 결정한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;v_t / v_hat_t (2차 모멘트):&lt;/b&gt; 과거 기울기 제곱들의 지수 이동 평균. 파라미터별 '적응적 보폭'을 결정한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;eta; (Eta / lr):&lt;/b&gt; 사용자가 지정하는 기본 학습률.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;beta;1, &amp;beta;2 (beta1, beta2):&lt;/b&gt; 과거 기억을 얼마나 남겨두고 축적할지 결정하는 하이퍼파라미터.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;epsilon; (Epsilon / eps):&lt;/b&gt; 분모가 0이 되는 연산 에러를 막기 위한 수치 안정성 상수.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 파이썬 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1781872946835&quot; class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import numpy as np

# 1. 환경 세팅 (PyTorch Adam 기반 파라미터)
lr = 0.1            # 기본 학습률 (보폭 기준)
beta1, beta2 = 0.9, 0.5  # beta2를 0.5로 낮춰 3번 만에 보폭 변화가 보이도록 설정
eps = 1e-08

# 기억 장치 및 시작 가중치 초기화
m, v = 0.0, 0.0
theta = 10.0

# 가상의 절벽 지형: 1회차에 엄청 가파르다가, 2회차부터 순식간에 평지가 됨
gradients = [20.0, 0.1, 0.1] 

print(&quot;-&quot; * 60)
print(f&quot;  Adam 최적화 시작 (시작 가중치: {theta} | 학습률: {lr})&quot;)
print(&quot;-&quot; * 60)

for step in range(1, 4):
    grad = gradients[step - 1]
    
    # [방향 누적] 과거 기울기 흐름 기록
    m = beta1 * m + (1 - beta1) * grad
    
    # [보폭 누적] 과거 기울기 제곱 흐름 기록
    v = beta2 * v + (1 - beta2) * (grad ** 2)
    
    # [초기 편향 보정]
    m_hat = m / (1 - beta1 ** step)
    v_hat = v / (1 - beta2 ** step)
    
    # [최적화 실행] 맞춤형 보폭을 반영하여 업데이트
    next_theta = theta - (lr / (np.sqrt(v_hat) + eps)) * m_hat
    
    # 변동량 계산 (현재 위치에서 얼마나 멀리 이동했는가)
    step_size = abs(next_theta - theta)
    
    print(f&quot;[Iteration {step}] 입력 기울기: {grad}&quot;)
    print(f&quot;  - 현재 가중치 상태 : {theta:.2f} ➡️ {next_theta:.2f}&quot;)
    print(f&quot;  - 실제 파라미터 보폭: {step_size:.4f}&quot;)
    print(&quot;-&quot; * 60)
    
    theta = next_theta&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ Adam의 한계점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지형의 한계를 똑똑하게 극복하는 만능 옵티마이저처럼 보이지만, 실제 학습 환경에서는 다음과 같은 명확한 부작용을 마주한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고:&lt;br /&gt;&lt;a href=&quot;https://ompramod.medium.com/mastering-gradient-descent-optimizing-neural-networks-with-precision-749e24c483f4&quot;&gt;Mastering Gradient Descent: Optimizing Neural Networks with Precision&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 일반화(Generalization) 성능의 한계 (막판 스퍼트 부실)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 초&amp;middot;중반에는 온갖 함정을 다 피해 다니며 압도적으로 빠르게 오차를 줄여나간다. 하지만 학습 후반부에 접어들었을 때, 최종 테스트 데이터에 대한 정확도가 투접한 경사하강법(SGD) 계열에 밀리는 현상이 자주 발생한다. 파라미터별로 보폭을 독립적으로 계속 바꾸는 적응적 특성 때문에, 마지막 정밀 튜닝 구간에서 가장 깊고 정교한 최적점(Global Minimum)의 정중앙에 안착하지 못하고 주변을 미세하게 헤매는 경향이 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 극심한 GPU 메모리(VRAM) 소모&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 경사하강법은 현재 가중치 파라미터의 위치만 메모리에 들고 있으면 된다. 반면 Adam은 모든 파라미터마다 1차 모멘트(m, 방향) 주머니와 2차 모멘트(v, 보폭) 주머니를 하나씩 더 차고 있어야 한다. 즉, 기억해야 할 변수가 3배로 늘어나기 때문에 대규모 모델(LLM 등)을 학습할 때 GPU 메모리 압박이 매우 심하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Gradient Descent와 Adam의 알고리즘적 차이와 선택 기준&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  알고리즘적 핵심 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 알고리즘의 본질적인 차이는 역전파로 계산된 기울기(g_t)를 받아 과거의 이동 기록을 업데이트에 반영하는가에 있다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Gradient Descent (경사하강법)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방향:&lt;/b&gt; 현재 시점의 기울기 방향만을 기준으로 움직인다. 이 때문에 안장점이나 로컬 미니마 부근처럼 기울기가 매우 작아지는 구간에서는 학습이 느려지거나 멈춘 것처럼 보일 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보폭:&lt;/b&gt; 모든 파라미터에 사용자가 지정한 학습률(lr)을 동일하게 적용한다. 손실 함수 지형이 길고 좁은 계곡처럼 생긴 경우, 특정 방향으로 지그재그로 움직이며 수렴이 느려질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Adam (Adaptive Moment Estimation)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방향:&lt;/b&gt; 과거 기울기들의 이동 평균인 1차 모멘트를 사용해 이전 이동 방향을 어느 정도 기억한다. 덕분에 순간적으로 기울기가 작아지는 구간에서도 더 안정적으로 이동할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보폭:&lt;/b&gt; 과거 기울기 제곱의 이동 평균인 2차 모멘트를 사용해 파라미터별 업데이트 크기를 조절한다. 변화가 큰 파라미터는 조심스럽게, 변화가 작은 파라미터는 상대적으로 크게 업데이트할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연산 메커니즘 대조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 항목 Gradient Descent (경사하강법) Adam&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;핵심 판단 기준&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;현재 기울기&lt;/td&gt;
&lt;td&gt;과거 기울기 정보 + 현재 기울기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;연산 직관&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;theta - lr * gradient&lt;/td&gt;
&lt;td&gt;theta - (lr / 적응적 보폭) * 관성 방향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;메모리 소모&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;적음&lt;/td&gt;
&lt;td&gt;큼. 파라미터별 1차&amp;middot;2차 모멘트를 추가로 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장단점 및 실무 선택 기준&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) Gradient Descent / SGD 계열&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 구조가 단순하고 메모리 사용량이 적다. Momentum, learning rate scheduler 등을 잘 조합하면 학습 후반부에 안정적으로 수렴하며 좋은 일반화 성능을 보이는 경우가 많다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 학습률 설정에 민감하고, 초기 수렴 속도가 Adam보다 느릴 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실무 선택 기준:&lt;/b&gt; CNN 기반 이미지 분류처럼 최종 일반화 성능을 중요하게 보는 문제에서 자주 고려된다. 충분한 학습 시간과 튜닝 여유가 있을 때 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) Adam&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 초기 수렴 속도가 빠르고, 파라미터별로 업데이트 크기를 조절하기 때문에 학습률 설정 부담이 상대적으로 적다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 파라미터마다 1차&amp;middot;2차 모멘트를 저장해야 하므로 메모리 사용량이 크다. 경우에 따라 SGD 계열보다 일반화 성능이 낮게 나올 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실무 선택 기준:&lt;/b&gt; NLP, Transformer, LLM 파인튜닝처럼 파라미터 수가 많고 손실 지형이 복잡한 모델에서 널리 사용된다. 현재는 Adam에 weight decay를 더 적절하게 적용한 AdamW가 실무에서 자주 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT</category>
      <category>Adam</category>
      <category>deep learning</category>
      <category>gradient descent</category>
      <category>Machine learning</category>
      <category>Optimizer</category>
      <category>경사하강법</category>
      <category>딥러닝</category>
      <category>머신러닝</category>
      <category>옵티마이저</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/23</guid>
      <comments>https://hi-bluebird.tistory.com/23#entry23comment</comments>
      <pubDate>Fri, 19 Jun 2026 22:14:09 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 LV.2] 괄호 회전하기 &amp;mdash; Python 정답 &amp;amp; 해설</title>
      <link>https://hi-bluebird.tistory.com/22</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 문제 URL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/76502&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/76502&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781614182541&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/76502&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LJBxw/dJMb8XSeMPr/7F3FMIhNSou2jEbkmNQsAK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bqLFDD/dJMb8T98CJJ/5M06d6EDugeNpzXNKZJk20/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/76502&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/76502&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LJBxw/dJMb8XSeMPr/7F3FMIhNSou2jEbkmNQsAK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bqLFDD/dJMb8T98CJJ/5M06d6EDugeNpzXNKZJk20/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 문제 요구사항 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대괄호, 중괄호, 소괄호로 이루어진 문자열 s가 주어졌을 때, 문자열을 왼쪽으로 x칸 회전했을 때 올바른 괄호 문자열이 되는 경우의 수를 구하는 문제입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;회전 범위:&lt;/b&gt; x는 0부터 s의 길이 - 1까지이며, 모든 회전 경우를 확인해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검증 기준:&lt;/b&gt; 여는 괄호와 닫는 괄호의 종류와 순서가 모두 올바르게 맞아야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값:&lt;/b&gt; 올바른 괄호 문자열이 되는 회전 횟수의 개수를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 1) 문자열 회전 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 x에 대해 문자열을 왼쪽으로 회전한 결과를 확인해야 합니다. Python에서는 s[i:] + s[:i] 형태로 i번째 위치부터 끝까지의 문자열과 앞부분 문자열을 이어 붙이면 회전된 문자열을 만들 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 2) 스택으로 괄호 유효성 검사하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 괄호 문자열인지 확인할 때는 스택을 사용합니다. 여는 괄호가 나오면 stack에 넣고, 닫는 괄호가 나오면 stack의 마지막 값이 짝이 맞는 여는 괄호인지 확인합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 3) 실패 조건을 빠르게 처리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닫는 괄호가 나왔는데 stack이 비어 있거나, stack의 마지막 괄호와 짝이 맞지 않으면 해당 회전 문자열은 더 볼 필요가 없습니다. 이때 is_valid를 False로 바꾸고 바로 반복을 종료하면 불필요한 검사를 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 4. 정답 Python 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1781614119013&quot; class=&quot;gradle&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(s):
    # 대괄호, 중괄호, 그리고 소괄호로 이루어진 문자열 s
    # 왼쪽으로 x칸 만큼 회전시켜야 올바른 괄호 문자열이 되게 하고, x 출력
    # 0 ~ len(s) 까지 수행. 맞으면 카운트 +1 누적
    x = 0
    pair = {']': '[', '}': '{', ')': '('}
    
    
    for i in range(len(s)):
        shifted = s[i:] + s[:i]
        
        
        stack = []
        is_valid = True
        
        
        for char in shifted:
            if char in ['[', '{', '(']:
                stack.append(char)
            else:
                if not stack or stack[-1] != pair[char]:
                    is_valid = False
                    break
                else:
                    stack.pop() 
        
        
        if is_valid and not stack:
            x += 1
    
    return x&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 풀이하면서 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 문자열을 회전시키는 부분이 핵심이라고 생각했는데, 풀이를 정리하다 보니 실제로 중요한 부분은 각 회전 결과가 올바른 괄호 문자열인지 안정적으로 검증하는 과정이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 각 회전마다 stack을 새로 만들고, 닫는 괄호가 나올 때마다 직전 여는 괄호와 짝이 맞는지 확인하는 방식으로 풀었습니다. 조건이 맞지 않는 순간 바로 break하도록 처리해서 불필요한 검사를 줄일 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 문자열을 매번 새로 만드는 대신 인덱스를 순환시키는 방식으로도 접근할 수 있다는 점을 이후에 생각해볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하신 점이나 포스팅에 대한 피드백이 있다면 댓글로 편하게 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 공감이나 댓글 한 줄 남겨주시면 포스팅을 이어가는 데 큰 힘이 됩니다.  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>코딩테스트/알고리즘</category>
      <category>Programmers</category>
      <category>Python</category>
      <category>python3</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>프로그래머스 lv.2</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/22</guid>
      <comments>https://hi-bluebird.tistory.com/22#entry22comment</comments>
      <pubDate>Tue, 16 Jun 2026 21:51:46 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 LV.0] 유한소수 판별하기 &amp;mdash; Python 정답 &amp;amp; 해설</title>
      <link>https://hi-bluebird.tistory.com/21</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 문제 URL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120878&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/120878&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781362223928&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120878&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjWumd/dJMb9jgFQ3g/nkKxlGk9wocVTCLAuIhIK1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bzwGZU/dJMb9kmk6Sc/qjfOr2wMjRz9kakKOJdgXK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120878&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120878&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjWumd/dJMb9jgFQ3g/nkKxlGk9wocVTCLAuIhIK1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bzwGZU/dJMb9kmk6Sc/qjfOr2wMjRz9kakKOJdgXK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 문제 요구사항 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 정수 a와 b가 주어졌을 때, 분수 a / b가 유한소수로 표현될 수 있는지 판별하는 문제입니다. 유한소수라면 1을, 무한소수라면 2를 반환해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;핵심 조건:&lt;/b&gt; 기약분수로 만들었을 때 분모의 소인수가 2와 5만 남아야 유한소수입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;약분 처리:&lt;/b&gt; a와 b의 최대공약수로 b를 나누어 기약분수의 분모를 구합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값:&lt;/b&gt; 분모에서 2와 5를 모두 제거한 뒤 1이 남으면 1, 다른 값이 남으면 2를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 1) 최대공약수로 기약분수 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유한소수 여부는 원래 분모가 아니라 기약분수로 바꾼 뒤의 분모를 기준으로 판단해야 합니다. 따라서 math.gcd(a, b)를 이용해 a와 b의 최대공약수를 구하고, b를 그 값으로 나누어 약분된 분모 n을 만듭니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 2) 유한소수의 분모 조건 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 분수가 유한소수가 되려면 기약분수의 분모가 2와 5만을 소인수로 가져야 합니다. 10이 2 &amp;times; 5로 이루어져 있기 때문에, 분모를 10의 거듭제곱 형태로 만들 수 있는 경우에만 소수점 아래가 유한하게 끝납니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 3) 2와 5를 반복해서 제거하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약분된 분모 n에서 2로 나누어떨어지는 동안 계속 2를 제거하고, 5로 나누어떨어지는 동안 계속 5를 제거합니다. 이 과정을 마친 뒤 n이 1이면 2와 5 외의 소인수가 없다는 뜻이므로 유한소수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 4. 정답 Python 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1781361961743&quot; class=&quot;angelscript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import math 

def solution(a, b):
    ## 유한소수면 1, 아니면 2
    gcd_num = math.gcd(a, b)
    n = b // gcd_num
    
    while n % 2 == 0:
        n //= 2
        
    while n % 5 == 0:
        n //= 5
    
    return 1 if n ==1 else 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 풀이하면서 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 분모에 2와 5만 있는지 확인하면 된다고 단순하게 생각할 수 있는데, 풀이를 정리하다 보니 먼저 기약분수로 약분하는 과정이 핵심이라는 점을 떠올릴 수 있었습니다. 분모가 처음에는 복잡해 보여도 약분하고 나면 유한소수 조건을 만족할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 최대공약수로 분모를 줄인 뒤, 남은 분모에서 2와 5를 하나씩 걷어내는 방식으로 풀었습니다. 수학 개념을 코드로 옮기는 문제라서 어렵지는 않았지만, 조건을 확인하는 순서를 놓치면 틀리기 쉬운 문제라고 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, math.gcd() 함수를 이용하는게 아닌, &lt;b&gt;유클리드 호제법&lt;/b&gt;으로도 풀이가 가능하다는 점을&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하신 점이나 포스팅에 대한 피드백이 있다면 댓글로 편하게 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 공감이나 댓글 한 줄 남겨주시면 포스팅을 이어가는 데 큰 힘이 됩니다.  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>코딩테스트/알고리즘</category>
      <category>Programmers</category>
      <category>Python</category>
      <category>python3</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>프로그래머스 lv.0</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/21</guid>
      <comments>https://hi-bluebird.tistory.com/21#entry21comment</comments>
      <pubDate>Sat, 13 Jun 2026 23:49:55 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 LV.0] 배열 만들기 2 &amp;mdash; Python 정답 &amp;amp; 해설</title>
      <link>https://hi-bluebird.tistory.com/20</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 문제 URL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181921&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/181921&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781361952508&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181921&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/MaxPn/dJMb9gxtXjI/2sw3lZ3VDkOfpwGPZqhUCk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/gRQMv/dJMb9lMjYbz/WiKIYBlytBwJo6kVIeB411/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181921&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181921&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/MaxPn/dJMb9gxtXjI/2sw3lZ3VDkOfpwGPZqhUCk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/gRQMv/dJMb9lMjYbz/WiKIYBlytBwJo6kVIeB411/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 문제 요구사항 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수 l과 r이 주어졌을 때, l 이상 r 이하의 정수 중에서 숫자 0과 5로만 이루어진 수를 모두 찾아 배열로 반환하는 문제입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;탐색 범위:&lt;/b&gt; l부터 r까지의 모든 정수를 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조건:&lt;/b&gt; 각 정수를 문자열로 바꿨을 때 모든 문자가 0 또는 5여야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예외 처리:&lt;/b&gt; 조건을 만족하는 수가 하나도 없다면 [-1]을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 1) 숫자를 문자열로 변환해 자리수 확인하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 숫자의 모든 자리수가 0 또는 5인지 확인해야 하므로, 숫자를 문자열로 변환하면 한 글자씩 쉽게 검사할 수 있습니다. 예를 들어 505는 문자열 &quot;505&quot;로 바꾼 뒤 각 문자가 &quot;0&quot; 또는 &quot;5&quot;에 포함되는지 확인하면 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 2) all() 함수로 모든 조건 만족 여부 확인하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python의 all() 함수는 반복 가능한 값들이 모두 True일 때만 True를 반환합니다. 따라서 all(char in &quot;05&quot; for char in str(cur))처럼 작성하면 현재 숫자의 모든 자리수가 0 또는 5인지 간단하게 확인할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 3) 조건을 만족하는 값이 없는 경우 처리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서는 조건을 만족하는 정수가 없다면 -1이 담긴 배열을 반환해야 합니다. 따라서 탐색이 끝난 뒤 answer가 비어 있는지 확인하고, 비어 있다면 -1을 추가해 예외 상황을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 4. 정답 Python 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1781164292414&quot; class=&quot;properties&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(l, r):
    # 없는 경우 -1 리턴 예외처리
    
    answer = []
    
    for cur in range(l, r + 1):
        # 0이나 5 이외의 숫자가 들었다면 
        if all(char in &quot;05&quot; for char in str(cur)):
            answer.append(cur)
            
    if len(answer) == 0:
        answer.append(-1)
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 풀이하면서 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 숫자의 각 자리수를 직접 나누어 확인하는 방식도 생각할 수 있지만, Python에서는 문자열로 변환하면 훨씬 간단하게 조건을 검사할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 all()과 제너레이터 표현식을 함께 사용하면 모든 자리수가 조건을 만족하는지 한 줄로 확인할 수 있어 코드가 깔끔해졌습니다. 조건을 만족하는 수가 없을 때 [-1]을 반환해야 한다는 예외 처리도 마지막에 한 번만 확인하면 되어 흐름이 단순했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하신 점이나 포스팅에 대한 피드백이 있다면 댓글로 편하게 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 공감이나 댓글 한 줄 남겨주시면 포스팅을 이어가는 데 큰 힘이 됩니다.  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>코딩테스트/알고리즘</category>
      <category>Programmers</category>
      <category>Python</category>
      <category>python3</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>프로그래머스 lv.0</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/20</guid>
      <comments>https://hi-bluebird.tistory.com/20#entry20comment</comments>
      <pubDate>Thu, 11 Jun 2026 16:54:26 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 LV.0] 배열 조각하기 &amp;mdash; Python 정답 &amp;amp; 해설</title>
      <link>https://hi-bluebird.tistory.com/19</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 문제 URL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181893&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/181893&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781361980036&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181893&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dBmRMU/dJMb9efmwAw/ZAXkpVRJuNoGOTVnR2hgdK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/9P5H4/dJMb9hC9GQp/83Ren201U1jR3v5lyTvTk1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181893&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181893&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dBmRMU/dJMb9efmwAw/ZAXkpVRJuNoGOTVnR2hgdK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/9P5H4/dJMb9hC9GQp/83Ren201U1jR3v5lyTvTk1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 문제 요구사항 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수 배열 arr와 query가 주어졌을 때, query를 순서대로 확인하면서 arr의 특정 구간만 남기는 문제입니다. query의 인덱스가 짝수인지 홀수인지에 따라 잘라내는 방향이 달라집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;짝수 인덱스:&lt;/b&gt; query[i]번 인덱스까지는 남기고, 그 뒤쪽 원소들을 잘라냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;홀수 인덱스:&lt;/b&gt; query[i]번 인덱스부터 끝까지 남기고, 그 앞쪽 원소들을 잘라냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 반환값:&lt;/b&gt; 모든 query를 처리한 뒤 남아 있는 arr의 부분 배열을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 1) query의 인덱스 기준으로 동작 나누기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에서 중요한 점은 query의 값 자체가 아니라, 현재 query가 몇 번째 인덱스에 있는지입니다. 인덱스가 짝수라면 뒤쪽을 제거하고, 홀수라면 앞쪽을 제거해야 하므로 idx % 2 값을 기준으로 분기할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 2) Python 슬라이싱으로 배열 자르기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python에서는 슬라이싱을 이용하면 배열의 일부 구간만 간단하게 가져올 수 있습니다. arr[:val + 1]은 0번 인덱스부터 val번 인덱스까지 남기고, arr[val:]은 val번 인덱스부터 마지막 원소까지 남깁니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ 3) 매 단계마다 갱신되는 arr 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;query를 처리할 때마다 arr의 길이와 원소 구성이 달라집니다. 따라서 처음 주어진 arr를 기준으로 계속 계산하는 것이 아니라, 매번 잘라낸 결과를 다시 arr에 저장하면서 다음 query를 처리해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 4. 정답 Python 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1781163734388&quot; class=&quot;properties&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(arr, query):
    for idx, val in enumerate(query):
        if idx % 2 == 1: # 홀수 인덱스
            arr = arr[val : ]
        else: # 짝수 인덱스
            arr = arr[ : val + 1]
    
    return arr&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 풀이하면서 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명만 보면 앞부분과 뒷부분을 번갈아 자르는 과정이 조금 헷갈릴 수 있지만, query의 인덱스가 짝수인지 홀수인지에 따라 동작을 나누면 훨씬 단순하게 정리할 수 있는 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Python 슬라이싱의 끝 인덱스는 포함되지 않는다는 점을 잘 기억해야 했습니다. 짝수 인덱스에서 query[i]번 인덱스까지 남겨야 하므로 arr[:val + 1]처럼 +1을 해주는 부분이 핵심이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하신 점이나 포스팅에 대한 피드백이 있다면 댓글로 편하게 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되셨다면 공감이나 댓글 한 줄 남겨주시면 포스팅을 이어가는 데 큰 힘이 됩니다.  &lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>코딩테스트/알고리즘</category>
      <category>Programmers</category>
      <category>Python</category>
      <category>python3</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>프로그래머스 lv.0</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/19</guid>
      <comments>https://hi-bluebird.tistory.com/19#entry19comment</comments>
      <pubDate>Thu, 11 Jun 2026 16:46:06 +0900</pubDate>
    </item>
    <item>
      <title>[Python/Pandas] 데이터 리모델링 완벽 이해: 와이드 포맷을 롱 포맷으로 (melt와 pivot)</title>
      <link>https://hi-bluebird.tistory.com/18</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 데이터 리모델링의 이해&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) 데이터 리모델링이란?&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;정의:&lt;/b&gt; 수집된 원천 데이터(Raw Data)의 형태를 분석 목적이나 시각화 도구의 요구 조건에 맞게 데이터프레임의 구조(행과 열)를 재구성하는 프로세스다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;필요성:&lt;/b&gt; 현업이나 공공데이터 포털에서 제공되는 데이터가 항상 분석과 집계에 최적화된 형태로 제공되지는 않기 때문에 리모델링 과정이 필수적이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;주요 패키지:&lt;/b&gt; 파이썬 생태계에서는 &lt;b&gt;Pandas&lt;/b&gt;가 데이터 리모델링 패키지 역할을 수행한다. (R 언어의 tidyr, reshape2와 궤를 같이함)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  NumPy는 왜 리모델링 패키지에 속하지 않을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;본질적 목적의 차이: NumPy는 배열의 형태를 바꾸는 함수(reshape 등)를 지원하지만, 데이터 형태의 재구성보다는 초고속 행렬 연산을 위한 고성능 수치 계산 라이브러리w에 가깝다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;라벨(Label)의 유무:&lt;/b&gt; NumPy 배열은 [0][1] 같은 컴퓨터 중심의 주소(방 번호)로만 데이터에 접근한다. 반면, Pandas는 지점, 월 등 인간 중심의 **텍스트 라벨(컬럼명)**을 시스템으로 관리하므로, 이를 기준으로 유연하게 데이터를 녹이고 피벗하는 인간 중심의 리모델링이 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;결론:&lt;/b&gt; NumPy는 Pandas 내부에서 초고속 메모리 연산을 담당하는 &lt;b&gt;숨겨진 엔진&lt;/b&gt;이고, Pandas는 구조를 뜯어고치는 &lt;b&gt;리모델링 애플리케이션&lt;/b&gt;이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) 데이터 형태의 두 가지 분류: Wide Format vs Long Format&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;데이터 분석 라이브러리와 시각화 도구를 효율적으로 다루기 위해서는 두 포맷의 명확한 차이와 장단점을 이해해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;① 와이드 포맷 (Wide Format)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;특징:&lt;/b&gt; 사람이 눈으로 읽고 직관적으로 파악하기 좋은 형태다. (.xlsx, .csv 보고서용 포맷)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;학생 이름 1학기 국어 1학기 수학 2학기 국어 2학기 수학&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;harry&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;90&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;85&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;95&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;90&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;alex&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;100&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;100&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;100&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;100&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;장점 (인간 중심):&lt;/b&gt; 한 개체(예: 학생)의 모든 데이터가 한 줄(Row)에 표현된다. 사람은 정보를 가로로 훑는 데 익숙하며 공간을 적게 차지하여 인쇄용 보고서에 적합하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;단점 (컴퓨터/라이브러리 관점):&lt;/b&gt; 데이터의 중요한 의미(학기, 과목 등)가 '컬럼명'에 숨어 있다. 컴퓨터 입장에서 1학기 수학은 단순한 텍스트일 뿐이므로, 전체 학기의 수학 평균을 구하려면 컬럼명을 일일이 파싱해 찾아내는 복잡한 로직이 강제된다. 특히 새로운 학기 데이터가 추가될 때마다 데이터프레임의 열(Column) 구조를 아예 새로 만들어야 하므로 확장성이 최악이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;② 롱 포맷 (Long Format)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;특징:&lt;/b&gt; 컴퓨터와 분석/시각화 툴(Pandas, Seaborn 등)이 가장 좋아하는 깔끔한(Tidy) 형태다. 기준 열을 고정한 채 데이터를 밑으로 길게 쌓는 방식(Melt)이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;학생 이름 학기 과목 점수&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;harry&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1학기&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;국어&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;90&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;harry&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1학기&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;수학&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;85&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;harry&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2학기&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;국어&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;95&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;harry&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2학기&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;수학&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;90&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;alex&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1학기&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;국어&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;100&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;...&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;...&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;...&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;...&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;장점 (컴퓨터/라이브러리 관점):&lt;/b&gt; '고정된 구조와 손쉬운 필터링'이 가능하다. 새로운 과목이나 학기가 추가되더라도 열 구조는 그대로 유지한 채 행(Row)만 아래로 추가하면 된다. 컴퓨터는 기본적으로 세로(컬럼) 방향 연산에 최적화되어 있으므로 집계 함수를 적용하기에 가장 이상적이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;단점 (인간 중심):&lt;/b&gt; 동일한 식별자(harry)가 여러 행에 걸쳐 중복 출력되고 아래로 지나치게 길어져, 스크롤을 내리며 한눈에 데이터를 파악하기 어렵다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  핵심 요약&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;데이터 구조에서 &lt;b&gt;'행(Row)'의 추가는 비용이 매우 적고 관대&lt;/b&gt;하지만, &lt;b&gt;'열(Column)'을 추가하거나 수정하는 것은 데이터 구조 자체를 바꾸는 일이기 때문에 연산 비용이 크고 민감&lt;/b&gt;하다. 따라서 열 위주의 와이드 포맷을 행 위주의 롱 포맷으로 전환하는 리모델링이 선행되어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 데이터 리모델링 실전 파이프라인 (End-to-End 예제)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실제 공공데이터 포털에서 와이드 포맷의 CSV 파일을 다운로드하여&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;롱 포맷 리모델링 &amp;rarr; 전처리 &amp;rarr; 그룹 집계 &amp;rarr; 보고서용 피벗 테이블 생성&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;으로 이어지는 전체 분석 파이프라인 흐름이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;lt; 참고&amp;gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;.csv &amp;rarr; 쉼표로 구분된 값(Comma-Separated Values)을 뜻하는 파일 형식&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html#pandas.read_csv&quot;&gt;https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html#pandas.read_csv&lt;/a&gt; - 0번 .csv 파일 읽기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://app.notion.com/p/279394a480618172a314d34ed812aab8?pvs=21&quot;&gt;&lt;b&gt;그룹별 여러 집계 연산&lt;/b&gt;&lt;/a&gt; - 4번 데이터 집계 .agg()&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://app.notion.com/p/279394a480618102bcc2c1471b9e7ff3?pvs=21&quot;&gt;출력 결과&lt;/a&gt; - 4번 데이터 집계 .reset_index()&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;groupby의 기준이 되어 인덱스로 가버린 '지점' 컬럼을 다시 일반 열로 내려주고 인덱스를 기본 숫자(0,1,2..)로 정렬&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) 예제 코드&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;import pandas as pd

# ==========================================
# 0. [공공데이터 시나리오] pd.read_csv() 로드 단계
# ==========================================
# ※ 실제 공공데이터 포털에서 다운로드한 '지점별_매출.csv' 파일이 아래와 같다고 가정합니다.
# ------------------------------------------
# [지점별_매출.csv 원본 형태]
# 지점,1월,2월
# &quot;서울 &quot;,100,150
# &quot;부산&quot;,200,250
# &quot;서울 &quot;,100,150
# &quot;광주&quot;,300,350
# ------------------------------------------
#
# [공식 문서 핵심 파라미터 적용 가이드]
# - filepath_or_buffer: '지점별_매출.csv' (읽어올 파일 경로)
# - encoding='utf-8': 한글 깨짐 방지를 위한 표준 인코딩 옵션 (윈도우 환경에선 cp949 사용)
# - header=0: 파일의 몇 번째 행을 컬럼명(헤더)으로 지정할지 설정 (기본값은 0번째 행).
#             만약 파일 첫 줄에 쓸데없는 제목이 있고 2번째 줄부터 데이터가 시작된다면 header=1로 설정하여 정제함.
#
# [코드 실행] 아래 코드를 통해 원천 CSV 파일이 '데이터프레임 단위'로 메모리에 로드됩니다.
# df_wide = pd.read_csv('지점별_매출.csv', encoding='utf-8', header=0)

# (실습 편의를 위해 read_csv 결과물과 동일한 데이터프레임을 수동 생성하여 파이프라인을 시작합니다.)

# ==========================================
# 1. 원본 데이터: 사람이 보기 편하지만 오염된 [와이드 포맷]
# ==========================================
# - '지점'명에 공백 존재 (문자열 오염 및 중복 발생)
df_wide = pd.DataFrame({
    '지점': ['서울 ', '부산', '서울 ', '광주'],  
    '1월': [100, 200, 100, 300],
    '2월': [150, 250, 150, 350]
})
print(&quot;=== [1] 원본 와이드 포맷 ===&quot;)
print(df_wide)

# ==========================================
# 2. 리모델링: 컴퓨터가 좋아하는 [롱 포맷]으로 구조 변경 (Melt)
# ==========================================
# [melt() 핵심 파라미터 3대장 한 줄 요약]
# - id_vars='지점': 세로로 녹이지 않고 기준점으로 고정해 둘 '기둥' 컬럼을 지정합니다.
# - var_name='월': 가로로 나열되어 있던 기존 컬럼명('1월', '2월')들을 모아서 새로 만들 '열 이름'입니다.
# - value_name='매출': 그 컬럼들 밑에 적혀 있던 진짜 숫자(값)들을 모아서 새로 만들 '열 이름'입니다.

df_long = df_wide.melt(
    id_vars='지점', 
    var_name='월', 
    value_name='매출'
)
print(&quot;\\n=== [2] melt 적용 후 롱 포맷 ===&quot;)
print(df_long)

# ==========================================
# 3. 데이터 변환(Transformation): 롱 포맷 상태에서 '단 한 줄로' 정제
# ==========================================
# - 롱 포맷은 구조가 고정되어 있어 컬럼이 늘어나도 전처리 코드가 변하지 않음
df_long['지점'] = df_long['지점'].str.strip()  # 문자열 처리: 공백 제거로 '서울 '을 '서울'로 통일
df_long = df_long.drop_duplicates()           # 중복 제거: 공백이 합쳐지면서 생긴 진짜 중복 행 제거
print(&quot;\\n=== [3] 데이터 변환 및 정제 완료 ===&quot;)
print(df_long)

# ==========================================
# 4. 데이터 집계(Aggregation): 6대 핵심 함수와 요약 표 완성
# ==========================================
# - 개별 기본 집계 함수 (sum, mean, count, max, min)를 .agg()로 묶어 다중 집계 수행
# - groupby()를 결합하여 지점별로 완벽하게 요약된 표(DataFrame)를 생성
df_result = df_long.groupby('지점')['매출'].agg(['sum', 'mean', 'count', 'max', 'min']).reset_index()

# 팀원들이 보기 편하게 요약 표 컬럼명 정제
df_result.columns = ['지점', '총합(sum)', '평균(mean)', '개수(count)', '최대(max)', '최소(min)']
print(&quot;\\n=== [4] 최종 데이터 집계 결과 (요약 표) ===&quot;)
print(df_result)

# ==========================================
# 5. 데이터 재구조화: 집계 완료된 데이터를 다시 보고서용 [피벗 테이블]로 변환
# ==========================================
# [공식 문서 pivot() 파라미터 설명]
# - index='지점': 새로운 표의 행(Row) 자리에 위치할 기준 컬럼을 지정합니다.
# - columns='지표': 새로운 표의 열(Column) 자리에 가로로 나열할 기존 행 데이터들을 지정합니다.
# - values='값': 표의 내부에 채워 넣을 진짜 수치형 데이터 컬럼을 지정합니다.

# 집계 표(df_result)를 사람이 가로로 한눈에 비교하기 좋게 '총합'과 '평균' 지표만 골라 피벗을 수행합니다.
df_long_summary = df_result.melt(id_vars='지점', var_name='지표', value_name='값') # 피벗을 보여주기 위해 살짝 롱 포맷화
df_final_pivot = df_long_summary.pivot(index='지점', columns='지표', values='값')

print(&quot;\\n=== [5] 최종 보고서용 피벗 테이블 (Pivot 완료) ===&quot;)
print(df_final_pivot)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) 단계별 출력 결과&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;=== [1] 원본 와이드 포맷 ===
    지점   1월   2월
0  서울   100  150
1   부산  200  250
2  서울   100  150
3   광주  300  350

=== [2] melt 적용 후 롱 포맷 ===
    지점   월   매출
0  서울   1월  100
1   부산  1월  200
2  서울   1월  100
3   광주  1월  300
4  서울   2월  150
5   부산  2월  250
6  서울   2월  150
7   광주  2월  350

=== [3] 데이터 변환 및 정제 완료 ===
   지점   월   매출
0  서울  1월  100
1  부산  1월  200
3  광주  1월  300
4  서울  2월  150
5  부산  2월  250
7  광주  2월  350

=== [4] 최종 데이터 집계 결과 (요약 표) ===
   지점  총합(sum)  평균(mean)  개수(count)  최대(max)  최소(min)
0  광주      650     325.0          2      350      300
1  부산      450     225.0          2      250      200
2  서울      250     125.0          2      150      100

=== [5] 최종 보고서용 피벗 테이블 (Pivot 완료) ===
지표  개수(count)  총합(sum)  최대(max)  최소(min)  평균(mean)
지점                                                
광주        2.0    650.0    350.0    300.0     325.0
부산        2.0    450.0    250.0    200.0     225.0
서울        2.0    250.0    150.0    100.0     125.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. 핵심 요약 및 결론&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;데이터 전처리와 집계는 유기적인 파이프라인이다:&lt;/b&gt; 데이터 리모델링과 전처리, 집계는 각각 분리된 작업이 아니다. 외부 정형 데이터(CSV) 로드 후 [컴퓨터가 좋아하는 롱 포맷 변환 (melt)] &amp;rarr; [일괄 데이터 정제] &amp;rarr; [그룹 기반 다중 집계 (groupby + agg)]&amp;rarr; [최종 사람 중심 보고서 변환 (pivot)]으로 이어지는 선형적 파이프라인 구조를 가질 때 개발 생산성과 코드 가독성이 극대화된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;리모델링의 본질은 유지보수성 확보다:&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;롱 포맷으로 데이터를 리모델링하면 월별 컬럼이 아무리 우측으로 추가되거나 데이터의 양이 늘어나도 &lt;b&gt;전처리 및 집계 코드가 단 한 줄도 변하지 않는 강력한 확장성&lt;/b&gt;을 확보할 수 있다. 이것이 현업 분석가들이 번거로운 형태 변환을 매 프로젝트마다 선행하는 핵심 원동력이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>IT</category>
      <category>long format</category>
      <category>Pandas groupby agg</category>
      <category>Pandas melt</category>
      <category>Pandas pivot</category>
      <category>StandardScaler</category>
      <category>wide format</category>
      <category>공공데이터 전처리</category>
      <category>데이터 리모델링</category>
      <category>데이터 전처리 파이프라인</category>
      <category>와이드 포맷 롱 포맷</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/18</guid>
      <comments>https://hi-bluebird.tistory.com/18#entry18comment</comments>
      <pubDate>Sat, 6 Jun 2026 09:44:01 +0900</pubDate>
    </item>
    <item>
      <title>[FastAPI] async/await는 왜 필요할까? 비동기 처리의 본질과 모델 추론 API 예제로 이해하기</title>
      <link>https://hi-bluebird.tistory.com/17</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  1. 미리보는 3줄 요약 (TL;DR)&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;비동기 처리의 본질:&lt;/b&gt; 한 작업을 더 빨리 끝내는 기술이 아니라, 결과값을 기다리는 동안 &lt;b&gt;서버가 놀지 않게 하여 전체 처리 능력을 높이는 기술&lt;/b&gt;입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;async/await 문법의 핵심:&lt;/b&gt; async def 선언과 내부의 await 호출이 한 세트로 움직여야 하며, await은 대기하는 동안 &lt;b&gt;이벤트 루프에 제어권을 돌려준다&lt;/b&gt;는 의미입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;무조건 비동기가 정답은 아니다:&lt;/b&gt; CPU를 오래 쓰는 작업에서는 효과가 제한적이며, 서비스 규모와 병목 지점에 따라 동기와 비동기 방식을 올바르게 선택해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  2. 주제에 대한 깊은 이해&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  주요 개념 정리&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;FastAPI:&lt;/b&gt; Python으로 API 서버를 만들 수 있는 모던 웹 프레임워크입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;비동기 처리와 async/await의 차이:&lt;/b&gt; * &lt;b&gt;비동기 처리:&lt;/b&gt; 서버 자원을 효율적으로 쓰게 해주는 큰 개념 및 처리 방식입니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;async/await:&lt;/b&gt; Python에서 이 비동기 처리를 직관적으로 표현하는 문법적 수단입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;이벤트 루프 (Event Loop):&lt;/b&gt; 비동기 작업 관리자입니다. 서버에 들어오는 요청들을 계속 왔다 갔다 순회하면서, 지금 바로 실행 가능한 작업이 있는지 판단하고 처리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;모델 추론 API:&lt;/b&gt; AI 모델을 학습시키는 API가 아니라, 이미 학습된 모델에게 입력을 주고 예측 결과를 받는 API입니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전체 흐름: 요청 ➡️ 입력 검증(Pydantic) ➡️ 전처리 ➡️ 모델 추론 ➡️ 후처리 ➡️ 응답 반환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  3. 비동기 처리가 필요한 이유와 시나리오&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;호출, DB 조회, 큐 대기, 파일 입출력(I/O)처럼 결과값 반환까지 대기 시간이 긴 요청을 처리할 때, 서버가 그 시간을 낭비하지 않고 실행 가능한 다른 요청을 함께 처리하기 위함입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;⚠️ 주의: 단일 요청의 계산 시간 자체가 줄어드는 것은 아닙니다. (스트리밍도 걸리는 시간은 똑같으나 결과를 받는 대로 조금씩 보여주는 것뿐입니다.) 하지만, 동시에 많은 요청이 들어오는 상황(트래픽이 몰릴 때)에서 서버 처리량을 높이고 대기열을 줄여 전체 응답성을 크게 개선합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  동기 vs 비동기 트래픽 시나리오 비교&lt;/span&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;❌ 동기적 처리 (Sync)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한 작업이 끝날 때까지 다음 흐름이 그대로 멈춰서 기다리는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;A 요청 시작&lt;/b&gt; ➡️ 모델 응답 대기 (3초간 서버 멈춤) ➡️ &lt;b&gt;A 완료&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;B 요청 시작&lt;/b&gt; ➡️ 모델 응답 대기 (3초간 서버 멈춤) ➡️ &lt;b&gt;B 완료&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;C 요청 시작&lt;/b&gt; ➡️ 모델 응답 대기 (3초간 서버 멈춤) ➡️ &lt;b&gt;C 완료&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;⭕ 비동기적 처리 (Async)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대기 시간이 발생하면 시간을 낭비하지 않고 다음 요청을 바로 받습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;A 요청 시작&lt;/b&gt; ➡️ 모델 응답 대기 (기다리는 동안 제어권 반납)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;B 요청 시작&lt;/b&gt; ➡️ 모델 응답 대기 (기다리는 동안 제어권 반납)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;C 요청 시작&lt;/b&gt; ➡️ 모델 응답 대기 (기다리는 동안 제어권 반납)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;➡️ &lt;b&gt;A / B / C 응답이 도착하는 순서대로 즉시 반환&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; ️ 4. 검증을 위한 FastAPI PoC&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import asyncio
import time
from fastapi import FastAPI

app = FastAPI()

# 실제 프로덕션 환경이라면 대형 AI 모델(가중치)이 로드되어 있다고 가정합니다.

# ------------------------------------------------------------------
# ❌ [시나리오 1] 동기(Sync) 방식의 모델 추론 API
# ------------------------------------------------------------------
@app.post(&quot;/predict/sync&quot;)
async def predict_sync(text: str):
    start_time = time.time()
    
    #   Blocking 작업 시뮬레이션
    # 예: 동기 방식 외부 API 호출, 동기 DB 조회, 동기 파일 I/O, 잘못 작성된 모델 호출
    # time.sleep은 현재 실행 흐름을 완전히 멈추는 동기 함수입니다.
    #
    # 즉, &quot;async를 붙였는데도 서버가 막히는&quot; 상황을 보여주는 예시입니다.

    time.sleep(5.0) 
    
    end_time = time.time()
    process_time = end_time - start_time
    return {
        &quot;mode&quot;: &quot;Sync (Blocking)&quot;,
        &quot;input&quot;: text,
        &quot;result&quot;: f&quot;요약 완료 (소요시간: {process_time:.2f}초)&quot;
    }

# ------------------------------------------------------------------
# ⭕ [시나리오 2] 외부 API를 활용한 비동기(Async) 방식의 모델 추론 API
# ------------------------------------------------------------------
@app.post(&quot;/predict/async&quot;)
async def predict_async(text: str):
    start_time = time.time()
    
    # ✨ I/O-Bound 대기 시뮬레이션 (예: OpenAI나 외부 Ollama 서버에 API 요청 후 대기)
    # asyncio.sleep은 대기하는 동안 '이벤트 루프'에 제어권을 반납하여 다른 요청을 받게 합니다.
    # (외부 AI API, DB, 큐 응답을 기다리는 I/O 대기 상황을 흉내냅니다.)
    #
    #   Q. 'await time.sleep(5.0)'은 안 되나요?
    #   A. time.sleep은 동기 함수이므로 await과 함께 사용할 수 없습니다. (= 자격 미달)
    #      마지막에 '10 + &quot;안녕&quot;'을 하면 데이터 타입이 맞지 않아 TypeError가 나는 것처럼
    #      await 뒤에 일반 함수를 넣으면 파이썬이 아래와 같은 자격 미달 에러를 던집니다.
    #        TypeError: object NoneType can't be used in 'await' expression
    
    await asyncio.sleep(5.0) 
    
    end_time = time.time()
    process_time = end_time - start_time
    return {
        &quot;mode&quot;: &quot;Async (Non-Blocking)&quot;,
        &quot;input&quot;: text,
        &quot;result&quot;: f&quot;요약 완료 (소요시간: {process_time:.2f}초)&quot;
    }

# ------------------------------------------------------------------
#   [상태 점검 API] 서버가 살아있는지 확인하는 가벼운 헬스체크
# ------------------------------------------------------------------
@app.get(&quot;/health&quot;)
def health_check():
    return {&quot;status&quot;: &quot;healthy&quot;, &quot;message&quot;: &quot;서버가 정상적으로 응답하고 있습니다!&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;⚖️ 5. 그렇다면 비동기가 무조건 정답일까? (한계와 선택)&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;① CPU를 오래 쓰는 작업에서의 제한&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;AI 모델 추론 과정에서 외부 API가 아닌, 서버 내부적으로 &lt;b&gt;CPU를 극도로 오래 쓰는 연산 작업&lt;/b&gt;이 포함되어 있다면 비동기 처리의 효과가 제한적입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;② 일반 CRUD 및 게시글 조회(Read) API에서의 고민&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;비동기가 좋은 이유:&lt;/b&gt; 여러 사람이 하나의 게시글을 동시에 읽을 때, DB에서 내용을 가져오는 대기 시간이 존재하므로 비동기 드라이버나 ORM을 활용하면 서버가 대기하는 동안 다른 요청을 처리할 수 있어 효율이 좋아집니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;고려해야 할 트레이드오프:&lt;/b&gt; 하지만 서버가 비동기로 요청을 너무 빠르게 다 받아내면, &lt;b&gt;역으로 데이터베이스(DB) 쪽에 부하가 몰릴 수 있습니다.&lt;/b&gt; 이런 경우 DB 단에서 캐싱이나 인덱스 최적화 같은 설계가 추가로 필요할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  최종 요약&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트래픽이 크지 않다면 &lt;b&gt;동기 방식도 충분히 안정적이며 구현을 단순화&lt;/b&gt;시켜 줍니다. 무조건 비동기가 정답이라기보다는, 서비스의 규모와 시스템의 어느 지점에 병목이 있는지에 따라 개발자가 알맞게 선택해야 합니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>IT</category>
      <category>api 서버</category>
      <category>async await</category>
      <category>backend</category>
      <category>CPU Bound</category>
      <category>FastAPI</category>
      <category>I/O Bound</category>
      <category>Python</category>
      <category>모델 추론</category>
      <category>비동기 처리</category>
      <category>이벤트 루프</category>
      <author>작지만 꾸준한 성장으로</author>
      <guid isPermaLink="true">https://hi-bluebird.tistory.com/17</guid>
      <comments>https://hi-bluebird.tistory.com/17#entry17comment</comments>
      <pubDate>Sat, 30 May 2026 03:12:43 +0900</pubDate>
    </item>
  </channel>
</rss>