<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>black_han26.log</title>
        <link>https://velog.io/</link>
        <description>Slow-starter</description>
        <lastBuildDate>Wed, 12 Jun 2024 17:02:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>black_han26.log</title>
            <url>https://velog.velcdn.com/images/black_han26/profile/405831a0-9cf7-4214-90da-2292ca29f32a/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. black_han26.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/black_han26" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[비관적 락을 이용한 동시성 문제 해결하기]]></title>
            <link>https://velog.io/@black_han26/%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD%EC%9D%84-%EC%9D%B4%EC%9A%A9</link>
            <guid>https://velog.io/@black_han26/%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD%EC%9D%84-%EC%9D%B4%EC%9A%A9</guid>
            <pubDate>Wed, 12 Jun 2024 17:02:57 GMT</pubDate>
            <description><![CDATA[<h2 id="진행상황">진행상황</h2>
<p>지난번에는 <a href="https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%B2%98%EB%A6%AC-91cwalxa">낙관적 락</a>을 이용해서 문제 해결을 해보았다.</p>
<p>이번에는 비관적 락을 이용해보자
<a href="https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-%EA%B8%B0%EB%B2%95-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD">비관적 락</a>의 단점에 대해서는 해당 링크에서 확인했었는데, 우리 프로젝트는 팀에 가입하고 업무를 부여받는 식이기 때문에 충돌할 가능성이 낮다고 판단했고 낙관적 락을 사용했었다.</p>
<h1 id="동시성-처리-전">동시성 처리 전</h1>
<p>해당 Test코드는 낙관적 락을 적용하기 전과 같다. 100명을 가입신청 한다.</p>
<pre><code class="language-java">    @Test
    @DisplayName(&quot;5명 제한 팀에 100명이 동시에 가입 신청&quot;)
    void joinTeamTest()throws InterruptedException{
        //given
        final int JOIN_USER = 100;
        User user1 = createUser();
        createTeam(user1.getId());
        TeamJoinDto joinDto = TeamJoinDto.builder()
                .joinCode(&quot;1111&quot;)
                .build();
        CountDownLatch countDownLatch = new CountDownLatch(JOIN_USER);
        //101명을 한번에 넣어주기 위함
        ExecutorService executorService = Executors.newFixedThreadPool(101);

        //when
        for (int i = 0; i &lt; JOIN_USER; i++) {
            User user2 = createUser();
            executorService.submit(()-&gt;{
                try{
                    teamService.joinTeam(user2.getId(),joinDto,1L);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        // 모든 작업이 완료될 때까지 대기
        countDownLatch.await();

        // then
        // 5명이어야한다.
        List&lt;MemberEntity&gt; members = memberRepository.findAll();
        System.out.println(members.size());
        assertThat(members.size()).isEqualTo(5);
    }</code></pre>
<h2 id="결과">결과<img src="https://velog.velcdn.com/images/black_han26/post/eba79941-5e5c-42a7-861f-0366385dfa38/image.png" alt=""></h2>
<p>5명 정원인데, 30명이 가입되었다.</p>
<h1 id="동시성-처리">동시성 처리</h1>
<p>JPA의 @Lock을 이용했다. TeamReposiotry에서 <code>@Lock(LockModeType.PESSIMISTIC_WRITE)</code>을 통해 쿼리에 비관적 잠금을 적용한다. 이렇게 되면 해당 엔티티에 대해 다른 트랜잭션이 쓰기 잠금을 걸 수 없다.</p>
<pre><code class="language-java">@Repository
public interface TeamReposiotry extends JpaRepository&lt;TeamEntity, Long&gt; {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(&quot;select t &quot; +
            &quot;from TeamEntity t &quot; +
            &quot;where t.id =:teamId&quot;)
    Optional&lt;TeamEntity&gt; findByIdWithPessimisticLock(@Param(&quot;teamId&quot;) Long teamId);
}</code></pre>
<p>현재는 팀을 조회하고 5명이 넘었는지 확인하는 과정에 있다.
따라서 JPQL을 위처럼 작성했다.
이제 joinTeam 메서드에서 팀을 조회할 때 <code>findByIdWithPessimisticLock</code> 를 사용한다. <img src="https://velog.velcdn.com/images/black_han26/post/172dc763-1c7c-453f-b9e9-40208716779a/image.png" alt="">팀을 조회할 때 기존의 findById를 주석처리</p>
<h2 id="결과-1">결과</h2>
<p>다섯명이 정상적으로 가입되었다.<img src="https://velog.velcdn.com/images/black_han26/post/4293c5a0-59cb-417f-84ad-cc3870ee0e2d/image.png" alt="">다음 시간에는 분산락을 알아보고, 적용해보는 시간을 가지려 한다. 그리고 여태 적용해봤던 락들이 실제 실행시간을 비교해보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[정처기]24년 2회 정보처리기사 필기.. 두 번째 합격/응시자격 서류제출]]></title>
            <link>https://velog.io/@black_han26/%EC%A0%95%EC%B2%98%EA%B8%B024%EB%85%84-2%ED%9A%8C-%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC-%ED%95%84%EA%B8%B0..-%EB%91%90-%EB%B2%88%EC%A7%B8-%EC%8B%9C%ED%97%98</link>
            <guid>https://velog.io/@black_han26/%EC%A0%95%EC%B2%98%EA%B8%B024%EB%85%84-2%ED%9A%8C-%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC-%ED%95%84%EA%B8%B0..-%EB%91%90-%EB%B2%88%EC%A7%B8-%EC%8B%9C%ED%97%98</guid>
            <pubDate>Tue, 11 Jun 2024 12:16:28 GMT</pubDate>
            <description><![CDATA[<p>24년 2회 정보처리기사 필기 두 번째 시험 합격했다.
<img src="https://velog.velcdn.com/images/black_han26/post/dc458eaa-171c-4735-9c4c-a31374e38098/image.png" alt=""></p>
<p>이게 무슨 일인가 싶다.. 나는 <a href="https://velog.io/@black_han26/24%EB%85%841%ED%9A%8C%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC">정처기 1회</a> 필기를 합격했었다.</p>
<p>맞다. 학력증명서를 내지 않아서 합격이 취소되었다..... 
그래서 실기를 보지 못하고 필기를 다시 봤다😢</p>
<p>다들 시험 결과가 발표되면 대략 열흘 이내 응시자격 서류제출을 꼭 하길 바란다.. 
<img src="https://velog.velcdn.com/images/black_han26/post/111bd34f-2d50-4c5c-9244-45d2ebbaf5dd/image.png" alt=""><strong>고객지원 공지사항</strong>에 가보면 일정이 있다. 기사 시험은 밑에 1회, 2회, 3회로 구분 지어 나와 있다. 온라인 제출을 하지 않으면 서류제출기간 내에 직접 공단에 방문하거나, 우편으로 보내야 하는 것으로 안다.
본인은 실기접수를 하려고 보니깐, 이미 서류제출기간이 지나있었다..
전화까지 해봤는데 기간 지나면 절대 안 받아줍니다.. 주의하세요! </p>
<p>그렇게 온라인 접수를 하는데, 학력증명서는 사본제출이 안 돼서 여기서 직접 결제하고 제출했다 .내돈 2,500원..<img src="https://velog.velcdn.com/images/black_han26/post/547ee059-2530-419e-8c45-a94857357a5e/image.png" alt="">제출완료 되면 바로 승인되지는 않는 모양이다..<img src="https://velog.velcdn.com/images/black_han26/post/ce09872c-3b1b-4d51-8168-74ecfeb84f7a/image.png" alt="">당장은 아무것도 뜨지 않는다. 이게 제출이 된 것인지, 또 기한 내에 못내는 건 아닌지 걱정이 됐다.. 그리고 이틀이 지난 후
<img src="https://velog.velcdn.com/images/black_han26/post/0d1978d5-abf5-42f4-bfaf-547ff82ab084/image.png" alt="">승인이 완료됐다. 이제 안심이 된다..😂
자격증 하나도 못 따는 양상이 될까 걱정했는데 다행히 그때 같이 준비했던 SQLD가 합격했다.<img src="https://velog.velcdn.com/images/black_han26/post/e09a3c50-d1c7-42e1-b990-925d70632273/image.png" alt="">지금부터라도 잘 확인하고 차곡차곡 역량을 쌓아나가야겠다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[낙관적 락을 이용한 동시성 문제 해결하기]]></title>
            <link>https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%B2%98%EB%A6%AC-91cwalxa</link>
            <guid>https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%B2%98%EB%A6%AC-91cwalxa</guid>
            <pubDate>Sat, 08 Jun 2024 14:48:36 GMT</pubDate>
            <description><![CDATA[<h3 id="진행상황">진행상황</h3>
<p>지난 시간에 <a href="https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-%EA%B8%B0%EB%B2%95-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD">동시성 제어</a>와 <a href="https://velog.io/@black_han26/%EB%8D%B0%EB%93%9C%EB%9D%BD%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%99%80-%EB%B0%9C%EC%83%9D-%EC%9B%90%EC%9D%B8-MySQL-Deadlock">데드락</a>에 대해서 알아봤다.
이번에는 프로젝트에 동시성 처리를 적용해본다.</p>
<p>대략 서비스를 설명하자면, 먼저 회원 가입을 하고 팀에 가입한다. 팀에 가입하기 위해서는 Join코드가 필요하고 &#39;1111&#39; 로 세팅했다.</p>
<p>팀 인원이 5명 제한이 걸려있는데, 100명을 동시에 가입시켜보려 한다.</p>
<h1 id="동시성-처리-전-code">동시성 처리 전 Code</h1>
<pre><code class="language-java">    @Test
    @DisplayName(&quot;5명 제한 팀에 100명이 동시에 가입 신청&quot;)
    void joinTeamTest()throws InterruptedException{
        //given
        final int JOIN_USER = 100;
        User user1 = createUser();
        createTeam(user1.getId());
        TeamJoinDto joinDto = TeamJoinDto.builder()
                .joinCode(&quot;1111&quot;)
                .build();
        CountDownLatch countDownLatch = new CountDownLatch(JOIN_USER);
        //101명을 한번에 넣어주기 위함
        ExecutorService executorService = Executors.newFixedThreadPool(101);

        //when
        for (int i = 0; i &lt; JOIN_USER; i++) {
            User user2 = createUser();
            executorService.submit(()-&gt;{
                try{
                    teamService.joinTeam(user2.getId(),joinDto,1L);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        // 모든 작업이 완료될 때까지 대기
        countDownLatch.await();

        // then
        // 5명이어야한다.
        List&lt;MemberEntity&gt; members = memberRepository.findAll();
        System.out.println(members.size());
        assertThat(members.size()).isEqualTo(5);
    }</code></pre>
<h2 id="결과">결과<img src="https://velog.velcdn.com/images/black_han26/post/dd8c04b5-cd9a-44ad-b03f-30dadbd5d6a9/image.png" alt=""></h2>
<p>5명은커녕 29명이 가입된 것을 알 수 있다. 제작한 프로젝트는 팀의 인원수를 제한하고 인원수 증설에 따른 결제를 필요로하게했지만, 이런 오류가 발생하면 치명적일 수 있다. 앞서 배운 동시성 제어가 필요해보인다. </p>
<p>이런 오류가 이커머스나 선착순 타임세일 같은 긴박한 프로젝트였을 경우 문제는 더 커지겠지만, 우리 프로젝트는 그저 팀에 가입하고 업무를 부여받는 식이기 때문에 충돌할 가능성이 낮다고 판단했고 낙관적 락을 사용해보기로 했다.</p>
<h1 id="동시성-처리">동시성 처리</h1>
<p><a href="https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-%EA%B8%B0%EB%B2%95-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD">낙관적 락 개념</a>은 지난 시간에 학습했다.
낙관적 락은 JPA에서 지원해주기에 구현이 간단하다. 먼저 아래를 Entity에 추가한다.
<img src="https://velog.velcdn.com/images/black_han26/post/4201e444-ad6a-423a-9797-c7189b570ba6/image.png" alt="">팀에 가입하는 단계에 버전을 조회해야 한다.
즉, joinTeam 메소드를 수정해야 한다.</p>
<p>테스트 코드에서는 변한 게 없다. 테스트 코드에서 <img src="https://velog.velcdn.com/images/black_han26/post/7881dcf7-eb92-4811-b472-55bb178b5539/image.png" alt=""> 체크한 부분의 joinTeam메서드가 수정되어야 한다.<img src="https://velog.velcdn.com/images/black_han26/post/e907f2ba-6007-4d9b-a20c-6db2e095705e/image.png" alt=""> 팀을 조회할 때 findById를 주석처리하고 <code>findByIdWithOptimisticLock</code> 를 사용했다.</p>
<p>findByIdWithOptimisticLock는 TeamReposiotry에서 아래처럼 간단하게 작성한다.</p>
<pre><code class="language-java">@Repository
public interface TeamReposiotry extends JpaRepository&lt;TeamEntity, Long&gt; {
    @Lock(value = LockModeType.OPTIMISTIC)
    @Query(&quot;select t &quot; +
            &quot;from TeamEntity t &quot; +
            &quot;where t.id =:teamId&quot;)
    Optional&lt;TeamEntity&gt; findByIdWithOptimisticLock(@Param(&quot;teamId&quot;) Long teamId);</code></pre>
<p>현재는 팀을 조회하고 5명이 넘었는지 확인하는 과정에 있다.
따라서 JPQL을 위처럼 작성했다.</p>
<h2 id="결과-1">결과<img src="https://velog.velcdn.com/images/black_han26/post/4d31a6dd-ae13-400f-9b03-78b9cd06d10d/image.png" alt=""></h2>
<p>정상적으로 5명이 가입된 것을 알 수 있다.
다음 시간에는 비관적 락을 사용해서 처리해본다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 카운트 다운(Lv 3)]]></title>
            <link>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%B9%B4%EC%9A%B4%ED%8A%B8%EB%8B%A4%EC%9A%B4Lv-3</link>
            <guid>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%B9%B4%EC%9A%B4%ED%8A%B8%EB%8B%A4%EC%9A%B4Lv-3</guid>
            <pubDate>Fri, 31 May 2024 09:10:22 GMT</pubDate>
            <description><![CDATA[<p>[문제 링크_카운트 다운]
(<a href="https://school.programmers.co.kr/learn/courses/30/lessons/131129">https://school.programmers.co.kr/learn/courses/30/lessons/131129</a>)</p>
<h2 id="첫-문제접근">첫 문제접근</h2>
<p>나올 수 있는 점수를 모두 생각했다. 이 문제의 경우에는 1~20 그리고 2의 배수들, 3의 배수들, 50이 있다.
해당 점수들로 target을 만들 수 있는 조합은 굉장히 많다.
거기서 가장 적은 수로 조합하고, 조합한 화살의 개수가 같다면 Single이나 50점을 많이 쓴 경우를 출력한다. </p>
<p>만약 이마저도 같다면, 먼저 던진 선수가 승리한다는데, 이 조건은 필요 없다. 
어차피 우리가 구해야 하는 것은 가장 적은 개수의 조합으로 target을 완성했을 때 single과 50점을 몇 개나 맞췄는지를 구하는 것이다. </p>
<p> <strong>시간복잡도</strong> </p>
<ul>
<li>1 ≤ target ≤ 100,000</li>
</ul>
<p>최대 Nlog(N)의 시간복잡도로 접근해야 한다.
해당 문제는 target을 달성해도 또 조건이 있기 때문에 greedy로는 풀 수 없다.
최선의 경우의 수가 target이 변할 때마다 최적의 조건으로 변하기 때문에 DP를 생각해 볼 수 있다.
그러나 나는 여기까지 밖에 생각을 못했다. 구현하기가 너무나 어려웠음. 
다른 풀이를 참고했다. </p>
<h2 id="문제-풀이">문제 풀이</h2>
<ol>
<li>DP를 이용해서 역순으로 target을 줄여가며 0이 될 때까지 최적의 경우를 찾아간다.</li>
<li>50점이 가능한 경우, Single이 가능한 경우, 두 배, 세 배 점수를 맞춰서 가능한 경우를 나눈다.
2-1. 50점과 Single을 최대한 써야 하기 때문에 if를 앞단에 배치한다.</li>
<li>50점 또는 Single이 더 많은 경우 DP값을 갱신한다. </li>
</ol>
<p>아래 주석으로 설명하겠다.</p>
<pre><code class="language-python">def solution(target):
    answer = []

    # 이렇게 쓴 이유는 아래 설명
    dp = [[target-i, target-i] for i in range(target+1)]

    # 목표 점수에서 0점까지 역순으로 탐색
    for i in range(target, -1, -1):
        # 1점부터 20점까지 점수를 고려
        for j in range(20, 0, -1):

            # 50점을 쏠 수 있는 경우
            if i-50 &gt;= 0:
                # 50점을 쐈을 때 화살 개수가 더 적다면 갱신
                if dp[i-50][0] &gt; dp[i][0]+1:
                    dp[i-50] = [dp[i][0]+1, dp[i][1]+1]

                # 더 많이 쏜 불 또는 싱글 수로 갱신
                elif dp[i-50][0] == dp[i][0]+1 and dp[i-50][1] &lt; dp[i][1]+1:
                    dp[i-50] = [dp[i][0]+1, dp[i][1]+1]

            # 싱글을 쏠 수 있는 경우
            if i-j &gt;= 0:
                # 더 적은 화살 수로 갱신
                if dp[i-j][0] &gt; dp[i][0]+1:
                    dp[i-j] = [dp[i][0]+1, dp[i][1]+1]

                # 더 많이 쏜 불 또는 싱글 수로 갱신
                elif dp[i-j][0] == dp[i][0]+1 and dp[i-j][1] &lt; dp[i][1]+1:
                    dp[i-j] = [dp[i][0]+1, dp[i][1]+1]
            else:
                # 남은 점수가 단일 점수보다 작으면 더 이상 진행하지 않음
                break

            # 더블 을 쏠 수 있는 경우
            if i-2*j &gt;= 0:
                # 더 적게 쏴서 도달할 수 있을 때
                if dp[i-2*j][0] &gt; dp[i][0]+1:
                    dp[i-2*j] = [dp[i][0]+1, dp[i][1]]

                # 같은 수를 쐇지만, 불 또는 싱글을 더 많이 맞출때
                elif dp[i-2*j][0] == dp[i][0]+1 and dp[i-2*j][1] &lt; dp[i][1]:
                    dp[i-2*j] = [dp[i][0]+1, dp[i][1]]

            # 트리플 을 쏠 수 있는 경우
            if i-3*j &gt;= 0:
                # 더 적게 쏴서 도달할 수 있을 때
                if dp[i-3*j][0] &gt; dp[i][0]+1:
                    dp[i-3*j] = [dp[i][0]+1, dp[i][1]]

                # 같은 수를 쐇지만, 불 또는 싱글을 더 많이 맞출때
                elif dp[i-3*j][0] == dp[i][0]+1 and dp[i-3*j][1] &lt; dp[i][1]:
                    dp[i-3*j] = [dp[i][0]+1, dp[i][1]]

    # 목표 점수에 도달하기 위한 최소 화살 수와 최대 불 또는 싱글 수 반환
    return dp[0]

</code></pre>
<hr>
<h3 id="코드해석">코드해석</h3>
<p>본인은 저 코드만 보고 이해하기 어려웠다.. 좀 더 해석을 덧붙이겠다.</p>
<p><code>dp = [[target-i, target-i] for i in range(target+1)]</code>
이렇게 작성한 이유는 DP를 뒤에서부터 진행하기 때문이다.</p>
<p>dp[i]의 인덱스 i는 현재 점수를 나타낸다.
예를 들어
dp[100]은 목표 점수 100에 도달하는 경우를 나타내고, 
dp[50]은 목표 점수 50에 도달하는 경우를 나타낸다.</p>
<p>target이 100이라면 </p>
<pre><code># dp[100] = [0, 0]  # 100점에 도달하기 위한 초기값 (화살 0개, 싱글 0개)
# dp[99] = [1, 1]   # 99점에 도달하기 위한 초기값 (화살 1개, 싱글 1개)
# dp[98] = [2, 2]   # 98점에 도달하기 위한 초기값 (화살 2개, 싱글 2개)</code></pre><p>자 여기까지 이해됐다면, 아래 코드까지만 해석해보겠다. 아래가 이해된다면 모든 게 끝</p>
<pre><code class="language-python"> for i in range(target, -1, -1):
        # 1점부터 20점까지 점수를 고려
        for j in range(20, 0, -1):

            # 50점을 쏠 수 있는 경우
            if i-50 &gt;= 0:
                # 50점을 쐈을 때 화살 개수가 더 적다면 갱신
                if dp[i-50][0] &gt; dp[i][0]+1:
                    dp[i-50] = [dp[i][0]+1, dp[i][1]+1]

                # 더 많이 쏜 불 또는 싱글 수로 갱신
                elif dp[i-50][0] == dp[i][0]+1 and dp[i-50][1] &lt; dp[i][1]+1:
                    dp[i-50] = [dp[i][0]+1, dp[i][1]+1]</code></pre>
<p>여기서 </p>
<pre><code class="language-python">if dp[i-50][0] &gt; dp[i][0]+1:
    dp[i-50] = [dp[i][0]+1, dp[i][1]+1]</code></pre>
<p>이부분이 이해가 잘 안됐다. 여기서 주의해야 할 점은 dp[i-50][0]이 50점을 쐈을 때 화살의 개수가 아니다. dp[i][0]+1이 50점을 쐈을 때 화살의 개수이다.</p>
<p>i가 100일 때를 생각해보자
<code>dp[50][0] &gt; dp[100][0]+1</code> 을 비교하게 된다. 
처음 dp[50][0] = 50으로 세팅되었고, d[100][0] = 0이다.
그러니 50점을 맞췄다고 가정했을 때 화살의 개수는 d[100][0] + 1 이 된다. 
이것은 1이고 dp[50][0]보다 작으니 <code>dp[i-50] = [dp[i][0]+1, dp[i][1]+1]</code> 처럼 갱신한다. </p>
<p>아래 코드들도 그런 식이다. . </p>
<h3 id="후기">후기</h3>
<p>솔직히 처음에 코드를 보고 이해가 어려웠다.
어떻게든 이해해보고 싶었는데, 설명할 수 있다면 이해했다고 할 수 있지 않을까 싶어 작성했다. 한 명이라도 도움이 되었다면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데드락의 의미와 발생 원인 및 해결 방안]]></title>
            <link>https://velog.io/@black_han26/%EB%8D%B0%EB%93%9C%EB%9D%BD%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%99%80-%EB%B0%9C%EC%83%9D-%EC%9B%90%EC%9D%B8-MySQL-Deadlock</link>
            <guid>https://velog.io/@black_han26/%EB%8D%B0%EB%93%9C%EB%9D%BD%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%99%80-%EB%B0%9C%EC%83%9D-%EC%9B%90%EC%9D%B8-MySQL-Deadlock</guid>
            <pubDate>Mon, 27 May 2024 12:37:28 GMT</pubDate>
            <description><![CDATA[<p>저번 시간에 동시성 제어 기법의 종류들을 알아봤다.
Lock을 하다 보면 데드락이 발생하는 경우가 있다. 
이번에는 데드락에 대해서 알아보자</p>
<blockquote>
<h1 id="데드락이란-">데드락이란 ?</h1>
<p>교착 상태(膠着狀態) 또는 데드락(영어: deadlock)은 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태</p>
</blockquote>
<p>쉽게 그림을 이용해 설명하겠다.</p>
<p>회사에서 코딩테스트를 치뤘다.
총 두 문제가 출제되었고, 이 회사에 합격하려면 두 문제를 모두 맞춰야 한다. </p>
<p>A는 1번 문제의 정답 코드를 알고 있다.
B는 2번 문제의 정답 코드를 알고 있다.
<img src="https://velog.velcdn.com/images/black_han26/post/c22a4d70-6ac0-410f-9f34-8fc40f9fc7c4/image.png" alt="">상대가 답안 제출을 끝내고 퇴실하면, 상대가 쓴 코드를 몰래 볼 수 있다.</p>
<p>당신 같으면 먼저 갈 것인가? 
아마 서로 먼저 가길 눈치만 보다가 시험이 종료되고 집에 가서 울게 될 것이다.</p>
<p>그럼 이게 왜 발생하는데 ? 를 생각해 볼 수 있다. 
데드락의 발생조건은 네 가지가 있다. 이 네 가지가 모두 만족되어야 데드락이 발생한다. </p>
<h2 id="데드락의-필요조건-4가지">데드락의 필요조건 4가지</h2>
<ul>
<li><strong>비선점</strong>
이미 할당된 자원을 강제로 빼앗을 수 없다.
상대의 답안지를 강제로 뺏을 수 없던 것처럼 말이다.</li>
<li><strong>점유와 대기</strong>
다른 프로세스에 할당된 자원을 점유하기 위해 대기해야 한다.
상대의 답안지를 보기 위해 다 쓰고 제출할 때까지 기다려야 했다.</li>
<li><strong>순환 대기</strong>
대기 프로세스의 집합이 순환 형태로 자원을 대기한다.
A는 B의 답안을 기다리고, B는 A의 답안을 기다리는 형태이다.</li>
<li><strong>상호 배제</strong>
자원은 한 번에 한 프로세스만 사용할 수 있다.
위 예시에서는 한 명이 하나의 답안만 아는 상황을 가정했다. </li>
</ul>
<h2 id="데드락-해결-방안">데드락 해결 방안</h2>
<p>그렇다면 데드락 조건의 4가지를 어기면 되는 거 아닌가? 를 생각해 볼 수 있다.
이 방식은 교착 상태 예방에 해당하는 방법이고, 데드락 해결 방안은 크게 4가지가 있다.</p>
<ul>
<li><strong>교착 상태 예방</strong>
<code>필요조건을 거부하는 방법</code> </li>
</ul>
<ol>
<li>비선점 조건을 거부 - 프로세스가 강제로 자원을 잃을 수 있어, 작업의 일관성이 깨질 수 있다.</li>
<li>점유와 대기 조건을 거부 - 모든 자원을 한꺼번에 요청하면 다른 프로세스가 자원을 얻기 어려워져서 자원의 효율적인 사용이 어려워진다. </li>
<li>상호 배제 조건 거부 - 자원을 여러 프로세스가 동시에 사용할 수 있게 되는데, 이는 실제로 불가능하거나, 데이터의 무결성을 해칠 수 있다.</li>
<li>순환 대기 조건 거부 - 자원의 순서를 정하는 데 복잡성이 증가하고, 순서에 따라 요청이 제한되어 비효율적일 수 있다.</li>
</ol>
<p>결론적으로 자원 사용 효율성이 떨어지고 비용이 많이 드는 단점이 있기에 실제로 잘 사용되지 않는다.</p>
<ul>
<li><p><strong>교착 상태 회피</strong>
<code>데드락이 발생할 위험이 있으면 진작에 회피하여 방지하는 방법</code>
자원 할당시, 교착 상태로부터 안전한 경우에만 자원을 할당함으로써 예방한다.
여기서는 주로 은행원 알고리즘을 사용한다. </p>
<p>시스템 자원의 사용을 최적화할 수 있지만, 자원을 요청할 때마다 계산을 해야 하므로 복잡도가 높고, 얼마의 자원을 필요로 하는지 알아야 하는데 동적 자원환경에서 유연성이 떨어진다.</p>
</li>
<li><p><strong>교착 상태 탐지 및 회복</strong> 
<code>교착 상태가 발생할 가능성을 허용하고, 교착 상태가 발생하면 이를 탐지하고 회복하는 방법</code></p>
</li>
<li><p><em>탐지 *</em>방법으로는 자원 할당 그래프를 통해 순환 사이클의 존재를 확인한다. 순환 사이클의 존재를 교착 상태 발생으로 간주한다. 그렇게 주기적으로 자원의 상태와 프로세스의 상태를 검사하여 교착 상태가 발생했는지 확인</p>
<p>단점으로 자원의 상태를 지속적으로 모니터링해야하기 때문에 오버헤드가 커질 수 있다.</p>
<p><strong>회복</strong>으로는 교착 상태에 있는 프로세스 중 일부를 종료하여 자원을 해제하는 방법이 있다. 이때 먼저 종료할 프로세스의 우선순위를 두어 자원을 잃을 수 있는 단점을 최소화한다. </p>
<p>또한 자원을 선점하는 방식이 있다. 특정 프로세스에서 자원을 강제로 회수하여 다른 프로세스에 할당한다.
이때는 기아상태를 조심해야 한다. 매번 자원을 회수 당하기만 해서 처리가 되지 않는 상태가 발생할 수 있다.</p>
</li>
</ul>
<p>교착 상태가 자주 발생하는 곳에서 사용되며, 실제로 가장 많이 사용되는 기법이다.</p>
<ul>
<li><strong>교착 상태 무시</strong>
<code>교착 상태를 해결하는 것은 비용 문제를 일으킨다고 판단하고 특별한 방법을 사용하지 않는다.</code>
만약 교착 상태 발생시에는 시스템을 재부팅 시키는 방식으로 해결 
교착 상태가 자주 발생하지 않는 시스템에서 사용된다.</li>
</ul>
<p>이렇게 데드락에 대해서 알아보았다. 
👋개념 정리를 했으니 다음에는 MySQL을 사용한 프로젝트에 적용한 것을 소개하겠다.👋</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성 제어 기법 낙관적 락, 비관적 락]]></title>
            <link>https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-%EA%B8%B0%EB%B2%95-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD</link>
            <guid>https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-%EA%B8%B0%EB%B2%95-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD</guid>
            <pubDate>Fri, 24 May 2024 08:34:50 GMT</pubDate>
            <description><![CDATA[<p>저번 시간에 동시성 제어의 배경과 하지 않았을 때의 문제점을 살펴보았다. 
이번에는 동시성 제어 기법의 종류를 설명하고자 한다.</p>
<p>동시성 제어에는 Lock을 사용한 방법이 있다. 이번 시간에는 낙관적 락과 비관적 락에 대해서 알아본다.</p>
<h1 id="낙관적-lock">낙관적 Lock</h1>
<p>충돌하지 않을 것이라고 보고 진행한다. 
그렇다면 어떻게 충돌을 방지할까?</p>
<h2 id="방식">방식</h2>
<p>버전(Version)을 사용한다. 해당 데이터의 버전을 읽고, 데이터를 Commit하기 전에도 다시 버전을 읽는다.
이 두 버전이 일치할 때 비로소 Commit을 한다.</p>
<p>대략적 이미지는 아래와 같다.
<img src="https://velog.velcdn.com/images/black_han26/post/4404fcd7-9898-41ee-935e-6e2abf56f170/image.png" alt=""></p>
<p>이렇게 하면, 누군가가 동시에 데이터를 수정했을 때 먼저 수정한 사람의 Commit으로 인해 버전이 변경된다. 따라서 두 번째로 데이터를 수정하려는 사람은 처음 읽었을 때의 버전과 Commit 전에 읽은 버전이 달라진 것을 확인하고, 충돌이 발생할 뻔했다는 것을 인지하게 되어 롤백을 한다.</p>
<h2 id="낙관적-락을-쓰는-경우">낙관적 락을 쓰는 경우</h2>
<ul>
<li>충돌 가능성이 낮은 경우 - 데이터 수정이 드문 시스템에서 효과적이다. (읽는 작업이 많은 경우 ex 게시판)</li>
<li>성능이 중요한 경우 - 충돌이 발생하지 않는다는 가정하에 트랜잭션을 유지할 필요가 없어 성능이 비관적 락보다 좋다.</li>
<li>비동기 처리를 할 경우 
예를 들면 영화 티켓을 예매한다고 생각해보자. </li>
</ul>
<ol>
<li>클라이언트는 서버에 잔여석 정보를 요청한다.</li>
<li>클라이언트는 잔여석 정보를 받고, 정보를 입력하며 결제 준비를 한다. 이후 결제 요청</li>
<li>서버는 처음 버전과 결제 요청시 버전을 확인하기만 하면 된다.
=&gt; 클라이언트가 데이터를 받고 수정 후 재전송 할 때까지 트랜잭션을 유지할 필요도 없고, 비동기적으로 처리할 수 있다. 이는 데드락을 피하는 데 유리하다.</li>
</ol>
<h3 id="낙관적-락의-단점">낙관적 락의 단점</h3>
<ul>
<li>실패시 롤백 처리가 복잡도가 올라간다.</li>
<li>충돌이 일어난 경우 재시도를 해야 한다. 수정이 빈번하게 일어나는 시스템에서는 계속 버전이 변경될 수 있어, 재시도가 반복될 수 있다. </li>
</ul>
<h1 id="비관적-lock">비관적 Lock</h1>
<p>충돌할 걸 예상하고 락을 걸어서 진행하는 방식이다.
락을 걸고 데이터를 수정하는 동안, 다른 사용자는 자원을 이용할 수 없기에 대기를해야 한다.</p>
<p>여기에는 배타락(Exclusive lock)과 공유락(Shared lock)이 있다.</p>
<h2 id="1-배타-lock">1. 배타 Lock</h2>
<p>흔하게 생각해 볼 수 있는 방식으로, 한 트랜잭션이 진행되는동안 다른 트랜잭션이 접근을 못하게 Lock을 걸어둔다.
이는 전용 lock이라고도 하며, Lock이 걸리는 순간 다른 트랜잭션의 읽기와 쓰기는 불가능하다.  Commit이 끝난 뒤 Lock이 풀리면 다음 트랜잭션이 권한을 가져간다.</p>
<h2 id="2-공용-lock">2. 공용 Lock</h2>
<p>read연산은 가능하고 write연산은 불가능 하게 끔 한다.
한 트랜잭션이 공유 락을 걸면 다른 트랜잭션도 동일한 데이터에 공유 락을 걸 수 있지만, 배타 락은 걸 수 없다.</p>
<p>우리가 여기서 알아야 할 점은** 로킹 단위(Loking Granularity)**이다.</p>
<h3 id="로킹-단위loking-granularity">로킹 단위(Loking Granularity)</h3>
<p>즉 어떤 범위로 묶어서 Lock을 걸 것이냐 ? 라는 의미인데</p>
<p>크다 --------------------------------&gt; 작다
데이터베이스, 릴레이션, 튜플, 속성 단위로 가능하다.</p>
<p>로킹 단위가 커질수록 구현이 쉽고 (그냥 대충 한번에 묶어버리면 되니깐_) 
병행성 수준이 낮아진다. 때문에 로킹 오버헤드 감소</p>
<p>로킹 단위가 작아지면 병행성 수준이 높아져 여러 트랜잭션이 동시에 진행될 수 있지만, 관리가 어렵고 락 오버헤드가 증가</p>
<p>이렇게 종류를 알아보았다.
그렇다면 언제 낙관을 쓰고 언제 비관을 쓰는가? </p>
<h2 id="비관적-락을-쓰는-경우">비관적 락을 쓰는 경우</h2>
<ul>
<li>수정하는 일이 빈번한 경우 - 낙관적 락의 단점을 처리할 수 있다.</li>
<li>실시간으로 데이터를 처리해야 하는 시스템에서 사용</li>
<li>데이터 무결성이 매우 중요한 경우 - 갱신 손실(Lost Update) 문제를 해결하여 데이터의 정합성을 유지한다.</li>
</ul>
<h3 id="비관적-락의-단점">비관적 락의 단점</h3>
<ul>
<li>동시에 접근하는 트랜잭션이 많아지면 대기 시간이 늘어나고, 시스템 성능이 저하된다.</li>
<li>락이 걸려 있는 동안 다른 트랜잭션이 대기해야 하므로 병행성이 떨어진다.</li>
</ul>
<p>👋다음 시간에는 이로 인해 발생하는 데드락이 무엇인지 알아본다.👋</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스Lv3/Python] 등대]]></title>
            <link>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Lv3Python-%EB%93%B1%EB%8C%80</link>
            <guid>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Lv3Python-%EB%93%B1%EB%8C%80</guid>
            <pubDate>Fri, 24 May 2024 03:39:49 GMT</pubDate>
            <description><![CDATA[<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/133500#">등대</a>
트리를 이용해 보는 문제이다.
풀이를 봐도 잘 이해가 가지 않아서 다시 정리해보려고 한다.</p>
<h3 id="첫-문제접근">첫 문제접근</h3>
<p> <strong>시간복잡도</strong> </p>
<ul>
<li>2 ≤ n ≤ 100,000</li>
</ul>
<p>이정도의 시간 복잡도에서는 이중반복문을 사용하면 안 된다는 생각이 들었다. 그래서 내가 생각한 풀이는 딕셔너리를 사용하여 연결을 나타냈다.</p>
<p><em><strong>여기서 leaf 노드란 자식 노드가 없는 노드를 말한다.</strong></em></p>
<h3 id="💥-그러나-놓쳤던-점-주의해야-할-점❗">💥 그러나.. 놓쳤던 점, 주의해야 할 점❗</h3>
<p>내가 문제를 잘 못 이해했었다..
위처럼 하게 되면<img src="https://velog.velcdn.com/images/black_han26/post/c727c8e2-8334-49b2-89df-84e8840688b1/image.png" alt="">이게 성립되는데, 이 그림에서 배는 지나갈 수가 없다. 3과 4 그리고 6과 7이 끊어져 있는 상태이기 때문이다. 이 그림은 아래처럼 변해야 한다.<img src="https://velog.velcdn.com/images/black_han26/post/624e5f6e-b824-4c53-b86d-f2e2ab4655a9/image.png" alt=""><strong>이게 문제에서 말하는 것이었다. 최소, 하나 걸러 하나는 켜져 있어야 한다.</strong></p>
<h2 id="문제-풀이">문제 풀이</h2>
<ol>
<li>딕셔너리를 이용해 연결 정보를 넣어둔다.</li>
<li>DFS를 이용해서 현재 노드가 켜졌을 때와 꺼졌을 때 두 개의 값을 반환한다.</li>
</ol>
<p>아래 주석으로 설명하겠다.</p>
<pre><code class="language-python">from collections import defaultdict
import sys
sys.setrecursionlimit(1000001)

visited = [False] * 1000001 
nodes = defaultdict(list) # 기본값이 빈 리스트인 딕셔너리

def dfs(now):
    visited[now] = True  # 현재 노드 방문처리
    if nodes[now]==[]: # 본인이 leaf(자식 노드가 없는 노드)라면
        return 1, 0 #본인이 켜졌을 때와 꺼졌을 때 두 개의 값 반환

    # leaf가 아니라면 (자식 노드가 하나 이상 있는 경우)
    on, off = 1, 0  # 시작 값을 켰을 때와 껐을 때, 두 개로 나눠서 출발

    for nd in nodes[now]: # 자식 노드를 둘러본다
        if not visited[nd]:
        # 자식 노드 nd를 켰을 때와 껐을 때의 최소 점등 등대 개수
          child_on, child_off = dfs(nd) 

          # 내가 켜졌다면 자식 노드들은 켜지든 꺼지든 상관 없음 
                  #-&gt; 자식 노드가 켜진 경우와 꺼진 경우 중 최소값을 더함
          on += min(child_on, child_off)

          # 내가 꺼졌다면 자식 노드들은 무조건 켜져야 함 
                  #-&gt; 자식 노드가 켜진 경우의 값을 더함
          off += child_on

    return on, off  # 현재 노드를 켰을 때와 껐을 때의 최소 점등 등대 개수를 반환

def solution(n, lighthouse):
    for a, b in lighthouse:
        #연결 정보 생성
        nodes[a].append(b)
        nodes[b].append(a)

    on, off = dfs(1)  # 1번 노드에서 DFS를 시작
    return min(on, off)  # 켰을 때와 껐을 때의 최소 점등 등대 개수 중 작은 값을 반환</code></pre>
<h3 id="알게-된-것">알게 된 것</h3>
<p>딕셔너리 초반에 항상 아래처럼 선언하고 get( )을 사용하여 빈 값의 키를 넣어주곤 했다. </p>
<pre><code class="language-python">    dict={}
    for a,b in lighthouse:
        if not dict.get(a):
            dict[a]=0
        if not dict.get(b):
            dict[b]=0
        dict[a]+=1
        dict[b]+=1</code></pre>
<p>defaultdict(list)를 이용해서 바로 빈 리스트의 값을 넣어줄 수 있다는 것을 알게 되었다. 좀 더 깔끔한 코드 구현이 가능할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성 제어( Concurrency Control ) 이해를 쉽게 해보자]]></title>
            <link>https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-Concurrency-Control-%EC%9D%B4%ED%95%B4%EB%A5%BC-%EC%89%BD%EA%B2%8C-%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@black_han26/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-Concurrency-Control-%EC%9D%B4%ED%95%B4%EB%A5%BC-%EC%89%BD%EA%B2%8C-%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 19 May 2024 18:38:08 GMT</pubDate>
            <description><![CDATA[<p>동시성 제어를 정리해 본다.</p>
<blockquote>
<h1 id="동시성-제어란">동시성 제어란?</h1>
<p>동시에 실행되는 여러 개의 트랜잭션이 작업을 성공적으로 마칠 수 있도록 트랜잭션의 실행 순서를 제어하는 기법</p>
</blockquote>
<p>의미가 잘 와 닿지 않는다. 배경을 알아보자 </p>
<h2 id="배경">배경</h2>
<p>예시를 들어본다. 만약 영화 표를 예약한다고 생각해보자.
<img src="https://velog.velcdn.com/images/black_han26/post/857dd5bc-b402-4949-9fee-d1e9518e9ee4/image.png" alt="">자리가 1개가 남았다. 그런데 만약 여러 명이 동시에 해당 자리를 예약한다면 어떤 현상이 일어날까? 
당연히 가장 먼저 예매한 사람이 K24 번 자리를 갖게 될 것이다.
그러나 컴퓨터 시스템에서는 그렇지 않을 수 있다.
우리는 데이터를 변경하고 저장할 때 DB에 자료를 읽어와 수정하고 Commit 과정을 거치게 된다.
쉽게 말해, A가 데이터를 읽어오는 과정에서는 자리가 있었는데, 그 사이에 누군가가 예약한 줄도 모르고 예약이 진행된다면 영화관에서는 싸움이 일어날 것이다.</p>
<p>이제 다시 동시성 제어의 의미를 살펴보면 이해가 간다.
<strong>트랜잭션들이 동시에 수행될 때</strong> -&gt; 여러명이 동시에 예약할 때
<strong>일관성을 해치지 않도록</strong> -&gt; 여러 예약이 동시에 발생해서 일관성이 해쳐지지 않도록
<strong>트랜잭션의 실행 순서를 제어</strong> -&gt; 데이터 접근을 제어하여 충돌을 방지</p>
<p><em>이렇게 동시성 제어를 하지 않았을 때 생기는 일례를 들었다.</em></p>
<h3 id="동시성-제어를-하지-않았을-때-문제점">동시성 제어를 하지 않았을 때 문제점</h3>
<p> 굳이 구분하자면 크게 4가지가 있다.
<img src="https://velog.velcdn.com/images/black_han26/post/55dfa8dd-2e33-451f-9f16-66ab8ca89287/image.png" alt="">
이것도 너무 복잡해 보인다.. 예시로 설명해 본다.</p>
<p><strong>갱신 손실</strong>이 위의 영화관 예시이다. 자리가 예매되어 잔여석 = 0인 데이터가 갱신되기 전에, 두 트랜잭션이 동시에 잔여석 = 1을 읽고 수행하여 생긴 문제이다. </p>
<p><strong>현황파악 오류</strong>는 A가 아직 예약을 완료하진 않았는데, 중간 데이터를 읽어온 B는 잔여석 = 0으로 판단하여 생기는 오류이다. A가 아직 Commit을 하지 않아서 Rollback을 하게 되어도, B는 진작에 포기해서 예약을 하지 않을 것이다. 이는 잘 일어나지 않는 오류라고 한다.</p>
<p><strong>모순성</strong>
A가 한 좌석을 2만 원으로 예약한다. 그런데 결제 과정에서 영화관이 영화 값을 반값으로 할인하여 데이터를 처리한다고 가정한다. 이 경우 영화관의 데이터베이스에는 1명 * 10,000원으로 계산하여 1만 원의 수입이 기록된다. 그러나 실제로 A가 2만 원을 지불했기 때문에, 계좌를 확인하면 2만 원이 입금된 것을 확인할 수 있다. </p>
<p>이러한 상황에서 영화관의 데이터베이스는 실제 수입과 기록된 수입이 일치하지 않는 모순된 상태가 된다.</p>
<p><strong>연쇄복귀</strong>
A사가 5천만 원을 주고 영화관을 통째로 대관했다. 기분이 좋은 영화관은 일주일 전부터 A사 직원들에게 팝콘과 음료 50% 이벤트를 진행했다. 그런데 A사가 코로나 발생을 이유로 계약을 파기(Rollback 요청)했다.
이렇게 되면 영화관도 이벤트를 철회해야 하고, 이를 통해 할인을 받은 고객들에게는 추가 요금을 청구할 수도 있다. 이는 굉장히 까다롭기도 하고 실제로 불가능할지도 모른다.</p>
<p>이처럼 Rollback 과정에서 이미 Commit 된 것이 있다면 회복 불능이 발생할 수도 있다. </p>
<p>👋 다음시간에는 동시성 제어 기법이 무엇이 있는지를 파악해 본다.👋</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/Python] 디펜스 게임]]></title>
            <link>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EB%94%94%ED%8E%9C%EC%8A%A4-%EA%B2%8C%EC%9E%84</link>
            <guid>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EB%94%94%ED%8E%9C%EC%8A%A4-%EA%B2%8C%EC%9E%84</guid>
            <pubDate>Fri, 17 May 2024 05:50:15 GMT</pubDate>
            <description><![CDATA[<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/142085">디펜스 게임</a><img src="https://velog.velcdn.com/images/black_han26/post/3dc3fa4d-4799-4183-8225-2f87bee44779/image.png" alt="">드디어 heap을 사용해 볼 기회가 찾아왔다.. </p>
<h3 id="문제접근">문제접근</h3>
<p>가장 먼저 생각해 볼 수 있는 방법은, 배열을 돌면서 무적권을 쓸지 정면 돌파를 할지 경우의 수를 나누는 방법이다.
이를 위해서 고민할 것은 <strong>시간복잡도</strong>.. </p>
<ul>
<li>1 ≤ n ≤ 1,000,000,000</li>
<li>1 ≤ k ≤ 500,000</li>
<li>1 ≤ enemy의 길이 ≤ 1,000,000</li>
<li>1 ≤ enemy[i] ≤ 1,000,000
<img src="https://velog.velcdn.com/images/black_han26/post/cca84653-c4e5-48bc-a6a2-6b103c2e0c4c/image.png" alt=""></li>
</ul>
<p>보다시피 enemy의 길이가 너무 크다..2 가지의 경우를 나눠서 DFS 해주기엔 시간복잡도가 2^1,000,000이 나오게 된다..
그렇다면 최소 Nlog(N)의 복잡도를 사용해야 한다.</p>
<p>반복문을 한 번만 돌아한다는 말인데..여기서 고민 끝에 내린 결론이 heap을 사용하자였다.</p>
<ol>
<li>enemy 반복문을 돈다.</li>
<li>enemy[i]가 n보다 작으면 맞서 싸운다. (answer+=1)</li>
<li>enemy[i]가 n보다 크면 무적권을 쓴다.
(1) 이 전에 맞서 싸운 enemy중에, 더 큰 enemy가 있다면 그때 enemy에게 무적권을 쓴걸로 치자 ! (이를 위해 heappop을 쓴다)
(2) 더 큰 enemy가 없다면, 지금 쓴다.
(3) 첫 싸움이면 무조건 무적권을 쓴다.(무적권 없으면 끝 answer 출력)</li>
</ol>
<h3 id="내가-쓴-풀이">내가 쓴 풀이</h3>
<p>이해를 돕고자 리팩토링을 안 했습니다..위에 과정 그대로 해설 시작
<strong>참고로 여기서는 MaxHeap을 쓰고자, (-)를 곱해서 heappush를 했다.</strong></p>
<pre><code class="language-python">import heapq
def solution(n, k, enemy):
    answer = 0
    h=[] # heap배열
    for i in enemy: # enemy 반복문을 돈다.
        if n&gt;=i: # n보다 작으면 맞서 싸움

            n-=i
            heapq.heappush(h,-i) # 기록한다.
            answer+=1

        elif n &lt; i and k != 0: # n보다 크면 무적권을 씀

            if h and -h[0]&gt;=i: # 이 전에 맞서 싸운 더 큰 enemy가 있다면
                a=-heapq.heappop(h)
                n+=a # 그때 무적권을 썼던 것으로 친다.
                k-=1
                n-=i
                heapq.heappush(h,-i)
                answer+=1
            else: # 이 전에 맞서 싸운 더 큰 enemy가 없다면
                k-=1 # 지금 무적권을 쓴다.
                answer+=1

        elif n &lt; i and k==0: return answer #무적권 없으면 끝 
    return answer</code></pre>
<p><img src="https://velog.velcdn.com/images/black_han26/post/1d88d6c8-716e-4b83-be92-d24f64c043f9/image.png" alt=""></p>
<h3 id="검색-풀이">검색 풀이</h3>
<pre><code class="language-python">from heapq import heappop, heappush

def solution(n, k, enemy):
    answer, sumEnemy = 0, 0
    heap = []

    for e in enemy:
        heappush(heap, -e)  # 현재 적을 힙에 추가
        sumEnemy += e       # 현재까지의 적의 합을 더함
        if sumEnemy &gt; n:    # 적의 합이 n을 초과하면
            if k == 0:      # k가 0이면 더 이상 처리 불가, 루프 종료
                break
            sumEnemy += heappop(heap)  # 가장 큰 적을 되살림으로써 합을 줄임
            k -= 1         # k를 하나 소모
        answer += 1        # 적을 처리했으므로 answer를 증가
    return answer          # 최종 처리한 적의 수 반환
</code></pre>
<p>처리 시간은 비슷한데 훨씬 깔끔하다. 리팩토링 된 느낌인데, 실전에서 이렇게 깔끔한 코드를 생각하는 게 관건인 것 같다.. 
그래도 저번에 <a href="https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%ED%98%B8%ED%85%94-%EB%8C%80%EC%8B%A4">호텔 대실</a> 문제를 풀면서 heap은 생각도 못 했는데, 이번 알고리즘 문제를 풀면서 heap을 어떤 식으로 활용해야 하는지 감이 온 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 딕셔너리, 자바 해시맵]]></title>
            <link>https://velog.io/@black_han26/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EC%9E%90%EB%B0%94-%ED%95%B4%EC%8B%9C%EB%A7%B5</link>
            <guid>https://velog.io/@black_han26/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EC%9E%90%EB%B0%94-%ED%95%B4%EC%8B%9C%EB%A7%B5</guid>
            <pubDate>Fri, 03 May 2024 17:58:15 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        // 딕셔너리 생성
        Map&lt;String, Integer&gt; dictionary = new HashMap&lt;&gt;();

        // 요소 추가
        dictionary.put(&quot;apple&quot;, 5);
        dictionary.put(&quot;banana&quot;, 3);
        dictionary.put(&quot;orange&quot;, 7);

        // items() 대신 entrySet() 사용하여 엔트리(키-값 쌍)의 집합 얻기
        for (Map.Entry&lt;String, Integer&gt; entry : dictionary.entrySet()) {
            System.out.println(&quot;Key: &quot; + entry.getKey() + &quot;, Value: &quot; + entry.getValue());
        }

        // keys() 대신 keySet() 사용하여 키의 집합 얻기
        for (String key : dictionary.keySet()) {
            System.out.println(&quot;Key: &quot; + key);
        }

        // values() 사용하여 값의 집합 얻기
        for (int value : dictionary.values()) {
            System.out.println(&quot;Value: &quot; + value);
        }

        // get(key) 사용하여 값 얻기
        int appleCount = dictionary.get(&quot;apple&quot;);
        System.out.println(&quot;Apple Count: &quot; + appleCount);
    }
}</code></pre>
<pre><code class="language-java">// HashMap 선언
Map map = new HashMap();
map.put(&quot;name&quot;, &quot;a&quot;);
map.put(&quot;age&quot;, 100);
map.put(&quot;name&quot;, &quot;b&quot;); // 처음 대임된 a -&gt; b로 변경 됩니다.

// 결과
// key : name, value : b
// key : age, value : 100
for (Object o : map.keySet()) {
    System.out.println(&quot;key : &quot; + o.toString() + &quot;, value : &quot; + map.get(o));
}

map.keySet();                             // key 값들을 Set 형태로 모두 불러옵니다.
map.values();                             // value에 해당하는 값들을 Collection 형태로 불러옵니다.
map.get(&quot;name&quot;);                          // key에 해당하는 값을 읽기
map.getOrDefault(&quot;address&quot;, &quot;Not Found&quot;); // key에 해당하는 값이 없을 경우 default 값을 참조
map.containsKey(&quot;email&quot;);                 // key 값이 map에 포함하는지 확인
map.remove(&quot;name1&quot;);                      // key에 해당하는 값을 제거
map.clear();                              // 모든 map 내부 데이터 제거

Map map2 = new HashMap();
map.put(&quot;name&quot;, &quot;aa&quot;);
map.put(&quot;age&quot;, 200);

// 결과
// {name=aa, age=200}
map.putAll(map2);                         // map에 map2의 값을 대입합니다.</code></pre>
<h2 id="도넛과-막대-그래프">도넛과 막대 그래프</h2>
<pre><code class="language-java">import java.util.*;

class Solution {
    public int[] solution(int[][] edges) {
        Map&lt;Integer, Integer&gt; out = new HashMap&lt;&gt;();
        Map&lt;Integer, Integer&gt; in = new HashMap&lt;&gt;();
        int[] answer = new int[4];

        for (int[] edge : edges) { // (1)
            out.put(edge[0], out.getOrDefault(edge[0], 0) + 1);
            in.put(edge[1], in.getOrDefault(edge[1], 0) + 1);
        }

        for (int node : out.keySet()) {
            if (out.get(node) &gt; 1) { // (2)
                if (!in.containsKey(node)) {
                    answer[0] = node;
                } else {
                    answer[3] += 1;
                }
            }
        }

        for (int node : in.keySet()) {
            if (!out.containsKey(node)) { // (3)
                answer[2] += 1;
            }
        }
        answer[1] = out.get(answer[0]) - answer[2] - answer[3]; // (4)
        return answer;
    }
}</code></pre>
<p>다른 풀이</p>
<pre><code class="language-java">public class HashEx {
    public int[] solution(int[][] edges) {
        int[] answer = new int[4];
        Map&lt;Integer, int[]&gt; map = new HashMap&lt;&gt;();
        for (int[] edge : edges) {
            map.put(edge[0],map.getOrDefault(edge[0],new int[]{0,0}));
            map.put(edge[1], map.getOrDefault(edge[1], new int[]{0, 0}));
            map.get(edge[0])[0]++;
            map.get(edge[1])[1]++;
        }
        for(Map.Entry&lt;Integer,int[]&gt; entry : map.entrySet()){
            int Key = entry.getKey();
            int[] val = entry.getValue();
            if (val[0] &gt;= 2 &amp;&amp; val[1] == 0) {
                answer[0] = Key;
            }
            // 막대 모양 그래프의 수 확인
            else if (val[0] == 0 &amp;&amp; val[1] &gt; 0) {
                answer[2]++;
            }
            // 8자 모양 그래프의 수 확인
            else if (val[0] &gt;= 2 &amp;&amp; val[1] &gt;= 2) {
                answer[3]++;
            }
        }
        // 도넛 모양 그래프의 수 확인
        answer[1] = map.get(answer[0])[0] - answer[2] - answer[3];
        return answer;
    }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 -> Java 알고리즘 적응하기]]></title>
            <link>https://velog.io/@black_han26/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%97%90%EC%84%9C-%EC%9E%90%EB%B0%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A0%81%EC%9D%91%ED%95%98%EA%B8%B0%EC%8B%A4%EC%A0%84-%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@black_han26/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%97%90%EC%84%9C-%EC%9E%90%EB%B0%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A0%81%EC%9D%91%ED%95%98%EA%B8%B0%EC%8B%A4%EC%A0%84-%EC%BD%94%EB%93%9C</guid>
            <pubDate>Fri, 03 May 2024 07:45:10 GMT</pubDate>
            <description><![CDATA[<p>상단에 import.java.util.* 을 먼저 추가한다.</p>
<h2 id="stack">Stack</h2>
<p>추가로는 add와 push를 사용 가능하다.
삭제로는 pop 사용</p>
<pre><code class="language-java">    public static void main(String[] args) {
        Stack&lt;Integer&gt; stk = new Stack&lt;&gt;();
        stk.push(2); // push사용 
        stk.add(3); //add 사용
        System.out.println(stk);
        stk.pop();
        System.out.println(stk);
    }
</code></pre>
<h3 id="결과">결과</h3>
<p>2와 3이 잘 들어가고, 3이 pop 된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/black_han26/post/aaba7824-0a1f-41af-b0e1-bdc0464cce12/image.png" alt=""><strong>push와 add의 차이</strong>
push()는 stack에서 제공, add()는 List에서 제공하는 메서드
push()의 리턴값은 추가한 객체이고, add()의 리턴값은 boolean이다.
Stack에서는 push()를 쓰는 것을 추천한다. 그래야 스택 사용을 알아보기 쉽다.</p>
<h2 id="deque">Deque</h2>
<p>기본적으로 ArrayDeque&lt;&gt;()를 사용한다.
스레드 세이프를 보장하진 않지만, 효율이 좋다고 한다.
자세한 내용은 <a href="https://tech-monster.tistory.com/159">해당 블로그</a>에서 알아봤다. </p>
<pre><code class="language-java">    public static void main(String[] args) {
        Deque&lt;Integer&gt; q = new ArrayDeque&lt;&gt;();
        q.addLast(3);
        q.addLast(2);
        q.addLast(1);
        System.out.println(q);
        q.removeFirst();
        System.out.println(q);
        q.removeLast();
        System.out.println(q);
    }</code></pre>
<h3 id="결과-1">결과</h3>
<p>3, 2, 1이 잘 삽입 되었고, 앞에서 3과 뒤에서 1이 잘 삭제 된 것을 볼 수 있다.<img src="https://velog.velcdn.com/images/black_han26/post/4671b46f-18de-41b7-b980-301300544a66/image.png" alt=""></p>
<hr>
<h2 id="1차원-배열-정렬">1차원 배열 정렬</h2>
<p>파이썬에서 주로 arr.sort()로 쓰이는 기능이다.</p>
<pre><code class="language-java">    public static void main(String[] args) {
        Integer[] arr = {1, 26, 17, 25, 99, 44, 303};
        Arrays.sort(arr);
        System.out.println(&quot;Sorted arr[] : &quot; + Arrays.toString(arr));
        Arrays.sort(arr, (i1, i2) -&gt; i2 - i1);
        System.out.println(&quot;Sorted arr[] : &quot; + Arrays.toString(arr));
    }</code></pre>
<p><code>Arrays.sort(arr);</code> 이건 짐작하듯 오름차순 정렬
<code>Arrays.sort(arr, (o1, o2) -&gt; o2 - o1);</code> 이게 내림차순 정렬(람다 표현식)이다.</p>
<p>사실 오름차순도<code>Arrays.sort(arr, (o1, o2) -&gt; o1 - o2);</code> 이 표현이 오름차 순인데 그냥 <code>Arrays.sort(arr);</code> 로 간단하게 쓴다.</p>
<h3 id="결과-2">결과</h3>
<p><img src="https://velog.velcdn.com/images/black_han26/post/3769b6a7-c0c0-417e-832c-750c2dfddd8e/image.png" alt=""></p>
<h2 id="2차원-배열-정렬">2차원 배열 정렬</h2>
<p>1차원 배열 정렬과 동일한데 , 몇 번째 원소로 정렬할 것인지 index를 넣어준다.</p>
<pre><code class="language-java">    public static void main(String[] args) {
        int[][] arr = {{2, 4}, {6, 3}, {1, 5}};
        Arrays.sort(arr, (o1, o2) -&gt; o2[1] - o1[1]);
        System.out.println(Arrays.deepToString(arr));
    }</code></pre>
<h3 id="결과-3">결과</h3>
<p>위 코드는 <code>o2[1] - o1[1]</code> 두번째 원소로 내림차순 정렬이다. 
<img src="https://velog.velcdn.com/images/black_han26/post/49fd00c1-896a-4aa0-ae62-60d85217adc3/image.png" alt=""></p>
<p>아래 코드를 참고하면 <code>o1[n] - o2[n]</code> 을 어떻게 써야하는지 알 수 있다.</p>
<pre><code class="language-java">Arrays.sort(arr, new Comparator&lt;int[]&gt;() {

    @Override
    public int compare(int[] o1, int[] o2) {
        return o1[0]-o2[0]; // 첫번째 숫자 기준 오름차순 
        return o2[0]-o1[0]; // 첫번째 숫자 기준 내림차순 
        return o1[1]-o2[1]; // 두번째 숫자 기준 오름차순 
        return o2[1]-o1[1]; // 두번째 숫자 기준 내림차순 
    }
</code></pre>
<h3 id="참고">참고</h3>
<p>가끔 알고리즘 문제 속에, 첫번째요소는 오름차순, 두번째 요소에 대해서는 내림 차순으로 정리해야 할 때가 있다.
아래처럼 작성한다.</p>
<pre><code class="language-java">Arrays.sort(arr, (o1, o2) -&gt; {
    if (o1[0] == o2[0]) { // 첫 번째 요소가 같을 때
        return o2[1] - o1[1]; // 두 번째 요소에 대해 내림차순 정렬
    } else {
        return o1[0] - o2[0]; // 첫 번째 요소에 대해 오름차순 정렬
    }
});
</code></pre>
<p>아래의 결과가 나온다.
<img src="https://velog.velcdn.com/images/black_han26/post/b3a95674-ae1b-4e3f-8df2-3bf3a6605a8e/image.png" alt=""></p>
<hr>
<h2 id="math-클래스">Math 클래스</h2>
<p>상단에 import java.lang.Math; 추가.
파이썬에서 쓰이던 웬만한 기능들을 사용 가능하다.
min(), max(), pow(),sqrt() 등등.. </p>
<pre><code class="language-java">    public static void main(String[] args) {
        int a = -3;
        int b = 2;
        double c = 2.6;
        System.out.println((int)Math.pow(a,b));
        System.out.println(Math.abs(a));
        System.out.println(Math.max(a,b));
        System.out.println(Math.floor(c));
    }</code></pre>
<h3 id="결과-4">결과</h3>
<p><img src="https://velog.velcdn.com/images/black_han26/post/2db8b23c-ac87-462a-9493-dfb46b1023cb/image.png" alt=""></p>
<p>출처 : <a href="https://lktprogrammer.tistory.com/114#google_vignette">https://lktprogrammer.tistory.com/114#google_vignette</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/Python] 호텔 대실]]></title>
            <link>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%ED%98%B8%ED%85%94-%EB%8C%80%EC%8B%A4</link>
            <guid>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%ED%98%B8%ED%85%94-%EB%8C%80%EC%8B%A4</guid>
            <pubDate>Mon, 29 Apr 2024 15:12:38 GMT</pubDate>
            <description><![CDATA[<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/155651">호텔 대실</a><img src="https://velog.velcdn.com/images/black_han26/post/d2e0f958-4d5e-4805-965e-5468af77a4ab/image.png" alt=""><img src="https://velog.velcdn.com/images/black_han26/post/460bfc75-ae5a-462c-aa0e-3986373fbcf0/image.png" alt=""></p>
<h2 id="1-문제-해석">1. 문제 해석</h2>
<p><strong>예약 시간을 담은 리스트를 주고, 최소한의 방을 사용하여 겹치지 않게 고객들에게 방을 내어준다.</strong>
사용한 방은 청소시간 10분을 가져야 한다.
위에 1번 예시를 살펴보면, 아래처럼 3개의 방을 사용하여 배정할 수 있다.<img src="https://velog.velcdn.com/images/black_han26/post/2c7c8f78-024c-49e9-89ac-721ffdb34336/image.png" alt=""></p>
<h2 id="2-문제-접근">2. 문제 접근</h2>
<p>book_time의 범위가 (1 ≤ N ≤ 1,000)이 주어졌고, 적은 범위이기에 DP나 브루트 포스 혹은 오래 걸리는 알고리즘까지 생각할 수 있다. 문제는 각 예약에 맞는 방을 배정하면서 겹치지 않는 또 하나의 루프를 생각해야 하는 것으로 보였다. O(N^2)까지 가능한 범위이기에 <strong>그리디</strong>을 사용했다. </p>
<h2 id="3-알고리즘-풀이">3. 알고리즘 풀이</h2>
<p>먼저 주어진 book_time을 int로 바꿔줬다.</p>
<pre><code class="language-python">for i in book_time:
        for j in range(2):
            h,m=i[j].split(&quot;:&quot;) # 시간 문자열을 시간과 분으로 분리
            i[j]=int(h)*60+int(m) # 시간과 분을 분 단위로 변환</code></pre>
<p>그리고서 생각할 수 있는 방법은 </p>
<ol>
<li>먼저 방이 아예 없으면 새로운 방을 만들어서 배정한다</li>
<li>방이 있으면 사용 가능 한지 확인한 뒤, 가능하면 배정한다.</li>
<li>사용 가능한 방도 없다면 추가적인 방을 새로 만들어야 한다. <h3 id="최종-제출-코드">최종 제출 코드</h3>
<pre><code class="language-python"></code></pre>
</li>
</ol>
<p>def solution(book_time):</p>
<pre><code>for i in book_time:
    for j in range(2):
        h,m=i[j].split(&quot;:&quot;)
        i[j]=int(h)*60+int(m)

book_time.sort()  # 예약 시간을 시작 시간에 따라 정렬
rooms=[] # 방 리스트
for book in book_time:
    if not rooms: # 맨 처음, 방이 없다면
        rooms.append(book) # 방을 만들어줌
        continue

    # 생성 된 각 방을 돔
    for index, room in enumerate(rooms):
        # 대실 시작 시각 &gt;= 퇴실 시간 + 청소시간 10분
        if book[0] &gt;= room[1]+10: 
            rooms [index] = book # 새로운 예약으로 갱신
            break
    else: # 방은 있는데 사용 불가능하다면 방 추가
        rooms.append(book) 
return len(rooms)</code></pre><pre><code>이 코드에서는 조심해야 할 점이 if- else를 쓴게 아니라, for - else 문을 이용했다. 
이는 for 루프에서 찾으려는 조건이 충족되지 않았을 때의 대안적인 동작을 제공한다.

for - else는[ 해당 게시물 ](https://velog.io/@black_han26/%EB%B0%B1%EC%A4%80Python-10972-%EB%8B%A4%EC%9D%8C-%EC%88%9C%EC%97%B4)  끝 부분에도 정리해 놓았다.

### (2) heap을 이용한 풀이
```python
from heapq import heappop, heappush

def solution(book_time):
    rooms = []
    book_time.sort(key = lambda _:_[0])
    for book in book_time :
        check_in = num(book[0])
        check_out = num(book[1]) + 10
        if len(rooms) != 0 and rooms[0] &lt;= check_in :
            heappop(rooms)
        heappush(rooms,check_out)
    return len(rooms)
</code></pre><p>사실 이 코드를 소개해보고 싶어서 글을 썼다.
heap을 이용한 알고리즘인데, 실행 결과 속도가 압도적으로 빠르다.<img src="https://velog.velcdn.com/images/black_han26/post/eadf258f-031b-42f6-a9fc-702d0fcbce4d/image.png" alt="">앞에 흐름까진 동일하고, 뒤에 방이 존재하고 사용 가능할 때 heappop그리고 heappush를 해주는 점이 다르다..</p>
<h2 id="회고">회고</h2>
<p>heap알고리즘은 생각도 못 했기에 소개하고자 글을 썼다.</p>
<p>해당 문제처럼 예약, 대기 시간 문제에서 자주 heap이 쓰이는 것은 알고 있었지만, 아직 응용 능력이 부족하여 이를 활용하지 못했다.
Heap 알고리즘은 우선순위 큐를 구현하는데 매우 유용하다. 이를 통해 데이터의 삽입, 삭제, 최솟값 또는 최댓값에 대한 조회를 빠르게 수행할 수 있다. 특히 예약 및 대기 시간과 같은 문제에서는 우선순위에 따라 데이터를 관리하는 것이 중요함을 느꼈다.</p>
<p>heap 개념에 대해서는 아래 두 게시물에서 다뤘었다.</p>
<ol>
<li><a href="https://velog.io/@black_han26/heap-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">heap 알고리즘 문제 회고</a></li>
<li><a href="https://velog.io/@black_han26/Softeer-%EA%B0%95%EC%9D%98%EC%8B%A4-%EB%B0%B0%EC%A0%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%94%94%EC%8A%A4%ED%81%AC-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC-Heap">heappush만 가능할까</a></li>
</ol>
<p>이렇게 수행시간이 차이가 날 줄은 몰랐다. 앞으로는 문제 해결에 Heap을 고려하여 보다 효율적인 알고리즘을 구현해보고자 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/Python] 도넛과 막대그래프 - 2024 Kakako 기출]]></title>
            <link>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EB%8F%84%EB%84%9B%EA%B3%BC-%EB%A7%89%EB%8C%80%EA%B7%B8%EB%9E%98%ED%94%84-2024-Kakako-%EA%B8%B0%EC%B6%9C</link>
            <guid>https://velog.io/@black_han26/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EB%8F%84%EB%84%9B%EA%B3%BC-%EB%A7%89%EB%8C%80%EA%B7%B8%EB%9E%98%ED%94%84-2024-Kakako-%EA%B8%B0%EC%B6%9C</guid>
            <pubDate>Sat, 13 Apr 2024 06:30:23 GMT</pubDate>
            <description><![CDATA[<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/258711">도넛과 막대그래프</a><img src="https://velog.velcdn.com/images/black_han26/post/2437e64f-6398-425c-a66f-200ab5547113/image.png" alt=""><img src="https://velog.velcdn.com/images/black_han26/post/ead3af65-9b5e-4014-9fe5-d864f49dcc62/image.png" alt=""></p>
<h2 id="1-문제-해석">1. 문제 해석</h2>
<p><strong>추가로 정점을 하나 생성하여 각 그래프의 특성에 따라 간선을 연결하고 번호를 부여한다. 이때 주어진 간선 정보를 바탕으로 각 그래프 형태에 대한 정보를 계산한다.</strong></p>
<p>예를 들어, 다음과 같은 간선 입력이 들어온다.<img src="https://velog.velcdn.com/images/black_han26/post/f55bb331-6b51-44d0-9581-d590654e332f/image.png" alt=""> 이 간선 정보를 가지고 그래프를 그리면 아래와 같다.<img src="https://velog.velcdn.com/images/black_han26/post/24c70d95-e0eb-4d43-92c5-630ba2425096/image.png" alt="">
여기서 우리는 2가 추가 된 임의의 정점이라는 것을 알 수 있고, 3-4 막대그래프와 1번 도넛 그래프가 하나 있다는 것을 알 수 있다.</p>
<p>아래 그래프도 마찬가지이다.<img src="https://velog.velcdn.com/images/black_han26/post/c7ee4992-4e6e-4939-94e0-2186387104dd/image.png" alt="">4번이 추가된 정점임을 알 수 있고, 2번 막대그래프 하나 그리고 8자 모양 그래프 두 개가 있다.  </p>
<p>이 정보들을 출력하면 된다. </p>
<h2 id="2-문제-접근">2. 문제 접근</h2>
<p>범위가 1 ≤ edges의 길이 ≤ 1,000,000으로 주어졌고, 최대 Nlog(N)의 알고리즘을 이용해야 한다.</p>
<p>해당 문제는 어떤 정점이 몇개의 간선을 가지고 있느냐로 구분 지어야 하기 때문에 딕셔너리를 이용하기로 한다. </p>
<h2 id="3-알고리즘-풀이">3. 알고리즘 풀이</h2>
<p>문제를 살펴보면, 먼저 추가된 정점을 찾아야 구분짓기가 편할 것 같다.
추가된 정점은 그래프 개수만큼 나가는 간선을 가지고 있고 들어오는 간선은 없다.<img src="https://velog.velcdn.com/images/black_han26/post/4a9c86b5-6b44-4993-9612-9c55967c608a/image.png" alt="">그렇다면 다른 그래프는 어떨까?
먼저 막대그래프 또한 들어오는 간선만 있고 나가는 간선이 없는 정점을 하나씩 가지고 있는 것으로 보인다.</p>
<p>8자 그래프는 2개 이상 들어오고, 2개가 나가는 정점을 하나씩은 가지고 있다.
마지막으로 도넛 그래프는 전체 그래프의 개수 중에 (막대그래프 + 8자 그래프) 개수를 뺀 것과 같다.
전체 그래프의 개수는 임의 정점에서 나가는 간선의 개수와 같다.</p>
<pre><code class="language-python">def solution(edges):
    answer = [0,0,0,0]
    edge={}
    # 오가는 간선의 개수를 파악
    for a,b in edges: 
        if not edge.get(a):
            edge[a]=[0,0]
        if not edge.get(b):
            edge[b]=[0,0]
        edge[a][0] +=1
        edge[b][1] +=1

    for key, value in edge.items():
        # 임의의 정점
        if value[0]&gt;=2 and value[1]==0:
            answer[0]=key
        # 막대 그래프
        elif value[0]==0 and value[1]&gt;=1:
            answer[2]+=1
        # 8자 그래프 
        elif value[0]==2 and value[1]&gt;=2:
            answer[3]+=1
    answer[1] = edge.get(answer[0])[0]-(answer[2]+answer[3])
    return answer</code></pre>
<p>이번 문제는 딕셔너리에 대해 연습해볼 기회가 되었다.
<code>.items()</code> 함수와 <code>.get()</code> 가 어떤 기능을 하는지 확실하게 짚고 넘어가야겠다. 
<code>items()</code>는 어떤 key와 value의 값이 있는지 확인한다.
keys() 와 values()도 있다. 이는 어떤 key와 value가 있는지 각각 확인한다. </p>
<p><code>.get()</code>은 보통 없는 key에 접근할 때 발생하는 오류를 막기 위해서 사용된다. print(edge[&#39;a&#39;])를 하게 되면 a의 value를 출력하는데, 이때 a라는 key가 없다면 오류가 발생한다.</p>
<p>여기서 사용된 원리는<code>edge.get(a)</code>라고 사용하면, key=&#39;a&#39;가 있다면 True 없다면 False를 반환한다.</p>
<ul>
<li>딕셔너리 Values 에서 가장 큰 값은 max(my_dict.values())</li>
<li>Keys 에서 가장 큰 키는 max(my_dict, key=my_dict.get)이다.</li>
<li>dict정렬은 키 값으로 정렬 dict(sorted(my_dict.items()))</li>
<li>values 값으로 정렬 dict(sorted(my_dict.items(), key=lambda item: item[1]))</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준/Python] 11057- 오르막 수( 직관성 vs 가독성 )]]></title>
            <link>https://velog.io/@black_han26/%EB%B0%B1%EC%A4%80Python-11057-%EC%98%A4%EB%A5%B4%EB%A7%89-%EC%88%98-%EC%A7%81%EA%B4%80%EC%84%B1-vs-%EA%B0%80%EB%8F%85%EC%84%B1</link>
            <guid>https://velog.io/@black_han26/%EB%B0%B1%EC%A4%80Python-11057-%EC%98%A4%EB%A5%B4%EB%A7%89-%EC%88%98-%EC%A7%81%EA%B4%80%EC%84%B1-vs-%EA%B0%80%EB%8F%85%EC%84%B1</guid>
            <pubDate>Mon, 08 Apr 2024 00:22:16 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/11057">14889 - 오르막 수</a><img src="https://velog.velcdn.com/images/black_han26/post/64fb3242-3ee1-43a0-bb69-12d8881c1c5a/image.png" alt=""><img src="https://velog.velcdn.com/images/black_han26/post/2ab8e50e-a7a4-4e73-9fcd-d4a72f45c493/image.png" alt=""></p>
<h2 id="1-문제-해석">1. 문제 해석</h2>
<p><strong>N 자리의 수가 주어지고, 각 자리가 오름차순을 이루는 오르막 수의 총 개수를 구하는 문제이다.(접한 숫자가 같을 경우에도 오름차순으로 간주)</strong></p>
<p>예를 들어, N=4일 때, 1111, 1122, 1234는 모두 오르막 수</p>
<h2 id="2-문제-접근">2. 문제 접근</h2>
<p>N의 범위가 N (1 ≤ N ≤ 1,000)이 주어졌고, 이렇게 적은 범위에서는 주로 DP나 브루트 포스를 생각할 수 있는데 여기서 나는  <strong>다이나믹 프로그래밍</strong>을 사용했다. </p>
<h2 id="3-알고리즘-풀이">3. 알고리즘 풀이</h2>
<p>나열 -&gt; 규칙 찾기 ( 점화식 )
누구나 아래까지는 나열할 수 있을 것이다.<img src="https://velog.velcdn.com/images/black_han26/post/893fe0ca-1dff-4cf5-9ccc-a12fbf8b1361/image.png" alt="">
여기서 대각선 합의 규칙을 찾을 수 있다.
<img src="https://velog.velcdn.com/images/black_han26/post/d2e60b4a-1ced-4eba-b52d-afce0e562157/image.png" alt=""></p>
<pre><code class="language-python">N=int(input())
dp=list([0]*10 for _ in range(N+1))
dp[1]=[1]*10 # 첫 행은 모두 1
for i in range(2,N+1):# 2부터 N+1까지만
    for j in range(10):
        if j==0: #첫 열은 모두 1
            dp[i][j]=1
        else:
            dp[i][j]=dp[i-1][j]+dp[i][j-1]
print(sum(dp[N])%10007)</code></pre>
<h3 id="2-리팩토링">(2) 리팩토링</h3>
<p>사실 아래 코드가 풀이를 검색하면 주로 나오는 코드인데, 직관적으로 와닿지 않았다.</p>
<pre><code class="language-python">n = int(input())
dp = [1] * 10

for i in range(n-1):
    for j in range(1, 10):
        dp[j] += dp[j-1]

print(sum(dp) % 10007)</code></pre>
<p>왜 1차원 배열을 쓰는지와, 어떻게 <code>dp[j] += dp[j-1]</code> 로 한방에 정리가 되는지..</p>
<p>그림으로 이해하기 쉽게 설명하자면 아래와 같다.
<img src="https://velog.velcdn.com/images/black_han26/post/bc61f77d-44fe-4e15-b6a4-577b1e61f6a9/image.png" alt="">쉽게 설명하자면, 그림처럼 바로 윗줄 행의 값을 더하면 된다는 점과
어차피 필요한 결과는 마지막 행이기에, 그 전의 행들의 값들을 저장할 필요가 없다는 것 같다. </p>
<h2 id="회고">회고</h2>
<p><img src="https://velog.velcdn.com/images/black_han26/post/8b4747a9-959d-4d93-a9b0-a1a3d97e41c1/image.png" alt="">
둘다 시간은 같지만, 2차원 배열보다는 1차원 배열이 공간 복잡도 측면에서 더 효율적이다. 
그리고 코드도 훨씬 깔끔하다. 
다만 저게 무슨 코드인지 직관적으로 알기 힘들었다.(나만 그럴수도..)</p>
<h3 id="코드-깔끔-vs-누가-봐도-이해할-수-있게">코드 깔끔 vs 누가 봐도 이해할 수 있게</h3>
<p>후자 풀이는 코드를 보고 해설을 보면 이해가 가능하지만,
막상 시험 당일 저렇게까지 클린하게 떠올리기가 어려울 것 같다는 판단이 든다..</p>
<p>코딩 테스트를 준비하는 입장에서는 짧은 시간 안에 정확한 풀이를 떠올리는 능력이 중요하다.
어떻게 처리해야 하는지 고민하는 시간과 더 넓은 시야를 가져야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준/Python] 14501- 퇴사]]></title>
            <link>https://velog.io/@black_han26/%EB%B0%B1%EC%A4%80Python-14501-%ED%87%B4%EC%82%AC</link>
            <guid>https://velog.io/@black_han26/%EB%B0%B1%EC%A4%80Python-14501-%ED%87%B4%EC%82%AC</guid>
            <pubDate>Sun, 07 Apr 2024 07:17:40 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/14501">14501 - 퇴사</a><img src="https://velog.velcdn.com/images/black_han26/post/bc99419e-b5c0-4a43-a6ce-825f6a9f9ee5/image.png" alt=""><img src="https://velog.velcdn.com/images/black_han26/post/a6ba6e36-0a9f-4b7a-bc0a-f8cf2605be00/image.png" alt=""></p>
<h2 id="1-문제-해석">1. 문제 해석</h2>
<p><strong>남은 N일 동안 최대한 많은 이익을 얻기 위해 상담을 계획하고 있다. 각 상담은 완료하는 데 일정 기간이 소요되며, 해당 기간 동안 다른 상담을 진행할 수 없고 얻을 수 있는 최대 수익을 계산하는 문제이다.</strong></p>
<p>예를 들어, N=7일 때, 상담 일정이 다음과 같다고 가정하자:</p>
<p>1일차 상담: 3일 소요, 10의 수익
4일차 상담: 1일 소요, 20의 수익
5일차 상담: 2일 소요, 15의 수익</p>
<p>상담이 다른 상담을 진행할 수 있는 기간과 겹치지 않게 스케줄을 짜야 하며, N+1일째 되는 날에는 퇴사 예정이므로 그 전까지 모든 상담이 마무리되어야 한다.</p>
<h2 id="2-문제-접근">2. 문제 접근</h2>
<p>N의 범위가 N (1 ≤ N ≤ 15)이 주어졌고, 시간제한은 2초이기 때문에 어떤 알고리즘을 이용해도 여유로워 보였다. 이럴 경우 DP 혹은, 오랜시간이 걸리더라도 가장 확실한 답이 나오는 <strong>브루트 포스</strong>를 사용할 수 있다.</p>
<p>나는 후자를 사용했지만, DP를 사용하는 방법도 소개해 보려고 한다.</p>
<h2 id="3-알고리즘-풀이">3. 알고리즘 풀이</h2>
<ol>
<li>트리형태로 상담을 선택했을 때와 선택하지 않았을 때를 나눔</li>
<li>종료 조건 : day가 N이상이 된다면 멈추고, 최대 수익으로 갱신</li>
</ol>
<p>이는 이전에 작성했던 <a href="https://velog.io/@black_han26/%EB%B0%B1%EC%A4%80Python-14889-%EC%8A%A4%ED%83%80%ED%8A%B8%EC%99%80-%EB%A7%81%ED%81%AC">스타트와 링크 알고리즘</a> 풀이와 유사하다. 
<img src="https://velog.velcdn.com/images/black_han26/post/a1180dc5-6918-4c17-85af-634ca565e3c2/image.png" alt="">1번을 1일차 상담을 선택할 경우와 안할 경우를 나눈다. 마찬가지로 2번과 3번도 나누어 간다. 
depth가 N이상이 되었을 때는 더 이상 불가능 하기에 return한다.</p>
<pre><code class="language-python">N=int(input())
arr = [list(map(int,input().split())) for _ in range(N)]
ans=0

#day는 현재 날짜, profit은 현재까지의 수익
def dfs(day,profit):
    global ans # 전역 변수 ans 사용
    # 현재 날짜가 상담 가능 일수 N을 넘으면 현재까지의 수익을 최대 수익과 비교
    if day &gt;= N:
        ans=max(ans,profit) # 최대 수익 갱신
        return

    # 상담 완료 날짜가 N을 넘지 않으면 상담 진행
    if day+arr[day][0] &lt;= N:
        dfs(day+arr[day][0],profit+arr[day][1]) 
    # 상담을 선택하지 않을 경우, 다음 날짜로 넘어감
    dfs(day+1,profit)

# 초기 날짜(0)와 수익(0)으로 DFS 탐색 시작
dfs(0,0)
# 최대 수익 출력
print(ans)</code></pre>
<p><code>dfs(day+1,profit)</code> 상담을 선택하지 않았을 경우는 날짜만 지나기 때문에 이처럼 작성했다.</p>
<p><img src="https://velog.velcdn.com/images/black_han26/post/7a904c82-bce2-4fa3-9bd1-09614d465632/image.png" alt=""></p>
<h3 id="2-dp를-이용한-다른-풀이">(2) DP를 이용한 다른 풀이</h3>
<ol>
<li>날짜를 뒤에서 부터 세면서 DP를 이용한다.</li>
<li>dp[0]이 최대 이익이 된다.</li>
</ol>
<pre><code class="language-python">N=int(input())
T=[0]*N
P=[0]*N
dp=[0]*(N+1)

for i in range(N):
    T[i], P[i] = map(int,input().split())

# 뒤에서부터 탐색을 시작하여 각 일자에서 얻을 수 있는 최대 이익을 dp 리스트에 저장
for i in range(N-1,-1,-1):
    # 만약 상담을 시작하면 N일 내에 완료 가능한 경우
    if i+T[i]&lt;=N:
        # 현재 상담을 선택하는 경우와 선택하지 않는 경우 중 더 큰 이익을 최대 이익으로 결정
        dp[i]=max(dp[i+1],dp[i+T[i]]+P[i])
    else:
        # 현재 상담을 완료할 수 없는 경우, 현재 일자의 최대 이익은 다음 날과 같다
        dp[i]=dp[i+1]

# 최종적으로 0일차에서의 최대 이익 출력
print(dp[0])
</code></pre>
<p>이 풀이에서는 DP를 사용했다.</p>
<p><img src="https://velog.velcdn.com/images/black_han26/post/7b3e3e0b-a109-40d1-94ea-cedc65caa771/image.png" alt=""></p>
<p>스타트와 링크 문제와 유사한 알고리즘으로 해결이 가능하지만, DP를 이용하는 것이 신박해서 다시 한 번 기록해봤다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[정처기] 24년 1회 정보처리기사 필기 비전공 합격 후기/공부법]]></title>
            <link>https://velog.io/@black_han26/24%EB%85%841%ED%9A%8C%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC</link>
            <guid>https://velog.io/@black_han26/24%EB%85%841%ED%9A%8C%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC</guid>
            <pubDate>Fri, 15 Mar 2024 18:29:13 GMT</pubDate>
            <description><![CDATA[<p>정보처리기사 시험을 알게 된 것은 작년 8월 이후였다.
정처기 필기시험은 1년에 3번으로 나뉘는데 6월이 마지막 접수였기 때문에 2024년이 되길 기다렸다.<img src="https://velog.velcdn.com/images/black_han26/post/cc31cc8c-0e90-47da-b7b9-a21766ba2f1d/image.png" alt="">
24년 1월 23일 접수 일자가 되어 바로 접수했다.</p>
<blockquote>
<h3 id="정처기-합격-기준">정처기 합격 기준</h3>
<p>총 5과목으로 구성되어 한 과목당 최소 40점은 넘어야 하고, 평균 60점 이상이어야 합격을 할 수 있다. </p>
</blockquote>
<p>자격증 시험은 자격증을 취득하는 게 목표라고 생각했기 때문에 커트라인만 넘으면 된다고 생각을 했다.</p>
<p>나는 비전공자였지만, 코딩 테스트를 준비했었고 자바와 데이터베이스도 공부를 했었기 때문에 자신이 있었는데 막상 내용 보니깐 멘붕..😨 처음 보는 내용이 압도적으로 더 많았다..
코테 준비, 취준, SQLD 준비까지 겹쳐서 실제 준비 시간은 10일 정도 된 것 같다.</p>
<h3 id="공부법">공부법</h3>
<p>내가 참고했던 자료는 <a href="https://m.blog.naver.com/wook2124/222102990691">네이버 블로그</a> 이곳을 참고했다.
블로그 정리 내용을 가지고 하루에 한 과목씩 5일동안 개념을 &#39;훑었다&#39; .
솔직히 여기까지는 하루에 2~3시간가량만 썼다.
외우기보단 말 그대로 훑었다. 키워드를 아는 게 중요했고, 실제로 처음 보는 개념이 너무 많았다..
이거 다 기억하려다가는 머리가 터질 것 같다는 생각이 들었기 때문.</p>
<p>빨리 기출을 풀고 싶었는데 다 읽을 때까지 참았다. 
그리고 나서 <a href="https://www.comcbt.com/">CBT기출창고</a> 여기서 기출을 풀기 시작했다.</p>
<p>시험까지 5일가량 남은 상태였고
나는 딱 2개년 치만 풀었다. 22년도에 2회분이랑 21년도 3회분.. 총 5번의 기출
처음 푸는 1~2회분은 한 문제씩 풀고 해설을 보며 개념을 익혀갔다.
(처음 풀 땐 거의 다 틀리기 때문에 100문제 한 번에 푸는 게 의미가 없음을 느낌..)</p>
<p>여기서 느껴지는 게 앞서 훑었던 개념들이 어떤 식으로 출제가 되고, 어느 부분이 중요한지 알게 되었다.
그리고 3회분부터는 진짜 시험을 보듯 100문제를 풀었고 이때도 해설은 다 읽었다.
가장 중요한 건 3회분부터는 내가 몰랐거나, 아직도 헷갈리는 개념들은 쏙 뽑아서 정리했다.</p>
<p>그렇게 4일간 5회분을 풀고, 시험 보기 전까지 정리했던 개념만 읽었다.</p>
<h3 id="시험-당일">시험 당일</h3>
<p>신분증 필수, 참고로 컴싸는 필요가 없었습니다..
확실히 시험을 보는데, 문제 은행식이라는 느낌이 없잖아 있긴 했다. 소량 몇 문제 정도는 기출과 동일해 보이는? 문제가 나왔다.</p>
<p>그리고 정처기 필기는 시험 제출을 누르면 그 자리에서 화면에 합불 결과와 가채점 점수가 보이는데
맨 앞에 앉아서 살짝 부끄러웠다..</p>
<p>제출 전에 애매한 문제와 틀린 문제를 체크해 봤는데, 평균 60점은 넘을 거 같은데 제5과목이 확신이 없어서 과락이 두려웠다.</p>
<p>고민하다가 결국 제출했고 결과는
<img src="https://velog.velcdn.com/images/black_han26/post/db7f6859-c1ef-441e-b62d-6983c4da2c42/image.png" alt=""><img src="https://velog.velcdn.com/images/black_han26/post/816f02aa-c9ec-42ae-bf18-143115179f99/image.png" alt="">
79점으로 합격이었다.!! 가채점 결과이며 점수가 올라갈 순 있으나, 떨어지진 않는다고 하니 안심하고 집에 갔다.</p>
<h3 id="필기-후기">필기 후기</h3>
<p>아슬하게 합격할 줄 알았는데, 그나~마 안정적이라서 기분은 좋았다.
그리고 어느정도 안다고 자만하며 시작했는데, 개념정리서 첫장을 보니깐 큰일 났다고 생각 들었다..
사전에 내가 아는 게 도움이 됐던 건 3과목과 4과목이었는데, 그것도 30% 정도?</p>
<p>간혹 머리 비상하신 분들은 5일 만에 하시는 것 같은데,
정말 생노베이스라면 일주일은 시간을 풀로 써야 가능할 것 같다..</p>
<p>이제 다시 정처기 실기, SQLD와 코테등 취준 준비시작.. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정규화 개념 정리]]></title>
            <link>https://velog.io/@black_han26/%EC%A0%95%EA%B7%9C%ED%99%94-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@black_han26/%EC%A0%95%EA%B7%9C%ED%99%94-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 09 Feb 2024 13:57:00 GMT</pubDate>
            <description><![CDATA[<h2 id="제-1정규화">제 1정규화</h2>
<ul>
<li>모든 속성은 더 이상 쪼갤 수 없는 하나의 값만 가져야 한다.</li>
</ul>
<p>다가 속성 ➡ 전화번호가 여러 개 존재. 콤마(,)로 구분되어 있는 것
고객번호가 결정되어도 전화번호가 결정되지 않음..
<img src="https://velog.velcdn.com/images/black_han26/post/f2671f19-2b48-494b-99b4-772154e1ec85/image.png" alt="">
<img src="https://velog.velcdn.com/images/black_han26/post/03dcea89-39aa-44ed-8878-07a66ece8b9a/image.png" alt="">
<img src="https://velog.velcdn.com/images/black_han26/post/0219eeee-1e6e-4341-9f06-36e4a5551eb1/image.png" alt="">
즉 전화번호를를 나누고 새로운 테이블을 만들어야 한다.
새로 테이블을 만들때는 새로 생긴 마스터 테이블끼리 관계를 가지고있느냐를 판단.. </p>
<p>복합 속성➡ 고객명은 성과 이름으로 복합되어 있다. 이런것을 나누느냐를 판단한다. ex) 주민등록번호 -&gt; 생년,월일 등등..</p>
<p>유사 속성
중첩 릴레이션
➡ 한 릴레이션에서 반복형태 속성이 있으면 안된다.
<img src="https://velog.velcdn.com/images/black_han26/post/bc0f84d2-68bc-4d29-bd56-027ddb7a0cff/image.png" alt="">이것처럼 반복 속성을 분리한다.</p>
<p><img src="https://velog.velcdn.com/images/black_han26/post/e8a1c82e-4849-4346-bdb0-22a945772146/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/black_han26/post/98081bc3-d8ce-4f55-8b36-da96a1d6ce33/image.png" alt=""></p>
<h2 id="제-2정규화">제 2정규화</h2>
<ul>
<li>두개 이상의 PK가 존재한다. 모든 비 식별자 속성은 후보 식별자 속성에 완전 함수 종속돼야 한다.</li>
</ul>
<h3 id="부분함수-종속이-존재하는-릴레이션">부분함수 종속이 존재하는 릴레이션</h3>
<p><img src="https://velog.velcdn.com/images/black_han26/post/5467103c-e48f-4e1e-b241-0437dc1d4ea9/image.png" alt=""> A와 B를 통해서 C와 D가 결정되어야 하는데, B만으로도 C를 알 수 있는 상황 
즉, B 왕비가 사조직이 있다. -&gt; 왕족의 반란이다.
<img src="https://velog.velcdn.com/images/black_han26/post/e8e316e5-302b-4168-bdec-6f6123bdf754/image.png" alt="">B와C 를 따로 빼서 통치한다. 이는 마스터테이블이 돼서 1:M의 관계를 가지게 된다.</p>
<p>아래 테이블은 소분류와 소분류명으로 묶어서 테이블을 나눌 필요가 있는가 ? 
<img src="https://velog.velcdn.com/images/black_han26/post/8999c163-fc43-4075-a0b7-373b5987c312/image.png" alt=""></p>
<p>나누었을 때 소분류명이 딱히 의미를 가지지 않기때문에 대분류와 붙여준다. </p>
<p>만약 소분류명이 나누었을 때 의미가 있다면 아래처럼 구분한다.
<img src="https://velog.velcdn.com/images/black_han26/post/3f0ff883-ede4-4306-a44e-70f811cbdcf6/image.png" alt=""></p>
<h2 id="제-3정규화">제 3정규화</h2>
<p>-일반 속성간에는 종속성이 존재하지 않는다.
<img src="https://velog.velcdn.com/images/black_han26/post/a71e6592-ada1-424f-9796-c8702789516a/image.png" alt="">
이 테이블은 C가 결정되면 D가 결정된다. 즉 D는 C에 종속된다. -&gt; 왕권은 문제없는데 지방 호족이 사조직이 있다.</p>
<p>C와 D를 내보내서 테이블을 만든다. 마치 2정규화랑 비슷하다.</p>
<p><img src="https://velog.velcdn.com/images/black_han26/post/19b24e54-ea31-4892-8099-78857da63d08/image.png" alt=""></p>
<h3 id="bc정규화-잘-나오지않음">BC정규화 (잘 나오지않음)</h3>
<p><img src="https://velog.velcdn.com/images/black_han26/post/b51abc18-48a1-4e4b-918e-3e9babd10aee/image.png" alt="">문제없이 A,B가 결정되면 C와 D가 잘 결정되는데, C가 결정되면 PK인 B가 결정된다.</p>
<p>진짜 왕족C는 신분을 속이고 B를 세워놓는다. -&gt; 처갓집의 반란
장인어른이 진짜권력이고 처가 왕비의 역할을 하고있다.</p>
<p><img src="https://velog.velcdn.com/images/black_han26/post/41eb3141-7a4e-4f25-9868-503b20db1a06/image.png" alt="">
B자리에 C를 PK로 넣어주고, B와 C를 따로 뽑아서 테이블을 만든다. 여기서도 C가 PK가 된다. 결국 장인어른이 PK가 되도록 만들었다. </p>
<h2 id="역정규화">역정규화</h2>
<p>-효율을 위해서 일부를 중복 허용
Join시 발생되는 엄청난 계산량을 해결하기 위해 사용</p>
<p>데이터가 많은데 JOIN을 하자니 너무 오래 걸린다. 그냥 중복되더라도 분리를 시키지않으면 조인을 할 필요가 없어서 빠르게 처리가 가능해진다.</p>
<p><img src="https://velog.velcdn.com/images/black_han26/post/4e7d9459-92d5-4083-9877-830dcf20264d/image.png" alt="">
그러나 위 그림처럼 K를 두개의 테이블에 모두 놓으면 실패된 모델링이다.
한곳에만 있으면 됨. 나머지는 FK로 찾아들어간다. 
<img src="https://velog.velcdn.com/images/black_han26/post/516eec36-81c2-4df6-8171-0af3979a5ca0/image.png" alt=""> 위 그림처럼 고객명이라는 사조직을 가지고 있지만, 일부러 냅둔다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 모델링 관계 정리]]></title>
            <link>https://velog.io/@black_han26/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%AA%A8%EB%8D%B8%EB%A7%81-%EA%B4%80%EA%B3%84-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@black_han26/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%AA%A8%EB%8D%B8%EB%A7%81-%EA%B4%80%EA%B3%84-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 02 Feb 2024 02:09:39 GMT</pubDate>
            <description><![CDATA[<p>일기장에 기억하기 쉽게 간단히 정리</p>
<h1 id="테이블-관계">테이블 관계</h1>
<h2 id="1--m-관계">1 : M 관계</h2>
<h3 id="한쪽이-관계를-맺은-쪽의-여러-객체를-갖는-것">한쪽이 관계를 맺은 쪽의 여러 객체를 갖는 것</h3>
<p><strong>부모와 자식 관계</strong>로 볼 수 있다. ➡ 부모의 PK는 자식의 FK가 된다.
자식은 없을 수 있으나, 부모는 존재해야 한다. 
즉 부모 없는 자식은 없다 ➡ 오류 발생</p>
<h2 id="1--m-재귀적-관계">1 : M 재귀적 관계</h2>
<p><img src="https://velog.velcdn.com/images/black_han26/post/f81efc85-028e-4e70-853c-480e3fafe7d8/image.png" alt=""></p>
<p>1:M 관계가 너무 많아서 복잡해질 때 자신을 참조하는 관계를 나타내서 표시하는 것
하나의 테이블에 PK와 FK가 있게 설계한다.
PK와 부모 ID를 FK로 두고 재귀적 관계를 설정한다.</p>
<p>셀프 조인이라고도 불림. 
본인의 상위부서를 확인할 수 있게 한다.
<img src="https://velog.velcdn.com/images/black_han26/post/e97a6fd5-2f02-48d1-95c2-e6f0b18d5f4f/image.png" alt="">
최상위 부서는 상위부서가 존재하지 않기에 null을 허용한다.</p>
<hr>
<h2 id="m--n-관계">M : N 관계</h2>
<ol>
<li>부모 관계가 아니어야 한다.  ➡ 마스터 테이블이 서로 독립적으로 존재할 수 있어야 한다. </li>
<li>어느 쪽에서 봐도 다대다 관계가 성립한다.</li>
</ol>
<p>*<em>헷갈리면, 테이블을 만들어 본다. !
*</em>
예를 들어, 유저가 있고 유저가 팀에 가입하여 팀을 형성한다고 생각해 보자.</p>
<ol>
<li>먼저 팀은 여러 유저를 가질 수 있다.
<img src="https://velog.velcdn.com/images/black_han26/post/026967a3-47e9-4e63-907f-aa3252a2da97/image.png" alt="">이는 마치 부모 관계 인것 처럼 보인다.. 그렇다면 1 : M 관계로 설정할 수 있다. 그런데 만약에 유저는 여러팀에 가입할 수 있다면 어떻게 될까?<img src="https://velog.velcdn.com/images/black_han26/post/95a24c17-e48a-4052-9e0d-c46b2d68b02d/image.png" alt="">위 테이블처럼 team테이블에서도, user테이블에서도 중복이 발생한다.
이게 M:N 관계이고 <strong>관계 테이블을 만듦으로써 연결시켜 준다.</strong>
<img src="https://velog.velcdn.com/images/black_han26/post/e3e449fc-63af-441b-8921-d6d730f322a8/image.png" alt="">join이라는 테이블을 만들어서 양쪽 마스터 테이블의 PK를 FK로 받는다.
이로써 가입한 날짜를 속성으로 추가할 수도 있으며, 양쪽 테이블을 중복 없이 연결시킨다.</li>
</ol>
<h3 id="상속형-pk와-독립형-pk">상속형 PK와 독립형 PK</h3>
<p>위 관계 테이블에서 join_id가 없고 team_id와 user_id를 복합키로 사용하면 상속형 PK라고 한다. 이는 복합키의 장점을 살리며 join_id를 따로 만들 필요가 없게 한다. </p>
<p>독립형 PK는 위에 그림 그대로이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준/Python] 13023- ABCDE]]></title>
            <link>https://velog.io/@black_han26/%EB%B0%B1%EC%A4%80Python-13023-ABCDE</link>
            <guid>https://velog.io/@black_han26/%EB%B0%B1%EC%A4%80Python-13023-ABCDE</guid>
            <pubDate>Thu, 25 Jan 2024 08:38:37 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/13023">13023- ABCDE</a><img src="https://velog.velcdn.com/images/black_han26/post/d633df60-eafb-4a08-8c4e-f1f575af13d9/image.png" alt=""></p>
<h2 id="1-문제-해석">1. 문제 해석</h2>
<p>쉽게 말해 5명이 이어져 있는 가를 판단하고, 이어져있다면 1 아니라며 0을 출력한다.</p>
<blockquote>
<p>ex )<img src="https://velog.velcdn.com/images/black_han26/post/846f1f7d-8d34-4a92-b60c-87381f0f83eb/image.png" alt="">(0-1), (1-2), (2-3), (3-4), (4-5) 으로 1-2-3-4-5가 연결된 것으로 1을 출력한다.</p>
</blockquote>
<blockquote>
<p>ex )<img src="https://velog.velcdn.com/images/black_han26/post/ffbc4bb3-523c-4220-99c6-f9f971420226/image.png" alt="">언뜻 보면 (0-1), (1-2), (2-3), (3-0) 으로 0-1-2-3-0 으로 4개만 연결된 것처럼 보이는데, 4부터 시작하는 것을 생각했을 때 4-1-2-3-0 으로 5개가 연결되어 있다.</p>
</blockquote>
<h2 id="2-문제-접근">2. 문제 접근</h2>
<p>연결 관계를 확인하고 방문 처리를 해야하는 문제라고 판단이 들었다.
다만, 5개의 연결 관계를 빠르게 파악하는 게 우선이기 때문에 BFS보단 DFS가 더 적합하다고 판단했다.</p>
<h2 id="3-알고리즘-풀이">3. 알고리즘 풀이</h2>
<p><img src="https://velog.velcdn.com/images/black_han26/post/bfd6c120-94a7-4669-bedc-2df6d7922456/image.png" alt=""> A(depth=1)를 시작으로 (depth=5)까지 가게 된다면 5명이 연결된 것으로 간주한다. </p>
<ol>
<li>처음 start를 시작으로 연결 관계를 확인한다.</li>
<li>종료 조건 : 트리의 깊이가 5가 된다면 멈추고, 1을 출력</li>
<li><code>for i in range(N)</code> 을 통해 start를 바꿔가며 DFS를 실행</li>
</ol>
<pre><code class="language-python">N,M = map(int,input().split())
arr=list([] for _ in range(N))
visited=[False] * N
for _ in range(M): # 간선 정보 입력
    a,b=map(int,input().split())
    arr[a].append(b)
    arr[b].append(a)
five = False # 5명 연결시 True

def dfs(start,depth):
    global five
    visited[start]=True

    if depth==5 : # 5명 연결확인
        five = True
        return
    for i in arr[start]: # 연결된 것들중에
        if visited[i] == False : #방문하지 않은게 있는지 확인
            dfs(i,depth+1)
    visited[start]=False


for i in range(N):
    dfs(i,1)
    if five:
        print(1)
        break
else:
    print(0)</code></pre>
<p><img src="https://velog.velcdn.com/images/black_han26/post/6ca21fc6-e966-4373-ad07-479896751e96/image.png" alt=""></p>
<h3 id="처음-나의-풀이--회고">처음 나의 풀이 &amp; 회고</h3>
<p><img src="https://velog.velcdn.com/images/black_han26/post/87156de9-e81b-4ea2-a90b-88f9262e56b9/image.png" alt=""></p>
<pre><code class="language-python">for _ in range(M):
    a,b=map(int,input().split())
    arr[a][b]=1
    arr[b][a]=1
five = False

def dfs(start,depth):
    global five
    visited[start]=True

    if depth==5 :
        five = True
        return
    for j in range(N):
        if arr[start][j]==1 and visited[j] == False :
            dfs(j,depth+1)
    visited[start]=False</code></pre>
<p>처음에 위처럼 작성했는데 자꾸 시간 초과가 발생했다. 
알고 보니 <strong>간선 정보를 2차원 배열</strong>에 넣었는데, 이렇게 되면 연결이 되었는지를 확인하기 위해서는 모든 사람을 조사해 봐야 한다는 차이를 깨달았다.
List를 이용하면, <code>for i in List</code> 처럼 연결된 사람을 바로 확인할 수 있어서 탐색하는 더 빠르다. </p>
<p>여태까지 DFS 간선 정보를 2차원으로만 사용했는데 List를 이용한 좋은 예인 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB 모델링 키 개념, DBeaver - 복합키 설정]]></title>
            <link>https://velog.io/@black_han26/DB-%EB%AA%A8%EB%8D%B8%EB%A7%81-%ED%82%A4-%EA%B0%9C%EB%85%90-DBeaver-%EB%B3%B5%ED%95%A9%ED%82%A4-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@black_han26/DB-%EB%AA%A8%EB%8D%B8%EB%A7%81-%ED%82%A4-%EA%B0%9C%EB%85%90-DBeaver-%EB%B3%B5%ED%95%A9%ED%82%A4-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Tue, 23 Jan 2024 11:45:36 GMT</pubDate>
            <description><![CDATA[<p>복합키에 대한 개념을 기록하기 전에 간단하게 슈퍼키, 후보키, PK와 FK의 의미를 알아보자
위 개념을 구분하기 위해서는 유일성과 최소성이 무슨 의미인지를 아는 것이 중요하다.</p>
<h3 id="유일성">유일성</h3>
<p>쉽게 말해 중복되지 않는가를 말한다. 이름이나 혈액형은 중복되기가 쉽기 때문에 유일성을 보장하지 못한다. 그러나, 주민번호나 전화번호 따위는 중복될 수 없기 때문에 유일성을 보장한다.</p>
<h3 id="최소성">최소성</h3>
<p>키를 구성하는 속성들 중 가장 최소로 필요한 속성들로만 구성함을 말한다. 예를 들면 학번과 학과의 묶음은 유일성을 보장하지만 학번만으로도 충분히 학생을 식별할 수 있기 때문에 최소성을 보장하지 못한다.
또한 상품 주문을 식별할 때 주문 일자+고객명+상품명으로도 식별할 수 있겠지만, 간단히 주문 번호만으로도 식별을 할 수 있기 때문에 최소성을 고려하지 않은 설계라고 칭할 수 있다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/black_han26/post/fa4e8c61-444d-4430-b805-8f0074c17f3f/image.png" alt=""></p>
<h3 id="슈퍼키super-key">슈퍼키(Super Key)</h3>
<p>유일성을 만족하는 집합</p>
<h3 id="후보키candidate-key">후보키(Candidate Key)</h3>
<p>유일성과 최소성을 만족하는 집합
이는 기본 키가 될 수 있는 키의 집합이며, 기본키로 설정되면 나머지는 대체키가 된다.</p>
<p>아래 테이블을 통해 무엇이 유일성과 최소성을 만족하는지 알아보자.<img src="https://velog.velcdn.com/images/black_han26/post/d27bf3bc-08c4-4935-a6bb-0db9b0ccf024/image.png" alt=""></p>
<pre><code>사진 출처: http://dbteam1116.pbworks.com/w/page/38413057/%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD%20%EB%B6%84%EC%84%9D</code></pre><p>위의 표에서 후보키는 무엇이 될까?
고객번호, 전화번호, 주민번호가 될 것이다.</p>
<h3 id="primary-key-pk---기본-키">Primary Key (PK - 기본 키)</h3>
<p>후보키에서 선택받은 키이다. </p>
<p>주민번호나 전화번호 같은 고객의 개인정보는 PK로 사용하는 것을 지양한다.</p>
<h3 id="foreign-key-fk---외래-키">Foreign Key (FK - 외래 키)</h3>
<p>다른 테이블의 기본키를 가져와 관계를 연결시키면 이를 외래 키라고 부른다.
이는 테이블이 다른 테이블의 데이터를 참조하여 테이블간의 관계를 설정한다.</p>
<hr>
<blockquote>
<h2 id="복합키">복합키</h2>
<p>두 개 이상의 컬럼을 묶어서 하나의 기본키로 지정한다. 이는 하나의 테이블에 하나의 기본키만 존재할 수 있다는 규칙을 위배하지 않는다.</p>
</blockquote>
<p>기본키는 하나의 테이블에 하나만 존재할 수 있다. 그런데 데이터베이스 모델링을 하면 하나의 컬럼으로는 기본키를 선택하기 어려운 상황이 발생한다.<img src="https://velog.velcdn.com/images/black_han26/post/3b4209d3-e1db-4c5b-ad44-e34f20a6b6d4/image.png" alt="">예를 들면, 위와 같은 반 테이블을 만들고 싶다. 학년, 반, 반이름 컬럼이 있을 때 어떤 것을 기본 키로 설정해야 하는지이다. <img src="https://velog.velcdn.com/images/black_han26/post/98f9e030-2443-4de8-befb-4056af8de766/image.png" alt="">학년과 반 이름은 모두 유일성을 위배하기 때문에, 반을 PK로 설정하게 되면, 최대 3반까지만 있는 학교라고 가정했을 때 위에처럼 2학년 부터는 4반, 5반, 6반으로 증가하는 문제가 생긴다. <img src="https://velog.velcdn.com/images/black_han26/post/8e5702d2-28c8-49ad-991c-105aac057919/image.png" alt="">이럴 때는 학년 컬럼과 반 컬럼을 하나로 묶어서 지정하는 복합키를 사용 한다.
즉, 1-1, 1-2, 1-3, 2-1, 2-2, 3-1처럼 학년과 반을 묶어서 사용하면 유일성을 가지게 된다.</p>
<h2 id="dbeaver에서-복합키-설정">DBeaver에서 복합키 설정</h2>
<p>아래처럼 관계도를 만들 예정이다.
<img src="https://velog.velcdn.com/images/black_han26/post/08350e18-c33d-4993-b5a5-ebb40e1f33fe/image.png" alt="">1. 먼저 Create New Table로 복합키를 설정할 테이블을 만든다.<img src="https://velog.velcdn.com/images/black_han26/post/17a2c056-7fff-4469-8d86-fa81c201b76d/image.png" alt="">2. 컬럼에는 아래처럼 부서 이름, 부서id, 회사id를 넣어주었다.<img src="https://velog.velcdn.com/images/black_han26/post/640281c3-2f12-4415-93d5-fc8bbbe96f58/image.png" alt="">3. 그리고 Columns아래 Constraints로 들어가서 Create New Constraint를 선택한다.<img src="https://velog.velcdn.com/images/black_han26/post/81b5f200-7a3d-444f-8c4d-acc7ea341817/image.png" alt="">4. 복합키로 설정할 컬럼을 선택한다. 여기서 이미 PK가 존재한다면, PK를 먼저 삭제하고 진행해야 한다.<img src="https://velog.velcdn.com/images/black_han26/post/376f496f-eb3c-44b4-906d-89d07ad1ed03/image.png" alt="">
5. PK가 2개로 등록이 된 것을 확인 할 수 있다.<img src="https://velog.velcdn.com/images/black_han26/post/01bea833-ed92-45ae-b5b5-dc026861893a/image.png" alt=""></p>
<h2 id="결과">결과</h2>
<p>여기서는 Companyid와 Deptid의 결합이 PK로 설정되었기 때문에, 아래 그림처럼 총무부를 두번째 INSERT했을 때 Depid가 3이 되지 않고 1이 되는 것을 볼 수 있다.<img src="https://velog.velcdn.com/images/black_han26/post/d5e98c13-7fd6-4b3b-926f-3cccdd3089f2/image.png" alt="">그 이유는 처음 INSERT 한 총무부는 Company id가 1인 회사이기에 PK가 1-1이고, 세 번째 INSERT 한 총무부는 Company id는 2인 회사이기에  Depid를 3을 배정할 필요가 없이 1을 배정하더라도 PK는 2-1로 중복이 된 것이 아니게 된다.</p>
<p><strong>만약 PK를 Deptid 하나로만 설정한다면</strong> 아래처럼 Deptid는 1,2,3...으로 증가되게끔 설정된다.<img src="https://velog.velcdn.com/images/black_han26/post/cc91595d-8620-4ace-81d8-1afc410f3c60/image.png" alt="">그리고 아래처럼 쿼리입력을 하면 &#39;1&#39;은 이미 존재한다는 오류가 나게 된다.
<img src="https://velog.velcdn.com/images/black_han26/post/a8bfeab9-0473-456e-97c7-3ed2ed35277b/image.png" alt=""></p>
<h3 id="💡-복합키-사용-시-고려해야-할-점">💡 복합키 사용 시 고려해야 할 점</h3>
<p>복합키를 사용할 때는 PK로 설정하는 순간, Null을 허용하지 않기 때문에
반드시 데이터값이 들어가야 한다. </p>
<p>그러나 우리가 실제로 데이터 모델링을 할 때는 부모나 자식의 위치를 모르고 일단 INSERT 하게 되는 경우도 존재한다. 예를 들면, 몇 학년인지는 정해졌지만, 아직 반이 정해지지 않았을 경우 복합키를 사용하면 반이 정해질 때까지 INSERT를 할 수 없게 된다. 사용자의 의도를 고려해서 복합키를 사용할 필요가 있다.</p>
<h2 id="회고">회고</h2>
<p>키 개념에 대해서 다시 한번 정리하는 계기가 되었다. 특히, 복합키의 개념은 처음 접해보았고, 직접 구현하면서 이를 설정하고 작동하는 방식에 대해 이해하게 되었다. 이러한 경험을 통해 복합키는 나중에 AK(Alternate Key)로도 사용될 수 있음을 알게 되었고, 개념을 정리하는 중요성을 깨달았다. </p>
<p>실제로 기존의 기본 키(PK)를 삭제하고 다시 생성하는 작업에서 몇 가지 어려움을 겪었다. 이유는 먼저 기존의 PK를 삭제하고 PK를 생성시에 하나의 PK는 반드시 설정해야 한다는 오류가 발생했고, 먼저 PK를 생성하고 PK를 삭제했을 때는 한 테이블에는 PK를 하나만 생성할 수 있다는 오류가 발생했다.
이를 해결하기 위해서는 기존 데이터를 먼저 지워야 한다는 점을 알게 되었다. PK를 변경하면 기존 데이터는 변경 전의 PK를 따르고 있기 때문에 이러한 처리가 필요한 것이었다. </p>
<p>이러한 오류 해결을 통해 DBeaver와 같은 데이터베이스 관리 도구에 점차 익숙해지고 있음을 느낀다. 계속해서 경험을 쌓아가면서 데이터베이스 작업에 대한 이해와 능력을 향상시켜 나갈 계획이다.</p>
]]></description>
        </item>
    </channel>
</rss>