<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>프로그래밍 기록장</title>
    <link>https://daily-programming-til.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 12 Jul 2026 00:30:07 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>yolang</managingEditor>
    <image>
      <title>프로그래밍 기록장</title>
      <url>https://tistory1.daumcdn.net/tistory/6575022/attach/5f6d695982e44aa089b9b73a6058c17a</url>
      <link>https://daily-programming-til.tistory.com</link>
    </image>
    <item>
      <title>문자열 코딩테스트, 유형이 아니라 &amp;quot;도구&amp;quot;로 정리하기 (Java)</title>
      <link>https://daily-programming-til.tistory.com/189</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 관련 문제는 여러 코딩테스트에서 빈번하게 등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 내가 실제로 풀고 틀린 문제들을 근거로 정리한 것이다. 문제를 &quot;유형&quot;으로 나열하는 대신, &lt;b&gt;어떤 도구로 푸는지 + 어디서 자주 틀리는지&lt;/b&gt;를 중심으로 묶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 글에서 다루는 문제 (전부 직접 풀며 틀렸던 것)&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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12918&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;문자열 다루기 기본&lt;/b&gt;&lt;/a&gt; &amp;mdash; 문자열이 길이 4 또는 6이고 전부 숫자인지 판별 &amp;rarr; &lt;i&gt;길이&amp;middot;문자 조건을 정규식으로 묶기&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/468370&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;중요한 단어를 스포 방지&lt;/b&gt;&lt;/a&gt; &amp;mdash; 스포 처리된 단어 중 '중요한 단어' 수 세기 &amp;rarr; &lt;i&gt;공백 파싱 함정&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17687&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;[3차] n진수 게임&lt;/b&gt;&lt;/a&gt; &amp;mdash; n진수로 이어붙인 문자열에서 특정 위치의 숫자 뽑기 &amp;rarr; &lt;i&gt;&lt;code&gt;+=&lt;/code&gt; 누적 함정&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문자열 도구 상자&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 String 메서드, 언제 Pattern + Matcher?&lt;/h3&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매칭의 위치(인덱스)나 캡처 그룹이 필요 없다면 &amp;rarr; &lt;code&gt;String&lt;/code&gt; 메서드로 끝낸다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인덱스나 그룹을 꺼내야 한다면 &amp;rarr; &lt;code&gt;Pattern&lt;/code&gt; + &lt;code&gt;Matcher&lt;/code&gt;를 쓴다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 문자열 문제는 &lt;code&gt;String&lt;/code&gt;에 있는 메서드로 충분하다.&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;str.matches(&quot;[0-9]+&quot;);      // 전체가 패턴과 일치하는지 (부분 아님, 전체)
