<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>choi-yh.log</title>
        <link>https://velog.io/</link>
        <description>golang과 elasticsearch를 좋아하는 3년차 백엔드 엔지니어입니다.</description>
        <lastBuildDate>Sat, 20 Apr 2024 11:37:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>choi-yh.log</title>
            <url>https://images.velog.io/images/choi-yh/profile/c85ed3af-5437-443c-972f-b80042ffb34a/IMG_2648.PNG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. choi-yh.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/choi-yh" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[lines 세미나] 3년간의 검색 서비스 담당기]]></title>
            <link>https://velog.io/@choi-yh/lines-%EC%84%B8%EB%AF%B8%EB%82%98-3%EB%85%84%EA%B0%84%EC%9D%98-%EA%B2%80%EC%83%89-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%8B%B4%EB%8B%B9%EA%B8%B0</link>
            <guid>https://velog.io/@choi-yh/lines-%EC%84%B8%EB%AF%B8%EB%82%98-3%EB%85%84%EA%B0%84%EC%9D%98-%EA%B2%80%EC%83%89-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%8B%B4%EB%8B%B9%EA%B8%B0</guid>
            <pubDate>Sat, 20 Apr 2024 11:37:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>24.04.20 (토) lines 세미나 발표 자료</p>
</blockquote>
<h1 id="0-도입">0. 도입</h1>
<hr>
<p>사내 동료 분께서 기술 / 경험 공유 세미나를 연다고 하셨고, 간단하게라도 발표 할 의향이 있다면 참석해보면 좋을 것 같다고 하셔서 세미나에 참가해 발표를 진행했습니다.</p>
<p>ELK 시스템을 구축해보는 내용을 발표해볼까 했지만 발표 시간이 짧아 경험한 내용을 공유하는게 좋다고 생각했고, 현재 업무로 진행하고 있고 관심이 많은 검색 도메인 담당에 대한 얘기를 발표 주제로 선정했습니다.</p>
<h1 id="1-왜-검색인가">1. 왜 검색인가?</h1>
<hr>
<p>우리는 살면서 정보를 얻기 위해 항상 검색 행위를 합니다. 개발 디버깅을 위해 구글링을 하거나, 유튜브에서 관심있는 정보를 얻거나, 원하는 물건을 사기 위해서 등등...
저도 많은 검색 행위를 해오며 검색이 잘 되면 좋겠다 라는 생각을 가지고 있던 와중에 사내에서 검색 서비스와 관련된 업무를 하게 되었습니다. 그러면서 자연스럽게 ELK 스택에 대해서 알게 되었고, 여러 삽질을 거치며 이 기술에 대한 매력을 느끼게 되었습니다.</p>
<blockquote>
<p>ELK 란 Elasticsearch, Logstash, Kibana, Beats 라는 오픈 소스 프로젝트를 합쳐 ELK 스택이라고 합니다.</p>
</blockquote>
<ul>
<li>Elasticsearch : 검색 엔진을 위한 데이터베이스</li>
<li>Logstash : 데이터 파이프라인,</li>
<li>Kibana : 이와 관련한 UI, 시각화, 대시보드 역할을 가능하게 합니다.</li>
<li>Beats : 데이터 수집기 (metricbeat, filebeat, auditbeat, ...)<blockquote>
<p>주로 beats (수집) -&gt; logstash (전송) -&gt; elasticsearch (저장) -&gt; kibana (UI)</p>
</blockquote>
</li>
</ul>
<h1 id="2-무얼-했는가">2. 무얼 했는가?</h1>
<hr>
<p>크게 두 가지를 진행했습니다. 첫째, 검색 기능 개선 프로젝트. 둘째, 검색 서비스 운영</p>
<p>검색 기능 개선 프로젝트로는 </p>
<ul>
<li>기존 레거시 코드에 대한 개선을 진행했습니다. node.js 로 되어있던 코드를 사내 표준 언어였던 golang 으로 전환하고 이 과정에서 string 으로 짜여있던 검색 쿼리를 <a href="https://github.com/elastic/go-elasticsearch">go-elasticsearch</a> 라이브러리를 활용해 유지 보수가 좀 더 수월하도록 변경했습니다. 
(시간이 지난 지금 봤을 때는 이 코드도 마음에 들지 않아 리팩토링을 거치고 싶은데 단순히 마음에 들지 않은 이유로 리팩토링을 하고 싶다는 생각이기 때문에 우선순위가 밀려있습니다.)</li>
<li>데이터 수집 방식을 조금이나마 통합했습니다. 각 기능 별로 비즈니스 로직이 끝난 뒤에 필요한 경우에 Elasticsearch 로 넣어주고 있었는데, 이로 인해 어느 포인트에서 데이터 수집이 이뤄지고 있는지 파악이 잘 안되는 상황이었습니다.
이러한 포인트를 줄이고자 GCP Pub/Sub 을 통해 각 도메인의 이벤트를 받고 여기서 Elasticsearch 로 데이터를 쌓아주도록 작업했습니다.</li>
<li>그리고 기존에 Elasticsearch index mapping 이 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html">dynamic mapping</a> 을 사용하고 있어 필드 타입이 안맞는 경우 인덱싱이 안되는 이슈가 있었는데 필요한 필드를 정리하고 해당 옵션을 끄고 불필요한 text 필드를 keyword 필드로 바꾸는 등 약간의 인덱스 매핑 수정 작업을 진행했습니다.</li>
</ul>
<p>서비스 운영으로는 관련 기능 이슈 해결이나 장애 대응 등의 작업을 주로 진행했습니다.
주로 처리했던 이슈 중 하나로는 데이터 싱크가 맞지 않는 이슈가 있었는데 logstash 를 통해 데이터를 넣어주던 부분도 존재해 logstash 문제인 경우도 있었고, ES cluster 장애로 인해 데이터 수집이 안되는 경우가 있었습니다.
아직은 자동으로 장애 데이터를 싱크해주는 작업이 되어있지 않아 장애 발생시 수동으로 해당 기간의 데이터를 검색 엔진으로 넣어주는식으로 처리했습니다.</p>
<h1 id="3-앞으론-무얼-할-것인가--하고-싶은가">3. 앞으론 무얼 할 것인가 &amp; 하고 싶은가?</h1>
<hr>
<p>앞으로 내가 맡는 검색 서비스는 안정적이고 빠르게 운영을 하고 싶습니다.
현재 시점에서 진행하고 있는 작업으로는</p>
<ol>
<li>ES 클러스터에 대한 모니터링</li>
<li>CDC 를 활용한 검색 데이터 수집</li>
<li>Elasticsearch reindex 자동화 및 작업시 클러스터 부하 감소</li>
</ol>
<p>를 크게 생각하고 있습니다.</p>
<p>현재 ELK 는 VM 에 직접 설치로 운영을 하고 있는데 그렇다보니 logging 이나 metric 시스템이 빈약한 상황입니다. 장애 감지를 위해서 이런 시스템이 필요하다고 생각해서 구축해보는 작업을 현재 진행하고 있고,
데이터 수집 방식도 일부 개선을 했지만 아직 logstash 를 통한 데이터 수집 방식도 남아있고, 새로운 기능이 추가되고 검색에 대한 요구사항이 들어올 때, 관련 시스템을 빠르게 구축할 수 있는 작업을 진행하고 싶습니다.
마지막으로 검색 품질에 대한 부분도 아쉬운 부분이 좀 있는데 필요할 때만 작업을 진행하다보니 elasticsearch reindex 시 사람이 직접하고 있습니다. 이를 좀 더 자동화하고, 부하없이 효과적으로 이런 작업을 할 수 있는 시스템도 구축해놓고 싶습니다.</p>
<h1 id="4-맺으며">4. 맺으며..</h1>
<hr>
<p>ELK 를 좋아하고 현업에서 사용하고 있지만 아직 부족하다는 생각이 항상 듭니다. 해결 방법은 계속해서 공부하고 업무에서 사용해 현재 가지고 있는 문제를 해결하면 이런 생각도 조금씩 나아질거라고 생각합니다. 좀 더 나은 시스템을 위해서 다시 열심히 해봐야겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[slice 에서 length 와 capacity 에 대하여]]></title>
            <link>https://velog.io/@choi-yh/slice-%EC%97%90%EC%84%9C-length-%EC%99%80-capacity-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@choi-yh/slice-%EC%97%90%EC%84%9C-length-%EC%99%80-capacity-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Thu, 11 Jan 2024 15:28:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://100go.co/">100 Go Mistakes and How to Avoid Them</a> 
<a href="https://100go.co/20-slice/">Not understanding slice length and capacity (#20)</a><br>slice 의 length 와 capacity 를 이해하는 것은 고퍼에게 매우 중요한 정보다.</p>
</blockquote>
<h2 id="length--capacity">length &amp; capacity</h2>
<h3 id="기본-개념">기본 개념</h3>
<ul>
<li><code>length</code> 는 slice 에 있는 원소의 수</li>
<li><code>capacity</code> 는 해당 slice 가 가질 수 있는 사이즈</li>
<li><code>make</code> 함수를 사용해서 생성한다.</li>
</ul>
<pre><code>// int 를 원소로 가지고, length=3, capactiy=6 인 슬라이스
s := make([]int, 3, 6)
fmt.Println(s) // [0 0 0]</code></pre><ul>
<li>length 만큼의 해당 slice type의 default value를 가지는 슬라이스를 생성한다.</li>
</ul>
<h3 id="slice-의-element-추가">slice 의 element 추가</h3>
<pre><code>s[1] = 1
fmt.Println(s) // [0 1 0]

s[3] = 3 // panic: runtime error: index out of range [3] with length 3

s = append(s, 3)
fmt.Println(s) // [0 1 0 3]

s = append(s, 4, 5)
fmt.Println(s) // [0, 2, 0, 4, 3, 4, 5]

s = append(s, 4, 5, 6)
fmt.Println(s) // [0 1 0 3 4 5 6]</code></pre><ul>
<li>length 로 초기화된 slice 의 경우 해당 length 안에서 value 를 변경 할 수 있다.</li>
<li>하지만 인덱스가 넘어가는 경우, panic 이 발생한다.</li>
<li>length 로 선언한 뒤에 원소를 추가하고 싶다면 <code>append</code> 함수를 사용</li>
</ul>
<p><img src="https://velog.velcdn.com/images/choi-yh/post/e8a394b5-ed4d-4d30-839d-97d065c610fe/image.png" alt=""></p>
<ul>
<li><code>append</code> 를 통해 원소를 추가하다가 capacity 가 넘어가는 경우, go 에서는 기존 capacity 의 2배를 가지는 array 를 새로 생성하고 해당 array 에 <code>append</code> 로 받은 원소를 추가.
slice 는 새로운 array 를 참조하게 된다.
(기존 array 는 참조되지 않는 경우, GC 에 의해서 해제된다.)</li>
</ul>
<h3 id="slice-재할당에서의-주소-관리">slice 재할당에서의 주소 관리</h3>
<pre><code>s1 := make([]int, 3, 6) // Three-length, six-capacity slice
s2 := s1[1:3] // Slicing from indices 1 to 3

fmt.Println(s1) // [0, 0, 0]
fmt.Println(s2) // [0, 0]</code></pre><ul>
<li>slicing 을 통해 새로운 slice 를 할당해주는 경우, <strong>같은 array 를 참조하지만, length 와 capacity 가 달라진다.</strong>
<img src="https://velog.velcdn.com/images/choi-yh/post/0b0c81c6-8858-4c15-a63a-ad9dccb93d65/image.png" alt=""></li>
</ul>
<pre><code>s1[1] = 1
fmt.Println(s1) // [0, 1, 0]
fmt.Println(s2) // [1, 0]

s2 = append(s2, 2)
fmt.Println(s1) // [0, 1, 0]
fmt.Println(s2) // [1, 0, 2]</code></pre><ul>
<li>같은 array 를 참조하고 있기 때문에 length 에 포함되는 원소를 변경하는 경우, 같이 변하게 된다.</li>
</ul>
<pre><code>s2 = append(s2, 3)
s2 = append(s2, 4) // backing array 가 가득차는 경우
s2 = append(s2, 5)</code></pre><p><img src="https://velog.velcdn.com/images/choi-yh/post/db63fb6c-0532-4fab-a7d1-63e3f317899c/image.png" alt=""></p>
<ul>
<li>append 를 하다가 capacity 가 넘어가게 되는 경우, 위에서 언급했듯이 새로운 array 를 생성하고 slice 에 할당하기 때문에 서로 다른 array 를 참조하게 된다.</li>
</ul>
<h2 id="정리">정리</h2>
<ul>
<li>slice 선언시 capacity 는 잘 선언을 해주자.</li>
<li>개인적으로는 length 도 선언해주는게 좋다고 생각한다.</li>
</ul>
<pre><code>type MyModel struct {
    field1 string
    field2 int
}

a := []MyModel{}
for _, m := range asdf {
    a = append(a, m)
}</code></pre><p>go를 처음 사용할 때 별 생각없이 이런 식으로 많이 사용했었는데 빈 값으로 초기화 됐기 때문에 array capacity doubling 과 메모리 재할당이 계속 일어나는 코드였을 것 같다. (인지하고서는 <code>make</code>를 사용해서 선언해주기는 했지만) 
하지만 capacity 를 2배씩 늘리고, 메모리를 재할당하고, slicing을 했을 때 같은 array 를 참조하고 이런 내용은 몰랐다.<br>기본적인 내용이지만 항상 숙지하고 습득해서 개발하도록 하자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[goroutine 에서의 context 종료 이슈 (23.12.22)]]></title>
            <link>https://velog.io/@choi-yh/goroutine-%EC%97%90%EC%84%9C%EC%9D%98-context-%EC%A2%85%EB%A3%8C-%EC%9D%B4%EC%8A%88-23.12.22</link>
            <guid>https://velog.io/@choi-yh/goroutine-%EC%97%90%EC%84%9C%EC%9D%98-context-%EC%A2%85%EB%A3%8C-%EC%9D%B4%EC%8A%88-23.12.22</guid>
            <pubDate>Thu, 11 Jan 2024 14:29:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>고루틴 사용시 context 종료를 잘 신경쓰자</p>
