<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>PIDGEY's Dev. BLOG</title>
    <link>https://pidgey.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 17 Apr 2026 04:11:34 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>PIDGEY</managingEditor>
    <image>
      <title>PIDGEY's Dev. BLOG</title>
      <url>https://tistory1.daumcdn.net/tistory/5402132/attach/d0b79b4bf4ab40389d20edc30f0e02d0</url>
      <link>https://pidgey.tistory.com</link>
    </image>
    <item>
      <title>[Java]  다음 큰 숫자 (Programmers, Level 2)</title>
      <link>https://pidgey.tistory.com/30</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세한 설명은 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12911&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프로그래머스&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연수 n을 이진법으로 표현했을 때의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1의 개수가 같으며 n보다 큰 자연수 중 가장 작은 값을 구하는 문제&lt;/b&gt;&lt;/span&gt;입니다.&lt;br /&gt;예를 들어 4를 이진법으로 표현하면 0100이고, 이보다 크면서 1의 개수가 1개인 수 중 가장 작은 값은 1000인 8입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제한 사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; n &amp;lt;= 1,000,000&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입출력 예시&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 54px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;&lt;b&gt;n&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;&lt;b&gt;result&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;78&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;83&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;15&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙을 발견해서 풀 수 있지만 가장 쉬운 풀이는 n+1부터 이진법으로 표현했을 때의 1의 개수가 같은 수를 반환하는 것입니다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Integer&lt;/b&gt;&lt;/span&gt;의 메서드 중에 이진법으로 변환했을 때 1의 개수를 카운팅 하는&lt;b&gt;&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;.bitCount(n)&lt;/span&gt;&lt;/b&gt;이라는 메서드를 활용하면 매우 간단하게 풀 수 있습니다. 또는 직접 구현해도 쉽게 풀 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre id=&quot;code_1689646106337&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 n) {
        int cnt = Integer.bitCount(n);
        
        int answer = n + 1;
        
        while (Integer.bitCount(answer) != cnt) {
            answer++;
        }
        
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발/Algorithm</category>
      <category>bitCount</category>
      <category>integer</category>
      <category>Java</category>
      <category>코딩 테스트</category>
      <category>프로그래머스</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/30</guid>
      <comments>https://pidgey.tistory.com/30#entry30comment</comments>
      <pubDate>Tue, 18 Jul 2023 11:09:38 +0900</pubDate>
    </item>
    <item>
      <title>[SOLID] 객체 지향 설계의 5가지 원칙</title>
      <link>https://pidgey.tistory.com/1</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2000년대 초반 로버트 마틴(Uncle Bob)이 명명한 객체 지향 프로그래밍 및 설계의 5가지 기본 원칙입니다.&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;유연하고 재사용 가능한 시스템&lt;/b&gt;&lt;/span&gt;을 만들고자 할 때 이 원칙들을 적용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1654172340894&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;SOLID (객체 지향 설계) - 위키백과, 우리 모두의 백과사전&quot; data-og-description=&quot;&quot; data-og-host=&quot;ko.wikipedia.org&quot; data-og-source-url=&quot;https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84)&quot; data-og-url=&quot;https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84)&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84)&quot; data-source-url=&quot;https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84)&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('&amp;quot;&amp;quot;');&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;SOLID (객체 지향 설계) - 위키백과, 우리 모두의 백과사전&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원칙&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. SRP (Single Responsbility Principle, 단일 책임 원칙)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;한 클래스는 하나의 책임만 가져야 한다는 원칙&lt;/b&gt;&lt;/span&gt;입니다.&lt;br /&gt;쉽게 풀어 설명하면 요구사항이 &lt;b&gt;변경에 따라 영향받는 요소가 한 가지&lt;/b&gt;여야 한다는 것입니다.&lt;br /&gt;책임의 범위는 상황에 따라 다르거나 모호할 수 있지만 한 클래스의 변경에 대한 영향력이 적어야 한다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller -&amp;gt; Service -&amp;gt; Repository &amp;lt;- (UserRepository, PostRepository) 형태의 웹 애플리케이션 구조가 있다고 가정해 봅시다.&lt;br /&gt;만약, Service가 두 Repository에 의존하고 있고 사용자(User) 등록/조회와 게시물(Post) 등록/조회에 대한 책임을 가지고 있다면 이는 단일 책임 원칙이 깨진 경우라고 할 수 있습니다. 사용자 기능이 변경되거나 게시물 기능이 변경되는 경우 모두 영향을 받기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 UserService, PostService로 분리하는 등의 책임을 나누는 작업이 필요합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. OCP (Open/ Closed Principle, 개방 폐쇄 원칙)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;소프트웨어 요소는 확장에는 열려(Open) 있으나 변경에는 닫혀(Closed) 있어야 한다는 원칙&lt;/b&gt;&lt;/span&gt;입니다.&lt;br /&gt;기능을 추가/변경해야 할 때 이미 잘 동작하고 있던 원래 코드를 변경하지 않아도, 새로운 코드를 추가함으로써 목적을 달성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserService -&amp;gt; UserRepository (findById, findByEmail) &amp;lt;- InMemoryUserRepository의 구조를 가정해 봅시다.&lt;br /&gt;만약 여기서 데이터베이스에 저장하고 싶다면 DatabaseUserRepository 구현체를 새롭게 정의해서 기능을 확장할 수 있습니다.&lt;br /&gt;이때, 기존에 있던  UserService의 코드에는 영향을 주지 않게 됩니다. 이것이 바로 확장에는 열려있다는 의미입니다.&lt;br /&gt;만약 데이터베이스에 저장하기 위한 구현체를 새롭게 추가하는 과정에서 인터페이스에 의존하는 것이 아니라 실제 구현체에 의존했다면 UserService의 Repository 레퍼런 스 변수나 다른 코드가 영향을 받을 수 있습니다. 이는 변경에도 열려있다는 의미이기 때문에 원칙이 깨진다고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시와 같이 인터페이스나 그 외 다양한 기법을 통해 변경에는 닫히도록 설계하여 기능 변경에 따른 영향을 최소화하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. LSP (Liskov Substitution Principle, 리스코프 치환 원칙)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다는 원칙&lt;/b&gt;&lt;/span&gt;입니다.&lt;br /&gt;부모 클래스의 잘 동작한 연산이 자식 클래스에서는 정상적으로 수행할 수 없는 상황을 피해야 한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 아래의 코드 예시를 통해 리스코프 치환 원칙이 깨진 경우의 문제점을 이해할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688470533206&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 직사각형
class Rectangle {
    private int width;
    private int height;
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    public long getArea() {
        return width * height;
    }
}

// 정사각형
class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width;
    }
    
    @Override
    public void setHeight(int height) {
        this.width = height;
        this.height = height;
    }
}