str.replaceAll(&quot;\\s+&quot;, &quot; &quot;); // 정규식으로 치환
str.split(&quot;\\s+&quot;);           // 정규식으로 분리 &amp;rarr; String[]
str.substring(2, 5);         // 부분 문자열
str.indexOf('a');            // 위치 찾기
str.toUpperCase();           // 대소문자 변환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드들은 편하지만 &lt;b&gt;원본에서의 위치를 알려주지 못한다.&lt;/b&gt; &lt;code&gt;split&lt;/code&gt;은 잘린 조각만 돌려주고 인덱스를 잃어버린다. 그래서 &quot;이 단어가 원본 몇 번째 인덱스에 있나&quot;가 필요하면 &lt;code&gt;Matcher&lt;/code&gt;를 써야 한다.&lt;/p&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;Pattern p = Pattern.compile(&quot;\\S+&quot;);
Matcher m = p.matcher(text);
while (m.find()) {
    m.group();   // 매칭된 문자열
    m.start();   // 원본에서의 시작 인덱스
    m.end();     // 끝 다음 인덱스
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;code&gt;Matcher&lt;/code&gt;가 문자열을 잘라서 새로 만드는 게 아니라, &lt;b&gt;원본 위에서 위치만 짚어준다&lt;/b&gt;는 점이다. 그래서 &lt;code&gt;m.start()&lt;/code&gt;/&lt;code&gt;m.end()&lt;/code&gt;가 원본 인덱스와 일치한다. 뒤의 스포 방지 문제에서 이게 왜 중요한지 나온다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 추출 vs 캡처 그룹 &amp;mdash; Matcher를 쓰는 두 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Matcher&lt;/code&gt;가 필요한 상황은 사실 두 갈래로 나뉜다. 이 둘은 다른 개념이라 구분해두면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 매칭 위치(인덱스)&lt;/b&gt; &amp;mdash; &lt;code&gt;start()&lt;/code&gt;/&lt;code&gt;end()&lt;/code&gt;로 원본에서의 위치를 얻는다. 매칭된 조각이 원본 어디에 있는지를 알아야 할 때 쓴다. 뒤의 스포 방지 문제가 정확히 이 경우다(단어 위치로 &lt;code&gt;hidden[]&lt;/code&gt;을 검사해야 함).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) 캡처 그룹&lt;/b&gt; &amp;mdash; 정규식 안에 &lt;code&gt;( )&lt;/code&gt;로 부분을 묶어두고 &lt;code&gt;group(1)&lt;/code&gt;, &lt;code&gt;group(2)&lt;/code&gt;로 그 조각을 따로 꺼낸다. 하나의 매칭을 여러 필드로 분해할 때 쓴다. 예를 들어 &lt;code&gt;2025-09-10&lt;/code&gt; 같은 날짜를 연&amp;middot;월&amp;middot;일로 쪼갠다면 이렇게 쓸 수 있다:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Matcher m = Pattern.compile(&quot;(\\d+)-(\\d+)-(\\d+)&quot;).matcher(&quot;2025-09-10&quot;);
if (m.find()) {
    int year  = Integer.parseInt(m.group(1)); // 캡처 그룹 1 &amp;rarr; 2025
    int month = Integer.parseInt(m.group(2)); // 캡처 그룹 2 &amp;rarr; 9
    int day   = Integer.parseInt(m.group(3)); // 캡처 그룹 3 &amp;rarr; 10
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 같은 패턴을 반복 사용할 거면 &lt;code&gt;Pattern.compile()&lt;/code&gt;을 한 번만 해두고 재사용하는 게 좋다(compile은 비교적 무거운 작업이다). &lt;code&gt;String.matches()&lt;/code&gt;도 내부에서 매번 compile하므로, 루프 안에서 같은 패턴을 검사한다면 &lt;code&gt;Pattern&lt;/code&gt;을 미리 만들어 두자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아두면 좋은 정규식 조각&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;조각&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\d&lt;/code&gt; / &lt;code&gt;\D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;숫자 / 숫자 아님&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\s&lt;/code&gt; / &lt;code&gt;\S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;공백 / 공백 아님&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\w&lt;/code&gt; / &lt;code&gt;\W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;영문&amp;middot;숫자&amp;middot;밑줄 / 그 외&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;+&lt;/code&gt; &lt;code&gt;*&lt;/code&gt; &lt;code&gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1번 이상 / 0번 이상 / 0 또는 1번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{4}&lt;/code&gt; &lt;code&gt;{2,4}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;정확히 4번 / 2~4번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[abc]&lt;/code&gt; &lt;code&gt;[^abc]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a&amp;middot;b&amp;middot;c 중 하나 / 그것들 제외&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;|&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;또는&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;^&lt;/code&gt; &lt;code&gt;$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;문자열 시작 / 끝&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 &lt;code&gt;\&lt;/code&gt;가 두 번 이스케이프되므로 &lt;code&gt;&quot;\\d&quot;&lt;/code&gt;, &lt;code&gt;&quot;\\S+&quot;&lt;/code&gt;처럼 백슬래시를 두 개 쓴다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StringBuilder &amp;mdash; 문자열을 &quot;쌓을&quot; 때&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 &lt;code&gt;String&lt;/code&gt;은 &lt;b&gt;불변(immutable)&lt;/b&gt;이다. 그래서 반복문에서 &lt;code&gt;+=&lt;/code&gt;로 이어붙이면 매번 새 문자열을 통째로 복사한다. 길이 n짜리를 n번 붙이면 O(n&amp;sup2;)가 되어 버린다. 문자열을 반복해서 쌓을 때는 반드시 &lt;code&gt;StringBuilder&lt;/code&gt;를 쓴다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;StringBuilder sb = new StringBuilder();
for (...) sb.append(x);   // O(n) 누적
String result = sb.toString();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;char 산술&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자는 정수처럼 연산된다. 아스키 코드 기반이라 이런 계산이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;char c = 'c';
int idx = c - 'a';        // 2 (a부터 몇 번째인지)
char up = (char) (c - 32); // 'C' (소문자 &amp;rarr; 대문자)
int d = '7' - '0';        // 7 (문자 숫자 &amp;rarr; 정수)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 내가 실제로 틀린 함정들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함정 1. 길이&amp;middot;문자 조건을 부등호도 좋지만 정규식도 가능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;문자열 다루기 기본&lt;/code&gt;은 문자열이 &lt;b&gt;길이 4 또는 6이면서 전부 숫자인지&lt;/b&gt;를 판별한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;if (s.length() != 4 &amp;amp;&amp;amp; s.length() != 6) return false;
return s.matches(&quot;[0-9]+&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 조건은 부등호로 적어도 좋지만 정규식으로도 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;matches&lt;/code&gt;는 문자열 &lt;b&gt;전체&lt;/b&gt;가 맞아야 하므로 &lt;code&gt;{4}&lt;/code&gt;, &lt;code&gt;{6}&lt;/code&gt;이 곧 길이 제약이 되고, 문자 종류(&lt;code&gt;[0-9]&lt;/code&gt;)까지 한 번에 표현된다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;return s.matches(&quot;[0-9]{4}|[0-9]{6}&quot;);  // 숫자 4개 또는 숫자 6개&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;교훈:&lt;/b&gt; 길이 같은 수치 조건도 부등호로 나누기보다 정규식 &lt;code&gt;{n}&lt;/code&gt;으로 표현하면 사이값 실수가 사라지고, 문자 종류 검사까지 한 번에 묶을 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함정 2. 수동 파싱의 인덱스 off-by-one (선행 공백)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지에서 &lt;b&gt;스포 처리된 단어 중 '중요한 단어' 수를 세는&lt;/b&gt; &lt;code&gt;중요한 단어를 스포 방지&lt;/code&gt;에서, 처음엔 &lt;code&gt;end&lt;/code&gt; 인덱스를 직접 움직이며 공백을 찾는 방식으로 파싱했다. 채점에서 딱 2개만 틀렸는데, 시간이 아주 짧게 실패해서(=시간초과 아님) 작은 입력의 엣지임을 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 파싱 시작점이었다. 인덱스 1부터 검사를 시작해서 &lt;b&gt;인덱스 0을 경계로 검사하지 않았고&lt;/b&gt;, 그래서 메시지가 공백으로 시작하면 첫 단어가 &lt;code&gt;&quot;here&quot;&lt;/code&gt;가 아니라 &lt;code&gt;&quot; here&quot;&lt;/code&gt;(앞 공백 포함)로 잘렸다. 그러면 이 단어가 뒤쪽 평문 &lt;code&gt;&quot;here&quot;&lt;/code&gt;와 문자열이 달라져 매칭에 실패한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항엔 &quot;공백은 연속해서 등장하지 않습니다&quot;는 있었지만 &lt;b&gt;선행 공백을 금지하는 문구는 없었다.&lt;/b&gt; 명시적으로 배제되지 않은 케이스는 테스트에 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결은 파싱을 직접 짜지 않고 &lt;code&gt;\S+&lt;/code&gt; 매처에 위임하는 것이었다. &lt;code&gt;\S+&lt;/code&gt;는 정의상 공백이 아닌 문자에서만 시작하므로, 앞&amp;middot;뒤&amp;middot;연속 공백이 몇 개든 단어의 진짜 시작을 짚는다. 오프바이원이 생길 자리 자체가 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;교훈:&lt;/b&gt; 파싱은 인덱스를 손으로 굴리지 말고 &lt;code&gt;split&lt;/code&gt;&amp;middot;&lt;code&gt;\S+&lt;/code&gt; 같은 검증된 도구에 맡긴다. 꼭 직접 짜야 하면 앞공백&amp;middot;뒷공백&amp;middot;한 글자 단어&amp;middot;경계 인덱스(0과 n-1)를 반드시 손으로 돌려본다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함정 3. 반복문에서 String += &amp;rarr; O(n&amp;sup2;)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;n진수로 0, 1, 2, &amp;hellip;를 이어붙인 문자열에서 특정 위치의 숫자를 뽑는&lt;/b&gt; &lt;code&gt;[3차] n진수 게임&lt;/code&gt;을 이렇게 통과는 시켰는데, &lt;b&gt;시간 2327ms / 메모리 381MB&lt;/b&gt;로 아슬아슬했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;String total = &quot;&quot;;
for (Integer i = 0; i &amp;lt; m * t; i++) {
    total += Integer.toString(i, n);   // 매 반복마다 total 전체를 복사 &amp;rarr; O(n&amp;sup2;)
}
String answer = &quot;&quot;;
for (int idx = 0; idx &amp;lt; total.length(); idx++) {
    if (idx % m == (p - 1)) {
        answer += total.substring(idx, idx + 1);  // 여기도 += 누적
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열이 불변인데 &lt;code&gt;+=&lt;/code&gt;로 계속 쌓으니 붙일 때마다 전체 복사가 일어난다. &lt;code&gt;StringBuilder&lt;/code&gt;로 바꾸면 극적으로 빨라진다. 그리고 진법 변환은 직접 나눗셈을 돌릴 필요 없이 &lt;code&gt;Integer.toString(값, 진수)&lt;/code&gt;가 해준다(반대는 &lt;code&gt;Integer.parseInt(문자열, 진수)&lt;/code&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;class Solution {
    public String solution(int n, int t, int m, int p) {
        int need = p + (t - 1) * m;              // 마지막으로 필요한 위치 + 1
        StringBuilder total = new StringBuilder();
        for (int i = 0; total.length() &amp;lt; need; i++)
            total.append(Integer.toString(i, n)); // n진수 변환은 내장 메서드로
        StringBuilder answer = new StringBuilder();
        for (int i = 0; i &amp;lt; t; i++)
            answer.append(total.charAt(p - 1 + i * m));
        return answer.toString().toUpperCase();   // 10~15 &amp;rarr; A~F
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지를 고쳤다. &lt;code&gt;total&lt;/code&gt;&amp;middot;&lt;code&gt;answer&lt;/code&gt;를 &lt;code&gt;StringBuilder&lt;/code&gt;로 바꿔 O(n) 누적으로 만들었고, 필요한 길이(&lt;code&gt;need&lt;/code&gt;)까지만 생성해 불필요한 반복도 줄였다. 세 입출력 예 모두 동일하게 통과한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;효과는 채점에서 그대로 드러났다. 원래 코드가 최악 &lt;b&gt;2327ms / 381MB&lt;/b&gt;였는데, 이 버전은 가장 무거운 테스트에서도 &lt;b&gt;10~15ms / 91MB&lt;/b&gt;로 떨어졌다. 대략 &lt;b&gt;150배&lt;/b&gt; 빨라지고 메모리도 4배 가까이 줄었다. 입력이 작은 케이스에선 둘 다 1ms 미만이라 티가 안 나지만, 문자열이 길어질수록 &lt;code&gt;+=&lt;/code&gt;의 복사 비용이 제곱으로 커진 게 수치로 확인된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;교훈:&lt;/b&gt; 문자열을 반복해서 쌓으면 무조건 &lt;code&gt;StringBuilder&lt;/code&gt;. 진법 변환은 &lt;code&gt;Integer.toString(값, 진수)&lt;/code&gt; / &lt;code&gt;Integer.parseInt(문자열, 진수)&lt;/code&gt;를 기억하자.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 유형별 접근과 대표 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 유형이 결국 &lt;b&gt;어떤 도구로 환원되는지&lt;/b&gt; 한 줄로 정리하고, 실제 풀이를 근거로 연결한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;검증(validation) &amp;mdash; &quot;형식이 맞나?&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 전체가 규칙에 맞는지 확인하는 유형. &lt;code&gt;matches&lt;/code&gt; 한 방이면 끝난다. 길이&amp;middot;문자종류를 정규식 안에 녹이는 게 핵심.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대표 문제: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12918&quot;&gt;문자열 다루기 기본 (12918)&lt;/a&gt; &amp;rarr; &lt;code&gt;s.matches(&quot;[0-9]{4}|[0-9]{6}&quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파싱/토큰화 + 치환/마스킹 &amp;mdash; &quot;쪼개서 분류한다&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공백이나 구분자로 단어를 나누고, 각 단어를 조건에 따라 분류&amp;middot;집계하는 유형. 인덱스가 필요하면 &lt;code&gt;\S+&lt;/code&gt; 매처, 아니면 &lt;code&gt;split&lt;/code&gt;. 그리고 분류 결과는 &lt;code&gt;Set&lt;/code&gt;/&lt;code&gt;Map&lt;/code&gt;으로 관리한다. 특히 &quot;A 집합에는 있고 B 집합에는 없는 것&quot;은 &lt;code&gt;set.removeAll()&lt;/code&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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/468370&quot;&gt;중요한 단어를 스포 방지 (468370)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 서술이 &quot;왼쪽부터 클릭해 공개&quot;라는 절차처럼 보이지만, 실제로는 순서와 무관한 집합 연산으로 붕괴된다. 답 = &lt;code&gt;(스포 단어 집합) &amp;minus; (평문 단어 집합)&lt;/code&gt;의 크기. 단어의 원본 인덱스로 스포 여부를 판정해야 해서 &lt;code&gt;Matcher&lt;/code&gt;의 &lt;code&gt;start()&lt;/code&gt;/&lt;code&gt;end()&lt;/code&gt;를 썼다(캡처 그룹이 아니라 위치가 필요한 경우다).&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;boolean[] hidden = new boolean[message.length()];
for (int[] r : spoiler_ranges)
    for (int i = r[0]; i &amp;lt;= r[1]; i++) hidden[i] = true;

Set&amp;lt;String&amp;gt; spoiler = new HashSet&amp;lt;&amp;gt;(), normal = new HashSet&amp;lt;&amp;gt;();
Matcher m = Pattern.compile(&quot;\\S+&quot;).matcher(message);
while (m.find()) {
    boolean sp = false;
    for (int k = m.start(); k &amp;lt; m.end(); k++) if (hidden[k]) sp = true;
    (sp ? spoiler : normal).add(m.group());
}
spoiler.removeAll(normal);   // 평문에도 등장한 단어 제거
return spoiler.size();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;진법 변환 + 문자열 빌드 &amp;mdash; &quot;숫자를 문자열로 쌓는다&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진법을 바꿔가며 문자열을 길게 만드는 유형. &lt;code&gt;Integer.toString(값, 진수)&lt;/code&gt;로 변환하고, 누적은 &lt;code&gt;StringBuilder&lt;/code&gt;로 한다. &lt;code&gt;+=&lt;/code&gt;를 쓰는 순간 O(n&amp;sup2;)로 터진다(실측으로 2327ms &amp;rarr; 15ms 차이가 났다).&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대표 문제: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17687&quot;&gt;[3차] n진수 게임 (17687)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 문제는 알고리즘 난이도보다 &lt;b&gt;요구사항을 정확한 조건으로 줄였는가 + 파싱을 안전하게 했는가&lt;/b&gt;에서 승부가 갈린다. 도구 선택은 &quot;인덱스/그룹이 필요한가&quot;로 갈리고, 실수는 대개 경계값&amp;middot;공백 파싱&amp;middot;&lt;code&gt;+=&lt;/code&gt; 누적에서 나온다. 이 세 가지 함정만 의식해도 카카오 1~2번류에서 시간과 정확도를 같이 챙길 수 있다.&lt;/p&gt;</description>
      <category>코딩테스트</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/189</guid>
      <comments>https://daily-programming-til.tistory.com/189#entry189comment</comments>
      <pubDate>Mon, 6 Jul 2026 12:41:54 +0900</pubDate>
    </item>
    <item>
      <title>Settings는 신뢰 범위(trust boundary)다: fail-fast</title>
      <link>https://daily-programming-til.tistory.com/188</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI를 이용해 ML serving을 실습하면서 pydantic를 이용한 fail-fast 설계에 대해 공부했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심은 Settings를 trust boundart로 만들어 외부 입력을 부팅 시점에 한 번에 검증하고 그 후로는 코드 어디서나 신뢰하는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 글은 그 원리와 pydantic_settings의 세 가지 도구가 어떻게 이 원리를 구현하는지 정리한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱은 환경마다 다르게 동작해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;- 개발 환경: localhost:5432 DB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;- 스테이징: staging-db.internal:5432&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;프로덕션&lt;/span&gt;: prod-db.internal:5432&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 하드코딩하지 않고 외부에서 주입받기 위한 가장 흔한 방법은 환경변수다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  하지만 만약 &lt;b&gt;config 검증이 입력 시점에 이뤄지지 않고 사용 시점에 일어나면 생기는 일&lt;/b&gt;을 보자.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;가장 원시적인 방법 - os.getenv&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 사용하면 가장 큰 문제는 환경변수가 무조건 문자열이라는 점이다. 따라서 설정이 늘어날수록 전부 타입 변환을 해줘야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1777940278701&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import os

DB_HOST = os.getenv(&quot;DB_HOST&quot;, &quot;localhost&quot;)
DB_PORT = os.getenv(&quot;DB_PORT&quot;, &quot;5432&quot;)
MAX_BATCH_SIZE = os.getenv(&quot;MAX_BATCH_SIZE&quot;, &quot;8&quot;)
  
if MAX_BATCH_SIZE &amp;gt; 10:  # &amp;larr; 항상 False! &quot;8&quot; &amp;gt; 10 비교 (str vs int)
      ...
      
      
MAX_BATCH_SIZE = int(os.getenv(&quot;MAX_BATCH_SIZE&quot;, &quot;8&quot;))
FP16 = os.getenv(&quot;FP16&quot;, &quot;true&quot;).lower() == &quot;true&quot;  # bool 변환 직접&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 만약 변수를 잘못 입력했을 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;부팅 시점에 잡히지 않고 진행되었을 때&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제가 발생할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 위처럼 MAX_BATCH_SIZE가 타입변환이 되지 않았을 때 문자열과 숫자를 비교하면서 검증이 제대로 이뤄지지 않는 문제가 생긴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V2Yxi/dJMcafNh3hG/B5vyfkDKrOmWebjHkwBCBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V2Yxi/dJMcafNh3hG/B5vyfkDKrOmWebjHkwBCBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V2Yxi/dJMcafNh3hG/B5vyfkDKrOmWebjHkwBCBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV2Yxi%2FdJMcafNh3hG%2FB5vyfkDKrOmWebjHkwBCBk%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;473&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&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;p data-ke-size=&quot;size16&quot;&gt;이것을 해결하는 원칙은 Trust Boundary이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trust Boundary(신뢰 범위)를 정확하게 설정하여&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 입력은 한 곳에서 받고, 한 곳에서 검증한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;통과한 객체는 코드 어디에서나 신뢰한다.&lt;/li&gt;
&lt;li&gt;부팅 시점에 모든 검증을 완료한다. ➡️ fail-fast&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구현하기 위한 pydantic_settings의 세 가지 도구가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. BaseSettings&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. env_prefix&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. @model_validator&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. BaseSettings&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BaseSettings는 trust boundary 자체를 만든다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pydantic_settings의 BaseSettings는 BaseModel을 상속하는데 이것은 결국 BaseModel의 검증 능력을 그대로 쓸 수 있다는 뜻이다.&lt;/p&gt;
&lt;pre id=&quot;code_1777878022097&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Field(ge=1, le=100) &amp;mdash; 숫자 범위
Literal[&quot;cuda&quot;, &quot;cpu&quot;] &amp;mdash; 허용 값 제한
@field_validator, @model_validator &amp;mdash; 커스텀 검증
SecretStr &amp;mdash; 비밀번호 로그 노출 방지&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 BaseSettings는 &lt;b&gt;검증 엔진(BaseModel) + 환경 변수 loader &lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이를 통해 풀려고 하는 문제는 &quot;앱의 설정값을 어디서, 어떻게 읽을 것인가&quot;였다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777880141436&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
	db_host: str = &quot;localhost&quot;
	db_port: int = 5432              # &amp;larr; 자동으로 int 변환
	max_batch_size: int = 8          # &amp;larr; 자동 변환 + 잘못된 값이면 부팅 시 폭발
	fp16: bool = True                # &amp;larr; &quot;true&quot;/&quot;1&quot;/&quot;yes&quot; 알아서 처리

settings = Settings()  # &amp;larr; 이 한 줄에서 모든 검증 끝&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BaseSettings를 사용하면 아래와 같은 3가지 이점이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 타입 자동 변환: &quot;5432&quot; &amp;rarr; 5432, &quot;true&quot; &amp;rarr; True&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 부팅 시점 검증 (fail-fast): 잘못된 값이면 Settings() 호출에서 즉시 Fail&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;span&gt;타입&lt;/span&gt; &lt;span&gt;안전&lt;/span&gt;: &lt;span&gt;이후&lt;/span&gt; &lt;span&gt;코드에서&lt;/span&gt; settings.db_port&lt;span&gt;는&lt;/span&gt; &lt;span&gt;항상&lt;/span&gt; int&lt;span&gt;라고&lt;/span&gt; &lt;span&gt;신뢰&lt;/span&gt; &lt;span&gt;가능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이와 같은 설계를 통해 외부 주입 데이터에 대한 방어선을 구축할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. env_prefix&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;env_prefix는 모든 env 환경변수에 같은 prefix를 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이를 사용하는 이유는 &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;p data-ke-size=&quot;size16&quot;&gt;많은 충돌 문제는 네임스페이스로 푼다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- 파이썬 모듈: import os vs import myapp.os&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- 자바 패키지: com.company.utils vs org.apache.utils&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- CSS BEM:. button vs. modal__button&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- DB &lt;span&gt;스키마&lt;/span&gt;: public.users vs analytics.users&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수도 이의 연장선이라고 보면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;env_prefix로 분리하게 되면 결론적으로는 해당 앱에 대한 설정을 다른 설정과 독립시킬 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 시스템 충돌 방지: 시스템 PATH와 SERVING_PATH 분리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 앱 격리: 다른 앱은 OTHERAPP_PORT을 쓰니까 충돌 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 출처 추적: env | grep SERVING_ 한 줄이면 타깃 앱 설정만 분리&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777881386077&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# config.py
  class Settings(BaseSettings):
      model_config = SettingsConfigDict(env_prefix=&quot;SERVING_&quot;)
      model_path: Path = Path(&quot;/models/model.pt&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. @model_validator&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 그대로 model와 관련된 모든 데이터를 검증할 수 있는 데코레이터이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;model은 필드들을 담고 있는 객체 전체의 상태와 구조를 뜻&lt;/b&gt;한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 @model_validator를 쓰는 이유는 타입시스템으로는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;표현하지 못하는 비즈니스 규칙을 코드로 강제하기 위함&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;@model_validator&lt;span&gt;는&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;&lt;span&gt;이&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;list&lt;span&gt;와&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;저&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;dict&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;사이엔&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;이런&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;관계가&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;있어야&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;한다&lt;/span&gt;&quot;&lt;span&gt;를&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;표현한다&lt;/span&gt;.&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1777881590128&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Validators&quot; data-og-description=&quot;Most of the time, the input data will be a dictionary (e.g. when calling UserModel(username='...')). However, this is not always the case. For instance, if the from_attributes configuration value is set, you might receive an arbitrary class instance for th&quot; data-og-host=&quot;pydantic.dev&quot; data-og-source-url=&quot;https://pydantic.dev/docs/validation/latest/concepts/validators/#model-validators&quot; data-og-url=&quot;https://pydantic.dev/docs/validation/latest/concepts/validators/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bqiYM6/dJMb9b3VLKK/Q7ZsoZfZ8bPKqvefWA8Wo0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/udsd3/dJMb82eQKqd/ERNIJ2LBh7HooC2wxNtJ4K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://pydantic.dev/docs/validation/latest/concepts/validators/#model-validators&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pydantic.dev/docs/validation/latest/concepts/validators/#model-validators&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bqiYM6/dJMb9b3VLKK/Q7ZsoZfZ8bPKqvefWA8Wo0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/udsd3/dJMb82eQKqd/ERNIJ2LBh7HooC2wxNtJ4K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;Validators&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Most of the time, the input data will be a dictionary (e.g. when calling UserModel(username='...')). However, this is not always the case. For instance, if the from_attributes configuration value is set, you might receive an arbitrary class instance for th&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pydantic.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;before, after의 기능이 다른데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@model_validator(mode='before')&lt;span&gt;&amp;nbsp; 일 경우&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;각 데이터가 있는지, 키 이름을 변환하거나 한 필드를 여러 필드로 분리할 때 여기서 처리한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@model_validator(mode='after') 일 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 필드들은 검증이 끝났다고 가정하고 필드들 간의 관계를 검증할 때 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시를 보면 password와 password_repeat이 일치하는지 검사한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777882364427&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pydantic import BaseModel, model_validator

class Settings(BaseSettings):
    target_classes: list[str] = [&quot;class_a&quot;, &quot;class_b&quot;]
    confidence_thresholds: dict[str, float] = {&quot;class_a&quot;: 0.5, &quot;class_b&quot;: 0.5}

    @model_validator(mode=&quot;after&quot;)
    def validate_thresholds_match_classes(self) -&amp;gt; 'Settings':
        missing = set(self.target_classes) - set(self.confidence_thresholds.keys())
        if missing:
            raise ValueError(f&quot;thresholds에 누락된 클래스: {missing}&quot;)
        return self&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3가지가 다 모여서 하는 일은 fail-fast 즉,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;앱이 잘못된 설정으로 부팅되는 것을 막는다&quot;&lt;/b&gt;&lt;/p&gt;</description>
      <category>프로젝트 (회고 &amp;amp; 트러블슈팅)</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/188</guid>
      <comments>https://daily-programming-til.tistory.com/188#entry188comment</comments>
      <pubDate>Mon, 4 May 2026 21:16:27 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 2025 카카오 하반기 2차 - 선인장 숨기기 (java)</title>
      <link>https://daily-programming-til.tistory.com/187</link>
      <description>&lt;figure id=&quot;og_1777077399548&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/468379&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cPO1zO/dJMb9lk9NqL/I581ip267KoMvKnMLIYRH1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/ba9jt9/dJMb9iaTMKZ/rIMKnPkfk2AoeLZCSHFOfk/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/468379&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/468379&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cPO1zO/dJMb9lk9NqL/I581ip267KoMvKnMLIYRH1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/ba9jt9/dJMb9iaTMKZ/rIMKnPkfk2AoeLZCSHFOfk/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;2025&amp;nbsp;카카오&amp;nbsp;하반기&amp;nbsp;2차&amp;nbsp;-&amp;nbsp;선인장&amp;nbsp;숨기기&lt;br /&gt;&lt;br /&gt;주제:&amp;nbsp;2차원&amp;nbsp;슬라이딩&amp;nbsp;윈도우&lt;br /&gt;&lt;br /&gt;문제:&amp;nbsp;선인장&amp;nbsp;구역이&amp;nbsp;가능한&amp;nbsp;늦게&amp;nbsp;비를&amp;nbsp;맞도록&amp;nbsp;하기&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;변수&lt;/b&gt;:&amp;nbsp;&lt;br /&gt;&amp;nbsp;-&amp;nbsp;세로&amp;nbsp;&lt;b&gt;m&lt;/b&gt;,&amp;nbsp;가로&amp;nbsp;&lt;b&gt;n&lt;/b&gt;&lt;br /&gt;&amp;nbsp;-&amp;nbsp;선인장&amp;nbsp;구역&amp;nbsp;세로&lt;b&gt;&amp;nbsp;h&lt;/b&gt;,&amp;nbsp;가로&lt;b&gt;&amp;nbsp;w&lt;/b&gt;&lt;br /&gt;&amp;nbsp;-&amp;nbsp;빗방울이&amp;nbsp;떨어지는&amp;nbsp;순서대로&amp;nbsp;좌표를&amp;nbsp;담은&amp;nbsp;2차원&amp;nbsp;정수&amp;nbsp;배열&amp;nbsp;&lt;b&gt;drops&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;동일&amp;nbsp;조건&amp;nbsp;처리:&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;- 가장 왼쪽 위칸의 좌표&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;시간복잡도:&amp;nbsp;&lt;br /&gt;&amp;nbsp;- 완전탐색시 (m-h+1)*(n-w+1)*h*w&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 최악의 경우(h=m/2, w=n/2): 연산량 약 156억 번 -&amp;gt; 1초(1억 번) 기준&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 슬라이딩 윈도우 최적화 시: O(m * n) -&amp;gt; 50만 번으로 0.01초 내 해결&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&amp;lt;슬라이딩 윈도우 구현&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[단조 큐(Monotonic Deque) 구현 규칙]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 유의점: 덱에는 실제 값이 아닌 '인덱스'를 저장하며, 초기 배열은 MAX_VALUE로 세팅(비 안 온 칸 방어)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;반복문:&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; 1. 쓸모없는 값 제거 (Tail): 덱의 뒤(tail)에서부터, 새로운 값보다 '크거나 같은(&amp;gt;=)' 값을 모두 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ➡️ 정렬을 유지하며, 현재 값보다 늦게 떨어질 빗방울은 최소값 후보에서 영구 배제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2. 새로운 값 삽입 (Tail): 덱의 뒤에 현재 인덱스 삽입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 3. 만료된 값 제거 (Head): 현재 윈도우 범위를 벗어난 인덱스를 덱의 앞(head)에서 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 4. 결과 저장: 윈도우 크기가 채워진 시점부터 덱의 맨 앞(head) 인덱스의 값을 해당 구간 최소값으로 기록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;각 열에 대해 적용 후, 그 2차원 배열을 갖고 행에도 적용&lt;br /&gt;&lt;br /&gt;[최적화 방법]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 처음에는 슬라이딩 윈도우를 &lt;b&gt;다 돈 후에 답을 찾았는데&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;행 별 슬라이딩 윈도우를 돌면서 답을 바로 업데이트 하는 방법이 있었다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1777079191832&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    public int[] solution(int m, int n, int h, int w, int[][] drops) {
        int[] answer = new int[2];
        int[][] T = new int[m][n];
        int[][] R = new int[m][n];
        int[][] C = new int[m][n];
        
        for(int r=0; r&amp;lt;m; r++){
            for(int c=0; c&amp;lt;n; c++){
                T[r][c] = Integer.MAX_VALUE;
            }
        }
        
        for(int i=0; i&amp;lt;drops.length; i++){
            int r = drops[i][0];
            int c = drops[i][1];
            
            T[r][c] = i+1;
        }
        
        
        for(int r=0; r&amp;lt;m; r++){
            int[] deque = new int[n];
            
            int head = 0;
            int tail = 0;
            for (int c = 0; c &amp;lt; n; c++) {
                while(head &amp;lt; tail &amp;amp;&amp;amp; T[r][deque[tail-1]] &amp;gt;= T[r][c]){
                    tail--;
                }
                
                deque[tail++] = c;
                
                if(deque[head] &amp;lt;= c - w){
                    head++;
                }
                
                if (c &amp;gt;= w - 1) {
                    R[r][c - w + 1] = T[r][deque[head]];
                }
            }
        }
        
        int maxVal = -1;
        int ansR = 0, ansC = 0;
        
        for(int c = 0; c &amp;lt; n; c++){
            int[] deque = new int[m];
            
            int head = 0;
            int tail = 0;
            for (int r=0; r&amp;lt;m; r++) {
                while(head &amp;lt; tail &amp;amp;&amp;amp; R[deque[tail-1]][c] &amp;gt;= R[r][c]){
                    tail--;
                }
                
                deque[tail++] = r;
                
                if(deque[head] &amp;lt;= r - h){
                    head++;
                }
                
                // 윈도우가 완성된 즉시 최댓값 갱신 (메모리에 배열 C를 쌓지 않음)
                if (r &amp;gt;= h - 1) {
                    int currentMin = R[deque[head]][c];
                    int currentR = r - h + 1;
                    int currentC = c;
                    
                    if (currentMin &amp;gt; maxVal) {
                        maxVal = currentMin;
                        ansR = currentR;
                        ansC = currentC;
                    } else if (currentMin == maxVal) {
                        // Tie-breaking: 행이 작거나, 행이 같으면 열이 작은 것
                        // 세로로 돌기 때문에 필요한 분기
                        if (currentR &amp;lt; ansR || (currentR == ansR &amp;amp;&amp;amp; currentC &amp;lt; ansC)) {
                            ansR = currentR;
                            ansC = currentC;
                        }
                    }
                }
            }
        }
        
        
        return new int[]{ansR, ansC};
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>코딩테스트</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/187</guid>
      <comments>https://daily-programming-til.tistory.com/187#entry187comment</comments>
      <pubDate>Sat, 25 Apr 2026 10:07:00 +0900</pubDate>
    </item>
    <item>
      <title>AI 생산성 올리는 방법 - AI는 칭찬을 좋아한다는 걸 안다.</title>
      <link>https://daily-programming-til.tistory.com/186</link>
      <description>&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=jDJZxERIhnc&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cXauKl/dJMb9gxhR0G/ZiOUlwrx4GvS5xLOWKFKv0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=564_134_748_336,https://scrap.kakaocdn.net/dn/AxGKV/dJMb9cBEALA/zzEDRjlr8t06FuS3l6ts2k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=564_134_748_336&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;스탠포드가 가르치는 AI생산성 10배 높이는 방법 | 제레미 어틀리 2편&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/jDJZxERIhnc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;AI 시대에 이미 알고 있었던 내용 말고 흥미로운 사실이 있어서 적어보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 프롬프트를 제작할때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;1. Few-shot Prompting (예시 제공)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원하는 결과물의 '좋은 예시'와 '나쁜 예시'를 함께 줘서 AI가 모방하도록 하라.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;2. Chain of Thought (생각의 사슬)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;답변하기 전에 단계별로 생각 과정을 설명해줘&quot;라고 지시하여 AI가 소리 내어 사고하도록 하라. (단순히 답만 내는 것보다 추론 능력이 향상됨)&lt;/li&gt;
&lt;li&gt;기본적으로 요즘 AI는 사고 과정을 보여줘서 Chain of Thought를 쓴다고 생각했는데, Deep Research 같은 연구 말고는 명시하지 않는 이상 잘 이용하지 않는다고 한다.&lt;/li&gt;
&lt;li&gt;쓴다 해도 명시적인 CoT보다는 Hidden CoT 여서 사용자에게 보이지 않는다.&lt;/li&gt;
&lt;li&gt;영상에서는 명시적으로 보이게 해서 사용자가 어디가 잘못되었는지 코칭 하는 방향을 제시한다.&lt;/li&gt;
&lt;li&gt;이거는 결국 LLM이 이전 단어들을 갖고 다음 단어를 예측하는 기계여서 전체 과정을 사고하게 하면 성능이 좋아지는 원리였다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 2가지는 알고 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7&quot;&gt;3. Reverse Prompting (역방향 프롬프팅)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;작업을 시작하기 전에, 더 필요한 정보가 있다면 나에게 질문해줘&quot;라고 지시하라.&lt;/li&gt;
&lt;li&gt;AI가 멋대로 정보를 지어내지(Hallucination) 않고, 완벽한 결과를 위해 필요한 데이터를 사용자에게 되묻게 만드는 핵심 기술입니다. (영상에서는 이를 'Table Stakes', 즉 기본 요건이라고 표현합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10&quot;&gt;4.&amp;nbsp; 페르소나 부여&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;예:&amp;nbsp; &quot;너는 냉전 시대 러시아 올림픽 심판이야. 아주 엄격하게 점수를 깎아줘&quot;라고 구체적인 태도와 역할을 지정하라.&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 2가지 새로웠는데, 어떤 일을 새롭게 시작하는 사람에게 알려주듯 AI에게 알려주면 더 일을 잘한다는 전체적인 내용있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 일을 시작하는 사람에게&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 일에 대한 소개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 일에 대한 좋고 나쁜 예시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &quot;언제든 질문을 해 달라&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 소개하지 않는가. 그거를 그대로 AI에게도 적용하면 되는 것이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 사람이 칭찬받는 걸 좋아하는 걸 안다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 페르소나 부여에서 오히려 가혹한 페르소나를 부여하면 칭찬만 하던 AI가 돌변했다..ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 인지의 외주화(Cognitive offloading) 에 대해서도 걱정했었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상에서는 AI가 결국 사용자의 거울이라며, 내가 일을 대신해 주길 바라면 그거를 도와줄거고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 내가 비판적이고 날카로운 사고를 하고 싶으면 그거를 도와줄거라고 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 사용하기 나름이라는 것이다. 그리고 사용하고 싶은 방향을 AI에게 충분히 설명하는 것이 중요했다.&amp;nbsp;&lt;/p&gt;</description>
      <category>ML, 딥러닝</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/186</guid>
      <comments>https://daily-programming-til.tistory.com/186#entry186comment</comments>
      <pubDate>Thu, 19 Feb 2026 08:45:40 +0900</pubDate>
    </item>
    <item>
      <title>Java Arrays 유틸리티 클래스 정리</title>
      <link>https://daily-programming-til.tistory.com/185</link>
      <description>&lt;pre id=&quot;code_1762837127361&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Arrays;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 메서드가 static으로 인스턴스 생성이 불필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Arrays는 &lt;b&gt;배열&lt;/b&gt;을 위한 필수 유틸리티 클래스이다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Arrays를 사용하면 아주 편리한 배열 관련 기능을 쓸 수 있다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정렬&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762839651642&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Arrays.sort(arr);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762839705992&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Arrays.toString(arr);
Arrays.deepToString(arr2);


int[] arr = {1, 2, 3};
System.out.println(arr);                    // [I@15db9742 (주소)
System.out.println(Arrays.toString(arr));   // [1, 2, 3] ✅

int[][] arr2 = new int[][]{{1, 2, 3}, {4, 5, 6}};
System.out.println(Arrays.deepToString(arr2)); // [[1, 2, 3], [4, 5, 6]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열 비교&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762840147493&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};

arr1 == arr2;                    // false (참조 비교)
Arrays.equals(arr1, arr2);       // true (내용 비교) ✅


Arrays.deepEquals(arr1, arr2);  // 다차원&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변환&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762840163510&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Arrays.asList(arr);             // 배열 &amp;rarr; 리스트
Arrays.stream(arr);             // 배열 &amp;rarr; 스트림&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;복사&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762840248427&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Arrays.copyOf(arr, length);
Arrays.copyOfRange(arr, from, to);

int[] arr = {1, 2, 3, 4, 5};
int[] copy = Arrays.copyOf(arr, 3);        // [1, 2, 3]
int[] range = Arrays.copyOfRange(arr, 1, 4);  // [2, 3, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;값 채우기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(원래 있던 값까지 전부 대체)&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1762840284411&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] arr = new int[5];
Arrays.fill(arr, 10);
// [10, 10, 10, 10, 10]

Arrays.fill(arr, 1, 3, 99);  // 인덱스 1~2만
// [10, 99, 99, 10, 10]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이진 탐색(정렬되어 있어야 함)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762837857048&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Arrays.binarySearch(arr, key);&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩테스트</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/185</guid>
      <comments>https://daily-programming-til.tistory.com/185#entry185comment</comments>
      <pubDate>Tue, 11 Nov 2025 15:00:06 +0900</pubDate>
    </item>
    <item>
      <title>Java ArrayList, 배열 서로 변환하기</title>
      <link>https://daily-programming-til.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Java에는 여러가지 배열이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트에서 자주 쓰는거는 그냥 배열([]), ArrayList 정도 될거 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 2개의 가장 큰 차이점은 &lt;b&gt;고정 크기이냐 가변 크기&lt;/b&gt;이냐의 차이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 배열은 고정 크기로 크기를 바꿀 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArrayList는 동적 할당을 하여 크기를 바꿀 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열은 어떻게 선언/할당할까&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/61euI/dJMcacg8JQs/c8HVPOV5h0QGKVWgpTfFy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/61euI/dJMcacg8JQs/c8HVPOV5h0QGKVWgpTfFy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/61euI/dJMcacg8JQs/c8HVPOV5h0QGKVWgpTfFy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F61euI%2FdJMcacg8JQs%2Fc8HVPOV5h0QGKVWgpTfFy1%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;313&quot; height=&quot;202&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 보다시피&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기만 정해서 빈배열을 만들거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정식 방법과 약식 문법이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XRf0H/dJMcaihmbAe/fy3z3lIkj4KkUqma7QEMDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XRf0H/dJMcaihmbAe/fy3z3lIkj4KkUqma7QEMDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XRf0H/dJMcaihmbAe/fy3z3lIkj4KkUqma7QEMDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXRf0H%2FdJMcaihmbAe%2Ffy3z3lIkj4KkUqma7QEMDk%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;534&quot; height=&quot;122&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약식 문법은 &lt;b&gt;선언할때만 가능&lt;/b&gt;하여 할당 할때는 사용할 수 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ArrayList는 어떻게 선언/할당할까&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CwfLS/dJMcacuFYRU/QEgNCksdNm3E08ICUCkki1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CwfLS/dJMcacuFYRU/QEgNCksdNm3E08ICUCkki1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CwfLS/dJMcacuFYRU/QEgNCksdNm3E08ICUCkki1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCwfLS%2FdJMcacuFYRU%2FQEgNCksdNm3E08ICUCkki1%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;531&quot; height=&quot;275&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List.of와 Arrays.asList를 사용해서 리스트를 할당할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List.of는 null 값을 처리하지 못한 다는 것을 기억하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNSGHh/dJMcafroAKj/zAkA4pIKXEBEzBgMvUUxX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNSGHh/dJMcafroAKj/zAkA4pIKXEBEzBgMvUUxX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNSGHh/dJMcafroAKj/zAkA4pIKXEBEzBgMvUUxX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNSGHh%2FdJMcafroAKj%2FzAkA4pIKXEBEzBgMvUUxX0%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;568&quot; height=&quot;129&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ImkVW/dJMcahvYZ6g/S3jkDEOu4K0gVzuwIz3fO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ImkVW/dJMcahvYZ6g/S3jkDEOu4K0gVzuwIz3fO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ImkVW/dJMcahvYZ6g/S3jkDEOu4K0gVzuwIz3fO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FImkVW%2FdJMcahvYZ6g%2FS3jkDEOu4K0gVzuwIz3fO1%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;631&quot; height=&quot;218&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 ArrayList를 배열로 바꾸고 싶으면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dx0weB/dJMcacBrwvj/nK5b39W0eyjYGkuR8ptlK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dx0weB/dJMcacBrwvj/nK5b39W0eyjYGkuR8ptlK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dx0weB/dJMcacBrwvj/nK5b39W0eyjYGkuR8ptlK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdx0weB%2FdJMcacBrwvj%2FnK5b39W0eyjYGkuR8ptlK0%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;448&quot; height=&quot;154&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 2가지 방법이 있다. 인자로 넘겨주는거는 타입 정보이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입을 알려줄테니 크기는 알아서 맞게 할당해서 반환하라는 거다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/184</guid>
      <comments>https://daily-programming-til.tistory.com/184#entry184comment</comments>
      <pubDate>Tue, 11 Nov 2025 13:57:37 +0900</pubDate>
    </item>
    <item>
      <title>Sticky Session vs Stateless 아키텍처(Redis, JWT)</title>
      <link>https://daily-programming-til.tistory.com/183</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Stateless 아키텍처가 무엇인지 궁금했었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Load Balancer를 공부하며 Sticky Session에 대해 알게 되었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘에는 Sticky Session 대신 어떤 것을 더 선호하는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stick Session(Session Affinity)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpwjzC/dJMcahQhL9N/KUrWgkIEJO58ZdlaOGplKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpwjzC/dJMcahQhL9N/KUrWgkIEJO58ZdlaOGplKK/img.png&quot; data-alt=&quot;출처: 네이버&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpwjzC/dJMcahQhL9N/KUrWgkIEJO58ZdlaOGplKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpwjzC%2FdJMcahQhL9N%2FKUrWgkIEJO58ZdlaOGplKK%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;371&quot; height=&quot;190&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 네이버&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Affinity는 친밀감을 뜻한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Session Affinity는 말 그대로 &lt;span style=&quot;background-color: #ef5369; color: #ffffff;&quot;&gt;세션을 특정 서버에 붙여두는 것&lt;/span&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;li&gt;저장된 세션에 접근이 가능하다.&lt;/li&gt;
&lt;/ul&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;Duration-based cookie(기간 기반)&lt;/li&gt;
&lt;li&gt;Application-based cookie(애플리케이션 기반)&lt;/li&gt;
&lt;/ul&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;Duration-based&lt;/td&gt;
&lt;td&gt;Application-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;쿠키 생성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ALB&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;AWSALB (고정)&lt;/td&gt;
&lt;td&gt;커스텀 (예: SESSIONID)&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;애플리케이션 로직&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;가능&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;보통&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;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(AWS SAA 강의에 나오길래 정리해봤다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요즘은 마이크로 서비스 아키텍처를 구성하며 Sticky Session을 쓰지 않는 방향으로 바뀌고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Sticky Session을 사용했던 이유는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;특정 서버의 메모리에 세션이 저장&lt;/b&gt;되어 있어&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자의 요청이 무조건 &quot;그&quot; 서버로 가야 했었다. 즉 서버를 늘리더라도 기존 사용자는 새로운 서버를 쓰지 못한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 어떤 서버가 죽어도 다른 서버는 작동되는 것을 목적으로 하는 마이크로 서비스 아키텍처에서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Sticky Session처럼 서버의 메모리에 세션이 저장되어 있으면&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 서버가 죽으면 그 서버의 세션 정보가 사라져 해당 서버 사용자들이 로그아웃 됨&lt;/li&gt;
&lt;li&gt;다른 서버들은 정상 작동하지만, 세션 정보가 없어 해당 사용자 처리 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;수평 확장이 어렵고, 서버 간 부하 불균형이 발생할 수 있다. (고가용성을 달성하기 어렵다.)&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Redis/DynamoDB 사용하여 중앙에서 관리하도록 하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT를 사용하여 Stateless 하도록 하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis/DynamoDB&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(DynamoDB는 AWS의 noSQL 서비스다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis나 DynamoDB를 사용하면 여기에 세션을 저장해 둔다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;사용자 로그인 &amp;rarr; 서버 1 &amp;rarr; &lt;b&gt;Redis에 세션 저장 &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음 요청 &amp;rarr; 서버 2로 가도 OK &amp;rarr; &lt;b&gt;Redis에서 세션 읽어옴 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또 다음 요청 &amp;rarr; 서버 3으로 가도 OK &amp;rarr; &lt;b&gt;Redis에서 세션 읽어옴&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 &lt;b&gt;Redis에서 중앙관리를 하기 때문에&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;어떤 서버로 가도 상관없게 만들 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JWT&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jwt 토큰을 사용하면 완전 stateless로 구현이 가능하다. 이거는 아예 서버에 세션 저장을 안 하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;사용자 로그인 &amp;rarr; 서버가 &lt;b&gt;JWT 토큰 발급&lt;/b&gt; (서버에 저장 안 함)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음 요청 &amp;rarr; 서버 2 &amp;rarr; &lt;b&gt;JWT 검증&lt;/b&gt;만 함 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또 다음 요청 &amp;rarr; 서버 3 &amp;rarr; &lt;b&gt;JWT 검증&lt;/b&gt;만 함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 신분증을 들고 다니면서 서버에서는 검증만 하는 구조이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 전부 장점만 있는 건 아니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis 세션 관리 시 단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 redis가 다운되면 전체 서비스 세션이 불가해진다.&lt;/li&gt;
&lt;li&gt;redis를 조회하는 오버헤드가 발생한다.&lt;/li&gt;
&lt;li&gt;redis를 운영하는 인프라 비용이 발생한다.&lt;/li&gt;
&lt;li&gt;데이터 일관성 문제가 발생할 수 있다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 1에서 세션을 업데이트하는 동시에 서버 2가 읽으면 문제가 발생할 수 있다. (Race condition)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JWT의 단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉시 무효화가 불가능하다. (토큰 탈취가 가능할 수 있다.)&lt;/li&gt;
&lt;li&gt;상태 변경이 반영되지 않는다. (토큰 만료까지 기다려야 함.)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 어느 정도 보완하는 하이브리드 방식을 이전 프로젝트 때 사용했다.(현업에서도 가장 많이 쓰이는 거 같다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JWT + Redis 블랙리스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 JWT는 access token과 refresh token을 함께 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token &lt;b&gt;재발급&lt;/b&gt; 시에만 Redis를 확인한다.&lt;/li&gt;
&lt;li&gt;로그아웃 하면 Refresh Token을 Redis에 블랙리스트에 등록한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;Access Token이 유효할 때는 JWT의 강점인 stateless 한 상태를 유지&lt;/b&gt;할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis를 거칠 필요 없이 바로 검증만 진행하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;Access Token이 만료되었을 때 Refresh Token이 유효한지 Redis로 확인한 후 발급한다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #e06c75;&quot;&gt;Pure JWT의 문제점&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;로그아웃해도 토큰이 만료될 때까지 유효하다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Access Token(15분)은 그렇다 치고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Refresh Token(7일)까지 계속 작동한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;rarr; 로그아웃이 의미가 없다&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #e06c75;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #e06c75;&quot;&gt;Redis 블랙리스트로 개선&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Refresh Token을 블랙리스트에 등록하여 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;더 이상 새 Access Token을 발급받을 수 없게 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;rarr; Access Token 만료(15분) 후 완전 차단 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;rarr; &quot;즉시 무효화&quot;는 아니지만 &quot;짧은 시간 내 무효화&quot; 달성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 요즘은 무상태 인증을 통해 서버를 수평확장하는 것이 더 유리한 상황이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sticky Session을 사용하면 구현이 간단하고 추가 인프라가 불필요하지만 확장성이 떨어진다는 점을 알고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Redis를 이용한 세션, JWT만 쓴 방법, 그리고 JWT+ Redis를 쓰는 방식의 장단점을 알고 있으면 좋을 거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ML 서빙 &amp;amp; 백엔드 시스템</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/183</guid>
      <comments>https://daily-programming-til.tistory.com/183#entry183comment</comments>
      <pubDate>Tue, 11 Nov 2025 12:47:40 +0900</pubDate>
    </item>
    <item>
      <title>TLS, Let's encrypt 와 cert-manager</title>
      <link>https://daily-programming-til.tistory.com/182</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저번 프로젝트를 진행할때 cert-manager와 Let's encrypt를 썼었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS를 위해 꼭 필요한 SSL/TLS에 대해 일단 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSL/TLS&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선 SSL(Secure Sockets Layer)를 업그레이드 한것이 TLS(Transport Layer Security)이다. (무려 1999년에)&lt;/li&gt;
&lt;li&gt;모두&lt;span style=&quot;background-color: #ef6f53; color: #ffffff;&quot;&gt; client와 서버간 암호화를 위해 만들어진 통신 프로토콜이다.&amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;HTTPS의 S도 이 TLS를 이용해 Secure 한다는 뜻이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;SSL은 현재 쓰이지 않고 TLS를 쓰지만 관용적으로 그냥 TLS 대신 SSL라고 부르기도 한다.&lt;/li&gt;
&lt;li&gt;(현실에서 SSL를 거의 TLS 의미로 계속 쓰고 있는 듯 하다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TLS Handshake, 인증서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;TLS 암호화 통신을 하려면&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TLS Handshake 필요하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &amp;darr; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;TLS Handshake에서 서버 인증 필요한데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &amp;darr; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그래서 인증서 필요하다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;span&gt;TLS Handshake는 다음과 같이 이루어진다.&amp;nbsp;&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. 클라이언트 Hello - (실제로 hello 이렇게 보낸다. ㅋㅋㅋ)&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;이때 TLS 버전, 무작위 바이트 문자열(랜덤값), 그리고 클라이언트가 지원하는 암호화 스위트 목록을 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMeIzg/dJMcajHkzLi/8uk87l4F0kCBJyPc4nul1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMeIzg/dJMcajHkzLi/8uk87l4F0kCBJyPc4nul1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMeIzg/dJMcajHkzLi/8uk87l4F0kCBJyPc4nul1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMeIzg%2FdJMcajHkzLi%2F8uk87l4F0kCBJyPc4nul1K%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;597&quot; height=&quot;177&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 서버 hello&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 바로 인증서와 서버가 선택한 암호화 스위트, 서버가 생성한 무작위 바이트 문자열(랜덤값)을 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2xOxA/dJMcacVJZYA/qfgJL0cEKflS8BpjnMcch0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2xOxA/dJMcacVJZYA/qfgJL0cEKflS8BpjnMcch0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2xOxA/dJMcacVJZYA/qfgJL0cEKflS8BpjnMcch0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2xOxA%2FdJMcacVJZYA%2FqfgJL0cEKflS8BpjnMcch0%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;618&quot; height=&quot;184&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 키 교환&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 서버의 인증서를 검토해 신뢰하는 CA(certificate authority)인지, 도메인의 소유자가 맞는지 확인한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;확인 후 클라이언트, 서버 각자 랜덤값과 교환된 키 값을 이용해 세션 키를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 클라이언트에서 'Cipher Spec'를 전송하고 이후로는 암호화 하여 통신된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bryA4x/dJMcaeeXaSw/ZeTOx5qGVpkrWRlpraZhck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bryA4x/dJMcaeeXaSw/ZeTOx5qGVpkrWRlpraZhck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bryA4x/dJMcaeeXaSw/ZeTOx5qGVpkrWRlpraZhck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbryA4x%2FdJMcaeeXaSw%2FZeTOx5qGVpkrWRlpraZhck%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;633&quot; height=&quot;173&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(제가 한번 캡처해 봤습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인증서 발급 수단&lt;/h3&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;p data-ke-size=&quot;size16&quot;&gt;우선 개인이 가장 쓰기 만만한 것이 Let's encrypt이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징으로는&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무료다.&lt;/li&gt;
&lt;li&gt;유효기한이 90일이다.&lt;/li&gt;
&lt;li&gt;Domain Validation만 지원한다. (즉, 도메인을 소유하고 있는지만 체크하고, 실제 기업인지 이런것들은 확인하지 않는다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 본인 도메인을 사서 서버에서 직접 발급을 받아야 하는데 단점이 아까 특징에서 보이다시피 유효기간이 90일이라 계속 갱신해줘야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사용하는 것이&lt;span style=&quot;background-color: #ffc9af;&quot;&gt; certbot 또는 쿠버네티스 환경에서는 cert-manager&lt;/span&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 프로젝트에서 실수했던 점이 쿠버네티스 환경인데 certbot을 사용해 인증서를 발급받고 secret으로 등록해서 90일 후에 재갱신해야하는 비효율적인 설계를 했었다. (완전 바보)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 서버 환경에서는 certbot을 사용해 자동 재갱신을 하도록 할 수 있고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에서는 cert-manager를 통해 알아서 관리하도록 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;따라서 정리하면 Let's encrypt는 인증서를 발급하는 기관이라고 보면 되고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;cert-manager는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;인증서의 기한이 만료되지 않도록 관리하고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;인증서를 새로 발급 받고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;kubenetes secret에 등록하며 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;여러가지 일을 하는 전담 직원이라고 보면 좋을거 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.tosspayments.com/resources/glossary/tls&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.tosspayments.com/resources/glossary/tls&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/compare/the-difference-between-ssl-and-tls/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/compare/the-difference-between-ssl-and-tls/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ML 서빙 &amp;amp; 백엔드 시스템</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/182</guid>
      <comments>https://daily-programming-til.tistory.com/182#entry182comment</comments>
      <pubDate>Mon, 10 Nov 2025 17:44:12 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스: 지게차와 크레인</title>
      <link>https://daily-programming-til.tistory.com/181</link>
      <description>&lt;figure id=&quot;og_1762756785041&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/388353&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bi37pG/hyZNa3KIUj/QCLtxKA53jGgcIMNV2mVfk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/czAlhw/hyZNtm76Bq/4axsIkOipCBW3rMT3cfYC0/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/388353&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/388353&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bi37pG/hyZNa3KIUj/QCLtxKA53jGgcIMNV2mVfk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/czAlhw/hyZNtm76Bq/4axsIkOipCBW3rMT3cfYC0/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;구현 문제처럼 보이는 BFS 문제였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제 요약&lt;/h4&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;p data-ke-size=&quot;size16&quot;&gt;각 회차별 수단과 제거 알파벳이 주어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;([&quot;A&quot;, &quot;BB&quot;, &quot;A&quot;])&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알파벳이 한번만 나오면 지게차를 사용하고 (예시: A)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번 나오면 크레인을 사용한다. (예시: AA)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지게차를 사용할 경우 외부에서 접근 가능한 블록들만 제거가 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 순차적으로 지게차를 이용해 제거하고 접근 가능한 블록을 연쇄적으로 제거하는 것이 아닌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 회차별 딱 그 시점에서 제거 가능한 블록들만 제거 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHeMn6/dJMcaaQ9N8g/JgmWO3Kei1SGbvHbnv1ZXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHeMn6/dJMcaaQ9N8g/JgmWO3Kei1SGbvHbnv1ZXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHeMn6/dJMcaaQ9N8g/JgmWO3Kei1SGbvHbnv1ZXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHeMn6%2FdJMcaaQ9N8g%2FJgmWO3Kei1SGbvHbnv1ZXK%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;232&quot; height=&quot;235&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 상황에서 오직 주황색 C만 제거가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 알고리즘&lt;/h3&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;해당하는 알파벳 갯수를 세면서 그 자리를 0으로 채워넣는다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;지게차를 사용할 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;크레인을 사용했을때 0으로 처리된 부분들이 외부와 연결되는지 확인하는 BFS를 실행한다. - cleanUp&lt;/li&gt;
&lt;li&gt;이후 외곽인지 체크 후 0으로 채워 넣으며 갯수를 센다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외곽인지 체크하는 방법은 위, 아래, 양 옆으로 이동했을 때 외부인지 확인하는 방법을 사용했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TroubleShooting&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 내가 했던 실수는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BFS 구현시 visited를 queue에서 poll 하자마자 검사하지 않았다는 점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이것이 상당한 성능 저하를 불러왔다. 나는 그 다음 이동할때 검사를 했는데 그렇게 되면 중복처리되는 경우가 발생한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;1160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/la2Nu/dJMcajURJTS/smV3nQEDKWpUAYbkrKjQhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/la2Nu/dJMcajURJTS/smV3nQEDKWpUAYbkrKjQhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/la2Nu/dJMcajURJTS/smV3nQEDKWpUAYbkrKjQhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fla2Nu%2FdJMcajURJTS%2FsmV3nQEDKWpUAYbkrKjQhK%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;268&quot; height=&quot;407&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;1160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 상황일때, poll 하자 마자 visited 검사를 하지 않으면 두번째 3에서도 주변 탐색을 진행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 불필요한 for문을 돌게 되는것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 코드를 더 빠르게 하는 방법은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;String 대신 char 쓰기&lt;/li&gt;
&lt;li&gt;int[] 대신 boolean[] 쓰기&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정도 있을거 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;char를 사용했을때, 모든 초기값은 0이라는 사실을 기억하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/181</guid>
      <comments>https://daily-programming-til.tistory.com/181#entry181comment</comments>
      <pubDate>Mon, 10 Nov 2025 16:08:58 +0900</pubDate>
    </item>
    <item>
      <title>Throughput과 IOPS 차이</title>
      <link>https://daily-programming-til.tistory.com/180</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정의부터 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOPS는 Input/Output Operations Per Second를 의미한다.&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;p data-ke-size=&quot;size16&quot;&gt;반면에 Throughput은 한국말로 처리량이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초당 전송할 수 있는 데이터의 양을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOPS는 빈도를 나타내고 Througpht은 양을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비유로 이해해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 고속도로에 작은 차가 많이 있다면 높은 IOPS에 낮은 Throughtput를 의미하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 트럭들이 적게 있다면 낮은 IOPS에 높은 Throughtput를 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실에서는 어떤 상황에서 각각이 중요할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 큰 파일을 읽어야 한다면 당연히 Throughput이 중요할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 데이터베이스 쿼리를 많이 해야한다면 IOPS가 중요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적으로 본다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Throughput = IOPS x Block Size로 표현할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div data-test-render-count=&quot;1&quot;&gt;
&lt;div&gt;
&lt;div data-is-streaming=&quot;false&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;u&gt;클로드에게 실제 사례를 알려달라고 했다.&amp;nbsp;&lt;/u&gt;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;IOPS 중심 워크로드:&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&amp;nbsp;데이터베이스&amp;nbsp;(OLTP)&lt;br /&gt;&lt;/b&gt;특징:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;많은&amp;nbsp;작은&amp;nbsp;쿼리&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;랜덤&amp;nbsp;읽기/쓰기&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;4KB&amp;nbsp;~&amp;nbsp;16KB&amp;nbsp;블록&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;예:&amp;nbsp;MySQL,&amp;nbsp;PostgreSQL&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;필요:&amp;nbsp;높은&amp;nbsp;IOPS&amp;nbsp;★★★★★&lt;b&gt;&lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;이메일&amp;nbsp;서버&lt;br /&gt;&lt;/b&gt;&amp;nbsp;특징:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;많은&amp;nbsp;작은&amp;nbsp;메시지&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;랜덤&amp;nbsp;접근&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;필요:&amp;nbsp;높은&amp;nbsp;IOPS&amp;nbsp;★★★★★&lt;b&gt;&lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;가상&amp;nbsp;머신&amp;nbsp;부팅&amp;nbsp;볼륨&lt;br /&gt;&lt;/b&gt;&amp;nbsp;&amp;nbsp;특징:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;많은&amp;nbsp;작은&amp;nbsp;파일&amp;nbsp;읽기&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;랜덤&amp;nbsp;패턴&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;필요:&amp;nbsp;높은&amp;nbsp;IOPS&amp;nbsp;★★★★☆&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Throughput 중심 워크로드:&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&amp;nbsp;비디오&amp;nbsp;스트리밍&lt;br /&gt;&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;특징:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;큰&amp;nbsp;파일&amp;nbsp;순차&amp;nbsp;읽기&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;연속된&amp;nbsp;데이터&amp;nbsp;전송&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;필요:&amp;nbsp;높은&amp;nbsp;Throughput&amp;nbsp;★★★★★&lt;b&gt;&lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;빅데이터&amp;nbsp;분석&amp;nbsp;(Hadoop,&amp;nbsp;Spark)&lt;br /&gt;&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;특징:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;대용량&amp;nbsp;파일&amp;nbsp;스캔&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;순차&amp;nbsp;읽기/쓰기&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;필요:&amp;nbsp;높은&amp;nbsp;Throughput&amp;nbsp;★★★★★&lt;b&gt;&lt;br /&gt;&lt;br /&gt;3. 로그 수집&amp;nbsp; - (의외로 대용량이구만)&lt;br /&gt;&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;특징:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;연속&amp;nbsp;쓰기&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;큰&amp;nbsp;데이터&amp;nbsp;블록&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;필요:&amp;nbsp;높은&amp;nbsp;Throughput&amp;nbsp;★★★★☆&lt;b&gt;&lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;백업/복원&lt;br /&gt;&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;특징:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;대용량&amp;nbsp;전송&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;순차&amp;nbsp;접근&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;필요:&amp;nbsp;높은&amp;nbsp;Throughput&amp;nbsp;★★★★★&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;radix-_r_v_&quot; data-state=&quot;closed&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div data-state=&quot;closed&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>ML 서빙 &amp;amp; 백엔드 시스템</category>
      <author>yolang</author>
      <guid isPermaLink="true">https://daily-programming-til.tistory.com/180</guid>
      <comments>https://daily-programming-til.tistory.com/180#entry180comment</comments>
      <pubDate>Fri, 7 Nov 2025 14:51:57 +0900</pubDate>
    </item>
  </channel>
</rss>