</blockquote>
<h3 id="발생-이슈">발생 이슈</h3>
<ul>
<li>내부 공통 패키지를 변경하면서 <a href="https://github.com/redis/go-redis">go-redis</a> 버전이 변경됨</li>
<li>해당 패키지를 적용한 뒤에 소켓이 발생하지 않는 이슈</li>
</ul>
<h3 id="이슈-발생-원인">이슈 발생 원인</h3>
<ol>
<li>go-redis v7 -&gt; v9 로 버전이 변경되면서 <code>ctx</code> 를 인자로 받게 됨.</li>
<li>메인 로직을 goroutine 으로 처리하고 있었는데 해당 로직의 내부에서 추가 goroutine 을 통해 소켓을 발송하고 있음</li>
<li>소켓 로직이 비동기로 처리되다보니 메인 로직의 goroutine 이 종료되면서 해당 context 를 종료. 이로 인해 소켓 로직이 종료</li>
</ol>
<h3 id="해결">해결</h3>
<ul>
<li>goroutine 사용 로직을 internal 에 정의해서 사용하고 있었기 때문에 goroutine 누수는 발생하지 않을 것이라고 판단, <code>context.Background()</code> 로 새로운 컨텍스트를 통해 소켓 발송</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AUTH on Web]]></title>
            <link>https://velog.io/@choi-yh/AUTH-on-Web-Session-vs-Token-vs-Cookies</link>
            <guid>https://velog.io/@choi-yh/AUTH-on-Web-Session-vs-Token-vs-Cookies</guid>
            <pubDate>Mon, 22 Nov 2021 10:16:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Session vs Token vs Cookies에 대한 개념</p>
