<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>luna_lee.log</title>
        <link>https://velog.io/</link>
        <description>기술블로그보다는 기록블로그</description>
        <lastBuildDate>Tue, 14 Jan 2025 03:01:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>luna_lee.log</title>
            <url>https://images.velog.io/images/luna_lee/profile/8f5b0159-2fd0-4773-80a8-d40f714ab211/anwlrptorRKfkefqhfmaekf00.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. luna_lee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/luna_lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[SpringBoot / React] STOMP로 웹소켓 통신하기 ]]></title>
            <link>https://velog.io/@luna_lee/SpringBootReact-STOMP%EB%A1%9C-%EC%9B%B9%EC%86%8C%EC%BC%93-%ED%86%B5%EC%8B%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@luna_lee/SpringBootReact-STOMP%EB%A1%9C-%EC%9B%B9%EC%86%8C%EC%BC%93-%ED%86%B5%EC%8B%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 14 Jan 2025 03:01:41 GMT</pubDate>
            <description><![CDATA[<p>개인적으로 토이 프로젝트를 하다가 서버에서 실시간으로 클라이언트의 화면을 변경해야하는 기능이 필요해서 웹소켓 통신을 구현하게 되었다.
처음에는 직접 웹소켓 통신을 구현하고 싶었는데 고려할 것이 너무 많았다... 메시지 전송 방식이나 heartbeat, 재연결 등등...
조금 더 찾아보니 STOMP(Simple Text Oriented Messaging Protocol)라는 프로토콜과 이를 사용하기 위한 라이브러리들이 존재하고 있었다. 
그래서 STOMP를 이용해 웹소켓 통신을 구현하기로 했고, 역시나 처음에 조금 헤맸었기에 일단 내가 구현한/이해한 내용을 정리해보려고 한다. 😁</p>
<br>
<hr>

<h1 id="springboot-설정">SpringBoot 설정</h1>
<h3 id="1-buildgradle에-웹소켓과-관련-stomp-의존성-추가">1. build.gradle에 웹소켓과 관련 STOMP 의존성 추가</h3>
<pre><code>implementation &#39;org.springframework.boot:spring-boot-starter-websocket&#39;
implementation &#39;org.webjars:sockjs-client:1.5.1&#39;
implementation &#39;org.webjars:stomp-websocket:2.3.4&#39;</code></pre><br>

<h3 id="2-websocketconfig-추가">2. WebSocketConfig 추가</h3>
<p>1) <strong>WebSocketMessageBrokerConfigurer</strong>을 상속 받은 Configuration 클래스 생성</p>
<p>2) 클래스에 <strong>@EnableWebSocketMessageBroker</strong> 어노테이션 추가: Spring WebSocket 메시징 시스템에서 브로커를 활성화</p>
<p>3) <strong>registerStompEndpoints</strong> override: </p>
<ul>
<li>registry.addEndpoint()에 웹소켓 연결을 실행할 수 있는 엔드포인트 지정</li>
<li>setAllowedOrigins()으로 CORS 에러가 발생하지 않도록 origin 허용</li>
<li>withSockJS()로 SockJS 사용 여부 설정(사용하지 않을 거라면 withSockJS()를 생략하면 된다.)</li>
</ul>
<p>4) <strong>configureMessageBroker</strong> override: </p>
<ul>
<li><p>setApplicationDestinationPrefixes: 클라이언트로부터 메시지를 받을 엔드포인트의 접두사 설정</p>
</li>
<li><p>enableSimpleBroker: 메시지를 클라이언트가 구독한 경로로 전달하기 위한 메시지 브로커 설정
(참고로 내 경우에는 서버에서 특정 로직 수행 후 <strong>클라이언트에 메시지를 전송하는 기능</strong>만 구현했기 때문에 configureMessageBroker는 실제 필요하지 않았다. 다만 참고를 위해 적어두었다. MessageBroker 기능을 사용하기 위해서는 @MessageMapping @SendTo 어노테이션을 이용해 메시지를 수신하고 전달하게 된다.)</p>
<pre><code>@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {
      registry
              // 클라이언트가 서버로 메시지를 보낼 때 엔드포인트의 접두사
              .setApplicationDestinationPrefixes(&quot;/pub&quot;)
              // 클라이언트로 구독한 경로로 메시지를 전달하기 위해 사용하는 내장 메시지 브로커 활성화
              .enableSimpleBroker(&quot;/sub&quot;);
  }

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
      registry.addEndpoint(&quot;/ws&quot;)
              .setAllowedOrigins(&quot;http://localhost:3000&quot;)
              .withSockJS();
  }
}
</code></pre></li>
</ul>
<pre><code>&lt;br&gt;

### 3. WebSocket 메시지 전송
위에서 언급한 것처럼 웹소켓 메시지 브로커 기능을 이용하여 웹소켓으로 메시지를 받고, 그 메시지를 클라이언트에 전달하기 위해서는 컨트롤러에 @MessageMapping, @SendTo 어노테이션을 붙여서 기능을 구현하면 된다. 

내 경우에는 특정 API가 호출되면 로직을 처리한 후 대시보드에 내용을 실시간으로 업데이트하기 위해서, 즉 **메시지를 전달하는 기능**만 필요해서 SimpMessagingTemplate를 사용하였다. SimpMessagingTemplate의 convertAndSend 메서드에 메시지를 전달할 엔드포인트와 payload를 적어주면 된다.
</code></pre><p>@Service
@RequiredArgsConstructor
public class DashBoardService {</p>
<pre><code>private final SimpMessagingTemplate simpMessagingTemplate;

public void processOrder( ) {
    simpMessagingTemplate.convertAndSend(&quot;/dash-board&quot;, &quot;TEST 메시지&quot;);
}</code></pre><p>}</p>
<pre><code>&lt;br&gt;
&lt;hr&gt;

# React 설정

먼저 리액트 STOMP 관련 라이브러리를 찾아봤을 때, 크게 2가지 라이브러리를 사용하는 것 같았다.
stompjs와 @stomp/stompjs가 그것인데, stompjs가 좀 더 설정이 간단하지만 고급 설정에 제한이 있다는 단점이 있었고 @stomp/stompjs는 설정은 조금 더 복잡하지만 Heartbeat, 자동 재연결 같은 고급 기능을 제공한다는 장점이 있었다. 
나는 일단 두 가지 다 구현해 보았다.

### 1. stompjs 사용
1) 라이브러리 설치(sockjs도 사용하려고 sockjs-client도 함께 설치)
</code></pre><p>npm install stompjs sockjs-client</p>
<pre><code>2) 웹소켓 연결 코드 
- 해당 페이지 렌더링 시 웹소켓을 연결하기 위해 useEffect 안에 웹소켓 연결 로직 작성
</code></pre><pre><code>useEffect(() =&gt; {
  const socket = new SockJS(&#39;http://localhost:9090/ws&#39;); // 서버 url + 서버에서 설정한 웹소켓 연결 엔드포인트
  const stompClient = Stomp.over(socket);

  stompClient.connect({}, () =&gt; {
      stompClient.subscribe(&#39;/dash-board&#39;, (message) =&gt; {
          console.log(message.body); 
      });
  });

  return () =&gt; {
      stompClient.disconnect(); 
  };</code></pre><p>  }, []); </p>
<pre><code>&lt;br&gt;

### 2. @stomp/stompjs 사용
1) 라이브러리 설치</code></pre><p>npm install @stomp/stompjs</p>
<pre><code>&lt;br&gt;
2) 웹소켓 연결 코드 

**SockJS**를 사용하는 경우에는 **webSocketFactory**를 설정해주고, 사용하지 않는 경우에는 **brokerURL**만 적어주면 된다. 
참고로 둘 다 적어준 경우에는 webSocketFactory가 우선 적용(?)되는 것 같았다. (= 서버에 withSockJS() 설정이 있어야 웹소켓이 연결됨)

</code></pre><p>  const [client, setClient] = useState(null);</p>
<p>  useEffect(() =&gt; {
    const connect = () =&gt; {
      const clientdata = new Client({
        // webSocketFactory: () =&gt; new SockJS(&quot;<a href="http://localhost:9090/ws&quot;">http://localhost:9090/ws&quot;</a>), // 서버에 withSockJS() 설정이 있는 경우
        brokerURL: &quot;ws://localhost:9090/ws&quot;, // 서버에 withSockJS() 설정이 없는 경우
        debug: function (str) {
          console.log(str);
        },
        reconnectDelay: 9000, // 재연결 시간 
        heartbeatIncoming: 4000,
        heartbeatOutgoing: 4000,
      });</p>
<pre><code>  clientdata.onConnect = () =&gt; {
    clientdata.subscribe(&quot;/dash-board&quot;, (message) =&gt; {
      console.log(&quot;Received message:&quot;, message.body);
    });
  };

  clientdata.onStompError = (frame) =&gt; {
    console.error(&quot;STOMP error:&quot;, frame.headers[&quot;message&quot;]);
  };

  clientdata.activate();
  setClient(clientdata);
};

connect();

return () =&gt; {
  if (client) {
    client.deactivate();
  }
};</code></pre><p>  }, []);</p>
<pre><code>
&lt;br&gt;
&lt;hr&gt;
&lt;br&gt;
이상 내가 기억하려고 적어본 웹소켓 서버/클라이언트 설정 방법이다. 나중에 기회가 되면 채팅 어플리케이션도 만들어 보고 싶다! 😊
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1919 - 애너그램 만들기 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1919-%EC%95%A0%EB%84%88%EA%B7%B8%EB%9E%A8-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1919-%EC%95%A0%EB%84%88%EA%B7%B8%EB%9E%A8-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA</guid>
            <pubDate>Mon, 13 Jan 2025 07:21:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/029f3e1a-dad7-4bc0-9abf-8798a3605d09/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/1919">[백준] 1919 - 애너그램 만들기</a></p>
<p>애너그램은 결국 순서에 상관없이 두 단어의 철자의 알파벳 개수가 같으면 조건을 만족하기 때문에, 문제를 풀기 위해서는 두 단어의 알파벳 개수 차이를 전부 더해주면 된다.</p>
<p>그러기 위해서 우선 행의 길이 2(두 개의 단어), 열의 길이 26(알파벳)을 가진 2차원 배열을 선언해주고, 각 단어의 철자를 하나씩 살펴보며 해당하는 알파벳의 개수를 증가시킨다.
<img src="https://velog.velcdn.com/images/luna_lee/post/189b7c30-8029-4667-9e66-7e78402ae337/image.png" alt=""></p>
<p>마지막에는 알파벳 순서대로 반복문을 돌면서 0번째 배열과 1번째 배열의 알파벳 개수 차이를 delete에 합산한 후 출력해주면 된다.
 <img src="https://velog.velcdn.com/images/luna_lee/post/109b684f-8ce3-4cec-b7ab-773f4349631d/image.png" alt=""></p>
