<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jeong_yooony.log</title>
        <link>https://velog.io/</link>
        <description>개발 기록장</description>
        <lastBuildDate>Wed, 26 Jun 2024 05:32:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jeong_yooony.log</title>
            <url>https://velog.velcdn.com/images/jeong_yooony/profile/9d315151-c557-4444-b327-a49be17128f9/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jeong_yooony.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jeong_yooony" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[네트워크] 인터넷에 대하여]]></title>
            <link>https://velog.io/@jeong_yooony/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%B8%ED%84%B0%EB%84%B7%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@jeong_yooony/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9D%B8%ED%84%B0%EB%84%B7%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Wed, 26 Jun 2024 05:32:16 GMT</pubDate>
            <description><![CDATA[<h1 id="1-인터넷의-원리">1. 인터넷의 원리</h1>
<h2 id="인터넷">인터넷</h2>
<ul>
<li>정의: TCP/IP 프로토콜을 기반으로 하여 전 세계 수많은 컴퓨터와 네트워크들이 연결된 광범위한 컴퓨터 통신망.</li>
<li>인터넷의 원리는 쉽게 말해 컴퓨터 2대가 연결되어 있는 것이다.<ul>
<li>예를 들어 우리가 가지고 있는 컴퓨터가 네이버가 가지고 있는 컴퓨터에게 명령을 내리면 네이버가 가지고 있는 네이버 화면과 여러 자료들을 우리 컴퓨터에 전달해준다.</li>
<li>이때 네이버의 컴퓨터를 흔히 말하는 서버라고 한다.</li>
</ul>
</li>
<li>결론적으로 인터넷은 이런 컴퓨터들이 엄청나게 많이 연결되어 있는 느낌을 말한다.</li>
</ul>
<h2 id="tcpip란">TCP/IP란?</h2>
<ul>
<li>컴퓨터 사이에서 데이터를 주고받는 방식 중 하나</li>
<li>해외 서버에는 어떻게 접속할까?<ul>
<li>실제 해저에 광케이블이 설치되어 있고, 그 케이블을 타고 아마존 서버 등을 통해 홈페이지를 전달받는다.</li>
</ul>
</li>
</ul>
<h1 id="2-lan-wan이란-무엇인가-isp란">2. LAN, WAN이란 무엇인가? ISP란?</h1>
<ul>
<li>컴퓨터 간에 정보를 전달하려면 연결을 해줄 요소가 필요하다.<h2 id="연결-방법">연결 방법</h2>
</li>
<li>컴퓨터를 연결하는 요소는 바로 전기이다.</li>
<li>요즘 무선도 많이 쓰지만 고속으로 연결하고 싶을 때는 케이블과 같은 유선LAN을 많이 사용한다.</li>
<li>WIFI 또한 똑같이 전기를 사용한다.<ul>
<li>공기 중에 눈에 보이지 않는 파장대의 빛을 쏘고 이러한 빛에 의해 전기 신호가 만들어져 연결이 되는 것이다.</li>
</ul>
</li>
</ul>
<h2 id="가까이를-연결해주는-lan">가까이를 연결해주는 LAN</h2>
<ul>
<li>LAN이란 Loca Area Network의 약자이다.</li>
<li>한정된 공간에서 외부가 아닌 내부끼리 이어주는 작은 네트워크를 의미한다.<h3 id="lan도-종류가-다양하다">LAN도 종류가 다양하다</h3>
</li>
<li>Fully Connected 방식: 각자의 컴퓨터를 일일이 연결</li>
<li>Star 방식: 가운데 공유기에 한곳에 전부 모아 연결하는 별모양 방식</li>
<li>네트워크 토폴로지
<img src="https://velog.velcdn.com/images/jeong_yooony/post/ae04e43e-795b-460a-94db-b46e75f4c8db/image.png" alt=""></li>
</ul>
<h2 id="lan의-한계-장거리-통신-불가">LAN의 한계: 장거리 통신 불가</h2>
<ul>
<li>LAN을 통해 정보를 전달하기 위해서는 케이블 연결이 필요하다.</li>
</ul>
<h2 id="lan-통신의-한계를-해결해주는-isp">LAN 통신의 한계를 해결해주는 ISP</h2>
<ul>
<li>ISP란 인터넷 버시스 제공자이다.<ul>
<li>대표적으로 KT, SK, L GU+가 있다.</li>
</ul>
</li>
<li>각 통신사들이 우리나라 곳곳에 인터넷 케이블을 엄청나게 깔아두고 우리가 그 케이블들을 사용하는 것이다.</li>
</ul>
<h2 id="더-넓은-범위의-네트워크-wan">더 넓은 범위의 네트워크 WAN</h2>
<ul>
<li>ISP 등이 제공해주는 좀 더 큰 범위의 네트워크</li>
<li>WAN은 LAN으로 구성된 네트워크(집, 회사, 건물)들을 연결해 주는 역할을 한다.</li>
<li>LAN: 가깝고 덜 복잡하기 때문에 오류가 적고 속도가 빠르다.</li>
<li>WAN: 멀리까지 연결해 줘야하고 복잡하기에 속도가 느린다. 여러 물리적 상황과 환경 영향을 받기 때문에 오류가 많은 편이다.</li>
</ul>
<h3 id="wan이-망가지면-인터넷-먹통">WAN이 망가지면? 인터넷 먹통</h3>
<ul>
<li>ISP에서 대신 연결해 주었던 케이블들과 장비들이 망가지면 휴대폰과 인터넷은 전부 먹통이 될 수 있다.</li>
</ul>
<h2 id="용어-정리">용어 정리</h2>
<ul>
<li><strong>네트워크</strong>: 컴퓨터를 두 대 이상 연결하여 서로 데이터를 전송할 수 있는 통신망이다.</li>
<li><strong>인터넷</strong>: TCP/IP 프로토콜을 사용하는 세계 최대 규모의 네트워크다. 전 세계의 컴퓨터를 서로 연결하여 정보를 교환할 수 있도록 만든 하나의 거대한 컴퓨터 통신망이다.</li>
<li><strong>랜 (LAN)</strong>: 비교적 가까운 거리에 위치한 장치들을 서로 연결한 네트워크를 말한다. 집, 사무실, 학교 등의 건물과 같이 가까운 지역을 연결하는 네트워크다.</li>
<li><strong>웬 (WAN)</strong>: 랜을 다시 하나로 묶는 거대한 네트워크다. 특정 도시, 국가, 대륙과 같이 매우 넓은 범위를 연결하는 네트워크를 말한다. 넓은 지역에 설치된 컴퓨터들 간의 정보와 자원을 공유하기에 적합하도록 설계한 컴퓨터 통신망이다.</li>
<li><strong>인터넷 서비스 제공자 (ISP)</strong>: 인터넷에 접속하는 수단을 제공하는 주체다. 일반 사용자, 기업체, 기관, 단체 등이 인터넷에 접속하여 인터넷을 이용할 수 있도록 돕는 사업자다. 현재는 KT, U+, SKT와 같은 ISP가 인터넷 서비스를 제공한다.</li>
</ul>
<h1 id="3-프로토콜과-tcpip-이해하기">3. 프로토콜과 TCP/IP 이해하기</h1>
<h2 id="네트워크의-규칙-프로토콜">네트워크의 규칙: 프로토콜</h2>
<ul>
<li>송신자와 수신자가 사용하는 언어가 다르면 서로 통신이 되지 않는다.</li>
<li>공용어로 사용하기로 한 약속을 프로토콜이라 한다.</li>
</ul>
<h2 id="네트워크의-프로토콜-osi-tcpip">네트워크의 프로토콜: OSI, TCP/IP</h2>
<ul>
<li>컴퓨터끼리는 국가 상관없이 어디서든 연결되도록, 전 세계를 통일한 언어를 지정함.<ul>
<li>OSI 모델, TCP/IP</li>
</ul>
</li>
</ul>
<h3 id="네트워크-계의-공용-모델-osi">네트워크 계의 공용 모델: OSI</h3>
<ul>
<li>1970년대 컴퓨터는 늘어나는데, 서로 통신 규격이 정해져 있지 않아 혼란이 생김.</li>
<li>ISO라는 국제 표준화 기구는 언어 통일을 위해, OSI라는 네트워크의 표준모델을 지정한다.</li>
<li>OSI는 보내고자 하는 데이터를 다른 컴퓨터에 잘 전달해 주는 표준 모델이다.</li>
<li>인간이 이해하는 언어를 컴퓨터만의 언어로 바꾸어주는 과정, 컴퓨터만의 언어를 전기신호로 바꾸어 주는 과정 등 다양한 과정이 있고 이를 계층이라 부른다. OSI 7계층 모델이라고 한다.</li>
<li>여러 가지 계층을 통해 우리의 카톡 데이터가 전기적 신호로 바뀌고, 이런 전기적 신호는 다시 카카오톡 메시지로 바뀌게 되어 메시지가 전달된다.
<img src="https://velog.velcdn.com/images/jeong_yooony/post/6850f733-5669-4e94-b1d2-12e8dc3b9261/image.png" alt=""></li>
</ul>
<h3 id="osi보다-빠르고-단순한-tcpip">OSI보다 빠르고 단순한: TCP/IP</h3>
<ul>
<li>TCP/IP는 OSI보다 먼저 나온 통신 프로토콜이다.
<img src="https://velog.velcdn.com/images/jeong_yooony/post/e845aafe-9b19-44cc-98cf-e1d0a27bd6ad/image.png" alt=""></li>
<li>7층으로 되어있던 계층이 4층으로 된 것이다.</li>
<li>초기에 여러 통신이 난립했지만 TCP/IP는 큰 주류 중 하나였다.</li>
<li>여러 국가들이 모여 만든 꿈의 통신 규격 OSI가 등장하고 나서, 1990년대 기업들은 OSI와 TCP/IP를 둘 다 사용했다. 하지만 너무 많은 국가가 참여하다보니, OSI의 개발 속도가 느렸다. 결국 TCP/IP가 더욱 빠르게 개발되어 빠르고 높은 신뢰성으로 인터넷의 표준이 되었다.</li>
</ul>
<h2 id="데이터의-전달-과정-캡슐화-역-캡슐화">데이터의 전달 과정: 캡슐화 역 캡슐화</h2>
<ul>
<li>캡슐화 / 역 캡슐화: 캡슐화는 컴퓨터 통신상에서 상위 계층의 통신 프로토콜 정보를 데이터에 추가하여 하위 계층으로 전송하는 기술, 반대로 역 캡슐화는 상위 계층의 통신 프로토콜에서 하위 계층에서 추가한 정보와 데이터를 분리하는 기술</li>
<li>헤더: 저장되거나 전송되는 데이터의 맨 앞에 위치하는 추가적인 정보 데이터, 데이터의 내용이나 성격을 식별 또는 제어하는데 사용함.</li>
</ul>
<hr>
<p><strong>[참고]</strong></p>
<ul>
<li><a href="https://m.blog.naver.com/PostView.naver?blogId=wngjs3&amp;logNo=222053320101&amp;fromRecommendationType=category&amp;targetRecommendationDetailCode=1000">https://m.blog.naver.com/PostView.naver?blogId=wngjs3&amp;logNo=222053320101&amp;fromRecommendationType=category&amp;targetRecommendationDetailCode=1000</a></li>
<li><a href="https://m.blog.naver.com/PostView.naver?blogId=wngjs3&amp;logNo=222061315403&amp;fromRecommendationType=category&amp;targetRecommendationDetailCode=1000">https://m.blog.naver.com/PostView.naver?blogId=wngjs3&amp;logNo=222061315403&amp;fromRecommendationType=category&amp;targetRecommendationDetailCode=1000</a></li>
<li><a href="https://m.blog.naver.com/PostView.naver?blogId=wngjs3&amp;logNo=222067721866&amp;fromRecommendationType=category&amp;targetRecommendationDetailCode=1000">https://m.blog.naver.com/PostView.naver?blogId=wngjs3&amp;logNo=222067721866&amp;fromRecommendationType=category&amp;targetRecommendationDetailCode=1000</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새나] 3월 3주차 화요일 - 프로그래머스]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EB%82%98-3%EC%9B%94-3%EC%A3%BC%EC%B0%A8-%EC%9B%94%EC%9A%94%EC%9D%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-rronn0f0</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EB%82%98-3%EC%9B%94-3%EC%A3%BC%EC%B0%A8-%EC%9B%94%EC%9A%94%EC%9D%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-rronn0f0</guid>
            <pubDate>Tue, 19 Mar 2024 08:01:03 GMT</pubDate>
            <description><![CDATA[<h1 id="pro2-게임-맵-최단거리">[pro2] 게임 맵 최단거리</h1>
<h2 id="문제-설명">문제 설명</h2>
<p>ROR 게임은 두 팀으로 나누어서 진행하며, 상대 팀 진영을 먼저 파괴하면 이기는 게임입니다. 따라서, 각 팀은 상대 팀 진영에 최대한 빨리 도착하는 것이 유리합니다.</p>
<p>지금부터 당신은 한 팀의 팀원이 되어 게임을 진행하려고 합니다. 다음은 5 x 5 크기의 맵에, 당신의 캐릭터가 (행: 1, 열: 1) 위치에 있고, 상대 팀 진영은 (행: 5, 열: 5) 위치에 있는 경우의 예시입니다.
<img src="https://velog.velcdn.com/images/jeong_yooony/post/f8101ff3-8020-41af-9aca-edcf916ca393/image.png" alt=""></p>
<p>위 그림에서 검은색 부분은 벽으로 막혀있어 갈 수 없는 길이며, 흰색 부분은 갈 수 있는 길입니다. 캐릭터가 움직일 때는 동, 서, 남, 북 방향으로 한 칸씩 이동하며, 게임 맵을 벗어난 길은 갈 수 없습니다.
아래 예시는 캐릭터가 상대 팀 진영으로 가는 두 가지 방법을 나타내고 있습니다.</p>
<p>첫 번째 방법은 11개의 칸을 지나서 상대 팀 진영에 도착했습니다.
<img src="https://velog.velcdn.com/images/jeong_yooony/post/edd9e78e-8e3d-4612-8d65-7535b5af5957/image.png" alt=""></p>
<p>두 번째 방법은 15개의 칸을 지나서 상대팀 진영에 도착했습니다.
<img src="https://velog.velcdn.com/images/jeong_yooony/post/e4860204-2df0-470e-9df6-432de82b04df/image.png" alt=""></p>
<p>위 예시에서는 첫 번째 방법보다 더 빠르게 상대팀 진영에 도착하는 방법은 없으므로, 이 방법이 상대 팀 진영으로 가는 가장 빠른 방법입니다.</p>
<p>만약, 상대 팀이 자신의 팀 진영 주위에 벽을 세워두었다면 상대 팀 진영에 도착하지 못할 수도 있습니다. 예를 들어, 다음과 같은 경우에 당신의 캐릭터는 상대 팀 진영에 도착할 수 없습니다.
<img src="https://velog.velcdn.com/images/jeong_yooony/post/22242405-4722-4e5d-a90e-68f7e6884c6a/image.png" alt=""></p>
<p>게임 맵의 상태 maps가 매개변수로 주어질 때, 캐릭터가 상대 팀 진영에 도착하기 위해서 지나가야 하는 칸의 개수의 최솟값을 return 하도록 solution 함수를 완성해주세요. 단, 상대 팀 진영에 도착할 수 없을 때는 -1을 return 해주세요.</p>
<h3 id="제한사항">제한사항</h3>
<p>maps는 n x m 크기의 게임 맵의 상태가 들어있는 2차원 배열로, n과 m은 각각 1 이상 100 이하의 자연수입니다.
n과 m은 서로 같을 수도, 다를 수도 있지만, n과 m이 모두 1인 경우는 입력으로 주어지지 않습니다.
maps는 0과 1로만 이루어져 있으며, 0은 벽이 있는 자리, 1은 벽이 없는 자리를 나타냅니다.
처음에 캐릭터는 게임 맵의 좌측 상단인 (1, 1) 위치에 있으며, 상대방 진영은 게임 맵의 우측 하단인 (n, m) 위치에 있습니다.
입출력 예
maps    answer
[[1,0,1,1,1],[1,0,1,0,1],[1,0,1,1,1],[1,1,1,0,1],[0,0,0,0,1]]    11
[[1,0,1,1,1],[1,0,1,0,1],[1,0,1,1,1],[1,1,1,0,0],[0,0,0,0,1]]    -1</p>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<h4 id="입출력-예-1">입출력 예 #1</h4>
<p>주어진 데이터는 다음과 같습니다.
<img src="https://velog.velcdn.com/images/jeong_yooony/post/fc52840a-aed9-4f52-b3bb-12c1da9e2f13/image.png" alt=""></p>
<p>캐릭터가 적 팀의 진영까지 이동하는 가장 빠른 길은 다음 그림과 같습니다.
<img src="https://velog.velcdn.com/images/jeong_yooony/post/5df8fc43-ecd7-4712-b024-15aadd600fff/image.png" alt=""></p>
<p>따라서 총 11칸을 캐릭터가 지나갔으므로 11을 return 하면 됩니다.</p>
<h3 id="입출력-예-2">입출력 예 #2</h3>
<p>문제의 예시와 같으며, 상대 팀 진영에 도달할 방법이 없습니다. 따라서 -1을 return 합니다.</p>
<pre><code class="language-python">from collections import deque

def solution(maps):
    answer = 0
    n = len(maps)
    m = len(maps[0])
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]

    def BFS(x, y):
        queue = deque()
        queue.append((x,y))
        while queue:
            x, y = queue.popleft()
            for i in range(4):
                nx = x + dx[i]
                ny = y + dy[i]
                if nx &lt; 0 or nx &gt;= n or ny &lt; 0 or ny &gt;= m:
                    continue
                if maps[nx][ny] == 0:
                    continue
                if maps[nx][ny] == 1:
                    maps[nx][ny] = maps[x][y] + 1
                    queue.append((nx,ny))
                if nx == n-1 and ny == m-1:
                    return maps[n-1][m-1]
        if nx != n-1 or ny != m-1:
            return -1

    answer = BFS(0, 0)
    return answer</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새나] 3월 3주차 월요일 - 프로그래머스]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EB%82%98-3%EC%9B%94-3%EC%A3%BC%EC%B0%A8-%EC%9B%94%EC%9A%94%EC%9D%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EB%82%98-3%EC%9B%94-3%EC%A3%BC%EC%B0%A8-%EC%9B%94%EC%9A%94%EC%9D%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4</guid>
            <pubDate>Mon, 18 Mar 2024 08:14:55 GMT</pubDate>
            <description><![CDATA[<h1 id="뒤에-있는-큰-수-찾기">뒤에 있는 큰 수 찾기</h1>
<h2 id="문제-설명">문제 설명</h2>
<p>정수로 이루어진 배열 numbers가 있습니다. 배열 의 각 원소들에 대해 자신보다 뒤에 있는 숫자 중에서 자신보다 크면서 가장 가까이 있는 수를 뒷 큰수라고 합니다.
정수 배열 numbers가 매개변수로 주어질 때, 모든 원소에 대한 뒷 큰수들을 차례로 담은 배열을 return 하도록 solution 함수를 완성해주세요. 단, 뒷 큰수가 존재하지 않는 원소는 -1을 담습니다.</p>
<h3 id="제한사항">제한사항</h3>
<p>4 ≤ numbers의 길이 ≤ 1,000,000
1 ≤ numbers[i] ≤ 1,000,000</p>
<h3 id="입출력-예">입출력 예</h3>
<p>numbers    result
[2, 3, 3, 5]    [3, 5, 5, -1]
[9, 1, 5, 3, 6, 2]    [-1, 5, 6, 6, -1, -1]</p>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<h4 id="입출력-예-1">입출력 예 #1</h4>
<p>2의 뒷 큰수는 3입니다. 첫 번째 3의 뒷 큰수는 5입니다. 두 번째 3 또한 마찬가지입니다. 5는 뒷 큰수가 없으므로 -1입니다. 위 수들을 차례대로 배열에 담으면 [3, 5, 5, -1]이 됩니다.</p>
<h4 id="입출력-예-2">입출력 예 #2</h4>
<p>9는 뒷 큰수가 없으므로 -1입니다. 1의 뒷 큰수는 5이며, 5와 3의 뒷 큰수는 6입니다. 6과 2는 뒷 큰수가 없으므로 -1입니다. 위 수들을 차례대로 배열에 담으면 [-1, 5, 6, 6, -1, -1]이 됩니다.</p>
<h3 id="풀이방법">풀이방법</h3>
<ul>
<li>numbers: [2,3,3,5]</li>
<li>answer: [-1,-1,-1,-1]</li>
<li>stack: []</li>
<li><blockquote>
<p>비교하고 값이 더 작을 경우 인덱스 값을 append, 비교하고 값이 더 클 경우 answer에 해당 인덱스에 해당하는 값을 answer에 저장.</p>
</blockquote>
</li>
</ul>
<h3 id="코드">코드</h3>
<p><strong>정답</strong></p>
<pre><code class="language-python">def solution(numbers):
    answer = [-1] * len(numbers)
    stack = []
    for i in range (len(numbers)): # 기준 값
        while stack and numbers[stack[-1]] &lt; numbers[i]:
                answer[stack.pop()] = numbers[i]

        stack.append(i)      
    return answer</code></pre>
<p><strong>시간초과</strong></p>
<pre><code class="language-python">def solution(numbers):
    answer = []
    for i in range (len(numbers)): # 기준 값
        for j in range (i, len(numbers)): # 비교할 값
            if(numbers[i] &lt; numbers[j]):
                answer.append(numbers[j])
                break
            elif(j == len(numbers)-1):
                answer.append(-1)
    return answer</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[와일트루] 2월 1-2주차 : 0212-0218]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%99%80%EC%9D%BC%ED%8A%B8%EB%A3%A8-2%EC%9B%94-1-2%EC%A3%BC%EC%B0%A8-0212-0218</link>
            <guid>https://velog.io/@jeong_yooony/%EC%99%80%EC%9D%BC%ED%8A%B8%EB%A3%A8-2%EC%9B%94-1-2%EC%A3%BC%EC%B0%A8-0212-0218</guid>
            <pubDate>Sun, 18 Feb 2024 12:46:36 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-1011-fly-me-to-the-alpha-centauri">📌 1011. Fly me to the Alpha Centauri</h1>
<h2 id="문제">문제</h2>
<p>우현이는 어린 시절, 지구 외의 다른 행성에서도 인류들이 살아갈 수 있는 미래가 오리라 믿었다. 그리고 그가 지구라는 세상에 발을 내려 놓은 지 23년이 지난 지금, 세계 최연소 ASNA 우주 비행사가 되어 새로운 세계에 발을 내려 놓는 영광의 순간을 기다리고 있다.</p>
<p>그가 탑승하게 될 우주선은 Alpha Centauri라는 새로운 인류의 보금자리를 개척하기 위한 대규모 생활 유지 시스템을 탑재하고 있기 때문에, 그 크기와 질량이 엄청난 이유로 최신기술력을 총 동원하여 개발한 공간이동 장치를 탑재하였다. 하지만 이 공간이동 장치는 이동 거리를 급격하게 늘릴 경우 기계에 심각한 결함이 발생하는 단점이 있어서, 이전 작동시기에 k광년을 이동하였을 때는 k-1 , k 혹은 k+1 광년만을 다시 이동할 수 있다. 예를 들어, 이 장치를 처음 작동시킬 경우 -1 , 0 , 1 광년을 이론상 이동할 수 있으나 사실상 음수 혹은 0 거리만큼의 이동은 의미가 없으므로 1 광년을 이동할 수 있으며, 그 다음에는 0 , 1 , 2 광년을 이동할 수 있는 것이다. ( 여기서 다시 2광년을 이동한다면 다음 시기엔 1, 2, 3 광년을 이동할 수 있다. )</p>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/1a17948a-fef8-420c-82a8-c048f3c3d8a6/image.png" alt=""></p>
<p>김우현은 공간이동 장치 작동시의 에너지 소모가 크다는 점을 잘 알고 있기 때문에 x지점에서 y지점을 향해 최소한의 작동 횟수로 이동하려 한다. 하지만 y지점에 도착해서도 공간 이동장치의 안전성을 위하여 y지점에 도착하기 바로 직전의 이동거리는 반드시 1광년으로 하려 한다.</p>
<p>김우현을 위해 x지점부터 정확히 y지점으로 이동하는데 필요한 공간 이동 장치 작동 횟수의 최솟값을 구하는 프로그램을 작성하라.</p>
<h3 id="입력">입력</h3>
<p>입력의 첫 줄에는 테스트케이스의 개수 T가 주어진다. 각각의 테스트 케이스에 대해 현재 위치 x 와 목표 위치 y 가 정수로 주어지며, x는 항상 y보다 작은 값을 갖는다. (0 ≤ x &lt; y &lt; 231)</p>
<h3 id="출력">출력</h3>
<p>각 테스트 케이스에 대해 x지점으로부터 y지점까지 정확히 도달하는데 필요한 최소한의 공간이동 장치 작동 횟수를 출력한다.</p>
<h3 id="예제-입력-1">예제 입력 1</h3>
<p>3
0 3
1 5
45 50</p>
<h3 id="예제-출력-1">예제 출력 1</h3>
<p>3
3
4</p>
<h3 id="알고리즘-분류">알고리즘 분류</h3>
<ul>
<li>수학</li>
</ul>
<h3 id="코드---python3-성공">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

t = int(input())

for _ in range(t):
    x, y = map(int,input().split())
    distance = y - x
    count = 0  # 이동 횟수
    move = 1  # count별 이동 가능한 거리
    move_plus = 0  # 이동한 거리의 합
    while move_plus &lt; distance :
        count += 1
        move_plus += move  # count 수에 해당하는 move를 더함
        # 이동 회수를 나타내는 숫자의 빈도수가 1,1,2,2,3,3,4,4 두 번씩 나타나는 규칙
        if count % 2 == 0 :  # count가 2의 배수일 때,
            move += 1
    print(count)</code></pre>
<hr>
<h1 id="📌-12904-a와-b">📌 12904. A와 B</h1>
<h2 id="문제-1">문제</h2>
<p>수빈이는 A와 B로만 이루어진 영어 단어가 존재한다는 사실에 놀랐다. 대표적인 예로 AB (Abdominal의 약자), BAA (양의 울음 소리), AA (용암의 종류), ABBA (스웨덴 팝 그룹)이 있다.</p>
<p>이런 사실에 놀란 수빈이는 간단한 게임을 만들기로 했다. 두 문자열 S와 T가 주어졌을 때, S를 T로 바꾸는 게임이다. 문자열을 바꿀 때는 다음과 같은 두 가지 연산만 가능하다.</p>
<ul>
<li>문자열의 뒤에 A를 추가한다.</li>
<li>문자열을 뒤집고 뒤에 B를 추가한다.
주어진 조건을 이용해서 S를 T로 만들 수 있는지 없는지 알아내는 프로그램을 작성하시오. </li>
</ul>
<h3 id="입력-1">입력</h3>
<p>첫째 줄에 S가 둘째 줄에 T가 주어진다. (1 ≤ S의 길이 ≤ 999, 2 ≤ T의 길이 ≤ 1000, S의 길이 &lt; T의 길이)</p>
<h3 id="출력-1">출력</h3>
<p>S를 T로 바꿀 수 있으면 1을 없으면 0을 출력한다.</p>
<h3 id="예제-입력-1-1">예제 입력 1</h3>
<p>B
ABBA</p>
<h3 id="예제-출력-1-1">예제 출력 1</h3>
<p>1</p>
<h3 id="예제-입력-2">예제 입력 2</h3>
<p>AB
ABB</p>
<h3 id="예제-출력-2">예제 출력 2</h3>
<p>0</p>
<h3 id="알고리즘-분류-1">알고리즘 분류</h3>
<ul>
<li>구현</li>
<li>그리디 알고리즘</li>
<li>문자열</li>
</ul>
<h3 id="코드---python3-성공-1">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

# 입력으로부터 문자열 S와 T를 받아 리스트로 변환
s = list(input().rstrip())
t = list(input().rstrip())

switch = False  # S를 T로 바꿀 수 있는지 여부를 나타내는 변수 초기화
while t:
    if t[-1] == &#39;A&#39;:  # T의 마지막 문자가 &#39;A&#39;일 경우
        t.pop()  # T의 마지막 문자를 제거
    elif t[-1] == &#39;B&#39;:  # T의 마지막 문자가 &#39;B&#39;일 경우
        t.pop()  # T의 마지막 문자를 제거
        t.reverse()  # T를 뒤집음
    if s == t:  # 만약 S와 T가 같아졌다면
        switch = True  # switch 변수를 True로 변경하고 반복문 종료
        break

# 결과 출력
if switch:
    print(1)  # S를 T로 바꿀 수 있는 경우
else:
    print(0)  # S를 T로 바꿀 수 없는 경우</code></pre>
<hr>
<h1 id="📌-1339-단어-수학">📌 1339. 단어 수학</h1>
<h2 id="문제-2">문제</h2>
<p>민식이는 수학학원에서 단어 수학 문제를 푸는 숙제를 받았다.</p>
<p>단어 수학 문제는 N개의 단어로 이루어져 있으며, 각 단어는 알파벳 대문자로만 이루어져 있다. 이때, 각 알파벳 대문자를 0부터 9까지의 숫자 중 하나로 바꿔서 N개의 수를 합하는 문제이다. 같은 알파벳은 같은 숫자로 바꿔야 하며, 두 개 이상의 알파벳이 같은 숫자로 바뀌어지면 안 된다.</p>
<p>예를 들어, GCF + ACDEB를 계산한다고 할 때, A = 9, B = 4, C = 8, D = 6, E = 5, F = 3, G = 7로 결정한다면, 두 수의 합은 99437이 되어서 최대가 될 것이다.</p>
<p>N개의 단어가 주어졌을 때, 그 수의 합을 최대로 만드는 프로그램을 작성하시오.</p>
<h3 id="입력-2">입력</h3>
<p>첫째 줄에 단어의 개수 N(1 ≤ N ≤ 10)이 주어진다. 둘째 줄부터 N개의 줄에 단어가 한 줄에 하나씩 주어진다. 단어는 알파벳 대문자로만 이루어져있다. 모든 단어에 포함되어 있는 알파벳은 최대 10개이고, 수의 최대 길이는 8이다. 서로 다른 문자는 서로 다른 숫자를 나타낸다.</p>
<h3 id="출력-2">출력</h3>
<p>첫째 줄에 주어진 단어의 합의 최댓값을 출력한다.</p>
<h3 id="예제-입력-1-2">예제 입력 1</h3>
<p>2
AAA
AAA</p>
<h3 id="예제-출력-1-2">예제 출력 1</h3>
<p>1998</p>
<h3 id="예제-입력-2-1">예제 입력 2</h3>
<p>2
GCF
ACDEB</p>
<h3 id="예제-출력-2-1">예제 출력 2</h3>
<p>99437</p>
<h3 id="예제-입력-3">예제 입력 3</h3>
<p>10
A
B
C
D
E
F
G
H
I
J</p>
<h3 id="예제-출력-3">예제 출력 3</h3>
<p>45</p>
<h3 id="예제-입력-4">예제 입력 4</h3>
<p>2
AB
BA</p>
<h3 id="예제-출력-4">예제 출력 4</h3>
<p>187</p>
<h3 id="알고리즘-분류-2">알고리즘 분류</h3>
<ul>
<li>그리디 알고리즘</li>
</ul>
<h3 id="코드---python3-성공-2">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

N = int(input())
S = [input().strip() for _ in range(N)]
words = {} # 단어별 값을 지정
for s in S:
    x = len(s)-1 # 10의 제곱을 해줄 값
    for i in s :
        if i in words:
            words[i] += 10**x # 있으면 x만큼 제곱한걸 더하고
        else :
            words[i] = 10**x # 없으면 x만큼 제곱해서 넣자
        x -= 1

words_sort = sorted(words.values(),reverse=True) # 딕셔너리의 value만 내림차순으로 가져오자
result = 0
num = 9
for k in words_sort:
    result += k * num # 내림차순 한거에 9부터 하나씩 곱해서 더해주자
    num -= 1
print(result)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Figma] 피그마 잘 활용하기]]></title>
            <link>https://velog.io/@jeong_yooony/Figma-%ED%94%BC%EA%B7%B8%EB%A7%88-%EC%9E%98-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jeong_yooony/Figma-%ED%94%BC%EA%B7%B8%EB%A7%88-%EC%9E%98-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 14 Feb 2024 00:54:47 GMT</pubDate>
            <description><![CDATA[<h1 id="그룹화">그룹화</h1>
<p><a href="https://antlerstudio.tistory.com/87">컴포넌트와 그룹화 차이</a></p>
<h1 id="컴포넌트">컴포넌트</h1>
<p><a href="https://brunch.co.kr/@applehong/14">컴포넌트 만들기</a>
<a href="https://yetis.tistory.com/79">컴포넌트 해제</a></p>
<h1 id="프레임">프레임</h1>
<p><a href="https://brunch.co.kr/@smu00/2">프레임이란?</a></p>
<h1 id="플로우-및-프로토타입">플로우 및 프로토타입</h1>
<p><a href="https://brunch.co.kr/@applehong/21">피그마 기본 기능 총정리</a></p>
<h1 id="데브모드">데브모드</h1>
<p><a href="https://heeeming.tistory.com/entry/Figma-%ED%94%BC%EA%B7%B8%EB%A7%88-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%AA%A8%EB%93%9C-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EB%B0%8F-VSCode-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8Figma-Dev-mode-with-Figma-for-VS-Code">피그마 개발자 모드</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240213~240315 최종 프로젝트 - React]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240213-%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-React</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240213-%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-React</guid>
            <pubDate>Wed, 14 Feb 2024 00:06:25 GMT</pubDate>
            <description><![CDATA[<p><strong>240213 기록</strong></p>
<h1 id="프론트엔드-환경-구축하기">프론트엔드 환경 구축하기</h1>
<ul>
<li>react</li>
<li>typescript</li>
<li>redux</li>
<li>recoil</li>
<li>toolkit</li>
<li><a href="https://velog.io/@vltea/%EA%B0%9C%EC%9D%B8%EC%A0%81%EC%9D%B8-2022%EB%85%84-style-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90">Tailwind</a></li>
<li>scss</li>
</ul>
<h2 id="설치하기"><a href="https://what-whale-wants-to-say-is.tistory.com/123">설치하기</a></h2>
<h3 id="1-타입스크립트가-적용된-리액트-프로젝트-셋팅하기">1. 타입스크립트가 적용된 리액트 프로젝트 셋팅하기</h3>
<p><a href="https://create-react-app.dev/docs/adding-typescript/#installation">create-react-app 홈페이지</a></p>
<pre><code class="language-bash">npx create-react-app my-app --template typescript</code></pre>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/224f5b75-b55b-4363-aa59-67cda689dda0/image.png" alt=""></p>
<h3 id="2-tailwind-css-설치하고-적용하기">2. tailwind css 설치하고 적용하기</h3>
<p><a href="https://tailwindcss.com/docs/guides/create-react-app">tailwind css 공식 홈페이지</a></p>
<pre><code class="language-bash">npm install -D tailwindcss postcss autoprefixer</code></pre>
<pre><code class="language-bash">npx tailwindcss init -p</code></pre>
<p>▶️ tailwind.config.js 랑 postcss.config.js 파일 생성</p>
<p><strong>tailwind.config.js 수정하기</strong></p>
<pre><code class="language-js">/** @type {import(&#39;tailwindcss&#39;).Config} */
module.exports = {
  content: [
    &quot;./src/**/*.{js,jsx,ts,tsx}&quot;,
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}</code></pre>
<p><strong>src/index.css</strong></p>
<pre><code class="language-css">@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, &#39;Roboto&#39;, &#39;Oxygen&#39;,
    &#39;Ubuntu&#39;, &#39;Cantarell&#39;, &#39;Fira Sans&#39;, &#39;Droid Sans&#39;, &#39;Helvetica Neue&#39;,
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, &#39;Courier New&#39;,
    monospace;
}</code></pre>
<p><strong>App.tsx</strong></p>
<pre><code class="language-ts">import React from &#39;react&#39;;
import logo from &#39;./logo.svg&#39;;
import &#39;./App.css&#39;;

function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;h1 className=&quot;text-3xl font-bold underline&quot;&gt;
        Hello world!
      &lt;/h1&gt;
      {/* &lt;header className=&quot;App-header&quot;&gt;
        &lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&gt;
        &lt;p&gt;
          Edit &lt;code&gt;src/App.tsx&lt;/code&gt; and save to reload.
        &lt;/p&gt;
        &lt;a
          className=&quot;App-link&quot;
          href=&quot;https://reactjs.org&quot;
          target=&quot;_blank&quot;
          rel=&quot;noopener noreferrer&quot;
        &gt;
          Learn React
        &lt;/a&gt;
      &lt;/header&gt; */}
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<pre><code class="language-bash">npm run start</code></pre>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/7567b3dd-a244-4b2b-90a6-52efc7623fa2/image.png" alt=""></p>
<h3 id="3-craco-설치"><a href="https://velog.io/@uomnf97_web/TailwindCSS%EB%A1%9C-Header%EC%99%80-Footer-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0">3. Craco 설치</a></h3>
<p>create-react-app은 PostCSS를 지원해주지만, 재정의를 할 수 없기에 TailwindCSS를 이용하기에 다양한 제약이 따릅니다. 따라서 CRACO를 설치하고, craco.config.js파일을 추가해서 쉽고 다양하게 커스터마이징을 할 수 있도록 설정해줍니다.</p>
<pre><code class="language-bash">yarn add @craco/craco
npm install  @craco/craco</code></pre>
<p><strong>craco.config.js</strong></p>
<pre><code class="language-jsx">module.exports = {
  style: {
      postcssOptions: {
          plugins: [require(&#39;tailwindcss&#39;), require(&#39;autoprefixer&#39;)],
      },
  },
};</code></pre>
<p><strong>package.json</strong></p>
<pre><code class="language-json">  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;react-scripts start&quot;,
    &quot;build&quot;: &quot;react-scripts build&quot;,
    &quot;test&quot;: &quot;react-scripts test&quot;,
    &quot;eject&quot;: &quot;react-scripts eject&quot;
  }</code></pre>
<p>▶️ 위 코드를 아래 코드와 같이 수정</p>
<pre><code class="language-json"> &quot;scripts&quot;: {
    &quot;start&quot;: &quot;craco start&quot;,
    &quot;build&quot;: &quot;craco build&quot;,
    &quot;test&quot;: &quot;craco test&quot;,
    &quot;eject&quot;: &quot;react-scripts eject&quot;
  }</code></pre>
<hr>
<h1 id="tailwind-css-반응형-메뉴-navbar-만들기"><a href="https://cpro95.tistory.com/531">Tailwind css 반응형 메뉴 navbar 만들기</a></h1>
<p><strong>패키지 설치</strong></p>
<pre><code class="language-bash">npm install classnames
npm install react-router-dom
npm install react-router-dom@latest</code></pre>
<h2 id="header-router-연결하기"><a href="https://velog.io/@seul_/React-%EB%B0%98%EC%9D%91%ED%98%95-%ED%97%A4%EB%8D%94-useState-router-styled-components">Header Router 연결하기</a></h2>
<h2 id="scss-사용하기"><a href="https://velog.io/@jin_jin_dev/sassscss-react-scss-%EC%82%AC%EC%9A%A9%EB%B2%95">SCSS 사용하기</a></h2>
<pre><code class="language-bash">npm install sass</code></pre>
<h2 id="이미지-추가하기"><a href="https://iancoding.tistory.com/222#google_vignette">이미지 추가하기</a></h2>
<hr>
<h1 id="홈화면-구현하기">홈화면 구현하기</h1>
<h2 id="배너-만들기"><a href="https://jae04099.tistory.com/entry/Tailwindcss-%EB%AC%B4%ED%95%9C%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EC%96%B4%EC%A7%80%EB%8A%94-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%8D%94-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0feat%EB%A6%AC%EC%95%A1%ED%8A%B8">배너 만들기</a></h2>
<p><strong>패키지 설치</strong></p>
<pre><code class="language-bash">npm install swiper</code></pre>
<h2 id="무한-슬라이드"><a href="https://velog.io/@rkio/React-%EB%AC%B4%ED%95%9C-%EC%9E%90%EB%8F%99-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0ft.-Typescript-Tailwind#semantic-tag">무한 슬라이드</a></h2>
<h2 id="슬라이드-배너"><a href="https://flowbite.com/docs/components/carousel/">슬라이드 배너</a></h2>
<h3 id="리액트버전"><a href="https://www.flowbite-react.com/docs/components/carousel">리액트버전</a></h3>
<hr>
<h1 id="소개페이지-구현하기"><a href="https://d8040.tistory.com/117">소개페이지 구현하기</a></h1>
<hr>
<h1 id="로그인-페이지-구현하기"><a href="https://tw-elements.com/docs/standard/forms/registration-form/">로그인 페이지 구현하기</a></h1>
<hr>
<p><strong>240215 기록</strong></p>
<h1 id="홈화면-구현하기-1">홈화면 구현하기</h1>
<h2 id="슬라이드-배너-구현">슬라이드 배너 구현</h2>
<h3 id="react-slick-사용하기"><a href="https://blog.naver.com/PostView.naver?blogId=jaeeun_98&amp;logNo=222835174514">react-slick 사용하기</a></h3>
<p><strong>패키지 설치</strong></p>
<pre><code class="language-bash"># react-slick 사용하기
npm install react-slick

# react-slick에서 css 수정하고 싶다면
npm install slick-carousel

npm install --save-dev @types/react-slick</code></pre>
<hr>
<p><strong>240219 기록</strong></p>
<h1 id="홈화면">홈화면</h1>
<h2 id="리액트에서-스크롤드래그-둘-다-되는-슬라이더"><a href="https://wnsdufdl.tistory.com/518#recentComments">리액트에서 스크롤,드래그 둘 다 되는 슬라이더</a></h2>
<h2 id="폰트적용"><a href="https://enne.tistory.com/6">폰트적용</a></h2>
<ul>
<li><a href="https://noonnu.cc/font_page/38">스퀘어나눔라운드</a></li>
</ul>
<h2 id="케러셀"><a href="https://1yoouoo.tistory.com/26">케러셀</a></h2>
<h2 id="카드-케러셀"><a href="https://stickode.tistory.com/988">카드 케러셀</a></h2>
<h1 id="로그인화면">로그인화면</h1>
<h2 id="폼작성"><a href="https://flowbite.com/docs/components/forms/">폼작성</a></h2>
<hr>
<p><strong>240220 기록</strong></p>
<h1 id="주문페이지">주문페이지</h1>
<h2 id="select-구현">select 구현</h2>
<pre><code class="language-bash">npm install tw-elements</code></pre>
<h2 id="모달구현"><a href="https://flowbite.com/docs/components/modal/">모달구현</a></h2>
<h2 id="토스페이구현"><a href="https://velog.io/@tosspayments/React%EB%A1%9C-%EA%B2%B0%EC%A0%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-ft.-%EA%B2%B0%EC%A0%9C%EC%9C%84%EC%A0%AF">토스페이구현</a></h2>
<pre><code class="language-bash">npm install @tosspayments/payment-widget-sdk
npm install nanoid</code></pre>
<h1 id="마이페이지-구현">마이페이지 구현</h1>
<h2 id="중첩라우팅"><a href="https://rinn-story.tistory.com/30">중첩라우팅</a></h2>
<hr>
<p><strong>240227 기록</strong></p>
<h1 id="모달창-만들기">모달창 만들기</h1>
<pre><code class="language-bash">npm install @emotion/react
npm install @emotion/styled
</code></pre>
<p><strong>[참고링크]</strong></p>
<ul>
<li><a href="https://phrygia.github.io/2021-09-21-react-modal/">https://phrygia.github.io/2021-09-21-react-modal/</a></li>
</ul>
<hr>
<p><strong>240301 기록</strong></p>
<h1 id="리스토어-신청페이지-구현">리스토어 신청페이지 구현</h1>
<h2 id="사진첨부-구현하기"><a href="https://velog.io/@mary0393/React-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%88%8C%EB%9F%AC%EC%84%9C-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B0-%EA%B8%B0%EB%8A%A5">사진첨부 구현하기</a></h2>
<h2 id="div-스크롤하기"><a href="https://rateye.tistory.com/916">div 스크롤하기</a></h2>
<h2 id="아이콘-추가하기"><a href="https://react-icons.github.io/react-icons/">아이콘 추가하기</a></h2>
<pre><code class="language-bash">npm install react-icons</code></pre>
<h2 id="파일업로드-버튼-꾸미기"><a href="https://et-tu-coding.tistory.com/entry/CSS-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%EB%B2%84%ED%8A%BC-%EC%98%88%EC%81%98%EA%B2%8C-%EB%B0%94%EA%BE%B8%EA%B8%B0">파일업로드 버튼 꾸미기</a></h2>
<h2 id="주소검색기능"><a href="https://dlsgh120.tistory.com/54">주소검색기능</a></h2>
<pre><code class="language-bash">npm install react-daum-postcode</code></pre>
<hr>
<p><strong>MyRestore.tsx</strong></p>
<pre><code>import &quot;../../../Components_scss/MyRestore.scss&quot;
import { useImage } from &quot;../../common/hooks/useImage&quot;;
import { useEffect, useRef, useState } from &quot;react&quot;
import {Form, useActionData} from &quot;react-router-dom&quot;;
import ModalBase from &#39;../../../Components/ModalBase&#39;;
import CardModal from &#39;../../../Components/CardModal&#39;;
import {FormMessage} from &quot;../../../common/FormMessage&quot;;
import {getApi} from &quot;../../../api/ApiWrapper&quot;;
import {PageOrderResDto, ProductDto} from &quot;../../../api/Api&quot;;

interface State {
  id: string;
  value: string;
  label: string;
  desc: string;
}

const stateList: State[] = [
  { id: &#39;1&#39;, value: &#39;S&#39;, label: &#39;S - 가장 낮았던 판매가격의 50%&#39;, desc: &#39;흠집이 없으며 새 것과 동일한 상태&#39;},
  { id: &#39;2&#39;, value: &#39;A&#39;, label: &#39;A - 가장 낮았던 판매가격의 40%&#39;, desc: &#39;경미한 흠집이 있으나 전반적으로 양호한 상태&#39;},
  { id: &#39;3&#39;, value: &#39;B&#39;, label: &#39;B - 가장 낮았던 판매가격의 30%&#39;, desc: &#39;흠집 다소 있으며 사용감이 있는 상태&#39;},
];

const MyRestore = () =&gt; {
  const image = useImage()
  const [orderHistoryItem, setOrderHistoryItem] = useState&lt;ProductDto[]&gt;([])
  const error = useActionData() as FormMessage
  const formRef = useRef&lt;HTMLFormElement | null&gt;(null);

  // 리스토어 항목 불러오기
  useEffect(() =&gt; {
    async function fetchOrderHistory() {
        try {
            const products: ProductDto[] = []
            const api = await getApi()
            const myOrders = (await api.getOrders({page: 0, pageSize: 5}, {})).data as PageOrderResDto
            if (myOrders.content !== undefined) {
                for (let p of myOrders.content) {
                    if (p.products === undefined) continue
                    for (let product of p.products) {
                        try {
                            // @ts-ignore
                            const res = (await api.getProduct1(product.productId)).data as ProductDto
                            products.push(res)

                        } catch (e) {

                        }
                    }
                }
                setOrderHistoryItem(products)
            }
        } catch (e) {
        }
    }

    fetchOrderHistory().then()
  }, []);

  // 모달 기능
  const [isActive, setIsActive] = useState(false);
  const onClickModalOn = () =&gt; {
    setIsActive(true);
  };
  const onClickModalOff = () =&gt; {
    setIsActive(false);
  };
  const onClickCardConfirm = () =&gt; {
    // 모달을 닫고 캐시 비우기
    onClickModalOff();
    setImgFile(undefined);
    setRestoreImgPath(&quot;&quot;);
    if (imgRef.current) {
      imgRef.current.value = &quot;&quot;;
    }
    alert(&#39;리스토어가 신청되었습니다.&#39;);
  };

  const handleFormSubmit = (event: React.FormEvent) =&gt; {
    event.preventDefault();

    console.log(&quot;Restoration Data:&quot;);
    console.log(&quot;Selected State:&quot;, restoreGrade);
    console.log(&quot;Image File:&quot;, imgFile);
    console.log(&quot;Restore Description:&quot;, restoreDesc);
    console.log(&quot;리스토어 이미지 경로:&quot;, restoreImgPath);
  };


  // 상태 선택 기능
  const [restoreGrade, setRestoreGrade] = useState&lt;string&gt;();
  // console.log(`Selected state: ${restoreGrade}`);

  // 사진 첨부 기능
  const [imgFile, setImgFile] = useState&lt;File&gt;();
  const [restoreImgPath, setRestoreImgPath] = useState&lt;string&gt;();
  const imgRef = useRef&lt;HTMLInputElement&gt;(null);
  const MAX_IMAGE_SIZE_BYTES = 1024 * 1024 * 2;
  // console.log(restoreImgPath);

  const previewImage = () =&gt; {
    if (imgRef.current &amp;&amp; imgRef.current.files) {
      const img = imgRef.current.files[0];
      setImgFile(img);

      //이미지 미리보기 기능
      const reader = new FileReader();
      reader.readAsDataURL(img);
      reader.onload = () =&gt; {
        setRestoreImgPath(reader.result as string);
      };
    }
  };

  // 상품 설명
  const [restoreDesc, setRestoreDesc] = useState&lt;String&gt;();
  // console.log(restoreDesc);

  return (
    &lt;div className=&quot;MyRestore&quot;&gt;
      &lt;div className=&quot;MyRestoreWrapper mb-3&quot;&gt;
        &lt;div className=&quot;MyRestoreWrapperTitle&quot;&gt;리스토어 신청&lt;/div&gt;
        &lt;div className=&quot;MyRestoreSearchWrapper&quot;&gt;
          &lt;div className=&quot;MyRestoreSearch relative mb-4 flex w-full flex-wrap items-stretch&quot;&gt;
            &lt;input
              type=&quot;search&quot;
              className=&quot;MyRestoreSearchInput ㅌrelative m-0 -mr-0.5 block min-w-0 flex-auto rounded-l border border-solid border-neutral-300 bg-transparent bg-clip-padding px-3 py-[0.25rem] text-base font-normal leading-[1.6] text-neutral-700 outline-none transition duration-200 ease-in-out focus:z-[3] focus:border-primary focus:text-neutral-700 focus:shadow-[inset_0_0_0_1px_rgb(59,113,202)] focus:outline-none dark:border-neutral-600 dark:text-neutral-200 dark:placeholder:text-neutral-200 dark:focus:border-primary&quot;
              placeholder=&quot;검색어를 입력하세요.&quot;
              aria-label=&quot;Search&quot;
              aria-describedby=&quot;button-addon3&quot; /&gt;
            &lt;button
              className=&quot;MyRestoreSearchBtn relative z-[2] rounded-r border-2 border-primary px-6 py-2 text-xs font-medium uppercase text-primary transition duration-150 ease-in-out hover:bg-black hover:bg-opacity-5 focus:outline-none focus:ring-0&quot;
              type=&quot;button&quot;
              id=&quot;button-addon3&quot;
              data-te-ripple-init&gt;
              검색
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div className=&quot;MyRestoreContent&quot;&gt;
          &lt;div className=&quot;MyRestoreContentItem&quot;&gt;
            &lt;img className=&quot;MyRestoreContentItemImg&quot; src={image(&quot;베스트1.jpeg&quot;)} title=&quot;pic&quot;&gt;&lt;/img&gt;
            &lt;div className=&quot;MyRestoreContentItemText&quot;&gt;
              &lt;div className=&quot;MyRestoreContentItemBrand&quot;&gt;코이&lt;/div&gt;
              &lt;div className=&quot;MyRestoreContentItemName&quot;&gt;코이 6단 이동식 책상세트&lt;/div&gt;
              &lt;div className=&quot;MyRestoreContentItemInfo&quot;&gt;사이즈: 1200 / 색상: 화이트&lt;/div&gt;
            &lt;/div&gt;
            &lt;button onClick={onClickModalOn} className=&quot;MyRestoreContentItemBtn&quot; title=&quot;신청하기&quot;&gt;신청하기&lt;/button&gt;
            &lt;ModalBase active={isActive} closeEvent={onClickModalOff}&gt;
              &lt;Form className=&quot;RestoreForm&quot; method={&quot;post&quot;}&gt;
                &lt;CardModal closeEvent={onClickModalOff} title=&quot;리스토어 신청하기&quot; actionMsg=&quot;확인&quot; actionEvent={onClickCardConfirm}&gt;
                  &lt;div className=&quot;RestoreModal&quot;&gt;
                    {/* 상품상태 */}
                    &lt;div className=&quot;RestoreModalState&quot;&gt;
                      &lt;div className=&quot;RestoreModalStateTitle&quot;&gt;제품상태&lt;/div&gt;
                      &lt;div className=&quot;RestoreModalStateContent&quot;&gt;
                      &lt;ul className=&quot;PreferenceTestRightAnswer space-y-4 mb-4&quot;&gt;
                        {stateList.map((state) =&gt; (
                          &lt;li key={state.id}&gt;
                            &lt;input
                              type=&quot;radio&quot;
                              id={state.id}
                              name=&quot;job&quot;
                              value={state.value}
                              className=&quot;hidden peer&quot;
                              onChange={() =&gt; setRestoreGrade(state.value)}
                              required
                            /&gt;
                            &lt;label
                              htmlFor={state.id}
                              className=&quot;inline-flex items-center justify-between w-full p-5 text-gray-900 bg-white border border-gray-200 rounded-lg cursor-pointer dark:hover:text-gray-300 dark:border-gray-500 dark:peer-checked:text-blue-500 peer-checked:border-blue-600 peer-checked:text-blue-600 hover:text-gray-900 hover:bg-gray-100 dark:text-white dark:bg-gray-600 dark:hover:bg-gray-500&quot;
                            &gt;
                              &lt;div className=&quot;block&quot;&gt;
                                &lt;div className=&quot;w-full text-lg font-semibold&quot;&gt;{state.label}&lt;/div&gt;
                                &lt;div className=&quot;w-full text-lg font-semibold&quot;&gt;{state.desc}&lt;/div&gt;
                              &lt;/div&gt;
                            &lt;/label&gt;
                          &lt;/li&gt;
                        ))}
                      &lt;/ul&gt;
                      &lt;/div&gt;
                    &lt;/div&gt;
                    {/* 상품사진 */}
                    &lt;div className=&quot;RestoreModalPic&quot;&gt;
                      &lt;div className=&quot;RestoreModalPicTitle&quot;&gt;상품사진&lt;/div&gt;
                      &lt;div className=&quot;RestoreModalPicContent&quot;&gt;
                        &lt;div className=&quot;RestoreModalPicContentText&quot;&gt;사진을 첨부해주세요.&lt;/div&gt;
                        &lt;div className=&quot;RestoreModalPicContentButton&quot;&gt;
                          &lt;label className=&quot;RestoreModalPicUploadPreviewLabel&quot; htmlFor=&quot;photo&quot;&gt;
                            &lt;img
                              //사용자가 이미지 파일을 업로드하면 해당 이미지를 보여주고, 없으면 기본 이미지를 보여준다.
                              className=&quot;RestoreModalPicUploadPreviewLImg&quot;
                              src={restoreImgPath ? restoreImgPath : image(&quot;upload.png&quot;)}
                              alt=&quot;사진 첨부하기&quot;
                            /&gt;
                          &lt;/label&gt;
                          &lt;label className=&quot;RestoreModalPicUploadInputLabel&quot; htmlFor=&quot;photo&quot;&gt;
                            사진 첨부하기
                            &lt;input
                              className=&quot;RestoreModalPicUploadInput&quot;
                              type=&quot;file&quot;
                              id=&quot;photo&quot;
                              name=&quot;photo&quot;
                              accept=&quot;.png, .jpeg, .jpg&quot;
                              onChange={previewImage}
                              ref={imgRef}
                            /&gt;
                            &lt;input type=&quot;hidden&quot; name=&quot;restoreImgPath&quot; 
                            onChange={() =&gt; setRestoreGrade(restoreImgPath)} /&gt;
                          &lt;/label&gt;
                        &lt;/div&gt;
                      &lt;/div&gt;
                    &lt;/div&gt;
                    {/* 상품설명 */}
                    &lt;div className=&quot;RestoreModalDescription&quot;&gt;
                      &lt;div className=&quot;RestoreModalDescriptionTitle&quot;&gt;상품설명&lt;/div&gt;
                      &lt;textarea 
                      className=&quot;RestoreModalDescriptionContent&quot;
                      placeholder=&quot;상품상태를 간략하게 설명해주세요.&quot; 
                      title=&quot;상품설명&quot;
                      onChange={(e) =&gt; setRestoreDesc(e.target.value)}
                      /&gt;
                    &lt;/div&gt;
                    {/* 안내사항 */}
                    &lt;div className=&quot;RestoreModalInfo&quot;&gt;
                      &lt;div className=&quot;RestoreModalInfoTitle&quot;&gt;위의 조건을 충족하지 못하나요?&lt;/div&gt;
                      &lt;div className=&quot;RestoreModalInfoContent&quot;&gt;조건에 맞지 않는 제품은 리스토어 판매가 어렵습니다. 
                      &lt;br/&gt;H.Livv 리스토어 서비스는 가구에 제2의 삶을 불어 넣을 수 있는 선택 중 하나일 뿐입니다. 가구를 폐기할 때가 되었다면 다른 재활용 방법을 고려해보세요.&lt;/div&gt;
                    &lt;/div&gt;
                  &lt;/div&gt;
                &lt;/CardModal&gt;
              &lt;/Form&gt;
            &lt;/ModalBase&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default MyRestore;</code></pre><p><strong>MyRestoreRouter.ts</strong></p>
<pre><code>import {FormMessage} from &quot;../../../common/FormMessage&quot;;
import {Api} from &quot;../../../api/ApiWrapper&quot;;
import {getAuthToken} from &quot;../../../api/auth/Token&quot;;

// @ts-ignore
export async function myRestoreAction({request, params}) {
  const formData = await request.formData()
  const formDataObj = Object.fromEntries(formData.entries());

  // 프론트에서 입력 받아올 값
  const {
    requestGrade,
    restoreDesc,
    restoreImageUrls
  } = formDataObj;

  // 입력값 유효성 확인
  const validationResult = validateRestoreInput(requestGrade, restoreDesc, restoreImageUrls)

  if (validationResult !== null){
    return validationResult
  }

  // 프론트에서 입력하지 않은 변수들을 임의의 값으로 채워넣기
  const filledProductId = 0;
  const filledPickUpDate = &quot;2024-03-09T10:34:33.145Z&quot;;
  const filledWhenRejected = true;

  try {
    const api = Api
    const headers = {
      Authorization: `Bearer ${getAuthToken()}`,
      // 다른 필요한 헤더도 추가할 수 있음
    };
    const result = await api.restoreRegister({
      productId: filledProductId,
      pickUpDate: filledPickUpDate,
      requestGrade: requestGrade ? requestGrade.toString() : &#39;&#39;,
      restoreDesc: restoreDesc ? restoreDesc.toString() : &#39;&#39;,
      whenRejected: filledWhenRejected,
      restoreImageUrls: restoreImageUrls ? restoreImageUrls.split(&#39;,&#39;) : []
    }, { headers })
    // 콘솔에 성공적인 응답을 출력
    console.log(&#39;Successful response:&#39;, result);
    return FormMessage.createFormMessage(&quot;리스토어 신청 성공&quot;, 200)
  } catch(e) {
    // 실패한 경우 콘솔에 에러 메시지 출력
    console.error(&#39;Error:&#39;, (e as Error).message);
    return FormMessage.createFormMessage(`${(e as Error).message}`, 500)
  }
}

const validateRestoreInput = (requestGrade: string, restoreDesc: string, restoreImageUrls:string) =&gt; {
  if (requestGrade === null) return FormMessage.createFormMessage(&quot;등급을 선택해주세요&quot;, 400)
  if (restoreDesc === null) return FormMessage.createFormMessage(&quot;상풍설명을 입력해주세요&quot;, 400)
  if (restoreImageUrls === null) return FormMessage.createFormMessage(&quot;상품사진을 등록해주세요&quot;, 400)
  return null
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[와일트루] 2월 1-2주차 : 0129-0211]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%99%80%EC%9D%BC%ED%8A%B8%EB%A3%A8-2%EC%9B%94-1-2%EC%A3%BC%EC%B0%A8-0129-0211</link>
            <guid>https://velog.io/@jeong_yooony/%EC%99%80%EC%9D%BC%ED%8A%B8%EB%A3%A8-2%EC%9B%94-1-2%EC%A3%BC%EC%B0%A8-0129-0211</guid>
            <pubDate>Sun, 11 Feb 2024 12:02:23 GMT</pubDate>
            <description><![CDATA[<h1 id="☘️-20500-ezreal-여눈부터-가네-ㅈㅈ">☘️ <a href="https://www.acmicpc.net/problem/20500">20500. Ezreal 여눈부터 가네 ㅈㅈ</a></h1>
<h2 id="문제">문제</h2>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/e0f4c355-6dd8-4bb9-a274-012f3d9e9196/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/c82840d0-9c3b-4300-bb0d-adb10f974726/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/622c1fbf-819b-40a3-a54e-e909ffbb28eb/image.png" alt=""></p>
<p>욱제는 15라는 수를 굉장히 싫어한다. 그래서 0으로 시작하지 않고 1과 5로만 구성된 
$N$자리 양의 정수 중에서, 15의 배수가 몇 개인지 궁금해졌다.</p>
<p>참가자 여러분도 궁금하지요?</p>
<p>안 궁금함? 15ㄱ</p>
<h3 id="입력">입력</h3>
<p> $N$이 주어진다.</p>
<h3 id="출력">출력</h3>
<p>문제의 답을 
$1,000,000,007$로 나눈 나머지를 출력한다.</p>
<h3 id="제한">제한</h3>
<p>$1 \le N \le 1,515$ </p>
<h3 id="예제-입력-1">예제 입력 1</h3>
<p>1</p>
<h3 id="예제-출력-1">예제 출력 1</h3>
<p>0</p>
<h3 id="예제-입력-2">예제 입력 2</h3>
<p>2</p>
<h3 id="예제-출력-2">예제 출력 2</h3>
<p>1</p>
<h3 id="예제-입력-3">예제 입력 3</h3>
<p>3</p>
<h3 id="예제-출력-3">예제 출력 3</h3>
<p>1</p>
<h3 id="예제-입력-4">예제 입력 4</h3>
<p>1515</p>
<h3 id="예제-출력-4">예제 출력 4</h3>
<p>939178250</p>
<h3 id="알고리즘-분류">알고리즘 분류</h3>
<ul>
<li>수학</li>
<li>다이나믹 프로그래밍</li>
<li>정수론</li>
</ul>
<h3 id="코드---python3-성공">코드 - python3 성공</h3>
<pre><code class="language-python">import sys

input = sys.stdin.readline

# 초기화: dp[i][j]는 i자리 수에서 j로 끝나는 수의 개수
dp = [[0 for _ in range(3)] for _ in range(1516)]

# 1자리 수일 때, 1로 끝나는 수의 개수는 1
dp[1][1] = 1

# Bottom-up 방식으로 동적 계획법 수행
for i in range(2, 1516):
    # i자리 수에서 0으로 끝나는 경우
    dp[i][0] = dp[i - 1][1] + dp[i - 1][2]
    # i자리 수에서 1로 끝나는 경우
    dp[i][1] = dp[i - 1][0] + dp[i - 1][2]
    # i자리 수에서 5로 끝나는 경우
    dp[i][2] = dp[i - 1][0] + dp[i - 1][1]

    # 나머지를 적용하여 값 갱신
    for j in range(3):
        dp[i][j] %= 1000000007

# 입력받은 N에 대한 결과 출력
print(dp[int(input())][0])</code></pre>
<hr>
<h1 id="☘️-1041-주사위">☘️ <a href="https://www.acmicpc.net/problem/1041">1041. 주사위</a></h1>
<h2 id="문제-1">문제</h2>
<pre><code>    +---+        
    | D |        
+---+---+---+---+
| E | A | B | F |
+---+---+---+---+
    | C |        
    +---+  </code></pre><p>주사위는 위와 같이 생겼다. 주사위의 여섯 면에는 수가 쓰여 있다. 위의 전개도를 수가 밖으로 나오게 접는다.</p>
<p>A, B, C, D, E, F에 쓰여 있는 수가 주어진다.</p>
<p>지민이는 현재 동일한 주사위를 N3개 가지고 있다. 이 주사위를 적절히 회전시키고 쌓아서, N×N×N크기의 정육면체를 만들려고 한다. 이 정육면체는 탁자위에 있으므로, 5개의 면만 보인다.</p>
<p>N과 주사위에 쓰여 있는 수가 주어질 때, 보이는 5개의 면에 쓰여 있는 수의 합의 최솟값을 출력하는 프로그램을 작성하시오.</p>
<h3 id="입력-1">입력</h3>
<p>첫째 줄에 N이 주어진다. 둘째 줄에 주사위에 쓰여 있는 수가 주어진다. 위의 그림에서 A, B, C, D, E, F에 쓰여 있는 수가 차례대로 주어진다. N은 1,000,000보다 작거나 같은 자연수이고, 쓰여 있는 수는 50보다 작거나 같은 자연수이다.</p>
<h3 id="출력-1">출력</h3>
<p>첫째 줄에 문제의 정답을 출력한다.</p>
<h3 id="예제-입력-1-1">예제 입력 1</h3>
<p>2
1 2 3 4 5 6</p>
<h3 id="예제-출력-1-1">예제 출력 1</h3>
<p>36</p>
<h3 id="예제-입력-2-1">예제 입력 2</h3>
<p>3
1 2 3 4 5 6</p>
<h3 id="예제-출력-2-1">예제 출력 2</h3>
<p>69</p>
<h3 id="예제-입력-3-1">예제 입력 3</h3>
<p>1000000
50 50 50 50 50 50</p>
<h3 id="예제-출력-3-1">예제 출력 3</h3>
<p>250000000000000</p>
<h3 id="예제-입력-4-1">예제 입력 4</h3>
<p>10
1 1 1 1 50 1</p>
<h3 id="예제-출력-4-1">예제 출력 4</h3>
<p>500</p>
<h3 id="알고리즘-분류-1">알고리즘 분류</h3>
<ul>
<li>수학</li>
<li>그리디 알고리즘</li>
</ul>
<h3 id="코드---python3-성공-1">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

N = int(input())
arr = list(map(int, input().split()))
ans = 0
min_lists = []

if N == 1: # 주사위 1개일 때
    arr.sort()
    for i in range(5):
        ans += arr[i]
else:
    # 주사위 여러개인 경우, 마주보는 면 중 작은 값을 선택하여 리스트에 저장
    min_lists.append(min(arr[0], arr[5]))
    min_lists.append(min(arr[1], arr[4]))
    min_lists.append(min(arr[2], arr[3]))
    min_lists.sort()

    # 1, 2, 3개의 면을 보았을 때의 최소값
    min1 = min_lists[0]
    min2 = min_lists[0] + min_lists[1]
    min3 = sum(min_lists)

    # 각 경우에 대한 주사위 개수 계산
    n1 = 4 * (N - 2) * (N - 1) + (N - 2) ** 2
    n2 = 4 * (N - 1) + 4 * (N - 2)
    n3 = 4

    # 최종 정답 계산
    ans += min1 * n1
    ans += min2 * n2
    ans += min3 * n3

# 결과 출력
print(ans)</code></pre>
<h1 id="☘️-24551-일이-너무-많아">☘️ <a href="https://www.acmicpc.net/problem/24551">24551. 일이 너무 많아...</a></h1>
<h2 id="문제-2">문제</h2>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/38af5a79-6780-4da4-ac31-b4ee0be8a340/image.png" alt=""></p>
<p>카카오에 7년 경력을 가진 신입 개발자로 입사한 pichulia. pichulia 는 카카오 서비스 중 카카오 지갑 서비스 개발 담당자가 되었다.</p>
<p>카카오 지갑은 사용자가 소유한 디지털 자산과 아이템이 담기는 곳으로써 본인 확인을 거쳐 이용할 수 있는 카카오의 다양한 서비스를 모아볼 수 있는 공간이다.</p>
<p>카카오 지갑에서 제공하는 서비스는 매우 다양하다. 우선 &#39;카카오 인증서&#39;를 통해 각종 금융기관과의 연동 서비스를 지원한다. 그리고 &#39;톡명함&#39;을 통해 나를 돋보이게 만드는 명함을 만들 수 있고, 이 명함을 이용해 개발자 커뮤니티 등, 나를 필요로 하는 사람들과 소통할 수 있다. 게다가 &#39;지갑 QR&#39; 을 이용한 무인 매장 이용 서비스도 지원한다. 이 외에도 많은 서비스를 제공하고 있다.</p>
<p>사용자 입장에서는 진짜 지갑처럼 매우 유용하게 사용할 수 있을 것이다. 하지만 개발자 입장에서는 이 모든 것이 정상적으로 돌아가도록 관리를 해야만 하기 때문에 pichulia 는 언제나 일이 많다.</p>
<p>일이 하나만 있는 것도 힘든데, 이렇게 일이 여러 개가 있다... ㅠㅜ</p>
<p>그래서 pichulia 는 
$11$, 
$111$, 
$1111$, 
$\cdots$ 와 같이 2개 이상의 숫자 
$1$로만 이루어진 수를 싫어한다. 게다가 이러한 수를 약수로 가진 수도 싫어한다.</p>
<p>양의 정수 
$N$이 주어졌을 때, 
$1$ 이상 
$N$ 이하의 정수 중 pichulia 가 싫어하는 수의 개수를 구해보자. pichulia 는 위에 서술된 특징을 가진 정수를 제외한 모든 수를 싫어하지 않는다고 가정한다.</p>
<h3 id="입력-2">입력</h3>
<p>첫 번째 줄에 문제에서 정의된 정수 
$N$이 주어진다. (
$1 \le N \le 10^{18}$)</p>
<h3 id="출력-2">출력</h3>
<p>$1$ 이상 
$N$ 이하의 정수 중 2개 이상의 숫자 
$1$로만 이루어진 수를 약수로 가지는 수의 개수를 출력한다.</p>
<h3 id="예제-입력-1-2">예제 입력 1</h3>
<p>111</p>
<h3 id="예제-출력-1-2">예제 출력 1</h3>
<p>11</p>
<h3 id="예제-입력-2-2">예제 입력 2</h3>
<p>111111111</p>
<h3 id="예제-출력-2-2">예제 출력 2</h3>
<p>11020111</p>
<h3 id="예제-입력-3-2">예제 입력 3</h3>
<p>1000000000000000000</p>
<h3 id="예제-출력-3-2">예제 출력 3</h3>
<p>99180991810801810</p>
<h3 id="알고리즘-분류-2">알고리즘 분류</h3>
<ul>
<li>수학</li>
<li>임의 정밀도 / 큰 수 연산</li>
<li>포함 배제의 원리</li>
</ul>
<h3 id="코드---python3-성공-2">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

# 함수: 최대공약수(Greatest Common Divisor) 계산
def GCD(a, b):
    if b == 0:
        return a
    else:
        return GCD(b, a % b)

# 함수: 최소공배수(Least Common Multiple) 계산
def LCM(a, b):
    return a * b // GCD(a, b)

# 함수: 이진수에서 1의 개수 계산
def count_one(mask):
    cnt = 0
    for i in range(0, 7):
        if mask &amp; (1 &lt;&lt; i) != 0:
            cnt += 1
    return cnt

# 함수: 주어진 마스크에 해당하는 최소공배수 계산
def LCMs(mask):
    ret = 1;
    for i in range(0, 7):
        if mask &amp; (2 ** i) != 0:
            ret = LCM(ret, arr[i])
    return ret

# 입력: N 값
N = int(input())

# 주어진 숫자 패턴 : 소수 2, 3, 5, 7, 11, 13, 17
arr = [11, 111, 11111, 1111111, 11111111111, 1111111111111, 11111111111111111]

# 초기값: 결과 변수 ret에 N 대입
ret = N;

# 모든 경우의 수에 대해 반복
for i in range(0, (2 ** 7)):
    # 이진수로 표현했을 때 1의 개수가 짝수이면 빼기, 홀수이면 더하기
    if count_one(i) % 2 == 0:
        ret -= N // LCMs(i)
    else:
        ret += N // LCMs(i)

# 결과 출력
print(ret)</code></pre>
<hr>
<h1 id="☘️-1368-물대기">☘️ <a href="https://www.acmicpc.net/problem/1368">1368. 물대기</a></h1>
<h2 id="문제-3">문제</h2>
<p>선주는 자신이 운영하는 N개의 논에 물을 대려고 한다. 물을 대는 방법은 두 가지가 있는데 하나는 직접 논에 우물을 파는 것이고 다른 하나는 이미 물을 대고 있는 다른 논으로부터 물을 끌어오는 법이다.</p>
<p>각각의 논에 대해 우물을 파는 비용과 논들 사이에 물을 끌어오는 비용들이 주어졌을 때 최소의 비용으로 모든 논에 물을 대는 것이 문제이다.</p>
<h3 id="입력-3">입력</h3>
<p>첫 줄에는 논의 수 N(1 ≤ N ≤ 300)이 주어진다. 다음 N개의 줄에는 i번째 논에 우물을 팔 때 드는 비용 Wi(1 ≤ Wi ≤ 100,000)가 순서대로 들어온다. 다음 N개의 줄에 대해서는 각 줄에 N개의 수가 들어오는데 이는 i번째 논과 j번째 논을 연결하는데 드는 비용 Pi,j(1 ≤ Pi,j ≤ 100,000, Pi,j = Pj,i, Pi,i = 0)를 의미한다.</p>
<h3 id="출력-3">출력</h3>
<p>첫 줄에 모든 논에 물을 대는데 필요한 최소비용을 출력한다.</p>
<h3 id="예제-입력-1-3">예제 입력 1</h3>
<p>4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0</p>
<h3 id="예제-출력-1-3">예제 출력 1</h3>
<p>9</p>
<h3 id="알고리즘-분류-3">알고리즘 분류</h3>
<ul>
<li>그래프 이론</li>
<li>최소 스패닝 트리</li>
</ul>
<h3 id="코드---python3-성공-3">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
import heapq

input = sys.stdin.readline

# 입력: 논의 수 N
N = int(input())
node_list = []  # (우물 파는 비용, 논 번호)를 저장하는 힙
pay_list = []   # 각 논의 우물 파는 비용을 저장하는 리스트

# 각 논의 우물 파는 비용을 입력받아 힙과 리스트에 저장
for i in range(N):
    pay = int(input())
    heapq.heappush(node_list, (pay, i))
    pay_list.append(pay)

# 각 논들 사이의 물을 끌어오는 비용을 2차원 리스트로 입력받음
connect_list = [list(map(int, input().split())) for _ in range(N)]

result = 0  # 결과 변수 초기화
visited = [False] * N  # 방문 여부를 저장하는 리스트 초기화

# 우선순위 큐(node_list)가 빌 때까지 반복
while node_list:
    pay, node = heapq.heappop(node_list)
    # 이미 방문한 논이면 스킵
    if visited[node]:
        continue
    visited[node] = True
    result += pay  # 결과에 현재 논의 우물 파는 비용 추가

    # 현재 논과 연결된 모든 논에 대해
    for next_node in range(N):
        if next_node != node:
            # 만약 더 저렴한 비용으로 물을 끌어올 수 있다면 업데이트 후 힙에 추가
            if pay_list[next_node] &gt; connect_list[node][next_node]:
                pay_list[next_node] = connect_list[node][next_node]
                heapq.heappush(node_list, (pay_list[next_node], next_node))

# 결과 출력
print(result)</code></pre>
<hr>
<h1 id="☘️-9527-1의-개수-세기">☘️ <a href="https://www.acmicpc.net/problem/9527">9527. 1의 개수 세기</a></h1>
<h2 id="문제-4">문제</h2>
<p>두 자연수 A, B가 주어졌을 때, A ≤ x ≤ B를 만족하는 모든 x에 대해 x를 이진수로 표현했을 때 1의 개수의 합을 구하는 프로그램을 작성하시오.</p>
<p>즉, f(x) = x를 이진수로 표현 했을 때 1의 개수라고 정의하고, 아래 식의 결과를 구하자.</p>
<h3 id="입력-4">입력</h3>
<p>첫 줄에 두 자연수 A, B가 주어진다. (1 ≤ A ≤ B ≤ 1016)</p>
<h3 id="출력-4">출력</h3>
<p>1의 개수를 세어 출력한다.</p>
<h3 id="예제-입력-1-4">예제 입력 1</h3>
<p>2 12</p>
<h3 id="예제-출력-1-4">예제 출력 1</h3>
<p>21</p>
<h3 id="알고리즘-분류-4">알고리즘 분류</h3>
<ul>
<li>수학</li>
<li>누적 합</li>
<li>비트마스킹</li>
</ul>
<h3 id="코드---python3-성공-4">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline


def count(num):
    cnt = 0
    # 주어진 숫자를 이진수로 변환하여 저장
    bin_num = bin(num)[2:]
    # 이진수의 길이 구하기
    length = len(bin_num)

    # 이진수를 뒤에서부터 순회하면서 1의 개수 계산
    for i in range(length):
        if bin_num[i] == &#39;1&#39;:
            # 현재 자릿수의 2의 거듭제곱 값
            val = length - i - 1
            # 현재 자릿수까지의 1의 개수를 누적
            cnt += one_sum[val]
            # 가장 큰 2의 거듭제곱 수까지의 1의 개수를 더해줌
            cnt += (num - 2 ** val + 1)
            # 다음 자릿수 계산을 위해 현재 자릿수 제외
            num = num - 2 ** val

    # 최종적으로 계산된 1의 개수 반환
    return cnt

# 입력 받기
x, y = map(int, input().split())

# 각 자릿수의 1의 개수를 저장하는 리스트 초기화 (log2(10**16))
one_sum = [0 for _ in range(60)]

# one_sum 리스트 계산
for i in range(1, 60):
    # 현재 자릿수까지의 1의 개수를 계산하기 위해 2의 거듭제곱을 사용, i - 1은 현재 자릿수
    one_sum[i] = 2 ** (i - 1) + 2 * one_sum[i - 1]

# A부터 B까지의 1의 개수 합을 계산하고 출력
print(count(y) - count(x - 1))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240130 기록 - 개발보안 / DBA]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240130-%EA%B8%B0%EB%A1%9D-%EA%B0%9C%EB%B0%9C%EB%B3%B4%EC%95%88-DBA</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240130-%EA%B8%B0%EB%A1%9D-%EA%B0%9C%EB%B0%9C%EB%B3%B4%EC%95%88-DBA</guid>
            <pubDate>Tue, 06 Feb 2024 08:34:21 GMT</pubDate>
            <description><![CDATA[<h1 id="정보보안-사고사례">정보보안 사고사례</h1>
<h2 id="랜섬웨어">랜섬웨어</h2>
<ul>
<li>데이터에 대한 몸값을 요구하는 악성 프로그램으로 2021 글로벌 기준 23조6천억원의 피해금액이 산출되고 있음</li>
</ul>
<h2 id="중요정보-유출">중요정보 유출</h2>
<ul>
<li>개인정보, 기업정보 등이 유출되어 금전적 손실을 야기시키며, 개인정보 처리자의 무단 수집, 내부 보안관리 소홀, 외부 해킹을 통해 유출되는 경우가 대부분</li>
</ul>
<h2 id="제로데이">제로데이</h2>
<ul>
<li>JAVA, Kotlin 등 코딩 도중에 프로그램의 로그를 기록해 주는 라이브러리로 메시지에 신뢰할 수 없는 데이터를 삽입하여 RCE 공격</li>
</ul>
<h2 id="보안정책">보안정책</h2>
<h3 id="인증제도">인증제도</h3>
<ul>
<li>국내<ul>
<li>ISMS</li>
<li>ISMS-P</li>
<li>전자금융</li>
</ul>
</li>
<li>국제<ul>
<li>ISO27001</li>
<li>BS200</li>
</ul>
</li>
</ul>
<hr>
<h1 id="공격-종류-및-방법">공격 종류 및 방법</h1>
<h2 id="모의해킹-대상-환경분석">모의해킹 대상 환경분석</h2>
<ul>
<li>모의해킹 대상이 어떠한 서비스를 하고 있는지 분석하는 단계</li>
</ul>
<h2 id="정보수집">정보수집</h2>
<ul>
<li>환경분석을 통해 발견된 모의해킹 대상에 대한 정보(CVE정보, 서버 정보, 버전 정보 등)에 대해 더욱 세부적인 정보수집 단계</li>
</ul>
<h2 id="취약점-분석">취약점 분석</h2>
<ul>
<li>발생가능한 취약점에 대해 공격방법, 공격원리 등을 분석하여 어떤 방식으로 공격할지 분석하는 단계</li>
</ul>
<h2 id="취약점-공격">취약점 공격</h2>
<ul>
<li>사전 정보를 바탕으로 Metasploit, PoC코드, 진단 Tool 등을 활용하여 실제 공격을 통해 우회, 정보유출 등 가능한지 테스트하는 단계로 존재하는 위협을 찾는 단계</li>
</ul>
<h2 id="결과도출-및-대응방안-제시">결과도출 및 대응방안 제시</h2>
<ul>
<li>발견된 취약점 별 리포팅 및 취약점 대응방안을 제시하여 시큐어코딩 또는 패치 등의 방법을 통해 위협을 제거하는 단계</li>
</ul>
<hr>
<h1 id="시큐어-코딩-업무">시큐어 코딩 업무</h1>
<p>select useid from user_t where userid=&#39; admin&#39; or &#39;1=1&#39; and userpw=&#39;&#39;</p>
<hr>
<h1 id="현대아이티앤이-dba-소개">현대아이티앤이 DBA 소개</h1>
<h2 id="백화점-그룹-db관리-직무에-대한-이해">백화점 그룹 DB관리 직무에 대한 이해</h2>
<ul>
<li>데이터 베이스 서비스 요청<ul>
<li>표준 수립 및 관리<ul>
<li>각종표준수립</li>
<li>각종표준에대한교육수행</li>
<li>표준준수여부점검</li>
</ul>
</li>
<li>Model 및 Object관리<ul>
<li>Object관리 원칙 수립</li>
<li>데이터모델 및 DBtjfrO</li>
<li>Object 생성</li>
<li>Object 관리</li>
</ul>
</li>
<li>모니터링<ul>
<li>모니터링정책 및 전략수립</li>
<li>모니터링요건정리</li>
<li>모니터링환경구축</li>
<li>모니터링수행</li>
<li>모니터링결과보고</li>
</ul>
</li>
<li>성능/용량 관리<ul>
<li>성능/용량관리 기본원칙수립</li>
<li>성능/용량관리 지표선정</li>
<li>성능/용량관리 시스템구축</li>
<li>성능/용량진단</li>
<li>성능개선용량확보</li>
</ul>
</li>
<li>백업/복구 관리<ul>
<li>백업/복구 정책 및 정략수립</li>
<li>백업요건정의</li>
<li>백업/복구 환경구성</li>
<li>백업 수행</li>
<li>백업확인 및 모니터링</li>
<li>복구테스트 수행</li>
</ul>
</li>
<li>보안관리<ul>
<li>보안정책수립</li>
<li>보안적용대상선정</li>
<li>보안시스템구축</li>
<li>보안적용</li>
<li>보안점검</li>
<li>보안교육수행</li>
</ul>
</li>
<li>변경관리<ul>
<li>변경관리원칙수립</li>
<li>변경계획수립</li>
<li>변경테스트수행</li>
<li>운영시스템변경적용</li>
<li>적용 후 모니터링</li>
</ul>
</li>
<li>설치/패치관리<ul>
<li>설치/패치 기본원칙수립</li>
<li>설치/패치 요건검토</li>
<li>설치/패치 적용</li>
<li>SW라이센스관리</li>
</ul>
</li>
<li>장애관리<ul>
<li>장애관리원칙수립</li>
<li>장애조치</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="백화점-부문-db-현황">백화점 부문 DB 현황</h2>
<h3 id="운영-db-목록">운영 DB 목록</h3>
<ul>
<li>배송</li>
<li>포탈</li>
<li>사은품</li>
<li>결제정보</li>
<li>재경(회계) 정보</li>
<li>상품정보</li>
<li>온라인 식품</li>
<li>인사</li>
<li>메일</li>
<li>더현대닷컴</li>
<li>통합멤버십</li>
<li>직승인</li>
<li>직승인 통신애니링크</li>
<li>그린푸드</li>
<li>드림투어</li>
<li>면세점온라인</li>
<li>면세점오프라인</li>
<li>리바트ERP</li>
<li>리바트DW</li>
<li>리바트 WSI</li>
<li>H&amp;S</li>
<li>한섬ERP_DW</li>
<li>더한섬닷컴</li>
<li>홈쇼핑 3노드 + PostgreSQL</li>
<li>홈쇼핑보험</li>
<li>대량메일</li>
<li>리바트 ETL</li>
<li>리바트 ERP</li>
<li>리바트_WSL_개발</li>
<li>직승인개발</li>
<li>통합그린푸드개발</li>
<li>인사개발</li>
<li>웹메일개발</li>
<li>드림투어개발</li>
<li>H&amp;S개발</li>
<li>상품정보개발</li>
<li>통합멤버십개발</li>
<li>카드정보개발</li>
<li>면세점개발</li>
<li>배송개발</li>
<li>고객사랑개발</li>
<li>사은품개발</li>
<li>온라인식품개발</li>
<li>포탈개발</li>
</ul>
<h2 id="데이터베이스-관련-솔루션-소개">데이터베이스 관련 솔루션 소개</h2>
<h3 id="db접근제어">DB접근제어</h3>
<ul>
<li>보안 대상자는 개인정보 DB 접근 시 DB접근제어서버를 경유하도록 강제화하고 Application 서버에서의 접근은 Sniffing서버를 이용하여 DB접속에 대한 모니터링 기능을 수행한다.</li>
<li>구성내역<ul>
<li>사용자 PC에 DB접근제어 Agent를 설치하여 DB접근 시 DB접근제어서버를 통해서만 접근</li>
<li>Application 서버에서의 보안 대상 서버로의 접속은 스니핑 서버에 접근</li>
</ul>
</li>
</ul>
<h3 id="db암호화">DB암호화</h3>
<ol start="2">
<li>암호화 이후 Table 구조</li>
</ol>
<ul>
<li>기존 Table name이 View로 변경되며 암호화 테이블이 생성됨.</li>
</ul>
<ol start="3">
<li>암호화 이후 Table 컬럼 구조</li>
</ol>
<ul>
<li>컬럼 타입이 Varchar2(4000)으로 변경</li>
</ul>
<ol start="4">
<li>암호화 적용 예시</li>
</ol>
<h3 id="db암호화tde-tablespace-암호화방식">DB암호화(TDE, Tablespace 암호화방식)</h3>
<ul>
<li>테이블스페이스 전체를 암호화 -&gt; 암호화 테이블스페이스 안에 생성된 모든 objects는 자동으로 암호화</li>
<li>주로 민감한 데이터를 가진 칼럼이 많은 경우에 사용하며, 기존 테이블스페이스에 대해서는 암호화를 수행할 수 없다.</li>
<li>암호화된 테이블스페이스의 모든 데이터들은 디스크에 암호화된 형식으로 저장.</li>
</ul>
<h3 id="db-표준관리">DB 표준관리</h3>
<h4 id="2-표준-데이터-세부-관리-대상">2. 표준 데이터 세부 관리 대상</h4>
<ul>
<li>표준단어<ul>
<li>일정한 의미를 갖는 최소단위의 단어로 표준용어를 구성함</li>
<li>의미를 갖는 더 이상 분할이 불가능한 단어로 된 표준단어를 단일어라 함</li>
<li>둘 이상의 단어를 결합하여 만든 표준단어를 복합어라 함</li>
</ul>
</li>
<li>도메인<ul>
<li>데이터타입과 허용되는 값, 필요하다면 측정단위 등을 포함한 데이터 구성의 형태</li>
<li>데이터 구성에 대한 허용 값의 집합을 데이터 도메인이라 하며, 하나의 용어는 하나의 도메인만 지정</li>
<li>논리적/물리적으로 유사한 유형의 데이터를 그룹화하여 해당 그룹에 속하는 데이터타입과 길이를 정의한 것</li>
</ul>
</li>
<li>표준용어<ul>
<li>일정한 표준단어의 조합으로 만들어진 유일한 용어</li>
<li>모델링 할 때 속성명에 사용함</li>
<li>단어는 개별적이나 용어는 업무와 조직의 성격에 따라 조합이 달라질 수 있음</li>
</ul>
</li>
<li>코드 인스턴스<ul>
<li>코드도메인에 포함되어 관리되는 실제 사용 가능한 코드의 값을 의미</li>
<li>코드는 2개 이상의 구분코드가 필요한 경우 관리대상이 됨</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240205 최종 프로젝트 - Vue (Part1)]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240205-%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Vue-Part1</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240205-%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Vue-Part1</guid>
            <pubDate>Mon, 05 Feb 2024 10:57:29 GMT</pubDate>
            <description><![CDATA[<h1 id="🌏-개발환경-셋팅과-vue-3-설치-자주겪는-에러-포함">🌏 개발환경 셋팅과 Vue 3 설치 (자주겪는 에러 포함)</h1>
<p>(참고) 설치나 실행시 ESLint is not a constructor 에러가 뜨는 경우 
터미널에서 npm i -D <a href="mailto:eslint@7.32.0">eslint@7.32.0</a> 입력해봅시다.</p>
<p>Vue 설치부터 하도록 합시다.
문법 체험을 위해 HTML 파일에 라이브러리 식으로 간략하게 설치하는 방법은 쓰지 않고 
실제 Web-app 개발에 필요한 Vue 프로젝트를 처음부터 만드는 식으로 진행합니다.
그러려면 Vue-cli 라는 라이브러리가 필요한데 그것 부터 설치하도록 합시다. 
그리고 요즘은 설치과정 맥이나 윈도우나 똑같음  </p>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/b76f817c-df77-4729-ac23-ba05d93f1e6b/image.png" alt=""></p>
<p>요약부터 하자면 </p>
<h2 id="1-nodejs-최신버전-설치">1. nodejs 최신버전 설치</h2>
<p>우측 최신버전이 뭔가 에러가 덜 납니다. 
그래도 에러나면 LTS 왼쪽 버전으로 바꾸실 수 있습니다. 제어판에서 삭제 후 재설치하셈 </p>
<h2 id="2-vs-code-에디터-설치">2. VS code 에디터 설치</h2>
<p>구글 검색해서 다운받고 설치하셈 </p>
<h2 id="3-아무데나-작업용폴더-만들고-에디터로-오픈">3. 아무데나 작업용폴더 만들고 에디터로 오픈</h2>
<p>코딩실력이 부끄럽다면 은밀한 곳에 만들어줍니다. 에디터 상단 메뉴에 open folder라고 있을겁니다 그렇게 오픈하셈</p>
<h2 id="4-에디터에서-터미널-열고-npm-install--g-vuecli-입력">4. 에디터에서 터미널 열고 npm install -g @vue/cli 입력</h2>
<p>터미널은 에디터 상단 Terminal - New Terminal 누르면 됩니다. 
입력 후에 Vue 3 버전을 방향키 + 엔터로 선택하면 됩니다.
안되면 90%확률로 nodejs 이상하게 설치해서 그렇습니다. 해결책은 하단에 </p>
<h3 id="뭔가-npm-하는거-부터-에러나면">뭔가 npm 하는거 부터 에러나면</h3>
<p>yarn 1.22 버전 구글검색해서 설치하고 윈도우 재시작하고 yarn global add @vue/cli 이거 해보셈 
윈도우는 yarn 1.22 인스톨러를 다운받아서 설치하고 컴퓨터 재시작,
맥은 터미널 아무데나 열고 npm install -g yarn 하시면 됩니다.
윈도우는 맥처럼 설치하면 컴퓨터 폭발함 그러지마세요 </p>
<blockquote>
<h3 id="오류해결기록">오류해결기록</h3>
<p><strong>권한문제로 인한 오류 발생</strong></p>
</blockquote>
<pre><code class="language-bash">npm install -g @vue/cli</code></pre>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/d8e2b07b-50da-48dd-a71a-01c2f0eeb07a/image.png" alt="">
<strong>[sudo로 강제설치]
(<a href="https://milooy.github.io/dev/how-to-fix-npm-package-permission-error/">https://milooy.github.io/dev/how-to-fix-npm-package-permission-error/</a>)</strong></p>
<pre><code class="language-bash">sudo npm install -g @vue/cli</code></pre>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/9d95d5b3-73de-497e-bfcc-2f822b75e6d1/image.png" alt="">
<strong>환경변수 설정</strong></p>
<pre><code class="language-bash">vi ~/.bashrc
export PATH=$PATH:~/.npm-global/bin # 편집기 접속 후 해당 명령어 추가
source ~/.bashrc
vue --version</code></pre>
<pre><code class="language-bash">vi ~/.bash_profile
export PATH=$PATH:~/.npm-global/bin # 편집기 접속 후 해당 명령어 추가
source ~/.bash_profile
vue --version</code></pre>
<pre><code class="language-bash">vi ~/.zshrc
export PATH=$PATH:~/.npm-global/bin # 편집기 접속 후 해당 명령어 추가
source ~/.zshrc
vue --version</code></pre>
<p><strong>npm 캐시 폴더 소유권 변경</strong></p>
<pre><code class="language-bash">sudo chown -R 501:20 &quot;/Users/jeong-yoon/.npm&quot;</code></pre>
<h2 id="5-에디터에서-터미널-열고-npm-install--g-vuecli-입력">5. 에디터에서 터미널 열고 npm install -g @vue/cli 입력</h2>
<p>터미널은 에디터 상단 Terminal - New Terminal 누르면 됩니다. 
입력 후에 Vue 3 버전을 방향키 + 엔터로 선택하면 됩니다.
안되면 90%확률로 nodejs 이상하게 설치해서 그렇습니다. 해결책은 하단에 
뭔가 npm 하는거 부터 에러나면
yarn 1.22 버전 구글검색해서 설치하고 윈도우 재시작하고 yarn global add @vue/cli 이거 해보셈 
윈도우는 yarn 1.22 인스톨러를 다운받아서 설치하고 컴퓨터 재시작,
맥은 터미널 아무데나 열고 npm install -g yarn 하시면 됩니다.
윈도우는 맥처럼 설치하면 컴퓨터 폭발함 그러지마세요 
<img src="https://velog.velcdn.com/images/jeong_yooony/post/efdeada6-cdca-4823-b6b1-531800bbdfb3/image.png" alt=""></p>
<h2 id="6-설치가-끝났으면-터미널에서-vue-create-프로젝트명-입력">6. 설치가 끝났으면 터미널에서 vue create 프로젝트명 입력</h2>
<p>프로젝트명은 자유 작명 가능합니다.
그럼 하위폴더로 프로젝트폴더가 생성됩니다. </p>
<h2 id="7-프로젝트명으로-생성된-폴더를-에디터로-오픈-후-코딩시작">7. 프로젝트명으로 생성된 폴더를 에디터로 오픈 후 코딩시작</h2>
<p>하위폴더로 vuedongsan 생성된거 그거 다시 open folder 로 오픈하라는 소리입니다.
폴더오픈 안한채로 코딩하다가 뭐 안된다그러면 혼납니다. 
여러 파일 중 App.vue에다가 코딩 시작하면 됩니다. </p>
<h2 id="8-미리보고-싶으면-터미널-열고-npm-run-serve-입력">8. 미리보고 싶으면 터미널 열고 npm run serve 입력</h2>
<p>위에서 프로젝트 폴더 오픈 안해놨으면 에러가 날 수 있습니다. </p>
<h2 id="9-vs-code-에디터-부가기능을-설치해줍니다">9. VS code 에디터 부가기능을 설치해줍니다</h2>
<p>에디터 제일 왼쪽 네모네모 버튼 (Extensions) 누르면 됩니다. Vetur, Vue 3 snippets, HTML CSS Support 이거 세개 설치합니다. </p>
<h2 id="🚨-오류상황">🚨 오류상황</h2>
<p>하지만 설치과정에서 20% 확률로 에러가 뜹니다. 원래그럼 
대부분 에러메세지 구글 검색으로 해결이 가능하지만 자주 겪는 에러를 알아보도록 합시다.</p>
<h3 id="▶-저는-설치가-10분이상-걸려요">▶ 저는 설치가 10분이상 걸려요</h3>
<ul>
<li>인터넷 느리면 그렇습니다. 스타벅스에서 하지 말고 집에서 하시길 바랍니다. </li>
</ul>
<h3 id="▶-npm-yarn-명령어-입력하자마자-에러가-납니다">▶ npm, yarn 명령어 입력하자마자 에러가 납니다</h3>
<ul>
<li>설치가 잘 되다가 갑자기 중간에 빨간게 뜨며 에러가 나는 대부분의 경우는</li>
<li>99%확률로 nodejs가 최신버전이 아닐 경우 입니다. </li>
<li><ol>
<li>nodejs 삭제 후 2. 다른 버전 다운받아서 다시 시도해보십시오 </li>
</ol>
</li>
</ul>
<h3 id="▶-npm--command-not-found-에러">▶ npm : command not found 에러</h3>
<ul>
<li>npm : command not found 라는 에러가 뜨는 것은 역시 99%의 확률로 node 이상하게 설치하셔서 입니다. </li>
<li>nodejs 설치시 설치경로 만지지 마십시오. </li>
<li>맥도 brew 어쩌구 그런걸로 설치하지 마시고 다운받으세요. </li>
<li>리눅스는 nodejs 버전 업그레이드 커맨드 찾아서 입력하시면 되니 알아서 잘 하시리라 믿습니다. </li>
</ul>
<h3 id="▶-맥에서-permission이-없어요-권한이-없어요-이런-에러가-뜬다면">▶ 맥에서 permission이 없어요, 권한이 없어요 이런 에러가 뜬다면</h3>
<ul>
<li><p>직관적인 해결책은 그냥 npm이나 yarn 쓰실 때 앞에 sudo 라는 단어를 붙여주시면 됩니다. </p>
</li>
<li><p>sudo npm install -g @vue/cli 이런 식으로 하면 잘 됩니다</p>
</li>
<li><p>설치 중간에 여러분 맥북 비번입력이 필요할 수 있습니다.</p>
</li>
<li><p>근데 sudo는 임시방편일 뿐입니다. </p>
<pre><code class="language-bash">npm ERR! syscall access
npm ERR! Error: EACCES: permission denied, access &#39;/usr/local/lib/node_modules&#39;</code></pre>
<p>▲ 예를 들면 이런 에러인데 폴더 수정 권한이 없다고 에러를 띄우는 거면 </p>
</li>
<li><p>위의 경우 /usr/local/lib/node_modules 라는 폴더에 수정권한을 주시면 됩니다. </p>
</li>
<li><p>터미널을 켜서 이거 둘 중에 하나를 입력해보십시오</p>
</li>
<li><p>sudo chown -R 님맥북유저이름: 위에에러뜬경로</p>
</li>
<li><p>sudo chown -R $USER 위에에러뜬경로</p>
</li>
<li><p>아마 둘 중 하나 입력하시면 대부분 해결될 겁니다. </p>
</li>
<li><p>님맥북유저이름은 터미널에 whoami 입력하시면 나옵니다. </p>
</li>
<li><p>역시 이것도 임시방편이긴 한데</p>
</li>
<li><p>이거 말고도 다른 경우가 있을 수 있으니 그대로 구글에 에러메세지 검색해보시면 되겠습니다. </p>
</li>
</ul>
<h3 id="▶-윈도우-powershell에서-빨간글씨로-보안오류가-뜹니다">▶ 윈도우 Powershell에서 빨간글씨로 &#39;보안오류&#39;가 뜹니다.</h3>
<ul>
<li>&quot;허가되지 않은 스크립트 입니다 어쩌구~&quot; 그런 에러가 뜨면</li>
<li>윈도우 검색메뉴 (돋보기) - Powershell 검색 - 우클릭 - 관리자 권한으로 실행한 뒤</li>
<li>Set-ExecutionPolicy Unrestricted라고 대소문자 하나라도 틀리지않고 입력하십시오.</li>
<li>그럼 이제 npm으로 뭐 하는거 잘됩니다. </li>
</ul>
<h3 id="▶-윈도우-powershell을-이용하는-경우도-권한이-없다고-뭐라-그럴-수-있습니다">▶ 윈도우 Powershell을 이용하는 경우도 권한이 없다고 뭐라 그럴 수 있습니다.</h3>
<ul>
<li>그렇다면 윈도우 검색메뉴에서 powershell을 검색 후 우클릭 - 관리자 권한으로 실행합니다. </li>
<li>그 다음에 npm install -g @vue/cli 를 입력하거나</li>
<li>yarn global add @vue/cli 를 입력합니다. 
그럼 됩니다. </li>
<li>이 경우 vue create 어쩌구 할 때도 작업폴더를 오픈한 뒤에 상단 메뉴에서 파일 - powershell열기 - 관리자 권한으로 powershell 열기 누르신 후 입력해보십시오.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/88291f9b-bcd9-43de-81ea-bfcdee804a8a/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/52097005-b02f-494d-88c5-07e004e7d7e6/image.png" alt=""></p>
<hr>
<h1 id="🌏-html에-데이터-꽂아넣는-vue-데이터바인딩-문법">🌏 HTML에 데이터 꽂아넣는 Vue 데이터바인딩 문법</h1>
<p><strong>오늘의 5분 숙제 :</strong>
일단 이렇게 생긴 데이터를 하단에 하나 저장하십시오. 
products : [&#39;역삼동원룸&#39;, &#39;천호동원룸&#39;, &#39;마포구원룸&#39;]</p>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/e2252e2d-ea9a-4a9b-acc8-85da20d28c02/image.png" alt="">
▲ 그 데이터와 {{데이터바인딩}} 문법으로 대충 이렇게 생긴 HTML 레이아웃을 만들어오십시오. 
가격은 대충 임시로 아무거나 기입하시길 바랍니다. </p>
<p>HTML에 자바스크립트 데이터를 꽂아넣고 싶을 때가 있습니다. 
데이터바인딩이라고 하는데 Vue에서 데이터바인딩 하는 문법을 2개 알아보도록 합시다. 
근데 애초에 데이터바인딩을 왜 하는지 의문점 부터 들지 않습니까.
그런거 궁금해해야 나중에 여러분 데이터 바인딩 할지 말지 스스로 판단하는 훌륭한 사람이 됩니다. 
그것 부터 알아봅시다. </p>
<p>Vue 개발은 어려운게 아닙니다.
어딜가나 이상한 문법부터 가르치니까 혼자 코드짜는거 어려워들 하시는데 
그냥 평소에 HTML CSS로 웹페이지 개발하던 대로 쭉 코드짜시면 되고
필요한 순간순간 Vue 문법을 첨가하면 됩니다. 
그래서 원룸파는 쇼핑몰을 한번 만들어봅시다. 일단 레이아웃 부터요. </p>
<pre><code>&lt;template&gt;
  &lt;div&gt;
    &lt;h4&gt;XX 원룸&lt;/h4&gt;
    &lt;p&gt;XX 만원&lt;/p&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;h4&gt;XX 원룸&lt;/h4&gt;
    &lt;p&gt;XX 만원&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;</code></pre><p>HTML 코드들은 <code>&lt;template&gt;</code>안에 집어넣으면 됩니다.
자바스크립트 기능은 <code>&lt;script&gt;</code> 스타일은 <code>&lt;style&gt;</code> 안에 넣으십시오.
그래서 아무튼 저런 HTML을 추가하십시오. 배울 문법이 하나 있어서 그렇습니다. </p>
<h2 id="vue의-데이터바인딩-문법">Vue의 데이터바인딩 문법</h2>
<p>JavaScript로 자주 하는 짓거리가 있습니다.
자바스크립트 변수나 데이터를 HTML에 꽂아넣는 데이터바인딩입니다.
전통방식은 이렇게 길게 한줄 써야 데이터바인딩이 가능했습니다.</p>
<pre><code class="language-js">document.getElementById(어쩌구).innerHTML = 데이터;</code></pre>
<p>근데 Vue는 그럴 필요없이</p>
<ol>
<li>일단 데이터보관부터 어딘가에 하시고</li>
<li>그걸 {{데이터}} 이런 문법으로 HTML 중간중간에 쉽게 꽂아넣을 수 있습니다. 
data보관함은 여기있습니다.<pre><code class="language-js">&lt;script&gt;
export default {
name : &#39;App&#39;,
data(){
 return {
   price1 : 60
 }
}
}
</code></pre>
</li>
</ol>
<p></script></p>
<pre><code>script 태그 안에 data(){ return { } } 이걸 열고

데이터를 object 형식으로 저장하시면 됩니다.
이게 Vue의 data보관함, 변수보관함이라고 보시면 되겠습니다. 
중요한 데이터는 다 여기 보관하십시오. 중괄호니까 object 형식에 맞춰서요.
그럼 이제 price1 이라는 데이터를 HTML안에 꽂아넣어서 유저에게 보여주고 싶으면
{{ price1 }} 이것만 쓰면 됩니다.

```js
&lt;p&gt;{{ price1 }} 만원&lt;/p&gt;</code></pre><p>저장하면 브라우저에 60만원이라고 잘 뜹니다.</p>
<h3 id="q-뭐임-그냥-애초에-p60만원p-이렇게-하드코딩하면-되는데">Q. 뭐임 그냥 애초에 <p>60만원</p> 이렇게 하드코딩하면 되는데</h3>
<p>굳이 데이터로 저장해뒀다가 왜 데이터바인딩함?</p>
<h4 id="이유1-쇼핑몰은-가격이-맨날-변동되지않습니까">이유1. 쇼핑몰은 가격이 맨날 변동되지않습니까.</h4>
<p>그걸 데이터로 저장해놓으면 수정이 나중에 편리합니다. 
JS로 조작이 쉽거든요</p>
<h3 id="이유2-vue의-실시간-렌더링기능-쓰려면-데이터바인딩-해놓으십시오">이유2. Vue의 실시간 렌더링기능 쓰려면 데이터바인딩 해놓으십시오</h3>
<p>Vue는 신기해서 data가 변경되면 data와 관련된 HTML에 실시간으로 반영됩니다.
만약에 여러분이 price1을 60에서 70으로 조정하면 {{ price1 }} 에도 그 변경사항이 바로 적용된다는 소리입니다. 
따로 코드짤 필요 없이 자동으로 샥 바뀝니다. 
그리고 이런 사이트를 우리는 웹앱이라고 부릅니다.</p>
<p>그래서 웹앱 만들고 싶으면 좋은 말할 때 자주 바뀔 듯한 데이터들을 data란에 집어넣고 데이터바인딩해서 보여주시길 바랍니다. </p>
<h2 id="html-속성도-데이터바인딩이-가능합니다">HTML 속성도 데이터바인딩이 가능합니다.</h2>
<p>그니까 style=&quot;&quot; id=&quot;&quot; class=&quot;&quot; 이런 것들에도
밑에 저장해둔 data를 꽂아넣을 수 있다는 것입니다.</p>
<pre><code class="language-js">&lt;template&gt;
  &lt;div&gt;
    &lt;h4 :style=&quot;스타일&quot;&gt;XX 원룸&lt;/h4&gt;
    &lt;p&gt;XX 만원&lt;/p&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;h4&gt;XX 원룸&lt;/h4&gt;
    &lt;p&gt;XX 만원&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  name : &#39;App&#39;,
  data(){
    return {
      price1 : 60,
      스타일 : &#39;color:red&#39;
    }
  }
}

&lt;/script&gt;</code></pre>
<p>이렇게 짜시면 color : red라는 데이터도 원하는 곳에 꽂아넣을 수 있습니다.
상상하는 모든 속성에 데이터 꽂아넣기가 가능합니다. </p>
<hr>
<h1 id="🌏-리액트보다-100배-쉬운-vue-반복문-v-for">🌏 리액트보다 100배 쉬운 Vue 반복문 v-for</h1>
<p><strong>오늘의 5분 숙제 :</strong>
v-for 문법을 이용해 지금 있는 상품목록을 반복문으로 축약해보십시오.
가격은 신경쓰지말고 상품제목만 잘 보이면 됩니다. </p>
<blockquote>
<p><strong>저번시간 숙제는 어떻게 했냐면</strong>
저번 시간에 하단에 데이터로
products : [ &#39;역삼동원룸&#39;, &#39;천호동원룸&#39;, &#39;마포구원룸&#39; ]
이렇게 3개의 상품명을 저장하라고 했습니다.
그 다음 3개의 상품명을 저기 HTML에다가 데이터바인딩하라고 했습니다.
고민할 것 없이 중괄호 문법으로 데이터를 꽂아넣으면 됩니다.</p>
</blockquote>
<pre><code class="language-jsx">&lt;div&gt;
  &lt;h4&gt;{{products[0]}}&lt;/h4&gt;
  &lt;p&gt;50만원&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h4&gt;{{products[1]}}&lt;/h4&gt;
  &lt;p&gt;50만원&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h4&gt;{{products[2]}}&lt;/h4&gt;
  &lt;p&gt;50만원&lt;/p&gt;
&lt;/div&gt;</code></pre>
<p>HTML을 짜다보면 비슷한 <code>&lt;div&gt;</code> 이런게 수백번 출몰하지 않습니까.
그걸 보고 있으면 비슷한 <code>&lt;div&gt;</code>들을 간단하게 for 반복문 같은 걸로 축약하고 싶은 충동이 들지 않습니까.
리액트나 Vue를 쓰면 가능합니다. 그리고 그런 장점 때문에 리액트와 Vue를 쓰는겁니다. </p>
<p>하지만 for, map, forEach 이런 문법 배우다 포기하셨다면 
Vue 반복문은 100배 쉬우니 걱정하지 맙시다. </p>
<h2 id="상단-메뉴를-만들어봅시다">상단 메뉴를 만들어봅시다</h2>
<p>페이지가 여러개 있는 것은 아니지만 심심하니 상단메뉴를 만들어봅시다.
HTML과 CSS는 다음과 같이 작성합니다.
CSS는 vue파일 하단 <code>&lt;style&gt;</code> 태그 안에 넣으면 됩니다.</p>
<pre><code class="language-jsx">&lt;div class=&quot;menu&quot;&gt;
  &lt;a&gt;Home&lt;/a&gt;
  &lt;a&gt;Products&lt;/a&gt;
  &lt;a&gt;About&lt;/a&gt;
&lt;/div&gt;</code></pre>
<pre><code class="language-jsx">.menu {
  background : darkslateblue;
  padding : 15px;
  border-radius : 5px;
}
.menu a {
  color : white;
  padding : 10px;
}</code></pre>
<h2 id="v-for-html-반복문">v-for HTML 반복문</h2>
<p>가끔 코드를 짜다가 
HTML에 반복되는 부분이 보이면 축약하고 싶은 충동이 듭니다.
이걸 축약하고 싶으면 v-for을 쓰면 됩니다. 
v-for쓰시면 원하는 만큼 HTML 태그를 복붙할 수 있습니다. </p>
<p>예를 들어서 위에서 작성했던 <code>&lt;a&gt;</code>태그 3개를
한줄 컷으로 생성하는 마법을 보여드리겠습니다. </p>
<pre><code class="language-jsx">&lt;div class=&quot;menu&quot;&gt;
  &lt;a v-for=&quot;작명 in 3&quot; :key=&quot;작명&quot;&gt;Home&lt;/a&gt;
&lt;/div&gt;</code></pre>
<ol>
<li>원하는 태그에 v-for=&quot;작명 in 반복할횟수&quot; 를 적습니다.</li>
<li>그리고 센스있게 :key=&quot;작명&quot; 이것도 추가해줍니다.
그럼 이 HTML 태그는 내가 원하는 만큼 반복생성됩니다.
위의 예제는 Home이라는 a태그가 3개나 생성되겠네요. </li>
</ol>
<p>작명은 아무렇게나 하시면 됩니다. 변수하나 작명하는 겁니다.
:key 속성은 반복문돌릴 때 꼭 필요합니다. 반복한 요소들을 각각 구분짓기 위한 속성입니다. </p>
<h2 id="v-for-html-반복문2">v-for HTML 반복문2</h2>
<p>v-for 반복문을 사용할 때 array, object 데이터를 집어넣을 수도 있습니다. 
예를 들어서 array 자료를 하나 만들어봅시다. 데이터로 저장해두십시오. </p>
<pre><code class="language-jsx">data(){
  return {
    메뉴들 : [&#39;Home&#39;, &#39;Shop&#39;, &#39;About&#39;]
  }
}</code></pre>
<p>그럼 이제 메뉴들 이라는 데이터로 반복문을 돌릴 수도 있다는 겁니다.</p>
<pre><code class="language-jsx">&lt;div class=&quot;menu&quot;&gt;
  &lt;a v-for=&quot;작명 in 메뉴들&quot; :key=&quot;작명&quot;&gt;Home&lt;/a&gt;
&lt;/div&gt;</code></pre>
<p>아까랑 사용하는 법은 똑같은데 반복시킬 횟수 적는 란에다가 array 자료를 집어넣을 수 있습니다.
이 경우</p>
<ol>
<li>메뉴들안의 자료 갯수만큼 반복됩니다.</li>
<li>작명한 변수는 반복될 때마다 메뉴들 안에 있던 자료들이 됩니다.
예를 들어서 작명한 변수는 출력해보면 1회 반복시엔 &#39;Home&#39; 2회 반복시엔 &#39;Shop&#39; 이렇게 변한다는 겁니다. </li>
</ol>
<pre><code class="language-jsx">&lt;div class=&quot;menu&quot;&gt;
  &lt;a v-for=&quot;작명 in 메뉴들&quot; :key=&quot;작명&quot;&gt; {{ 작명 }}&lt;/a&gt;
&lt;/div&gt;</code></pre>
<p>그래서 작명한 변수를 저기 실제로 출력해보면 (데이터바인딩해보면) 진짜 그렇죠?
<code>&lt;a&gt;</code>태그가 3번 생성되는데 생성될 때마다 {{작명}} 부분은 차례로
Home이 되고 Shop이 되고 About이 됩니다.</p>
<p>그래서 밑에 있는 array, object 데이터 안에 있던 자료들을
하나씩 HTML로 만들어서 보여주고 싶을 때도 v-for을 쓰시면 유용합니다.</p>
<h2 id="v-for-안에-변수는-2개까지-작명이-가능합니다">v-for 안에 변수는 2개까지 작명이 가능합니다.</h2>
<pre><code>&lt;div class=&quot;menu&quot;&gt;
  &lt;a v-for=&quot;(작명,i) in 메뉴들&quot; :key=&quot;i&quot;&gt; {{ 작명 }}&lt;/a&gt;
&lt;/div&gt;</code></pre><p>() 소괄호를 여시면 작명을 두개까지 허용해줍니다.
첫째 작명한건 아까 설명했던 array안의 데이터가 되는 것이고
둘째 작명한건 0, 1, 2 .. 이런 식으로 1씩 증가하는 정수가 됩니다.
반복 횟수를 알려주는 숫자라고 보시면 되겠습니다.</p>
<p>그래서 i는 출력해보시면 반복될 때마다 0, 1, 2.. 이렇게 변할걸요
그리고 이걸 보통 관습적으로 :key 안에 집어넣습니다. </p>
<p>그래서 결론은 HTML 복붙하기 힘들면 쓰시길 바랍니다.
아니면 혹은 HTML을 데이터 갯수만큼 자동으로 생성하고 싶으면 쓰십시오. </p>
<hr>
<h1 id="🌏-vue-이벤트-핸들러로-click-감지하기-허위매물-신고버튼-만들기">🌏 Vue 이벤트 핸들러로 click 감지하기 (허위매물 신고버튼 만들기)</h1>
<p>*<em>오늘의 5분 숙제 : *</em>
모든 상품에 신고버튼과 기능을 만들어오십시오.
상품마다 각각 신고수를 따로 집계해야합니다. 그래서 신고수 3개를 각각 저장할 공간이 미리 필요하겠군요.</p>
<p><strong>저번시간 숙제</strong>
어떤 HTML 요소를 클릭했을 때 뭔가 일이 일어나게 만들고 싶으면 </p>
<div onclick=""> 이 안에 자바스크립트를 집어넣습니다. 
Vue에서는 살짝 다르게 집어넣으면 됩니다. @click="" 이걸 집어넣으면 됩니다.
문법만 설명하면 다음날 다 까먹을게 분명하니
저번시간까지 만들던 부동산 사이트에 허위매물 신고버튼과 기능을 만들어보며 배워봅시다. 
허위매물 신고버튼과 신고수를 만들어봅시다
일단 저번시간 반복문 돌린건 복잡해보이니까 다시 원래대로 복구시킵니다.

<pre><code class="language-jsx">&lt;div&gt;
  &lt;h4&gt;{{products[0]}}&lt;/h4&gt;
  &lt;p&gt;50만원&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h4&gt;{{products[1]}}&lt;/h4&gt;
  &lt;p&gt;50만원&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;h4&gt;{{products[2]}}&lt;/h4&gt;
  &lt;p&gt;50만원&lt;/p&gt;
&lt;/div&gt;</code></pre>
<p>우리같은 빡대가리들은 간단한걸 좋아하기 때문입니다.
여기다가 버튼을 하나 추가해보자는겁니다. </p>
<pre><code>&lt;div&gt;
  &lt;h4&gt;{{products[0]}}&lt;/h4&gt;
  &lt;p&gt;50만원&lt;/p&gt;
  &lt;button&gt;허위매물신고&lt;/button&gt;
  &lt;span&gt;신고수 : 0&lt;/span&gt;
&lt;/div&gt;</code></pre><p>이거 버튼을 누르면 옆에 있는 신고수가 1 증가하는 기능을 만들어볼겁니다.</p>
<p>그러려면 어떻게 해야할지 생각해봅시다.</p>
<p>일단 신고수를 기록할 수 있는 변수나 데이터가 하나 필요하지 않을까요?</p>
<p>data(){
  return {
    신고수 : 0,
  }
}</p>
<div>
  <h4>{{products[0]}}</h4>
  <p>50만원</p>
  <button>허위매물신고</button>
  <span>신고수 : {신고수}</span>
</div>
1. 그래서 하단에 데이터를 하나 만들고 2. 데이터를 HTML란에 꽂아넣었습니다.

<p>이제 버튼을 누르면 신고수라는 데이터를 1증가시키면 신고수가 1이 되고 HTML도 재렌더링이 되고</p>
<p>원하는 기능이 완성될 것 같습니다.</p>
<p>버튼을 누르면 기능을 실행하고 싶은 경우</p>
<p>자바스크립트는 onclick=&quot;&quot; 이라는 이벤트 핸들러를 HTML태그에 달았지만</p>
<p>Vue에서는 @click=&quot;&quot; 이라고 사용합니다.</p>
<p>그럼 안에다가 자바스크립트를 자유롭게 입력가능합니다. </p>
<div>
  <h4>{{products[0]}}</h4>
  <p>50만원</p>
  <button @click="신고수++">허위매물신고</button>
  <span>신고수 : {신고수}</span>
</div>


<p>이거 버튼을 누르면 신고수라는 데이터를 +1 해주고 싶어서 저렇게 작성했습니다.</p>
<p>그럼 버튼 누를 때마다 신고수가 +1 됩니다. 끝!</p>
<p>신고수++</p>
<p>신고수+=1</p>
<p>아무렇게나 작성할 수 있습니다.</p>
<p>@click 말고 다른 이벤트 핸들러도 만들 수 있습니다.</p>
<p>@mouseover 하면 클릭이 아니라 마우스만 댔을 때 자바스크립트를 실행가능하고</p>
<p>@input 하면 인풋에 값을 입력했을 때 자바스크립트를 실행가능하고</p>
<p>님들이 알던 이벤트명을 자유롭게 기입해주면 됩니다. 모르면 어쩔 수 없이 @click이나 씁시다. </p>
<p>코드가 길 경우 함수를 만들어씁니다</p>
<p>긴 코드를 짧게 축약해주는게 바로 함수문법입니다.</p>
<p>그래서 @click 안에 들어갈 말이 너무 길다면 함수를 만들어서 집어넣으십시오.</p>
<p>함수 만드는 자리는 이미 정해져있습니다. 밑에서 methods : {} 라는 항목을 신설해주면 됩니다. </p>
<p>data(){
  return {
    신고수 : 0,
  },
}</p>
<p>methods : { 
  increase(){ 
    this.신고수 += 1 
  } 
}</p>
<p>methods라는 항목을 만드신 후</p>
<p>함수를 안에다가 계속 만들어낼 수 있습니다. 함수만들 땐 함수이름(){} 이게 끝입니다.</p>
<p>그리고 이건 꼭 기억해야하는 부분인데</p>
<p>여기서 데이터를 가져다쓰고 싶으면 꼭 this.데이터이름 이라고 사용해야합니다.</p>
<p>this는 그냥 위에 있는 데이터와 함수를 담은 큰 object라고 생각하시면 되겠습니다. </p>
<p>그리고 아까 HTML 부분에서 함수를 자유롭게 사용하면</p>
<p>아까 축약했던 this.신고수+=1 이게 실행됩니다. 끝 </p>
<div>
  <h4>{{products[0]}}</h4>
  <p>50만원</p>
  <button @click="increase()">허위매물신고</button>
  <span>신고수 : {신고수}</span>
</div>
함수() 이렇게 하셔도 되고

<p>함수이름만 쓰셔도 됩니다. @click=&quot;increase&quot; 이렇게요. </p>
<p>함수는 한글로 작명하면 잘 안될 수 있습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코비] 24년 1월 2주차 웹 개발자 면접 예상질문 - React]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%BD%94%EB%B9%84-24%EB%85%84-1%EC%9B%94-2%EC%A3%BC%EC%B0%A8-%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-%EC%98%88%EC%83%81%EC%A7%88%EB%AC%B8-React-oyjn0apt</link>
            <guid>https://velog.io/@jeong_yooony/%EC%BD%94%EB%B9%84-24%EB%85%84-1%EC%9B%94-2%EC%A3%BC%EC%B0%A8-%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-%EC%98%88%EC%83%81%EC%A7%88%EB%AC%B8-React-oyjn0apt</guid>
            <pubDate>Mon, 05 Feb 2024 08:58:32 GMT</pubDate>
            <description><![CDATA[<h1 id="🌈-http와-https의-차이점에-대해-설명해주세요">🌈 <a href="https://seo.tbwakorea.com/blog/https-http/#:~:text=HTTP%EC%99%80%20HTTPS%EC%9D%98%20%EA%B0%80%EC%9E%A5,%EC%84%B8%EC%85%98%20%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC%20%EC%95%94%ED%98%B8%ED%99%94%ED%95%A9%EB%8B%88%EB%8B%A4.">http와 https의 차이점에 대해 설명해주세요</a></h1>
<h2 id="https와-http란">HTTPS와 HTTP란?</h2>
<p>HTTP(HyperText Transfer Protocol)란 하이퍼텍스트 전송 프로토콜로, 서버/클라이언트 모델을 따라 데이터를 주고받기 위한 프로토콜입니다. HTTP 프로토콜을 사용하여 통신을 수행하면 홈페이지 URL이 ‘http://’로 시작합니다.</p>
<p>간단히 말하자면, HTTP 프로토콜은 네트워크 통신을 작동하게 하는 기본 기술입니다.</p>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/701c789f-f580-4f91-b4c6-b867022eb3ad/image.png" alt=""></p>
<p>HTTPS(HyperText Transfer Protocol Secure)란 HTTP의 확장 버전으로 보다 안전한 버전입니다. HTTPS 프로토콜을 사용하여 통신을 수행하면 홈페이지 URL이 ‘https://’로 시작하고, 통신하는 과정에서 HTTPS는 전송 내용을 암호화합니다. 암호화를 통해 발신자와 수신자를 제외한 중간 매개체에서 통신 내용을 확인할 수 없기 때문에, 발신자가 전송한 암호 및 기밀문서를 보호할 수 있습니다.</p>
<table>
<thead>
<tr>
<th></th>
<th>HTTP</th>
<th>HTTPS</th>
</tr>
</thead>
<tbody><tr>
<td>의미</td>
<td>Hypertext Transfer Protocol</td>
<td>Hypertext Transfer Protocol Secure</td>
</tr>
<tr>
<td>용도</td>
<td>이전 텍스트 기반 웹사이트</td>
<td>모든 최신 웹사이트</td>
</tr>
<tr>
<td>보안</td>
<td>추가 보안 기능 없음</td>
<td>퍼블릭 키 암호화에 SSL 인증서 사용</td>
</tr>
<tr>
<td>이점</td>
<td>인터넷을 통한 통신 지원</td>
<td>웹사이트에 대한 권위, 신뢰성 및 검색 엔진 순위 개선</td>
</tr>
</tbody></table>
<p>HTTP와 HTTPS의 가장 큰 차이점은 보안입니다. HTTPS는 HTTP와 다르게, 브라우저와 서버가 데이터를 전송하기 전에 안전하고 암호화된 연결을 설정하기 때문에 보다 안전하다는 장점이 있습니다. HTTPS는 SSL/TLS 프로토콜을 통해 세션 데이터를 암호화합니다. </p>
<h2 id="https와-http의-작동-방식">HTTPS와 HTTP의 작동 방식</h2>
<h3 id="http-작동-방식">HTTP 작동 방식</h3>
<ul>
<li><p>HTTP는 OSI(Open Systems Interconnection) 네트워크 통신 모델의 애플리케이션 계층 프로토콜로, 여러 유형의 요청과 응답을 정의하고 있습니다.</p>
</li>
<li><p>숫자 코드 및 데이터 양식으로 서버에서 다양한 유형의 HTTP 응답을 전송하는데요. 몇 가지 예시를 살펴보겠습니다.</p>
</li>
<li><p>200 – OK (정상)</p>
</li>
<li><p>400 – Bad request (잘못된 요청)</p>
</li>
<li><p>404 – Resource not found (리소스를 찾을 수 없음)</p>
</li>
</ul>
<p>HTTP는 암호화되지 않은 데이터를 전송해, 제3자가 그 정보를 가로채고 읽을 수 있습니다. HTTP 상태코드에 대해 더 자세히 알고 싶다면 HTTP 상태 코드란? 의미부터 빠른 확인까지 글을 참고해보세요.</p>
<h3 id="https-작동-방식">HTTPS 작동 방식</h3>
<p>HTTPS는 HTTP 요청 및 응답을 SSL/TLS 기술에 결합합니다. SSL/TLS는 HTTPS의 암호화 방식으로, 이를 통해 HTTPS 암호화를 진행하고 발신자와 수신자만 해당 암호를 해독하여 정보를 주고받을 수 있습니다.</p>
<p>HTTPS 암호화를 책임지는 SSL 인증서는 다음과 같이 동작합니다. 기존의 HTTP는 클라이언트 서버 통신을 진행할때 두 당사자만 믿고 통신하게 됩니다. 하지만 HTTPS는 제3자가 등장합니다. 즉 신뢰할 수 있는 다른 기관이 존재하고 해당 기관이 서버 또는 클라이언트에 SSL 인증서를 발급하여 해당 당사자를 보증하는 방법입니다.</p>
<p>위 방법을 통해 우리는 접속할 사이트가 진짜 은행 사이트인지 가짜 은행 사이트인지 SSL 인증서를 통해 신뢰할 수 있게 됩니다. 그 후 신뢰된 당사자 간 암호화된 통신을 진행하면 외부에서는 어떤 내용으로 통신하는지 알 수 없습니다.</p>
<h2 id="웹사이트-https-전환의-중요성">웹사이트 HTTPS 전환의 중요성</h2>
<h3 id="1-보안에-우수한-https">1) 보안에 우수한 HTTPS</h3>
<p>HTTPS는 HTTP와 달리 모든 데이터를 암호화된 형태로 전송합니다. HTTP는 사용자가 민감한 데이터를 전송할 때 제3자가 네트워크를 통해 해당 데이터를 가로챌 수 없어 보안 측면에서 더 우수합니다.</p>
<p>예를 들어, 신용카드 세부 정보 혹은 고객 개인 정보와 같은 민감한 정보를 보호하기 위해서는 HTTPS를 사용하는 것이 좋습니다.</p>
<h3 id="2-신뢰성이-높은-https">2) 신뢰성이 높은 HTTPS</h3>
<p>HTTP는 HTTPS에 비해 신뢰성이 더 낮기 때문에 검색 엔진은 HTTP 웹사이트 순위를 HTTPS보다 낮게 지정합니다.</p>
<p>또한, 브라우저 주소 표시줄에서 URL 옆에 자물쇠 아이콘을 배치해 사용자에게 HTTPS 연결을 표시하고, 사용자는 추가 보안에 대한 신뢰로 HTTP보다 HTTPS를 선호합니다.</p>
<h3 id="3-seo에-좋은-영향을-주는-https">3) SEO에 좋은 영향을 주는 HTTPS</h3>
<p>앞에서 잠시 언급했듯이, 구글은 몇 년 전부터 HTTPS를 순위 결정 신호로 사용하고 있습니다. HTTP에서 HTTPS로의 전환은 SEO (검색엔진최적화)에 좋은 영향을 미칩니다. 즉, HTTPS를 사용하지 않을 경우 검색 결과에서 하단으로 밀려나게 됩니다.</p>
<p>또한, 구글은 HTTPS를 사용하지 않을 경우 URL 창에 “안전하지 않음”이라는 경고를 띄우는 패널티를 적용하고 있습니다.</p>
<p>위에서 살펴본 바와 같이 HTTPS는 HTTP의 후속 모델입니다. 따라서 HTTP를 사용할지 HTTPS를 사용할지 결정하기 보단 무조건 보안을 위해 HTTPS를 사용해야 합니다. 추가로 2021년부터 대부분의 브라우저는 HTTP로 통신하는 웹사이트에서 경고 문구를 발생시킵니다. 이 때문에 HTTP 통신을 사용해서 사이트에 방문하게 되면 경고 문구 때문에 바로 진입하지 못하게 됩니다.</p>
<p>이처럼, 사업자로서 고객의 정보를 보호하고 클라이언트 유입과 검색 결과 노출을 위해 웹사이트를 HTTPS로 전환해야 합니다.</p>
<h1 id="🌈-vanillajs와-비교하여-리액트를-사용하는-이유에-대해-설명해주실-수-있을까요">🌈 <a href="https://velog.io/@ek2356/VanillaJS%EC%99%80-%EB%B9%84%EA%B5%90%ED%95%98%EC%97%AC-%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">VanillaJS와 비교하여 리액트를 사용하는 이유에 대해 설명해주실 수 있을까요?</a></h1>
<p>리액트를 쓰는 이유는 동적으로 변하는 웹 서비스가 많아진 현재 상황에서 사용자에게 더 좋은 인터페이스(UI)와 경험(UX)을 제공하기 위해서이다.
VanillaJS는 외부 라이브러리나 프레임워크를 쓰지않는 순수 자바스크립트를 뜻하는데, VanillaJS에 비해 React는 더 빠른 렌더링 및 업데이트를 위한 가상 DOM과 대규모 커뮤니티를 가지고 있으며 더 효율적이고 확장 가능성이 있다.
기본적으로 view, UI를 위한 라이브러리(프레임워크라고 말하기도 하는데 사실은 라이브러리다)인 리액트는 정말 손쉽게 view를 컨트롤할 수 있도록 제작됐다.</p>
<p>바닐라 자바스크립트 등 프레임워크를 이용하지 않은 순수 개발은 정해진 규격이 없어 개발하는 사람들에 따라 중구난방식 코드가 짜진다고 한다.
특히 기업에서 협업할 시, 프레임워크의 틀 없이 짜면, 새로 팀에 합류할 사람들이 코드에 적응하기 힘들며 해당 코드를 수정하는 것에 있어서도 진입 장벽이 높다.
그래서 코드의 일관성은 모든 개발에서 프레임워크 사용의 장점이라고도 볼 수 있다.</p>
<p>..
사실 React와 같은 라이브러리는 자체적으로 기능을 제공하고 추상화된 프로그래밍 패턴을 제공하기 때문에 이로 인해 추가적인 코드와 리소스가 필요하다. 또한, 모듈 시스템, Babel 및 웹팩과 같은 도구들의 사용으로 인해 비용이 발생할 수 있다. 따라서 Vanilla JavaScript와 비교했을 때 React는 일반적으로 더 큰 크기를 가질 수 밖에 없다.
앱의 크기는 성능에 영향을 미치는 중요한 요소 중 하나로 크기가 커지면 클라이언트 측에서 네트워크 전송 및 처리에 필요한 시간도 증가한다. 특히 인터넷 연결 속도가 느린 사용자의 경우 더욱 그렇다.</p>
<p>React는 빠른 업데이트를 위해서 Virtual DOM 을 사용하여 UI 업데이트를 최적화하고 성능을 향상시킨다. 하지만 Virtual DOM을 사용한다고 해서 &quot;Vanilla 보다&quot; 더 속도가 빠르다 라는 얘기는 아니다. React는 충분히 빠르지만 그만큼 부가적인 빌드 코드와 React 환경 관련된 코드들이 추가되기 때문에 성능 면으로는 바닐라 스크립트보다 뒤떨어질 수도 있다.</p>
<p>React는 중복되는 UI 코드들을 컴포넌트화를 시킬 수 있고 이로 인해 코드의 재사용성이 증가한다는 이점이 있다. 이는 곧 개발기간 단축과 유지 보수의 용이를 뜻한다. 먼 옛날에 비해 웹의 성능은 충분히 좋아졌지만, 반대로 개발자의 몸값은 배로 뛰었다. 결론적으로 대부분의 프로그래머들은 유지 보수 하는 비용을 아끼는 것이 최종적으로 더 큰 이득이라는 결론을 내렸다.</p>
<p>Vanilla 와 React를 직접적으로 성능 면에서 비교하는 것은 어렵기도 하고 비교 자체가 의미 없을 수 있다고 생각한다.</p>
<h1 id="🌈-리액트에서-상태의-불변성이-중요한-이유가-무엇인가요">🌈 <a href="https://velog.io/@dikum98/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-%EB%B6%88%EB%B3%80%EC%84%B1%EC%9D%B4-%EC%A4%91%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0#:~:text=%EB%B6%88%EB%B3%80%EC%84%B1%EC%9D%B4%20%EC%97%86%EB%8B%A4%EB%A9%B4%20%EB%AA%A8%EB%93%A0%20%EC%9E%90%EC%8B%9D,%EB%A5%BC%20%EA%B0%90%EC%A7%80%ED%95%A0%20%EC%88%98%20%EC%9E%88%EB%8B%A4.">리액트에서 상태의 불변성이 중요한 이유가 무엇인가요?</a></h1>
<h2 id="상태를-효율적으로-업데이트하기-위함">상태를 효율적으로 업데이트하기 위함</h2>
<p>리액트에서 상태의 불변성을 지켜야하는 이유는 리액트가 상태를 업데이트하는 원리 때문이다.</p>
<p>리액트는 내부적으로 UI를 최신화하기 위해 비용이 많이 드는 DOM 작업의 수를 최소화 하기위해 성능 최적화를 진행한다.</p>
<p>컴포넌트의 prop이나 state가 변경되면 리액트는 새로 반환된 엘리먼트를 이전에 렌더링된 엘리먼트와 비교해서 실제 DOM 업데이트가 필요한지 여부를 결정한다. 같지 않을 경우 리액트는 DOM을 업데이트 한다.</p>
<blockquote>
<p>여기서 렌더링이란, 컴포넌트의 현재 props와 state에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미한다.
하지만 렌더링이 항상 UI 업데이트를 의미하는 것은 아니다.</p>
</blockquote>
<p>리액트에서는 값 자체가 아닌 참조값을 비교(얕은 비교)하므로 참조값이 이전과 동일하면, 상태의 변화를 감지할 수 없다.</p>
<p>얕은 비교는 계산 리소스를 줄여주기 때문에 효율적인 상태 업데이트가 가능하다. 불변성이 없다면 모든 자식 상태 트리를 깊은 비교(deep compare)해야 하므로 비용이 많이 든다.</p>
<p>이러한 이유로 상태를 업데이트할 때 새로운 참조값을 가진 배열이나 객체를 생성하여 불변성을 지켜줌으로써 리액트는 상태 변화를 감지할 수 있다.</p>
<pre><code class="language-jsx">const [info, setInfo] = useState({ id: 1, name: &#39;kim&#39; });

setState({...info, id: 1004});</code></pre>
<h2 id="예측-가능성">예측 가능성</h2>
<p>원본 데이터가 훼손될 경우 예기치 못한 변경을 발생시켜 사이드 이펙트를 유발하고 버그가 발생할 가능성이 존재한다. 프로그래밍의 복잡도 또한 올라간다.</p>
<p>반면에 불변성을 지킬경우 의도치 않은 변경 없이 애플리케이션 아키텍처를 단순하게 유지할 수 있어 보다 쉽게 예측 가능하다.</p>
<p>또한 불변성을 지킴으로써 변경이 발생했는지 여부를 매번 관찰하지 않고, 실제 변경이 발생한 경우만 쉽게 감지할 수 있다. 즉, 불필요한 관찰을 수행할 필요가 없다. 이는 곧 애플리케이션 최적화에도 도움이 된다.</p>
<h1 id="🌈-라이프사이클이-의미하는-바에-대해서-설명해주세요">🌈 <a href="https://velog.io/@yellog/TIL-74#:~:text=%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4%EC%9D%B4%20%EC%9D%98%EB%AF%B8%ED%95%98%EB%8A%94,%ED%95%98%EB%8A%94%EC%A7%80%EB%A5%BC%20%EC%84%A4%EB%AA%85%ED%95%98%EB%8A%94%20%EA%B0%9C%EB%85%90%EC%9E%85%EB%8B%88%EB%8B%A4.">라이프사이클이 의미하는 바에 대해서 설명해주세요.</a></h1>
<p>라이프사이클은 어떤 시스템, 제품, 프로세스 또는 개체가 경험하는 다양한 단계나 상태의 연속성을 나타냅니다. 이것은 해당 개체가 어떻게 생성되고, 성장하며, 운영되며, 소멸하는지를 설명하는 개념입니다. 라이프사이클은 다음과 같이 주로 나눠집니다.</p>
<ul>
<li>생성 (Creation): 개체나 시스템이 처음 만들어지는 단계로 초기화와 설정이 이루어집니다.</li>
<li>성장 (Growth): 개체가 기능을 확장하고 발전하며, 새로운 기능이나 업데이트가 추가됩니다.</li>
<li>운영 (Operation): 개체가 실제 환경에서 사용되며, 주요 기능을 수행하고 유지보수가 이루어집니다.</li>
<li>유지보수 (Maintenance): 필요한 경우 개체를 업데이트하고 고치며, 성능을 최적화하고 문제를 해결합니다.</li>
<li>소멸 (Decommissioning): 개체가 더 이상 사용되지 않거나 대체될 때 종료되는 단계로, 데이터의 삭제 또는 저장이 이루어질 수 있습니다.</li>
</ul>
<p>라이프사이클 관리는 제품 개발, 프로젝트 관리, 시스템 운영, 유지보수, 제품 수명 주기 평가 등 다양한 분야에서 중요한 역할을 합니다. 이를 통해 개체나 시스템이 효율적으로 관리되며, 변경사항이나 문제를 식별하고 처리할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240202 최종 프로젝트 - Vue]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240202-%EA%B8%B0%EB%A1%9D-Vue</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240202-%EA%B8%B0%EB%A1%9D-Vue</guid>
            <pubDate>Fri, 02 Feb 2024 08:35:25 GMT</pubDate>
            <description><![CDATA[<h1 id="vue-사전공부하기">Vue 사전공부하기</h1>
<h2 id="0강-vuejs-왜-쓸까">0강. Vue.js 왜 쓸까?</h2>
<ul>
<li>web-app 만들 때 쓴다.<ul>
<li>mobile app 처럼 쓸 수 있다고 해서 web-app</li>
</ul>
</li>
<li>카카오 프론트엔드 반 vue, 반 react이다.<h3 id="1-vuejs-쉬워서-쓴다">1. vue.js 쉬워서 쓴다.</h3>
<ul>
<li>react, angular보다 성능 떨어지는거 아니야?<ul>
<li>과정만 다른거임</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/8bac2fce-dc1a-4c99-a431-d03a5f03e837/image.png" alt=""></p>
<h3 id="2-개발할-때-방법이-정해져-있다">2. 개발할 때 방법이 정해져 있다.</h3>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/6026cb10-690a-476f-8dad-bbe95e0b358b/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/51469d14-dd08-4978-b7dd-583a1a72f858/image.png" alt=""></p>
<ul>
<li>vue는 방법이 하나라 초보가 사용하기 아주 좋음.</li>
<li>문법 몇개만 외워주면 초보도 output 쉽게 낼 수 있음. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/de980781-1569-4486-93cb-8f84a268870f/image.png" alt=""></p>
<h3 id="3-html-렌더링-속도가-react보다-빠름">3. HTML 렌더링 속도가 React보다 빠름</h3>
<h3 id="4-장기적-지원으로-업데이트-잘됨">4. 장기적 지원으로 업데이트 잘됨.</h3>
<hr>
<h2 id="1강-vue-3버전-설치랑-셋팅">1강. Vue 3버전 설치랑 셋팅</h2>
<h3 id="1-nodejs-설치">1. Node.js 설치</h3>
<p><a href="https://nodejs.org/en">https://nodejs.org/en</a></p>
<h3 id="2-vscode-설치">2. VScode 설치</h3>
<h3 id="3-vue-설치">3. Vue 설치</h3>
<pre><code class="language-bash">$ npm install -g @vue/cli@4.5.11</code></pre>
<h3 id="4-부가-기능-설치">4. 부가 기능 설치</h3>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/c0cdef10-948b-48bc-b4e5-9d3524e84af7/image.png" alt=""></p>
<h3 id="모듈-재설치-방법"><a href="https://velog.io/@hrzo1617/Mac-node-npm-%EC%82%AD%EC%A0%9C-%EB%B0%8F-%EC%9E%AC%EC%84%A4%EC%B9%98">모듈 재설치 방법</a></h3>
<pre><code class="language-bash">sudo rm -rf /usr/local/lib/node
sudo rm -rf /usr/local/lib/node_modules
sudo rm /usr/local/lib/dtrace/node.d
sudo rm /usr/local/share/man/man1/node.1
sudo rm -rf /usr/local/share/doc/node
sudo rm -rf /usr/local/share/systemtap/tapset/node.stp
sudo rm -rf /usr/local/include/node
sudo rm /usr/local/bin/node
sudo rm /usr/local/bin/npm
sudo rm /usr/local/bin/npx

//아래는 경우에따라 필요하면 입력한다.
sudo rm -rf /Users/$USER/.npm
sudo rm -rf ~/.npm

//Homebrew로 설치한 경우는 아래 명령어도 입력해야한다.
brew uninstall node
brew doctor
brew cleanup

// node 설치
brew install node

node -v        //node 버전 확인
            //X.X.X와 같은 형태로 나오면 ok
npm -v        //npm 버전 확인
            //X.X.X와 같은 형태로 나오면 ok</code></pre>
<hr>
<h2 id="초기-세팅-정보">초기 세팅 정보</h2>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/be95eb0f-752c-437a-ab8a-107b27a50deb/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/e7bfb07a-d589-461a-9f85-22ffdcca2032/image.png" alt=""></p>
<pre><code class="language-bash">npm run serve</code></pre>
<blockquote>
</blockquote>
<ul>
<li>Local: <a href="http://localhost:8080/">http://localhost:8080/</a> </li>
<li>Network: <a href="http://192.168.100.90:8080/">http://192.168.100.90:8080/</a></li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/234f2bed-e647-41d1-b606-6c23a0238202/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240131 기록 - Jira]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240131-%EA%B8%B0%EB%A1%9D-Jira</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240131-%EA%B8%B0%EB%A1%9D-Jira</guid>
            <pubDate>Wed, 31 Jan 2024 07:13:15 GMT</pubDate>
            <description><![CDATA[<h1 id="jira">Jira</h1>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/e2632d4d-a2c4-4d8e-8c95-df8d045ab38c/image.png" alt=""></p>
<ul>
<li>백로그를 분류하는 큰 분류가 에픽이다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/50ff59f5-13eb-4659-8712-7d13250537e5/image.png" alt=""></p>
<ul>
<li>보드에서는 현재 진행중인 이슈만 보인다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/59232f49-c79d-486a-9280-51f344c70bd9/image.png" alt=""></p>
<ul>
<li>지난 스프린트를 다시 보고 싶다면 보고서를 확인하면 된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240130 기록 - Agile]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240130-%EA%B8%B0%EB%A1%9D-Agile</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240130-%EA%B8%B0%EB%A1%9D-Agile</guid>
            <pubDate>Wed, 31 Jan 2024 05:41:30 GMT</pubDate>
            <description><![CDATA[<h1 id="1-애자일이란">1. 애자일이란?</h1>
<h2 id="애자일이란">애자일이란?</h2>
<ul>
<li>애자일은 소프트웨어 팀이 특정 문제를 해결하는데 도움을 주기 위해 최적화된, 단수함을 유지해서 상대적으로 간단하게 개발하는 일련의 방법 또는 방법론을 말한다.</li>
<li>애자일은 프로젝트 관리, 소프트웨어 설계 및 아키텍처, 프로세스 개선 등 전통적인 소프트웨어 공학의 모든 분야에 걸쳐 존재한다.</li>
<li>애자일에 속한 방법 또는 방법론들에는 가능한 한 쉽게 적용할 수 있도록 최적화되고 간결한 프랙티스들이 포함되어 있다.</li>
</ul>
<h3 id="스크럼은-가장-일반적인-애자일-방법">스크럼은 가장 일반적인 애자일 방법</h3>
<ul>
<li>제품책임자(PO): 팀과 협력하여 제품 백로그를 관리</li>
<li>스크럼 마스터: 가시화된 문제점을 처리하기 위해 팀을 도움</li>
<li>개발팀원: 모든 팀원</li>
</ul>
<hr>
<h1 id="2차-플젝-리팩토링">2차 플젝 리팩토링</h1>
<h2 id="서버">서버</h2>
<pre><code class="language-bash">git fetch origin
git checkout refactor/95-diary-annotation</code></pre>
<pre><code class="language-bash">git fetch origin
git checkout feat/64-create-friend-api</code></pre>
<h2 id="안드로이드">안드로이드</h2>
<pre><code class="language-bash">git fetch origin
git checkout refactor/78-diary-annotation</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240126~28 2차 프로젝트 - Kotlin 다이어리 API 연동 / SpringBoot 친구 RestAPI]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-24012628-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Kotlin-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-API-%EC%97%B0%EB%8F%99-SpringBoot-%EC%B9%9C%EA%B5%AC-RestAPI</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-24012628-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Kotlin-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-API-%EC%97%B0%EB%8F%99-SpringBoot-%EC%B9%9C%EA%B5%AC-RestAPI</guid>
            <pubDate>Tue, 30 Jan 2024 00:17:40 GMT</pubDate>
            <description><![CDATA[<h1 id="kotlin-안드로이드">Kotlin 안드로이드</h1>
<h2 id="다이어리-api-연동">다이어리 API 연동</h2>
<h3 id="객체-ver">객체 ver</h3>
<pre><code class="language-bash">git fetch origin
git checkout feat/33-diary-api</code></pre>
<h3 id="하드-ver">하드 ver</h3>
<pre><code class="language-bash">git fetch origin
git checkout feat/49-diary-api-hard</code></pre>
<hr>
<h1 id="springboot-친구-구현">SpringBoot 친구 구현</h1>
<h2 id="친구-api-구현">친구 API 구현</h2>
<h3 id="api-명세서-작성"><a href="https://wookcode.tistory.com/7">API 명세서 작성</a></h3>
<h3 id="코드">코드</h3>
<pre><code class="language-bash">git fetch origin
git checkout feat/44-ceate-friend

git fetch origin
git checkout feat/64-create-friend-api</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240125 2차 프로젝트 - SpringBoot 다이어리 / 친구 Rest API 리팩토링]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240125-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-SpringBoot-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%B9%9C%EA%B5%AC-Rest-API-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240125-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-SpringBoot-%EB%8B%A4%EC%9D%B4%EC%96%B4%EB%A6%AC-%EC%B9%9C%EA%B5%AC-Rest-API-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Fri, 26 Jan 2024 00:17:35 GMT</pubDate>
            <description><![CDATA[<h1 id="백엔드">백엔드</h1>
<h2 id="다이어리-restapi-수정">다이어리 RestAPI 수정</h2>
<h3 id="crud-리팩토링">CRUD 리팩토링</h3>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/13f5900f-9e72-4b2a-8116-c9eff26a1d72/image.png" alt=""></p>
<p><strong>controller/dto/request/DiaryCreateRequest.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.controller.dto.request;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class DiaryCreateRequest {
    private final Long userId;
    private final LocalDateTime date;
    private final String content;
}</code></pre>
<p><strong>controller/dto/request/DiaryUpdateRequest.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.controller.dto.request;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class DiaryUpdateRequest {
    private final String content;
}</code></pre>
<p><strong>controller/dto/response/GetDiaryResponse.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.controller.dto.response;

import java.time.LocalDateTime;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class GetDiaryResponse {

    private final Long id;
    private final String content;
    private final LocalDateTime date;

}</code></pre>
<p><strong>controller/DiaryController.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.controller;

import java.net.URI;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import kr.sesac.aoao.server.diary.controller.dto.request.DiaryCreateRequest;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryUpdateRequest;
import kr.sesac.aoao.server.diary.controller.dto.response.GetDiaryResponse;
import kr.sesac.aoao.server.diary.service.DiaryService;
import kr.sesac.aoao.server.global.controller.dto.response.ApplicationResponse;
import lombok.RequiredArgsConstructor;

/**
 * @since 2024.01.23
 * @author 최정윤
 */
@RestController
@RequestMapping(&quot;/diary&quot;)
@RequiredArgsConstructor
public class DiaryController {

    private final DiaryService diaryService;

    /**
     * 다이어리 작성
     * @since 2024.01.23
     * @return createDiary
     * @author 최정윤
     */
    @PostMapping
    public ResponseEntity&lt;ApplicationResponse&lt;Void&gt;&gt; createDiary(@RequestParam Long userId, @PathVariable Long date, @RequestBody DiaryCreateRequest request) {
        Long diaryId = diaryService.createDiary(userId, date, request);
        return ResponseEntity.created(URI.create(&quot;/diary/&quot; + diaryId)).build();
    }

    /**
     * 다이어리 정보 조회
     * @since 2024.01.23
     * @return getDiaryInfo
     * @author 최정윤
     * 완료
     */
    @GetMapping(&quot;/&quot;)
    public ResponseEntity&lt;ApplicationResponse&lt;GetDiaryResponse&gt;&gt; getDiaryInfo(@RequestParam Long userId) {
        GetDiaryResponse userDiaryResponse = diaryService.getDiaryInfo(userId);
        return ResponseEntity.ok(ApplicationResponse.success(userDiaryResponse));
    }

    /**
     * 다이어리 수정
     * @since 2024.01.23
     * @return updateDiary
     * @author 최정윤
     */
    @PostMapping(&quot;/{diaryId}&quot;)
    public ResponseEntity&lt;ApplicationResponse&lt;Void&gt;&gt; updateDiary(
        @RequestParam Long userId, @PathVariable Long diaryId,
        @RequestBody DiaryUpdateRequest request) {
        diaryService.updateDiary(userId, diaryId, request);
        return ResponseEntity.ok().build();
    }

    /**
     * 다이어리 삭제
     * @since 2024.01.23
     * @return deleteDiary
     * @author 최정윤
     */
    @DeleteMapping(&quot;/{diaryId}&quot;)
    public ResponseEntity&lt;ApplicationResponse&lt;Void&gt;&gt; deleteDiary(
        @RequestParam Long userId, @PathVariable Long diaryId) {
        diaryService.deleteDiary(userId, diaryId);
        return ResponseEntity.noContent().build();
    }

}</code></pre>
<p><strong>donmain/Diary.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.domain;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @since 2024.01.23
 * @author 최정윤
 */
@Getter
@AllArgsConstructor
public class Diary {
    private final Long diaryId; // 일기 id
    private final String content; // 일기 내용
    private final LocalDateTime date; // 캘린더 설정 날짜
    private final Long userId; // 일기 작성자 id
    private final LocalDateTime createdAt; // 일기 생성 일자
    private final LocalDateTime updateAt; // 일기 수정 일자

}</code></pre>
<p><strong>exception/DiaryErrorCode.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.exception;

import org.springframework.http.HttpStatus;

import kr.sesac.aoao.server.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum DiaryErrorCode implements ErrorCode {
    NO_DIARY(HttpStatus.BAD_REQUEST, &quot;일기 정보가 존재하지 않습니다.&quot;),
    EMPTY_DIARY(HttpStatus.BAD_REQUEST, &quot;내용이 비어있습니다.&quot;);

    private final HttpStatus httpStatus;
    private final String message;
    @Override
    public HttpStatus getStatusCode() {
        return httpStatus;
    }

    @Override
    public String getMessage() {
        return message;
    }
}</code></pre>
<p><strong>repository/DiaryEntity.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.repository;

import java.time.LocalDate;
import java.time.LocalDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryCreateRequest;
import kr.sesac.aoao.server.global.entity.BaseEntity;
import kr.sesac.aoao.server.global.exception.ApplicationException;
import kr.sesac.aoao.server.todo.exception.TodoFolderErrorCode;
import kr.sesac.aoao.server.todo.repository.PaletteEntity;
import kr.sesac.aoao.server.user.repository.UserEntity;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = &quot;diary&quot;)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class DiaryEntity extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // 다이어리 아이디

    @Column
    private String content; // 일기 내용

    @Column
    private LocalDateTime date; // 캘린더 설정 날짜

    @ManyToOne
    @JoinColumn(name = &quot;user_id&quot;)
    private UserEntity user; // 일기 작성자

    public DiaryEntity(String content, LocalDateTime date, UserEntity user) {
        this.content = content;
        this.date = date;
        this.user = user;
    }

    public void diaryUpdate(UserEntity user, String content) {
        validateUserIsWriter(user);
        this.content = content;
    }

    public void validateUserIsWriter(UserEntity user) {
        if (!this.user.isWriter(user)) {
            throw new ApplicationException(TodoFolderErrorCode.IS_NOT_WRITER);
        }
    }

}</code></pre>
<p><strong>repository/DiaryJpaRepository.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import kr.sesac.aoao.server.dino.repository.DinoEntity;
import kr.sesac.aoao.server.user.repository.UserEntity;

public interface DiaryJpaRepository extends JpaRepository &lt;DiaryEntity, Long&gt; {

    Optional&lt;DiaryEntity&gt; findByUser(UserEntity user);
}</code></pre>
<p><strong>service/DiaryService.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.service;

import org.springframework.stereotype.Service;

import kr.sesac.aoao.server.diary.controller.dto.request.DiaryCreateRequest;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryUpdateRequest;
import kr.sesac.aoao.server.diary.controller.dto.response.GetDiaryResponse;

@Service
public interface DiaryService {

    GetDiaryResponse getDiaryInfo(Long userId);

    Long createDiary(Long userId, Long date, DiaryCreateRequest request);

    void updateDiary(Long userId, Long diaryId, DiaryUpdateRequest request);

    void deleteDiary(Long userId, Long diaryId);


}</code></pre>
<p><strong>DiaryServiceImpl.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.service;

import org.springframework.stereotype.Service;
import jakarta.transaction.Transactional;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryCreateRequest;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryUpdateRequest;
import kr.sesac.aoao.server.diary.controller.dto.response.GetDiaryResponse;
import kr.sesac.aoao.server.diary.exception.DiaryErrorCode;
import kr.sesac.aoao.server.diary.repository.DiaryEntity;
import kr.sesac.aoao.server.diary.repository.DiaryJpaRepository;
import kr.sesac.aoao.server.global.exception.ApplicationException;
import kr.sesac.aoao.server.user.exception.UserErrorCode;
import kr.sesac.aoao.server.user.repository.UserEntity;
import kr.sesac.aoao.server.user.repository.UserJpaRepository;
import lombok.RequiredArgsConstructor;

/**
 * @since 2024.01.23
 * @author 최정윤
 */
@Service
@RequiredArgsConstructor
@Transactional
public class DiaryServiceImpl implements DiaryService{

    private final DiaryJpaRepository diaryJpaRepository;
    private final UserJpaRepository userRepository;
    private final DiaryJpaRepository diaryRepository;

    private GetDiaryResponse result(DiaryEntity diary){
        return new GetDiaryResponse(
            diary.getUser().getId(),
            diary.getContent(),
            diary.getDate()
        );
    }

    /**
     * 다이어리 작성
     * @since 2024.01.23
     * @return saveDiary
     * @author 최정윤
     */
    @Override
    public Long createDiary(Long userId, Long date, DiaryCreateRequest request) {
        UserEntity savedUser = findUserById(userId);

        DiaryEntity diaryEntity = new DiaryEntity(
            request.getContent(),
            request.getDate(),
            savedUser
        );
        return diaryJpaRepository.save(diaryEntity).getId();
    }

    /**
     * 다이어리 조회
     * @since 2024.01.123
     * @parameter userId
     * @return GetDiaryResponse
     * @author 최정윤
     */
    @Override
    public GetDiaryResponse getDiaryInfo(Long userId) {
        UserEntity user = userRepository.findById(userId)
            .orElseThrow(() -&gt; new ApplicationException(UserErrorCode.NOT_FOUND_USER));
        DiaryEntity diary = diaryRepository.findByUser(user)
            .orElseThrow(() -&gt; new ApplicationException(DiaryErrorCode.NO_DIARY));
        return result(diary);
    }

    /**
     * 다이어리 수정
     * @since 2024.01.23
     * @return updateDiary
     * @author 최정윤
     */

    @Override
    public void updateDiary(Long userId, Long diaryId, DiaryUpdateRequest request) {
        UserEntity savedUser = findUserById(userId);
        DiaryEntity savedDiary = findDiaryById(diaryId);

        savedDiary.diaryUpdate(savedUser, request.getContent());
    }


    /**
     * 다이어리 삭제
     * @since 2024.01.23
     * @return deleteDiary
     * @author 최정윤
     */
    @Override
    public void deleteDiary(Long userId, Long diaryId) {
        UserEntity savedUser = findUserById(userId);
        DiaryEntity savedDiary = findDiaryById(diaryId);

        savedDiary.validateUserIsWriter(savedUser);
        diaryJpaRepository.deleteById(savedDiary.getId());
    }

    private UserEntity findUserById(Long userId) {
        return userRepository.findById(userId)
            .orElseThrow(() -&gt; new ApplicationException(UserErrorCode.NOT_EXIST));
    }

    private DiaryEntity findDiaryById(Long diaryId) {
        return diaryRepository.findById(diaryId)
            .orElseThrow(() -&gt; new ApplicationException(DiaryErrorCode.NO_DIARY));
    }

}</code></pre>
<h3 id="api-통신-테스트-완료-코드">API 통신 테스트 완료 코드</h3>
<p><strong>DiaryCreateRequest.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.controller.dto.request;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class DiaryCreateRequest {
    private final String date;
    private final String content;
}</code></pre>
<p><strong>DiaryUpdateRequest.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.controller.dto.request;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class DiaryUpdateRequest {
    private final String content;
    private final boolean flag;
}</code></pre>
<p><strong>GetDiaryResponse.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.controller.dto.response;

import java.time.LocalDateTime;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class GetDiaryResponse {
    private final Long diaryId;
    private final String content;
    private final LocalDateTime date;

}</code></pre>
<p><strong>DiaryController.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.controller;

import java.net.URI;

import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import kr.sesac.aoao.server.diary.controller.dto.request.DiaryCreateRequest;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryUpdateRequest;
import kr.sesac.aoao.server.diary.controller.dto.response.GetDiaryResponse;
import kr.sesac.aoao.server.diary.service.DiaryService;
import kr.sesac.aoao.server.global.controller.dto.response.ApplicationResponse;
import kr.sesac.aoao.server.user.jwt.UserCustomDetails;
import lombok.RequiredArgsConstructor;

/**
 * @since 2024.01.23
 * @author 최정윤
 */
@RestController
@RequestMapping(&quot;/diary&quot;)
@RequiredArgsConstructor
public class DiaryController {

    private final DiaryService diaryService;

    /**
     * 다이어리 작성
     * @since 2024.01.23
     * @return createDiary
     * @author 최정윤
     */
    @PostMapping
    public ResponseEntity&lt;ApplicationResponse&lt;String&gt;&gt; createDiary(@AuthenticationPrincipal UserCustomDetails userDetails,
        @RequestBody DiaryCreateRequest request) {
        Long diaryId = diaryService.createDiary(userDetails.getUserEntity().getId(), request);
        return ResponseEntity.ok(ApplicationResponse.success(diaryId + &quot;번 다이어리가 생성되었습니다.&quot;));

    }

    /**
     * 다이어리 정보 조회
     * @since 2024.01.23
     * @return getDiaryInfo
     * @author 최정윤
     * 완료
     */
    @GetMapping
    public ResponseEntity&lt;ApplicationResponse&lt;GetDiaryResponse&gt;&gt; getDiaryInfo(
        @AuthenticationPrincipal UserCustomDetails userDetails, @RequestParam(&quot;date&quot;) String date) {
        GetDiaryResponse userDiaryResponse = diaryService.getDiaryInfo(userDetails.getUserEntity().getId(), date);
        return ResponseEntity.ok(ApplicationResponse.success(userDiaryResponse));
    }

    /**
     * 다이어리 수정
     * @since 2024.01.23
     * @return updateDiary
     * @author 최정윤
     */
    @PostMapping(&quot;/{diaryId}&quot;)
    public ResponseEntity&lt;ApplicationResponse&lt;String&gt;&gt; updateDiary(
        @AuthenticationPrincipal UserCustomDetails userDetails,
        @PathVariable Long diaryId,
        @RequestBody DiaryUpdateRequest request) {
        diaryService.updateDiary(userDetails.getUserEntity().getId(), diaryId, request);
        return ResponseEntity.ok(ApplicationResponse.success(diaryId + &quot;번 다이어리가 수정되었습니다.&quot;));
    }

    /**
     * 다이어리 삭제
     * @since 2024.01.23
     * @return deleteDiary
     * @author 최정윤
     */
    @DeleteMapping(&quot;/{diaryId}&quot;)
    public ResponseEntity&lt;ApplicationResponse&lt;String&gt;&gt; deleteDiary(
        @AuthenticationPrincipal UserCustomDetails userDetails, @PathVariable Long diaryId) {
        diaryService.deleteDiary(userDetails.getUserEntity().getId(), diaryId);
        return ResponseEntity.ok(ApplicationResponse.success(diaryId + &quot;번 다이어리가 삭제되었습니다.&quot;));
    }

}</code></pre>
<p><strong>Diary.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.domain;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @since 2024.01.23
 * @author 최정윤
 */
@Getter
@AllArgsConstructor
public class Diary {
    private final Long diaryId; // 일기 아이디
    private final String content; // 일기 내용
    private final LocalDateTime date; // 캘린더 설정 날짜
    private final Long userId; // 일기 작성자 아이디
    private final LocalDateTime createdAt; // 일기 생성 일자
    private final LocalDateTime updateAt; // 일기 수정 일자
}</code></pre>
<p><strong>DiaryErrorCode.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.exception;

import org.springframework.http.HttpStatus;

import kr.sesac.aoao.server.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum DiaryErrorCode implements ErrorCode {
    NO_DIARY(HttpStatus.BAD_REQUEST, &quot;일기 정보가 존재하지 않습니다.&quot;),
    EMPTY_DIARY(HttpStatus.BAD_REQUEST, &quot;내용이 비어있습니다.&quot;);

    private final HttpStatus httpStatus;
    private final String message;
    @Override
    public HttpStatus getStatusCode() {
        return httpStatus;
    }

    @Override
    public String getMessage() {
        return message;
    }
}</code></pre>
<p><strong>DiaryEntity.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.repository;

import java.time.LocalDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import kr.sesac.aoao.server.global.entity.BaseEntity;
import kr.sesac.aoao.server.global.exception.ApplicationException;
import kr.sesac.aoao.server.todo.exception.TodoFolderErrorCode;
import kr.sesac.aoao.server.user.repository.UserEntity;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = &quot;diary&quot;)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class DiaryEntity extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // 다이어리 아이디

    @Column
    private String content; // 일기 내용

    @Column
    private LocalDateTime date; // 캘린더 설정 날짜

    @ManyToOne
    @JoinColumn(name = &quot;user_id&quot;)
    private UserEntity user; // 일기 작성자

    public DiaryEntity(String content, LocalDateTime date, UserEntity user) {
        this.content = content;
        this.date = date;
        this.user = user;
    }

    public void diaryUpdate(UserEntity user, String content) {
        validateUserIsWriter(user);
        this.content = content;
    }

    public void validateUserIsWriter(UserEntity user) {
        if (!this.user.isWriter(user)) {
            throw new ApplicationException(TodoFolderErrorCode.IS_NOT_WRITER);
        }
    }

}</code></pre>
<p><strong>DiaryJpaRepository.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.repository;

import java.time.LocalDateTime;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import kr.sesac.aoao.server.user.repository.UserEntity;

public interface DiaryJpaRepository extends JpaRepository&lt;DiaryEntity, Long&gt; {

    Optional&lt;DiaryEntity&gt; findByUser(UserEntity user);

    Optional&lt;DiaryEntity&gt; findByUserAndDate(UserEntity user, LocalDateTime date);
}</code></pre>
<p><strong>DiaryService.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.service;

import org.springframework.stereotype.Service;

import kr.sesac.aoao.server.diary.controller.dto.request.DiaryCreateRequest;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryUpdateRequest;
import kr.sesac.aoao.server.diary.controller.dto.response.GetDiaryResponse;

@Service
public interface DiaryService {

    GetDiaryResponse getDiaryInfo(Long userId, String date);

    Long createDiary(Long userId, DiaryCreateRequest request);

    void updateDiary(Long userId, Long diaryId, DiaryUpdateRequest request);

    void deleteDiary(Long userId, Long diaryId);

}</code></pre>
<p><strong>DiaryServiceImpl.java</strong></p>
<pre><code class="language-java">package kr.sesac.aoao.server.diary.service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import org.springframework.stereotype.Service;

import jakarta.transaction.Transactional;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryCreateRequest;
import kr.sesac.aoao.server.diary.controller.dto.request.DiaryUpdateRequest;
import kr.sesac.aoao.server.diary.controller.dto.response.GetDiaryResponse;
import kr.sesac.aoao.server.diary.exception.DiaryErrorCode;
import kr.sesac.aoao.server.diary.repository.DiaryEntity;
import kr.sesac.aoao.server.diary.repository.DiaryJpaRepository;
import kr.sesac.aoao.server.global.exception.ApplicationException;
import kr.sesac.aoao.server.user.exception.UserErrorCode;
import kr.sesac.aoao.server.user.repository.UserEntity;
import kr.sesac.aoao.server.user.repository.UserJpaRepository;
import lombok.RequiredArgsConstructor;

/**
 * @since 2024.01.23
 * @author 최정윤
 */
@Service
@RequiredArgsConstructor
@Transactional
public class DiaryServiceImpl implements DiaryService {

    private final DiaryJpaRepository diaryJpaRepository;
    private final UserJpaRepository userRepository;
    private final DiaryJpaRepository diaryRepository;

    private GetDiaryResponse result(DiaryEntity diary) {
        return new GetDiaryResponse(
            diary.getId(),
            diary.getContent(),
            diary.getDate()
        );
    }

    /**
     * 다이어리 작성
     * @since 2024.01.23
     * @return saveDiary
     * @author 최정윤
     */
    @Override
    public Long createDiary(Long userId, DiaryCreateRequest request) {

        // Long 값을 &quot;yyyyMMdd&quot; 형식의 날짜 문자열로 변환
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;yyyyMMdd&quot;);
        String dateString = String.valueOf(request.getDate());
        LocalDate localDate = LocalDate.parse(dateString, formatter);

        UserEntity savedUser = findUserById(userId);

        DiaryEntity diaryEntity = new DiaryEntity(
            request.getContent(),
            localDate.atStartOfDay(),
            savedUser
        );
        return diaryJpaRepository.save(diaryEntity).getId();
    }

    /**
     * 다이어리 조회
     * @since 2024.01.23
     * @parameter userId
     * @return GetDiaryResponse
     * @author 최정윤
     */
    @Override
    public GetDiaryResponse getDiaryInfo(Long userId, String date) {
        // Long 값을 &quot;yyyyMMdd&quot; 형식의 날짜 문자열로 변환
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;yyyyMMdd&quot;);
        String dateString = String.valueOf(date);
        LocalDate localDate = LocalDate.parse(dateString, formatter);

        UserEntity user = userRepository.findById(userId)
            .orElseThrow(() -&gt; new ApplicationException(UserErrorCode.NOT_FOUND_USER));
        DiaryEntity diary = diaryRepository.findByUserAndDate(user, localDate.atStartOfDay())
            .orElseThrow(() -&gt; new ApplicationException(DiaryErrorCode.NO_DIARY));

        return result(diary);
    }

    /**
     * 다이어리 수정
     * @since 2024.01.23
     * @return updateDiary
     * @author 최정윤
     */

    @Override
    public void updateDiary(Long userId, Long diaryId, DiaryUpdateRequest request) {
        UserEntity savedUser = findUserById(userId);
        DiaryEntity savedDiary = findDiaryById(diaryId);

        savedDiary.diaryUpdate(savedUser, request.getContent());
    }

    /**
     * 다이어리 삭제
     * @since 2024.01.23
     * @return deleteDiary
     * @author 최정윤
     */
    @Override
    public void deleteDiary(Long userId, Long diaryId) {
        UserEntity savedUser = findUserById(userId);
        DiaryEntity savedDiary = findDiaryById(diaryId);

        savedDiary.validateUserIsWriter(savedUser);
        diaryJpaRepository.deleteById(savedDiary.getId());
    }

    private UserEntity findUserById(Long userId) {
        return userRepository.findById(userId)
            .orElseThrow(() -&gt; new ApplicationException(UserErrorCode.NOT_EXIST));
    }

    private DiaryEntity findDiaryById(Long diaryId) {
        return diaryRepository.findById(diaryId)
            .orElseThrow(() -&gt; new ApplicationException(DiaryErrorCode.NO_DIARY));
    }

}</code></pre>
<h3 id="좋아요-api-구현"><a href="https://velog.io/@korea3611/Spring-Boot%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%A2%8B%EC%95%84%EC%9A%94-%EA%B8%B0%EB%8A%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0">좋아요 API 구현</a></h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240124 2차 프로젝트 - Kotlin 친구 리스트 구현]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240124-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Kotlin-%EC%B9%9C%EA%B5%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EA%B5%AC%ED%98%84-SpringBoot-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%BD%94%EB%93%9C-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240124-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Kotlin-%EC%B9%9C%EA%B5%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EA%B5%AC%ED%98%84-SpringBoot-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%BD%94%EB%93%9C-%EC%88%98%EC%A0%95</guid>
            <pubDate>Thu, 25 Jan 2024 00:33:56 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-bash">git fetch origin
git checkout chore/17-edit-calendar-diary-2</code></pre>
<h2 id="안드로이드-에뮬레이터-버전-업그레이드---34ver">안드로이드 에뮬레이터 버전 업그레이드 - 34ver</h2>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/5cc06009-2074-4d48-ac2d-f5c6626ba7b6/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/22c08afe-fd68-487d-84d1-c80258edb2a5/image.png" alt=""></p>
<hr>
<h1 id="다이어리-수정">다이어리 수정</h1>
<h2 id="레이아웃-수정">레이아웃 수정</h2>
<blockquote>
</blockquote>
<ul>
<li>layout_constraintLeft_toLeftOf : 해당 뷰의 왼쪽을 지정된 요소의 왼쪽과 맞춥니다.</li>
<li>layout_constraintLeft_toRightOf : 해당 뷰의 왼쪽을 지정된 요소의 오른쪽과 맞춥니다.</li>
<li>layout_constraintRight_toLeftOf : 해당 뷰의 오른쪽을 지정된 요소의 왼쪽과 맞춥니다.</li>
<li>layout_constraintRight_toRightOf : 해당 뷰의 오른쪽을 지정된 요소의 오른쪽과 맞춥니다.</li>
<li>layout_constraintTop_toTopOf : 해당 뷰의 위쪽을 지정된 요소의 위쪽과 맞춥니다.</li>
<li>layout_constraintTop_toBottomOf : 해당 뷰의 위쪽을 지정된 요소의 아래쪽과 맞춥니다.</li>
<li>layout_constraintBottom_toTopOf : 해당 뷰의 아래쪽을 지정된 요소의 위쪽과 맞춥니다.</li>
<li>layout_constraintBottom_toBottomOf : 해당 뷰의 아랫쪽을 지정된 요소의 아래쪽과 맞춥니다.</li>
<li>layout_constraintStart_toStartOf : 해당 뷰의 시작 위치를 지정된 요소의 시작위치와 맞춥니다.</li>
<li>layout_constraintStart_toEndOf : 해당 뷰의 시작위치를 지정된 요소의 끝 위치와 맞춥니다.</li>
<li>layout_constraintEnd_toStartOf : 해당 뷰의 끝 위치를 지정된 요소의 시작위치와 맞춥니다.</li>
<li>layout_constraintEnd_toEndOf : 해당 뷰의 끝위치를 지정된 요소의 끝 위치와 맞춥니다.</li>
<li>layout_constraintBaseline_toBaselineOf : 해당 뷰와 지정된 요소의 문자열 기준선(Baseline)을 맞춥니다.</li>
<li>*[참고링크]**</li>
<li><a href="https://kadosholy.tistory.com/131">https://kadosholy.tistory.com/131</a></li>
</ul>
<hr>
<h1 id="친구리스트-구현">친구리스트 구현</h1>
<h2 id="친구리스트-조회">친구리스트 조회</h2>
<p><strong>activity_frined.xml</strong></p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;!-- res/layout/activity_main.xml --&gt;

    &lt;androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;&gt;

        &lt;RelativeLayout
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;match_parent&quot;&gt;

            &lt;!-- 제목 --&gt;
            &lt;TextView
                android:id=&quot;@+id/titleTextView&quot;
                android:layout_width=&quot;wrap_content&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_centerHorizontal=&quot;true&quot;
                android:paddingTop=&quot;16dp&quot;
                android:text=&quot;친구&quot;
                android:textSize=&quot;24sp&quot; /&gt;

            &lt;!-- 검색 창 --&gt;
            &lt;EditText
                android:id=&quot;@+id/searchEditText&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_below=&quot;@+id/titleTextView&quot;
                android:layout_marginTop=&quot;8dp&quot;
                android:hint=&quot;검색하세요&quot; /&gt;

            &lt;!-- 친구 목록 리스트 --&gt;
            &lt;ListView
                android:id=&quot;@+id/friendsListView&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_below=&quot;@+id/searchEditText&quot;
                android:layout_marginTop=&quot;8dp&quot;
                android:divider=&quot;@android:color/darker_gray&quot;
                android:dividerHeight=&quot;1dp&quot; /&gt;

        &lt;/RelativeLayout&gt;
    &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;

    &lt;include layout=&quot;@layout/activity_bottom_bar&quot;
        android:id=&quot;@+id/bottom_bar&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_alignParentBottom=&quot;true&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre>
<p><strong>recycle_friend_item.list.xml</strong></p>
<pre><code class="language-xml">&lt;RelativeLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:padding=&quot;16dp&quot;&gt;

    &lt;!-- 프로필 이미지 --&gt;
    &lt;ImageView
        android:id=&quot;@+id/profileImageView&quot;
        android:layout_width=&quot;50dp&quot;
        android:layout_height=&quot;50dp&quot;
        android:layout_centerVertical=&quot;true&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:src=&quot;@drawable/user_profile&quot; /&gt;

    &lt;!-- 친구 이름 --&gt;
    &lt;TextView
        android:id=&quot;@+id/friendNameTextView&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_toEndOf=&quot;@id/profileImageView&quot;
        android:layout_centerVertical=&quot;true&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:textSize=&quot;18sp&quot;
        android:text=&quot;친구 이름&quot; /&gt;

    &lt;!-- 추가 버튼 --&gt;
    &lt;Button
        android:id=&quot;@+id/addButton&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_alignParentEnd=&quot;true&quot;
        android:layout_centerVertical=&quot;true&quot;
        android:text=&quot;Todo&quot;
        android:layout_marginEnd=&quot;8dp&quot;/&gt;
&lt;/RelativeLayout&gt;</code></pre>
<p><strong>FriendActivity.kt</strong></p>
<pre><code class="language-kotlin">package kr.sesac.aoao.android.friend

import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.view.ViewGroup
import android.widget.*

import androidx.appcompat.app.AppCompatActivity
import kr.sesac.aoao.android.R

/**
 * @since 2024.01.24
 * @author 최정윤
 */
public class FriendActivity : AppCompatActivity() {
    private lateinit var searchEditText: EditText
    private lateinit var friendsListView: ListView
    private lateinit var titleTextView: TextView
    private lateinit var adapter: ArrayAdapter&lt;String&gt;

    private val dummyData = listOf(
        &quot;김유빈&quot;, &quot;김은서&quot;, &quot;김은솔&quot;, &quot;엄상은&quot;, &quot;이상민&quot;, &quot;이혜연&quot;, &quot;황수연&quot;, &quot;최정윤&quot;
    )

    /**
     * 친구리스트 구현
     * @since 2024.01.24
     * @author 최정윤
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_friend)

        titleTextView = findViewById(R.id.titleTextView)
        searchEditText = findViewById(R.id.searchEditText)
        friendsListView = findViewById(R.id.friendsListView)

        // 어댑터 초기화 및 더미 데이터 설정
        adapter = object : ArrayAdapter&lt;String&gt;(this, R.layout.recycle_friend_item_list, R.id.friendNameTextView, dummyData) {
            override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
                val itemView = super.getView(position, convertView, parent)

                // 현재 위치에 있는 친구 이름 가져오기
                val currentFriendName = getItem(position)

                // 레이아웃 요소 초기화
                val profileImageView = itemView.findViewById&lt;ImageView&gt;(R.id.profileImageView)
                val friendNameTextView = itemView.findViewById&lt;TextView&gt;(R.id.friendNameTextView)
                val addButton = itemView.findViewById&lt;Button&gt;(R.id.addButton)

                // 데이터 설정 (프로필 이미지 리소스 ID와 클릭 리스너는 적절하게 수정해야 함)
                // 예제에서는 profile1, profile2, ... 와 같이 프로필 이미지 리소스를 사용
//                val profileImageResource = resources.getIdentifier(&quot;profile${position + 1}&quot;, &quot;drawable&quot;, packageName)
                val profileImageResource = resources.getIdentifier(&quot;user_profile&quot;, &quot;drawable&quot;, packageName)
                profileImageView.setImageResource(profileImageResource)
                friendNameTextView.text = currentFriendName

                // 추가 버튼 클릭 리스너 설정 (원하는 동작 추가)
                addButton.setOnClickListener {
                    // 추가 버튼 클릭 시 수행할 동작
                }

                return itemView
            }
        }

        friendsListView.adapter = adapter


        /**
         * 친구 검색기능 구현
         * @since 2024.01.24
         * @author 최정윤
         */
        // 검색 기능 구현
        searchEditText.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) {}
            override fun onTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) {
                adapter.filter.filter(charSequence)
            }

            override fun afterTextChanged(editable: Editable?) {}
        })
    }
}</code></pre>
<p><strong>FriendResponse.kt</strong></p>
<pre><code class="language-kotlin">package kr.sesac.aoao.android.friend.model

class FriendResponse(
    val name: String,
    val profileImageResource: Int
)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240122 2차 프로젝트 - Kotlin 화면 스위칭 구현]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Kotlin-%ED%99%94%EB%A9%B4-%EC%8A%A4%EC%9C%84%EC%B9%AD-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Kotlin-%ED%99%94%EB%A9%B4-%EC%8A%A4%EC%9C%84%EC%B9%AD-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Mon, 22 Jan 2024 08:33:21 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-bash">git fetch origin
git checkout chore/9-edit-calendar-1</code></pre>
<h1 id="화면-스위치-초안-작성"><a href="https://aries574.tistory.com/392#google_vignette">화면 스위치 초안 작성</a></h1>
<p><strong>HomeActivity.kt</strong></p>
<pre><code class="language-kotlin">package kr.sesac.aoao.android

import android.annotation.SuppressLint
import java.io.FileInputStream
import java.io.FileOutputStream

import android.view.View
import android.os.Bundle
import android.widget.Button
import android.widget.CalendarView
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat

/**
 * @since 2024.01.19 ~
 * @author 김유빈, 최정윤
 */
class HomeActivity : AppCompatActivity(){

    var userID: String = &quot;userID&quot;
    lateinit var fname: String
    lateinit var str: String
    lateinit var calendarView: CalendarView
    lateinit var updateBtn: Button
    lateinit var deleteBtn:Button
    lateinit var saveBtn:Button
    lateinit var diaryTextView: TextView
    lateinit var diaryContent:TextView
    lateinit var title:TextView
    lateinit var contextEditText: EditText

    /**
     * 캘린더 구현, 화면 스위칭 구현
     * @since 2024.01.19 ~
     * @author 최정윤
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_calendar)

        // 스위칭 객체 생성
        val statusText: TextView = findViewById(R.id.todo)
        val switchView: SwitchCompat = findViewById(R.id.diary)

        //switch 체크 이벤트
        switchView.setOnCheckedChangeListener { p0, isChecked -&gt;
            if (isChecked) {
                statusText.text = &quot;다이어리&quot;
                showDiaryFunctionality()
            } else {
                statusText.text = &quot;투두&quot;
                showTodoFunctionality()
            }
        }

        // UI값 생성
        calendarView=findViewById(R.id.calendarView)
        diaryTextView=findViewById(R.id.diaryTextView)
        saveBtn=findViewById(R.id.saveBtn)
        deleteBtn=findViewById(R.id.deleteBtn)
        updateBtn=findViewById(R.id.updateBtn)
        diaryContent=findViewById(R.id.diaryContent)
        title=findViewById(R.id.title)
        contextEditText=findViewById(R.id.contextEditText)

        title.text = &quot;달력 일기장&quot;

        calendarView.setOnDateChangeListener { view, year, month, dayOfMonth -&gt;
            diaryTextView.visibility = View.VISIBLE
            saveBtn.visibility = View.VISIBLE
            contextEditText.visibility = View.VISIBLE
            diaryContent.visibility = View.INVISIBLE
            updateBtn.visibility = View.INVISIBLE
            deleteBtn.visibility = View.INVISIBLE
            diaryTextView.text = String.format(&quot;%d / %d / %d&quot;, year, month + 1, dayOfMonth)
            contextEditText.setText(&quot;&quot;)
            checkDay(year, month, dayOfMonth, userID)
        }

        saveBtn.setOnClickListener {
            saveDiary(fname)
            contextEditText.visibility = View.INVISIBLE
            saveBtn.visibility = View.INVISIBLE
            updateBtn.visibility = View.VISIBLE
            deleteBtn.visibility = View.VISIBLE
            str = contextEditText.text.toString()
            diaryContent.text = str
            diaryContent.visibility = View.VISIBLE
        }
    }

//    스위칭 다이어리 보이게 하기
    private fun showDiaryFunctionality() {
        // 다이어리 관련 기능 보이게 설정
        diaryTextView.visibility = View.VISIBLE
        saveBtn.visibility = View.VISIBLE
        contextEditText.visibility = View.VISIBLE
        diaryContent.visibility = View.INVISIBLE
        updateBtn.visibility = View.INVISIBLE
        deleteBtn.visibility = View.INVISIBLE

        // 투두 관련 기능 숨기기
        // TODO: 투두 관련 기능에 대한 가시성 조절 코드 추가
    }

//    스위칭 투두 보이게 하기
    private fun showTodoFunctionality() {
        // 투두 관련 기능 보이게 설정
        // TODO: 투두 관련 기능에 대한 가시성 조절 코드 추가

        // 다이어리 관련 기능 숨기기
        diaryTextView.visibility = View.INVISIBLE
        saveBtn.visibility = View.INVISIBLE
        contextEditText.visibility = View.INVISIBLE
        diaryContent.visibility = View.INVISIBLE
        updateBtn.visibility = View.INVISIBLE
        deleteBtn.visibility = View.INVISIBLE
    }

    // 달력 내용 조회, 수정
    fun checkDay(cYear: Int, cMonth: Int, cDay: Int, userID: String) {
        //저장할 파일 이름설정
        fname = &quot;&quot; + userID + cYear + &quot;-&quot; + (cMonth + 1) + &quot;&quot; + &quot;-&quot; + cDay + &quot;.txt&quot;

        var fileInputStream: FileInputStream
        try {
            fileInputStream = openFileInput(fname)
            val fileData = ByteArray(fileInputStream.available())
            fileInputStream.read(fileData)
            fileInputStream.close()
            str = String(fileData)
            contextEditText.visibility = View.INVISIBLE
            diaryContent.visibility = View.VISIBLE
            diaryContent.text = str
            saveBtn.visibility = View.INVISIBLE
            updateBtn.visibility = View.VISIBLE
            deleteBtn.visibility = View.VISIBLE
            updateBtn.setOnClickListener {
                contextEditText.visibility = View.VISIBLE
                diaryContent.visibility = View.INVISIBLE
                contextEditText.setText(str)
                saveBtn.visibility = View.VISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                diaryContent.text = contextEditText.text
            }
            deleteBtn.setOnClickListener {
                diaryContent.visibility = View.INVISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                contextEditText.setText(&quot;&quot;)
                contextEditText.visibility = View.VISIBLE
                saveBtn.visibility = View.VISIBLE
                removeDiary(fname)
            }
            if (diaryContent.text == null) {
                diaryContent.visibility = View.INVISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                diaryTextView.visibility = View.VISIBLE
                saveBtn.visibility = View.VISIBLE
                contextEditText.visibility = View.VISIBLE
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }


    // 달력 내용 제거
    @SuppressLint(&quot;WrongConstant&quot;)
    fun removeDiary(readDay: String?) {
        var fileOutputStream: FileOutputStream
        try {
            fileOutputStream = openFileOutput(readDay, MODE_NO_LOCALIZED_COLLATORS)
            val content = &quot;&quot;
            fileOutputStream.write(content.toByteArray())
            fileOutputStream.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }


    // 달력 내용 추가
    @SuppressLint(&quot;WrongConstant&quot;)
    fun saveDiary(readDay: String?) {
        var fileOutputStream: FileOutputStream
        try {
            fileOutputStream = openFileOutput(readDay, MODE_NO_LOCALIZED_COLLATORS)
            val content = contextEditText.text.toString()
            fileOutputStream.write(content.toByteArray())
            fileOutputStream.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }
}</code></pre>
<p><strong>activity_calendar.xml</strong></p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:orientation=&quot;vertical&quot; &gt;

    &lt;CalendarView
        android:id=&quot;@+id/calendarView&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginTop=&quot;4dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintHorizontal_bias=&quot;0.488&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/title&quot; /&gt;

    &lt;!--  투두/다이어리 스위치  --&gt;
    &lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
        xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
        xmlns:tools=&quot;http://schemas.android.com/tools&quot;
        android:id=&quot;@+id/mainLayout&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:orientation=&quot;vertical&quot;
        tools:context=&quot;.HomeActivity&quot;&gt;

        &lt;TextView
            android:id=&quot;@+id/todo&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_above=&quot;@+id/diary&quot;
            android:layout_centerHorizontal=&quot;true&quot;
            android:text=&quot;투두&quot;
            android:textSize=&quot;30sp&quot;
            android:textStyle=&quot;bold&quot; /&gt;

        &lt;androidx.appcompat.widget.SwitchCompat
            android:id=&quot;@+id/diary&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_centerInParent=&quot;true&quot;
            android:layout_marginTop=&quot;20dp&quot; /&gt;
    &lt;/LinearLayout&gt;

    &lt;TextView
        android:id=&quot;@+id/diaryTextView&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:gravity= &quot;center&quot;
        android:layout_marginTop=&quot;16dp&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/calendarView&quot; app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginEnd=&quot;8dp&quot; android:layout_marginRight=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot; android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot; android:textAppearance=&quot;@style/TextAppearance.AppCompat.Large&quot;/&gt;

    &lt;EditText
        android:id=&quot;@+id/contextEditText&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;116dp&quot;
        android:inputType=&quot;textMultiLine&quot;
        android:ems=&quot;10&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/diaryTextView&quot; android:layout_marginTop=&quot;16dp&quot;
        android:hint=&quot;내용을 입력하세요.&quot; android:layout_marginEnd=&quot;8dp&quot; app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginRight=&quot;8dp&quot; android:layout_marginStart=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot; android:layout_marginLeft=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;TextView
        android:id=&quot;@+id/diaryContent&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;0dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintHorizontal_bias=&quot;0.0&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintTop_toTopOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintVertical_bias=&quot;0.0&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/title&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginTop=&quot;8dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:gravity=&quot;center&quot;
        android:text=&quot;달력일기장&quot;
        android:textAppearance=&quot;@style/TextAppearance.AppCompat.Large&quot;
        android:textColor=&quot;#9E28B3&quot;
        android:textSize=&quot;24sp&quot;
        android:textStyle=&quot;bold&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;Button
        android:text=&quot;저장&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_marginBottom=&quot;20dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/saveBtn&quot;
        android:layout_marginTop=&quot;16dp&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;Button
        android:text=&quot;수정&quot;
        android:layout_width=&quot;180dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/updateBtn&quot;
        app:layout_constraintBaseline_toBaselineOf=&quot;@+id/saveBtn&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        app:layout_constraintEnd_toStartOf=&quot;@+id/deleteBtn&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;Button
        android:text=&quot;삭제&quot;
        android:layout_width=&quot;176dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/deleteBtn&quot;
        app:layout_constraintBaseline_toBaselineOf=&quot;@+id/updateBtn&quot;
        app:layout_constraintEnd_toEndOf=&quot;@+id/saveBtn&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;&gt;
    &lt;/Button&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre><p><img src="https://velog.velcdn.com/images/jeong_yooony/post/0052a79d-4d30-4852-baa3-4616f41f5bba/image.png" alt=""></p>
<h2 id="구현현황">구현현황</h2>
<ul>
<li>스위치 구현</li>
</ul>
<h2 id="이슈상황">이슈상황</h2>
<ul>
<li>날짜를 누르고 스위치 작동시키면 투두/다이어리 이동함</li>
<li>투두 스위칭 상태에서 날짜 변경하면 갑자기 다이어리 보임<ul>
<li>날짜 선택 디폴트 상태가 다이어리라서 스위칭 이벤트 적용이 안되는 것 같다.</li>
</ul>
</li>
</ul>
<hr>
<h1 id="화면-스위치-구현-완료">화면 스위치 구현 완료</h1>
<p><strong>activity_calendar.xml</strong></p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:orientation=&quot;vertical&quot; &gt;

    &lt;CalendarView
        android:id=&quot;@+id/calendarView&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginTop=&quot;4dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintHorizontal_bias=&quot;0.488&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/title&quot; /&gt;

    &lt;!--  투두/다이어리 스위치  --&gt;
    &lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
        xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
        xmlns:tools=&quot;http://schemas.android.com/tools&quot;
        android:id=&quot;@+id/mainLayout&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:orientation=&quot;vertical&quot;
        tools:context=&quot;.HomeActivity&quot;&gt;

        &lt;TextView
            android:id=&quot;@+id/todo&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_above=&quot;@+id/diary&quot;
            android:layout_centerHorizontal=&quot;true&quot;
            android:text=&quot;투두&quot;
            android:textSize=&quot;30sp&quot;
            android:textStyle=&quot;bold&quot; /&gt;

        &lt;androidx.appcompat.widget.SwitchCompat
            android:id=&quot;@+id/diary&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_centerInParent=&quot;true&quot;
            android:layout_marginTop=&quot;20dp&quot; /&gt;
    &lt;/LinearLayout&gt;

    &lt;TextView
        android:id=&quot;@+id/diaryTextView&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:gravity= &quot;center&quot;
        android:layout_marginTop=&quot;16dp&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/calendarView&quot; app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginEnd=&quot;8dp&quot; android:layout_marginRight=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot; android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot; android:textAppearance=&quot;@style/TextAppearance.AppCompat.Large&quot;/&gt;

    &lt;EditText
        android:id=&quot;@+id/contextEditText&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;116dp&quot;
        android:inputType=&quot;textMultiLine&quot;
        android:ems=&quot;10&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/diaryTextView&quot; android:layout_marginTop=&quot;16dp&quot;
        android:hint=&quot;내용을 입력하세요.&quot; android:layout_marginEnd=&quot;8dp&quot; app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginRight=&quot;8dp&quot; android:layout_marginStart=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot; android:layout_marginLeft=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;TextView
        android:id=&quot;@+id/diaryContent&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;0dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintHorizontal_bias=&quot;0.0&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintTop_toTopOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintVertical_bias=&quot;0.0&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/title&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginTop=&quot;8dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:gravity=&quot;center&quot;
        android:text=&quot;달력일기장&quot;
        android:textAppearance=&quot;@style/TextAppearance.AppCompat.Large&quot;
        android:textColor=&quot;#9E28B3&quot;
        android:textSize=&quot;24sp&quot;
        android:textStyle=&quot;bold&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;Button
        android:text=&quot;저장&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_marginBottom=&quot;20dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/saveBtn&quot;
        android:layout_marginTop=&quot;16dp&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;Button
        android:text=&quot;수정&quot;
        android:layout_width=&quot;180dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/updateBtn&quot;
        app:layout_constraintBaseline_toBaselineOf=&quot;@+id/saveBtn&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        app:layout_constraintEnd_toStartOf=&quot;@+id/deleteBtn&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;Button
        android:text=&quot;삭제&quot;
        android:layout_width=&quot;176dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/deleteBtn&quot;
        app:layout_constraintBaseline_toBaselineOf=&quot;@+id/updateBtn&quot;
        app:layout_constraintEnd_toEndOf=&quot;@+id/saveBtn&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;&gt;
    &lt;/Button&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre>
<p><strong>HomeActivity.kt</strong></p>
<pre><code class="language-kotlin">package kr.sesac.aoao.android

import android.annotation.SuppressLint
import java.io.FileInputStream
import java.io.FileOutputStream

import android.view.View
import android.os.Bundle
import android.widget.Button
import android.widget.CalendarView
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat

/**
 * @since 2024.01.19 ~
 * @author 김유빈, 최정윤
 */
class HomeActivity : AppCompatActivity(){

    var userID: String = &quot;userID&quot;
    lateinit var fname: String
    lateinit var str: String
    lateinit var calendarView: CalendarView
    lateinit var updateBtn: Button
    lateinit var deleteBtn:Button
    lateinit var saveBtn:Button
    lateinit var diaryTextView: TextView
    lateinit var diaryContent:TextView
    lateinit var title:TextView
    lateinit var contextEditText: EditText

    /**
     * 캘린더 구현, 화면 스위칭 구현
     * @since 2024.01.19 ~
     * @author 최정윤
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_calendar)

        // 스위칭 객체 생성
        val statusText: TextView = findViewById(R.id.todo)
        val switchView: SwitchCompat = findViewById(R.id.diary)

        //switch 체크 이벤트
        switchView.setOnCheckedChangeListener { p0, isChecked -&gt;
            if (isChecked) {
                statusText.text = &quot;다이어리&quot;
                showDiaryFunctionality()
                isDiarySwitched = true
            } else {
                statusText.text = &quot;투두&quot;
                showTodoFunctionality()
                isDiarySwitched = false
            }

            // checkDay 함수 호출로 인해 화면 업데이트
//            checkDay(year, month, dayOfMonth, userID)
        }

        // UI값 생성
        calendarView=findViewById(R.id.calendarView)
        diaryTextView=findViewById(R.id.diaryTextView)
        saveBtn=findViewById(R.id.saveBtn)
        deleteBtn=findViewById(R.id.deleteBtn)
        updateBtn=findViewById(R.id.updateBtn)
        diaryContent=findViewById(R.id.diaryContent)
        title=findViewById(R.id.title)
        contextEditText=findViewById(R.id.contextEditText)

        title.text = &quot;달력 일기장&quot;

        calendarView.setOnDateChangeListener { view, year, month, dayOfMonth -&gt;
            diaryTextView.visibility = View.VISIBLE
            saveBtn.visibility = View.VISIBLE
            contextEditText.visibility = View.VISIBLE
            diaryContent.visibility = View.INVISIBLE
            updateBtn.visibility = View.INVISIBLE
            deleteBtn.visibility = View.INVISIBLE
            diaryTextView.text = String.format(&quot;%d / %d / %d&quot;, year, month + 1, dayOfMonth)
            contextEditText.setText(&quot;&quot;)
            checkDay(year, month, dayOfMonth, userID)
        }

        saveBtn.setOnClickListener {
            saveDiary(fname)
            contextEditText.visibility = View.INVISIBLE
            saveBtn.visibility = View.INVISIBLE
            updateBtn.visibility = View.VISIBLE
            deleteBtn.visibility = View.VISIBLE
            str = contextEditText.text.toString()
            diaryContent.text = str
            diaryContent.visibility = View.VISIBLE
        }
    }

    // 스위칭 상태를 나타내는 변수 추가
    private var isDiarySwitched = false // 기본적으로 투두 상태로 시작

    // 달력 내용 조회, 수정
    fun checkDay(cYear: Int, cMonth: Int, cDay: Int, userID: String) {

        // 가시성 체크를 try-catch 블록 외부로 이동
        var diaryVisibility = View.INVISIBLE
        var todoVisibility = View.INVISIBLE

        if (isDiarySwitched) {
            // 다이어리 스위칭 상태일 때의 처리
            showDiaryFunctionality()
            diaryVisibility = View.VISIBLE
        } else {
            // 투두 스위칭 상태일 때의 처리
            showTodoFunctionality()
            todoVisibility = View.VISIBLE
        }

        //저장할 파일 이름설정
        fname = &quot;&quot; + userID + cYear + &quot;-&quot; + (cMonth + 1) + &quot;&quot; + &quot;-&quot; + cDay + &quot;.txt&quot;

        var fileInputStream: FileInputStream
        try {
            fileInputStream = openFileInput(fname)
            val fileData = ByteArray(fileInputStream.available())
            fileInputStream.read(fileData)
            fileInputStream.close()
            str = String(fileData)
            contextEditText.visibility = View.INVISIBLE
            diaryContent.visibility = diaryVisibility
            diaryContent.text = str
            saveBtn.visibility = View.INVISIBLE
            updateBtn.visibility = diaryVisibility
            deleteBtn.visibility = diaryVisibility
            updateBtn.setOnClickListener {
                contextEditText.visibility = View.VISIBLE
                diaryContent.visibility = View.INVISIBLE
                contextEditText.setText(str)
                saveBtn.visibility = View.VISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                diaryContent.text = contextEditText.text
            }
            deleteBtn.setOnClickListener {
                diaryContent.visibility = View.INVISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                contextEditText.setText(&quot;&quot;)
                contextEditText.visibility = View.VISIBLE
                saveBtn.visibility = View.VISIBLE
                removeDiary(fname)
            }
            if (diaryContent.text == null) {
                diaryContent.visibility = View.INVISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                diaryTextView.visibility = View.VISIBLE
                saveBtn.visibility = View.VISIBLE
                contextEditText.visibility = View.VISIBLE
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    // 달력 내용 제거
    @SuppressLint(&quot;WrongConstant&quot;)
    fun removeDiary(readDay: String?) {
        var fileOutputStream: FileOutputStream
        try {
            fileOutputStream = openFileOutput(readDay, MODE_NO_LOCALIZED_COLLATORS)
            val content = &quot;&quot;
            fileOutputStream.write(content.toByteArray())
            fileOutputStream.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    // 달력 내용 추가
    @SuppressLint(&quot;WrongConstant&quot;)
    fun saveDiary(readDay: String?) {
        var fileOutputStream: FileOutputStream
        try {
            fileOutputStream = openFileOutput(readDay, MODE_NO_LOCALIZED_COLLATORS)
            val content = contextEditText.text.toString()
            fileOutputStream.write(content.toByteArray())
            fileOutputStream.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    //    스위칭 다이어리 보이게 하기
    private fun showDiaryFunctionality() {
        // 다이어리 관련 기능 보이게 설정
        diaryTextView.visibility = View.VISIBLE
        saveBtn.visibility = View.VISIBLE
        contextEditText.visibility = View.VISIBLE
        diaryContent.visibility = View.INVISIBLE
        updateBtn.visibility = View.INVISIBLE
        deleteBtn.visibility = View.INVISIBLE

        // 투두 관련 기능 숨기기
        // TODO: 투두 관련 기능에 대한 가시성 조절 코드 추가
    }

    //    스위칭 투두 보이게 하기
    private fun showTodoFunctionality() {
        // 투두 관련 기능 보이게 설정
        // TODO: 투두 관련 기능에 대한 가시성 조절 코드 추가

        // 다이어리 관련 기능 숨기기
        diaryTextView.visibility = View.INVISIBLE
        saveBtn.visibility = View.INVISIBLE
        contextEditText.visibility = View.INVISIBLE
        diaryContent.visibility = View.INVISIBLE
        updateBtn.visibility = View.INVISIBLE
        deleteBtn.visibility = View.INVISIBLE
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/c9d9ec2f-44fc-42a7-a177-d9e40eea6c2e/image.png" alt=""></p>
<h2 id="이슈-상황">이슈 상황</h2>
<ul>
<li>초안에 대한 이슈 모두 해결</li>
</ul>
<hr>
<h1 id="캘린더-디자인">캘린더 디자인</h1>
<p><strong>activity_calendar.xml</strong></p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:orientation=&quot;vertical&quot; &gt;
    &lt;ScrollView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;&gt;
        &lt;LinearLayout
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:orientation=&quot;vertical&quot;
        android:gravity=&quot;center_horizontal&quot;&gt;
        &lt;CalendarView
            android:id=&quot;@+id/calendarView&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            app:layout_constraintEnd_toEndOf=&quot;parent&quot;
            app:layout_constraintStart_toStartOf=&quot;parent&quot;
            app:layout_constraintTop_toBottomOf=&quot;parent&quot;
            app:layout_constraintHorizontal_bias=&quot;0.488&quot;
            android:background=&quot;#A4C66C&quot;
            android:foreground=&quot;?android:attr/selectableItemBackground&quot;
            android:outlineProvider=&quot;background&quot;
            android:padding=&quot;0dp&quot;
            android:dateTextAppearance=&quot;@style/CalendarDateText&quot;
            android:gravity=&quot;center&quot;
            /&gt; &lt;!--        app:layout_constraintTop_toBottomOf=&quot;@+id/title&quot; --&gt;
            &lt;!--  투두/다이어리 스위치  --&gt;
            &lt;LinearLayout
                android:id=&quot;@+id/mainLayout&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;match_parent&quot;
                android:orientation=&quot;horizontal&quot;
                android:gravity=&quot;center_vertical&quot;
                android:paddingLeft=&quot;15dp&quot;
                tools:context=&quot;.HomeActivity&quot;&gt;

                &lt;androidx.appcompat.widget.SwitchCompat
                    android:id=&quot;@+id/diary&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:layout_centerInParent=&quot;true&quot;
                    app:thumbTint=&quot;#FFFFFF&quot;
                    app:trackTint=&quot;#A4C66C&quot;/&gt;

                &lt;TextView
                    android:id=&quot;@+id/todo&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:layout_above=&quot;@+id/diary&quot;
                    android:layout_centerHorizontal=&quot;true&quot;
                    android:text=&quot;투두&quot;
                    android:textSize=&quot;20sp&quot;
                    android:textStyle=&quot;bold&quot; /&gt;


            &lt;/LinearLayout&gt;

            &lt;LinearLayout
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_marginLeft=&quot;10dp&quot;
                android:layout_marginRight=&quot;10dp&quot;
                android:orientation=&quot;vertical&quot;&gt;
                &lt;TextView
                    android:id=&quot;@+id/diaryTextView&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:layout_marginStart=&quot;8dp&quot;
                    android:layout_marginLeft=&quot;8dp&quot;
                    android:layout_marginTop=&quot;16dp&quot;
                    android:layout_marginEnd=&quot;8dp&quot;
                    android:layout_marginRight=&quot;8dp&quot;
                    android:gravity=&quot;left&quot;
                    android:textAppearance=&quot;@style/TextAppearance.AppCompat.Small&quot;
                    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                    app:layout_constraintStart_toStartOf=&quot;parent&quot;
                    app:layout_constraintTop_toBottomOf=&quot;@+id/calendarView&quot; /&gt;

                &lt;EditText
                    android:id=&quot;@+id/contextEditText&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:inputType=&quot;textMultiLine&quot;
                    android:ems=&quot;10&quot;
                    app:layout_constraintTop_toBottomOf=&quot;@+id/diaryTextView&quot;
                    android:layout_marginTop=&quot;16dp&quot;
                    android:hint=&quot;내용을 입력하세요.&quot;
                    android:layout_marginEnd=&quot;8dp&quot;
                    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                    android:layout_marginRight=&quot;8dp&quot;
                    android:layout_marginStart=&quot;8dp&quot;
                    app:layout_constraintStart_toStartOf=&quot;parent&quot;
                    android:layout_marginLeft=&quot;8dp&quot;
                    android:visibility=&quot;invisible&quot;/&gt;

                &lt;TextView
                    android:id=&quot;@+id/diaryContent&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:layout_marginStart=&quot;8dp&quot;
                    android:layout_marginEnd=&quot;8dp&quot;
                    android:visibility=&quot;invisible&quot;
                    android:elevation=&quot;4dp&quot;
                    app:layout_constraintBottom_toBottomOf=&quot;@+id/contextEditText&quot;
                    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                    app:layout_constraintHorizontal_bias=&quot;0.0&quot;
                    app:layout_constraintStart_toStartOf=&quot;@+id/contextEditText&quot;
                    app:layout_constraintTop_toTopOf=&quot;@+id/contextEditText&quot;
                    app:layout_constraintVertical_bias=&quot;0.0&quot; /&gt;

            &lt;!--    &lt;TextView--&gt;
            &lt;!--        android:id=&quot;@+id/title&quot;--&gt;
            &lt;!--        android:layout_width=&quot;0dp&quot;--&gt;
            &lt;!--        android:layout_height=&quot;wrap_content&quot;--&gt;
            &lt;!--        android:layout_marginStart=&quot;8dp&quot;--&gt;
            &lt;!--        android:layout_marginTop=&quot;8dp&quot;--&gt;
            &lt;!--        android:layout_marginEnd=&quot;8dp&quot;--&gt;
            &lt;!--        android:gravity=&quot;center&quot;--&gt;
            &lt;!--        android:text=&quot;달력일기장&quot;--&gt;
            &lt;!--        android:textAppearance=&quot;@style/TextAppearance.AppCompat.Large&quot;--&gt;
            &lt;!--        android:textColor=&quot;#9E28B3&quot;--&gt;
            &lt;!--        android:textSize=&quot;24sp&quot;--&gt;
            &lt;!--        android:textStyle=&quot;bold&quot;--&gt;
            &lt;!--        app:layout_constraintEnd_toEndOf=&quot;parent&quot;--&gt;
            &lt;!--        app:layout_constraintStart_toStartOf=&quot;parent&quot;--&gt;
            &lt;!--        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;--&gt;
                &lt;Button
                    android:text=&quot;저장&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_marginBottom=&quot;20dp&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:id=&quot;@+id/saveBtn&quot;
                    android:layout_marginTop=&quot;16dp&quot;
                    app:layout_constraintTop_toBottomOf=&quot;@+id/contextEditText&quot;
                    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                    android:layout_marginEnd=&quot;8dp&quot;
                    android:layout_marginRight=&quot;8dp&quot;
                    app:layout_constraintStart_toStartOf=&quot;parent&quot;
                    android:layout_marginLeft=&quot;8dp&quot;
                    android:layout_marginStart=&quot;8dp&quot;
                    android:visibility=&quot;visible&quot;
                    /&gt;
                &lt;!--                    android:background=&quot;#A4C66C&quot;--&gt;
                &lt;!--                    android:dateTextAppearance=&quot;@style/SaveBtn&quot;--&gt;
                &lt;LinearLayout
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:layout_marginBottom=&quot;20dp&quot;&gt;
                    &lt;Button
                        android:text=&quot;수정&quot;
                        android:layout_width=&quot;180dp&quot;
                        android:layout_height=&quot;wrap_content&quot;
                        android:id=&quot;@+id/updateBtn&quot;

                        app:layout_constraintStart_toStartOf=&quot;parent&quot;
                        android:layout_marginLeft=&quot;8dp&quot;
                        android:layout_marginStart=&quot;8dp&quot;
                        app:layout_constraintEnd_toStartOf=&quot;@+id/deleteBtn&quot;
                        android:layout_marginEnd=&quot;8dp&quot;
                        android:layout_marginRight=&quot;8dp&quot;
                        android:visibility=&quot;invisible&quot;
                        android:background=&quot;#A4C66C&quot;/&gt;
        &lt;!--                app:layout_constraintBaseline_toBaselineOf=&quot;@+id/saveBtn&quot;--&gt;

                    &lt;Button
                        android:text=&quot;삭제&quot;
                        android:layout_width=&quot;176dp&quot;
                        android:layout_height=&quot;wrap_content&quot;
                        android:id=&quot;@+id/deleteBtn&quot;
                        app:layout_constraintBaseline_toBaselineOf=&quot;@+id/updateBtn&quot;
                        android:layout_marginEnd=&quot;8dp&quot;
                        android:layout_marginRight=&quot;8dp&quot;
                        android:visibility=&quot;invisible&quot;
                        android:background=&quot;#A4C66C&quot;/&gt;
        &lt;!--                app:layout_constraintEnd_toEndOf=&quot;@+id/saveBtn&quot;--&gt;
                &lt;/LinearLayout&gt;
            &lt;/LinearLayout&gt;
        &lt;/LinearLayout&gt;
    &lt;/ScrollView&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre>
<p><strong>styles.xml</strong></p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;!--
* @since 2024.01.22
* @author 김유빈, 최정윤
--&gt;
&lt;resources&gt;
    &lt;style name=&quot;CalendarDateText&quot;&gt;
        &lt;item name=&quot;android:textColor&quot;&gt;#FFFFFF&lt;/item&gt;
    &lt;/style&gt;
    &lt;style name=&quot;SaveBtn&quot;&gt;
        &lt;item name=&quot;android:background&quot;&gt;#A4C66C&lt;/item&gt;
    &lt;/style&gt;
&lt;/resources&gt;</code></pre>
<p><strong>HomeActivity.kt</strong></p>
<pre><code class="language-kotlin">package kr.sesac.aoao.android

import android.annotation.SuppressLint
import java.io.FileInputStream
import java.io.FileOutputStream

import android.view.View
import android.os.Bundle
import android.widget.Button
import android.widget.CalendarView
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat

/**
 * @since 2024.01.19 ~
 * @author 김유빈, 최정윤
 */
class HomeActivity : AppCompatActivity(){

    var userID: String = &quot;userID&quot;
    lateinit var fname: String
    lateinit var str: String
    lateinit var calendarView: CalendarView
    lateinit var updateBtn: Button
    lateinit var deleteBtn:Button
    lateinit var saveBtn:Button
    lateinit var diaryTextView: TextView
    lateinit var diaryContent:TextView
    lateinit var title:TextView
    lateinit var contextEditText: EditText

    /**
     * 캘린더 구현
     * @since 2024.01.19
     * @author 최정윤
     *
     * 화면 스위칭 구현
     * @since 2024.01.22
     * @author 최정윤
     *
     * 캘린더 디자인 변경
     * @since 2024.01.22
     * @author 최정윤
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_calendar)

        // 스위칭 객체 생성
        val statusText: TextView = findViewById(R.id.todo)
        val switchView: SwitchCompat = findViewById(R.id.diary)

        //switch 체크 이벤트
        switchView.setOnCheckedChangeListener { p0, isChecked -&gt;
            if (isChecked) {
                statusText.text = &quot;다이어리&quot;
                showDiaryFunctionality()
                isDiarySwitched = true
            } else {
                statusText.text = &quot;투두&quot;
                showTodoFunctionality()
                isDiarySwitched = false
            }

            // checkDay 함수 호출로 인해 화면 업데이트
//            checkDay(year, month, dayOfMonth, userID)
        }

        // UI값 생성
        calendarView=findViewById(R.id.calendarView)
        diaryTextView=findViewById(R.id.diaryTextView)
        saveBtn=findViewById(R.id.saveBtn)
        deleteBtn=findViewById(R.id.deleteBtn)
        updateBtn=findViewById(R.id.updateBtn)
        diaryContent=findViewById(R.id.diaryContent)
//        title=findViewById(R.id.title)
        contextEditText=findViewById(R.id.contextEditText)

//        title.text = &quot;달력 일기장&quot;

        calendarView.setOnDateChangeListener { view, year, month, dayOfMonth -&gt;
            diaryTextView.visibility = View.VISIBLE
            saveBtn.visibility = View.VISIBLE
            contextEditText.visibility = View.VISIBLE
            diaryContent.visibility = View.INVISIBLE
            updateBtn.visibility = View.INVISIBLE
            deleteBtn.visibility = View.INVISIBLE
            diaryTextView.text = String.format(&quot;%d / %d / %d&quot;, year, month + 1, dayOfMonth)
            contextEditText.setText(&quot;&quot;)
            checkDay(year, month, dayOfMonth, userID)
        }

        saveBtn.setOnClickListener {
            saveDiary(fname)
            contextEditText.visibility = View.INVISIBLE
            saveBtn.visibility = View.INVISIBLE
            updateBtn.visibility = View.VISIBLE
            deleteBtn.visibility = View.VISIBLE
            str = contextEditText.text.toString()
            diaryContent.text = str
            diaryContent.visibility = View.VISIBLE
        }
    }

    // 스위칭 상태를 나타내는 변수 추가
    private var isDiarySwitched = false // 기본적으로 투두 상태로 시작

    // 달력 내용 조회, 수정
    fun checkDay(cYear: Int, cMonth: Int, cDay: Int, userID: String) {

        // 가시성 체크를 try-catch 블록 외부로 이동
        var diaryVisibility = View.INVISIBLE
        var todoVisibility = View.INVISIBLE

        if (isDiarySwitched) {
            // 다이어리 스위칭 상태일 때의 처리
            showDiaryFunctionality()
            diaryVisibility = View.VISIBLE
        } else {
            // 투두 스위칭 상태일 때의 처리
            showTodoFunctionality()
            todoVisibility = View.VISIBLE
        }

        //저장할 파일 이름설정
        fname = &quot;&quot; + userID + cYear + &quot;-&quot; + (cMonth + 1) + &quot;&quot; + &quot;-&quot; + cDay + &quot;.txt&quot;

        var fileInputStream: FileInputStream
        try {
            fileInputStream = openFileInput(fname)
            val fileData = ByteArray(fileInputStream.available())
            fileInputStream.read(fileData)
            fileInputStream.close()
            str = String(fileData)
            contextEditText.visibility = View.INVISIBLE
            diaryContent.visibility = diaryVisibility
            diaryContent.text = str
            saveBtn.visibility = View.INVISIBLE
            updateBtn.visibility = diaryVisibility
            deleteBtn.visibility = diaryVisibility
            updateBtn.setOnClickListener {
                contextEditText.visibility = View.VISIBLE
                diaryContent.visibility = View.INVISIBLE
                contextEditText.setText(str)
                saveBtn.visibility = View.VISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                diaryContent.text = contextEditText.text
            }
            deleteBtn.setOnClickListener {
                diaryContent.visibility = View.INVISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                contextEditText.setText(&quot;&quot;)
                contextEditText.visibility = View.VISIBLE
                saveBtn.visibility = View.VISIBLE
                removeDiary(fname)
            }
            if (diaryContent.text == null) {
                diaryContent.visibility = View.INVISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                diaryTextView.visibility = View.VISIBLE
                saveBtn.visibility = View.VISIBLE
                contextEditText.visibility = View.VISIBLE
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    // 달력 내용 제거
    @SuppressLint(&quot;WrongConstant&quot;)
    fun removeDiary(readDay: String?) {
        var fileOutputStream: FileOutputStream
        try {
            fileOutputStream = openFileOutput(readDay, MODE_NO_LOCALIZED_COLLATORS)
            val content = &quot;&quot;
            fileOutputStream.write(content.toByteArray())
            fileOutputStream.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    // 달력 내용 추가
    @SuppressLint(&quot;WrongConstant&quot;)
    fun saveDiary(readDay: String?) {
        var fileOutputStream: FileOutputStream
        try {
            fileOutputStream = openFileOutput(readDay, MODE_NO_LOCALIZED_COLLATORS)
            val content = contextEditText.text.toString()
            fileOutputStream.write(content.toByteArray())
            fileOutputStream.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    //    스위칭 다이어리 보이게 하기
    private fun showDiaryFunctionality() {
        // 다이어리 관련 기능 보이게 설정
        diaryTextView.visibility = View.VISIBLE
        saveBtn.visibility = View.VISIBLE
        contextEditText.visibility = View.VISIBLE
        diaryContent.visibility = View.INVISIBLE
        updateBtn.visibility = View.INVISIBLE
        deleteBtn.visibility = View.INVISIBLE

        // 투두 관련 기능 숨기기
        // TODO: 투두 관련 기능에 대한 가시성 조절 코드 추가
    }

    //    스위칭 투두 보이게 하기
    private fun showTodoFunctionality() {
        // 투두 관련 기능 보이게 설정
        // TODO: 투두 관련 기능에 대한 가시성 조절 코드 추가

        // 다이어리 관련 기능 숨기기
        diaryTextView.visibility = View.INVISIBLE
        saveBtn.visibility = View.INVISIBLE
        contextEditText.visibility = View.INVISIBLE
        diaryContent.visibility = View.INVISIBLE
        updateBtn.visibility = View.INVISIBLE
        deleteBtn.visibility = View.INVISIBLE
    }

}</code></pre>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/9855c665-e740-4bbf-b625-c1f8bdb549f8/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/19b69cb8-429e-4b6d-a044-90bf2fed74dc/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/3cbb07c3-9d38-4c18-b994-25aec51b4ef6/image.png" alt=""></p>
<p><strong>[참고링크]</strong></p>
<ul>
<li><a href="https://velog.io/@taeyang/Kotlin-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83">코틀린 화면 레이아웃</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[와일트루] 1월 2-3주차 : 0108-0121]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%99%80%EC%9D%BC%ED%8A%B8%EB%A3%A8-1%EC%9B%94-2%EC%A3%BC%EC%B0%A8-0108-0114</link>
            <guid>https://velog.io/@jeong_yooony/%EC%99%80%EC%9D%BC%ED%8A%B8%EB%A3%A8-1%EC%9B%94-2%EC%A3%BC%EC%B0%A8-0108-0114</guid>
            <pubDate>Sun, 21 Jan 2024 11:28:35 GMT</pubDate>
            <description><![CDATA[<h1 id="2023-신기한-소수"><a href="https://www.acmicpc.net/problem/2023">2023. 신기한 소수</a></h1>
<h2 id="문제">문제</h2>
<p>수빈이가 세상에서 가장 좋아하는 것은 소수이고, 취미는 소수를 가지고 노는 것이다. 요즘 수빈이가 가장 관심있어 하는 소수는 7331이다.</p>
<p>7331은 소수인데, 신기하게도 733도 소수이고, 73도 소수이고, 7도 소수이다. 즉, 왼쪽부터 1자리, 2자리, 3자리, 4자리 수 모두 소수이다! 수빈이는 이런 숫자를 신기한 소수라고 이름 붙였다.</p>
<p>수빈이는 N자리의 숫자 중에서 어떤 수들이 신기한 소수인지 궁금해졌다. N이 주어졌을 때, 수빈이를 위해 N자리 신기한 소수를 모두 찾아보자.</p>
<h3 id="입력">입력</h3>
<p>첫째 줄에 N(1 ≤ N ≤ 8)이 주어진다.</p>
<h3 id="출력">출력</h3>
<p>N자리 수 중에서 신기한 소수를 오름차순으로 정렬해서 한 줄에 하나씩 출력한다.</p>
<h3 id="예제-입력-1">예제 입력 1</h3>
<p>4</p>
<h3 id="예제-출력-1">예제 출력 1</h3>
<p>2333
2339
2393
2399
2939
3119
3137
3733
3739
3793
3797
5939
7193
7331
7333
7393</p>
<h3 id="알고리즘-분류">알고리즘 분류</h3>
<ul>
<li>수학</li>
<li>정수론</li>
<li>백트래킹</li>
<li>소수 판정</li>
</ul>
<h3 id="코드---python3-성공">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

# 입력 받기
n = int(input())

# 소수 판별 함수
def isPrime(a):
    if (a &lt; 2):
        return False
    for i in range(2, int(a ** 0.5) + 1):
        if (a % i == 0):
            return False
    return True

# 깊이 우선 탐색 함수
def dfs(num):
    # 목표 길이 도달 시 멈춤
    if len(str(num)) == n:
        print(num)
    else:
        # 한 자리씩 숫자를 더해가며 탐색
        for i in range(10):
            temp = num * 10 + i
            # 새로 만든 숫자가 소수인지 확인하고, 소수면 재귀적으로 탐색 진행
            if isPrime(temp):
                dfs(temp)

# 한자리수 소수인 2, 3, 5, 7에서 시작하여, 깊이 우선 탐색을 수행
dfs(2)
dfs(3)
dfs(5)
dfs(7)</code></pre>
<hr>
<h1 id="1504-특정한-최단-경로"><a href="https://www.acmicpc.net/problem/1504">1504. 특정한 최단 경로</a></h1>
<h2 id="문제-1">문제</h2>
<p>방향성이 없는 그래프가 주어진다. 세준이는 1번 정점에서 N번 정점으로 최단 거리로 이동하려고 한다. 또한 세준이는 두 가지 조건을 만족하면서 이동하는 특정한 최단 경로를 구하고 싶은데, 그것은 바로 임의로 주어진 두 정점은 반드시 통과해야 한다는 것이다.</p>
<p>세준이는 한번 이동했던 정점은 물론, 한번 이동했던 간선도 다시 이동할 수 있다. 하지만 반드시 최단 경로로 이동해야 한다는 사실에 주의하라. 1번 정점에서 N번 정점으로 이동할 때, 주어진 두 정점을 반드시 거치면서 최단 경로로 이동하는 프로그램을 작성하시오.</p>
<h3 id="입력-1">입력</h3>
<p>첫째 줄에 정점의 개수 N과 간선의 개수 E가 주어진다. (2 ≤ N ≤ 800, 0 ≤ E ≤ 200,000) 둘째 줄부터 E개의 줄에 걸쳐서 세 개의 정수 a, b, c가 주어지는데, a번 정점에서 b번 정점까지 양방향 길이 존재하며, 그 거리가 c라는 뜻이다. (1 ≤ c ≤ 1,000) 다음 줄에는 반드시 거쳐야 하는 두 개의 서로 다른 정점 번호 v1과 v2가 주어진다. (v1 ≠ v2, v1 ≠ N, v2 ≠ 1) 임의의 두 정점 u와 v사이에는 간선이 최대 1개 존재한다.</p>
<h3 id="출력-1">출력</h3>
<p>첫째 줄에 두 개의 정점을 지나는 최단 경로의 길이를 출력한다. 그러한 경로가 없을 때에는 -1을 출력한다.</p>
<h3 id="예제-입력-1-1">예제 입력 1</h3>
<p>4 6
1 2 3
2 3 3
3 4 1
1 3 5
2 4 5
1 4 4
2 3</p>
<h3 id="예제-출력-1-1">예제 출력 1</h3>
<p>7</p>
<h3 id="알고리즘-분류-1">알고리즘 분류</h3>
<ul>
<li>그래프 이론</li>
<li>데이크스트라</li>
<li>최단 경로</li>
</ul>
<h3 id="코드---python3-성공-1">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
import heapq
input = sys.stdin.readline

# 다익스트라
def solution(start):
    visited = [1e9 for _ in range(n + 1)] # 최단거리 테이블
    heap = []
    heapq.heappush(heap, [0, start])
    visited[start] = 0
    while heap:
        # 가장 최단거리가 짧은 노드에 대한 정보 꺼내기
        dist, num = heapq.heappop(heap) # 거리, 정점 번호

        # 거리가 해당 정점의 저장된 거리보다 크다면 탐색할 필요없음.
        if dist &gt; visited[num]:
            continue

        # 해당 정점과 인접한 정점의 노드를 확인
        for i, j in graph[num]:
            cost = dist + j

            # 인접한 노드를 거쳐서 이동하는 것이 더 빠른 경우
            if cost &lt; visited[i]:
                visited[i] = cost
                heapq.heappush(heap, [cost, i])

    return visited


n, e = map(int, sys.stdin.readline().split()) # 정점개수, 간선개수
graph = [[] for _ in range(n+1)] # 정점 정보와 그 거리

# 양방향 그래프 표시
for i in range(e):
    a, b, c = map(int, input().split())
    graph[a].append([b, c])
    graph[b].append([a, c])
v1, v2 = map(int, input().split())

a = solution(1) # 1부터 n까지 다익스트라
b = solution(v1) # v1부터 n까지 다익스트라
c = solution(v2) # v2부터 n까지 다익스트라

# 1-v1-v2-n 경우와 1-v2-v1-n 경우중 최단 거리를 구한다.
answer = min(a[v1] + b[v2] + c[n], a[v2] + c[v1] + b[n])

if answer &gt;= 1e9:
    print(-1)
else:
    print(answer)</code></pre>
<hr>
<h1 id="11094-행렬-곱셈-순서"><a href="https://www.acmicpc.net/problem/11049">11094. 행렬 곱셈 순서</a></h1>
<h2 id="문제-2">문제</h2>
<p>크기가 N×M인 행렬 A와 M×K인 B를 곱할 때 필요한 곱셈 연산의 수는 총 N×M×K번이다. 행렬 N개를 곱하는데 필요한 곱셈 연산의 수는 행렬을 곱하는 순서에 따라 달라지게 된다.</p>
<p>예를 들어, A의 크기가 5×3이고, B의 크기가 3×2, C의 크기가 2×6인 경우에 행렬의 곱 ABC를 구하는 경우를 생각해보자.</p>
<p>AB를 먼저 곱하고 C를 곱하는 경우 (AB)C에 필요한 곱셈 연산의 수는 5×3×2 + 5×2×6 = 30 + 60 = 90번이다.
BC를 먼저 곱하고 A를 곱하는 경우 A(BC)에 필요한 곱셈 연산의 수는 3×2×6 + 5×3×6 = 36 + 90 = 126번이다.
같은 곱셈이지만, 곱셈을 하는 순서에 따라서 곱셈 연산의 수가 달라진다.</p>
<p>행렬 N개의 크기가 주어졌을 때, 모든 행렬을 곱하는데 필요한 곱셈 연산 횟수의 최솟값을 구하는 프로그램을 작성하시오. 입력으로 주어진 행렬의 순서를 바꾸면 안 된다.</p>
<h3 id="입력-2">입력</h3>
<p>첫째 줄에 행렬의 개수 N(1 ≤ N ≤ 500)이 주어진다.</p>
<p>둘째 줄부터 N개 줄에는 행렬의 크기 r과 c가 주어진다. (1 ≤ r, c ≤ 500)</p>
<p>항상 순서대로 곱셈을 할 수 있는 크기만 입력으로 주어진다.</p>
<h3 id="출력-2">출력</h3>
<p>첫째 줄에 입력으로 주어진 행렬을 곱하는데 필요한 곱셈 연산의 최솟값을 출력한다. 정답은 231-1 보다 작거나 같은 자연수이다. 또한, 최악의 순서로 연산해도 연산 횟수가 231-1보다 작거나 같다.</p>
<h3 id="예제-입력-1-2">예제 입력 1</h3>
<p>3
5 3
3 2
2 6</p>
<h3 id="예제-출력-1-2">예제 출력 1</h3>
<p>90</p>
<h3 id="알고리즘-분류-2">알고리즘 분류</h3>
<ul>
<li>다이나믹 프로그래밍</li>
</ul>
<h3 id="코드---pypy-성공">코드 - pypy 성공</h3>
<pre><code class="language-python">import sys

N = int(input())
arr = [list(map(int, sys.stdin.readline().split())) for _ in range(N)]

dp = [[0] * (N) for _ in range(N)]

for term in range(1, N):
    for start in range(N):  # 첫행렬 : i, 끝행렬: i+term
        if start + term == N:  # 범위를 벗어나면 무시
            break

        dp[start][start + term] = int(1e9)  # 지금 계산할 첫행렬과 끝행렬

        for t in range(start, start + term):
            dp[start][start + term] = min(dp[start][start + term], # 1 + 2 + 3
                                          dp[start][t] + dp[t + 1][start + term] + arr[start][0] * arr[t][1] * arr[start + term][1])

print(dp[0][N - 1])</code></pre>
<hr>
<h1 id="1727-커플-만들기"><a href="https://www.acmicpc.net/problem/1727">1727. 커플 만들기</a></h1>
<h2 id="문제-3">문제</h2>
<p>여자친구가 없는 남자 n명과 남자친구가 없는 여자 m명을 불러 모아서 이성 친구를 만들어 주기로 하였다. 하지만 아무렇게나 해줄 수는 없고, 최대한 비슷한 성격의 사람들을 짝 지어 주기로 하였다.</p>
<p>당신은 뭔가 알 수 없는 방법으로 각 사람의 성격을 수치화 하는데 성공하였다. 따라서 각 사람의 성격은 어떤 정수로 표현된다. 이와 같은 성격의 수치가 주어졌을 때, 우선 최대한 많은 커플을 만들고, 각 커플을 이루는 두 사람의 성격의 차이의 합이 최소가 되도록 하려 한다. 남자-여자 커플만 허용된다.</p>
<h3 id="입력-3">입력</h3>
<p>첫째 줄에 n, m(1 ≤ n, m ≤ 1,000)이 주어진다. 다음 줄에는 n명의 남자들의 성격이 주어진다. 그 다음 줄에는 m명의 여자들의 성격이 주어진다. 성격은 1,000,000이하의 자연수이다.</p>
<h3 id="출력-3">출력</h3>
<p>첫째 줄에 성격의 차이의 합의 최솟값을 출력한다.</p>
<h3 id="예제-입력-1-3">예제 입력 1</h3>
<p>2 1
10 20
15</p>
<h3 id="예제-출력-1-3">예제 출력 1</h3>
<p>5</p>
<h3 id="알고리즘-분류-3">알고리즘 분류</h3>
<ul>
<li>알고리즘 분류</li>
<li>다이나믹 프로그래밍</li>
<li>그리디 알고리즘</li>
<li>정렬</li>
</ul>
<h3 id="코드---python3-성공-2">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

# 입력 받기
N, M = map(int, input().split())
man = list(map(int, input().split()))
woman = list(map(int, input().split()))

# 남자와 여자의 성격을 오름차순으로 정렬
man.sort()
woman.sort()

# DP 테이블 초기화
d = [[0 for _ in range(M + 1)] for _ in range(N + 1)]

# DP 테이블 갱신
for i in range(1, N + 1):
    for j in range(1, M + 1):
        # 현재 커플의 성격 차이를 계산하고 이전까지의 누적 차이를 더함
        d[i][j] = d[i - 1][j - 1] + abs(man[i - 1] - woman[j - 1])

        # i가 j보다 크면 i쪽이 사람이 더 많으므로, i-1쪽과의 차이 중 작은 값을 선택
        if i &gt; j:
            d[i][j] = min(d[i][j], d[i - 1][j])
        # i가 j보다 작으면 j쪽이 사람이 더 많으므로, j-1쪽과의 차이 중 작은 값을 선택
        elif i &lt; j:
            d[i][j] = min(d[i][j], d[i][j - 1])

# 결과 출력
print(d[N][M])</code></pre>
<hr>
<h1 id="11401-이항-계수-3"><a href="https://www.acmicpc.net/problem/11401">11401. 이항 계수 3</a></h1>
<h2 id="문제-4">문제</h2>
<p>자연수 
(N)과 정수 
(K)가 주어졌을 때 이항 계수 
(\binom{N}{K})를 1,000,000,007로 나눈 나머지를 구하는 프로그램을 작성하시오.</p>
<h3 id="입력-4">입력</h3>
<p>첫째 줄에 
(N)과 
(K)가 주어진다. (1 ≤ 
(N) ≤ 4,000,000, 0 ≤ 
(K) ≤ 
(N))</p>
<h3 id="출력-4">출력</h3>
<p>(\binom{N}{K})를 1,000,000,007로 나눈 나머지를 출력한다.</p>
<h3 id="예제-입력-1-4">예제 입력 1</h3>
<p>5 2</p>
<h3 id="예제-출력-1-4">예제 출력 1</h3>
<p>10</p>
<h3 id="알고리즘-분류-4">알고리즘 분류</h3>
<ul>
<li>수학</li>
<li>정수론</li>
<li>조합론</li>
<li>분할 정복을 이용한 거듭제곱</li>
<li>모듈로 곱셈 역원</li>
<li>페르마의 소정리</li>
</ul>
<h3 id="코드---python3-성공-3">코드 - python3 성공</h3>
<pre><code class="language-python">import sys
input = sys.stdin.readline

N, K = map(int, input().split())
p = 1000000007

# 팩토리얼 값 계산(나머지 연산 적용)
def factorial(N):
    n = 1
    for i in range(2, N + 1):
        n = (n * i) % p
    return n

# 거듭제곱 계산(나머지 연산 적용)
def square(n, k):
    if k == 0:
        return 1
    elif k == 1:
        return n

    tmp = square(n, k // 2)
    if k % 2:
        return tmp * tmp * n % p
    else:
        return tmp * tmp % p

# 분자에 해당하는 팩토리얼 계산
top = factorial(N)
# 분모에 해당하는 팩토리얼 계산 및 역원(페르마의 소정리) 적용
bot = factorial(N - K) * factorial(K) % p

# 조합 공식을 페르마의 소정리를 이용하여 구현
result = top * square(bot, p - 2) % p

# 결과 출력
print(result)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240119 2차 프로젝트 - Kotlin 캘린더 구현]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%98%EB%A6%B0%EB%8D%94-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-2%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%98%EB%A6%B0%EB%8D%94-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 19 Jan 2024 08:54:55 GMT</pubDate>
            <description><![CDATA[<h1 id="git-협업하기">Git 협업하기</h1>
<h2 id="1-git-issue-생성-및-branch-접속">1. Git issue 생성 및 branch 접속</h2>
<pre><code>git fetch origin
git checkout feat/3-create-calendar</code></pre><h2 id="2-pr-받고-코드리뷰하기">2. PR 받고 코드리뷰하기</h2>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/691b67aa-ec48-4a27-85d0-89adb482513d/image.png" alt=""></p>
<h1 id="코틀린으로-달력만들기"><a href="https://stickode.tistory.com/139">코틀린으로 달력만들기</a></h1>
<h2 id="calendarview-활용하기"><a href="https://developer.android.com/reference/android/widget/CalendarView">calendarview 활용하기</a></h2>
<p><strong>HomeActivity.kt</strong></p>
<pre><code class="language-kotlin">package kr.sesac.aoao.android

import android.annotation.SuppressLint
import java.io.FileInputStream
import java.io.FileOutputStream

import android.view.View
import android.os.Bundle
import android.widget.Button
import android.widget.CalendarView
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

/**
 * @since 2024.01.19 ~
 * @author 김유빈, 최정윤
 */
class HomeActivity : AppCompatActivity(){

    var userID: String = &quot;userID&quot;
    lateinit var fname: String
    lateinit var str: String
    lateinit var calendarView: CalendarView
    lateinit var updateBtn: Button
    lateinit var deleteBtn:Button
    lateinit var saveBtn:Button
    lateinit var diaryTextView: TextView
    lateinit var diaryContent:TextView
    lateinit var title:TextView
    lateinit var contextEditText: EditText

    /**
     * 캘린더 구현
     * @since 2024.01.19 ~
     * @author 최정윤
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_calendar)

        // UI값 생성
        calendarView=findViewById(R.id.calendarView)
        diaryTextView=findViewById(R.id.diaryTextView)
        saveBtn=findViewById(R.id.saveBtn)
        deleteBtn=findViewById(R.id.deleteBtn)
        updateBtn=findViewById(R.id.updateBtn)
        diaryContent=findViewById(R.id.diaryContent)
        title=findViewById(R.id.title)
        contextEditText=findViewById(R.id.contextEditText)

        title.text = &quot;달력 일기장&quot;

        calendarView.setOnDateChangeListener { view, year, month, dayOfMonth -&gt;
            diaryTextView.visibility = View.VISIBLE
            saveBtn.visibility = View.VISIBLE
            contextEditText.visibility = View.VISIBLE
            diaryContent.visibility = View.INVISIBLE
            updateBtn.visibility = View.INVISIBLE
            deleteBtn.visibility = View.INVISIBLE
            diaryTextView.text = String.format(&quot;%d / %d / %d&quot;, year, month + 1, dayOfMonth)
            contextEditText.setText(&quot;&quot;)
            checkDay(year, month, dayOfMonth, userID)
        }

        saveBtn.setOnClickListener {
            saveDiary(fname)
            contextEditText.visibility = View.INVISIBLE
            saveBtn.visibility = View.INVISIBLE
            updateBtn.visibility = View.VISIBLE
            deleteBtn.visibility = View.VISIBLE
            str = contextEditText.text.toString()
            diaryContent.text = str
            diaryContent.visibility = View.VISIBLE
        }
    }

    // 달력 내용 조회, 수정
    fun checkDay(cYear: Int, cMonth: Int, cDay: Int, userID: String) {
        //저장할 파일 이름설정
        fname = &quot;&quot; + userID + cYear + &quot;-&quot; + (cMonth + 1) + &quot;&quot; + &quot;-&quot; + cDay + &quot;.txt&quot;

        var fileInputStream: FileInputStream
        try {
            fileInputStream = openFileInput(fname)
            val fileData = ByteArray(fileInputStream.available())
            fileInputStream.read(fileData)
            fileInputStream.close()
            str = String(fileData)
            contextEditText.visibility = View.INVISIBLE
            diaryContent.visibility = View.VISIBLE
            diaryContent.text = str
            saveBtn.visibility = View.INVISIBLE
            updateBtn.visibility = View.VISIBLE
            deleteBtn.visibility = View.VISIBLE
            updateBtn.setOnClickListener {
                contextEditText.visibility = View.VISIBLE
                diaryContent.visibility = View.INVISIBLE
                contextEditText.setText(str)
                saveBtn.visibility = View.VISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                diaryContent.text = contextEditText.text
            }
            deleteBtn.setOnClickListener {
                diaryContent.visibility = View.INVISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                contextEditText.setText(&quot;&quot;)
                contextEditText.visibility = View.VISIBLE
                saveBtn.visibility = View.VISIBLE
                removeDiary(fname)
            }
            if (diaryContent.text == null) {
                diaryContent.visibility = View.INVISIBLE
                updateBtn.visibility = View.INVISIBLE
                deleteBtn.visibility = View.INVISIBLE
                diaryTextView.visibility = View.VISIBLE
                saveBtn.visibility = View.VISIBLE
                contextEditText.visibility = View.VISIBLE
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }


    // 달력 내용 제거
    @SuppressLint(&quot;WrongConstant&quot;)
    fun removeDiary(readDay: String?) {
        var fileOutputStream: FileOutputStream
        try {
            fileOutputStream = openFileOutput(readDay, MODE_NO_LOCALIZED_COLLATORS)
            val content = &quot;&quot;
            fileOutputStream.write(content.toByteArray())
            fileOutputStream.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }


    // 달력 내용 추가
    @SuppressLint(&quot;WrongConstant&quot;)
    fun saveDiary(readDay: String?) {
        var fileOutputStream: FileOutputStream
        try {
            fileOutputStream = openFileOutput(readDay, MODE_NO_LOCALIZED_COLLATORS)
            val content = contextEditText.text.toString()
            fileOutputStream.write(content.toByteArray())
            fileOutputStream.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }
}</code></pre>
<p><strong>activity_calendar.xml</strong></p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:orientation=&quot;vertical&quot; &gt;

    &lt;CalendarView
        android:id=&quot;@+id/calendarView&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginTop=&quot;4dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintHorizontal_bias=&quot;0.488&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/title&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/diaryTextView&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:gravity= &quot;center&quot;
        android:layout_marginTop=&quot;16dp&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/calendarView&quot; app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginEnd=&quot;8dp&quot; android:layout_marginRight=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot; android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot; android:textAppearance=&quot;@style/TextAppearance.AppCompat.Large&quot;/&gt;

    &lt;EditText
        android:id=&quot;@+id/contextEditText&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;116dp&quot;
        android:inputType=&quot;textMultiLine&quot;
        android:ems=&quot;10&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/diaryTextView&quot; android:layout_marginTop=&quot;16dp&quot;
        android:hint=&quot;내용을 입력하세요.&quot; android:layout_marginEnd=&quot;8dp&quot; app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginRight=&quot;8dp&quot; android:layout_marginStart=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot; android:layout_marginLeft=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;TextView
        android:id=&quot;@+id/diaryContent&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;0dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintHorizontal_bias=&quot;0.0&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintTop_toTopOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintVertical_bias=&quot;0.0&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/title&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginTop=&quot;8dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:gravity=&quot;center&quot;
        android:text=&quot;달력일기장&quot;
        android:textAppearance=&quot;@style/TextAppearance.AppCompat.Large&quot;
        android:textColor=&quot;#9E28B3&quot;
        android:textSize=&quot;24sp&quot;
        android:textStyle=&quot;bold&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;Button
        android:text=&quot;저장&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_marginBottom=&quot;20dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/saveBtn&quot;
        android:layout_marginTop=&quot;16dp&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/contextEditText&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;Button
        android:text=&quot;수정&quot;
        android:layout_width=&quot;180dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/updateBtn&quot;
        app:layout_constraintBaseline_toBaselineOf=&quot;@+id/saveBtn&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        android:layout_marginLeft=&quot;8dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        app:layout_constraintEnd_toStartOf=&quot;@+id/deleteBtn&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;/&gt;

    &lt;Button
        android:text=&quot;삭제&quot;
        android:layout_width=&quot;176dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/deleteBtn&quot;
        app:layout_constraintBaseline_toBaselineOf=&quot;@+id/updateBtn&quot;
        app:layout_constraintEnd_toEndOf=&quot;@+id/saveBtn&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginRight=&quot;8dp&quot;
        android:visibility=&quot;invisible&quot;&gt;
    &lt;/Button&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/bc27a129-bc7c-44f4-933b-07f40f8e587c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[새싹] 현대IT&E 240118 기록 - Kotlin]]></title>
            <link>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240118-%EA%B8%B0%EB%A1%9D-Kotlin</link>
            <guid>https://velog.io/@jeong_yooony/%EC%83%88%EC%8B%B9-%ED%98%84%EB%8C%80ITE-240118-%EA%B8%B0%EB%A1%9D-Kotlin</guid>
            <pubDate>Thu, 18 Jan 2024 06:07:35 GMT</pubDate>
            <description><![CDATA[<h1 id="11-미세먼지-앱-v-10-레트로핏을-이용한-네트워크-통신">11. 미세먼지 앱 V 1.0 레트로핏을 이용한 네트워크 통신</h1>
<h2 id="113-준비하기-프로젝트-뷰-바인딩-라이브러리-airvisual-api-키">11.3 준비하기: 프로젝트, 뷰 바인딩, 라이브러리, AirVisual API 키</h2>
<h3 id="1132-뷰-바인딩-설정과-필요-라이브러리-추가">11.3.2 뷰 바인딩 설정과 필요 라이브러리 추가</h3>
<p><strong>build.gradle</strong></p>
<pre><code>plugins {
    id &#39;com.android.application&#39;
    id &#39;org.jetbrains.kotlin.android&#39;

    id &#39;kotlin-kapt&#39;
}

android {
    namespace &#39;com.example.airquality&#39;
    compileSdk 34

    defaultConfig {
        applicationId &quot;com.example.airquality&quot;
        minSdk 26
        targetSdk 34
        versionCode 1
        versionName &quot;1.0&quot;

        testInstrumentationRunner &quot;androidx.test.runner.AndroidJUnitRunner&quot;
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile(&#39;proguard-android-optimize.txt&#39;), &#39;proguard-rules.pro&#39;
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = &#39;1.8&#39;
    }
    viewBinding {
        enabled = true
    }
}

dependencies {

    implementation &#39;androidx.core:core-ktx:1.12.0&#39;
    implementation &#39;androidx.appcompat:appcompat:1.6.1&#39;
    implementation &#39;com.google.android.material:material:1.11.0&#39;
    implementation &#39;androidx.constraintlayout:constraintlayout:2.1.4&#39;
    testImplementation &#39;junit:junit:4.13.2&#39;
    androidTestImplementation &#39;androidx.test.ext:junit:1.1.5&#39;
    androidTestImplementation &#39;androidx.test.espresso:espresso-core:3.5.1&#39;

    implementation &#39;com.squareup.retrofit2:retrofit:2.9.0&#39;
    implementation &#39;com.squareup.retrofit2:converter-gson:2.9.0&#39;
}</code></pre><h3 id="1133-airvisual에서-api키-발급받기">11.3.3 AirVisual에서 API키 발급받기</h3>
<p><a href="https://www.iqair.com/ko/">AirVisual 홈페이지 바로가기</a></p>
<p><strong>대시보드 접속</strong>
<img src="https://velog.velcdn.com/images/jeong_yooony/post/775671a2-3287-4954-a4fe-92d7ccd6fe73/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/8df0f5a4-0d22-4b54-b3f2-f3bfb2907727/image.png" alt="">
<img src="https://velog.velcdn.com/images/jeong_yooony/post/0557b02b-6172-4723-90a0-80987f595eb1/image.png" alt=""></p>
<h2 id="114-레이아웃-구성하기">11.4 레이아웃 구성하기</h2>
<h3 id="1141-깃허브에서-이미지-리소스-받아오기">11.4.1 깃허브에서 이미지 리소스 받아오기</h3>
<p><a href="https://github.com/code-with-joyce/must_have_android/tree/image_source">https://github.com/code-with-joyce/must_have_android/tree/image_source</a></p>
<h3 id="1142-mainactivity의-레이아웃-구성하기">11.4.2 MainActivity의 레이아웃 구성하기</h3>
<p><strong>activity_main.xml</strong></p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;


    &lt;TextView
        android:id=&quot;@+id/tv_location_title&quot;
        android:text=&quot;역삼1동&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:letterSpacing=&quot;-0.05&quot;
        android:textColor=&quot;#000000&quot;
        android:textSize=&quot;32sp&quot;
        android:textStyle=&quot;bold&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/guideline1&quot;
        app:layout_constraintTop_toTopOf=&quot;@+id/guideline4&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/tv_location_subtitle&quot;
        android:text=&quot;대한민국 서울특별시&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:letterSpacing=&quot;-0.05&quot;
        android:textColor=&quot;#000000&quot;
        android:textSize=&quot;16sp&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/guideline1&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/tv_location_title&quot; /&gt;

    &lt;ImageView
        android:layout_width=&quot;10dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;9dp&quot;
        android:src=&quot;@drawable/iocn_thunder&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@id/tv_location_title&quot;
        app:layout_constraintStart_toEndOf=&quot;@id/tv_location_title&quot;
        app:layout_constraintTop_toTopOf=&quot;@id/tv_location_title&quot; /&gt;

    &lt;ImageView
        android:id=&quot;@+id/img_bg&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;0dp&quot;
        android:src=&quot;@drawable/bg_soso&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintDimensionRatio=&quot;h,1:1&quot;
        app:layout_constraintEnd_toEndOf=&quot;@id/guideline3&quot;
        app:layout_constraintStart_toStartOf=&quot;@id/guideline2&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        app:layout_constraintVertical_bias=&quot;0.4&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/tv_count&quot;
        android:text=&quot;61&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:letterSpacing=&quot;0.05&quot;
        android:textColor=&quot;#4c4c4c&quot;
        android:textSize=&quot;32sp&quot;
        android:textStyle=&quot;bold&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@id/img_bg&quot;
        app:layout_constraintEnd_toEndOf=&quot;@id/img_bg&quot;
        app:layout_constraintStart_toStartOf=&quot;@id/img_bg&quot;
        app:layout_constraintTop_toTopOf=&quot;@id/img_bg&quot;
        /&gt;

    &lt;TextView
        android:id=&quot;@+id/tv_title&quot;
        android:text=&quot;보통&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:letterSpacing=&quot;-0.05&quot;
        android:textColor=&quot;#999999&quot;
        android:textSize=&quot;14sp&quot;
        app:layout_constraintEnd_toEndOf=&quot;@id/img_bg&quot;
        app:layout_constraintStart_toStartOf=&quot;@id/img_bg&quot;
        app:layout_constraintTop_toBottomOf=&quot;@id/tv_count&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/check_time&quot;
        android:text=&quot;측정 시간&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;40dp&quot;
        android:letterSpacing=&quot;-0.05&quot;
        android:textColor=&quot;#999999&quot;
        android:textSize=&quot;13sp&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@id/img_bg&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/tv_check_time&quot;
        android:text=&quot;2021-08-29 13:00&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:letterSpacing=&quot;-0.05&quot;
        android:textColor=&quot;#999999&quot;
        android:textSize=&quot;13sp&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@id/check_time&quot; /&gt;

    &lt;ImageView
        android:id=&quot;@+id/btn_refresh&quot;
        android:layout_width=&quot;28dp&quot;
        android:layout_height=&quot;28dp&quot;
        android:layout_marginTop=&quot;20dp&quot;
        android:padding=&quot;5dp&quot;
        android:src=&quot;@drawable/icon_refresh&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@id/tv_check_time&quot; /&gt;


    &lt;androidx.constraintlayout.widget.Guideline
        android:id=&quot;@+id/guideline1&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:orientation=&quot;vertical&quot;
        app:layout_constraintGuide_percent=&quot;0.1&quot; /&gt;

    &lt;androidx.constraintlayout.widget.Guideline
        android:id=&quot;@+id/guideline2&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:orientation=&quot;vertical&quot;
        app:layout_constraintGuide_percent=&quot;0.18&quot; /&gt;

    &lt;androidx.constraintlayout.widget.Guideline
        android:id=&quot;@+id/guideline3&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:orientation=&quot;vertical&quot;
        app:layout_constraintGuide_percent=&quot;0.82&quot; /&gt;

    &lt;androidx.constraintlayout.widget.Guideline
        android:id=&quot;@+id/guideline4&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:orientation=&quot;horizontal&quot;
        app:layout_constraintGuide_percent=&quot;0.1&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/jeong_yooony/post/28a9cd6c-db18-47d4-a74e-4c7e39ce029c/image.png" alt=""></p>
<h2 id="115-gps와-인터넷-권한-설정하기">11.5 GPS와 인터넷 권한 설정하기</h2>
<h3 id="1151-androidmanifestxml-파일에서-권한-추가">11.5.1 AndroidManifest.xml 파일에서 권한 추가</h3>
<p><strong>AndroidManifest.xml</strong></p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;&gt;

    &lt;!--Internet Permission--&gt;
    &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;

    &lt;!--GPS &amp; Location--&gt;
    &lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt;
    &lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_LOCATION&quot;/&gt;
    &lt;uses-permission android:name=&quot;android.permission.ACCESS_BACKGROUND_LOCATION&quot; /&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:dataExtractionRules=&quot;@xml/data_extraction_rules&quot;
        android:fullBackupContent=&quot;@xml/backup_rules&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/Theme.AirQuality&quot;
        tools:targetApi=&quot;31&quot;&gt;
        &lt;activity
            android:name=&quot;.MainActivity&quot;
            android:exported=&quot;true&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;</code></pre>
<h3 id="1152-런타임에서-권한과-위치-서비스-확인하기">11.5.2 런타임에서 권한과 위치 서비스 확인하기</h3>
<p><strong>MainActivity.kt</strong></p>
<pre><code class="language-java">package com.example.airquality

import android.Manifest
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.location.LocationManager
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.airquality.databinding.ActivityMainBinding
import com.example.airquality.retrofit.AirQualityResponse
import com.example.airquality.retrofit.AirQualityService
import com.example.airquality.retrofit.RetrofitConnection

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.IOException
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.*

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding

    // 런타임 권한 요청시 필요한 요청 코드입니다.
    private val PERMISSIONS_REQUEST_CODE = 100

    // 요청할 권한 리스트 입니다.
    var REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)

    // 위치 서비스 요청시 필요한 런처입니다.
    lateinit var getGPSPermissionLauncher: ActivityResultLauncher&lt;Intent&gt;

    // 위도와 경도를 가져올 때 필요합니다.
    lateinit var locationProvider: LocationProvider

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        checkAllPermissions()
        updateUI()
        setRefreshButton()
    }

    private fun setRefreshButton() {
        binding.btnRefresh.setOnClickListener {
            updateUI()
        }
    }

    private fun updateUI() {
        locationProvider = LocationProvider(this@MainActivity)

        //위도와 경도 정보를 가져옵니다.
        val latitude: Double = locationProvider.getLocationLatitude()
        val longitude: Double = locationProvider.getLocationLongitude()

        if (latitude != 0.0 || longitude != 0.0) {

            //1. 현재 위치를 가져오고 UI 업데이트
            //현재 위치를 가져오기
            val address = getCurrentAddress(latitude, longitude) //주소가 null 이 아닐 경우 UI 업데이트
            address?.let {
                binding.tvLocationTitle.text = &quot;${it.thoroughfare}&quot; // 예시: 역삼 1동
                binding.tvLocationSubtitle.text = &quot;${it.countryName} ${it.adminArea}&quot; // 예시 : 대한민국 서울특별시
            }

            //2. 현재 미세먼지 농도 가져오고 UI 업데이트
            getAirQualityData(latitude, longitude)

        } else {
            Toast.makeText(this@MainActivity, &quot;위도, 경도 정보를 가져올 수 없었습니다. 새로고침을 눌러주세요.&quot;, Toast.LENGTH_LONG).show()
        }
    }

    /**
     * @desc 레트로핏 클래스를 이용하여 미세먼지 오염 정보를 가져옵니다.
     * */
    private fun getAirQualityData(latitude: Double, longitude: Double) { // 레트로핏 객체를 이용하면 AirQualityService 인터페이스 구현체를 가져올 수 있습니다.
        val retrofitAPI = RetrofitConnection.getInstance().create(AirQualityService::class.java)

        retrofitAPI.getAirQualityData(latitude.toString(), longitude.toString(), &quot;f8f5a711-7da9-4118-a875-304ffded8cb8&quot;)
            .enqueue(object : Callback&lt;AirQualityResponse&gt; {
                override fun onResponse(
                    call: Call&lt;AirQualityResponse&gt;,
                    response: Response&lt;AirQualityResponse&gt;,
                ) { //정상적인 Response가 왔다면 UI 업데이트
                    if (response.isSuccessful) {
                        Toast.makeText(this@MainActivity, &quot;최신 정보 업데이트 완료!&quot;, Toast.LENGTH_SHORT).show() //만약 response.body()가 null 이 아니라면 updateAirUI()
                        response.body()?.let { updateAirUI(it) }
                    } else {
                        Toast.makeText(this@MainActivity, &quot;업데이트에 실패했습니다.&quot;, Toast.LENGTH_SHORT).show()
                    }
                }

                override fun onFailure(call: Call&lt;AirQualityResponse&gt;, t: Throwable) {
                    t.printStackTrace()
                }
            })
    }

    /**
     * @desc 가져온 데이터 정보를 바탕으로 화면을 업데이트한다.
     * */
    private fun updateAirUI(airQualityData: AirQualityResponse) {
        val pollutionData = airQualityData.data.current.pollution

        //수치 지정 (가운데 숫자)
        binding.tvCount.text = pollutionData.aqius.toString()

        //측정된 날짜 지정
        //&quot;2021-09-04T14:00:00.000Z&quot; 형식을  &quot;2021-09-04 23:00&quot;로 수정
        val dateTime = ZonedDateTime.parse(pollutionData.ts).withZoneSameInstant(ZoneId.of(&quot;Asia/Seoul&quot;)).toLocalDateTime()
        val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm&quot;)

        binding.tvCheckTime.text = dateTime.format(dateFormatter).toString()

        when (pollutionData.aqius) {
            in 0..50 -&gt; {
                binding.tvTitle.text = &quot;좋음&quot;
                binding.imgBg.setImageResource(R.drawable.bg_good)
            }

            in 51..150 -&gt; {
                binding.tvTitle.text = &quot;보통&quot;
                binding.imgBg.setImageResource(R.drawable.bg_soso)
            }

            in 151..200 -&gt; {
                binding.tvTitle.text = &quot;나쁨&quot;
                binding.imgBg.setImageResource(R.drawable.bg_bad)
            }

            else -&gt; {
                binding.tvTitle.text = &quot;매우 나쁨&quot;
                binding.imgBg.setImageResource(R.drawable.bg_worst)
            }
        }
    }

    /**
     * @desc 위도와 경도를 기준으로 실제 주소를 가져온다.
     * */
    fun getCurrentAddress(latitude: Double, longitude: Double): Address? {
        val geocoder = Geocoder(this, Locale.getDefault()) // Address 객체는 주소와 관련된 여러 정보를 가지고 있습니다. android.location.Address 패키지 참고.
        val addresses: List&lt;Address&gt;?

        addresses = try { //Geocoder 객체를 이용하여 위도와 경도로부터 리스트를 가져옵니다.
            geocoder.getFromLocation(latitude, longitude, 7)
        } catch (ioException: IOException) {
            Toast.makeText(this, &quot;지오코더 서비스 사용불가합니다.&quot;, Toast.LENGTH_LONG).show()
            return null
        } catch (illegalArgumentException: IllegalArgumentException) {
            Toast.makeText(this, &quot;잘못된 위도, 경도 입니다.&quot;, Toast.LENGTH_LONG).show()
            return null
        }

        //에러는 아니지만 주소가 발견되지 않은 경우
        if (addresses == null || addresses.size == 0) {
            Toast.makeText(this, &quot;주소가 발견되지 않았습니다.&quot;, Toast.LENGTH_LONG).show()
            return null
        }

        val address: Address = addresses[0]

        return address
    }

    private fun checkAllPermissions() {
        if (!isLocationServicesAvailable()) { //1. 위치 서비스(GPS)가 켜져있는지 확인합니다.
            showDialogForLocationServiceSetting();
        } else {  //2. 런타임 앱 권한이 모두 허용되어있는지 확인합니다.
            isRunTimePermissionsGranted();
        }
    }

    fun isLocationServicesAvailable(): Boolean {
        val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
        return (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
    }

    fun isRunTimePermissionsGranted() { // 위치 퍼미션을 가지고 있는지 체크합니다.
        val hasFineLocationPermission = ContextCompat.checkSelfPermission(this@MainActivity, Manifest.permission.ACCESS_FINE_LOCATION)
        val hasCoarseLocationPermission = ContextCompat.checkSelfPermission(this@MainActivity, Manifest.permission.ACCESS_COARSE_LOCATION)
        if (hasFineLocationPermission != PackageManager.PERMISSION_GRANTED || hasCoarseLocationPermission != PackageManager.PERMISSION_GRANTED) { // 권한이 한 개라도 없다면 퍼미션 요청을 합니다.
            ActivityCompat.requestPermissions(this@MainActivity, REQUIRED_PERMISSIONS, PERMISSIONS_REQUEST_CODE)
        }
    }

    /**
     * @desc 런타임 권한을 요청하고 권한 요청에 따른 결과를 리턴한다.
     * */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array&lt;out String&gt;,
        grantResults: IntArray,
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == PERMISSIONS_REQUEST_CODE &amp;&amp; grantResults.size == REQUIRED_PERMISSIONS.size) {

            // 요청 코드가 PERMISSIONS_REQUEST_CODE 이고, 요청한 퍼미션 개수만큼 수신되었다면
            var checkResult = true

            // 모든 퍼미션을 허용했는지 체크합니다.
            for (result in grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    checkResult = false
                    break
                }
            }
            if (checkResult) { //위치 값을 가져올 수 있음
                updateUI()
            } else { //퍼미션이 거부되었다면 앱을 종료합니다.
                Toast.makeText(this@MainActivity, &quot;퍼미션이 거부되었습니다. 앱을 다시 실행하여 퍼미션을 허용해주세요.&quot;, Toast.LENGTH_LONG).show()
                finish()
            }
        }
    }

    /**
     * @desc LocationManager를 사용하기 위해서 권한을 요청한다.
     * */
    private fun showDialogForLocationServiceSetting() {

        //먼저 ActivityResultLauncher를 설정해줍니다. 이 런처를 이용하여 결과 값을 리턴해야하는 인텐트를 실행할 수 있습니다.
        getGPSPermissionLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -&gt; //결과 값을 받았을 때 로직을 작성해줍니다.
            if (result.resultCode == Activity.RESULT_OK) { //사용자가 GPS 를 활성화 시켰는지 확인합니다.
                if (isLocationServicesAvailable()) {
                    isRunTimePermissionsGranted()
                } else { //위치 서비스가 허용되지 않았다면 앱을 종료합니다.
                    Toast.makeText(this@MainActivity, &quot;위치 서비스를 사용할 수 없습니다.&quot;, Toast.LENGTH_LONG).show()
                    finish()
                }
            }
        }

        val builder: AlertDialog.Builder = AlertDialog.Builder(this@MainActivity)
        builder.setTitle(&quot;위치 서비스 비활성화&quot;)
        builder.setMessage(&quot;위치 서비스가 꺼져있습니다. 설정해야 앱을 사용할 수 있습니다.&quot;)
        builder.setCancelable(true)
        builder.setPositiveButton(&quot;설정&quot;, DialogInterface.OnClickListener { dialog, id -&gt;
            val callGPSSettingIntent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
            getGPSPermissionLauncher.launch(callGPSSettingIntent)
        })
        builder.setNegativeButton(&quot;취소&quot;, DialogInterface.OnClickListener { dialog, id -&gt;
            dialog.cancel()
            Toast.makeText(this@MainActivity, &quot;기기에서 위치서비스(GPS) 설정 후 사용해주세요.&quot;, Toast.LENGTH_SHORT).show()
            finish()
        })
        builder.create().show()
    }
}</code></pre>
<h2 id="116-위치-정보-가져오기">11.6 위치 정보 가져오기</h2>
<h3 id="1161-locationprovider-클래스-생성하기">11.6.1 LocationProvider 클래스 생성하기</h3>
<p><strong>LocationProvider.kt</strong></p>
<pre><code class="language-java">package com.example.airquality

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.util.Log
import androidx.core.content.ContextCompat
import androidx.core.location.LocationManagerCompat.requestLocationUpdates

/**
 * @author Joyce Hong
 * @email joycehong0524@gmail.com
 * @created 2021/08/29
 * @desc
 */

class LocationProvider(val context: Context) {

    //Location 클래스는 위도, 경도, 고도와 같이 위치에 관련된 정보를 가지고 있는 데이터 클래스입니다.
    private var location: Location? = null
    //Location Manager는 시스템 위치 서비스에 접근을 제공하는 클래스입니다.
    private var locationManager: LocationManager? = null

    init {
        //초기화 시에 위치를 가져옵니다.
        getLocation();
    }

    private fun getLocation(): Location? {
        try {
            //먼저 위치 시스템 서비스를 가져옵니다.
            locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager


            var gpsLocation: Location? = null
            var networkLocation: Location? = null

            //GPS Provider 와 Network Provider 활성화 되어있는지 확인
            val isGPSEnabled: Boolean =
                locationManager!!.isProviderEnabled(LocationManager.GPS_PROVIDER)
            val isNetworkEnabled: Boolean =
                locationManager!!.isProviderEnabled(LocationManager.NETWORK_PROVIDER)

            if (!isGPSEnabled &amp;&amp; !isNetworkEnabled) {
                //GPS, Network Provider 둘 다 사용 불가능한 상황이면 null 을 반환합니다.
                return null
            } else {
                val hasFineLocationPermission = ContextCompat.checkSelfPermission(
                    context,
                    Manifest.permission.ACCESS_FINE_LOCATION // ACCESS_COARSE_LOCATION 보다 더 정밀한 위치 정보를 얻을 수 있습니다.
                )
                val hasCoarseLocationPermission = ContextCompat.checkSelfPermission(
                    context,
                    Manifest.permission.ACCESS_COARSE_LOCATION // 도시 Block 단위의 정밀도의 위치 정보를 얻을 수 있습니다.
                )
                //만약 위 두 개 권한 없다면 null을 반환합니다.
                if (hasFineLocationPermission != PackageManager.PERMISSION_GRANTED ||
                    hasCoarseLocationPermission != PackageManager.PERMISSION_GRANTED
                ) return null

                //네트워크를 통한 위치 파악이 가능한 경우에 위치를 가져옵니다.
                if (isNetworkEnabled) {
                    networkLocation =
                        locationManager?.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
                }

                //GPS를 통한 위치 파악이 가능한 경우에 위치를 가져옵니다.
                if (isGPSEnabled) {
                    gpsLocation =
                        locationManager?.getLastKnownLocation(LocationManager.GPS_PROVIDER)
                }

                if (gpsLocation != null &amp;&amp; networkLocation != null) {
                    //만약 두 개 위치가 있다면 정확도 높은 것으로 선택합니다.
                    if (gpsLocation.accuracy &gt; networkLocation.accuracy) {
                        location = gpsLocation
                        return gpsLocation
                    } else {
                        location = networkLocation
                        return networkLocation
                    }
                } else {
                    //만약 가능한 위치 정보가 한 개만 있는 경우
                    if (gpsLocation != null) {
                        location = gpsLocation
                    }

                    if (networkLocation != null) {
                        location = networkLocation
                    }
                }

            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return location
    }

    //위도 정보를 가져오는 함수입니다.
    fun getLocationLatitude(): Double {
        return location?.latitude ?: 0.0
    }

    //경도 정보르 가져오는 함수입니다.
    fun getLocationLongitude(): Double {
        return location?.longitude ?: 0.0
    }

}</code></pre>
]]></description>
        </item>
    </channel>
</rss>