</blockquote>
<h1 id="cookie">Cookie</h1>
<ul>
<li>데이터를 옮기는 매개체</li>
<li>(웹)서버가 브라우저에 지시해 사용자에게 저장하는 저장매체</li>
<li>웹 방문시마다 읽히고, 정보가 수시로 바뀐다.</li>
</ul>
<ul>
<li><strong>쿠키를 통해서</strong> 서버는 브라우저에 데이터를 넣을 수 있다. (유저에 대한 기억을 위해)</li>
<li>서버의 response에 쿠키가 담기게 된다.</li>
<li>쿠키는 서버가 정한 기간에 따라 유효기간이 있다.</li>
<li>브라우저에만 존재 (iOS, Android와 같은 네이티브 앱에는 없다.)</li>
</ul>
<h2 id="문제점">문제점</h2>
<ul>
<li>Http Header에 담아서 보내기 때문에 트래픽이 발생할 수 있음</li>
<li>중요한 정보를 쿠키에 담는 경우 보안 문제 발생 가능</li>
<li>브라우저에만 존재 (iOS, Android와 같은 네이티브 앱에는 없다.)</li>
</ul>
<hr>
<h1 id="session">Session</h1>
<h2 id="http">HTTP</h2>
<ul>
<li>http는 Stateless<ul>
<li>Stateless : 서버에 요청하는 모든 request는 각각 독립</li>
<li>request가 끝나면 서버는 대상이 누군지 모른다.
⇒ request마다 요청자가 누구인지 확인해야 함</li>
</ul>
</li>
</ul>
<h2 id="개념">개념</h2>
<ul>
<li>요청자를 확인하는 방법 중 하나가 Session</li>
<li>로그인한 유저에 대한 정보를 Session ID 형태로 서버의 DB에 저장하는 방식</li>
<li>메모리에 저장하기 때문에 브라우저 종료시 사라짐</li>
</ul>
<h2 id="절차">절차</h2>
<h3 id="유저-로그인-시">유저 로그인 시</h3>
<ol>
<li>유저가 로그인(id, pw 입력)을 하게 되면 User DB에서 유저 정보 확인</li>
<li>유저 정보가 맞다면 Session DB에서 Session ID를 부여 후 서버에 전달</li>
<li>서버는 Cookie에 Session ID를 담아 브라우저로 전송</li>
<li>해당 Cookie를 가지고 브라우저를 사용하게 됨</li>
</ol>
<h3 id="session-id-발급-후-브라우저-탐색">Session ID 발급 후 브라우저 탐색</h3>
<ol>
<li>브라우저(클라이언트)에서 Session ID가 담긴 Cookie를 서버로 전달</li>
<li>서버는 Session ID를 Session DB에서 탐색</li>
<li>Session ID를 가지고 User DB에서 유저 확인</li>
<li>유저 확인이 완료되면 response</li>
<li>1 ~ 4 과정을 브라우저에서 request를 보낼 때마다 반복</li>
</ol>
<h2 id="장점">장점</h2>
<ul>
<li>서버에 정보를 저장하기 때문에 관리가 편함</li>
<li>해당 정보를 가지고 추가 기능 사용 가능 (강제 로그아웃, ...)</li>
</ul>
<h2 id="단점">단점</h2>
<ul>
<li>load-balancing된 서버라면 Session ID를 handling 하기 어렵다
(1번 서버 접속 후 Session ID 발급 → 2번 서버 접속 시 재발급 받아야함)<ul>
<li>Session 정보를 하나의 저장장치에 공유</li>
</ul>
</li>
<li>Session을 위한 물리적인 Database가 필요하다.<ul>
<li>Redis와 같은 메모리 기반 저장장치 사용</li>
</ul>
</li>
</ul>
<hr>
<h1 id="jwt-json-web-token">JWT (Json Web Token)</h1>
<ul>
<li>토큰 형식</li>
<li>Session DB가 필요없고, 서버의 유저 인증을 위한 작업들이 필요하지 않게 됨</li>
<li>토큰과 달리 공간의 제약이 없다.</li>
<li>client가 가지고 있음</li>
</ul>
<ul>
<li>정보를 가진 Token (DB 없이 검증 가능)</li>
</ul>
<h2 id="token">Token</h2>
<ul>
<li>네이티브 앱(iOS, Android)에서 쿠키가 필요한 경우 사용 (서버에 토큰을 보냄)</li>
<li>긴 string</li>
<li>공간의 제약</li>
</ul>
<ul>
<li>서버가 기억하는 텍스트</li>
</ul>
<h2 id="로그인-예시">로그인 예시</h2>
<ol>
<li>유저의 id, pw를 서버에 전달 (Session과 동일)<ul>
<li>id, pw가 맞다면 DB에 뭔가를 생성하지 않음</li>
</ul>
</li>
<li>유저의 ID 등을 가져가서 Sign Algorithm을 활용해서 해당 정보를 sign함</li>
<li>Signed Information을 string 형태로 보내게 된다. (매우 긴 text)</li>
</ol>
<h2 id="종류">종류</h2>
<ol>
<li>Access Token<ul>
<li>실제로 권한을 얻을 때 사용하는 토큰 (개인정보, 이메일 등에 접근할 때 사용)</li>
</ul>
</li>
<li>Refresh Token<ul>
<li>Access Token의 유효기간이 만료됐을 때, Refresh Token을 사용해서 새로운 Access Token을 발급받음</li>
</ul>
</li>
</ol>
<h2 id="request">Request</h2>
<p><img src="https://images.velog.io/images/choi-yh/post/8b9216d1-9787-4c57-ba65-cd95c76b9e47/image.png" alt=""></p>
<ol>
<li>클라이언트가 id, pw를 통해 로그인 요청</li>
<li>서버에서 확인 후 Session ID와 비슷하게 Signed Info 혹은 Token을 전달</li>
<li>클라이언트는 Access Token을 가지고 있다가 서버에 전송</li>
<li>서버에서 토큰을 받으면 해당 토큰의 유효성 검증</li>
<li>토큰이 유효하다면 유저로 인증</li>
</ol>
<h2 id="장점-1">장점</h2>
<ul>
<li>Session과 다르게 DB를 생성할 필요가 없다.</li>
<li>서버가 이중, 삼중일 경우 Session보다 장점을 가진다.
수십, 수백대의 서버의 경우 load balancing으로 인해 유저가 처음 방문시 1번 서버에 Session이 있지만 2번 서버로 방문하게 되면 Session 정보가 없다.
⇒ Redis Session 통합을 하거나 JWT로 구현</li>
</ul>
<h2 id="단점-1">단점</h2>
<ul>
<li>암호화 되어있지 않다. (누구나 해당 컨텐츠를 확인 가능)</li>
</ul>
<hr>
<h1 id="session-vs-jwt">Session vs JWT</h1>
<p><strong>Session</strong></p>
<ul>
<li>로그인된 모든 유저의 정보를 저장</li>
<li>해당 정보를 통해 기능 추가 가능<ul>
<li>특정 유저 추방 (강제 로그아웃)
→ 해당 세션 삭제</li>
</ul>
</li>
<li>Session을 위한 DB가 필요하다<ul>
<li>주로 Redis를 사용</li>
</ul>
</li>
</ul>
<p><strong>JWT</strong></p>
<ul>
<li>생성된 토큰 추적 X</li>
<li>서버는 토큰의 유효성만 검증</li>
<li>DB 필요 X</li>
<li>ex) QR 체크인이 JWT임</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><a href="https://www.youtube.com/watch?v=tosLBcAX1vk">https://www.youtube.com/watch?v=tosLBcAX1vk</a></li>
<li><a href="https://velog.io/@shitaikoto/Web-Auth-Token">https://velog.io/@shitaikoto/Web-Auth-Token</a></li>
<li><a href="https://nesoy.github.io/articles/2017-03/Session-Cookie">https://nesoy.github.io/articles/2017-03/Session-Cookie</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OS] 가상 메모리 관리]]></title>
            <link>https://velog.io/@choi-yh/OS-%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@choi-yh/OS-%EA%B0%80%EC%83%81-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Thu, 18 Nov 2021 14:34:32 GMT</pubDate>
            <description><![CDATA[<h1 id="1-overview">1. Overview</h1>
<blockquote>
<p>가상메모리를 사용한다면 하나의 프로세스 전체가 주기억장치에 존재하지 않고 일부만 있더라도 해당 프로세스를 수행할 수 있다.</p>
</blockquote>
<ul>
<li><strong>virtual memory(가상메모리)</strong> : 가상 주소 공간, 논리적 주소</li>
<li><strong>real / main memory(주기억장치)</strong> : 실제 주소 공간, 물리적 주소</li>
<li>주기억장치보다 크기가 큰 프로세스를 수행시킬 수 있다.</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/73724453-9622-42d7-8e84-77b33af861d4/image.png" alt="가상메모리"></p>
<h2 id="mapping-사상">Mapping (사상)</h2>
<blockquote>
<p>물리적 저장 공간과 가상 저장 공간 사이의 <strong>mapping(사상)</strong>은 운영체제에 의해서 수행된다.</p>
</blockquote>
<ul>
<li><strong>virtual address(가상 주소)</strong> : 수행중인 프로세스가 참조하는 주소</li>
<li><strong>real address(실제 주소)</strong> : 주기억장치 상에서 이용할 수 있는 주소</li>
</ul>
<h3 id="11-datdynamic-address-translation-동적-주소-변환">1.1 DAT(Dynamic Address Translation) (동적 주소 변환)</h3>
<blockquote>
<p>프로세스가 수행될 때 가상 주소를 실제 주소로 변환하는 대표적인 메커니즘</p>
</blockquote>
<ul>
<li><strong>artificial continuity(인위적 연속성)</strong>
모든 프로세스가 가지는 가상 주소 공간 상의 연속된 주소들은 실기억 공간에서도 반드시 연속적일 필요가 없는 것을 의미</li>
<li>DAT에서는 어떤 가상기억 주소에 대한 <em>주기억장치 내의 존재 여부</em>와 <em>주기억장치의 어디에 위치하는가</em>를 나타내기 위한 대응관계 테이블을 유지, 관리한다.</li>
</ul>
<h3 id="12-block-mapping-블록-사상">1.2 Block Mapping (블록 사상)</h3>
<ul>
<li><p>DAT의 경우, mapping으로 인한 주기억장치가 많이 필요해질 수 있다.</p>
</li>
<li><p>mapping 내용을 가상메모리의 <strong>block</strong> 단위로 저장하고, 시스템은 블록이 위치하는 장소를 추적한다.</p>
</li>
<li><p><strong>block(블록)</strong> : 가상메모리에 대한 분할 단위  </p>
</li>
<li><p><strong>page(페이지)</strong> : block을 같은 단위로 구성</p>
<ul>
<li><strong>paging(페이징)</strong> : page를 활용한 가상메모리 구성</li>
</ul>
</li>
<li><p><strong>segment(세그먼트)</strong> : block을 서로 다른 크기로 구성</p>
<ul>
<li><strong>segmentation(세그먼테이션)</strong> : segment를 활용한 가상메모리 구성</li>
</ul>
</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/9c8defdd-e8b6-4114-8079-ca99b8cf839a/image.png" alt="Block Mapping을 통한 가상 주소 변환"></p>
<hr>
<h1 id="2-paging-페이징">2. Paging (페이징)</h1>
<blockquote>
</blockquote>
<ul>
<li><p>page : 일정한 크기의 block</p>
</li>
<li><p>paging : 주소 공간을 페이지 단위로 나누고, 실제 주소 공간은 페이지 크기와 같은 <strong>page frame(페이지 프레임)</strong>으로 나눠서 사용</p>
</li>
<li><p>페이징 시스템에서 가상 주소는 $v=(p, d)$로 표현된다.</p>
<ul>
<li>$p$ : 가상메모리 내에서 참조될 항목이 있는 페이지 번호</li>
<li>$d$ : 페이지 $p$ 내에서 참조될 항목이 위치하고 있는 곳의 변위</li>
</ul>
</li>
<li><p><strong>페이징 기법에서의 동적 주소 변환 과정</strong></p>
<ol>
<li>page mapping table에서 페이지 $p$를 찾는다.</li>
<li>페이지 $p$가 페이지 프레임 $p&#39;$에 있는지 확인한다.</li>
<li>이를 통해 주기억장치 상의 실제 주소 $r=p&#39;+d$를 구한다.</li>
</ol>
</li>
</ul>
<ul>
<li>위의 과정에서 페이지 $p$가 주기억장치 내에 존재하는지 확인해야 하는데, 
이는 <strong>page residence bit(페이지 존재 비트)</strong>를 활용한다.<ul>
<li>0 : 페이지가 주기억장치 상에 없기 때문에 하드 디스크에 잇는 내용을 메인 메모리에 적재</li>
<li>1 : 주기억장치 내에 존재한다는 의미이므로 바로 사용</li>
</ul>
</li>
</ul>
<h2 id="21-direct-mapping-직접-사상">2.1 Direct Mapping (직접 사상)</h2>
<blockquote>
<p>mapping : 가상메모리의 주소를 주기억장치 내의 주소로 변환하는 방법</p>
</blockquote>
<ul>
<li>direct mapping에서 크기가 큰 page mapping table은 보통 주기억장치에서 유지, 관리</li>
<li>빠른 주소 변환을 위해, 속도가 매우 빠른 highspeed cache memory(고속 캐시 메모리)를 이용해 direct mapping의 page mapping table을 구현</li>
</ul>
<h2 id="22-associative-mapping-연관-사상">2.2 Associative Mapping (연관 사상)</h2>
<blockquote>
<p>동적 주소 변환을 신속하게 수행하기 위해 주기억장치보다 빠른 연관기억장치에 페이지 사상 테이블 전체를 넣는 방법</p>
</blockquote>
<ul>
<li>저장된 값을 이용해 데이터에 접근하는 <strong>CAM(Content Addressable Memory) (내용 주소화 메모리)</strong>와 내용의 일부를 기억하는 데이터 레지스터 운영</li>
<li>여러개 주소에 동시 접속 가능</li>
<li>주소만 참조하는 것이 아니라 내용도 참조</li>
<li>연관 기억 장치는 고가</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/57a8b3c4-a691-421e-9e23-64f280a35dfd/image.png" alt="순수 연관 사상을 통한 주소 변환"></p>
<h2 id="23-direct--associative-mapping-연관--직접-사상">2.3 Direct / Associative Mapping (연관 / 직접 사상)</h2>
<blockquote>
<p><strong>가장 최근에 참조된 page는 다시 사용되기 쉽다는 사실</strong>을 이용해 
연관기억장치에는 page mapping table의 전체 항목 중 가장 최근에 참조된 일부 page 항목들만을 수용하는 방식</p>
</blockquote>
<ul>
<li>캐시기억장치만을 사용하거나 (direct mapping), 연관기억장치만을 사용하는 (associative mapping) 구현 방식은 주기억장치보다 비용이 많이 들기 때문에 고안됨</li>
<li>동적 주소 변환 방식 (가상 주소 : $v=(p, d)$)<ol>
<li>페이지 $p$를 연관 페이지 사상 테이블에서 찾는다.
존재한다면 $p&#39;$를 얻고 변위 $d$와 접속해 실제 주소 $r=p&#39;+d$를 구한다.</li>
<li>$p$와 일치하는 항목이 연관 페이지 사상 테이블에 없다면, 직접 페이지 사상 테이블의 주기억장치 주소를 페이지 사상 테이블 시작점 레지스터에 넣어 해당 페이지를 찾는다.</li>
</ol>
</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/7130d2fa-3e9c-4144-8d8a-82a0e26a305e/image.png" alt=""></p>
<h2 id="24-페이징-시스템의-공유">2.4 페이징 시스템의 공유</h2>
<blockquote>
<p>여러 사용자가 동일한 프로그램을 동시에 수행시킬 경우, 
공유가 가능한 페이지를 가능한 공유하도록 한다.</p>
</blockquote>
<ul>
<li><strong>pure procedure(순수 프로시듀어) / reentrant procedure(재진입가능 프로시듀어)</strong> <ul>
<li>수정이 불가능한 프로시듀어</li>
<li>이를 통해 자원을 공유한다.
<img src="https://images.velog.io/images/choi-yh/post/db5ef4a6-e980-4aa9-bc41-3bcf902517d4/image.png" alt="순수 페이징 시스템에서의 공유"></li>
</ul>
</li>
</ul>
<h2 id="25-페이지-크기">2.5 페이지 크기</h2>
<blockquote>
<p>페이지 크기를 결정함에 있어서 고려할 사항</p>
</blockquote>
<h3 id="251-페이지-크기가-작을-경우">2.5.1 페이지 크기가 작을 경우</h3>
<h4 id="장점">장점</h4>
<ol>
<li>프로세스가 작업 세트(working set)을 확보하는데 도움이 된다.<ul>
<li><strong>작업 세트(working set)</strong> : 프로세스가 자주 사용하는 페이지 집합</li>
</ul>
</li>
<li>동적 프로그래밍 정도 상승</li>
</ol>
<h4 id="단점">단점</h4>
<ol>
<li><strong>테이블 단편화(table fragmentation)</strong> 발생<ul>
<li>페이지 크기가 작다면 페이지와 페이지 프레임이 증가하고, 이를 관리하기 위한 페이지 테이블의 크기가 증가하여 기억 공간이 낭비되는 현상</li>
</ul>
</li>
</ol>
<h3 id="252-페이지-크기가-클-경우">2.5.2 페이지 크기가 클 경우</h3>
<h4 id="장점-1">장점</h4>
<ol>
<li>페이지 크기가 크다면, 한번에 많은 양의 데이터를 전송할 수 있고, 이는 입출력 전송의 횟수(I/O interrupt)를 줄일 수 있다.</li>
<li>메인메모리 적재횟수 감소 가능</li>
</ol>
<h4 id="단점-1">단점</h4>
<ol>
<li>참조되지 않을 정보들까지 주기억장치로 옮겨져 기억 공간이 낭비될 수 있다.</li>
</ol>
<h2 id="26-페이지-인출-기법">2.6 페이지 인출 기법</h2>
<blockquote>
<p>보조기억장치에서 주기억장치로 프로세스를 적재하는 방법</p>
</blockquote>
<h3 id="261-demand-paging-요구-페이징">2.6.1 Demand Paging (요구 페이징)</h3>
<blockquote>
<p>실행 중인 프로세스에 의해 명백하게 참조되는 프로세스만이 보조기억장치에서 주기억장치로 옮겨진다.</p>
</blockquote>
<h4 id="장점-2">장점</h4>
<ul>
<li>주기억장치에 적재된 프로세스는 모두 실제로 참조되는 프로세스라고 확신할 수 있다.</li>
<li>새로운 페이지를 임의로 주기억장치에 적재하지 않아도 된다.</li>
<li>어느 페이지를 얼만큼 주기억장치로 옮길지 결정하는 오버 헤드를 최소화 할 수 있다.</li>
</ul>
<h4 id="단점-2">단점</h4>
<ul>
<li>현재 메인메모리에 데이터가 없다면 유휴시간이 발생한다.<ul>
<li>사용할 데이터만 기다리는 경우가 발생할 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="262-anticipatory-paging-예상-페이징">2.6.2 Anticipatory Paging (예상 페이징)</h3>
<blockquote>
<p>프로세스가 필요로 할 페이지들을 운영체제가 예측하여 주기적장치에 여유가 있을 때 해당 페이지를 미리 적재</p>
</blockquote>
<ul>
<li>예상이 맞다면 유휴시간 없이 해당 프로세스를 바로 사용할 수 있다.</li>
</ul>
<h2 id="27-page-release-페이지-양도">2.7 Page Release (페이지 양도)</h2>
<blockquote>
</blockquote>
<ul>
<li>더 이상 필요로 하지 않는 특정한 페이지가 존재한다면, 작업세트에서 해당 프로세스를 제외해 페이지 프레임을 제대로 활용</li>
<li>** 효율적인 방법은 컴파일러나 운영체제가 자동으로 페이지 제거를 실행해 작업세트를 확보하도록 하는 것**</li>
</ul>
<hr>
<h1 id="3-segmentation-세그먼테이션">3. Segmentation (세그먼테이션)</h1>
<blockquote>
<p>가변 분할 메모리 할당</p>
</blockquote>
<h2 id="31-direct-mapping-직접-사상">3.1 Direct Mapping (직접 사상)</h2>
<h2 id="32-공유-및-보호">3.2 공유 및 보호</h2>
<hr>
<h1 id="4-세그먼트--페이징-혼용-기법">4. 세그먼트 / 페이징 혼용 기법</h1>
<h2 id="41-동적-주소-변환">4.1 동적 주소 변환</h2>
<h2 id="42-시스템의-공유">4.2 시스템의 공유</h2>
<hr>
<h1 id="5-페이지-교체-알고리즘">5. 페이지 교체 알고리즘</h1>
<blockquote>
<p>주기억장치 공간을 확보하기 위해, 현재 주기억장치를 차지하고 있는 페이지들 중에서 어떤 페이지를 선택해 가상공간으로 보낼 것인가를 결정하는 기법</p>
</blockquote>
<h2 id="51-fifofirst-in-first_out-알고리즘">5.1 FIFO(First-In First_Out) 알고리즘</h2>
<h2 id="52-optimal-replacement최적-교체-알고리즘">5.2 Optimal Replacement(최적 교체) 알고리즘</h2>
<h2 id="53-lruleast-recently-used-알고리즘">5.3 LRU(Least Recently Used) 알고리즘</h2>
<h2 id="54-second-chance2차-기회-알고리즘">5.4 Second Chance(2차 기회) 알고리즘</h2>
<blockquote>
<p>페이지를 바로 교체하지 않고 기회를 주는 알고리즘</p>
</blockquote>
<h2 id="55-lfuleast-frequency-used-알고리즘">5.5 LFU(Least Frequency Used) 알고리즘</h2>
<blockquote>
<p>가장 적게 사용되거나 집중적이 아닌 페이지가 대체</p>
</blockquote>
<hr>
<h1 id="6-thrashing-스래싱">6. Thrashing (스래싱)</h1>
<blockquote>
<p>페이지 부재가 계속적으로 발생해 프로세스가 수행되는 시간보다 페이지 교체에 소비되는 시간이 더 많은 경우</p>
</blockquote>
<h2 id="61-locality-구역성">6.1 Locality (구역성)</h2>
<h2 id="62-working-set-작업세트">6.2 Working Set (작업세트)</h2>
<hr>
<h1 id="references">References</h1>
<ul>
<li>[생능출판사] (<a href="https://www.booksr.co.kr/html/book/book.asp?seq=696837">https://www.booksr.co.kr/html/book/book.asp?seq=696837</a>)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Database] 인덱스]]></title>
            <link>https://velog.io/@choi-yh/Database-%EC%9D%B8%EB%8D%B1%EC%8A%A4</link>
            <guid>https://velog.io/@choi-yh/Database-%EC%9D%B8%EB%8D%B1%EC%8A%A4</guid>
            <pubDate>Thu, 18 Nov 2021 13:23:27 GMT</pubDate>
            <description><![CDATA[<h1 id="정의">정의</h1>
<blockquote>
</blockquote>
<ul>
<li>데이터베이스 내에서 테이블에 대한 동작 속도 (검색 속도)를 높여주는 자료구조</li>
<li>책의 색인처럼 데이터베이스 테이블을 저장한 뒤 해당 색인을 사용해 원하는 데이터를 검색 </li>
</ul>
<h1 id="원리">원리</h1>
<ul>
<li>특정 컬럼에 대한 인덱스 생성시, 해당 컬럼의 데이터들을 정렬해 별도의 공간에 데이터의 주소와 함께 저장<ul>
<li>MYI 파일에 인덱스 정보를 저장한다.</li>
<li>MYI 파일은 <code>Key</code> (인덱스로 생성한 컬럼 값), <code>RowID</code> (데이터가 저장된 주소 값)의 구조를 통해 검색 및 정렬 속도를 향상시킴</li>
</ul>
</li>
<li>저장한 인덱스를 통해 Table을 Full Scan하지 않는다.</li>
</ul>
<h1 id="장점">장점</h1>
<ul>
<li>원하는 데이터를 찾기 위해 테이블을 검색하는 작업을 줄이거나 없앨 수 있다.</li>
<li>색인화된 인덱스 파일 검색으로 검색 속도를 향상시킬 수 있다.</li>
</ul>
<ul>
<li>따라서 <code>SELECT</code>, <code>JOIN</code> 쿼리가 잦고, 변경이 잦지 않는 테이블의 경우 인덱스를 사용하면 좋다.</li>
</ul>
<h1 id="단점">단점</h1>
<ul>
<li>테이블에 새로운 데이터의 CRUD가 일어날 경우, 인덱스에도 작업이 생기기 때문에 오버헤드가 발생할 수 있다.</li>
</ul>
<ul>
<li>따라서 데이터 변경 작업이 자주 일어나는 경우, 성능이 떨어질 수 있다.</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><a href="https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Database#index">https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Database#index</a><ul>
<li><a href="https://gyoogle.dev/blog/computer-science/data-base/Index-.html">https://gyoogle.dev/blog/computer-science/data-base/Index-.html</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Database] 정규화]]></title>
            <link>https://velog.io/@choi-yh/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%95%EA%B7%9C%ED%99%94</link>
            <guid>https://velog.io/@choi-yh/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%95%EA%B7%9C%ED%99%94</guid>
            <pubDate>Tue, 31 Aug 2021 02:28:00 GMT</pubDate>
            <description><![CDATA[<h3 id="잘못된-db-설계로-인한-갱신-이상">잘못된 DB 설계로 인한 갱신 이상</h3>
<table>
<thead>
<tr>
<th align="center">name</th>
<th align="center">id</th>
<th align="center">address</th>
<th align="center">phone</th>
<th align="center">department_id</th>
<th align="center">department_name</th>
</tr>
</thead>
<tbody><tr>
<td align="center">김철수</td>
<td align="center">2100</td>
<td align="center">석촌동</td>
<td align="center">123-1234</td>
<td align="center">1</td>
<td align="center">개발</td>
</tr>
<tr>
<td align="center">김철수</td>
<td align="center">2100</td>
<td align="center">석촌동</td>
<td align="center">123-1234</td>
<td align="center">2</td>
<td align="center">기획</td>
</tr>
<tr>
<td align="center">박수영</td>
<td align="center">4311</td>
<td align="center">삼전동</td>
<td align="center">341-1002</td>
<td align="center">3</td>
<td align="center">영업</td>
</tr>
<tr>
<td align="center">정유나</td>
<td align="center">3012</td>
<td align="center">가락동</td>
<td align="center">859-4099</td>
<td align="center">2</td>
<td align="center">기획</td>
</tr>
<tr>
<td align="center">정유나</td>
<td align="center">3012</td>
<td align="center">가락동</td>
<td align="center">859-4099</td>
<td align="center">3</td>
<td align="center">영업</td>
</tr>
</tbody></table>
<ul>
<li><strong>정보의 중복</strong><ul>
<li>어떤 정보가 반복적으로 저장되어 저장 공간이 낭비된다</li>
<li>속한 부서(<em>department_id</em>)만큼 사원 정보가 낭비됨</li>
</ul>
</li>
<li><strong>수정 이상(modification anomaly)</strong><ul>
<li>반복된 데이터 중에 일부만 수정하면 데이터의 불일치가 발생한다. (모든 데이터를 수정해야 이용 가능)</li>
<li>부서 이름이 바뀔 때, 부서 id에 해당하는 모든 부서명을 바꿔줘야 한다. (바꾸지 않을 경우 오류가 생긴다.)</li>
</ul>
</li>
<li><strong>삽입 이상(insertion anomaly)</strong><ul>
<li>불필요한 정보를 함께 저장하지 않고는 어떤 정보를 저장하는 것이 불가능하다.</li>
<li>새로운 부서가 생겼는데 해당 부서에 사원이 한명도 없을 경우, 입력이 불가능하다. (Primary Key가 (<em>name</em>, <em>id</em>)이기 때문)</li>
</ul>
</li>
<li><strong>삭제 이상(deletion anomaly)</strong><ul>
<li>유용한 정보를 함꼐 삭제하지 않고는 어떤 정보를 삭제하는 것이 불가능하다. (원하는 정보 외에 다른 정보도 삭제)</li>
<li>어떤 부서에 사원이 한명 있을 때, 해당 사원 정보를 삭제할 경우, 부서 정보도 삭제된다.</li>
</ul>
</li>
</ul>
<h2 id="함수적-종속성">함수적 종속성</h2>
<ul>
<li><strong>determinant(결정자)</strong><ul>
<li><em>주어진 테이블에서 다른 필드를 고유하게 결정하는 하나 이상의 필드</em></li>
<li>$A \rightarrow B$ &quot;A가 B의 determinant이다.&quot;</li>
</ul>
</li>
<li><em>A 속성(attribute)이 B 속성의 결정자(determinant)이면 B가 A에 함수적으로 종속한다.</em>
(어떤 테이블 R에서 B 속성이 A 속성에 함수적으로 종속한다. 
$\Leftrightarrow$ 각 A 값에 대해 반드시 한 개의 B 값이 대응된다.)</li>
<li>제2정규형 부터 BCNF까지 적용</li>
</ul>
<h3 id="완전-함수적-종속성ffd-full-functional-dependency">완전 함수적 종속성(FFD: Full Functional Dependency)</h3>
<ul>
<li>Def) 테이블 R에서 $A \rightarrow B$ 이면서 A의 어떠한 진부분 집합에도 함수적으로 종속하지 않으면 <em>B는 A에 완전하게 함수적으로 종속한다</em>고 한다.</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/d81e8d47-fde5-4835-8979-0686a50071b2/image.png" alt=""></p>
<blockquote>
<p>이미지 참조) <a href="http://contents2.kocw.or.kr/KOCW/document/2016/shinhan/leeukhyun/11.pdf">http://contents2.kocw.or.kr/KOCW/document/2016/shinhan/leeukhyun/11.pdf</a></p>
</blockquote>
<ul>
<li>완전 함수적 종속성 $\Rightarrow$ 키의 최소성 / 키를 구성하는 모든 필드가 있어야 설명이 가능하다.</li>
<li>부분 함수적 종속성 $\Rightarrow$ (복합)키의 일부분으로도 설명할 수 있는 필드가 존재한다.</li>
</ul>
<h3 id="이행적-함수적-종속성transitive-fd">이행적 함수적 종속성(transitive FD)</h3>
<ul>
<li>테이블 R에서 필드 A, B, C가 있을 때, C가 A에 이행적으로 종속한다. ($A \rightarrow C$)
$\Leftrightarrow$ $A\rightarrow B \land B\rightarrow C$</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/308cf0e3-89fe-469a-9aef-2c4d186c9019/image.png" alt=""></p>
<blockquote>
<p>이미지 참조) <a href="http://contents2.kocw.or.kr/KOCW/document/2016/shinhan/leeukhyun/11.pdf">http://contents2.kocw.or.kr/KOCW/document/2016/shinhan/leeukhyun/11.pdf</a></p>
</blockquote>
<ul>
<li>학번을 알면 학과이름과 학과전화번호를 알 수 있고, 학과이름을 알면 학과전화번호를 알 수 있기 때문에 학과전화번호는 학번에 이행적으로 종속한다.</li>
</ul>
<h2 id="제1정규형-제2정규형-제3정규형-bcnf">제1정규형, 제2정규형, 제3정규형, BCNF</h2>
<h3 id="제1정규형">제1정규형</h3>
<ul>
<li>Def) 테이블 R이 제1정규형을 만족한다 $\Leftrightarrow$ 테이블 R의 모든 속성이 <strong>원자값</strong>만을 갖는다.</li>
<li>모든 속성에 반복 그룹(repeating group)이 나타나지 않으면 제1정규형을 만족한다.<ul>
<li>repeating group : 한 개의 기본 키값에 대해 두 개 이상의 값을 가질 수 있는 애트리뷰트 (아래 테이블에서 <em>과목번호</em>는 반복 그룹이므로 제1정규형을 만족하지 못한다.)</li>
</ul>
</li>
<li>객체 지향 데이터베이스에서는 제1정규형을 완화해 필드 값으로 집합, 리스트, 배열 등을 가질 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th align="center">학번</th>
<th align="center">이름</th>
<th align="center">과목번호</th>
</tr>
</thead>
<tbody><tr>
<td align="center">11302</td>
<td align="center">김창선</td>
<td align="center">{CS302, CS203}</td>
</tr>
<tr>
<td align="center">20511</td>
<td align="center">박준서</td>
<td align="center">{CS312, CS203}</td>
</tr>
<tr>
<td align="center">* 해결 방법</td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">1. 반복 그룹 필드를 분리해서 새로 저장</td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<pre><code>|학번|이름|과목번호|
|:-:|:-:|:-:|
|11302|김창선|CS302|
|11302|김창선|CS203|
|20511|박준서|CS312|
|20511|박준서|CS203|</code></pre><ol start="2">
<li><p>반복 그룹 필드를 분리해서 새로운 테이블에 저장</p>
<table>
<thead>
<tr>
<th align="center">학번</th>
<th align="center">이름</th>
</tr>
</thead>
<tbody><tr>
<td align="center">11302</td>
<td align="center">김창선</td>
</tr>
<tr>
<td align="center">20511</td>
<td align="center">박준서</td>
</tr>
<tr>
<td align="center">---</td>
<td align="center"></td>
</tr>
<tr>
<td align="center">학번</td>
<td align="center">과목번호</td>
</tr>
<tr>
<td align="center">:-:</td>
<td align="center">:-:</td>
</tr>
<tr>
<td align="center">11302</td>
<td align="center">CS302</td>
</tr>
<tr>
<td align="center">11302</td>
<td align="center">CS203</td>
</tr>
<tr>
<td align="center">20511</td>
<td align="center">CS312</td>
</tr>
<tr>
<td align="center">20511</td>
<td align="center">CS203</td>
</tr>
</tbody></table>
</li>
</ol>
<h3 id="제2정규형">제2정규형</h3>
<ul>
<li>Def) 테이블 R이 제2정규형을 만족한다 $\Leftrightarrow$ 테이블 R이 제1정규형을 만족하면서, 어떤 후보 키에도 속하지 않는 모든 속성들이 R의 기본 키에 완전하게 함수적으로 종속하는 것</li>
<li>기본 키가 두 개 이상의 필드로 구성되었을 경우에만 제1정규형이 제2정규형을 만족하는지 고려할 필요가 있음</li>
</ul>
<h3 id="제3정규형">제3정규형</h3>
<ul>
<li>Def) 테이블 R이 제3정규형을 만족한다. $\Leftrightarrow$ 테이블 R이 제2정규형을 만족하면서, 키가 아닌 모든 필드가 테이블 R의 기본 키에 이행적으로 종속하지 않는 것</li>
</ul>
<h3 id="bcnf-boyce-codd-normal-form">BCNF (Boyce-Codd normal form)</h3>
<ul>
<li>Def) 테이블 R이 BCNF를 만족한다. $\Leftrightarrow$ 테이블 R이 제3정규형을 만족하고, <strong>모든 결정자가 후보 키</strong>이어야 한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 거리두기 확인하기]]></title>
            <link>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B1%B0%EB%A6%AC%EB%91%90%EA%B8%B0-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B1%B0%EB%A6%AC%EB%91%90%EA%B8%B0-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 14 Jul 2021 10:52:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/81302">https://programmers.co.kr/learn/courses/30/lessons/81302</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>개발자를 희망하는 죠르디가 카카오에 면접을 보러 왔습니다.</p>