<p>아래는 전체 소스코드이다.</p>
<pre><code>// 1919 - 애너그램 만들기
public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int[][] alphabet = new int[2][26];
        for (int i = 0; i &lt; 2; i++) {
            String input = br.readLine();
            for (int j = 0; j &lt; input.length(); j++) {
                alphabet[i][input.charAt(j) - &#39;a&#39;]++;
            }
        }
        int delete = 0;
        for (int j = 0; j &lt; alphabet[0].length; j++) {
            delete += Math.abs(alphabet[0][j] - alphabet[1][j]);
        }

        bw.write(delete+&quot;&quot;);
        bw.close();
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1181 - 단어 정렬 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1181-%EB%8B%A8%EC%96%B4-%EC%A0%95%EB%A0%AC-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1181-%EB%8B%A8%EC%96%B4-%EC%A0%95%EB%A0%AC-JAVA</guid>
            <pubDate>Sun, 12 Jan 2025 02:21:39 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/e6452026-3e32-4f70-918a-6650c40363ea/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/1181">[백준] 1181 - 단어 정렬</a></p>
<p>이것도 <a href="https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-6443-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA-3jbb430u">1431-시리얼 번호</a>와 마찬가지로 Comparator로 정렬 기준을 만들어서 풀이하였다. 
다만 중복된 단어는 하나만 남기고 제거해야한다는 조건이 있어서 정렬 + 중복제거가 가능한 SortedSet 자료구조를 사용하였다.</p>
<p>아래는 전체 소스코드이다.</p>
<pre><code>// 1181 - 단어 정렬
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        SortedSet&lt;String&gt; set = new TreeSet&lt;&gt;((a, b) -&gt; {
            if(a.length() != b.length()) return a.length() - b.length();
            return a.compareTo(b);
        });
        int N = Integer.parseInt(br.readLine());
        for (int i = 0; i &lt; N; i++) {
            set.add(br.readLine());
        }

        for (String word : set) {
            bw.write(word);
            bw.write(&quot;\n&quot;);
        }

        br.close();
        bw.close();
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1431 - 시리얼 번호(JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-6443-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA-3jbb430u</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-6443-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA-3jbb430u</guid>
            <pubDate>Fri, 10 Jan 2025 02:43:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/67063df5-a2af-4759-9fb7-4621369807ae/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/1431">[백준] 1431 - 시리얼 번호</a></p>
<p>사실 이 문제는 많이 까다롭거나 어려운 문제는 아니었는데 내 실수로 헤맸었던 문제이다. 문제 자체는 문자열을 정렬하기 위한 comparator를 구현하면 되는 것이었는데, 내가 정렬 기준을 세울 때 잘못 생각한 부분이 있었다.</p>
<p>먼저 문제의 요구 조건 
<em>1.A와 B의 길이가 다르면, 짧은 것이 먼저 온다.
2.만약 서로 길이가 같다면, A의 모든 자리수의 합과 B의 모든 자리수의 합을 비교해서 작은 합을 가지는 것이 먼저온다. (숫자인 것만 더한다)
3. 만약 1,2번 둘 조건으로도 비교할 수 없으면, 사전순으로 비교한다. 숫자가 알파벳보다 사전순으로 작다.</em>
중에 2번이 문제였는데... 나는 각 자리수의 <u>&#39;합&#39;</u>을 구해서 비교하는 것이므로 실제값이 아닌 아스키 문자 값으로 더해도 합의 크기를 비교하는 데에는 문제가 없을 것이라고 생각했다.
그래서 아래와 같이 구현했고 결과는...
<img src="https://velog.velcdn.com/images/luna_lee/post/0f036455-8d1f-41d5-a191-0f1ae9db8b79/image.png" alt=""></p>
<p>실제로 테스트케이스는 모두 통과하는데 자꾸 제출하고 나면 거의 바로 틀렸습니다가 나오더라..^-ㅜ
결국 챗GPT의 도움을 받아서 반례 케이스를 얻을 수 있었다. 아스키 값의 합은 같지만 실제 숫자 값의 합은 다른 경우이다.</p>
<pre><code>문자열 &quot;39&quot;:
아스키 값 합: 51 + 57 = 108
숫자 값 합: 3 + 9 = 12

문자열 &quot;48&quot;:
아스키 값 합: 52 + 56 = 108
숫자 값 합: 4 + 8 = 12</code></pre><p>그래서 다시 아래처럼 -&#39;0&#39; 처리를 해줘서 실제 숫자값을 합하도록 수정했더니 통과했다.
<img src="https://velog.velcdn.com/images/luna_lee/post/cf44795a-8094-4b00-b721-fdfbb8f1a0be/image.png" alt=""></p>
<p>그리고 아래는 전체 소스코드인데, 사전순으로 정렬하는 부분은 return a.compareTo(b); 로 String의 compareTo 메서드를 이용해도 동일한 결과가 나온다.</p>
<pre><code>import java.io.*;
import java.util.Arrays;

// 1431 - 시리얼 번호
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int N = Integer.parseInt(br.readLine());
        String[] arr = new String[N];
        for (int i = 0; i &lt; N; i++) {
            arr[i] = br.readLine();
        }
        br.close();
        Arrays.sort(arr, (a, b) -&gt; {
            if (a.length() != b.length()) {
                return a.length() - b.length();
            }

            int sumA = 0;
            int sumB = 0;
            for (int i = 0; i &lt; a.length(); i++) {
                if(a.charAt(i) &gt;= &#39;0&#39; &amp;&amp; a.charAt(i) &lt;= &#39;9&#39;){
                    sumA += a.charAt(i) - &#39;0&#39;;
                }
            }
            for (int i = 0; i &lt; b.length(); i++) {
                if(b.charAt(i) &gt;= &#39;0&#39; &amp;&amp; b.charAt(i) &lt;= &#39;9&#39;){
                    sumB += b.charAt(i) - &#39;0&#39;;
                }
            }
            if(sumA != sumB) return sumA - sumB;

            // 사전순 정렬
            for (int i = 0; i &lt; a.length(); i++) {
                if(a.charAt(i) != b.charAt(i)) {
                    return a.charAt(i) - b.charAt(i);
                }
            }

            return 0;

        });

        for (int i = 0; i &lt; arr.length; i++) {
            bw.write(arr[i]);
            bw.write(&quot;\n&quot;);
        }
        bw.close();
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 6443 - 애너그램 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-6443-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-6443-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA</guid>
            <pubDate>Thu, 09 Jan 2025 01:58:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/c4af73b4-2979-4e54-8fb4-5ba7b477be7b/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/6443">[백준] 6443 - 팰린드롬 만들기</a></p>
<p>처음에는 문제를 읽고 &#39;음.. 순열 문제군... 근데 중복되는 건 어떻게 제거하지?&#39; 하다가 SortedSet<String>에 넣어보기로 했었다. 결과는 역시나 예상대로 메모리 초과!
그래서 그 다음에는 순열을 돌 때 이전 인덱스와 문자가 같으면... 어떻게 어떻게 지지고 볶고... 해서 중복을 제거하면 되겠지!? 했는데, 중복되는 문자가 2개일 때는 먹히는데 늘어나면 안 먹히더라 ^_ㅜ 
그렇다고 검사하는 범위가 늘어나면 또 메모리나 시간초과 날 것 같고...</p>
<p>결국 다른 사람들 풀이를 보고 힌트를 얻었는데, 문자 그 자체를 반복/조합해서 순열로 만드는 게 아니라 문자의 개수를 이용해서 중복없는 순열을 만드는 방법이 있었다. 
심지어는 visited 배열도 불필요!</p>
<p>  아래 Perm 메서드가 그걸 구현한 것인데, 알파벳의 개수를 기록한 int 배열 alphabet, 결과 문자를 담을 배열인 result, 현재 뽑은 문자를 카운트할 r, 뽑아야할 개수인 count(사실 result.length를 사용해도 된다.)를 인자로 사용한다.
  그렇게 앞에서부터 alphabet배열을 돌면서 사용한 문자는 개수에서 빼주고, 재귀함수를 호출한 후 재귀함수에서 빠져나오면 다시 개수를 더해준다.</p>
<p>  <img src="https://velog.velcdn.com/images/luna_lee/post/76ec00bd-46ad-48aa-9568-ca5ac6c2cc60/image.png" alt=""></p>
<p>  아래는 전체 소스코드이다.</p>
<pre><code>import java.io.*;

// 6443 - 애나그램
public class Main {
    static BufferedWriter bw;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
         bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int N = Integer.parseInt(br.readLine());
        for (int i = 0; i &lt; N; i++) {
            int[] alphabet = new int[26];
            String input = br.readLine();

            // 알파벳 개수 카운트
            for (int j = 0; j &lt; input.length(); j++) {
                alphabet[input.charAt(j) - &#39;a&#39;]++;
            }

            char[] result = new char[input.length()];
            perm(alphabet, result, 0, input.length());

        }
        bw.close();
    }

    static void perm(int[] alphabet, char[] result, int r, int count) throws IOException {
        if (r == count) {
            bw.write(String.valueOf(result));
            bw.write(&quot;\n&quot;);
        }
        for (int i = 0; i &lt; alphabet.length; i++) {
            if (alphabet[i] &gt; 0) {
                alphabet[i]--;
                result[r] = (char)( i+&#39;a&#39;);
                perm(alphabet, result, r+1, count);
                alphabet[i]++;
            }
        }
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1254 - 팰린드롬 만들기 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1254-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1254-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0-JAVA</guid>
            <pubDate>Wed, 08 Jan 2025 01:08:17 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/8e8d7e7d-c9aa-49f8-9ecc-9fb2e06ad013/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/1254">[백준] 1254 - 팰린드롬 만들기</a></p>
<p>규칙을 찾으면 코드 구현 자체는 그렇게 어렵지 않는 문제였다. (물론 늘 그렇지만 규칙을 찾는 게 어렵다는 게 문제...)</p>
<p>내가 생각한 규칙은 문자열의 맨 뒤는 고정하고, 앞에서부터 차례로 문자열을 확인하여 현재의 문자열이 팰린드롬일 때까지 인덱스를 1씩 늘려가는 것이었다.
그리고 현재 문자열이 팰린드롬이라면, 현재 인덱스의 값만큼 문자열 길이에 추가해주면 된다. (= 전체 문자열 뒤에 현재 인덱스까지의 문자열을 추가해주는 것)</p>
<p>아래는 전체 소스코드이다.</p>
<pre><code>// 1254 - 팰린드롬 만들기
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        String input = br.readLine();
        br.close();
        int i = 0;
        while (i &lt; input.length()) {
            if(isPalindrome(input.substring(i))) break;
            i++;
        }
        bw.write(String.valueOf(input.length() + i));
        bw.flush();
        bw.close();
    }

    static boolean isPalindrome(String string) {
        boolean result = true;
        for (int i = 0; i &lt; string.length() / 2; i++) {
            if (string.charAt(i) != string.charAt(string.length() - 1 - i)) {
                result = false;
                break;
            }
        }
        return result;
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 10942 - 팰린드롬? (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-10942-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-10942-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-JAVA</guid>
            <pubDate>Tue, 07 Jan 2025 08:06:45 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/4d5064f4-57cc-408e-94b8-24dd90a56aa5/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/10942">[백준] 10942 - 팰린드롬? </a></p>
<p>사실 처음에는 주어진 질문 M번에 대해 일일이 팰린드롬인지 체크를 해주는 방식으로 풀었는데 그래도 통과는 할 수 있었다. 
근데 풀면서도 이게 맞나?? 값을 저장해놓고 쓸 수 있을 것 같은데... DP??? 라는 생각이 들어서 다시 시도 해보았다. 
<img src="https://velog.velcdn.com/images/luna_lee/post/ea58cb28-0e39-4cc4-8a33-37ca4687649c/image.png" alt=""></p>
<p>역시 DP 점화식 세우기가 어려워서 애먹었지만 결과적으로 시간을 반 정도로 줄일 수 있었다.
나중의 나를 위해... 내가 푼 방법을 차례로 적어보겠다.</p>
<ol>
<li><p>우선 칠판에 적힌 수를 입력받을 int 배열 nums와 isPalindrome이라는 2차원 boolean 배열을 선언한다. isPalindrome의 행 인덱스를 문자열의 시작, 열 인덱스를 문자열의 끝으로 간주한다. 
<img src="https://velog.velcdn.com/images/luna_lee/post/72003085-2e96-4ed6-9a6f-bd5cb12c2adb/image.png" alt=""></p>
</li>
<li><p>칠판에 적힌 숫자를 입력받을 때 문자 하나는 무조건 팰린드롬이므로 시작 인덱스와 끝 인덱스가 같은 경우는 isPalindrome 값을 true로 넣어준다.
또한 현재 주어진 문자와 nums[현재 인덱스-1]의 문자와 동일한 경우에도 길이가 2인 팰린드롬이므로 isPalindrome 값을 true로 넣어준다.
<img src="https://velog.velcdn.com/images/luna_lee/post/9fa59987-2533-4ade-8d64-dde4dec52918/image.png" alt=""></p>
</li>
<li><p>길이가 1, 2인 경우는 체크했으므로 이제 길이가 3(간격이 2)인 경우부터 길이가 N (간격이 N-1)인 경우까지 차례로 체크해주면 된다.
아래 이중 포문에서 i는 간격을 하나씩 늘려가는 반복문이고, j는 시작인덱스이다. 이렇게 for문을 돌면서 현재 체크하려는 부분의 <strong>양쪽 끝을 제외한 문자열</strong>이 팰린드롬인지 확인하고, 이것이 팰린드롬이라면 <strong>양쪽 끝의 문자가 서로 같은 값인지</strong>까지 체크해주면 된다.
<img src="blob:https://velog.io/0557dbb1-68e4-4da0-beb3-0511e5670468" alt="업로드중.."></p>
</li>
</ol>
<p>아래가 전체 소스코드이다.</p>
<pre><code>// 10942 - 팰린드롬?
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;
        int N = Integer.parseInt(br.readLine());

        int[] nums = new int[N+1];
        // 행 인덱스는 문자열의 시작, 열 인덱스는 문자열의 끝
        boolean[][] isPalindrome = new boolean[N + 1][N + 1];
        st = new StringTokenizer(br.readLine());
        for (int i = 1; i &lt; N+1; i++) {
            nums[i] = Integer.parseInt(st.nextToken());
            // 문자 하나는 무조건 팰린드롬
            isPalindrome[i][i] = true;
            // 이전 인덱스의 문자와 현재 문자가 동일한 경우에도 팰런드롬
            if(nums[i-1] == nums[i]) isPalindrome[i-1][i] = true;
        }
        for (int i = 2; i &lt; N; i++) { // i는 간격
            for (int j = 1; j + i &lt; N + 1; j++) { // i+j가 마지막 인덱스보다 작거나 같을 때까지만 반복 
                isPalindrome[j][j+i] = isPalindrome[j+1][j+i-1] &amp;&amp; nums[j] == nums[j+i]; // 양쪽 끝을 제외한 문자열이 팰린드롬인지 &amp;&amp; 양쪽 끝이 동일한 문자인지 체크
            }
        }
        int M = Integer.parseInt(br.readLine());
        for (int i = 0; i &lt; M; i++) {
            st = new StringTokenizer(br.readLine());
            int start = Integer.parseInt(st.nextToken());
            int end = Integer.parseInt(st.nextToken());
            bw.write(isPalindrome[start][end] ? &quot;1\n&quot; : &quot;0\n&quot;);
        }

        br.close();
        bw.flush();
        bw.close();
    }

// 처음에 사용했던 팰린드롬 체크 메서드
    static boolean checkItIsPalindrome(int start, int end, int[] nums) {
        boolean isPalindrome = true;
        for (int i = 0; i &lt; (end-start+1)/2; i++) {
            if (nums[start+i] != nums[end - i]) {
                isPalindrome = false;
                break;
            }
        }
        return isPalindrome;
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 9996 - 한국이 그리울 땐 서버에 접속하지 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-9996-%ED%95%9C%EA%B5%AD%EC%9D%B4-%EA%B7%B8%EB%A6%AC%EC%9A%B8-%EB%95%90-%EC%84%9C%EB%B2%84%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EC%A7%80-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-9996-%ED%95%9C%EA%B5%AD%EC%9D%B4-%EA%B7%B8%EB%A6%AC%EC%9A%B8-%EB%95%90-%EC%84%9C%EB%B2%84%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EC%A7%80-JAVA</guid>
            <pubDate>Mon, 06 Jan 2025 06:53:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/1705c16c-fe91-4af4-9c22-6b03baf97e19/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/9996">[백준] 9996 - 한국이 그리울 땐 서버에 접속하지</a></p>
<p>처음엔 뭐야 쉬운 문제네~ 하고 풀었는데 자꾸 60% 대에서 틀렸습니다가 나와서 결국 반례 푸는 법을 찾아봤던 문제. 
어떤 반례가 있냐하면, 예를 들어 주어진 패턴이 &#39;ab*bc&#39; 이고, 주어진 파일 이름이 &#39;abc&#39;인 경우에 이 파일의 이름은 ab로 시작하는 것도 맞고 bc로 끝나는 것도 맞지만 &#39;ab*bc&#39; 패턴과는 일치하지 않는다.</p>
<p>그래서 단순히 패턴의 시작과 끝 문자열과 파일 이름의 앞뒤 문자열을 비교만 하면 안되고 <strong>파일 이름의 길이</strong>가 <strong>*을 제외한 패턴 문자열의 길이와 같거나 큰지</strong>도 체크해줘야한다.
다음 코드에서 &#39;patternString.length()-1 &lt;= input.length()&#39;에 해당하는 부분이 그것이다.</p>
<pre><code>// 9996 - 한국이 그리울 땐 서버에 접속하지
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());
        String patternString = br.readLine();
        String[] pattern = patternString.split(&quot;\\*&quot;);
        for (int i = 0; i &lt; N; i++) {
            String input = br.readLine();
            int i1 = input.indexOf(pattern[0]);
            int i2 = input.lastIndexOf(pattern[1]);

            bw.write(patternString.length()-1 &lt;= input.length() &amp;&amp; i1 == 0 &amp;&amp; i2 == input.length() - pattern[1].length() ? &quot;DA&quot; : &quot;NE&quot;);
            bw.write(&quot;\n&quot;);
        }
        br.close();
        bw.flush();
        bw.close();
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1167 - 트리의 지름 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1167-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1167-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84-JAVA</guid>
            <pubDate>Sun, 05 Jan 2025 04:15:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/1960c573-8452-4e54-8248-d32695cd4d8f/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/1167">백준 1167 - 트리의 지름</a></p>
<p>이 문제는 아무리 생각해도 어떻게 풀어야할지 감이 안 와서 검색해보니, 트리의 성질과 관련한 공식에 대한 이해가 필요한 문제였다. </p>
<p><strong>루트에서 가장 먼 정점 A를 구하고, A에서 가장 먼 정점 B를 구하면(참고로 B가 루트 노드일 수도 있다) A와 B의 거리가 트리의 지름</strong>이라는 것이었다. </p>
<p>이걸 처음 봤을 때는 오... 그렇구나.. 하면서도 수학적으로 증명이 된 건지 궁금했는데 다행히 증명 과정을 잘 적어놓은 블로그들도 많이 있었다. 물론...(^^) 증명들을 읽는다고 바로 이해가 가지는 않았는데, 개인적으로는 아래 블로그 설명이 가장 명확히 납득시켜 주었던 것 같다.
<a href="https://johoonday.tistory.com/217">https://johoonday.tistory.com/217</a></p>
<p>이제 증명에 대한 이해까지 끝냈으면 문제를 풀 차례! 공식을 생각해내는 게 어려워서 그렇지 풀이 자체는 어렵지 않았다.
dfs(혹은 bfs)를 2번 수행해서 루트에서 가장 먼 노드와 그 노드에서 가장 먼 노드와의 거리를 구하면 된다.</p>
<p>아래는 전체 소스코드이다.</p>
<pre><code>// 1167 - 트리의 지름
public class Main {
    static List&lt;List&lt;int[]&gt;&gt; list; // 노드의 번호와 간선의 거리 둘 다 저장해야하므로 int[] 

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;
        int N = Integer.parseInt(br.readLine());
        list = new ArrayList&lt;&gt;();
        for (int i = 0; i &lt; N + 1; i++) {
            list.add(new ArrayList&lt;&gt;());
        }

        for (int i = 0; i &lt; N; i++) {
            st = new StringTokenizer(br.readLine());
            int node = Integer.parseInt(st.nextToken());
            int n;
            int distance;
            while (true) {
                n = Integer.parseInt(st.nextToken());
                if (n == -1) {
                    break;
                }
                distance = Integer.parseInt(st.nextToken());
                list.get(node).add(new int[]{n, distance});
            }
        }
        br.close();

        boolean[] visited = new boolean[list.size()];
        visited[1] = true;
        // 루트 노드에 대한 dfs를 수행하고 가장 먼 거리와 노드 번호를 구한다.
        int[] fromRoot = dfs(1, visited, 0);
        visited = new boolean[list.size()];
        visited[fromRoot[1]] = true;
        // 가장 먼 노드에서부터 다시 dfs를 수행
        int[] farthest = dfs(fromRoot[1], visited, 0);

        bw.write(farthest[0] + &quot;&quot;);
        bw.flush();
        bw.close();
    }

    static int[] dfs(int index, boolean[] visited, int distance) {
        int maxDistance = distance;
        int farthestNode = index;

        for (int[] node : list.get(index)) {
            if (!visited[node[0]]) {
                visited[node[0]] = true;
                int[] disAndNode = dfs(node[0], visited, distance + node[1]);
                if (disAndNode[0] &gt; maxDistance) {
                    maxDistance = disAndNode[0];
                    farthestNode = disAndNode[1];
                }
            }
        }
        return new int[]{maxDistance, farthestNode};
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 11725 - 트리의 부모 찾기 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-11725-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EB%B6%80%EB%AA%A8-%EC%B0%BE%EA%B8%B0-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-11725-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EB%B6%80%EB%AA%A8-%EC%B0%BE%EA%B8%B0-JAVA</guid>
            <pubDate>Fri, 03 Jan 2025 06:12:06 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/11725">[백준] 트리의 부모 찾기</a></p>
<p>처음엔 트리!!??? 라고 해서 괜히 겁먹었지만 문제를 천천히 읽어보니 결국은 dfs, bfs 문제였다.
dfs, bfs 둘 다 비슷하게 메모리와 시간이 소요된다.</p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/d44df23d-791c-4615-b25b-8397eab8b0d2/image.png" alt=""></p>
<p>간단한 bfs, dsf 문제이기 때문에 다른 풀이들과 전반적인 코드는 비슷하다. 
다만 visited 배열과 부모 노드를 담는 배열을 따로 따로 선언할 필요가 없어 보이길래 그냥 visited 배열을 int 배열로 만들어서 값이 0인 경우에는 방문하지 않은 노드로 체크하고, 해당 인덱스의 배열 값에 부모 노드의 숫자를 넣도록 코드를 작성했다.</p>
<pre><code>
import java.io.*;
import java.util.*;

// 11725 - 트리의 부모 찾기
public class Main {
    static List&lt;List&lt;Integer&gt;&gt; list;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;
        int N = Integer.parseInt(br.readLine());
        list = new ArrayList&lt;&gt;();
        for (int i = 0; i &lt;= N; i++) {
            list.add(new ArrayList&lt;&gt;());
        }
        for (int i = 0; i &lt; N-1; i++) {
            st = new StringTokenizer(br.readLine());
            int n1 = Integer.parseInt(st.nextToken());
            int n2 = Integer.parseInt(st.nextToken());
            list.get(n1).add(n2);
            list.get(n2).add(n1);
        }
        int[] visited = new int[list.size()];
        visited[1] = 1;
        bfs(1, visited);
//        dfs(1, visited);

        for (int i = 2; i &lt; visited.length; i++) {
            bw.write(visited[i]+&quot;\n&quot;);
        }
        bw.flush();
        bw.close();
    }

    static void dfs(int p, int[] visited) {
        for (int node : list.get(p)) {
            if(visited[node] == 0){
                visited[node] = p;
                dfs(node, visited);
            }
        }
    }

    static void bfs(int root, int[] visited) {
        Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();
        queue.add(root);
        while (!queue.isEmpty()) {
            Integer poll = queue.poll();
            for (int node : list.get(poll)) {
                if(visited[node] == 0){
                    visited[node] = poll;
                    queue.add(node);
                }
            }
        }
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1707 - 이분 그래프 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1707-%EC%9D%B4%EB%B6%84-%EA%B7%B8%EB%9E%98%ED%94%84-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1707-%EC%9D%B4%EB%B6%84-%EA%B7%B8%EB%9E%98%ED%94%84-JAVA</guid>
            <pubDate>Fri, 27 Dec 2024 03:41:25 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/1707">백준 1707 - 이분그래프</a>
<img src="https://velog.velcdn.com/images/luna_lee/post/8f56c385-f025-43be-b11b-dc9e25ef87d4/image.png" alt=""></p>
<p>처음에는 &#39;이분 그래프&#39;라는 개념을 처음 접해봐서 이분 그래프 개념 자체를 이해하는 데 애먹었다. 친절한 챗GPT의 도움을 받아 이해가 안되는 걸 물어가며 차근차근 이해했다. 그러고나서 정점을 서로 다른 그룹에 나누는 아이디어까지는 생각보다 쉽게 떠올렸는데 자꾸 시간초과가 났다.
boolean으로 된 visited 배열과 각각의 정점을 다른 그룹에 담을 List<Integer> 배열까지 만들었더니 이게 시간을 많이 잡아먹었었던 것 같다.
visited 배열을 boolean이 아니라 int 배열로 선언하고 1 혹은 -1로 그룹을 나누어서 구현했더니 통과했다!</p>
<p>그리고 나는 단순히 1부터 V까지 정점이 있으니까 bfs든 dfs든 1부터 시작해서 순회하면 되겠지~ 생각했는데 중간에 자꾸 틀렸다고 나오더라... 그래서 정점들을 전부 순회하는 for문 안에서 bfs와 dfs를 돌도록 수정했더니 통과할 수 있었다. 
<em><strong>중간에 연결되지 않은 정점과 간선도 있다는 것 명심!!</strong></em>  <del>나만 하는 실수인가?</del></p>
<p>아래는 bfs와 dfs로 구현한 전체 코드이다. 메모리와 시간은 둘 다 비슷하게 소요된다. (dfs가 아주 근소한 차이로 빠르긴하다.)</p>
<pre><code>
import java.io.*;
import java.util.*;

// 1707 - 이분 그래프(Bipartite Graph)
public class Main {
    static int[] visited;
    static boolean isBipartite;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;
        int K = Integer.parseInt(br.readLine());
        for (int i = 0; i &lt; K; i++) {
            isBipartite = true;
            st = new StringTokenizer(br.readLine());
            int V = Integer.parseInt(st.nextToken()); // 정점
            int E = Integer.parseInt(st.nextToken()); // 간선
            List&lt;List&lt;Integer&gt;&gt; vertexes = new ArrayList&lt;&gt;();
            for (int j = 0; j &lt; V + 1; j++) {
                vertexes.add(new ArrayList&lt;&gt;());
            }
            for (int j = 0; j &lt; E; j++) {
                st = new StringTokenizer(br.readLine());
                int u = Integer.parseInt(st.nextToken());
                int v = Integer.parseInt(st.nextToken());

                vertexes.get(u).add(v);
                vertexes.get(v).add(u);
            }
            bw.write(isBipartiteGraph(vertexes) ? &quot;YES&quot; : &quot;NO&quot;);
            bw.write(&quot;\n&quot;);
        }

        bw.flush();
        bw.close();
    }

    static boolean isBipartiteGraph(List&lt;List&lt;Integer&gt;&gt; graph) {
        visited = new int[graph.size()];
        boolean result = false;
//        return bfs(graph);
  // bfs로 풀 때는 아래를 주석처리하면 된다
        for (int i = 1; i &lt; graph.size(); i++) {
            if (visited[i] == 0) {
                visited[i] = 1;
                result = dfs(graph, i);
                if(!result){
                    return result;
                }
            }
        }

        return result; 
    }


    static boolean bfs(List&lt;List&lt;Integer&gt;&gt; graph) {
        Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();

        for (int k = 1; k &lt; graph.size(); k++) {
            if (visited[k] == 0) {
                queue.add(k);
                visited[k] = 1;
            }
            while (!queue.isEmpty()) {
                int vertex = queue.poll();
                int currentGroup = visited[vertex];
                int oppositeGroupIndex = currentGroup * -1;

                for (int i : graph.get(vertex)) {
                    if (visited[i] != 0) {
                        // i들이 같은 그룹에 없어야함! 같은 그룹에 있는지 확인
                        if (visited[i] == currentGroup) {
                            return false;
                        }
                    } else {
                        // 다른 그룹에 넣어야함!
                        visited[i] = oppositeGroupIndex;
                        queue.add(i);
                    }
                }
            }
        }

        return true;
    }


    static boolean dfs(List&lt;List&lt;Integer&gt;&gt; graph, int index) {
        int currentGroup = visited[index];
        int opposite = currentGroup * -1;

        for (int v : graph.get(index)) {
            if (visited[v] != 0) {
                if (visited[v] == currentGroup) {
                    return false;
                }
            } else {
                visited[v] = opposite;
                if (!dfs(graph, v)) {
                    return false;
                }
            }
        }
        return true;
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 2206 - 벽 부수고 이동하기 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-2206-%EB%B2%BD-%EB%B6%80%EC%88%98%EA%B3%A0-%EC%9D%B4%EB%8F%99%ED%95%98%EA%B8%B0-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-2206-%EB%B2%BD-%EB%B6%80%EC%88%98%EA%B3%A0-%EC%9D%B4%EB%8F%99%ED%95%98%EA%B8%B0-JAVA</guid>
            <pubDate>Wed, 18 Dec 2024 09:00:20 GMT</pubDate>
            <description><![CDATA[<p>하핳 dfs bfs 이제 좀 알 것 같네^^ 하던 나에게 또 한번 고비를 안겨준 문제! 
최근에는 문제 풀고 깃헙에만 올렸는데 이건 생각해볼 만한 문제인 것 같아서 오랜만에 블로그에 기록.
나중에 복기하면 좋을 만한 문제는 블로그에 쓰는 버릇을 들여야하는데 쉽지 않다.... </p>
<p><a href="https://www.acmicpc.net/problem/2206">원본 백준 문제 페이지</a>는 링크로 남겨두기!</p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/007e4df8-42df-4649-893a-bd89f28b0852/image.png" alt=""></p>
<p>처음에는 단순히 벽을 부술 수 있는 기회를 같이 저장해서 이동한 위치가 0이거나 혹은 1일 때 (벽을 부술)기회가 있으면 큐에 넣고~ 아니면 말고~ 로 풀면 될 거라고 생각했다.
실제로 주어진 테스트 케이스 2개는 통과했고. 
하지만 역시 채점 결과는....
그런데 아무리 생각해도 반례가 생각이 안 나서 구글에 검색해보니 친절하게 반례를 적어놓은 블로그를 발견했다.
<a href="https://dev-note-97.tistory.com/35">https://dev-note-97.tistory.com/35</a></p>
<p>결국 결정적인 아이디어는 벽을 부순 경우와, 벽을 부수지 않은 경우의 거리를 따로 기록해야한다는 것이었다. 
그래서 visited 배열을 map 배열에 한 차원을 더해서 벽을 부순 경우와 부수지 않은 경우를 따로 기록하도록 코드를 다시 작성하였다.</p>
<pre><code>import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

// 2206 - 벽 부수고 이동하기
public class Main {
    static int[] dx = {1, -1, 0, 0};
    static int[] dy = {0, 0, 1, -1};
    static BufferedWriter bw;
    static int N;
    static int M;

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken()); 
        M = Integer.parseInt(st.nextToken()); 
        int[][] map = new int[N][M];
        int[][][] visited = new int[N][M][2];

        for (int i = 0; i &lt; N; i++) {
            String input = br.readLine();
            for (int j = 0; j &lt; M; j++) {
                map[i][j] = input.charAt(j) - &#39;0&#39;;
            }
        }
        br.close();
        Queue&lt;int[]&gt; queue = new LinkedList&lt;&gt;();
        queue.offer(new int[]{0, 0, 1}); // 좌표와 벽을 부술 수 있는 개수(기회)
        visited[0][0][1] = 1;
        while (!queue.isEmpty()) {
            int[] poll = queue.poll();
            int x = poll[0];
            int y = poll[1];
            int chance = poll[2];

            for (int i = 0; i &lt; 4; i++) {
                int nx = x + dx[i];
                int ny = y + dy[i];
                if (isValid(nx, ny)) {
                    // 다음 위치가 0이면 현재 chance에 해당하는 visited 배열의 그 위치의 방문 여부를 확인한다.
                    if (map[nx][ny] == 0 &amp;&amp; visited[nx][ny][chance] == 0) {

                        // 현재 chance에 해당하는 visited 배열에 거리 + 1을 한 후 큐에 넣는다.
                        queue.offer(new int[] {nx, ny, chance});
                        visited[nx][ny][chance] = visited[x][y][chance] + 1;

                        // 다음 위치가 1이면 chance가 0인 경우(벽을 부순 상태)의 방문 배열을 확인한다.
                    } else if (map[nx][ny] == 1 &amp;&amp; visited[nx][ny][0] == 0) {
                        // 기회가 남아 있으면
                        if (chance == 1) {
                            // 다음 위치에 해당하는 벽을 부순 경우의 방문 배열 = 현재 방문 배열의 거리 + 1
                            visited[nx][ny][0] = visited[x][y][1] + 1;
                            queue.offer(new int[] {nx, ny, 0});
                        }
                    }
                }
            }
        }
        // 방문배열 마지막이 둘 다 0이면 도달하지 못한 것이니 -1 =&gt; 
        // 둘 중 하나가 0인 경우에는 0이 아닌 것이 최종 거리 =&gt; 
        // 둘 다 0이 아닌 경우에는 둘 중 더 작은 것이 최단 거리
        int result = visited[N-1][M-1][0] == 0 &amp;&amp; visited[N-1][M-1][1] == 0 ? -1 :
                visited[N-1][M-1][0] == 0 ? visited[N-1][M-1][1] :
                        visited[N-1][M-1][1] == 0 ? visited[N-1][M-1][0] :
                                Math.min(visited[N-1][M-1][0], visited[N-1][M-1][1]);

        bw.write(String.valueOf(result));
        bw.flush();
        bw.close();
    }

    static boolean isValid(int x, int y) {
        return x &gt;= 0 &amp;&amp; x &lt; N &amp;&amp; y &gt;= 0 &amp;&amp; y &lt; M;
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 테스트코드 작성 시 Mock 데이터에 ID 부여하기 (FieldTypesDoNotMatchException: 'id' is String but the actual type is Null 에러 해결)]]></title>
            <link>https://velog.io/@luna_lee/Spring-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1-%EC%8B%9C-Mock-%EA%B0%9D%EC%B2%B4%EC%97%90-ID-%EB%B6%80%EC%97%AC%ED%95%98%EA%B8%B0-FieldTypesDoNotMatchException-id-is-String-but-the-actual-type-is-Null-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@luna_lee/Spring-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1-%EC%8B%9C-Mock-%EA%B0%9D%EC%B2%B4%EC%97%90-ID-%EB%B6%80%EC%97%AC%ED%95%98%EA%B8%B0-FieldTypesDoNotMatchException-id-is-String-but-the-actual-type-is-Null-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Tue, 23 Jul 2024 12:03:51 GMT</pubDate>
            <description><![CDATA[<p>에러 로그를 DB에 저장하는 기능을 개발하면서 테스트 코드를 작성할 일이 생겼다. 일단 Service를 MockBean으로 등록하고 리턴 타입과 같은 Mock 데이터를 리턴해주는 구문을 작성해주었다.</p>
<p>그런데 코드를 실행해보니 아래와 같은 에러가 발생했다.</p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/1cffeb9c-bcfe-4d2f-a28d-13c301024ac6/image.png" alt=""></p>
<p> 에러로그 엔티티의 경우 로그 데이터 객체를 생성자로 받아서 생성되고, 생성된 엔티티가 save되는 시점에 id가 자동으로 생성되도록 되어있었다. 
그래서 내가 가짜로(?) 생성한 엔티티에는 id가 아직 존재하지 않고 컨트롤러 테스트의 결과에는 id도 존재해야 하므로 에러가 나는 것은 당연했다.</p>
<p>그런데 문제는... 어떻게 id를 넣어주느냐 하는 것이었다. </p>
<p>로그 데이터 객체는 다른 곳에서도 사용되기 때문에 일관성을 위해 엔티티에서는 로그 데이터를 그대로 받아서 엔티티를 생성하는 방법만을 가져가고 싶었다. 
테스트 코드 때문에 억지로 id를 파라미터로 받는 생성자를 만들고 싶지는 않았다. 마찬가지로 builder를 만들거나 setter를 추가하고 싶지도 않았다.</p>
<p>테스트 코드 때문에 구조를 바꾸기는 싫어서 열심히 구글링을 하다가 아래와 같이 <strong>ReflectionTestUtils</strong>를 사용하는 방법을 찾아냈고 결과적으로 잘 동작했다! </p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/43ad895d-8a84-4cc4-a5ba-c0c9cb38bd8f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Apache Kafka] 4 - JAVA와 연동하기(Producer / Consumer)]]></title>
            <link>https://velog.io/@luna_lee/Apache-Kafka-4-JAVA%EC%99%80-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0Producer-Consumer</link>
            <guid>https://velog.io/@luna_lee/Apache-Kafka-4-JAVA%EC%99%80-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0Producer-Consumer</guid>
            <pubDate>Wed, 10 Jul 2024 12:42:22 GMT</pubDate>
            <description><![CDATA[<p>Apache Kafka와 JAVA를 연동하는 간단한 예제 코드를 알아보자.
</br></p>
<h3 id="1-java-코드로-producer-만들기">1. JAVA 코드로 Producer 만들기</h3>
<pre><code>public class ProducerJAVA {
    // 로그 설정
    private static final Logger log = LoggerFactory.getLogger(ProducerDemoKeys.class.getSimpleName());

    public static void main(String[] args) {
        // create Producer Properties
        Properties properties = new Properties();

// 부트스트랩 서버 설정 
properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;127.0.0.1:9092&quot;); // properties.setProperty( &quot;bootstrap.servers&quot;, &quot;127.0.0.1:9092&quot;); 로도 설정 가능

// Key serializer 설정  
properties.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); // properties.setProperty(&quot;key.serializer&quot;, StringSerializer.class.getName()); 로도 설정 가능

// Value serializer 설정        
properties.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); // properties.setProperty(&quot;value.serializer&quot;, StringSerializer.class.getName()); 로도 설정 가능

        // create the producer
        KafkaProducer&lt;String, String&gt; producer = new KafkaProducer&lt;&gt;(properties);
        String topic = &quot;demo_java&quot;; // 토픽 설정

        for (int j = 0; j &lt; 3; j++) {
            for (int i = 0; i &lt; 10; i++) {
                String key = &quot;id_&quot; + i; // 키 설정 (생략 가능)
                String value = &quot;hello world &quot; + i; // value 설정

                ProducerRecord&lt;String, String&gt; record = new ProducerRecord&lt;&gt;(topic, key, value);
                producer.send(record,
                        (metadata, exception) -&gt; { // 메시지 전송 callback
                            if(exception == null) {
                                log.info(&quot;key: {}&quot;, key);
                                log.info(&quot;partition: {}&quot;, metadata.partition());
                            } else {
                                log.error(&quot;Error!! &quot;, exception);
                            }
                        });
            }
        }

        producer.flush(); // 카프카 메세지 전송은 비동기 방식이기 때문에 플러시 안해주면 전송하기 전에 프로그램 끝나버림
        producer.close();
    }
}
</code></pre></br>
</br>

<h3 id="2-java-코드로-consumer-만들기">2. JAVA 코드로 Consumer 만들기</h3>
<pre><code>public class ConsumerJAVA {
    // 로그 설정
    private static final Logger log = LoggerFactory.getLogger(ConsumerDemo.class.getSimpleName());

    public static void main(String[] args) {
        String groupId = &quot;my-java-application&quot;; // 컨슈머 그룹 ID 설정
        String topic = &quot;wikimedia.recentchange.connect&quot;; // 토픽 설정

        // create Consumer Properties
        Properties properties = new Properties();

        // 부트스트랩 서버 설정
        properties.setProperty(&quot;bootstrap.servers&quot;, &quot;127.0.0.1:9092&quot;);

        // Key deserializer 설정
        properties.setProperty(&quot;key.deserializer&quot;, StringDeserializer.class.getName());

        // value deserializer 설정
        properties.setProperty(&quot;value.deserializer&quot;, StringDeserializer.class.getName());

        // 컨슈머 그룹 ID 설정
        properties.setProperty(&quot;group.id&quot;, groupId);

        // offset reset 설정
        // latest -&gt; 지금부터 보낸 메세지만 읽겠다 / earliest -&gt; 현재 컨슈머 그룹의 커밋된 오프셋을 찾지 못할 때 가장 처음의 오프셋부터 메시지를 읽겠다
        properties.setProperty(&quot;auto.offset.reset&quot;, &quot;earliest&quot;);

        KafkaConsumer&lt;String, String&gt; consumer = new KafkaConsumer&lt;&gt;(properties);
        consumer.subscribe(Arrays.asList(topic)); // 구독할 토픽 설정. 복수 개의 토픽 설정 가능함.

        // 메시지 폴링 루프
        while (true) {
            ConsumerRecords&lt;String, String&gt; records = consumer.poll(Duration.ofMillis(1000)); // 최대 1000 밀리초만큼 기다리면서 브로커로부터 메시지를 가져옴
            for (ConsumerRecord&lt;String, String&gt; record : records) {
                log.info(&quot;Key: {} , Value: {} &quot;, record.key(), record.value());
                log.info(&quot;Partition: {} , Offset: {} &quot;, record.partition(), record.offset());
            }
        }
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Apache Kafka] 3 - Consumer Groups(컨슈머 그룹)]]></title>
            <link>https://velog.io/@luna_lee/Apache-Kafka-3-Consumer-Groups-CLI</link>
            <guid>https://velog.io/@luna_lee/Apache-Kafka-3-Consumer-Groups-CLI</guid>
            <pubDate>Mon, 01 Jul 2024 03:11:08 GMT</pubDate>
            <description><![CDATA[<h3 id="1-consumer에게-group-지정하기--group-확인하기">1. Consumer에게 group 지정하기 / group 확인하기</h3>
<p>kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic third_topic <strong>--group 그룹명</strong>으로 그룹을 지정하면 된다. </p>
<pre><code>kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic first_topic --group my-first-group</code></pre><br>

<p>아래와 같이 <strong>kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list</strong> 명령어를 통해 컨슈머 그룹 목록을 조회하여 my-first-group 그룹이 생성된 것을 확인할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/f8a9e596-2210-43ec-a525-a19f14ca2b76/image.png" alt=""></p>
<p><strong>--describe --group 그룹명</strong> 옵션을 이용하면 더 자세한 정보를 확인할 수도 있다.</p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/fef13c0c-9bf6-44e0-a943-abe067e84e1b/image.png" alt=""></p>
<br>

<h3 id="2-consumer-group과-offset">2. Consumer Group과 Offset</h3>
<p>앞선 포스팅에서 컨슈머 콘솔에 --from-beginning 옵션을 주게 되면 해당 토픽의 처음부터 메시지를 읽어온다고 설명한 바 있다. 
마찬가지로 컨슈머 그룹을 설정한 후 --from-beginning 옵션을 주게 되면 토픽의 처음부터 메시지를 읽어온다. </p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/2c9574a2-d24c-4007-a160-58c222c710c8/image.png" alt=""></p>
<p>이번에는 동일하게 my-first-group 그룹에 속한 두번째 컨슈머 콘솔을 실행하면서 --from-beginning 옵션을 전달해보자.</p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/59175fd7-ac34-43c5-9a27-eae31ed1aac4/image.png" alt=""></p>
<p>그러면 두번째 컨슈머는 메시지를 처음부터 읽어오지 못하는 것을 확인할 수 있을 것이다. 
그 이유는 카프카는 --from-beginning 옵션이 전달되어도 <strong>그룹</strong>의 일부로 <strong>커밋된 offset이 없는 경우에만</strong> 처음부터 읽어오기 때문에 동일한 그룹 이름으로 또 --from-beginning 옵션을 줘도 처음부터 읽어오지 않는 것이다.</p>
<blockquote>
<h4 id="offset이란">offset이란?</h4>
<p>파티션 내에서 <strong>메시지의 순서</strong>를 나타내는 고유한 정수 값으로
<strong>offset을 commit</strong>한다는 것은 컨슈머가 메시지를 읽고 처리한 후에, 메시지를 마지막으로 어디까지 읽었는지 저장하는 것이다.
Kafka는 offset을 각 컨슈머 그룹 별로 관리한다.</p>
</blockquote>
<p>참고로 컨슈머 그룹에 <strong>커밋된 offset이 존재하는 경우</strong>에 처음부터 읽어오지 않는다는 것은 결국 해당 컨슈머 그룹의 offset을 초기화시키면 메시지를 처음부터 읽는다는 것이다.
아래 명령어를 통해 offset을 reset할 수 있다.</p>
<pre><code>kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group my-first-group --reset-offsets --to-earliest --topic first_topic --execute</code></pre><br>

<p>offset을 초기화시킨 후 다시 해당 그룹에 --from-beginning 옵션을 주면 메시지를 처음부터 읽어오는 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/0146cd2c-f43f-4043-90f4-d00dddaafdf4/image.png" alt=""></p>
<br>

<h3 id="3-consumer-group과-파티션">3. Consumer group과 파티션</h3>
<p>컨슈머 그룹과 오프셋의 관계를 살펴보았으니 이번에는 컨슈머 그룹과 파티션의 관계를 살펴보자.</p>
<p><strong>컨슈머 그룹과 토픽의 파티션의 관계는 어떻게 될까?</strong></p>
<p>컨슈머 그룹 내의 컨슈머는 해당 토픽 내의 <strong>파티션을 나눠가진다</strong>. 
만약 <strong>컨슈머의 수</strong>가 파티션의 수보다 <strong>작으면</strong> 하나의 컨슈머가 <strong>1 개 이상의 파티션</strong>을 담당하게 되고, <strong>컨슈머의 수</strong>와 파티션의 수가 <strong>같으면</strong> 하나의 컨슈머 당 <strong>하나의 파티션</strong>을 담당하게 된다. 그리고 만약 <strong>컨슈머의 수</strong>가 파티션의 수보다 <strong>크면</strong> 담당하는 파티션 없이 <strong>놀고 있는</strong> 컨슈머가 존재하게 되는 것이다.</p>
<p>예시를 통해 확인해보자.</p>
<h4 id="1-파티션이-5개인-multiple_partiton_topic-생성">1) 파티션이 5개인 &#39;multiple_partiton_topic&#39; 생성</h4>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/5dab6be6-7e49-442c-bf35-b7a4422442d4/image.png" alt=""></p>
<h4 id="2-multiple_partiton_group-그룹에-속한-하나의-컨슈머-생성">2) &#39;multiple_partiton_group&#39; 그룹에 속한 하나의 컨슈머 생성</h4>
<pre><code>kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic multiple_partiton_topic --group multiple_partiton_group -- 컨슈머 생성

kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group multiple_partiton_group --describe -- 컨슈머 그룹에 대한 정보 확인</code></pre><p>--describe 옵션을 통해 해당 컨슈머 그룹에 대한 정보를 조회한 결과, console-consumer-da1de9ad-ce7d-4fb7-8113-a269fd8d9d11라는 ID를 가진 하나의 컨슈머가 5개의 파티션(0,1,2,3,4)을 담당하고 있는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/luna_lee/post/73c020a8-14c3-41ea-9cf2-9f48d86465e6/image.png" alt=""></p>
<h4 id="3-multiple_partiton_group-그룹에-속한-컨슈머를-추가로-2개-생성total-3개">3) &#39;multiple_partiton_group&#39; 그룹에 속한 컨슈머를 추가로 2개 생성(total 3개)</h4>
<p>컨슈머를 추가로 2개 더 생성한 후 컨슈머 정보를 조회한 결과이다.</p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/4c3362aa-8c36-4582-80da-fa1142908785/image.png" alt=""></p>
<p>console-consumer-198de97a-792c-4cfc-bd5a-62b664df4ce7가 파티션 0, 1을 담당하고 
console-consumer-d112d1d7-3282-4d67-afd7-00cefc51a65d가 파티션 2, 3을 담당하고
console-consumer-da1de9ad-ce7d-4fb7-8113-a269fd8d9d11가 파티션 4를 담당하고 있는 것을 확인할 수 있다.</p>
<h4 id="4-multiple_partiton_group-그룹에-속한-컨슈머를-추가로-2개-생성total-5개">4) &#39;multiple_partiton_group&#39; 그룹에 속한 컨슈머를 추가로 2개 생성(total 5개)</h4>
<p>multiple_partiton_group 그룹의 컨슈머를 2개 더 생성하여 컨슈머의 수가 토픽의 파티션 수인 5개가 되었다. 이제는 각 컨슈머가 하나의 파티션을 담당하게 되었다.
<img src="https://velog.velcdn.com/images/luna_lee/post/7276ec21-abe7-4577-9f3b-8f44b30ae852/image.png" alt=""></p>
<h4 id="5-multiple_partiton_group-그룹에-속한-컨슈머를-추가로-2개-생성total-7개">5) &#39;multiple_partiton_group&#39; 그룹에 속한 컨슈머를 추가로 2개 생성(total 7개)</h4>
<p>multiple_partiton_group 그룹의 컨슈머를 2개 더 생성하여 컨슈머의 수는 파티션의 수보다 많은 7개가 되었다.
참고로 추가로 컨슈머를 더 생성해도 하나의 파티션은 하나의 컨슈머에만 속한다. 하나의 파티션을 여러 개의 컨슈머가 담당하지는 않는다.</p>
<p>아래는 컨슈머를 2개 더 추가한 후에 다시 컨슈머 그룹의 정보를 조회한 결과이다. 여전히 하나의 컨슈머는 하나의 파티션을 담당하고 있는 것을 확인할 수 있다. 하지만 자세히 보면 4번에서 조회한 파티션 - 컨슈머ID 조합과 현재의 조합이 달라진 것을 알 수 있다.<br><img src="https://velog.velcdn.com/images/luna_lee/post/2932af09-5f8f-476a-aa2d-355daf9799bd/image.png" alt=""></p>
<p>그 이유는 컨슈머 그룹에 컨슈머가 새로 추가되거나 나가면(토픽에 새로운 파티션이 추가될 때도) <strong>리밸런싱</strong>이 일어나기 때문이다. 파티션을 컨슈머에게 <strong>재할당</strong>하는 것이다.(<em>참고로 리밸런싱에도 여러 전략이 존재한다. 여기서 설명하기에는 너무 길어지므로 생략...</em>)</p>
<hr>

]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1912 - 연속합 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1912-%EC%97%B0%EC%86%8D%ED%95%A9-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-1912-%EC%97%B0%EC%86%8D%ED%95%A9-JAVA</guid>
            <pubDate>Mon, 01 Jul 2024 01:32:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/luna_lee/post/bfc86072-c63a-4a83-9d28-52bf9a9cc980/image.png" alt=""></p>
<p>처음에는 &#39;연속된 수의 합 중 가장 큰 값&#39;을 찾는 문제니까 n 개가 연속된 합들을 구할 때 이전까지의 값을 활용(dp...)하고, 그렇게 구한 연속합 중 큰 값을 구하면 되겠다^^! 라고 생각했었다. 그러나 결과는 메모리 초과, 2차 시도는 시간 초과... 
아래가 2차 시도 코드이다.</p>
<pre><code>public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer tokenizer = new StringTokenizer(br.readLine());
        br.close();
        int max = -1001;
        int[] values = new int[n + 1];
        int[] sums = new int[n + 1];
        for (int i = 0; i &lt; n; i++) {
            values[i + 1] = Integer.parseInt(tokenizer.nextToken());
            sums[i + 1] = values[i + 1];
            max = Math.max(max, values[i + 1]);
        }

        for (int i = 2; i &lt; n + 1; i++) {
            for (int j = 1; j + i -1 &lt;= n; j++) {
                sums[j] =  sums[j] + values[i+j-1];
                max = Math.max(max, sums[j]);
            }
        }

        bw.write(String.valueOf(max));
        bw.flush();
        bw.close();
    }</code></pre><p>막연히 dp는 이전 값들을 배열에 저장해서 사용하는 것! 이라는 것에 꽂혀서 이런 생각을 했던 것 같다. 결국 다른 사람들의 풀이를 보고 아하.. <strong>이전까지의 연속합의 자신의 값을 더한 값</strong>과 <strong>현재 자신의 값</strong> 중 큰 값을 취하는 거구나ㅠㅠ 깨닫고 다시 코드를 짰다. 