// 문제 예시
class Problem {
    public static void main(String[] args) {
        Rectangle a = new Rectangle();
        a.setWidth(2);
        a.setHeight(4);
        System.out.println(a.getArea()); // 2 * 4 = 8
        
        Square b = new Square();
        b.setWidth(4);
        b.setHeight(8);
        System.out.println(b.getArea()); // 8 * 8 = 64
        
        // 문제 발생
        Rectangle c = new Square();
        c.setWidth(2);
        c.setHeight(4);
        System.out.println(c.getArea()); // 4 * 4 = 16
        // Rectangle에서 기대했던 것과 다르게 가로, 세로 길이가 모두 한 번에 설정되면서 목적 달성 X
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직사각형 넓이를 구하는 getArea 메서드의 정확성이 깨지기 때문에 리스코프 치환 원칙을 위배했다고 해석할 수 있습니다.&lt;br /&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;4. ISP (Interface Segregation Principle, 인터페이스 분리 원칙)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다는 원칙&lt;/b&gt;&lt;/span&gt;입니다.&lt;br /&gt;큰 덩어리의 인터페이스들을 구체적인 작은 단위로 분리함으로써 명확성, 구체성을 높이고 역할 인터페이스 간 영향력을 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 책임 원칙에서 설명했던 Service -&amp;gt; Repository &amp;lt;- (UserRepository, PostRepository) 구조에서 만약 Repository가 다음과 같이 메서드를 선언하고 있다고 가정해 봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1688471666285&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Repository {
    // 사용자 관련 메서드
    User save(User user);
    Optional&amp;lt;User&amp;gt; findById(Long id);
    
    // 게시물 관련 메서드
    Post save(Post post);
    Optional&amp;lt;Post&amp;gt; findById(String id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구현한 UserRepository는 다음과 같을 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1688471972152&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserRepository implements Repository {
    // 사용자 관련 메서드
    @Override
    public User save(User user) {
    	// 실제 사용자를 저장한 후 반환
        // ...
      
        return user;
    }
    @Override
    public Optional&amp;lt;User&amp;gt; findById(Long id) {
    	// 실제 사용자를 찾은 후 반환
        
        return ...;
    }
    
    // 게시물 관련 메서드
    @Override
    Post save(Post post) {
    	// 아무 것도 수행하지 않음
        
    	return null;
    }
    @Override
    Optional&amp;lt;Post&amp;gt; findById(String id) {
    	// 아무 것도 수행하지 않음
        
    	return Optional.empty();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserRepository에서 관련 없고 구현할 필요가 없는 Post  관련 메서드를 구현해야 합니다.&lt;br /&gt;또 UserRepository를 사용하는 쪽에서는 불필요하게 Post 관련 메서드가 노출되어 어떤 메서드를 호출해야 하는지 헷갈릴 수 있습니다.&lt;br /&gt;따라서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;다음과 같이 인터페이스를 세분화하여 분리하는 것이 유지보수, 재사용 측면에서 유리할 가능성이 높습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688472159792&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface UserRepositoryInterface {
    User save(User user);
    Optional&amp;lt;User&amp;gt; findById(Long id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688472178478&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface PostRepositoryInterface {
    Post save(Post post);
    Optional&amp;lt;Post&amp;gt; findById(String id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. DIP (Dependency Inversion Principle, 의존관계 역전 원칙)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;추상화에 의존해야지, 구체화에 의존하면 안 된다는 원칙&lt;/b&gt;&lt;/span&gt;입니다.&lt;br /&gt;따라서, 저수준의 컴포넌트를 묶어 고수준 컴포넌트로 추상화하여 의존하도록 변경해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller -&amp;gt; Service -&amp;gt; (UserRepository, PostRepository) 에서 고수준 컴포넌트인 Repository 인터페이스를 정의하고 이를 UserRepository, PostRepository에서 각각 구현하게 되면 각 구현체들은 Repository 인터페이스에 의존하게 됩니다. 또한, Service가 구현체가 아닌 Repository를 의존하게 되면 위 구조는 아래와 같이 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Controller -&amp;gt; Service -&amp;gt; Repository &lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;lt;-&lt;/span&gt; (UserRepository, PostRepository)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존관계 역전이라고 설명하는 이유는 위의 빨간색 화살표처럼 의존관계 방향이 기존과 달리 반대쪽으로 향하기 때문입니다.&lt;br /&gt; 위 예시는 소프트웨어 확장, 변경에 따른 다른 코드에 영향을 줄인다는 관점에서 개방 폐쇄 원칙으로, 저수준 컴포넌트(구현체)를 고수준 컴포넌트(추상체)로 추상화한다는 관점에서 의존관계 역전 원칙으로 해석할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 객체 지향 설계의 원칙을 준수하면서 다양한 시스템의 공통적인 특성을 일반화한 설계법을 &lt;b&gt;디자인 패턴&lt;/b&gt;이라고 합니다.&lt;br /&gt;다음에는 디자인 패턴에 대해서 다뤄보도록 하겠습니다.&lt;/p&gt;</description>
      <category>개발/Java</category>
      <category>OOP</category>
      <category>SOLID</category>
      <category>객체 지향 설계</category>
      <category>로버트 마틴</category>
      <category>좋은 객체 지향 설계</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/1</guid>
      <comments>https://pidgey.tistory.com/1#entry1comment</comments>
      <pubDate>Tue, 4 Jul 2023 21:17:34 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 오픈채팅방 (2019 KAKAO BLIND RECRUITMENT)</title>
      <link>https://pidgey.tistory.com/29</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세한 문제 설명은 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42888&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프로그래머스&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오톡 오픈채팅방에서 채팅방에 입장 / 퇴장 메시지 출력을 구현하는 문제입니다.&lt;br /&gt;이미 출력된 메시지에 표시된 닉네임은 기존 사용자가 닉네임을 변경할 때 전부 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Muzi님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Muzi님이 나갔습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 상황에서 Muzi가 나간 후에, Prodo라는 닉네임으로 다시 들어올 경우 기존 채팅방에 남아있던 메시지의 Muzi도 Prodo로 변경됩니다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;닉네임의 중복은 허용&lt;/b&gt;&lt;/span&gt;하기 때문에 표시되는 닉네임은 겹칠 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 나갔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 기존의 Prodo 닉네임을 사용하던 사용자가 Ryan으로 변경하면 아래와 같이 메시지가 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Ryan님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 나갔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입장 / 퇴장 / 닉네임 변경에 담긴 채팅방 로그  배열 record가 주어지면 채팅방에 출력되는 메시지 배열을 출력하세요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제한 사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;record는&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;문자열이&amp;nbsp;담긴&amp;nbsp;배열이며,&amp;nbsp;길이는&amp;nbsp;1&amp;nbsp;이상&amp;nbsp;100,000&amp;nbsp;이하이다.&lt;/li&gt;
&lt;li&gt;다음은 record에 담긴 문자열에 대한 설명이다.
&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;[유저&amp;nbsp;아이디]&amp;nbsp;사용자가&amp;nbsp;[닉네임]으로&amp;nbsp;채팅방에&amp;nbsp;입장&amp;nbsp;-&amp;nbsp;&quot;Enter&amp;nbsp;[유저&amp;nbsp;아이디]&amp;nbsp;[닉네임]&quot;&amp;nbsp;(ex.&amp;nbsp;&quot;Enter&amp;nbsp;uid1234&amp;nbsp;Muzi&quot;)&lt;/li&gt;
&lt;li&gt;[유저 아이디] 사용자가 채팅방에서 퇴장 - &quot;Leave [유저 아이디]&quot; (ex. &quot;Leave uid1234&quot;)&lt;/li&gt;
&lt;li&gt;[유저 아이디] 사용자가 닉네임을 [닉네임]으로 변경 - &quot;Change [유저 아이디] [닉네임]&quot; (ex. &quot;Change uid1234 Muzi&quot;)&lt;/li&gt;
&lt;li&gt;첫 단어는 Enter, Leave, Change 중 하나이다.&lt;/li&gt;
&lt;li&gt;각 단어는 공백으로 구분되어 있으며, 알파벳 대문자, 소문자, 숫자로만 이루어져 있다.&lt;/li&gt;
&lt;li&gt;유저 아이디와 닉네임은 알파벳 대문자, 소문자를 구별한다.&lt;/li&gt;
&lt;li&gt;유저 아이디와 닉네임의 길이는 1 이상 10 이하이다.&lt;/li&gt;
&lt;li&gt;채팅방에서&amp;nbsp;나간&amp;nbsp;유저가&amp;nbsp;닉네임을&amp;nbsp;변경하는&amp;nbsp;등&amp;nbsp;잘못된&amp;nbsp;입력은&amp;nbsp;주어지지&amp;nbsp;않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입출력 예시&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 104px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;record&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 87px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 87px;&quot;&gt;[&quot;Enter uid1234 Muzi&quot;, &lt;br /&gt;&amp;nbsp;&quot;Enter uid4567 Prodo&quot;, &lt;br /&gt;&amp;nbsp;&quot;Leave uid1234&quot;,&lt;br /&gt;&amp;nbsp;&quot;Enter uid1234 Prodo&quot;,&lt;br /&gt;&amp;nbsp;&quot;Change uid4567 Ryan&quot;]&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 87px;&quot;&gt;[&quot;Prodo님이&amp;nbsp;들어왔습니다.&quot;,&lt;br /&gt;&amp;nbsp;&quot;Ryan님이 들어왔습니다.&quot;,&lt;br /&gt;&quot;Prodo님이 나갔습니다.&quot;,&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 식별자가 입력 데이터에서 주어지므로 Map을 사용하여 닉네임을 관리하면 됩니다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이때, 나간 사용자의 닉네임도 보관해야 함을 주의합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre id=&quot;code_1688078369316&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public String[] solution(String[] record) {
        List&amp;lt;String&amp;gt; answer = new ArrayList&amp;lt;&amp;gt;();
        
        // 사용자의 닉네임을 관리하기 위해 Map을 사용합니다.
        Map&amp;lt;String, String&amp;gt; nickname = new HashMap&amp;lt;&amp;gt;();
        for (String r : record) {
            String[] s = r.split(&quot; &quot;);
            
            // 들어오거나 변경되는 경우에만 반영하고 나간 경우는 유지시킵니다.
            if (s[0].equals(&quot;Enter&quot;) || s[0].equals(&quot;Change&quot;)) {
                nickname.put(s[1], s[2]);
            }
        }
        
        for (String r : record) {
            String[] s = r.split(&quot; &quot;);
            
            if (s[0].equals(&quot;Enter&quot;)) {
                answer.add(nickname.get(s[1]) + &quot;님이 들어왔습니다.&quot;);
            } else if (s[0].equals(&quot;Leave&quot;)) {
                answer.add(nickname.get(s[1]) + &quot;님이 나갔습니다.&quot;);
            }
        }
        
        return answer.toArray(String[]::new);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발/Algorithm</category>
      <category>Java</category>
      <category>오픈채팅방</category>
      <category>자바</category>
      <category>카카오</category>
      <category>코딩 테스트</category>
      <category>프로그래머스</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/29</guid>
      <comments>https://pidgey.tistory.com/29#entry29comment</comments>
      <pubDate>Fri, 30 Jun 2023 07:40:05 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 영어 끝말잇기 (프로그래머스 Summer/Winter Coding(~2018))</title>
      <link>https://pidgey.tistory.com/28</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세한 문제 설명은 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12981&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프로그래머스&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 우리가 알고 있는 영어 끝말잇기 규칙과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;1번부터 번호 순서대로 한 사람씩 차례대로 단어를 말합니다.&lt;/li&gt;
&lt;li&gt;마지막 사람이 단어를 말한 다음에는 다시 1번부터 시작합니다.&lt;/li&gt;
&lt;li&gt;앞사람이 말한 단어의 마지막 문자로 시작하는 단어를 말해야 합니다.&lt;/li&gt;
&lt;li&gt;이전에 등장했던 단어는 사용할 수 없습니다.&lt;/li&gt;
&lt;li&gt;한 글자인 단어는 인정되지 않습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 규칙에 맞게 N명이 끝말잇기를 진행할 때 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;가장 먼저 탈락한 사람의 번호&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;자신의 몇 번째 차례에서 탈락하는지&lt;/b&gt;&lt;/span&gt;를 구하면 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제한 사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;끝말잇기에 참여하는 사람의 수 n은 2 이상 10 이하의 자연수입니다.&lt;/li&gt;
&lt;li&gt;words는 끝말잇기에 사용한 단어들이 순서대로 들어있는 배열이며, 길이는 n 이상 100 이하입니다.&lt;/li&gt;
&lt;li&gt;단어의 길이는 2 이상 50 이하입니다.&lt;/li&gt;
&lt;li&gt;모든 단어는 알파벳 소문자로만 이루어져 있습니다.&lt;/li&gt;
&lt;li&gt;끝말잇기에 사용되는 단어의 뜻은 신경 쓰지 않으셔도 됩니다.&lt;/li&gt;
&lt;li&gt;정답은 [번호, 차례] 형태로 return 해주세요.&lt;/li&gt;
&lt;li&gt;만약 주어진 단어들로 탈락자가 생기지 않는다면, [0, 0]을 return 해주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입출력 예시&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 72px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 7.86814%; height: 18px;&quot;&gt;n&lt;/td&gt;
&lt;td style=&quot;width: 81.3566%; height: 18px;&quot;&gt;words&lt;/td&gt;
&lt;td style=&quot;width: 10.7752%; height: 18px;&quot;&gt;result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 7.86814%; height: 18px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 81.3566%; height: 18px;&quot;&gt;[&quot;tank&quot;,&amp;nbsp;&quot;kick&quot;,&amp;nbsp;&quot;know&quot;,&amp;nbsp;&quot;wheel&quot;,&amp;nbsp;&quot;land&quot;,&amp;nbsp;&quot;dream&quot;,&amp;nbsp;&quot;mother&quot;,&amp;nbsp;&quot;robot&quot;,&amp;nbsp;&quot;tank&quot;]&lt;/td&gt;
&lt;td style=&quot;width: 10.7752%; height: 18px;&quot;&gt;[3, 3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 7.86814%; height: 18px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 81.3566%; height: 18px;&quot;&gt;[&quot;hello&quot;,&amp;nbsp;&quot;observe&quot;,&amp;nbsp;&quot;effect&quot;,&amp;nbsp;&quot;take&quot;,&amp;nbsp;&quot;either&quot;,&amp;nbsp;&quot;recognize&quot;,&amp;nbsp;&quot;encourage&quot;,&amp;nbsp;&quot;ensure&quot;,&amp;nbsp;&quot;establish&quot;,&amp;nbsp;&quot;hang&quot;,&amp;nbsp;&quot;gather&quot;,&amp;nbsp;&quot;refer&quot;,&amp;nbsp;&quot;reference&quot;,&amp;nbsp;&quot;estimate&quot;,&amp;nbsp;&quot;executive&quot;]&lt;/td&gt;
&lt;td style=&quot;width: 10.7752%; height: 18px;&quot;&gt;[0, 0]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 7.86814%; height: 18px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 81.3566%; height: 18px;&quot;&gt;[&quot;hello&quot;,&amp;nbsp;&quot;one&quot;,&amp;nbsp;&quot;even&quot;,&amp;nbsp;&quot;never&quot;,&amp;nbsp;&quot;now&quot;,&amp;nbsp;&quot;world&quot;,&amp;nbsp;&quot;draw&quot;]&lt;/td&gt;
&lt;td style=&quot;width: 10.7752%; height: 18px;&quot;&gt;[1, 3]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 케이스의 경우에는 영어 끝말잇기 규칙을 모두 준수한 상태로 겹침 단어가 발생하지 않아 [0, 0] 이 정답입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Map&lt;/b&gt;&lt;/span&gt;을 이용해서 해당 단어가 사용됐는지 확인하기 위해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;containsKey(...)&lt;/b&gt;&lt;/span&gt; 메서드를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 나눗셈(/)과 나머지(%) 연산을 활용해서 정답을 구했습니다.&lt;br /&gt;i가 0부터 시작하고 i 번째 단어에서 탈락자가 발생했을 때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(i % n) + 1&lt;/b&gt;&lt;/span&gt; 이 탈락한 사람의 번호입니다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입출력 예시 1번처럼 n이 3이라면 (8 % 3) + 1는 3이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(i / n) + 1 &lt;/b&gt;&lt;/span&gt;이 현재 끝말잇기가 몇 번째 사이클인지를 가리킵니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마찬가지로 1번 예시 경우에서 연산하면 3이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre id=&quot;code_1687734490485&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.HashMap;

class Solution {
    public int[] solution(int n, String[] words) {
        // 값이 변경되지 않고 무사히 리턴되는 경우 [0, 0]
        int[] answer = { 0 , 0 };
        
        HashMap&amp;lt;String, Integer&amp;gt; wordsMap = new HashMap&amp;lt;&amp;gt;();
        
        // 첫 번째 사람의 첫 번째 단어를 미리 추가
        wordsMap.put(words[0], 1);
        
        // 두 번째 사람의 첫 번째 단어부터 시작
        for (int i = 1; i &amp;lt; words.length; i++) {
            // 이전 단어의 마지막 문자와 현재 단어의 시작 문자를 비교하기 위해 변수 설정
            char last = words[i-1].charAt(words[i-1].length()-1);
            char start = words[i].charAt(0);
        
            // 끝말잇기 규칙에 안 맞는 경우
            if (last != start || wordsMap.containsKey(words[i])) {
                answer[0] = (i % n) + 1; // 현재 걸린 사람
                answer[1] = (i / n) + 1; // 몇 번째 사이클
                break;
            }
            
            wordsMap.put(words[i], 1);
        }
        
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발/Algorithm</category>
      <category>Java</category>
      <category>영어 끝말잇기</category>
      <category>자바</category>
      <category>코딩 테스트</category>
      <category>프로그래머스</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/28</guid>
      <comments>https://pidgey.tistory.com/28#entry28comment</comments>
      <pubDate>Mon, 26 Jun 2023 08:08:42 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 성격 유형 검사하기 (2022 KAKAO TECH INTERNSHIP)</title>
      <link>https://pidgey.tistory.com/27</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세한 문제 설명은 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/118666&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프로그래머스&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 92px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 14.0698%; height: 20px;&quot;&gt;&lt;b&gt;지표 번호&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 85.9302%; height: 20px;&quot;&gt;&lt;b&gt;성격 유형&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.0698%; height: 18px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 85.9302%; height: 18px;&quot;&gt;R, T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.0698%; height: 18px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 85.9302%; height: 18px;&quot;&gt;C, F&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.0698%; height: 18px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 85.9302%; height: 18px;&quot;&gt;J, M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.0698%; height: 18px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 85.9302%; height: 18px;&quot;&gt;A, N&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 지표 번호에서 두 유형 중 성격이 강한 것을 조합하여 &quot;RFMN&quot;, &quot;TCMA&quot; 같은 성격 유형을 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검사지는 N개의 질문과 아래와 같은 7개의 선택지가 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 160px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.6511%; height: 20px;&quot;&gt;&lt;b&gt;선택지&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 80.3489%; height: 20px;&quot;&gt;&lt;b&gt;성격 유형 점수&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.6511%; height: 20px;&quot;&gt;매우 비동의&lt;/td&gt;
&lt;td style=&quot;width: 80.3489%; height: 20px;&quot;&gt;첫 번째 유형 3점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.6511%; height: 20px;&quot;&gt;비동의&lt;/td&gt;
&lt;td style=&quot;width: 80.3489%; height: 20px;&quot;&gt;첫 번째 유형 2점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.6511%; height: 20px;&quot;&gt;약간 비동의&lt;/td&gt;
&lt;td style=&quot;width: 80.3489%; height: 20px;&quot;&gt;첫 번째 유형 1점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.6511%; height: 20px;&quot;&gt;모르겠음&lt;/td&gt;
&lt;td style=&quot;width: 80.3489%; height: 20px;&quot;&gt;점수 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.6511%; height: 20px;&quot;&gt;약간 동의&lt;/td&gt;
&lt;td style=&quot;width: 80.3489%; height: 20px;&quot;&gt;두 번째 유형 1점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.6511%; height: 20px;&quot;&gt;동의&lt;/td&gt;
&lt;td style=&quot;width: 80.3489%; height: 20px;&quot;&gt;두 번째 유형 2점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.6511%; height: 20px;&quot;&gt;매우 동의&lt;/td&gt;
&lt;td style=&quot;width: 80.3489%; height: 20px;&quot;&gt;두 번째 유형 3점&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 질문이 &quot;CF&quot; 유형에 대한 질문인 경우&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매우 비동의는 C 유형에 3점&lt;/li&gt;
&lt;li&gt;동의는 F유형에 3점&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부여되는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;만약 각 지표 번호에서 두 성격 유형 점수가 같다면 사전 순으로 빠른 것을 선택합니다.&lt;/b&gt;&lt;/span&gt; &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(F = C = 0 이면 사전 순으로 빠른 C를 선택합니다)&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제한 사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 &amp;lt;= survey 의 길이 (= n) &amp;lt;= 1,000
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;survey의 원소는 &quot;RT&quot;, &quot;TR&quot;, &quot;FC&quot;, &quot;CF&quot;, &quot;MJ&quot;, &quot;JM&quot;, &quot;AN&quot;, &quot;NA&quot;  중 하나입니다.&lt;/li&gt;
&lt;li&gt;survey[i] 의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;첫 번째 캐릭터는 i+1번 질문의 비동의 관련 선택지&lt;/b&gt;&lt;/span&gt;를 선택하면 받는 성격 유형입니다.&lt;/li&gt;
&lt;li&gt;survey[i] 의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;두 번째 캐릭터는 i+1번 질문의 동의 관련 선택지&lt;/b&gt;&lt;/span&gt;를 선택하면 받는 성격 유형입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;choices 의 길이 = survey 의 길이
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;choices[i] 는 검사자가 선택한 i+1번째 질문의 선택지를 의미합니다.&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= choices 의 원소 &amp;lt;= 7&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입출력 예시&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 53px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;survey&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;choices&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;[&quot;AN&quot;, &quot;CF&quot;, &quot;MJ&quot;, &quot;RT&quot;, &quot;NA&quot;]&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;[5, 3, 2, 7, 5]&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px;&quot;&gt;&quot;TCMA&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;[&quot;TR&quot;, &quot;RT&quot;, &quot;TR&quot;]&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;[7, 1, 3]&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&quot;RCJA&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길이가 4인 배열을 생성하여 음수인 경우는 사전 순으로 빠른 유형, 양수인 경우는 사전 순으로 느린 유형으로&amp;nbsp;각 지표를 해석하는 방식으로 풀 수도 있을 것 같습니다만 저는 Map을 이용하여 각 성향을 key로 잡고 점수를 더하여 풀었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre id=&quot;code_1687730078713&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public String solution(String[] survey, int[] choices) {
        HashMap&amp;lt;Character, Integer&amp;gt; scoreMap = new HashMap&amp;lt;&amp;gt;();
        
        int middlePoint = 4;
        for (int i = 0; i &amp;lt; survey.length; i++) {
            if (choices[i] == middlePoint) {
                continue;
            } else {
                int point;
                char c;
                
                if (choices[i] &amp;lt; middlePoint) {
                    // 비동의쪽 성향 스코어 증가
                    point = (choices[i] - middlePoint) * -1; // 낮을 수록 더 강한 수치 부여
                    c = survey[i].charAt(0);
                } else {
                    // 동의쪽 성향 스코어 증가
                    point = choices[i] - middlePoint;
                    c = survey[i].charAt(1);
                }
                
                scoreMap.put(c, scoreMap.getOrDefault(c, 0) + point);
            }
        }
        
        StringBuilder sb = new StringBuilder();
        
        // 추출할 성격 유형 지표를 정의합니다.
        char[][] indicators = { { 'R', 'T' }, { 'C', 'F' }, { 'J', 'M' }, { 'A', 'N' } };
        
        for (char[] indicator : indicators) {
            // 사전 순으로 빠르도록 정렬합니다. (정렬된 상태로 정의하면 필요없는 부분입니다.)
            Arrays.sort(indicator);

            int firstItem = scoreMap.getOrDefault(indicator[0], 0);
            int secondItem = scoreMap.getOrDefault(indicator[1], 0);
            
            // 점수가 같은 경우 사전 순으로 빠른 유형을 선택하도록 등호를 포함합니다.)
            sb.append((firstItem &amp;gt;= secondItem)? indicator[0]: indicator[1]);
        }
        
        return sb.toString();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Algorithm</category>
      <category>Java</category>
      <category>카카오</category>
      <category>코딩 테스트</category>
      <category>프로그래머스</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/27</guid>
      <comments>https://pidgey.tistory.com/27#entry27comment</comments>
      <pubDate>Mon, 26 Jun 2023 06:58:56 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 크레인 인형뽑기 게임 (2019 카카오 개발자 겨울 인턴십)</title>
      <link>https://pidgey.tistory.com/26</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 상세한 문제 설명은 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/64061&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프로그래머스&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vYrua/btslpW3iJ26/SM5z245dem7PbYF7D6xSrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vYrua/btslpW3iJ26/SM5z245dem7PbYF7D6xSrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vYrua/btslpW3iJ26/SM5z245dem7PbYF7D6xSrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvYrua%2FbtslpW3iJ26%2FSM5z245dem7PbYF7D6xSrK%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;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인형들이 세팅된 N x N의 2차원 배열 board에서 인형을 집어 오른쪽 바구니로 옮기는 문제입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tUBDY/btslpVcgtAP/KA3EgD936qjVMbdnAd1OrK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tUBDY/btslpVcgtAP/KA3EgD936qjVMbdnAd1OrK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tUBDY/btslpVcgtAP/KA3EgD936qjVMbdnAd1OrK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/tUBDY/btslpVcgtAP/KA3EgD936qjVMbdnAd1OrK/img.gif&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;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 바구니로 옮겨진 인형들 위로 새로 옮긴 인형들이 쌓이는 방식인데, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;같은 인형이 두 개 쌓이게 되면 해당 인형들은 사라지게 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;또한 바구니 크기는 제한이 없습니다. (최대 N * N 개의 인형이 쌓일 수도 있는 겁니다)&lt;br /&gt;크레인 위치를 1~N 사이로 이동시키도록 하는 배열 moves에 따라 인형을 뽑을 때 최종적으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사라진 인형의 개수를 구하는 문제&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제한 사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;board 배열은 2차원 배열로 크기는 5x5&amp;nbsp; 이상 30x30 이하입니다.&lt;/li&gt;
&lt;li&gt;board의 각 칸에는 0 이상 100 이하인 정수가 담겨있습니다.
&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;li&gt;1 ~ 100의 각 숫자는 각기 다른 인형의 모양을 의미하며 같은 숫자는 같은 모양의 인형을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;moves 배열의 크기는 1 이상 1,000 이하입니다.&lt;/li&gt;
&lt;li&gt;moves 배열 각 원소들의 값은 1 이상이며 board 배열의 가로 크기 이하인 자연수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입출력 예시&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 54px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 63.9148%; height: 18px;&quot;&gt;board&lt;/td&gt;
&lt;td style=&quot;width: 21.7052%; height: 18px;&quot;&gt;moves&lt;/td&gt;
&lt;td style=&quot;width: 14.3799%; height: 18px;&quot;&gt;result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 63.9148%; height: 36px;&quot;&gt;[[0, 0, 0, 0, 0], [0, 0, 1, 0, 3], [0, 2, 5, 0, 1], [4, 2, 4, 4, 2], [3, 5, 1, 3, 1]]&lt;/td&gt;
&lt;td style=&quot;width: 21.7052%; height: 36px;&quot;&gt;[1, 5, 3, 5, 1, 2, 1, 4]&lt;/td&gt;
&lt;td style=&quot;width: 14.3799%; height: 36px;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF5m94/btslbwZsV6L/2Opr5adlyM6NgRgVWgodvK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF5m94/btslbwZsV6L/2Opr5adlyM6NgRgVWgodvK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF5m94/btslbwZsV6L/2Opr5adlyM6NgRgVWgodvK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF5m94%2FbtslbwZsV6L%2F2Opr5adlyM6NgRgVWgodvK%2Fimg.jpg&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;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택을 활용하여 풀면 간단하게 해결할 수 있는 문제입니다.&lt;br /&gt;1~N 번 크레인 위치의 인형들을 세로 기준으로 나누어서 각 스택에 삽입하고 바구니 또한 스택을 활용하여 풀면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 스택의 peek(), 즉 제일 위쪽에 있는 인형이 현재 크레인에서 집어온 인형과 같다면 삭제 처리해 주면 됩니다.&lt;br /&gt;삭제되는 인형의 개수는 현재 크레인에서 집어온 인형, 바구니 제일 위쪽에 있는 인형 총 2개입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre id=&quot;code_1687726284170&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public int solution(int[][] board, int[] moves) {
        int n = board.length;
        
		// 인형을 LIFO 순으로 뽑을 Stack List 구성
		List&amp;lt;Deque&amp;lt;Integer&amp;gt;&amp;gt; deques = new ArrayList&amp;lt;&amp;gt;();
        
		// 크레인 위치를 기준으로 각 번호에 맞는 스택에 인형을 넣어둡니다.
		for (int i = 0; i &amp;lt; n; i++) {
			Deque&amp;lt;Integer&amp;gt; deque = new ArrayDeque&amp;lt;&amp;gt;();
			for (int j = n - 1; j &amp;gt;= 0; j--) {
				if (board[j][i] != 0) {
					deque.push(board[j][i]);
				}
			}
			deques.add(deque);
		}

		// moves 배열과 규칙에 따라 계산
		int result = 0;
		Deque&amp;lt;Integer&amp;gt; stack = new ArrayDeque&amp;lt;&amp;gt;();
		for (int move : moves) {
			// 문제에서는 1번부터 시작하지만 리스트는 0번 부터 시작하기에 -1 해줍니다.
			int idx = move - 1;
            
			// idx번 위치에 더 이상 인형이 없는 경우 무시합니다.
			if (deques.get(idx).isEmpty()) {
				continue;
			}

			// idx번 위치에서 맨 위의 인형을 집습니다.
			int item = deques.get(idx).pop();
            
			// 바구니가 비어있지 않고, 바구니 맨 위 인형과 현재 집고 있는 인형이 같으면
			if (!stack.isEmpty() &amp;amp;&amp;amp; stack.getFirst() == item) {
				// 삭제 처리 해줍니다.
				stack.pop();
				result += 2;
			} else {
				// 그게 아니면 바구니에 인형을 옮겨줍니다.
				stack.push(item);
			}
		}

		return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N번 크레인 위치에 인형을 세팅해 줄 때 0이 등장하는 순간 해당 열의 인형 삽입을 중단할 수도 있습니다만, 1 0 2 1 3 처럼 0 이후에 인형이 담겨 있을 수도 있기&amp;nbsp;때문에 삽입 중단 로직을 작성하지 않았습니다.&lt;/p&gt;</description>
      <category>개발/Algorithm</category>
      <category>Java</category>
      <category>Stack</category>
      <category>카카오</category>
      <category>코딩 테스트</category>
      <category>프로그래머스</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/26</guid>
      <comments>https://pidgey.tistory.com/26#entry26comment</comments>
      <pubDate>Mon, 26 Jun 2023 05:52:51 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 서비스 코드를 테스트하기 어렵다면?</title>
      <link>https://pidgey.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프레임워크 기반으로 서비스를 개발할 때 간혹 단위 테스트하기 어려운 경우를 마주합니다.&lt;br /&gt;저는 S3 리소스 연동 모듈을 사용하여 서비스를 개발한 적이 있는데 테스트 코드를 작성할 때 서비스 클래스가 S3와 강한 의존성을 갖고 있어 테스트하기 어려웠던 경험이 있습니다. 리소스를 효율적으로 사용하기 위해서 반드시 연동을 테스트해야 하는 경우가 아니라면 임시 모듈로 교체할 필요가 있습니다.&lt;br /&gt;&lt;br /&gt;밑에 샘플 코드를 통해 비슷한 예시를 들 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;샘플 코드&lt;/h2&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #212121; color: #eeffff;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
public class GithubService {

  public RepositoryInformation getInformation(String owner, String repositoryName) throws IOException {
    // 특정 모듈에 대한 의존성이 높아 서비스(단위) 테스트가 어려워지게 됩니다.
    GitHub github = GitHub.connect();
    GHRepository repository = github.getRepository(...);

    return new RepositoryInformation(...);
  }
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 코드의 로직을 테스트할 때 Github.connect()와 같이 서비스 코드에서 직접 구현체를 받아오면 테스트하기 어려운 경우가 발생합니다. Github 클래스의 정적 메서드인 connect()의 리턴값에 의존적이기 때문이죠.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 서비스 코드에서 Github.connect()를 통해 Github 인스턴스를 받아오지 않고 필요한 메서드를 가진 인터페이스를 정의하고 구현체를 정의해서 사용하면 어떨까요?&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #212121; color: #eeffff;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface GithubConnector {

  GitHub connect() throws IOException;

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #212121; color: #eeffff;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class AnonymousGithubConnector implements GithubConnector {

  @Override
  public GitHub connect() throws IOException {
    return GitHub.connectAnonymously();
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 테스트 코드를 보여드리면 좋겠지만 간단하게 예시를 들기 때문에 Config 클래스에서 간단하게 의존성을 주입해보곘습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #212121; color: #eeffff;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class GithubConfig {

  @Bean
  public GithubConnector githubConnector() {
    return new AnonymousGithubConnector();
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; GithubService 클래스에서 의존성을 주입받도록 코드를 개선할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #212121; color: #eeffff;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
public class GithubService {

  private GithubConnector githubConnector;

  public GithubService(GithubConnector githubConnector) {
    this.githubConnector = githubConnector;
  }

  public RepositoryInformation getInformation(String owner, String repositoryName) throws IOException {
    GitHub github = githubConnector.connect();
    GHRepository repository = github.getRepository(owner + &quot;/&quot; + repositoryName);

    return new RepositoryInformation(repository.getFullName(), repository.getStargazersCount(), repository.getForksCount());
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상화를 통해 사용자 클래스를 정의해서 갈아 끼워 줄 수 있도록 유연해지고 테스트하기도 전에 비해 훨씬 수월해졌습니다.&lt;br /&gt;관련해서 더 자세한 내용은 Portable Service Abstraction (PSA) 키워드로 찾아보시면 도움이 될 겁니다.&lt;/p&gt;</description>
      <category>개발/Spring Framework</category>
      <category>Portable Service Abstraction</category>
      <category>PSA</category>
      <category>spring</category>
      <category>spring boot</category>
      <category>test</category>
      <category>Unit Test</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/25</guid>
      <comments>https://pidgey.tistory.com/25#entry25comment</comments>
      <pubDate>Mon, 19 Jun 2023 20:13:46 +0900</pubDate>
    </item>
    <item>
      <title>Naver Cloud Platform Certified Associate 합격 후기</title>
      <link>https://pidgey.tistory.com/24</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NCP 기술 자격증이란 네이버 클라우드에서 주관하는 시험으로 Associate, Professional, Expert 단계로 구성되어 있습니다.&lt;br /&gt;각 자격증 레벨별 설명은 아래 사이트를 참고하면 될 것 같습니다.&lt;br /&gt;&lt;a href=&quot;https://edu.ncloud.com/certi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://edu.ncloud.com/certi&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686565591224&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;EDU PORTAL - NCLOUD CLOUD PLATFORM&quot; data-og-description=&quot;Improve your company&amp;rsquo;s operational competitiveness with a simple and fast workflow.&quot; data-og-host=&quot;edu.ncloud.com&quot; data-og-source-url=&quot;https://edu.ncloud.com/certi&quot; data-og-url=&quot;https://edu.ncloud.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://edu.ncloud.com/certi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://edu.ncloud.com/certi&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;EDU PORTAL - NCLOUD CLOUD PLATFORM&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Improve your company&amp;rsquo;s operational competitiveness with a simple and fast workflow.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;edu.ncloud.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;응시 이유&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NCP에서 사용할 수 있는 크레딧을 꽤 많이 지원받아서 실습이나 실제 개발에 활용할 수 있는 기회가 주어지고&lt;/li&gt;
&lt;li&gt;다른 클라우드 자격증에 비해 저렴할 뿐 아니라 (50,000원)&lt;/li&gt;
&lt;li&gt;클라우드 생태계를 이해하고 활용할 수 있을 정도의 수준을 갖추기 위해&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시험 준비&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;네이버 클라우드에서 제공하는 무료 온라인 교육 (&lt;a href=&quot;https://www.edwith.org/associate-vpc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.edwith.org/associate-vpc&lt;/a&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;/li&gt;
&lt;li&gt; 네이버 클라우드 공식 문서 (&lt;a href=&quot;https://edu.ncloud.com/online&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://edu.ncloud.com/online&lt;/a&gt;)&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VPC, Classic 환경에서 차이나는 부분&lt;/li&gt;
&lt;li&gt;수치나 설정 관련 주의사항&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;1~2번에서 공부하면서 미리 예상 문제를 작성하고 풀이&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;다른 블로그의 내용을 참고하면서 부족한 내용 보충&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 예상 문제를 만들어보고 풀어보는 걸 추천합니다. 저는 객관식보단 단답형으로 만들어서 풀어보았고 유사한 개념, 서비스 등은 차이점을 명확하게 이해할 수 있도록 문제를 구성했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;응시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시험은 비대면, 대면 방식 중 하나를 선택할 수 있고 저는 비대면 방식을 선택했습니다.&lt;br /&gt;구글 Meet으로 1:1 감독 하에 진행했고 시험은 60분에 60문제 였지만 매우 넉넉하게 주어졌던 것 같습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TIPS&lt;/h2&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;VPC, Subnet, 비용 계산, 구체적인 수치, 서비스 용어 관련해서 많이 나왔습니다.&lt;/li&gt;
&lt;li&gt;한국어 시험에서 영어 문제가 1~2개 등장하기도 했습니다. (이 부분은 오류일 수도 있을 것 같네요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들어보았던 예상 문제가 필요하신 분은 댓글이나 개인적으로 연락 주시면 공유해 드릴게요 (잘못된 문제도 많을 겁니다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-06-09 오후 9.29.57.png&quot; data-origin-width=&quot;2278&quot; data-origin-height=&quot;1562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJoDS0/btsjBuiqDk7/KpqOvpG2dyyJQJhKhML861/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJoDS0/btsjBuiqDk7/KpqOvpG2dyyJQJhKhML861/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJoDS0/btsjBuiqDk7/KpqOvpG2dyyJQJhKhML861/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJoDS0%2FbtsjBuiqDk7%2FKpqOvpG2dyyJQJhKhML861%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;2278&quot; height=&quot;1562&quot; data-filename=&quot;스크린샷 2023-06-09 오후 9.29.57.png&quot; data-origin-width=&quot;2278&quot; data-origin-height=&quot;1562&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;</description>
      <category>일기</category>
      <category>Cloud</category>
      <category>NAVER CLOUD PLATFORM</category>
      <category>NCP</category>
      <category>NCP Associate</category>
      <category>네이버 클라우드</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/24</guid>
      <comments>https://pidgey.tistory.com/24#entry24comment</comments>
      <pubDate>Mon, 12 Jun 2023 19:48:20 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Data JPA] saveAll은 만능일까?</title>
      <link>https://pidgey.tistory.com/23</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개발 환경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 개발 환경에 맞추어 포스팅되었으니 참고 바랍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java / Gradle&lt;/li&gt;
&lt;li&gt;Spring Boot 2.7.8&lt;/li&gt;
&lt;li&gt;Spring Data JPA&lt;/li&gt;
&lt;li&gt;MySQL 8.0.24&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 간단한 팀 프로젝트를 진행하면서 Spring Data JPA를 사용했습니다.&lt;br /&gt;평소에 자주 애용하던 KREAM의 백엔드 서버를 분석하고 구현하는 프로젝트였는데, 필자는 이 프로젝트에서 스타일 파트를 맡았습니다.&lt;br /&gt;스타일은 일종의 인스타그램과 매우 비슷했는데, 특이한 점으로는 KREAM에서 거래 중인 상품을 태그 할 수 있다는 점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 도메인의 피드 등록 기능을 개발하던 중 피드에 들어가는 해쉬태그(#)나 상품들을 별도 테이블로 분리해서 관리할 필요가 있었습니다. 테이블이 분리되었기 때문에 하나의 피드가 등록될 때면 여러 개의 해쉬태그, 피드 상품들이 테이블에 저장되는 구조가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션으로 추출된 해쉬태그, 상품 리스트들이 JPA Repository에서 기본적으로 제공하는 saveAll 메서드를 통해 잘 들어가는 것을 확인했습니다.&lt;/p&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;&lt;b&gt;여러 건의 데이터를 넣을 때에는?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA를 사용하여 개발할 때 우리는 다건 삽입에 대해 크게 고민할 필요가 없습니다.&lt;br /&gt;JPA Repository를 상속받게 되면 SimpleJPARepository 구현체를 만들어주기 때문인데요.&lt;br /&gt;해당 구현체의 삽입, 수정 관련 메서드는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F8OrV/btrZi6agWRR/8I5aIdd6gDlDMXpffN4kt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F8OrV/btrZi6agWRR/8I5aIdd6gDlDMXpffN4kt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F8OrV/btrZi6agWRR/8I5aIdd6gDlDMXpffN4kt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF8OrV%2FbtrZi6agWRR%2F8I5aIdd6gDlDMXpffN4kt0%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;458&quot; height=&quot;108&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;영속성 관련 이야기만 해도 너무 길어지니 생략하고&lt;/span&gt;&lt;br /&gt;JPA는 save(AndFlush), saveAll(AndFlush)를 통해서 하나의 엔티티를 데이터베이스에 삽입, 수정하거나 여러 엔티티를 한 번에 삽입, 수정할 수 있도록 기본적인 메서드를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 로직을 살펴보면 save와 saveAll은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGSPa/btrZjgRf9Hi/JUGCSibm2BwFQWkha9R8T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGSPa/btrZjgRf9Hi/JUGCSibm2BwFQWkha9R8T0/img.png&quot; data-alt=&quot;save&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGSPa/btrZjgRf9Hi/JUGCSibm2BwFQWkha9R8T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnGSPa%2FbtrZjgRf9Hi%2FJUGCSibm2BwFQWkha9R8T0%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;309&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;save&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qODDZ/btrZoqdMI35/psnJk5dkPKDaClmDAgniPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qODDZ/btrZoqdMI35/psnJk5dkPKDaClmDAgniPK/img.png&quot; data-alt=&quot;saveAll&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qODDZ/btrZoqdMI35/psnJk5dkPKDaClmDAgniPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqODDZ%2FbtrZoqdMI35%2FpsnJk5dkPKDaClmDAgniPK%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;537&quot; height=&quot;308&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;saveAll&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 로직에서 Loop를 돌며 save를 하거나 saveAll를 통해 다건의 데이터를 삽입할 수 있겠죠.&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;(실제로는 트랜잭션과 관련하여 save의 비용이 더 높습니다. save vs. saveAll 키워드로 찾아보길 권장합니다.)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;saveAll의 문제점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 데이터베이스는 MySQL를 사용하고 있습니다.&lt;br /&gt;MySQL의 &lt;b&gt;AUTO_INCREMENT &lt;/b&gt;옵션은 기본키를 연속적으로 생성해주는 옵션인데요.&lt;br /&gt;일반적으로 JPA를 사용할 때 엔티티의 기본키 생성 전략을 &lt;b&gt;GenerationType.IDENTITY&lt;/b&gt;로 가져갑니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private Long authorId;

@Lob
private String contents;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Id(기본키)로 지정된 필드가 비어있는데 어떻게 영속화가 가능할까요?&lt;br /&gt;Hibernates에서는 IDENTITY 전략을 사용할 경우 save 할 때 데이터베이스에 일단 INSERT 한 뒤 생성된 기본키를 가져옵니다.&lt;br /&gt;그러다 보니 Bulk Insert 할 때 N번 째 데이터의 기본키(식별자)를 채번 하기 위해 N-1 데이터까지 INSERT가 되어있는 상태에서 N번 째 데이터를 &lt;b&gt;INSERT 하고 생성된 기본키를 가져오게 되어서 실질적으로 N번의 삽입 쿼리가 발생&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vladmihalcea.com/jpa-persist-and-merge/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ValdMihalcea의 How do persist and merge work in JPA&lt;/a&gt;를 참고하면 위와 같은 채번 원리 때문에 Hibernates에서는 기본적으로 &lt;b&gt;IDENTITY 기본키 생성 전략을 가져갈 때 Batch Insert를 허용하지 않는다&lt;/b&gt;는 것을 알 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;postRepository.saveAll(posts);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676428512884&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: insert into post (author_id, contents) values (?, ?)
Hibernate: insert into post (author_id, contents) values (?, ?)
Hibernate: insert into post (author_id, contents) values (?, ?)
Hibernate: insert into post (author_id, contents) values (?, ?)
Hibernate: insert into post (author_id, contents) values (?, ?)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 spring.jpa.show-sql=true을 주고 쿼리를 확인해 본 결과 N번의 삽입 쿼리가 발생합니다.&lt;br /&gt;이는 &lt;b&gt;데이터베이스 성능 저하의 원인이 되고 결과적으로 서버 응답 시간이 길어지기 &lt;/b&gt;때문에 개선할 필요가 있어 보입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;총 2가지 대안&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 saveAll을 정상적으로 동작시키기 위한 방법은 없을까요?&lt;br /&gt;아래에서 대표적인 대안 2가지를 소개하겠습니다.&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(그 외 대안도 있지만 이 포스팅에서는 키워드 정도만 소개하겠습니다)&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;대안 1. 기본키 생성 전략 변경&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GenerationType.SEQUENCE 또는 GenerationType.TABLE 방식을 사용하는 것입니다.&lt;br /&gt;다만, MySQL은 SEQUENCE 채빈 방식을 지원하지 않기 때문에 TABLE 채번 방식을 활용해 보도록 하겠습니다.&lt;br /&gt;(단 기본키 전략을 변경하게 되면 DB 스키마를 변경해야 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 테이블 스키마입니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE post
(
    id          BIGINT  PRIMARY KEY,
    contents    TEXT    NOT NULL,
    author_id   BIGINT  NOT NULL
);

CREATE TABLE sequence
(
    table_name  VARCHAR(255) NOT NULL PRIMARY KEY ,
    next_val    BIGINT
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity 클래스의 코드를 다음과 같이 추가 / 변경합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@TableGenerator(
  name = &quot;post_sequence_generator&quot;, // Generator 이름
  table = &quot;sequence&quot;, // TABLE 전략에 사용할 테이블명
  pkColumnName = &quot;table_name&quot;, // 생성 전략 테이블의 PK 컬럼명
  pkColumnValue = &quot;post&quot;, // 생성 전략 테이블의 PK 값
  allocationSize = 10000 // 한 번에 가져올 SEQUENCE 크기 (1이면 한 번씩 쿼리 나갑니다)
)
public class Post {

 @Id
 @GeneratedValue(
   strategy = GenerationType.TABLE, // 생성 전략 변경
   generator = &quot;post_sequence_generator&quot; // 위에서 정의한 TableGenerator Name
 )
 private Long id;

 private Long authorId;

 @Lob
 private String contents;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본에 Bulk Insert가 되지 않는 상태에서 saveAll의 시간대비 확연하게 줄어든 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, spring.jpa.show-sql=true 옵션만 준 상태에서는 INSERT 쿼리가 여러 번 나간 것처럼 보입니다.&lt;br /&gt;그럴 땐 spring.datasource.url 에 아래와 같은 옵션을 추가하면 INSERT 쿼리가 합쳐서 나가는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;?rewriteBatchedStatements=true&amp;amp;profileSQL=true&amp;amp;logger=Slf4JLogger&amp;amp;maxQuerySizeToLog=999999&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 설정 파일에 다음과 같은 옵션을 넣어주고 Batch Size는 적절하게 조절하면 됩니다.&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;(자세한 내용들은 직접 찾아보길 권장합니다.)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 10000
          batch_versioned_data: true
        order_inserts: true
        order_updates: true&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676430888704&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.
.
.

[QUERY] insert into post (author_id, contents, id) values (0, x'30', 21102),(1, x'31', 21103),(2, x'32', 21104),
(3, x'33', 21105),(4, x'34', 21106),(5, x'35', 21107)&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;과 기존 IDENTITY 전략을 TABLE 전략으로 변경한다는 점에서 &lt;b&gt;설계가 변경되는 점&lt;/b&gt;입니다. 따라서 다른 대안이 필요합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;대안 2. JDBC batchUpdate&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 별도의 테이블을 만들지 않고 기본키 생성 전략도 변경하지 않는 방법이 있습니다.&lt;br /&gt;바로 &lt;b&gt;Jdbc의 batchUpdate를 사용&lt;/b&gt;하는 방법입니다.&lt;br /&gt;Spring Boot Data JPA 의존성을 추가하면 기본적으로 JDBC 의존성도 같이 추가되기 때문에 별도로 설정할 필요가 없어서 좋습니다.&lt;br /&gt;DI를 통해서 JdbcTemplate를 주입받고 활용하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class PostCustomRepositoryImpl implements PostCustomRepository {

 private final JdbcTemplate jdbcTemplate;
 
 @Override
 public List&amp;lt;Long&amp;gt; batchUpdate(List&amp;lt;Post&amp;gt; posts) {
  return Arrays.stream(
      jdbcTemplate.batchUpdate(
        &quot;INSERT INTO post (contents, author_id) VALUES (?, ?)&quot;,
        new BatchPreparedStatementSetter() {
         @Override
         public void setValues(PreparedStatement ps, int i) throws SQLException {
          ps.setString(1,  posts.get(i).getContents());
          ps.setLong(2,  posts.get(i).getAuthorId());
         }

         @Override
         public int getBatchSize() { return posts.size(); }
        }
      ))
    .boxed()
    .map(Integer::longValue)
    .toList();
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외 설정 파일에 &lt;b&gt;대안 1 (GenerationType.TABLE)에서 설정한 DataSource URL 옵션&lt;/b&gt;만 추가해 주면 됩니다.&lt;br /&gt;JDBC Template과 batchUpdate에 대한 내용은 관련 자료를 더 찾아보면 될 것 같습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그 외&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mybatis &amp;lt;foreach&amp;gt;&lt;/li&gt;
&lt;li&gt;JOOQ&lt;/li&gt;
&lt;li&gt;QueryDSL-SQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법들을 사용하여 해결할 수도 있지만 이 포스팅에 전부 다루기는 어렵기 때문에 2부에서 다루도록 하겠습니다 :)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;성능 비교&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;총 10000건&lt;/b&gt;에 대해서 Bulk Insert 할 때 소요되는 시간을 측정해 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존 (GenerationType.IDENTITY &amp;amp; JPARepository.saveAll)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;49&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZkxAc/btrZmDxUL2X/BxOJabFZwpl3UiKUSb5Fvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZkxAc/btrZmDxUL2X/BxOJabFZwpl3UiKUSb5Fvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZkxAc/btrZmDxUL2X/BxOJabFZwpl3UiKUSb5Fvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZkxAc%2FbtrZmDxUL2X%2FBxOJabFZwpl3UiKUSb5Fvk%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;644&quot; height=&quot;49&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;49&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대안 1. GenerationType.TABLE&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tNZiB/btrZoqZnt2b/MMC7mKR7wzmVpERu5qTQHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tNZiB/btrZoqZnt2b/MMC7mKR7wzmVpERu5qTQHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tNZiB/btrZoqZnt2b/MMC7mKR7wzmVpERu5qTQHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtNZiB%2FbtrZoqZnt2b%2FMMC7mKR7wzmVpERu5qTQHK%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;640&quot; height=&quot;52&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대안 2. JDBC batchUpdate&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OuWPw/btrZmCTgiOC/Fywz72IZPkYkJ3xE5Tat0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OuWPw/btrZmCTgiOC/Fywz72IZPkYkJ3xE5Tat0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OuWPw/btrZmCTgiOC/Fywz72IZPkYkJ3xE5Tat0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOuWPw%2FbtrZmCTgiOC%2FFywz72IZPkYkJ3xE5Tat0K%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;642&quot; height=&quot;53&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC batchUpdate를 사용하는 방법이 간단하며 설계 변경이 필요하지 않다는 점에서 여러 장점이 많은 것 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;참고 자료&lt;/b&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;a href=&quot;https://wave1994.tistory.com/160&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring boot :: JdbcTemplate을 사용하여 batch insert 기능 구현 (훈훈훈)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@fj2008/JPA-save-saveAll-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%A3%BC%EC%9D%98-%EC%82%AC%ED%95%AD%EB%93%A4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JPA /save, saveAll 사용시 주의 사항들 (25gstory.log)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vladmihalcea.com/jpa-persist-and-merge/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;How do persist and merge work in JPA (Vlad Mihalcea)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@gillog/JPA-%EA%B8%B0%EB%B3%B8-%ED%82%A4-%EC%83%9D%EC%84%B1-%EC%A0%84%EB%9E%B5IDENTITY-SEQUENCE-TABLE#allocationsize%EC%99%80-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[JPA] 기본 키 생성 전략 (IDENTITY, SEQUENCE, TABLE) (gil.log)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sabarada.tistory.com/220&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[JPA] Spring JPA 환경에서 bulk insert를 효율적으로 해보자 - JPA의 한계와 JDBC 활용 (사라바다는 차곡차곡)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/Spring Framework</category>
      <category>auto_increment</category>
      <category>mysql</category>
      <category>spring boot</category>
      <category>spring data jpa</category>
      <category>spring jdbc</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/23</guid>
      <comments>https://pidgey.tistory.com/23#entry23comment</comments>
      <pubDate>Wed, 15 Feb 2023 13:13:57 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 백준 2178번: 미로 탐색</title>
      <link>https://pidgey.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2178&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/2178&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672910338148&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;2178번: 미로 탐색&quot; data-og-description=&quot;첫째 줄에 두 정수 N, M(2 &amp;le; N, M &amp;le; 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은 붙어서 입력으로 주어진다.&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/2178&quot; data-og-url=&quot;https://www.acmicpc.net/problem/2178&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kmytB/hyQ81m4leq/diMEOWIpAPJb9ijox18wvk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2178&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/2178&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kmytB/hyQ81m4leq/diMEOWIpAPJb9ijox18wvk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&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;2178번: 미로 탐색&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 두 정수 N, M(2 &amp;le; N, M &amp;le; 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은 붙어서 입력으로 주어진다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미로에서 1은 이동할 수 있는 칸을 나타내고, 0은 이동할 수 없는 칸을 나타낸다. 이러한 미로가 주어졌을 때, (1, 1)에서 출발하여 (N, M)의 위치로 이동할 때 지나야 하는 &lt;u&gt;&lt;b&gt;최소의 칸 수&lt;/b&gt;&lt;/u&gt;를 구하는 프로그램을 작성하시오. 한 칸에서 다른 칸으로 이동할 때, 서로 인접한 칸으로만 이동할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑줄 친 부분을 통해서 BFS 풀이법으로 접근해 볼 힌트를 얻을 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Point {
    int y;
    int x;

    Point(int y, int x) {
        this(y, x);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 풀이에 앞서 좌표를 특정할 Point 클래스를 정의하고 풀어나갑니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class BJ2178 {
    private static final Scanner scanner = new Scanner(System.in);

    public static int bfs(boolean[][] map, Point start, Point dest) {
        boolean[][] visited = new boolean[map.length][map[0].length];
        visited[start.y][start.x] = true;

        Deque&amp;lt;Point&amp;gt; deque = new LinkedList&amp;lt;&amp;gt;();
        deque.add(start);

        int count = 0;
        while (!deque.isEmpty()) {
            Point now = deque.poll();
            count++;
            if (now.y == dest.y &amp;amp;&amp;amp; now.x == dest.x) {
                break;
            } else {
                if (map[now.y+1][now.x] &amp;amp;&amp;amp; !visited[now.y+1][now.x]) {
                    visited[now.y+1][now.x] = true;
                    deque.add(new Point(now.y+1, now.x));
                }
                if (map[now.y][now.x+1] &amp;amp;&amp;amp; !visited[now.y][now.x+1]) {
                    visited[now.y][now.x+1] = true;
                    deque.add(new Point(now.y, now.x+1));
                }
                if (map[now.y-1][now.x] &amp;amp;&amp;amp; !visited[now.y-1][now.x]) {
                    visited[now.y-1][now.x] = true;
                    deque.add(new Point(now.y-1, now.x));
                }
                if (map[now.y][now.x-1] &amp;amp;&amp;amp; !visited[now.y][now.x-1]) {
                    visited[now.y][now.x-1] = true;
                    deque.add(new Point(now.y, now.x-1));
                }
            }
        }
        return count;
    }

    public static void main(String[] args) {
        int[] request = Arrays.stream(scanner.nextLine().split(&quot; &quot;))
                .mapToInt(Integer::parseInt)
                .toArray();
        boolean[][] map = new boolean[request[0]+2][request[1]+2];
        for (int i = 1; i &amp;lt;= request[0]; i++) {
            String[] numbers = scanner.nextLine().split(&quot;&quot;);
            for (int j = 0; j &amp;lt; numbers.length; j++) {
                map[i][j+1] = numbers[j].equals(&quot;1&quot;);
            }
        }
        System.out.println(bfs(map, new Point(1, 1), new Point(request[0], request[1])));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Main 클래스에서 입력을 받아 문제에서 주어진 미로를 한 번 감싸는 사이즈가 2인 패딩을 줍니다.&lt;br /&gt;그리고 미로와 시작점, 도착점을 지정해서 bfs 메서드를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS 메서드 내에서 방문한 곳을 기록하는 visited 2차 배열을 선언해 줍니다. (사이즈는 map과 같습니다)&lt;br /&gt;시작점을 밟은 상태로 시작하기 때문에 시작점은 미리 true로 설정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deque에 시작점을 넣어주고 Queue에 방문할 수 있는 노드가 있으면 계속 반복하도록 while 문을 시작합니다.&lt;br /&gt;현재 지점을 deque에서 꺼내오고 이동 횟수(count)를 증가시킨 뒤 현재 위치가 목표(dest) 좌표와 같은지 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같지 않다면 아래쪽 -&amp;gt; 오른쪽 -&amp;gt; 위쪽 -&amp;gt; 왼쪽 순으로 현재 노드 기준으로 방문할 수 있는 노드를 체크해서 deque에 넣습니다.&lt;br /&gt;문제의 규칙이 (1, 1)에서 시작해 (N, M)을 탐색하는, 즉 좌상단에서 우하단으로 탐색하기 때문에 위와 같은 순서로 접근해 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표 좌표에 도달했다면 count를 출력해 봅니다. 잘 동작할까요?&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;틀렸습니다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이(2)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 풀이에서는 노드를 이동할 때마다 무조건 count를 합니다.&lt;br /&gt;문제에서 요구한 최소 경로가 아니라 아무 노드로 빠져도 증가하게 되기 때문이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선을 해봅시다.&lt;br /&gt;우리는 좌표를 특정하는 Point 클래스를 정의했기 때문에 이 클래스를 활용해볼 수 있겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Point 클래스 필드에 현재 좌표가 몇 개의 길을 거쳐왔는지 즉 &lt;span&gt;depth를 갖도록 해보면 어떨까요?&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Point {
    int y;
    int x;
    int depth;

    Point(int y, int x) {
        this(y, x, 1);
    }

    Point(int y, int x, int depth) {
        this.y = y;
        this.x = x;
        this.depth = depth;
    }
}

class BJ2178 {
	// ...
    
    public static int bfs(boolean[][] map, Point start, Point dest) {
    	// ...
        
        while (!deque.isEmpty()) {
            Point now = deque.poll();
            if (now.y == dest.y &amp;amp;&amp;amp; now.x == dest.x) {
                count = now.depth;
                break;
            } else {
                if (map[now.y+1][now.x] &amp;amp;&amp;amp; !visited[now.y+1][now.x]) {
                    visited[now.y+1][now.x] = true;
                    deque.add(new Point(now.y+1, now.x, now.depth+1));
                }
                if (map[now.y][now.x+1] &amp;amp;&amp;amp; !visited[now.y][now.x+1]) {
                    visited[now.y][now.x+1] = true;
                    deque.add(new Point(now.y, now.x+1, now.depth+1));
                }
                if (map[now.y-1][now.x] &amp;amp;&amp;amp; !visited[now.y-1][now.x]) {
                    visited[now.y-1][now.x] = true;
                    deque.add(new Point(now.y-1, now.x, now.depth+1));
                }
                if (map[now.y][now.x-1] &amp;amp;&amp;amp; !visited[now.y][now.x-1]) {
                    visited[now.y][now.x-1] = true;
                    deque.add(new Point(now.y, now.x-1, now.depth+1));
                }
            }
        }
        return count;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 좌표에서 갈 수 있는 다른 노드를 추가할 때 현재 좌표가 거쳐온 depth+1을 넣어주면 목표 지점에 도달했을 때의 depth가 문제에서 요구한 가장 빠르게 (적게) 접근한 &lt;b&gt;최소 칸의 수 &lt;/b&gt;일 것입니다.&lt;/p&gt;</description>
      <category>개발/Algorithm</category>
      <category>2178</category>
      <category>BFS</category>
      <category>백준</category>
      <author>PIDGEY</author>
      <guid isPermaLink="true">https://pidgey.tistory.com/22</guid>
      <comments>https://pidgey.tistory.com/22#entry22comment</comments>
      <pubDate>Thu, 5 Jan 2023 18:42:36 +0900</pubDate>
    </item>
  </channel>
</rss>