<p>코로나 바이러스 감염 예방을 위해 응시자들은 거리를 둬서 대기를 해야하는데 개발 직군 면접인 만큼
아래와 같은 규칙으로 대기실에 거리를 두고 앉도록 안내하고 있습니다.</p>
<blockquote>
<ol>
<li>대기실은 5개이며, 각 대기실은 5x5 크기입니다.</li>
<li>거리두기를 위하여 응시자들 끼리는 맨해튼 거리1가 2 이하로 앉지 말아 주세요.</li>
<li>단 응시자가 앉아있는 자리 사이가 파티션으로 막혀 있을 경우에는 허용합니다.</li>
</ol>
</blockquote>
<p>예를 들어,
<img src="https://images.velog.io/images/choi-yh/post/0f966e61-5452-4163-8fef-cfce069b127b/image.png" alt=""></p>
<p>5개의 대기실을 본 죠르디는 각 대기실에서 응시자들이 거리두기를 잘 기키고 있는지 알고 싶어졌습니다. 자리에 앉아있는 응시자들의 정보와 대기실 구조를 대기실별로 담은 2차원 문자열 배열 places가 매개변수로 주어집니다. 각 대기실별로 거리두기를 지키고 있으면 1을, 한 명이라도 지키지 않고 있으면 0을 배열에 담아 return 하도록 solution 함수를 완성해 주세요.</p>
<hr>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>places의 행 길이(대기실 개수) = 5<ul>
<li>places의 각 행은 하나의 대기실 구조를 나타냅니다.</li>
</ul>
</li>
<li>places의 열 길이(대기실 세로 길이) = 5</li>
<li>places의 원소는 P,O,X로 이루어진 문자열입니다.<ul>
<li>places 원소의 길이(대기실 가로 길이) = 5</li>
<li>P는 응시자가 앉아있는 자리를 의미합니다.</li>
<li>O는 빈 테이블을 의미합니다.</li>
<li>X는 파티션을 의미합니다.</li>
</ul>
</li>
<li>입력으로 주어지는 5개 대기실의 크기는 모두 5x5 입니다.</li>
<li>return 값 형식<ul>
<li>1차원 정수 배열에 5개의 원소를 담아서 return 합니다.</li>
<li>places에 담겨 있는 5개 대기실의 순서대로, 거리두기 준수 여부를 차례대로 배열에 담습니다.</li>
<li>각 대기실 별로 모든 응시자가 거리두기를 지키고 있으면 1을, 한 명이라도 지키지 않고 있으면 0을 담습니다.</li>
</ul>
</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>places</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>[[&quot;POOOP&quot;, &quot;OXXOX&quot;, &quot;OPXPX&quot;, &quot;OOXOX&quot;, &quot;POXXP&quot;], [&quot;POOPX&quot;, &quot;OXPXP&quot;, &quot;PXXXO&quot;, &quot;OXXXO&quot;, &quot;OOOPP&quot;], [&quot;PXOPX&quot;, &quot;OXOXP&quot;, &quot;OXPOX&quot;, &quot;OXXOP&quot;, &quot;PXPOX&quot;], [&quot;OOOXX&quot;, &quot;XOOOX&quot;, &quot;OOOXX&quot;, &quot;OXOOX&quot;, &quot;OOOOO&quot;], [&quot;PXPXP&quot;, &quot;XPXPX&quot;, &quot;PXPXP&quot;, &quot;XPXPX&quot;, &quot;PXPXP&quot;]]</td>
<td>[1, 0, 1, 1, 1]</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p>입출력 예 #1</p>
<p><strong>첫 번째 대기실</strong></p>
<p>No.    0    1    2    3    4
0    P    O    O    O    P
1    O    X    X    O    X
2    O    P    X    P    X
3    O    O    X    O    X
4    P    O    X    X    P</p>
<ul>
<li>모든 응시자가 거리두기를 지키고 있습니다.</li>
</ul>
<p><strong>두 번째 대기실</strong></p>
<p>No.    0    1    2    3    4
0    P    O    O    P    X
1    O    X    P    X    P
2    P    X    X    X    O
3    O    X    X    X    O
4    O    O    O    P    P</p>
<ul>
<li>(0, 0) 자리의 응시자와 (2, 0) 자리의 응시자가 거리두기를 지키고 있지 않습니다.</li>
<li>(1, 2) 자리의 응시자와 (0, 3) 자리의 응시자가 거리두기를 지키고 있지 않습니다.</li>
<li>(4, 3) 자리의 응시자와 (4, 4) 자리의 응시자가 거리두기를 지키고 있지 않습니다.</li>
</ul>
<p><strong>세 번째 대기실</strong></p>
<p>No.    0    1    2    3    4
0    P    X    O    P    X
1    O    X    O    X    P
2    O    X    P    O    X
3    O    X    X    O    P
4    P    X    P    O    X</p>
<ul>
<li>모든 응시자가 거리두기를 지키고 있습니다.</li>
</ul>
<p><strong>네 번째 대기실</strong></p>
<p>No.    0    1    2    3    4
0    O    O    O    X    X
1    X    O    O    O    X
2    O    O    O    X    X
3    O    X    O    O    X
4    O    O    O    O    O</p>
<ul>
<li>대기실에 응시자가 없으므로 거리두기를 지키고 있습니다.</li>
</ul>
<p><strong>다섯 번째 대기실</strong></p>
<p>No.    0    1    2    3    4
0    P    X    P    X    P
1    X    P    X    P    X
2    P    X    P    X    P
3    X    P    X    P    X
4    P    X    P    X    P</p>
<ul>
<li>모든 응시자가 거리두기를 지키고 있습니다.</li>
</ul>
<p>두 번째 대기실을 제외한 모든 대기실에서 거리두기가 지켜지고 있으므로, 배열 [1, 0, 1, 1, 1]을 return 합니다.</p>
<p>제한시간 안내</p>
<ul>
<li>정확성 테스트 : 10초</li>
</ul>
<h2 id="풀이">풀이</h2>
<pre><code>def check_room(room):
    dx = [0, 0, 1, -1]
    dy = [-1, 1, 0, 0]
    # 모든 자리 확인
    for i in range(5):
        for j in range(5):
            if room[i][j] == &#39;P&#39;:
                stack = []
                visit = []
                stack.append([i, j])
                while stack:
                    cur_pos = stack.pop()
                    x, y = cur_pos[0], cur_pos[1]
                    visit.append([x, y])
                    for k in range(4):
                        nx = x + dx[k]
                        ny = y + dy[k]
                        if (nx &gt;= 0 and nx &lt; 5 and ny &gt;= 0 and ny &lt; 5) and room[nx][ny] != &#39;X&#39; and [nx, ny] not in visit and abs(i - nx) + abs(j - ny) &lt;= 2:
                            if room[nx][ny] == &#39;P&#39;:
                                return False
                            else:
                                stack.append([nx, ny])
    return True

def solution(places):
    answer = []
    for room in places:
        room = [list(people for people in row) for row in room]
        if check_room(room):
            answer.append(1)
        else:
            answer.append(0)

    return answer</code></pre><ul>