아래가 그 코드이고 결과는 성공! 
휴... dp 어렵다,,,</p>
<pre><code>import java.io.*;
import java.util.StringTokenizer;

// 1912 - 연속합
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer tokenizer = new StringTokenizer(br.readLine());
        br.close();

        int max = Integer.parseInt(tokenizer.nextToken());
        int sum = max;
        for (int i = 1; i &lt; n; i++) {
            int input = Integer.parseInt(tokenizer.nextToken());
            sum = Math.max(input, sum + input);
            max = Math.max(sum, max);
        }

        bw.write(String.valueOf(max));
        bw.flush();
        bw.close();
    }

}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Apache Kafka] 2 -Producer / Consumer CLI ]]></title>
            <link>https://velog.io/@luna_lee/Apache-Kafka-2-Producer-Consumer-CLI</link>
            <guid>https://velog.io/@luna_lee/Apache-Kafka-2-Producer-Consumer-CLI</guid>
            <pubDate>Fri, 28 Jun 2024 04:14:25 GMT</pubDate>
            <description><![CDATA[<p>메시지를 produce하고 consume 하는 간단한 명령어들을 알아보자.</p>
<h3 id="1-producer--consumer-콘솔-실행">1. Producer &amp; Consumer 콘솔 실행</h3>
<p>1) producer와 consumer 실행 명령어를 각각 다른 터미널 창에서 입력한다.</p>
<pre><code>kafka-console-producer.sh --bootstrap-server localhost:9092 --topic first_topic -- producer 실행

kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic first_topic -- consumer</code></pre><p>2) producer 콘솔 창에서 메세지를 입력한다. 
<img src="https://velog.velcdn.com/images/luna_lee/post/2e2b84e0-114b-46c4-aa1c-12cdb55aad9d/image.png" alt=""></p>
<p>3) 아래와 같이 consumer 콘솔에 producer가 보낸 메세지가 출력된다.
<img src="https://velog.velcdn.com/images/luna_lee/post/e9b7b775-78e6-4e1f-8ba3-a02fa6a589e7/image.png" alt=""><br><br></p>
<h3 id="2-메시지를-처음부터-consume하기">2. 메시지를 처음부터 Consume하기</h3>
<p>위에서 설명한 명령어로 consumer 콘솔을 실행하면 실행한 이후에 전달된 메시지만을 소비한다.
만약에 메세지를 맨 처음부터 읽고 싶다면 <strong>--from-beginning</strong> 옵션을 사용하면 된다. 아래와 같이 --from-beginning를 추가하여 명령어를 입력하면 컨슈머 콘솔을 실행하기 전에 <del>잘못 보낸(...)</del> 메세지까지 읽을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/be151eb0-4507-4edf-b7a0-24c610a026d5/image.png" alt="">
<br></p>
<h3 id="3-메시지를-key와-함께-produce하기">3. 메시지를 Key와 함께 Produce하기</h3>
<p><strong>--property parse.key=true --property key.separator</strong> 옵션을 이용해 메세지를 보낼 때 <strong>키</strong>를 설정하여 전송할 수도 있다. <strong>--property key.separator=</strong> 다음에 오는 값은 키와 값(메시지)을 구분하는 구분자가 된다.</p>
<pre><code>kafka-console-producer.sh --bootstrap-server localhost:9092 --topic first_topic --property parse.key=true --property key.separator=:</code></pre><br>