<li>그래프 문제고, 맨하탄 거리가 2 이하인 자리를 탐색하면 되기 때문에 BFS를 먼저 떠올리고 queue를 활용해서 구현해봤지만... DFS 문제기 때문에 다시 stack으로 구현했다.</li>
<li>입력받은 방을 그래프로 변환해서 DFS 체크하는 방식으로 구현</li>
<li>모든 자리를 확인하면서 참가자가 있는 자리(&quot;P&quot;)일 때, DFS 체크를 한다.</li>
<li>그래프를 벗어나지 않거나, 벽이 없거나 (&quot;P&quot; or &quot;O&quot;), 방문하지 않은 좌표, 출발 좌표와 맨하탄 거리가 2인 좌표에 대해서 확인</li>
<li>이 경우에서 다른 참가자가 들어올 경우, 맨하탄 거리가 2인 자리에 파티션이 없는 상황이기 때문에 False를 return</li>
<li>모든 자리에 대해서 확인하고 이상이 없을 경우 True</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Elasticsearch] Ubuntu 20.04에 Elasticsearch 6.5 설치하기]]></title>
            <link>https://velog.io/@choi-yh/Elasticsearch-Ubuntu-20.04%EC%97%90-Elasticsearch-6.5-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choi-yh/Elasticsearch-Ubuntu-20.04%EC%97%90-Elasticsearch-6.5-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 08 Jul 2021 01:22:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.5/deb.html">https://www.elastic.co/guide/en/elasticsearch/reference/6.5/deb.html</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://www.elastic.co/guide/en/logstash/6.5/installing-logstash.html">https://www.elastic.co/guide/en/logstash/6.5/installing-logstash.html</a></li>
</ul>
<h2 id="jdk-설치">jdk 설치</h2>
<pre><code>sudo apt install openjdk-8-jdk</code></pre><p><img src="https://images.velog.io/images/choi-yh/post/2d2effdb-f938-4694-9a4c-4476623828c4/image.png" alt=""></p>
<h2 id="elasticsearch-설치">Elasticsearch 설치</h2>
<h3 id="import-the-elasticsearch-pgp-key">Import the Elasticsearch PGP Key</h3>
<pre><code>wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -</code></pre><h3 id="installing-from-the-apt-repository">Installing from the APT repository</h3>
<ul>
<li>Ubuntu apt 저장소 등록 후 설치하는 방법<pre><code>sudo apt-get install apt-transport-https
echo &quot;deb https://artifacts.elastic.co/packages/6.x/apt stable main&quot; | sudo tee -a /etc/apt/sources.list.d/elastic-6.x.list
sudo apt-get update &amp;&amp; sudo apt-get install elasticsearch</code></pre></li>
</ul>
<h3 id="download-and-install-the-debian-package-manually">Download and install the Debian package manually</h3>
<ul>
<li>패키지 직접 설치하는 방법<pre><code>wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.4.deb
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.4.deb.sha512
shasum -a 512 -c elasticsearch-6.5.4.deb.sha512 
sudo dpkg -i elasticsearch-6.5.4.deb</code></pre></li>
</ul>
<h2 id="logstash">Logstash</h2>
<h3 id="apt">APT</h3>
<pre><code># 위에서 받음
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add - 
sudo apt-get install apt-transport-https

echo &quot;deb https://artifacts.elastic.co/packages/6.x/apt stable main&quot; | sudo tee -a /etc/apt/sources.list.d/elastic-6.x.list
sudo apt-get update &amp;&amp; sudo apt-get install logstash</code></pre><h2 id="실행">실행</h2>
<pre><code>sudo service elasticsearch start
sudo service logstash start</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Article] Mean Average Precision (mAP) for Recommendation System]]></title>
            <link>https://velog.io/@choi-yh/Mean-Average-Precision-mAP-for-Recommendation-System</link>
            <guid>https://velog.io/@choi-yh/Mean-Average-Precision-mAP-for-Recommendation-System</guid>
            <pubDate>Mon, 05 Jul 2021 08:36:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="http://sdsawtelle.github.io/blog/output/mean-average-precision-MAP-for-recommender-systems.html?source=post_page-----df56c6611093----------------------#Precision-and-Recall-of-Recommender-Systems">http://sdsawtelle.github.io/blog/output/mean-average-precision-MAP-for-recommender-systems.html?source=post_page-----df56c6611093----------------------#Precision-and-Recall-of-Recommender-Systems</a></p>