<p>예시와 같이 구분자를 :로 설정하고 메시지를 보내보자.
<img src="https://velog.velcdn.com/images/luna_lee/post/860dc8d3-8092-48a5-9552-82d5ffbadb1d/image.png" alt=""></p>
<p>그러면 consumer에서는 값만 전달받고 있는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/luna_lee/post/3f94a4d5-b930-4bf1-8e3c-5647596afe3a/image.png" alt=""></p>
<p>** 그렇다면 메시지를 전송할 때 Key는 어떤 역할을 하기에 필요한 것일까? **</p>
<blockquote>
</blockquote>
<p><strong>1) 파티셔닝 &amp; 논리적 그룹</strong>
 Kafka는 키를 해시하여 파티션을 결정한다. 이를 통해 같은 키를 가진 메시지는 항상 같은 파티션으로 전송되므로 메시지 키를 사용하면 특정 파티션에 메시지를 보낼 수 있다. 그리고 이를 이용하여 논리적인 그룹으로 메시지를 묶을 수도 있다. 예를 들어서 user의 ID를 키 값으로 사용하면 같은 user의 메시지는 항상 같은 파티션으로 전송되는 것이다.</p>
<blockquote>
</blockquote>
<p><strong>2) 키값에 따른 순서 보장</strong>
동일한 키를 가진 메시지는 동일한 파티션에 저장되기 때문에, 이 파티션에서의 메시지 순서가 보장된다. 키별로 순서를 보장하고자 할 때 유용하다.</p>
<br>


<h3 id="4-메시지를-acks-설정과-함께-produce하기">4. 메시지를 acks 설정과 함께 Produce하기</h3>
<p>메시지를 전달할 때 *<em>--producer-property acks= *</em> 옵션을 줌으로써 acks 모드를 설정할 수 있다. 설정 값으로는 acks=0, acks=1, acks=all(acks=-1)이 있다.</p>
<pre><code> kafka-console-producer.sh --bootstrap-server localhost:9092 --topic first_topic --producer-property acks=all -- acks 설정</code></pre><blockquote>
<p><strong>acks란?</strong> 
메시지를 브로커에 전송할 때의 확인(acknowledgement) 모드</p>
<ul>
<li><strong>acks=0:</strong> 프로듀서는 브로커로부터 어떤 응답도 기다리지 않는다. 메시지가 성공적으로 전달되었는지 알 수 없기 때문에 데이터 손실 가능성이 있다.</li>
<li><blockquote>
<p>속도는 가장 빠르지만 신뢰성이 낮음.</p>
</blockquote>
</li>
</ul>
</blockquote>
<ul>
<li><strong>acks=1:</strong> 리더 브로커가 메시지를 처리한 후 Producer에게 응답한다. 어느 정도의 신뢰성은 보장되지만 리더 브로커에 장애가 있을 경우 데이터 손실 가능성이 존재한다.</li>
<li><strong>acks=all (또는 acks=-1):</strong> 리더 브로커 및 리더와 동기화된 replica가 모두 메시지를 처리한 후 프로듀서에게 응답한다. 데이터 손실의 가능성이 거의 없고 가장 신뢰성이 높지만, 속도가 느려질 수 있다.</li>
</ul>
<br>