</blockquote>
<h2 id="mapmean-average-precision"><strong>MAP(Mean Average Precision)</strong></h2>
<ul>
<li><strong>정보 검색</strong> 수행 알고리즘에서 가장 많이 사용하는 평가 지표</li>
<li>알고리즘이 item의 순위 순서를 반환할 경우, 순위 또한 맞춰야 한다. 
(추천을 순위 지정 작업처럼 취급 - <strong>순위</strong>까지 예측)</li>
<li><strong>사용자 추천 시스템</strong>에도 사용이 가능 
(ex. 아마존 - 어떤 아이템을 장바구니 추가 후 추가로 구매하고 싶을 품목을 보여주는 기능)</li>
</ul>
<h3 id="precision-and-recall">Precision and Recall</h3>
<ul>
<li><p>$Precision = \frac {\text{# of correct positve}} {\text{# of predicted positive}}$ </p>
<ul>
<li>맞춘 실제 정답 수 / 정답이라고 예측한 수</li>
<li>1 - <em>false positive rate</em></li>
</ul>
</li>
<li><p>$Recall = \frac{\text{# of correct positve}} {\text{# of with condition}}$ </p>
<ul>
<li>맞춘 실제 정답 수 / 실제 정답 수</li>
<li>1 - <em>false negative rate</em></li>
</ul>
</li>
</ul>
<h3 id="precision-and-recall-of-recommender-systems">Precision and Recall of Recommender Systems</h3>
<ul>
<li>용어 정리</li>
</ul>
<table>
<thead>
<tr>
<th><strong>Terminology in Binary Classifier</strong></th>
<th><strong>Terminology in Recommender System</strong></th>
</tr>
</thead>
<tbody><tr>
<td># with condition</td>
<td># of all the possible relevant(&quot;correct&quot;) items for a user</td>
</tr>
<tr>
<td># predicted positive</td>
<td># of items we recommended (we predict these items as &quot;relevant&quot;</td>
</tr>
<tr>
<td># correct positives</td>
<td># of our recommendations that are relevant</td>
</tr>
</tbody></table>
<ul>
<li><p>recommender system precision: </p>
<ul>
<li>$P = \frac{\text{# of our recommendations that are relevant}}{\text{# of items we recommended}}$ </li>
<li>실제로 relevant하게 추천된 아이템 수 / 추천한 아이템 수</li>
</ul>
</li>
<li><p>recommender system recall: </p>
<ul>
<li>$r = \frac{\text{# of our recommendations that are relevant}} {\text{# of all the possible relevant items}}$ </li>
<li>실제로 relevant하게 추천된 아이템 수 / 사용자가 원하는 아이템 수(wishList? / 모든 추천 조합 수?)</li>
</ul>
</li>
</ul>
<h3 id="precision-and-recall-at-cutoff-k">Precision and Recall at Cutoff k</h3>
<ul>
<li>$P(k), r(k)$ 계산시에 N개의 추천 중 $k$까지 잘라서 계산한다.</li>
</ul>
<h3 id="average-precision">Average Precision</h3>
<ul>
<li><p>N: 추천 시스템이 추천하는 아이템 수</p>
</li>
<li><p>m: 사용자가 원하는 아이템 수</p>
</li>
<li><p>$AP@N$ 
  $= \frac{1}{m} \sum_{k=1}^N (P(k) \text{ if } k^{th} \text{ item was relevant })$ 
  $= \frac{1}{m} \sum_{k=1}^N P(k) \cdot rel(k)$, 
  $rel(k) = 1 \text{ (or not) } rel(k) = 0$
  (<em>relevant</em>. 옳은 항목에 대해서만 계산)</p>
</li>
<li><p>$k$까지의 $P(k)$를 구함 (순위가 높을수록(먼저 나올수록) 값이 높게 측정)</p>
</li>
<li><p>정답이 아닌 추천을 하더라도 AP 스코어 자체가 감소하지는 않는다. (하위에 둘 경우, k까지만 자르면 됨)</p>
</li>
</ul>
<h2 id="the-mean-in-map">The &quot;Mean&quot; in MAP</h2>
<ul>
<li>남은 모든 item($|U|$)에 대한 평균 계산</li>
<li>$MAP@N = \frac{1}{|U|} \sum_{u=1}^| U|(AP@N)<em>u = \frac{1}{|U|} \sum</em>{u=1}^| U|\frac{1}{m} \sum_{k=1}^N P_u(k) \cdot rel_u(k)$</li>
</ul>
<h3 id="common-variations-on-ap-formula">Common Variations on AP Formula</h3>
<ul>
<li><p>N=5이지만, 사용자가 m=10을 원할 경우, 일반적으로 $min(m, N)$ 으로 정규화</p>
</li>
<li><p>$AP@N = \frac{1}{min(m, N)} \sum_{k=1}^N P(k) \cdot rel(k)$</p>
</li>
<li><p>최종 스코어 ( $rel(k) = 0$ 인 경우 계산을 생략한다. )</p>
<ul>
<li>$AP@N = \frac{1}{min(m, N)} \sum_{k=1}^N P(k) \cdot rel(k)$ $\qquad \text{ if } m \ne 0$
$AP = 0$ $\qquad \text{ if } m = 0$</li>
</ul>
</li>
</ul>
<h2 id="recall-추가">Recall 추가</h2>
<p>$AP@N = \sum_{k=1}^N(\text{precision at } k) \cdot (\text{change in recall at } k) = \sum_{k=1}^N P(k) \Delta r(k)$</p>
<p>$\Delta r(k) = (k-1^{th}) \text{ to } k^{th}$</p>
<h1 id="references">References</h1>
<ul>
<li><a href="http://sdsawtelle.github.io/blog/output/mean-average-precision-MAP-for-recommender-systems.html?source=post_page-----df56c6611093----------------------#Precision-and-Recall-of-Recommender-Systems">http://sdsawtelle.github.io/blog/output/mean-average-precision-MAP-for-recommender-systems.html?source=post_page-----df56c6611093----------------------#Precision-and-Recall-of-Recommender-Systems</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 주식가격]]></title>
            <link>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A3%BC%EC%8B%9D%EA%B0%80%EA%B2%A9</link>
            <guid>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A3%BC%EC%8B%9D%EA%B0%80%EA%B2%A9</guid>
            <pubDate>Thu, 01 Jul 2021 15:17:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/42584#">https://programmers.co.kr/learn/courses/30/lessons/42584#</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>초 단위로 기록된 주식가격이 담긴 배열 prices가 매개변수로 주어질 때, 가격이 떨어지지 않은 기간은 몇 초인지를 return 하도록 solution 함수를 완성하세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>prices의 각 가격은 1 이상 10,000 이하인 자연수입니다.</li>
<li>prices의 길이는 2 이상 100,000 이하입니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>prices</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>[1, 2, 3, 2, 3]</td>
<td>[4, 3, 1, 1, 0]</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<ul>
<li>1초 시점의 ₩1은 끝까지 가격이 떨어지지 않았습니다.</li>
<li>2초 시점의 ₩2은 끝까지 가격이 떨어지지 않았습니다.</li>
<li>3초 시점의 ₩3은 1초뒤에 가격이 떨어집니다. 따라서 1초간 가격이 떨어지지 않은 것으로 봅니다.</li>
<li>4초 시점의 ₩2은 1초간 가격이 떨어지지 않았습니다.</li>
<li>5초 시점의 ₩3은 0초간 가격이 떨어지지 않았습니다.</li>
</ul>
<h2 id="풀이">풀이</h2>
<pre><code>def solution(prices):
    answer = [0] * len(prices)

    for i in range(len(prices)):
        cur_price = prices[i]
        for j in range(i+1, len(prices)):
            if cur_price &lt;= prices[j]:
                answer[i] += 1
            else:
                answer[i] += 1
                break        

    return answer</code></pre><ul>
<li>현재 주식 가격과 이후 가격을 비교해 더 작은 값이 언제 나오는지 count 한다.</li>
</ul>
<h3 id="다른-풀이">다른 풀이</h3>
<ul>
<li><code>prices</code>를 큐에 삽입한 뒤에 <em>pop</em> 하면서 뒤의 가격과 비교한다.</li>
<li>여전히 $O(n^2)$이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 기능개발]]></title>
            <link>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B8%B0%EB%8A%A5%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B8%B0%EB%8A%A5%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Wed, 30 Jun 2021 15:07:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/42586">https://programmers.co.kr/learn/courses/30/lessons/42586</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>프로그래머스 팀에서는 기능 개선 작업을 수행 중입니다. 각 기능은 진도가 100%일 때 서비스에 반영할 수 있습니다.</p>
<p>또, 각 기능의 개발속도는 모두 다르기 때문에 뒤에 있는 기능이 앞에 있는 기능보다 먼저 개발될 수 있고, 이때 뒤에 있는 기능은 앞에 있는 기능이 배포될 때 함께 배포됩니다.</p>
<p>먼저 배포되어야 하는 순서대로 작업의 진도가 적힌 정수 배열 progresses와 각 작업의 개발 속도가 적힌 정수 배열 speeds가 주어질 때 각 배포마다 몇 개의 기능이 배포되는지를 return 하도록 solution 함수를 완성하세요.</p>
<h3 id="제한-사항">제한 사항</h3>
<ul>
<li>작업의 개수(progresses, speeds배열의 길이)는 100개 이하입니다.</li>
<li>작업 진도는 100 미만의 자연수입니다.</li>
<li>작업 속도는 100 이하의 자연수입니다.</li>
<li>배포는 하루에 한 번만 할 수 있으며, 하루의 끝에 이루어진다고 가정합니다. 예를 들어 진도율이 95%인 작업의 개발 속도가 하루에 4%라면 배포는 2일 뒤에 이루어집니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>progresses</th>
<th>speeds</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>[93, 30, 55]</td>
<td>[1, 30, 5]</td>
<td>[2, 1]</td>
</tr>
<tr>
<td>[95, 90, 99, 99, 80, 99]</td>
<td>[1, 1, 1, 1, 1, 1]</td>
<td>[1, 3, 2]</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p>입출력 예 #1
첫 번째 기능은 93% 완료되어 있고 하루에 1%씩 작업이 가능하므로 7일간 작업 후 배포가 가능합니다.
두 번째 기능은 30%가 완료되어 있고 하루에 30%씩 작업이 가능하므로 3일간 작업 후 배포가 가능합니다. 하지만 이전 첫 번째 기능이 아직 완성된 상태가 아니기 때문에 첫 번째 기능이 배포되는 7일째 배포됩니다.
세 번째 기능은 55%가 완료되어 있고 하루에 5%씩 작업이 가능하므로 9일간 작업 후 배포가 가능합니다.</p>
<p>따라서 7일째에 2개의 기능, 9일째에 1개의 기능이 배포됩니다.</p>
<p>입출력 예 #2
모든 기능이 하루에 1%씩 작업이 가능하므로, 작업이 끝나기까지 남은 일수는 각각 5일, 10일, 1일, 1일, 20일, 1일입니다. 어떤 기능이 먼저 완성되었더라도 앞에 있는 모든 기능이 완성되지 않으면 배포가 불가능합니다.</p>
<p>따라서 5일째에 1개의 기능, 10일째에 3개의 기능, 20일째에 2개의 기능이 배포됩니다.</p>
<p>※ 공지 - 2020년 7월 14일 테스트케이스가 추가되었습니다.</p>
<h2 id="풀이">풀이</h2>
<pre><code>def solution(progresses, speeds):
    &quot;&quot;&quot;
    배포할 기능이 담길 스택을 만들고, 개발 일수를 스택에 push,
    스택의 첫 원소보다 작은 개발 일자들은 첫 개발 일자에 같이 배포됨
    &quot;&quot;&quot;
    answer = []
    stack = []
    deploy_days = 0

    for i in range(len(progresses)):
        if (100 - progresses[i]) % speeds[i] == 0:
            dev_days = (100 - progresses[i]) // speeds[i]
        else:
            dev_days = (100 - progresses[i]) // speeds[i] + 1

        print(dev_days)

        if dev_days &lt;= deploy_days:
            stack.append(dev_days)
        else:
            answer.append(len(stack))
            stack = [dev_days] # 스택 초기화
            deploy_days = dev_days

    answer.append(len(stack))
    return answer[1:]</code></pre><ul>
<li>기능별로 개발 일자를 구하고, 앞에 있는 기능보다 개발 일자가 빠르면 앞에 있는 기능에 맞춰서 배포하게 된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 프린터]]></title>
            <link>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%94%84%EB%A6%B0%ED%84%B0</link>
            <guid>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%94%84%EB%A6%B0%ED%84%B0</guid>
            <pubDate>Wed, 30 Jun 2021 11:41:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/42587">https://programmers.co.kr/learn/courses/30/lessons/42587</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>일반적인 프린터는 인쇄 요청이 들어온 순서대로 인쇄합니다. 그렇기 때문에 중요한 문서가 나중에 인쇄될 수 있습니다. 이런 문제를 보완하기 위해 중요도가 높은 문서를 먼저 인쇄하는 프린터를 개발했습니다. 이 새롭게 개발한 프린터는 아래와 같은 방식으로 인쇄 작업을 수행합니다.</p>
<pre><code>1. 인쇄 대기목록의 가장 앞에 있는 문서(J)를 대기목록에서 꺼냅니다.
2. 나머지 인쇄 대기목록에서 J보다 중요도가 높은 문서가 한 개라도 존재하면 J를 대기목록의 가장 마지막에 넣습니다.
3. 그렇지 않으면 J를 인쇄합니다.
예를 들어, 4개의 문서(A, B, C, D)가 순서대로 인쇄 대기목록에 있고 중요도가 2 1 3 2 라면 C D A B 순으로 인쇄하게 됩니다.</code></pre><p>내가 인쇄를 요청한 문서가 몇 번째로 인쇄되는지 알고 싶습니다. 위의 예에서 C는 1번째로, A는 3번째로 인쇄됩니다.</p>
<p>현재 대기목록에 있는 문서의 중요도가 순서대로 담긴 배열 priorities와 내가 인쇄를 요청한 문서가 현재 대기목록의 어떤 위치에 있는지를 알려주는 location이 매개변수로 주어질 때, 내가 인쇄를 요청한 문서가 몇 번째로 인쇄되는지 return 하도록 solution 함수를 작성해주세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>현재 대기목록에는 1개 이상 100개 이하의 문서가 있습니다.</li>
<li>인쇄 작업의 중요도는 1~9로 표현하며 숫자가 클수록 중요하다는 뜻입니다.</li>
<li>location은 0 이상 (현재 대기목록에 있는 작업 수 - 1) 이하의 값을 가지며 대기목록의 가장 앞에 있으면 0, 두 번째에 있으면 1로 표현합니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>priorities</th>
<th>location</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>[2, 1, 3, 2]</td>
<td>2</td>
<td>1</td>
</tr>
<tr>
<td>[1, 1, 9, 1, 1, 1]</td>
<td>0</td>
<td>5</td>
</tr>
</tbody></table>
<h2 id="풀이">풀이</h2>
<pre><code>def solution(priorities, location):
    index_list = [i for i in range(len(priorities))]
    answer_list = [i for i in range(len(priorities))]

    cnt = 1
    while priorities:
        maxx = max(priorities)
        cur_pri = priorities.pop(0)
        cur_index = index_list.pop(0)
        if cur_pri &gt;= maxx:
            answer_list[cur_index] = cnt
            cnt += 1
        else:
            priorities.append(cur_pri)
            index_list.append(cur_index)

    answer = answer_list[location]
    return answer</code></pre><ul>
<li><em>priorities</em>의 인덱스 별로 지정해주고, 해당 인덱스의 출력 순서를 리스트에 저장</li>
<li>현재 문서의 중요도와 최대 중요도를 비교해서 출력을 할 것 인지, 맨 뒤로 보낼 것인지 확인</li>
<li>정답 리스트에서 결과 출력</li>
</ul>
<h3 id="다른-풀이">다른 풀이</h3>
<ul>
<li>큐에 문서 번호와 우선 순위를 저장</li>
<li>loop 돌면서 뒤의 우선순위를 비교</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 실패율]]></title>
            <link>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8B%A4%ED%8C%A8%EC%9C%A8</link>
            <guid>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8B%A4%ED%8C%A8%EC%9C%A8</guid>
            <pubDate>Fri, 25 Jun 2021 10:43:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>2019 KAKAO BLIND RECRUITMENT
<a href="https://programmers.co.kr/learn/courses/30/lessons/42889">https://programmers.co.kr/learn/courses/30/lessons/42889</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>실패율
<img src="https://images.velog.io/images/choi-yh/post/34f1c7ce-f69e-42ed-8ced-b1692c337f79/image.png" alt=""></p>
<p>슈퍼 게임 개발자 오렐리는 큰 고민에 빠졌다. 그녀가 만든 프랜즈 오천성이 대성공을 거뒀지만, 요즘 신규 사용자의 수가 급감한 것이다. 원인은 신규 사용자와 기존 사용자 사이에 스테이지 차이가 너무 큰 것이 문제였다.</p>
<p>이 문제를 어떻게 할까 고민 한 그녀는 동적으로 게임 시간을 늘려서 난이도를 조절하기로 했다. 역시 슈퍼 개발자라 대부분의 로직은 쉽게 구현했지만, 실패율을 구하는 부분에서 위기에 빠지고 말았다. 오렐리를 위해 실패율을 구하는 코드를 완성하라.</p>
<ul>
<li>실패율은 다음과 같이 정의한다.<ul>
<li>스테이지에 도달했으나 아직 클리어하지 못한 플레이어의 수 / 스테이지에 도달한 플레이어 수</li>
</ul>
</li>
</ul>
<p>전체 스테이지의 개수 N, 게임을 이용하는 사용자가 현재 멈춰있는 스테이지의 번호가 담긴 배열 stages가 매개변수로 주어질 때, 실패율이 높은 스테이지부터 내림차순으로 스테이지의 번호가 담겨있는 배열을 return 하도록 solution 함수를 완성하라.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>스테이지의 개수 N은 <code>1</code> 이상 <code>500</code> 이하의 자연수이다.</li>
<li>stages의 길이는 <code>1</code> 이상 <code>200,000</code> 이하이다.</li>
<li>stages에는 <code>1</code> 이상 <code>N + 1</code> 이하의 자연수가 담겨있다.<ul>
<li>각 자연수는 사용자가 현재 도전 중인 스테이지의 번호를 나타낸다.</li>
<li>단, <code>N + 1</code> 은 마지막 스테이지(N 번째 스테이지) 까지 클리어 한 사용자를 나타낸다.</li>
</ul>
</li>
<li>만약 실패율이 같은 스테이지가 있다면 작은 번호의 스테이지가 먼저 오도록 하면 된다.</li>
<li>스테이지에 도달한 유저가 없는 경우 해당 스테이지의 실패율은 <code>0</code> 으로 정의한다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>N</th>
<th>stages</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>5</td>
<td>[2, 1, 2, 6, 2, 4, 3, 3]</td>
<td>[3,4,2,1,5]</td>
</tr>
<tr>
<td>4</td>
<td>[4,4,4,4,4]</td>
<td>[4,1,2,3]</td>
</tr>
</tbody></table>
<p><strong>입출력 예 설명</strong>
입출력 예 #1
1번 스테이지에는 총 8명의 사용자가 도전했으며, 이 중 1명의 사용자가 아직 클리어하지 못했다. 따라서 1번 스테이지의 실패율은 다음과 같다.</p>
<ul>
<li><p>1 번 스테이지 실패율 : 1/8
2번 스테이지에는 총 7명의 사용자가 도전했으며, 이 중 3명의 사용자가 아직 클리어하지 못했다. 따라서 2번 스테이지의 실패율은 다음과 같다.</p>
</li>
<li><p>2 번 스테이지 실패율 : 3/7
마찬가지로 나머지 스테이지의 실패율은 다음과 같다.</p>
</li>
<li><p>3 번 스테이지 실패율 : 2/4</p>
</li>
<li><p>4번 스테이지 실패율 : 1/2</p>
</li>
<li><p>5번 스테이지 실패율 : 0/1</p>
</li>
</ul>
<p>각 스테이지의 번호를 실패율의 내림차순으로 정렬하면 다음과 같다.</p>
<ul>
<li>[3,4,2,1,5]</li>
</ul>
<p>입출력 예 #2</p>
<p>모든 사용자가 마지막 스테이지에 있으므로 4번 스테이지의 실패율은 1이며 나머지 스테이지의 실패율은 0이다.</p>
<ul>
<li>[4,1,2,3]</li>
</ul>
<h2 id="풀이">풀이</h2>
<pre><code>def solution(N, stages):
    &quot;&quot;&quot;
    현재 스테이지의 유저 수 (해당 스테이지를 클리어하지 못한 유저 수)
    해당 스테이지를 클리어한 유저 수 -&gt; 현재 스테이지 이후의 유저 수 합
    &quot;&quot;&quot;
    answer = []
    num_users = len(stages) # 전체 유저 수

    stage_users = [stages.count(i) for i in range(1, N+2)] # 현재 스테이지의 유저 수
    challenge_users = [sum(stage_users[i:]) for i in range(N+1)] # 해당 스테이지를 도전한 유저 수
    challenge_users.pop() # 최종 스테이지까지 클리어한 유저 수 제거

    fail_ratio = [(i+1, stage_users[i] / challenge_users[i]) if challenge_users[i] != 0 else (i+1, 0) for i in range(N)]

    # 실패율 기준으로 정렬
    answer = [i[0] for i in sorted(fail_ratio, key=lambda x: x[1], reverse=True)]

    return answer</code></pre><ul>
<li><code>stages</code> 리스트에 있는 수가 현재 클리어하지 못한 스테이지 위치</li>
<li>스테이지 리스트를 생성하고, 현재 스테이지에 위치한 유저 수를 구함 (아직 클리어하지 못한 유저들)</li>
<li>현재 스테이지 이후 유저 수의 합이 해당 스테이지를 도전한 유저 수</li>
<li>현재 스테이지와 실패율을 계산</li>
<li><code>sorted()</code> 함수를 활용해서 정렬</li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li><p><code>from collections import Counter</code> 모듈을 활용해서 스테이지의 유저 수를 구하는 방법</p>
</li>
<li><p><code>sorted(list, key=lambda x: x[1], reverse=True)</code> 함수 잘 활용하기</p>
</li>
<li><p>클리어 유저, 도전 유저를 굳이 나누지 않고 한번에 계산하는 방법도 존재</p>
<pre><code>def solution(N, stages):
  &quot;&quot;&quot;
  현재 스테이지의 유저 수 (해당 스테이지를 클리어하지 못한 유저 수)
  해당 스테이지를 클리어한 유저 수 -&gt; 현재 스테이지 이후의 유저 수 합
  &quot;&quot;&quot;
  num_users = len(stages) # 전체 유저 수

  failure = []
  for i in range(1, N+1):
      fail_users = stages.count(i) # 실패한 유저 수 count
      failure.append([i, fail_users / num_users] if num_users != 0 else [i, 0]) # 스테이지와 실패율을 기록
      num_users -= fail_users # 현재 스테이지를 통과한 유저 수 (이전 스테이지 통과 - 현재 스테이지 실패)

  answer = [stage[0] for stage in sorted(failure, key=lambda x: x[1], reverse=True)]

  return answer</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[WSL2 ubuntu 설치 후 python setting]]></title>
            <link>https://velog.io/@choi-yh/WSL2-ubuntu-%EC%84%A4%EC%B9%98-%ED%9B%84-python-setting</link>
            <guid>https://velog.io/@choi-yh/WSL2-ubuntu-%EC%84%A4%EC%B9%98-%ED%9B%84-python-setting</guid>
            <pubDate>Fri, 18 Jun 2021 12:39:17 GMT</pubDate>
            <description><![CDATA[<h3 id=""></h3>
<blockquote>
<p> python3.6 설치 및 venv setting
현재 ubuntu20.04의 경우 python 기본 버전이 3.8</p>
</blockquote>
<ul>
<li><p>python3.6 설치</p>
<pre><code>sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install python3.6</code></pre></li>
<li><p>python venv 설치</p>
<pre><code>sudo apt install python3-pip # pip 설치
sudo pip install python3-venv # python3(python3.8) venv 설치
sudo pip install python3.6-venv # python3.6용 venv 설치</code></pre></li>
<li><p><code>python3.6 -m venv [venv_name]</code> </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] [카카오 인턴] 키패드 누르기]]></title>
            <link>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%9D%B8%ED%84%B4-%ED%82%A4%ED%8C%A8%EB%93%9C-%EB%88%84%EB%A5%B4%EA%B8%B0</link>
            <guid>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%9D%B8%ED%84%B4-%ED%82%A4%ED%8C%A8%EB%93%9C-%EB%88%84%EB%A5%B4%EA%B8%B0</guid>
            <pubDate>Wed, 16 Jun 2021 17:58:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/67256">https://programmers.co.kr/learn/courses/30/lessons/67256</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>스마트폰 전화 키패드의 각 칸에 다음과 같이 숫자들이 적혀 있습니다.</p>