<h3 id="5-기타-producer--consumer-cli-명령어">5. 기타 Producer &amp; Consumer CLI 명령어</h3>
<h4 id="1-producer-cli-명령어">1) Producer CLI 명령어</h4>
<ul>
<li>kafka-console-producer.sh --bootstrap-server localhost:9092 --topic <strong>non_existing_topic</strong>: 
  존재하지 않는 토픽에 메시지를 보낼 수 있다. (여기서 non_existing_topic의 자리에는 지금은 존재하지 않지만 produce와 동시에 생성하고자 하는 토픽 이름을 입력) 이렇게 토픽을 생성한 경우에는 해당 토픽의 파티션이 server.properties 파일의 <strong>num.partitions</strong> 값만큼 생성된다.</li>
</ul>
<h4 id="2-consumer-cli-명령어">2) Consumer CLI 명령어</h4>
<p>display key, values and timestamp in consumer</p>
<p>kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic first_topic <strong>--formatter kafka.tools.DefaultMessageFormatter --property print.timestamp=true --property print.key=true --property print.value=true --property print.partition=true</strong> --from-beginning : 
메시지 format 옵션을 줄 수 있다. 위와 같은 옵션을 주는 경우에는 메시지(value)와 key, partition, timestamp까지 함께 보여준다.
<img src="https://velog.velcdn.com/images/luna_lee/post/4e214e4f-66a7-45fa-95b8-51ce956fc96a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 2346- 풍선 터뜨리기 (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-2346-%ED%92%8D%EC%84%A0-%ED%84%B0%EB%9C%A8%EB%A6%AC%EA%B8%B0-JAVA</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-2346-%ED%92%8D%EC%84%A0-%ED%84%B0%EB%9C%A8%EB%A6%AC%EA%B8%B0-JAVA</guid>
            <pubDate>Tue, 25 Jun 2024 13:31:49 GMT</pubDate>
            <description><![CDATA[<p>이 문제는 처음에 Deque로 풀었다가 메모리 초과로 인해 int 배열로 변경해서 풀었다. 음수일 때 index 구하는 게 너무 어려웠다. 계속 OutOfIndex... 여차저차해서 풀었지만 저것보다 더 좋은 방법도 있을 것 같다. 하지만 토할 것 같아서 더 이상은 생각할 수가 없다. 나중에 다시 생각해봐야지.
<del>(근데 다른 사람들 풀이 보니까 Deque로도 다들 통과한 건 안 비밀... 나중에 Deque로 다시 도전해야겠다)</del></p>
<pre><code>import java.io.*;
import java.util.StringTokenizer;

// 2346 풍선 터뜨리기
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int n = Integer.parseInt(br.readLine());
        int[] arr = new int[n];
        StringTokenizer tokenizer = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; n; i++) {
            arr[i] = Integer.parseInt(tokenizer.nextToken());
        }
        int popNext = 0;
        int popCount = 1;
        bw.write((popNext + 1) + &quot; &quot;);

        while (popCount &lt; arr.length) {
            int popOrder = arr[popNext];
            arr[popNext] = 0;

            if (popOrder &gt; 0) {
                for (int i = 0; i &lt; popOrder; i++) {
                    int index = (i + 1 + popNext) % arr.length;

                    if (arr[index] == 0) {
                        popOrder++;
                    } else if (i == popOrder - 1) {
                        popNext = index;
                        popCount++;
                        bw.write((popNext + 1) + &quot; &quot;);
                    }
                }
            } else { // If the popOrder is negative
                for (int i = 0; i &gt; popOrder; i--) {
                    int index = popNext + (i - 1) % arr.length &lt; 0 ? arr.length + popNext + (i - 1) % arr.length : popNext + (i - 1) % arr.length;
                    if (arr[index] == 0) {
                        popOrder--;
                    } else if (i == popOrder + 1) {
                        popNext = index;
                        popCount++;
                        bw.write((popNext + 1) + &quot; &quot;);
                    }
                }
            }
        }

        br.close();

        bw.flush();
        bw.close();
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 24511- queuestack (JAVA)]]></title>
            <link>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-24511-queuestack-JAVA-e2q63mn3</link>
            <guid>https://velog.io/@luna_lee/%EB%B0%B1%EC%A4%80-24511-queuestack-JAVA-e2q63mn3</guid>
            <pubDate>Tue, 25 Jun 2024 13:24:58 GMT</pubDate>
            <description><![CDATA[<p>요즘에는 다시 백준 단계별로 풀어보기를 차근차근 풀어보는 중이어서 블로그에는 적지 않고 깃허브에만 올리고 있다. (아무도 안 궁금하겠지만... 깃헙 주소: <a href="https://github.com/eunju-lee-991/backjoon-step-by-step.git">https://github.com/eunju-lee-991/backjoon-step-by-step.git</a>)</p>
<p>그런데 스택, 큐, 덱 단계에서 메모리 제한이나 시간 제한을 둔 문제들이 꽤 나왔고 24511 queuestack에서 시간 초과로 많은 빠꾸... 아니 고배를 마셨기에 복기해보려고 한다. 
<img src="https://velog.velcdn.com/images/luna_lee/post/72971998-1c98-42a8-b7c3-2665aa4ade76/image.png" alt=""></p>
<p>처음에는 그냥 단순히 배열로 풀어보려다 -&gt; 시간초과
배열을 2개 써서? 2차원 배열로 간다 -&gt; 시간초과
배열을 0(queue)만 저장 -&gt; 시간초과
0(queue)인 값의 갯수까지 저장해서 그만큼만 for문 -&gt; 시간초과
......</p>
<p>결국 구글링해서 Deque를 쓰면 된다는 걸 깨달았다.
사실 나는 스택,큐,덱에서 덱이 뭔지도 몰랐던 사람인지라 Deque가 한 번에 생각나지 않았던 것 같다. 
막상 깨닫고 나니 어렵지 않은 문제였다! 
Deque를 잘 기억해두자</p>
<pre><code>import java.io.*;
import java.util.*;

// 24511 queuestack
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int n = Integer.parseInt(br.readLine());
        Deque&lt;Integer&gt; deque = new ArrayDeque&lt;&gt;();
        StringTokenizer tokenizer = new StringTokenizer(br.readLine());
        StringTokenizer tokenizer2 = new StringTokenizer(br.readLine());

        for (int i = 0; i &lt; n; i++) {
            String value = tokenizer2.nextToken();
            if (tokenizer.nextToken().equals(&quot;0&quot;)) {
                deque.add(Integer.parseInt(value));
            }
        }

        n = Integer.parseInt(br.readLine());
        tokenizer = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; n; i++) {
            int input = Integer.parseInt(tokenizer.nextToken());
            deque.offerFirst(input);
            bw.write(deque.pollLast() + &quot; &quot;);
        }

        br.close();

        bw.flush();
        bw.close();
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Apache Kafka] 1 - kafka 설치 및 주키퍼 없이 실행하기(Mac OS) / Topic 생성하기]]></title>
            <link>https://velog.io/@luna_lee/Apache-Kafka-1-kafka-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%A3%BC%ED%82%A4%ED%8D%BC-%EC%97%86%EC%9D%B4-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0Mac-OS-Topic-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@luna_lee/Apache-Kafka-1-kafka-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%A3%BC%ED%82%A4%ED%8D%BC-%EC%97%86%EC%9D%B4-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0Mac-OS-Topic-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 24 Jun 2024 08:32:44 GMT</pubDate>
            <description><![CDATA[<p>메세지 큐에 관심이 생겨 알아보다가 직접 사용해보고 싶어졌다. 그 중에서 대규모 시스템에서 많이 사용한다고 알려진 apache kafka에 더 관심이 가서 우선 kafka를 공부해보기로 했다. 생각보다 개념이 복잡하고 어려워서 복습할 겸 블로그에 정리해보려고 한다.</p>
<h2 id="1-kafka-설치">1. kafka 설치</h2>
<p> brew를 사용하는 경우 아래 명령어로 kafka를 설치한다.</p>
<pre><code>brew install kafka</code></pre><p>설치를 끝내고 kafka가 설치된 디렉토리의 bin 폴더로 가보면 아래와 같이 여러 개의 sh 파일들이 설치되어 있는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/luna_lee/post/5533993b-fa49-49f1-9c42-d7e626a95740/image.png" alt=""></p>
<h2 id="2-kafka-실행">2. kafka 실행</h2>
<p>kafka를 실행하는 방법에는 zookeeper와 함께 실행하는 방법과 zookeeper 없이 실행하는 방법이 존재한다. </p>
<h3 id="2-1-zookeeper와-함께-kafka-실행">2-1. zookeeper와 함께 kafka 실행</h3>
<p>zookeeper-server-start.sh (본인 pc의 카프카 config 경로)/zookeeper.properties 명령어로 zookeeper를 실행한다.</p>
<pre><code>zookeeper-server-start.sh (본인 pc의 카프카 config 경로)/zookeeper.properties

-- 명령어 예시
zookeeper-server-start.sh /opt/homebrew/Cellar/kafka/3.7.0/libexec/config/zookeeper.properties</code></pre><p>만약에 zookeeper-server-start.sh 로 실행이 되지 않는다면 환경변수 설정을 하거나 카프카 bin 폴더 안에서 ./zookeeper-server-start.sh 로 실행하면 작동할 것이다.</p>
<p>kafka-server-start.sh (본인 pc의 카프카 config 경로)/server.properties 명령어로 kafka server를 실행한다.</p>
<pre><code>kafka-server-start.sh (본인 pc의 카프카 config 경로)/server.properties

-- 명령어 예시
kafka-server-start.sh /opt/homebrew/Cellar/kafka/3.7.0/libexec/config/server.properties</code></pre><h3 id="2-2-zookeeper-없이-kafka-실행kraft-모드">2-2. zookeeper 없이 kafka 실행(KRaft 모드)</h3>
<h4 id="1-아래-명령어로-uuid-생성">1) 아래 명령어로 uuid 생성</h4>
<pre><code>kafka-storage.sh random-uuid -- 생성된 uuid가 출력된다. ex) 8OrZqVa1Sp2G_OAujOnKAA</code></pre><p><strong>2) 카프카가 데이터를 저장하는 경로를 format</strong>
아래 명령어로 kraft-combined-logs(카프카가 로그 데이터를 저장하는 경로)를 format하여 초기화한다. 참고로 지정된 UUID는 클러스터 ID로 설정된다.</p>
<pre><code>kafka-storage.sh format -t &lt;위에서 생성한 uuid&gt; -c  /opt/homebrew/Cellar/kafka/3.7.0/libexec/config/Kraft/server.properties
-- kafka-storage.sh format -t 8OrZqVa1Sp2G_OAujOnKAA -c  /opt/homebrew/Cellar/kafka/3.7.0/libexec/config/Kraft/server.properties</code></pre><p><strong>3) 카프카 서버 실행</strong></p>
<p>kafka-server-start.sh (본인 pc의 카프카 config 경로)/server.properties 명령어로 kafka server를 실행한다.</p>
<pre><code>kafka-server-start.sh (본인 pc의 카프카 config 경로)/server.properties 