<p><img src="https://images.velog.io/images/choi-yh/post/d54051c1-da6d-461c-b1fc-d1c918f84c18/image.png" alt=""></p>
<p>이 전화 키패드에서 왼손과 오른손의 엄지손가락만을 이용해서 숫자만을 입력하려고 합니다.
맨 처음 왼손 엄지손가락은 * 키패드에 오른손 엄지손가락은 # 키패드 위치에서 시작하며, 엄지손가락을 사용하는 규칙은 다음과 같습니다.</p>
<p>엄지손가락은 상하좌우 4가지 방향으로만 이동할 수 있으며 키패드 이동 한 칸은 거리로 1에 해당합니다.
왼쪽 열의 3개의 숫자 1, 4, 7을 입력할 때는 왼손 엄지손가락을 사용합니다.
오른쪽 열의 3개의 숫자 3, 6, 9를 입력할 때는 오른손 엄지손가락을 사용합니다.
가운데 열의 4개의 숫자 2, 5, 8, 0을 입력할 때는 두 엄지손가락의 현재 키패드의 위치에서 더 가까운 엄지손가락을 사용합니다.
4-1. 만약 두 엄지손가락의 거리가 같다면, 오른손잡이는 오른손 엄지손가락, 왼손잡이는 왼손 엄지손가락을 사용합니다.
순서대로 누를 번호가 담긴 배열 numbers, 왼손잡이인지 오른손잡이인 지를 나타내는 문자열 hand가 매개변수로 주어질 때, 각 번호를 누른 엄지손가락이 왼손인 지 오른손인 지를 나타내는 연속된 문자열 형태로 return 하도록 solution 함수를 완성해주세요.</p>
<h3 id="제한사항">[제한사항]</h3>
<p>numbers 배열의 크기는 1 이상 1,000 이하입니다.
numbers 배열 원소의 값은 0 이상 9 이하인 정수입니다.
hand는 &quot;left&quot; 또는 &quot;right&quot; 입니다.
&quot;left&quot;는 왼손잡이, &quot;right&quot;는 오른손잡이를 의미합니다.
왼손 엄지손가락을 사용한 경우는 L, 오른손 엄지손가락을 사용한 경우는 R을 순서대로 이어붙여 문자열 형태로 return 해주세요.</p>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>numbers</th>
<th>hand</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>[1, 3, 4, 5, 8, 2, 1, 4, 5, 9, 5]</td>
<td>&quot;right&quot;</td>
<td>&quot;LRLLLRLLRRL&quot;</td>
</tr>
<tr>
<td>[7, 0, 8, 2, 8, 3, 1, 5, 7, 6, 2]</td>
<td>&quot;left&quot;</td>
<td>&quot;LRLLRRLLLRR&quot;</td>
</tr>
<tr>
<td>[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]</td>
<td>&quot;right&quot;</td>
<td>&quot;LLRLLRLLRL&quot;</td>
</tr>
</tbody></table>
<h2 id="풀이">풀이</h2>
<pre><code>def solution(numbers, hand):
    answer = &#39;&#39;

    # 키패드 리스트로 저장
    keypad = [[i + j*3 for i in range(1, 4)] for j in range(3)]
    keypad.append([&#39;*&#39;, 0, &#39;#&#39;])

    # 시작 위치
    current_left = [3, 0]
    current_right = [3, 2]

    for num in numbers:
        # 눌러야 할 숫자의 좌표
        if num == 0:
            x, y = 3, 1
        else:
            x, y  = (num - 1) // 3, (num - 1) % 3

        if y == 0: # 왼손
            answer += &#39;L&#39;
            current_left = [x, y]
        elif y == 2: # 오른손
            answer += &#39;R&#39;
            current_right = [x, y]
        else:
            left_dist = abs(current_left[0] - x) + abs(current_left[1] - y)
            right_dist = abs(current_right[0] - x) + abs(current_right[1] - y)

            if left_dist &lt; right_dist:
                answer += &#39;L&#39;
                current_left = [x, y]
            elif left_dist &gt; right_dist:
                answer += &#39;R&#39;
                current_right = [x, y]
            else:
                if hand == &#39;left&#39;:
                    answer += &#39;L&#39;
                    current_left = [x, y]
                else:
                    answer += &#39;R&#39;
                    current_right = [x, y]

    return answer</code></pre><ul>
<li>처음 풀 때, <code>[2, 5, 8, 0]</code>에 대해서 거리를 모두 지정했다고 생각했지만 손가락이 해당 위치에 있는 경우를 생각하지 못했다.</li>
<li>거리 계산시에 좌표의 차이를 계산하면 됐다.</li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li>좌표를 가지고 푸는 것이 효율적이라고 생각은 했지만 입력된 숫자에 대해서 인덱스를 반환하는 것까지 구현이 익숙하지 않았다.</li>
<li>키패드를 dictionary로 저장했으면 인덱스에 대한 생각을 덜하고 풀 수 있었을 것 같다.<ul>
<li><code>keypad = {1: (0, 0), 2: (0, 1), ...}</code> 하는 식으로</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[검색엔진] 무신사 검색 추천 시스템 정리]]></title>
            <link>https://velog.io/@choi-yh/%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EB%AC%B4%EC%8B%A0%EC%82%AC-%EA%B2%80%EC%83%89-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@choi-yh/%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EB%AC%B4%EC%8B%A0%EC%82%AC-%EA%B2%80%EC%83%89-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 08 Jun 2021 12:59:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>무신사 기술 블로그를 개인적으로 공부 겸 정리했습니다.</p>
</blockquote>
<ul>
<li><p>무신사의 경우 상품 검색 시 <strong>추천순</strong>이라는 정렬 방식을 사용 중</p>
</li>
<li><p>기존 모델이 정말 고객이 원하는 상품을 잘 추천해주고 있는지에 대한 의문</p>
<p>  ex. &quot;후드 집업&quot;으로 검색을 했을 때, 랭킹 스코어가 낮은 <em>후드 집업</em>보다 스코어가 높은 <em>후드티</em>가 상위 랭크에 위치하는 경우가 발생</p>
</li>
</ul>
<h3 id="새로운-무신사-추천순-모델-제안">새로운 &quot;무신사 추천순&quot; 모델 제안</h3>
<ul>
<li><p><em>고객 피드백 결과, 특정 브랜드를 검색하는 고객에게는 해당 브랜드를, 특정 카테고리(ex. 슬리퍼)를 찾는 고객에게는 해당 카테고리를 보여준다.</em></p>
</li>
<li><p>적합도(Relevance) - 검색어와 상품 색인 필드의 단어간 확률적 연관 관계 - 개념 도입</p>
<p>  $Relevance(Qry, Doc) = \sum_{f \in Field} Imp_f(Qry) * Sim(Qry, Doc_f)$
  (필드별 중요도 * 필드별 유사도)</p>
</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/4e1f49b2-cd4a-4f0e-88a0-abd8c5eaa705/image.png" alt=""></p>
<ul>
<li><p>필드별 중요도</p>
<ul>
<li>특정 색인 필드를 얼마나 중시하는가 (ex. &quot;태그&quot; &lt; &quot;카테고리&quot;에서 검색어 매칭시 해당 상품 부스팅)</li>
</ul>
</li>
<li><p>필드별 유사도</p>
<ul>
<li>검색어와 특정 색인 필드 단어 간 벡터 유사도 사용</li>
<li>유사도 계산 모델로는 TF-IDF, BM25</li>
</ul>
</li>
<li><p>모델 개선안 (기존에 인기도 요소만 존재했던 모델에 적합도 요소 추가)</p>
<p>  $Score(Qry, Doc) = Poplularity(Qry, Doc) + \alpha \cdot Relevance(Qry, Doc)$</p>
<p>  인기도(Popularity) : 상품의 구매, 후기, 클레임 등으로 판단한 점수</p>
<p>  적합도(Relevance) : 검색어와 상품 정보 간 확률적 연관 관계</p>
<ul>
<li>A/B 테스트를 통해 효과 검증, metric은 mAP(mean Average Precision) 사용</li>
</ul>
</li>
</ul>
<h3 id="모델-프로토타입-개발">모델 프로토타입 개발</h3>
<ul>
<li>핵심 : 각 요소의 가중치를 어떻게 설정할 것인가</li>
<li>적합도 적용 대상 필드 : 브랜드, 카테고리 (고객 피드백이 많았던 부분)<ol>
<li>브랜드 적합도가 가장 높은 상품 그룹이 이외의 상품 그룹보다 상위에 노출되야 함</li>
<li>브랜드가 동일한 상품의 경우, 카테고리 적합도가 높은 순으로 노출되야 함</li>
</ol>
</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/67dca1cb-9877-4769-b589-a39a421d4428/image.png" alt=""></p>
<p>모델 프로토타이</p>
<ul>
<li>Tokenizer (형태소 분석) : Nori(Lucene의 한글 형태소 분석기)의 파이썬 버전 Pynori 사용
(Pynori의 경우 동의어 및 사용자 사전을 커스텀하게 적용 가능 → 기존 사전 적용)</li>
<li>BM25 : 검색어-색인 필드 단어간 유사도 알고리즘 모델 (elasticsearch 제공)</li>
<li>최근 1년 기준 무신사 인기 검색어 Top 1,000개와 모델링 요소별 가중치 조합 10,000개를 적용해 목표 시나리오를 만족시키는 가중치 조합 도출 → 초기 가중치 값 세팅</li>
</ul>
<h3 id="무신사-추천순-시스템-아키텍처-개선">무신사 추천순 시스템 아키텍처 개선</h3>
<ul>
<li>기존 추천 시스템 : 하루 전날 기준의 상품 데이터를 이용하는 랭킹 모델의 점수 사용
일별로 생성되는 랭킹 점수를 Elasticsearch에 저장, 기본적인 필드 정렬을 사용</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/059092d2-20d7-421f-8ad4-dccce5e323d9/image.png" alt="">
기존 추천 시스템 아키텍처</p>
<ul>
<li>랭킹 점수 뿐만 아니라, 브랜드나 카테고리 필드에서 적합도 점수 산출을 위한 데이터도 함께 상품 인덱스에 저장</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/977a329e-91d7-4b38-9c5e-fd636daf81da/image.png" alt="">
개선된 추천 시스템 아키텍처</p>
<h3 id="시스템-구현을-위한-세부-절차들">시스템 구현을 위한 세부 절차들</h3>
<ul>
<li><p>프로토 타입에서 구현한 목표를 실제 서비스에 적용
→ Elasticsearch의 Function score query를 사용해 스코어링 모델 구현</p>
</li>
<li><p>적합도 점수와 인기도 점수 조합시 사용한 데이터</p>
<ul>
<li>상품 랭킹에서 사용하는 상품별 랭킹 점수</li>
<li>한글/영문 브랜드 키워드</li>
<li>대/중/소 카테고리 키워드</li>
<li>부가 정보의 점수</li>
</ul>
</li>
<li><p>Function score query에서 사용할 수식</p>
<p>  $Score = (Relevance_{brand} + Relevance_{category} + Popularity + Additional \ Info)$</p>
</li>
<li><p>Function score query 작동 조건</p>
<ol>
<li>Field 점수 사용 여부</li>
<li>해당 함수 쿼리에 검색어의 Term이 있으면 1, 없으면 0</li>
<li>1번과 2번의 점수에 가중 할 점수 : 가중치로 우선 순위 지정</li>
<li>3번의 값들을 합산할 것인지 곱셈할 것인지 등에 관한 결정</li>
</ol>
<p>  <strong>$Score = (Rel.Score_{brand} * w_1 + Rel.Score_{category} * w_2 + Pop.score * w_3 + Add.Score * w_4)$</strong></p>
<p>  ⇒ 가중치를 통해 의도한대로 상품 정렬</p>
</li>
</ul>
<h3 id="데이터-전처리">데이터 전처리</h3>
<ol>
<li>상품 랭킹 데이터<ul>
<li>랭킹 점수는 다른 모델링 요소보다 스케일이 크기 때문에 데이터 스케일링 진행</li>
<li>음수인 랭킹 점수를 모두 0으로 치환 
→ 반품 실적만 있는 상품을 인기도가 없는 (인기도 점수가 0) 상품으로 간주</li>
</ul>
</li>
<li>적합도 적용 대상 필드 데이터 전처리<ul>
<li>적합도가 높은 상품들을 디테일하게 분류하기 위함</li>
<li>브랜드 : 한글명과 영문명 데이터를 각각 사용할 수 있도록 두 데이터를 구분해서 검색엔진에 저장</li>
<li>카테고리 : 대/중/소 카테고리를 사용하기 떄문에 서로 다른 Depth의 카테고리 필드에서 적합도 점수가 불필요하게 중복으로 부여될 수 있어 이를 방지하기 위해 데이터 전처리 수행<ul>
<li>상위 카테고리명이 하위 카테고리 명에 <strong>부분 일치</strong>하는 경우, 일치된 부분 제거
ex. &quot;팬츠 &gt; 데님 팬츠&quot; → &quot;팬츠 &gt; 데님&quot;으로 전처리해 &quot;팬츠&quot;로 검색 시 대카테고리 필드에서만 스코어링 되도록 함)</li>
<li>상위 카테고리명이 하위 카테고리명에 완전 일치하는 경우 전처리 하지 않음
ex. &quot;선글라스/안경테 &gt; 선글라스&quot; 는 전처리 하지 않음</li>
</ul>
</li>
<li>브랜드와 카테고리 필드에 불용어 사전을 구축 및 적용 → 적합도 점수 부여받지 않을 단어 처리. 상품 검색이 아닌 정렬에만 영향을 끼칠 수 있도록 스코어 계산 분석기에 적용<ol>
<li>브랜드 필드 전용 불용어 사전
카테고리, 동음이의어 브랜드 단어들로 구성 (ex. 아이웨어, 디자인 등)</li>
<li>카테고리 필드 전용 불용어 사전
유의미하지 않은 카테고리 용어(&#39;기타&#39;)로 구성</li>
<li>브랜드/카테고리 필드 공통 불용어 사전
성별, 컬러 용어들로 구성</li>
</ol>
</li>
</ul>
</li>
</ol>
<h3 id="검색엔진-쿼리-작성">검색엔진 쿼리 작성</h3>
<ul>
<li><p>무신사 검색 엔진은 여러 가지 색인 필드 항목(브랜드, 카테고리, 상품명, 태그 등)을 Multi match query를 통해 하나의 필드처럼 만들어서 검색</p>
<ul>
<li>&quot;type&quot; : &quot;cross_fields&quot;, &quot;operation&quot; : &quot;AND&quot; (elasticsearch parameters)</li>
<li>검색어가 대상 필드 중 존재하면 해당 상품이 검색 결과로 제공</li>
</ul>
</li>
<li><p>Function score query</p>
<ul>
<li><p>검색된 상품은 기존과 동일하나 정렬 순서만 변경하고자 함</p>
</li>
<li><p>query : 기존 검색 조건을 포함한 Multi match query 사용</p>
</li>
<li><p>functions : 스코어링하고 싶은 요소를 추가해 상품 정렬을 변경 (개선된 부분)</p>
<p><img src="https://images.velog.io/images/choi-yh/post/03ce08db-7bd6-46e9-9f0a-85ff48edadee/image.png" alt="">
Function score query의 스코어링 Flow</p>
</li>
</ul>
<ol>
<li>검색어 query (사용자)</li>
<li>검색엔진이 Function score query의 query 부분에서 검색어가 매칭되는 문서(상품) 조회
BM25 알고리즘을 통해 선별된 상품들을 검색 결과로 제공</li>
<li>functions 부분에서 미리 정의된 필드별 가중치 값과 &quot;score_mode&quot;에 설정된 계산 방법에 따라 각 필드의 적합도 점수를 하나의 적합도 점수로 합산</li>
<li>query 부분과 functions 부분에서 각각 계산된 점수를 &quot;boost_mode&quot; 계산 방법에 따라 합쳐서 최종 점수 산출</li>
</ol>
</li>
</ul>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://medium.com/musinsa-tech/%EA%B2%80%EC%83%89%EC%96%B4-%EB%B6%84%EC%84%9D%EC%9D%84-%ED%86%B5%ED%95%9C-%EC%83%81%ED%92%88-%EC%A0%95%EB%A0%AC-%EA%B0%9C%EC%84%A0-b92ded2923c3">https://medium.com/musinsa-tech/%EA%B2%80%EC%83%89%EC%96%B4-%EB%B6%84%EC%84%9D%EC%9D%84-%ED%86%B5%ED%95%9C-%EC%83%81%ED%92%88-%EC%A0%95%EB%A0%AC-%EA%B0%9C%EC%84%A0-b92ded2923c3</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[검색엔진] 쿠팡 검색 플랫폼 변천사 정리]]></title>
            <link>https://velog.io/@choi-yh/%EC%BF%A0%ED%8C%A1-%EA%B2%80%EC%83%89-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%B3%80%EC%B2%9C%EC%82%AC-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@choi-yh/%EC%BF%A0%ED%8C%A1-%EA%B2%80%EC%83%89-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%B3%80%EC%B2%9C%EC%82%AC-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 05 Jun 2021 14:59:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>쿠팡 기술 블로그를 개인적인 공부를 위해 정리했습니다.</p>
</blockquote>
<h2 id="쿠팡의-검색-목표">쿠팡의 검색 목표</h2>
<ul>
<li>고객이 &quot;쿠팡 없이 어떻게 살았을까?&quot;라고 묻는 세상을 만드는 것</li>
<li>고객 검색 키워드를 통해 최고의 프로덕트(연관 상품, 랭킹, 상품평, 가격, 브랜드)를 제공하는 것</li>
<li>관련성(relevancy)이 가장 중요한 웹 검색 엔진(ex. Google)과 달리, 
상품의 랭킹, 상품평, 가격, 브랜드 등의 요소도 중시</li>
</ul>
<h2 id="개요">개요</h2>
<h3 id="검색-엔진-작동-과정">검색 엔진 작동 과정</h3>
<ol>
<li><strong>query(검색어) 이해</strong>
고객의 검색 의도 및 카테고리/브랜드와 같은 부차적인 단어(query annotation) 파악</li>
<li><strong>검색</strong>
query와 상품 텍스트 정보를 기반으로 대응하는 상품 후보군 검색</li>
<li><strong>Ranking</strong> (순위 부여)
ranking function / ML model을 활용해 후보 상품들의 순위 도출
ranking function : Score = f(텍스트 관련성, 순위, 상품평, 가격, ...) (랭킹 시그널을 통한 스코어링)</li>
<li><strong>Twiddling</strong>
프로모션과 같은 비즈니스 로직에 따라 다시 한번 순위 정렬</li>
</ol>
<h2 id="인덱싱-플랫폼">인덱싱 플랫폼</h2>
<ul>
<li>검색 및 랭킹을 위한 모든 데이터를 제공</li>
<li>모든 데이터를 통합해 검색 엔진을 위한 검색 인덱스 생성 
⇒ 인덱싱 플랫폼을 잘 구성하는 것이 핵심!</li>
<li>랭킹 알고리즘 개발에 필요한 데이터를 위해서는 인덱싱 플랫폼 구축이 잘 되어 있어야 한다.</li>
</ul>
<p><img src="https://images.velog.io/images/choi-yh/post/a4d865e6-9398-43ee-b43e-dd6df60848e7/image.png" alt=""></p>
<h3 id="1-인덱싱-플랫폼-01">1) 인덱싱 플랫폼 0.1</h3>
<p><img src="https://images.velog.io/images/choi-yh/post/48b0f9d3-b208-4875-81d3-3e8948b559ef/image.png" alt=""></p>
<ul>
<li>초기에는 보유 상품 수가 많지 않았다.</li>
<li>Ground Truth (원본 데이터)를 가격, 카테고리, 브랜드 등과 같은 RDBMS에 저장했고, 
SQL을 활용해서 상품키를 통한 table join 방식으로 비정규화된 테이블에 통합</li>
<li>생성된 비정규화 테이블을 온라인 서빙 검색 클러스터 에 전달해 다이나믹 인덱스 생성
(비정규화 테이블을 검색 클러스터에 전달해서 이를 인덱싱 플랫폼으로 활용 했다는 의미?)</li>
<li>상품의 텍스트와 간단한 시그널 정보를 통한 기초 랭킹을 바탕으로 검색 기능 제공</li>
<li><strong>문제점</strong><ul>
<li>관계형 데이터베이스의 수십 개의 테이블을 조인하는 과정이 너무 느리고 불안정하다.</li>
<li>RDMBS와 온라인 서빙 서치 클러스터를 단순 연결할 뿐 플랫폼이 아니다.</li>
<li>많은 양의 데이터 처리를 위한 확장이 불가능</li>
<li>온라인 서빙 검색 클러스터에서 인덱스를 구축하는 것이 매우 오랜 시간 걸리며, replica(복제본)를 생성시 같은 데이터를 복제하는 것이기 때문에 자원이 많이 낭비됐다.</li>
<li>랭킹 엔지니어가 검색/랭킹 시그널을 단기간(며칠)에 추가하는 것이 불가능한 수준이다.</li>
</ul>
</li>
</ul>
<h3 id="2-인덱싱-플랫폼-10">2) 인덱싱 플랫폼 1.0</h3>
<p><img src="https://images.velog.io/images/choi-yh/post/35145924-7739-4d2e-b599-092744bf41b0/image.png" alt=""></p>
<ul>
<li>분산 데이터 처리를 위해 Hadoop에 구축</li>
</ul>
<ol>
<li>모든 원본 데이터(ground truth)는 하이브(Hive. 하둡의 데이터 웨어하우스)로 복제
랭킹 시그널도 하이브 테이블로 생성됨</li>
<li>Index Merger : Spark Job (모든 데이터를 <em>Product Join</em>으로 병합)
Product Join : protobuf 구조 (상품에 대한 모든 것을 갖춤)</li>
<li>병합된 데이터는 Hbase에 저장</li>
<li>다른 Index Builder가 hbase에서 Product Join을 사용해 검색 인덱스를 생성하고 이를 분산 스토리지에 저장</li>
</ol>
<ul>
<li>문제점<ul>
<li>랭킹 개발자의 작업량</li>
<li>랭킹 시그널 구축에 대한 향상성 문제 (신속한 시그널 구축과는 거리가 멂)</li>
<li>주니어 랭킹 개발자가 개발한 신규 시그널 같은 경우, 
병합할 데이터 소스를 찾고, 데이터 파이프 라인을 처리하고, 워크 플로우 일정을 관리하는데 리소스 소요가 컸다.</li>
</ul>
</li>
</ul>
<h3 id="3-인덱싱-플랫폼-20">3) 인덱싱 플랫폼 2.0</h3>
<p><img src="https://images.velog.io/images/choi-yh/post/cf5f4927-dcaf-4235-9c54-ea141d5521bb/image.png" alt=""></p>
<p>인덱싱 플랫폼 2.0은 랭킹 개발자가 데이터 소스, 데이터 파이프 라인 및 워크 플로우 일정 관리에 대한 걱정없이 오롯이 <strong>시그널의 로직에 집중할 수 있도록 해주는 인덱싱 버스(Indexing Bus)</strong></p>
<ul>
<li><p>대부분의 시그널이 플랫폼 내부에서 생성됨 
(인덱싱 플랫폼 1.0의 경우 외부 하이브 테이블에서 시그널 병합)</p>
</li>
<li><p>모든 원본 데이터가 Ground Truth Merger에 의해 병합돼 Session Log Parser로 모든 고객 행동을 parsing하고 merge. (모두 Spark Job)</p>
</li>
<li><p>Product Joiner 와 Query Joiner는 원본 데이터 및 세션 로그를 기반으로 모든 시그널 파생 (강력)</p>
<p>  ex. Query Join을 구축할 때 Product Join의 가격 정보를 활용해서 쿼리 가격 분포를 구축한 뒤, 상품 가격과  쿼리 가격 분포를 바탕으로 상품을 boosting / demote 하는 시그널을 생성할 수 있다.</p>
</li>
<li><p>Query Join을 통한 시그널 생성 프로세스</p>
<ol>
<li>원본 데이터 및 파싱된 세션 로그를 포함한 모든 소스 데이터를 로드</li>
<li>노출, 클릭, 구매와 같은 기본적으로 집계된 원시 고객 행동 데이터를 쿼리 수준 및 상품 쿼리 수준으로 생성</li>
<li>모든 시그널 프로세서를 실행해 시그널을 생성</li>
<li>모든 시그널을 갖춘 Query Join을 Hbase에 저장</li>
</ol>
</li>
<li><p>장점</p>
<ul>
<li>신규 랭킹 시그널 구축이 빠르게 진행되기 때문에, 시그널 로직에만 집중할 수 있다.</li>
<li>클러스터 자원이 절약되며, 신규 시그널 추가 과정에 리소스 소모가 거의 없다.</li>
<li>시그널 품질 테스트가 제공된다.</li>
</ul>
</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><a href="https://medium.com/coupang-tech/%EA%B2%80%EC%83%89-%EC%98%81%EC%97%AD-%ED%83%90%EC%83%89%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9D%B8%EB%8D%B1%EC%8B%B1-%ED%94%8C%EB%9E%AB%ED%8F%BC%EC%9D%98-%EB%B3%80%EC%B2%9C%EC%82%AC-eec241758e84#">https://medium.com/coupang-tech/%EA%B2%80%EC%83%89-%EC%98%81%EC%97%AD-%ED%83%90%EC%83%89%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9D%B8%EB%8D%B1%EC%8B%B1-%ED%94%8C%EB%9E%AB%ED%8F%BC%EC%9D%98-%EB%B3%80%EC%B2%9C%EC%82%AC-eec241758e84#</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 문자열을 정수로 바꾸기]]></title>
            <link>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%9D%84-%EC%A0%95%EC%88%98%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B8%B0</link>
            <guid>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%9D%84-%EC%A0%95%EC%88%98%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B8%B0</guid>
            <pubDate>Wed, 21 Apr 2021 13:50:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/12925">https://programmers.co.kr/learn/courses/30/lessons/12925</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>문자열 s를 숫자로 변환한 결과를 반환하는 함수, solution을 완성하세요.</p>
<h3 id="제한-조건">제한 조건</h3>
<ul>
<li>s의 길이는 1 이상 5이하입니다.</li>
<li>s의 맨앞에는 부호(+, -)가 올 수 있습니다.</li>
<li>s는 부호와 숫자로만 이루어져있습니다.</li>
<li>s는 &quot;0&quot;으로 시작하지 않습니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<p>예를들어 str이 &quot;1234&quot;이면 1234를 반환하고, &quot;-1234&quot;이면 -1234를 반환하면 됩니다.
str은 부호(+,-)와 숫자로만 구성되어 있고, 잘못된 값이 입력되는 경우는 없습니다.</p>
<h2 id="풀이">풀이</h2>
<pre><code>def solution(s):
    answer = eval(s)
    return answer</code></pre><ul>
<li><code>.eval(expression[, globals[, locals]])</code> : The return value is the result of the evaluated expression. Syntax errors are reported as exceptions.
(<a href="https://docs.python.org/3/library/functions.html">https://docs.python.org/3/library/functions.html</a>)
문자열로 된 표현식을 계산해서 리턴하는 파이썬 내장 함수</li>
<li>문제의 조건이 간단하기 때문에 사용했다.</li>
</ul>
<pre><code>def solution(s):
    answer = 0

    if s[0] == &#39;+&#39;:
        answer = int(s[1:])
    elif s[0] == &#39;-&#39;:
        answer = -1 * int(s[1:])
    else:
        answer = int(s)
    return answer</code></pre><ul>
<li><code>s</code>의 맨 앞에는 부호(+, -)가 올 수 있다고 했기 때문에 각각의 경우를 나눠서 계산</li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li>다른 풀이 중 <code>.int()</code>를 사용한 풀이도 있었는데 <code>.int()</code> 함수의 경우 자동으로 부호 계산을 해주기 때문에 사용 가능했다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 수박수박수박수박수박수?]]></title>
            <link>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%98%EB%B0%95%EC%88%98%EB%B0%95%EC%88%98%EB%B0%95%EC%88%98%EB%B0%95%EC%88%98%EB%B0%95%EC%88%98</link>
            <guid>https://velog.io/@choi-yh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%98%EB%B0%95%EC%88%98%EB%B0%95%EC%88%98%EB%B0%95%EC%88%98%EB%B0%95%EC%88%98%EB%B0%95%EC%88%98</guid>
            <pubDate>Wed, 21 Apr 2021 13:40:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/12922">https://programmers.co.kr/learn/courses/30/lessons/12922</a></p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>길이가 n이고, &quot;수박수박수박수....&quot;와 같은 패턴을 유지하는 문자열을 리턴하는 함수, solution을 완성하세요. 예를들어 n이 4이면 &quot;수박수박&quot;을 리턴하고 3이라면 &quot;수박수&quot;를 리턴하면 됩니다.</p>
<h3 id="제한-조건">제한 조건</h3>
<ul>
<li>n은 길이 10,000이하인 자연수입니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>n</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>3</td>
<td>&quot;수박수&quot;</td>
</tr>
<tr>
<td>4</td>
<td>&quot;수박수박&quot;</td>
</tr>
</tbody></table>
<h2 id="풀이">풀이</h2>
<pre><code>def solution(n):
    answer = &quot;수박&quot; * n
    return answer[:n]</code></pre>]]></description>
        </item>
    </channel>
</rss>