-- 데몬으로 실행할 경우
kafka-server-start.sh -daemon (본인 pc의 카프카 config 경로)/server.properties </code></pre><p>참고로 앞으로 카프카는 주키퍼를 제거하는 방향으로 나아갈 것이므로 주키퍼 없이 사용하는 것을 권장한다고 한다.</p>
<ul>
<li>혹시나하고 윈도우에서 실행해봤는데 KRaft 모드로 문제 없이 실행된다!</li>
</ul>
<p><img src="https://velog.velcdn.com/images/luna_lee/post/24aa781f-4495-4819-ab43-7b5cb6a2ec09/image.png" alt=""></p>
<h2 id="3-topic-생성-및-조회하기">3. Topic 생성 및 조회하기</h2>
<h4 id="1-토픽-생성">1) 토픽 생성</h4>
<p>kafka-topics.sh --bootstrap-server <strong>localhost:9092</strong> --topic 생성할 토픽명 --create 명령어로 토픽을 생성한다.
아래는 <strong>first_topic</strong>이라는 이름의 토픽을 생성한 결과이다. localhost:9092 부분은 부트스트랩 서버의 port를 변경했거나 다른 브로커를 부트스트랩 서버로 설정하고 싶으면 그에 맞게 변경해주면 된다.
<img src="https://velog.velcdn.com/images/luna_lee/post/5f69eb7c-8b40-45b6-b595-f9cc13f79543/image.png" alt=""></p>
<h4 id="2-토픽-조회">2) 토픽 조회</h4>
<p>kafka-topics.sh --bootstrap-server localhost:9092 --list
명령어로 토픽을 조회할 수 있다.
<img src="https://velog.velcdn.com/images/luna_lee/post/48852577-db9b-4257-9316-5a78911d69f7/image.png" alt=""></p>
<ul>
<li>부트스트랩 서버 (Bootstrap Servers)란?
부트스트랩 서버는 Kafka 클라이언트(프로듀서 또는 컨슈머)가 초기 연결을 설정하는 데 사용되는 브로커의 목록으로 부트스트랩 서버의 주요 역할은 클라이언트가 클러스터의 메타데이터(ex. 브로커 목록, 토픽 정보 등)를 가져올 수 있도록 하는 것이라고 한다.
예를 들어 내 pc에 localhost:9092, localhost:9093 localhost:... 와 같이 여러 개의 포트에 해당하는 여러 개의 브로커가 존재할 때, bootstrap-server를 통해 모든 브로커의 정보를 받아올 수 있는 것이다.</li>
</ul>
<h4 id="3-기타-토픽-cli-명령어-옵션">3) 기타 토픽 CLI 명령어 옵션</h4>
<ul>
<li><p>kafka-topics.sh --bootstrap-server localhost:9092 --topic first_topic <strong>--describe</strong> : 지정한 토픽에 대한 세부 정보(파티션, 복제본, ISR) 등의 정보 표시</p>
</li>
<li><p>kafka-topics.sh --bootstrap-server localhost:9092 --topic first_topic <strong>--delete</strong> : 토픽 삭제</p>
</li>
<li><p>kafka-topics.sh --bootstrap-server localhost:9092 --topic first_topic --create <strong>--partitions 3</strong> : 생성할 토픽에 3개의 파티션을 생성</p>
</li>
<li><p>kafka-topics.sh --bootstrap-server localhost:9092 --topic first_topic --create --partitions 3 <strong>--replication-factor 2</strong> : 각 파티션에 2개의 레플리카 생성</p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>