<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>준별개발</title>
    <link>https://junbyeol.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 16 Jun 2026 15:37:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>준별</managingEditor>
    <image>
      <title>준별개발</title>
      <url>https://tistory1.daumcdn.net/tistory/6561953/attach/9c5ba1e408c543beb16336ac60b1f519</url>
      <link>https://junbyeol.tistory.com</link>
    </image>
    <item>
      <title>Spring Boot 프로젝트 시작 시 생기는 모든 파일의 역할</title>
      <link>https://junbyeol.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://start.spring.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring Initiailzr&lt;/a&gt;를 이용하며 Spring Boot를 다음 설정으로 시작했다.&lt;br /&gt;- Gradle-Kotlin&lt;br /&gt;- 4.0.5&lt;br /&gt;- Group: me.junbyeol (프로젝트를 만드는 조직명을 반대로 쓴것 junbyeol.me &amp;lt;-&amp;gt; me.junbyeol)&lt;br /&gt;- Artifact: main-server (프로젝트 명)&lt;br /&gt;- Package name: 보통은 (Group) + (Artifact)의 형태로 자동 생성&lt;br /&gt;- Jar, yaml, java 25&lt;br /&gt;- Dependencies: Spring web, Spring Data JPA, Mysql driver, Spring Boot DevTools&lt;br /&gt;&lt;br /&gt;그 결과 아래의 디렉토리 구조로 프로젝트가 생성 됐다. 각 파일들이 무엇을 의미하는지 정리한다. 그 전에, 코틀린과 JVM, 그리고 Gradle에 대해 간단히 정리한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EU2lH/dJMcaibYGJb/TAiCCDUsZKsPvPRWQCknPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EU2lH/dJMcaibYGJb/TAiCCDUsZKsPvPRWQCknPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EU2lH/dJMcaibYGJb/TAiCCDUsZKsPvPRWQCknPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEU2lH%2FdJMcaibYGJb%2FTAiCCDUsZKsPvPRWQCknPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;439&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코틀린과 JVM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린(Kotlin)은 자바(Java)의 실행환경을 그대로 이용하면서도 자바의 문법적인 한계 등을 해결한 언어다.&lt;br /&gt;NPE(Null Point Exception)로부터 앱을 보호하거나, 간결한 문법 및 함수형 프로그래밍의 개념을 적극 도입했다.&lt;br /&gt;&lt;br /&gt;자바는 javac라는 컴파일러에 의해 자바 바이트코드(.class 파일)로 컴파일된다.&lt;br /&gt;대신 kotlin은 kotlinc라는 컴파일러에 의해 자바 바이트코드(.class 파일)로 컴파일된다.&lt;br /&gt;즉, 컴파일러만 다를 뿐 컴파일 결과물은 동일하기에, 동일하게 JVM(Java Virtual Machine)으로 실행 가능하다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;흥미로운 사실은, javac와 kotlinc는 컴파일러지만 이들도 각각 자바와 코틀린으로 작성된 후 바이트코드로 컴파일된 프로그램이라는 것이다. 그렇다면 최초의 자바 컴파일러는 어떻게 만든걸까? 자바 바이트코드로 직접 작성되었거나, C 언어로 작성된 컴파일러로 컴파일되었을 것이다. 후자가 정답이다. 그럼 최초의 C언어 컴파일러는 무엇으로 만들었을까? 또 다시 다른 언어(B 언어)의 컴파일러로 만들어졌다. 거슬러 올라가면, 정말 최초 최초의 컴파일러는 기계어로 사람이 작성했을 것이다!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;참고로, JRE(Java Runtime Environment)라는 말은 자바를 실행할 수 있도록 JVM과 일부 라이브러리만을 포함한 단어다.&lt;br /&gt;컴파일러를 포함하지 않으므로, 코드를 작성할수는 없고 실행만 할 수 있다.&lt;br /&gt;코드를 작성하려면, JRE에 javac와 jdb(java debugger)가 포함된 JDK(Java Developer Kit)를 이용해야한다.&lt;br /&gt;그렇다면 코틀린 코드를 작성하려면 kotlinc를 따로 내려받아야하는가?&lt;br /&gt;그렇지 않다. kotlinc는 다음에 소개할 gradle이 알아서 내려받아주기 때문이다.&lt;br /&gt;&lt;br /&gt;.jar 파일은 실행가능한 자바 바이트코드(.class)와 라이브러리를 압축해둔 파일이다.&lt;br /&gt;압축을 해제하면 JVM이 실행할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Gradle&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle은 의존성 관리, 빌드, 스크립트 실행 등 우리의 spring 프로젝트를 실행하는데에 거의 모든것을 담당하는 녀석이다.&lt;br /&gt;이 Gradle조차도 자바 바이트코드(.class 파일)로 컴파일되어 있기에, JVM을 통해서 실행된다.&lt;br /&gt;&lt;br /&gt;Gradle의 경쟁자로는 Maven이 있다&lt;br /&gt;Maven은 Gradle과 비슷한 역할을 하지만 결정적인 차이가 있다.&lt;br /&gt;Gradle의 설정 파일은 코틀린을 이용하기에 코드 작성과 동일한 언어를 사용하여 간결하게 표현할 수 있지만, Maven은 XML을 사용하여 좀 더 번거롭다.&lt;br /&gt;실행 속도면에서도, Gradle이 코드의 변경점만을 감지하여 더 빠르게 실행하기에 Gradle이 생태계에서 더 우위를 점하고 있는 것으로 보인다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이제야 Spring의 모든 파일 이해하기!&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Gradle-wrapper 관련 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- gradle/wrapper/gradle-wrapper.jar: Gradle을 다운받을 수 있는 자바 프로그램이다.&lt;br /&gt;- gradle/wrapper/gradle-wrapper.properties: 위의 gradle-wrapper.jar가 내려받을 gradle의 버전이나 내려받을 저장소 등의 설정을 정의한다. 이 파일이 없었더라면, 다운받고 싶은 gradle의 버전을 바꾸고 싶을때마다 gradle-wrapper.jar 파일 자체를 매번 교체해야 했을 것이다.&lt;br /&gt;&lt;br /&gt;- gradlew과 gradlew.bat: 실행 머신의 java 설치 여부 등을 체크하고, gradle-wrapper.jar를 실행하여 Gradle을 내려받은 후, 실행까지 하는 쉘 스크립트 파일이다. gradlew와 gradlew.bat의 차이는 실행하는 OS가 Mac/Linux(Unix계)냐 Window냐의 차이다. 즉, 직접 gradle-wrapper.jar를 개발자가 직접 실행해도 실질적인 동작은 같으나, 그 번거로운 과정을 gradlew가 추상화했다고 이해하면 된다.&lt;br /&gt;&lt;br /&gt;- .gitattributes: gradlew와 gradlew.bat는 OS에 종속적인 파일이다. 그런데 Unix계와 Window는 파일에서 줄바꿈을 다루는 방식이 다르다. .gitattributes는 이 차이가 git의 파일 변화 추적에 쓸데없는 기록을 만들지 않도록 하는 역할을 한다. 또, .jar 파일은 바이너리니까 쓸데없는 줄바꿈을 하지 않도록 제어한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Gradle 관련 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- settings.gradle.kts: 처음 spring boot를 시작하면, rootProject의 이름을 정의할 뿐이다. 이 파일은 우리 프로젝트안에 서브 프로젝트(서브모듈)들이 추가되면 진가를 발휘하는 파일이니 일단 넘어간다.&lt;br /&gt;- build.gradle.kts: 의존성을 관리하고 프로젝트 실행에 관한 정보를 기술한다. 앞서, Maven은 XML을 사용한다고 했는데 이 파일은 코틀린(.kts)으로 기술되어 있음을 확인하자. 파일 내용을 살펴보면, 여러개의 블록들로 관리되어 있는데, 주요 블럭은 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;plugins {}&lt;/td&gt;
&lt;td style=&quot;width: 80.3488%;&quot;&gt;빌드에 필요한 버전 정보와 플러그인들의 버전정보를 기술한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;repositories {}&lt;/td&gt;
&lt;td style=&quot;width: 80.3488%;&quot;&gt;의존성을 내려받을 저장소를 기술한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;dependencies {}&lt;/td&gt;
&lt;td style=&quot;width: 80.3488%;&quot;&gt;사용할 의존성들을 기술한다.&lt;br /&gt;- implementation: 항상 필요한 라이브러리&lt;br /&gt;- developmentOnly: 개발 환경에서만 사용할 라이브러리(개발을 위한 편의 기능들)&lt;br /&gt;- runtimeOnly: 실행할때만 필요한 라이브러리(DB 드라이버 등)&lt;br /&gt;- testImplementation: 테스트 할때만 필요한 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애플리케이션 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/main/kotlin: 본격적으로 우리가 작성하게 애플레케이션 코드가 있는 곳. xxxApplication.kt가 실행 시 진입점이 된다.&lt;br /&gt;src/main/resources: 쉽게 말하자면 코틀린 파일이 아닌데, 서비스에 필요한 이미지, yaml/json, html/css 등 모든것이 들어가는 곳. 여기 있으면 컴파일러가 이 파일들은 건드리지 않고 그대로 jar에 담는다.&lt;br /&gt;src/test: 프로젝트의 테스트 코드들을 작성하는 곳&lt;/p&gt;</description>
      <category>개발이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/64</guid>
      <comments>https://junbyeol.tistory.com/64#entry64comment</comments>
      <pubDate>Sat, 18 Apr 2026 22:54:43 +0900</pubDate>
    </item>
    <item>
      <title>비디오와 이미지를 압축하고 송출하는 원리</title>
      <link>https://junbyeol.tistory.com/63</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;이미지와 비디오 인코딩의 원리&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GHK4E/dJMcaadjTJS/Ee9pZSFeGXKwpiwkbS6LNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GHK4E/dJMcaadjTJS/Ee9pZSFeGXKwpiwkbS6LNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GHK4E/dJMcaadjTJS/Ee9pZSFeGXKwpiwkbS6LNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGHK4E%2FdJMcaadjTJS%2FEe9pZSFeGXKwpiwkbS6LNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;222&quot; height=&quot;750&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 저장하는 가장 단순한 방법은 모든 픽셀의 색상을 순서대로 저장하는 것이다. 이 픽셀 배열을 RAW 이미지 또는 &lt;b&gt;비트맵(bitmap)&lt;/b&gt;이라고 부른다. 디스플레이 장치들은 이 데이터로부터 각 픽셀에 어떤 색을 출력할지를 결정한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 방식은 비효율적이다. 예를 들어, 사람이 달리는 이미지를 살펴보자. 하늘을 표현하는 동일한 파란색이 픽셀 배열에 연이어 등장하는 일이 매우 잦다. 어떤 파란색이 200번 연속해서 픽셀에 등장한다면, 파란색을 배열에 200번 저장하는 것보다는, 파란색을 한 번만 저장하고 200번 반복된다는 정보를 저장하는 편이 경제적이다. 우리가 현실 속에서 다루는 이미지들은 픽셀들의 &lt;b&gt;중복도(redundancy)&lt;/b&gt;가 충분히 높기 때문에, 이런 정보 저장 방식은 효율적으로 동작할 수 있다. 달리 표현하면 이미지의 엔트로피(entropy, 불규칙성)가 높을수록 압축률은 떨어진다. 극단적으로 모든 픽셀이 전혀 중복 없이 랜덤한 노이즈로 가득하다면, 오히려 비트맵으로 데이터를 저장하는 것이 데이터 크기가 더 작을 수도 있다. 반면, 모든 픽셀이 같은 색인 단색 이미지라면 압축 효율은 극단적으로 좋을 것이다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동영상의 경우, 또 다른 방법을 떠올릴 수 있다. 앞서 소개한 이미지의 압축 방법은 하나의 정지화면 안에서 중복되는 픽셀을 이용한다. 한편, 연속된 프레임(정지화면) 사이의 픽셀 변화를 이용할 수도 있다. 예를 들어, 어떤 픽셀이 한 프레임에서 파란색인데, 그 다음 프레임에서도 동일한 파란색이라면 그 중복을 활용하여 데이터를 압축할 수 있다. 하나의 정지화면 안에서 픽셀의 중복을 활용하는 방식을 &lt;b&gt;공간적 코딩(Spatial Coding, Intra-frame Coding)&lt;/b&gt;, 연속된 정지화면 간의 픽셀의 중복을 활용하는 방식을 &lt;b&gt;시간적 코딩(Temporal Coding, Inter-frame Coding)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이미지의 압축 방식: JPG, PNG, GIF, WebP&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JPG(=JPEG, Joint Photographic&amp;nbsp;Experts Group)&lt;/b&gt;는 대표적인 손실 압축 방식이다. 사람의 눈은 미세한 색상 차이보다는 밝기에 더 민감하게&amp;nbsp;반응한다. 이 점을 이용하여 JPG 방식은 사람의&amp;nbsp;눈에 비슷하게 인식될 색상을 과감하게 통합한다.&amp;nbsp;정보 손실이 발생하지만, 압축률이 좋다. 한편 &lt;b&gt;PNG(Portable Network Graphics)&lt;/b&gt;는 단 1비트의 손실도 없는 무손실 압축 방식이다. 색을 RGB 3가지만이 아니라, 투명도(alpha) 채널까지 지원한다. &lt;b&gt;GIF(&lt;span data-token-index=&quot;1&quot;&gt;G&lt;/span&gt;raphics&amp;nbsp;&lt;span data-token-index=&quot;3&quot;&gt;I&lt;/span&gt;nterchange&amp;nbsp;&lt;span data-token-index=&quot;5&quot;&gt;F&lt;/span&gt;ormat)&lt;/b&gt;는 움직이는 이미지를 저장할 수 있는 무손실 압축 방식이지만, 표현하는 색상 팔레트가 제한적이다. 보통 색은 RGB 각 1바이트씩 3바이트로 표현되고, PNG는 투명도를 포함하여 4바이트를 사용한다. 전문가용 고채색 HDR 영상은 RGB&amp;nbsp;각 10비트(합쳐서 약 4바이트)를 사용한다. 반면, GIF는 색 전체를 표현하는데에 1바이트밖에 지원하지 않아서 색상의 손실이 발생한다.&lt;br /&gt;&lt;br /&gt;최근 웹 환경에서 가장&amp;nbsp;각광받는 방식은 &lt;b&gt;WebP(웹피)&lt;/b&gt; 방식이다. WebP는 위 방식들의 장점만을 취한 현 시점에서 가장 강력한 압축 방식이다. JPG보다도 더 높은 압축률을 보이고, PNG처럼 투명색을 표현할 수 있으며, GIF처럼 움직이는 이미지를 지원한다. 이미지에 따라 손실 방식과 무손실 방식을 선택할 수도 있다. WebP의 단점은 아주 오래된 브라우저에서는 지원되지 않을 수 있다는 것뿐이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비디오 송출 방식: VBR과 CBR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 영상 안에서도 격렬하게 화면 전환이 일어나는 액션 장면이나 폭발 장면은 픽셀의 변화가 잦아 저장에 많은 데이터를 요구한다. 반면, 뉴스 앵커 장면이나 잔잔한 장면이라면 픽셀의 변화가 적어서 작은 크기로도 정보를 저장할 수 있다. 이런 장면의 차이에 따라, 비디오를 송출하는 속도(비트레이트)를 조절하는 방식을 &lt;b&gt;VBR(Variable BitRate, 가변 비트레이트)&lt;/b&gt;라고 한다. 유튜브 서버가 사용자의 모바일 기기에 영상을 송출하는 경우가 대표적인 예이다. 한편, 영상의 내용에 관계없이 항상 일정한 속도로 비디오를 송출하는 방식을 &lt;b&gt;CBR(Constant BitRate, 고정 비트레이트)&lt;/b&gt;라고 한다. CBR 방식으로 송출되는 비디오는 대역폭 관리가 수월하다는 장점이 있지만, 격렬한 장면에서 화질 열화(화질 저하)가 일어날 수 있다. 실시간 라이브 스트리밍에서 주로 활용한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;DASH(Dynamic Adaptive Streaming over HTTP)&lt;/b&gt;는 동영상을 여러 개의 청크(chunk)로 나누어 송출하는 기술이다. 각 청크는 화면이 불규칙한 정도와 사용자의 네트워크 상태(사용자의 인터넷 연결 상태가 좋은지 나쁜지) 등을 고려하여 최적의 비트레이트로 사용자에게 송출된다. 흥미로운 점은 이 기술을 통해 나뉜 여러 청크의 비트레이트 분포가 영상마다 고유한 지문처럼 나타난다는 것이다. 이런 특징은 악성 사용자가 다른 사용자가 어떤 영상을 시청하고 있는지 탐지하는 보안 공격의 원리로 악용되기도 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비디오 표준 생태계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MPEG(Moving Picture Experts Group)&lt;/b&gt;은 어떻게 비디오와 오디오를 효율적으로 압축할지를 결정하는 집단이다. 이들이 압축 방법의 표준을 정한 덕에 LG에서 만든 영상이 삼성의 갤럭시 기기에서도 송출될 수 있는 것이다. MPEG-1, MPEG-2, MPEG-4, MPEG-H 등의 규격이 이들로부터 제정되었고, DASH 또한 이들로부터 만들어졌기에 MPEG-DASH라고도 불린다. 한편, 이들과의 로열티 문제로 인해 구글/넷플릭스들이 뭉쳐 만든 AOMedia라는 단체가 AV1이라는 무료 코덱을 내놓기도 했고, 애플은 DASH 대신 독자적인 HLS(HTTP Live Streaming)를 표준으로 사용하기도 한다.&lt;br /&gt;&lt;br /&gt;영상의 표준이 나뉘는 중요한 관점은 &lt;b&gt;코덱(Codec)&lt;/b&gt;과 &lt;b&gt;컨테이너&lt;/b&gt;다. 코덱은 압축(COmpressor)과 분해(DECompressor)의 약자다. 픽셀들의 시간적/공간적 중복을 알고리즘적으로 어떻게 압축할지를 의미한다. 현재 가장 널리 쓰이는 표준은 H.264이고, 초고화질 영상에는 H.265가 뛰어난 압축률 덕분에 유리하다.&lt;br /&gt;&lt;br /&gt;컨테이너는 영상을 담는 그릇과 같다. 여러개의 청크로 쪼개진 영상들을 모아둔 것이다. 하나의 청크는 .ts, .m4s같은 확장자를 이용한다. 이 청크들을 어떤 순서로 조립할지를 설명하는 인덱스는 .m3u8, .mpd 같은 확장자를 이용한다. 여러개의 청크들과 인덱스, 영상을 설명하는 메타데이터와 오디오 스트림 등이 모여 .mp4, .avi같은 확장자를 가진 하나의 영상 파일이 된다.&lt;/p&gt;</description>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/63</guid>
      <comments>https://junbyeol.tistory.com/63#entry63comment</comments>
      <pubDate>Thu, 26 Feb 2026 18:43:33 +0900</pubDate>
    </item>
    <item>
      <title>엔드 간 네트워크 통신 한도(rate limit) 조절에 관련된 영어 표현 모음</title>
      <link>https://junbyeol.tistory.com/62</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;클라이언트(프론트엔드) 관련 표현&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Debounce: 어떤 이벤트가 끝나자마자 다음 함수를 호출하지 않고 기다리다가, 같은 이벤트가 다시 반복되면 묶어서 처리하는 기법&lt;/li&gt;
&lt;li&gt;Throttle: 같은 함수를 다시 실행할 수 있는 최소 주기를 설정하는 것 = 짧은 주기 내에 같은 함수가 지나치게 많이 호출되는 것을 막는 기법&lt;/li&gt;
&lt;li&gt;Abort: 진행중인 요청을 취소한다는 의미. 서버와의 connection을 끊을 수도 있음. 웹에서는 보통 AbortController를 통해서 구현됨. 요청이 abort되면, 서버는 disconnection을 감지하여 처리를 멈추거나 클라이언트로 응답을 보내지 않는다.&lt;/li&gt;
&lt;li&gt;Cancel: 기술적인 용어라기보단, UX나 user-facing 관점의 의미.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서버/네트워크 관련 표현&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Drop: 네트워크 패킷을 중도에 폐기한다는 의미. 주로 네트워크 계층(network layer)의 라우터(router)에서 일어나는 일.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/prescriptive-guidance/latest/hyperscale-aurora-mysql/shed-load.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Shed&lt;/a&gt;: 'Drop'과 비슷하지만, 'drop'은 물리적인 리소스의 한계로 어쩔 수 없이 버려지는 느낌이라면, 'shed'는 네트워크 상태가 극한으로 치닫기 전에 의도적으로 포기하고 조절한다는 느낌이다.&lt;/li&gt;
&lt;li&gt;Ignore/Discard: 서버에서 응답을 보내기 위한 준비가 되었으나, 무시 혹은 폐기한다는 의미. 클라이언트 측에서도 사용 가능한 표현이다(서버로부터 온 응답을 무시/폐기한다).&lt;/li&gt;
&lt;li&gt;Reject: 정책, 권한 등의 이유로 요청을 거절한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발이야기/토막글</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/62</guid>
      <comments>https://junbyeol.tistory.com/62#entry62comment</comments>
      <pubDate>Thu, 26 Feb 2026 18:36:07 +0900</pubDate>
    </item>
    <item>
      <title>TCP의 혼잡제어(congestion control)</title>
      <link>https://junbyeol.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 연결이 맺어진 두 호스트 사이에는 많은 &lt;b&gt;라우터(router)&lt;/b&gt;들이 존재한다. 라우터의 저장공간(버퍼, buffer)은 한정되어 있다. 라우터가 다음 라우터로 패킷을 내보내는 속도보다, 라우터에 패킷이 들어오는 속도가 더 빠르면 버퍼는 점점 차오른다. 버퍼가 가득차있는데 들어온 새로운 버퍼는 모두 &lt;b&gt;드랍(drop)&lt;/b&gt;된다. 이런 &lt;b&gt;혼잡한 상황(congestion)&lt;/b&gt;에서도 TCP는 데이터를 유실없이 전송하면서도, 통신 채널을 최대한 효율적으로 활용해야 하는 목표가 있다.&lt;br /&gt;&lt;br /&gt;TCP는 전송 계층(transport layer)의 프로토콜인 한편, 라우터의 기능은 그 하위인 네트워크 계층(network layer)에 그친다. TCP가 혼잡 상황을 알 수 있는 방법은 두 가지가 있다. 네트워크 계층에서 직접 혼잡 상황을 알려주는 경우와 그렇지 않아서 직접 추론해야하는 경우다. 전자를 &lt;b&gt;네트워크 지원 혼잡 제어(Network-Assisted Congestion Control)&lt;/b&gt;라고 하고, 후자를 &lt;b&gt;종단 간 혼잡 제어(End-to-End Congestion Control)&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네트워크 지원 혼잡 제어(Network-Assisted CC)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 계층이 전송 계층에 혼잡 사실을 알리는 방법은 두 가지가 있다. 첫째는 라우터가 직접 TCP 송신자에게 &lt;b&gt;초크 패킷(choke packet)&lt;/b&gt;을 보내 혼잡을 알리는 것이다. 둘째는 송신자에게 전송되는 패킷의 헤더에 혼잡을 의미하는 비트를 담는 방법이다. 이것을 &lt;b&gt;ECN(Explicit Congestion Notification)&lt;/b&gt;이라고 부른다. ECN은 IP 헤더의 2비트를 차지한다. 이 두가지 방법은 TCP에게 혼잡 정보를 정확히 알리지만, 그만큼 네트워크 계층에게 의존적이게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;종단 간 혼잡 제어 (End-to-End CC)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트와 호스트 사이 네트워크 계층이 혼잡한지 아닌지 전송 계층은 추론할 수 있다. 그 근거는 패킷의 손실이다. OS 커널(kernel)의 TCP 혼잡 제어 구현체는 점점 더 정교하게 발전해왔다. TCP는 파이프라이닝(pipelining)된 프로토콜임을 기억하자. 수신자의 ACK를 기다리지 않고 한번에 전송할수 있는 윈도우 크기를 &lt;b&gt;&lt;i&gt;cwnd&lt;/i&gt;라는 변수값&lt;/b&gt;으로 부른다. 당연하게도 &lt;i&gt;cwnd&lt;/i&gt;가 커질수록 통신 채널의 활성화(utilization) 정도가 높아진다. 쉽게 말하면 효율적으로 더 빠르게 전송한다. 하지만 &lt;i&gt;cwnd&lt;/i&gt;가 지나치게 커지면, 호스트 사이의 라우터들의 버퍼가 차오르고 혼잡해져서 패킷 드랍이 발생할 수 있다. 따라서, 커널은 패킷이 드랍당하지 않는 선에서 최대의 &lt;i&gt;cwnd&lt;/i&gt;를 확보하기 위해 노력한다.&lt;br /&gt;&lt;br /&gt;TCP의 &lt;i&gt;cwnd&lt;/i&gt;가 조절되는 방식은 &lt;b&gt;AIMD(Additive Increase, Multiplicative Decrease)&lt;/b&gt;라는 철학으로 설명된다. TCP는 cwnd 값을 혼잡이 감지될 때까지 선형적으로(합연산으로) 증가시키지만, 혼잡이 감지되면 곱절로 깎아버린다.&lt;br /&gt;&lt;br /&gt;좀 더 구체적으로 설명하면, TCP는 3가지 상태를 오가며 &lt;i&gt;cwnd&lt;/i&gt;를 조절한다. 첫번째 상태는 &lt;b&gt;slow start&lt;/b&gt;다. 처음에는 아주 작은 &lt;i&gt;cwnd&lt;/i&gt;값으로 시작하되, &lt;i&gt;cwnd&lt;/i&gt;를 과감하게 두배씩 늘려가며 지수적으로 &lt;i&gt;cwnd&lt;/i&gt;를 높여간다. 그러다가 혼잡이 감지되거나, 내부적인 연산에 의해 설정된 &lt;b&gt;&lt;i&gt;ssthresh&lt;/i&gt;&lt;/b&gt;(slow start threshold) 변수 값에 &lt;i&gt;cwnd&lt;/i&gt;가 도달하면 slow start 상태를 벗어난다. 두번째 상태는 &lt;b&gt;congestion avoidance&lt;/b&gt;다. 지수적으로 &lt;i&gt;cwnd&lt;/i&gt;를 증가시켰던 slow start 상태와 달리, 지금은 &lt;i&gt;cwnd&lt;/i&gt;를 선형적으로 조금씩 증가시킨다. AIMD의 &amp;lsquo;AI&amp;rsquo;라고 볼 수 있다. 세번째 상태는 &lt;b&gt;fast recovery&lt;/b&gt;다. 패킷 드랍이 감지되었거나, 3번의 중복된 ACK(3-duplicated ACK)를 받으면 TCP 혼잡 제어 상태는 fast recovery 상태로 돌입하여, &lt;i&gt;ssthresh&lt;/i&gt;와 &lt;i&gt;cwnd&lt;/i&gt;를 즉시 절반가량 낮춘다. 이 3가지 상태를 오가며 최적의 &lt;i&gt;cwnd&lt;/i&gt;를 찾는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TCP 혼잡 제어의 발전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초의 TCP 혼잡 제어 알고리즘은 &lt;b&gt;TCP Tahoe&lt;/b&gt;다. 이때의 모델은 패킷 드랍이나 3-dup ACK가 발생하면 항상 &lt;i&gt;cwnd&lt;/i&gt;를 1로 초기화하고 slow start 상태에 들어갔다. 매번 slow start 상태로 다시 들어가니 비효율이 발생했다. 그래서, 개선된 다음 모델인 &lt;b&gt;TCP Reno&lt;/b&gt;는 &lt;i&gt;cwnd&lt;/i&gt;를 1로 만드는 대신 &lt;i&gt;cwnd&lt;/i&gt;를 절반으로 감소시키는 AIMD 전략을 사용했다. 현대의 표준은 &lt;b&gt;TCP Cubic&lt;/b&gt;이다. 이 모델은 &lt;i&gt;cwnd&lt;/i&gt;를 선형적으로 증가시키는 대신 3차 함수(cubic function) 곡선을 그리게 증가시킨다.&lt;br /&gt;&lt;br /&gt;최근 구글에서 개발한 &lt;b&gt;BBR(Bottleneck Bandwidth and Round-trip propagation time)&lt;/b&gt;은 더욱 정교하게 &lt;i&gt;cwnd&lt;/i&gt;를 조절한다. 앞선 전략들은 패킷의 손실만을 &lt;i&gt;cwnd&lt;/i&gt; 조절 근거로 사용했지만, BBR은 네트워크의 대역폭과 지연시간(RTT)등을 종합적으로 고려하여 최적의 &lt;i&gt;cwnd&lt;/i&gt;를 찾는다. 일단 라우터의 버퍼가 터져야 &lt;i&gt;cwnd&lt;/i&gt;를 조절할 수 있는 앞선 방법들보다 뛰어나다.&lt;br /&gt;&lt;br /&gt;TCP 혼잡제어의 구현 위치도 발전되어 왔다. TCP의 혼잡제어는 OS 커널에서 구현되었다. 앱이 socket()을 만들고 send() 등을 호출할 때 커널 모드에서 혼잡 제어 알고리즘이 동작했다. 그러나, 최근에는 커널이 아니라 앱에서 이것을 제어하여 커널 메모리에서 앱 메모리로 패킷을 옮기는 시간을 절약하려는 시도가 있다(user-level TCP). HTTP/3 기반의 QUIC 또한, 응용 계층(application layer)에서 혼잡 제어를 시도한 사례다. QUIC는 TCP가 아니라 UDP 위에서 동작하기 떄문이다. UDP는 혼잡 제어를 전혀 신경쓰지 않는다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TCP는 공평한가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 애플리케이션이 하나의 네트워크 채널을 공유하고 있다고 하자. 이 채널은 애플리케이션들에게 공평하게 할당될까? AIMD 철학에 의하면, 그래야한다. 여러 개의 연결들이 처음에는 서로 다른 대역폭을 갖더라도, 시간이 지나면 공평한 대역폭을 갖도록 수렴한다.&lt;br /&gt;&lt;br /&gt;그러나, &lt;b&gt;현실은 그렇지 않다.&lt;/b&gt; 그 이유는 첫째로, UDP 연결은 혼잡 제어를 신경쓰지 않기 때문이다. UDP 연결들은 TCP 연결들을 불공정하게 압도할 수 있다. 둘째로, 하나의 애플리케이션이 여러 개의 TCP 연결을 맺을 경우, 그렇지 않은 앱들보다 더 많은 대역폭을 확보할 수 있다. 셋째로, RTT가 짧은 쪽이 cwnd를 더 빠르게 증가시켜서 RTT가 긴 쪽을 압도할 수 있다.&lt;/p&gt;</description>
      <category>개발이야기</category>
      <category>TCP</category>
      <category>네트워크</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/61</guid>
      <comments>https://junbyeol.tistory.com/61#entry61comment</comments>
      <pubDate>Thu, 26 Feb 2026 18:25:05 +0900</pubDate>
    </item>
    <item>
      <title>네이버지도에서 매장 정보 수집을 실패하기까지의 고군분투 이야기</title>
      <link>https://junbyeol.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트 목적으로 네이버 지도에서 특정 매장의 정보를 수집하고 싶었다.&lt;br /&gt;2일정도 이 작업에 매진했는데, 결과적으로는 난관에 봉착했고 현재는 실패 상태로 멈췄다.&lt;br /&gt;그 이전까지의 고군분투 과정과 해결하지 못한 포인트를 소개한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 1. 크롤링? 해도 될까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 문제는 크롤링을 해도 되는지 윤리적/법적/정책적 측면의 고민이 들었다.&lt;br /&gt;사이트의 크롤링 허용 여부는 robots.txt를 확인하면 된다. 네이버 지도 사이트의 robots.txt 내용은 아래와 같았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;User-agent: *&lt;br /&gt;Disallow: /&lt;br /&gt;Allow: /$&lt;br /&gt;Allow: /p/$&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용은 누가 크롤링을 하던지, 정확히 지도의 루트 주소(map.naver.com/와 map.naver.com/p/)를 제외하고는 크롤링을 일체 금지한다는 내용이다. 주소 하위의 추가적인 path나 query를 허용하지 않는다. 즉, 네이버 지도에서 검색어를 넣었을 때 나오는 결과 화면의 크롤링을 금지한다는 내용이다.&lt;br /&gt;&lt;br /&gt;robots.txt 파일은 법적 효력이 없다고 한다. 정보가 대중에게 공개된 이상, 크롤링 자체를 법적으로 처벌할 근거는 없는 것으로 이해했다(내가 법적 지식은 부족하기에 확신하지 못하겠다). 하지만, 거대 플랫폼들 사이의 경쟁에서는 크롤링이 문제가 된 경우가 있는 것으로 보인다. &lt;a href=&quot;https://www.donga.com/news/It/article/all/20230404/118678619/1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기어때와 야놀자의 사례&lt;/a&gt;가 그렇다. 또한, 네이버의 사용 정책에서도 robots.txt의 허용 범위에 위배되는 사용 패턴이 감지되면 서비스 사용에 불이익을 감수해야 한다는 내용이 있었다. 고로 상업적 이용은 물론, 개인 프로젝트더라도 찝찝한 구석은 남아있다. 법적인 책임을 물어야 하지는 않더라도, ip 밴 정도는 충분히 당할 수 있어보였다.&lt;br /&gt;&lt;br /&gt;그러나, 구글링을 조금만 해보더라도 네이버 지도 크롤링 사례나 크롤링 도구들이 많이 나온다. 네이버씩이나 되는 대기업이 개인 프로젝트에 법적 책임을 묻는 일은 잘 일어나지 않기 때문으로 보인다. 그래서 양심 상, 그리고 ip밴을 피하기 위한 의도 상 아래의 결정을 내렸다.&lt;br /&gt;-&amp;nbsp; 크롤링을 막는 이유는 지적 재산권 보호의 목적도 있지만, ddos 공격으로부터의 서비스 보호 목적도 있다. 내 HTTP 요청이 네이버 서버에 ddos처럼 다뤄지지 않도록, GET 요청은 손수 애드혹(adhoc)하게 진행한다.&lt;br /&gt;- 내 크롬 브라우저에 랜더링된 정보를 내가 가공하기 좋은 형태로 정제하는 작업은, 사람이 손으로도 할 수 있는 작업이고 네이버 서버와도 무관하므로 자동화한다.&lt;br /&gt;&lt;br /&gt;이렇게 결정하여 만들기로 한 도구는 사실 크롤링이란 용어를 쓰기에도, 다소 멀어졌다. 고로, 아래부터는 크롤링 대신 자동화 도구라는 표현을 사용하겠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 2. 네이버 지도의 주소 파헤치기: 좌표로 검색하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯이, 내 자동화 도구는 특정 주소의 화면에 랜더링된 정보들을 내가 다루기 좋은 형태로 가공하는 일을 한다. 아래의 기능을 원한다.&lt;br /&gt;입력: 매장 검색의 기준이 될 위도(lat), 경도(lng) 좌표&lt;br /&gt;출력: 해당 좌표를 기준으로 검색한 네이버 지도 화면의 정보들을 가공한 형태&lt;br /&gt;&lt;br /&gt;이걸 구현하기 위해서는 네이버 지도 주소의 규칙을 역공학(reverse engineering)해야한다. 네이버 지도 검색 결과 화면을 구할 때 필요한 입력은 &quot;검색 위치&quot;와 &quot;검색어&quot; 두가지다. HTTP 응답(response)들로 미루어보아, 네이버 지도 서버는 이 두 입력을 바탕으로 매장들에게 우선도 점수를 책정하여, 점수가 높은 순으로 사용자에게 매장을 노출 시키는 것으로 보였다. 검색어를 입력했을 때 리다이렉션되는 주소 결과들을 보며, 네이버 지도 주소의 규칙을 파악했다. 아래의 주소로 접근하면 원하는 검색결과 화면을 획득할 수 있었다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767158387273&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyproj import Transformer

# lat, lng, keyword는 주어졌다고 가정

encoded_keyword = urllib.parse.quote(keyword)
transformer = Transformer.from_crs(&quot;EPSG:4326&quot;, &quot;EPSG:3857&quot;)
x, y = transformer.transform(lat, lng)

URL = f&quot;https://map.naver.com/p/search/{encoded_keyword}?c={x},{y},15.22,0,0,0,dh&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;lat, lng에 적절한 위도 경도 값과 keyword에 검색어가 채워지면, URL을 통해 검색 결과 화면으로 이동할 수 있다. 검색어는 한국어를 이용할 경우 URL에 들어가기 적합하도록 인코딩하고, 지도 상 검색 기준 좌표는 위도/경도 좌표와는 다른 좌표를 사용하기 때문에 적절히 변환했다. '15.22'라는 숫자는 내가 임의로 정했다. 지도의 축척을 의미하고, 숫자가 커질수록 더 좁은 면적을 구체적으로 보여주는데 15.22 라는 값이 내가 원하는 매장 정보를 빠짐없이 수집하기 적절해보였기 때문이다. 그 뒤의 숫자들은 보여지는 지도의 유형을 변경하는 값들인데 나에게는 별 도움이 되지 않는 옵션이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 3. 화면에서 필요한 정보 수집하기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1907&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6js9T/dJMcaaYfpIB/gTHewLf63u41vbyZJJG251/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6js9T/dJMcaaYfpIB/gTHewLf63u41vbyZJJG251/img.png&quot; data-alt=&quot;네이버지도 검색화면, 대충 안양을 기준으로 맥주를 검색한 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6js9T/dJMcaaYfpIB/gTHewLf63u41vbyZJJG251/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6js9T%2FdJMcaaYfpIB%2FgTHewLf63u41vbyZJJG251%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1907&quot; height=&quot;922&quot; data-origin-width=&quot;1907&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;네이버지도 검색화면, 대충 안양을 기준으로 맥주를 검색한 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 검색결과 화면 속 정보를 수집해야 하는데 아직 적절한 방법을 찾지 못했다. 두 가지 방법으로 접근했다. 첫번째는 화면에 그려진 html 요소들을 바탕으로 정보를 얻는 방법이다. 두번째는 이 화면을 구성할 때 사용된 HTTP response를 직접 활용하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 1. HTML 요소들로부터 정보 얻기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 크롤링을 할 때, 일반적으로 이 방법을 이용한다. 가게의 이름은 &lt;a href=&quot;https://baka9131.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 블로그&lt;/a&gt;의 코드를 참고하여 쉽게 작성할 수 있었다. 작성한 코드의 내용을 쉽게 말하면, 가게 리스트의 스크롤을 최하단까지 내려 모든 정보를 로딩한 후(네이버 지도는 한 번에 모든 정보를 랜더링하지 않고, 스크롤 상태에 따라 필요한 만큼 정보를 로딩한다), 화면에 있는 모든 가게이름 요소에 xpath를 통해 접근하여 텍스트를 추출하는 것이었다. 그렇게 가게 이름 리스트는 아주 쉽게 획득했다.&lt;br /&gt;&lt;br /&gt;그러나, 나는 가게의 지도상 좌표도 필요했다. 지도의 가게 정보 마커는 CSS의 transform을 통해 제 위치에 그려진다. 그러므로, 현재 보여지고 있는 지도의 좌표, 지도의 배율, 지도 상에서 마커의 위치를 계산하여 좌표를 구하고 해당 마커와 대응되는 가게 이름을 매칭하면 된다. 말로 하면 간단하지만 코드를 작성하려 하니 생각보다 복잡하고, 더 다양하고 구체적인 정보를 획득할 수 있을 것으로 보이는 방법2로 고개를 돌렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법2. HTTP response를 직접 활용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 개발자 도구의 네트워크 탭을 역공학(reverse engineering)하면, 역시나 네이버 지도 클라이언트가 화면을 그리기 위해 필요한 정보들을 파악할 수 있다. 이 정보들이야말로, 네이버 지도가 화면을 그리기 위해 필요한 모든 정보와 정확한 값이 완전히 가공되지 않은 날 것의 상태이기에 내가 다루기 아주 좋은 정보다.&lt;br /&gt;&lt;br /&gt;다양한 위치 기준을 바탕으로 애드혹하게 정보를 수집하기로 했기 때문에, 내가 지도 위치를 옮겨 다니며 정보 수집 상태를 주시하기 편하도록 구글 크롬 확장(extension)의 형태로 개발에 착수했다. 보안 문제로 아주 간편하게 HTTP response body를 훔쳐볼 수 있는 chrome API는 없다. 그래서, 다소 hacky한 접근으로 아래의 방식들을 생각해봤다.&lt;br /&gt;- Chrome Devtool API를 이용하여, 직접 개발자 도구의 네트워크 탭에 접근하는 방식.&lt;br /&gt;- window.fetch 함수를 response body를 출력하도록 내가 작성한 새로운 fetch 함수로 대체하는 방식. 이른바 몽키 패칭(Monkey Patching)&lt;br /&gt;- Chrome background에서 chrome.debugger API를 붙여 사용하는 방식. Debugger가 &quot;Network.responseReceived&quot; 이벤트를 감지했을 때, payload를 꺼내보면 송신된 HTTP response를 얻을 수 있음.&lt;br /&gt;&lt;br /&gt;첫번째 방법은 난이도가 높고, 두번째 방법은 우아하지 않다는 생각이 들어 세번째 방법을 선택했다. 그 결과, 네이버 서버로부터 날아오는 모든 HTTP response를 콘솔에 출력하는 것까지 성공했다. 그러나, 또 다른 문제에 봉착했다.&lt;br /&gt;&lt;br /&gt;네이버 지도는 한 페이지에 80개의 가게를 보여주는데, 사용자가 리스트를 스크롤 할 때 마다 필요에 맞게 20개씩 fetch해오는 방식을 사용하고 있었다. 페이지 최초 랜더링 시, 첫 20개의 가게는 HTTP response로 전달되는 것을 확인했다. 문제는 나머지 60개의 가게 정보가 어느 시점에 로딩되는지 역공학에 실패한 것이었다. 지도 검색 시 서버로부터 내려오는 모든 HTTP response를 확인했으나 가게 정보를 찾을 수 없다. 아마도, 나머지 정보들은 protobuf 혹은 다른 방식으로 인코딩되어 전송되는 것이 아닐까 추측되는데, 그렇다면 내가 원하는 정보가 어디에 있고 어떻게 인코딩된 값인지를 찾아 그것을 디코딩하는 로직을 추가하는 것이 또 난이도 있는 작업이었다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼, 현재까지 네이버 지도로부터 내가 원하는 정보를 수집하는 작업은 속 시원한 방법을 찾지 못했다. 조만간, 깔끔한 해답을 찾아서 새로운 글을 작성할 수 있기를 바란다.&lt;/p&gt;</description>
      <category>개발이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/59</guid>
      <comments>https://junbyeol.tistory.com/59#entry59comment</comments>
      <pubDate>Wed, 31 Dec 2025 15:54:43 +0900</pubDate>
    </item>
    <item>
      <title>파이썬 프로젝트 우아하게 시작하기: pyenv, poetry</title>
      <link>https://junbyeol.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;여러분의 맥북에는 이미 파이썬(Python)이 깔려있다. 맥 OS의 여러 유틸리티들이 파이썬을 필요로 하기 때문이다.&lt;br /&gt;그럼 파이썬 프로젝트를 시작할 때, 파이썬을 별도로 설치하지 않아도 되는 것일까?&lt;br /&gt;그렇지 않다. 프로젝트마다 요구하는 파이썬의 버전이 다르기 때문이다.&lt;br /&gt;이 글은 파이썬 프로젝트를 우아하게 시작하려면, 반드시 이용해야 할 버전 관리 도구(version manager)와 의존성 관리 도구(dependency manager)의 필요성과 간단한 사용 방법을 소개한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이썬의 버전관리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;버전관리의 필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트마다 파이썬 버전을 맞춰야하는 이유가 무엇일까? 무조건 최신의 파이썬을 쓰면 안되는 것일까?&lt;br /&gt;&lt;br /&gt;- 파이썬 버전을 올리면 지원하는 기본 라이브러리가 사라지거나, 코드의 기능이 달라질 수 있다. 내 컴퓨터에서는 잘되는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;from xxx import xxx&lt;/span&gt; 라인이 옆 사람 컴퓨터에서는 안되는 일이 일어날 수 있다. 또 하나 예를 들자면, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;print(&quot;abc&quot; + b&quot;def&quot;)&lt;/span&gt; 라는 라인은 Python 2 에서는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&quot;abcdef&quot;&lt;/span&gt;를 출력하지만, Python 3에서는 Type Error를 반환한다. &lt;span style=&quot;background-color: #dddddd;&quot;&gt;b&quot;def&quot;&lt;/span&gt;는 bytes를 의미한다. 내 코드에는 그럴 문제 없다고 생각하지 몰라도, 내 코드가 의존하는 다른 패키지가 문제가 있을 수 있다.&lt;br /&gt;&lt;br /&gt;- 최신의 파이썬 버전에서는 실험적인 기능들을 빠르게 사용할 수 있다. 한편, 이 기능들은 &quot;실험적&quot;인 만큼 보안상의 문제나 기능상의 문제가 있을 수 있다.&lt;br /&gt;&lt;br /&gt;- 앞서, 맥 OS 시스템에 기본으로 깔려있는 파이썬이 있다고 했다. 이 파이썬의 버전을 성급히 변경했다가는 맥북의 시스템 유틸리티의 정상적인 동작을 보장할 수 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;파이썬 버전 개념&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0IuD7/dJMcaihFlJn/3plvD2Vm13cDAsVTdStcPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0IuD7/dJMcaihFlJn/3plvD2Vm13cDAsVTdStcPk/img.png&quot; data-alt=&quot;2025년 12월 기준, https://devguide.python.org/versions/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0IuD7/dJMcaihFlJn/3plvD2Vm13cDAsVTdStcPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0IuD7%2FdJMcaihFlJn%2F3plvD2Vm13cDAsVTdStcPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1698&quot; height=&quot;748&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2025년 12월 기준, https://devguide.python.org/versions/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬은 버전마다 5단계의 생애주기를 거쳐 관리된다. 이에 관한 내용은 이 주소에서 확인할 수 있고, 해당 내용의 번역과 첨언을 아래에 적어둔다.&lt;br /&gt;1. feature&lt;br /&gt;- 베타 오픈도 되기 전 단계. 이 단계는 릴리즈(binary)마다 새로운 기능, 오류 수정, 보안 개선 코드 등을 위한 코드 변경이 발생한다.&lt;br /&gt;2. prerelease&lt;br /&gt;- 베타 오픈 단계. 새로운 기능이 추가되지는 않지만 기능의 수정(중요한 변경 사항 포함), 오류 수정, 보안 개선 등을 위한 코드 변경이 발생한다.&lt;br /&gt;3. bugfix&lt;br /&gt;- 버전이 정식으로 출시된 단계. 오류 수정과 보안 개선은 이뤄진다. 새로운 릴리즈(binary)는 약 2달마다 출시되며, 유지(maintenance) 혹은 안정(stable) 단계라고도 불린다.&lt;br /&gt;4. security&lt;br /&gt;- 출시 후 2년이 경과된 단계. 보안 문제만이 반영되며, 더 이상 새로운 릴리즈가 나오지 않는다. 필요에 따라 코드 변경이 있을 수는 있지만 빌드는 사용자가 직접 해야 한다(source-only versions can be realeased as needed).&lt;br /&gt;5. end-of-life&lt;br /&gt;- 출시 후 5년이 경과된 단계. 더 이상의 릴리즈는 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파이썬 버전 매니저: pyenv&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/pyenv/pyenv&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Pyenv&lt;/a&gt;는 인기있는 Python version manager다. 대안으로는 &lt;a href=&quot;https://asdf-vm.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;asdf&lt;/a&gt;도 사용해봤다. (이름이 괴상한) asdf는 다양한 프로그래밍 언어와 CLI 툴들의 버전 관리를 한 번에 해준다는 장점이 있다. Pyenv의 사용방법을 아래에 간단히 소개한다. 구체적인 사용방법은 공식 문서를 참고하자.&lt;/p&gt;
&lt;pre id=&quot;code_1767081064823&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# brew로 pyenv 설치
brew update &amp;amp;&amp;amp; brew install pyenv

# pyenv 업그레이드(pyenv에 원하는 Python 버전이 없을때)
brew upgrade pyenv

# 설치된 python 리스트 확인 및 3.13.0 설치/삭제
pyenv versions
pyenv install 3.13.0
pyenv uninstall 3.13.0

# 기본 python을 3.13.0으로 설정
pyenv global 3.13.0

# (프로젝트 루트 경로에서) 이 프로젝트의 python 버전을 3.13.0으로 설정(.python-version 파일이 생긴다)
pyenv local 3.13.0&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이썬 의존성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 관리의 필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 프로젝트는 하위 의존성(dependency)을 갖는다. 자동차를 만드는데 필요한 바퀴를 고무 제련단계부터 하고 있으면, 완성된 자동차가 나오기까지 너무 비효율적이다. 그래서 자동차를 만들 때 바퀴는 시제품을 이용하는 것처럼, 프로젝트들은 미리 만들어진 의존성 패키지를 이용한다. 기본적으로 pip라는 python 기본 패키치 설치 도구를 통해 의존성 패키지를 설치하고, 프로젝트마다 이용할 의존성 목록이 기록되는 requirements.txt 파일을 수동으로 관리해야 한다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;만약 pyenv를 통해 우리 프로젝트가 python 3.13 을 사용하도록 했다면, 그 프로젝트 경로 내에서 쓰이는 pip는 3.13버전의 pip가 된다. 만약 서로 다른 두 프로젝트가 우연히 같은 버전의 파이썬을 사용한다고 하자. 별다른 관리 도구를 사용하지 않는다면, 두 프로젝트의 의존성들이 같은 pip 하위 경로에 섞이게 된다. 이런 상황을 피하기 위해, 패키지를 pip 하위가 아니라 프로젝트 내에 포함하는 것이 좋다. 패키지마다 가상환경을 갖고 있다고 생각하고, 서로 다른 프로젝트의 패키지들 설치 경로를 격리해야한다. 대표적으로 &lt;a href=&quot;https://docs.python.org/ko/3/library/venv.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;venv&lt;/a&gt;가 표준으로써 해당 기능을 지원한다. 비슷한 이름의 &lt;a href=&quot;https://virtualenv.pypa.io/en/latest/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;virtualenv&lt;/a&gt;는 비표준이다.&lt;br /&gt;&lt;br /&gt;제일 편한 방법은 &lt;a href=&quot;https://python-poetry.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Poetry&lt;/a&gt;를 이용하는 것이다. Poetry는 패키지 설치 뿐만 아니라 파이썬 프로젝트의 패키징(배포)까지 관리해주는 도구다. Javascript에 익숙한 사람에게는 npm과 비슷하다(패키지를 관리해준다는 측면에서)고 생각하면 이해가 쉽다. pyproject.toml은 프로젝트의 의존성 관리에 필요한 정보들이 작성되며, poetry가 자동으로 관리해준다. 사람이 직접 이 파일을 수정하지 않아도 된다. Npm의 package.json이 이것과 대응된다. Conda도 Poetry의 대안이 될 수 있지만, GPU 리소스와 data science, deep learning 쪽에 특화되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;poetry 이용방법&lt;/h3&gt;
&lt;pre id=&quot;code_1767083176651&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# poetry로 프로젝트 시작
poetry init

# poetry로 프로젝트 실행(poetry가 하위 의존성들을 관리하므로, 반드시 이렇게 실행해야한다!)
poetry run python main.py

# poetry에 의존성 xx 추가/삭제
poetry add xx
poetry remove xx

# pyproject.toml을 참고하여, 필요한 의존성들 모두 설치
poetry install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 &lt;a href=&quot;https://python-poetry.org/docs/cli&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;를 참고하자.&lt;/p&gt;</description>
      <category>개발이야기/토막글</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/58</guid>
      <comments>https://junbyeol.tistory.com/58#entry58comment</comments>
      <pubDate>Tue, 30 Dec 2025 17:42:12 +0900</pubDate>
    </item>
    <item>
      <title>구직까지 6개월, 무엇을 해야 할까?</title>
      <link>https://junbyeol.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;6개월 뒤 저는 IT 회사에 백엔드 엔지니어로 지원할겁니다.&lt;br /&gt;짧다면 짧고, 길다면 긴 이 6개월, 무엇을 해야 저는 원하는 회사에 합격함은 물론, 제가 만족할만한 성장을 이룰 수 있을까요?&lt;br /&gt;무엇을 할지 고민하기 전, 목표를 분명히 하기로 했습니다.&lt;br /&gt;설령 &quot;오픈소스를 읽을거야!&quot; 라고 하면 오픈소스를 읽어서 무슨 목표를 달성하려고 하는지 이해하고 싶은 것입니다.&lt;br /&gt;그래서 GPT와 함께 고민해 본 결과, 소프트웨어 엔지니어에게 필요한 역량을 3+1가지로 정리해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SW 엔지니어에게 필요한 역량&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;첫째, 기술적 역량.&lt;br /&gt;Background 지식을 통해 요구사항을 실제로 설계/구현하는 능력입니다.&lt;br /&gt;CS 기초(fundamental) 지식, 언어/프레임워크 숙련도, 요구사항 구현 능력, 디버깅/프로파일링(profiling) 능력 등이 포함됩니다.&lt;br /&gt;&lt;br /&gt;둘째, 메타 스킬(meta-skill).&lt;br /&gt;기술, 도메인에 상관없이 통용되는 엔지니어링적 사고를 말합니다.&lt;br /&gt;이 능력이 모자랐던 주니어 시절 저는 문제를 풀기 위한 기술과 방법에 집착하는 한편, 시니어분들은 나무가 아니라 숲을 보고 문제의 본질을 먼저 이해합니다. 그 후, 의사결정의 임팩트(타 문제에 미치는 긍정적/부정적 영향, side-effect)와 선택지 별 트레이드오프(trade-off) 상황을 이해하고 의사결정의 근거로 삼습니다.&lt;br /&gt;또, 이 능력이 뛰어난 사람은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;기존의 기술들을 연상하며&lt;span&gt; &lt;/span&gt;&lt;/span&gt;새로운 기술을 접해도 쉽게 받아들입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제 정의 능력(해결해야할 문제, 제약조건, 세부 목표 등을 설정, 이해)&lt;/li&gt;
&lt;li&gt;시스템 이해 능력(나무가 아니라 숲을 보는 능력)&lt;/li&gt;
&lt;li&gt;엔지니어링 판단력(문제 상황 속 선택지 파악, 임팩트 파악, 트레이드오프 이해)&lt;/li&gt;
&lt;li&gt;코드를 읽는 능력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;셋째, 소프트스킬(soft skill)&lt;br /&gt;다른 사람과의 협업 능력, 조직 내 의사결정 방법, 타인과의 신뢰도 유지 요령을 말합니다.&lt;br /&gt;간과하기 쉽지만, 조직 내에서 살아남기 위해서는 꼭 필요한 역량입니다. 아무리 기술적인 경험이 많고 역량이 뛰어나도, 소프트스킬이 부족하여 생산성, 심지어는 생존 여부에 하자가 발생하는 사람이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설명/설득 능력(기술적인 배경 지식이 있는 사람과 없는 사람 모두에게)&lt;/li&gt;
&lt;li&gt;피드백 제공, 수용&lt;/li&gt;
&lt;li&gt;문서화&lt;/li&gt;
&lt;li&gt;인성, 예의, 센스&lt;/li&gt;
&lt;li&gt;명확한 책임 소재 구분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 셋과 좀 궤가 다른 넷째 역량은, 자기관리 능력입니다.&lt;br /&gt;조직 내에서 오래, 꾸준히 근무하기 위해서는 반드시 필요함을 뼈저리게 실감했습니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;물리적인 체력&lt;/li&gt;
&lt;li&gt;번아웃 관리&lt;/li&gt;
&lt;li&gt;지나친 완벽주의 버리기&lt;/li&gt;
&lt;li&gt;지속적이고 일관적인 생산성 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내가 앞으로 해볼 법한 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 본론으로 돌아와서, 저는 6개월 동안 무엇을 해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;난이도 하(지금 당장도 무엇이든 할 수 있는 수동적인 일)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 엔지니어링 책 읽기&lt;br /&gt;사놓고 읽지 않은 책이 많습니다. 책 페이지만 넘기면서 이해해도 도움이 될 것입니다.&lt;br /&gt;- 기술적역량 ++++&lt;br /&gt;- 메타기술 +&lt;br /&gt;- 소프트스킬 0&lt;br /&gt;&lt;br /&gt;2. 전공과목 복습하기&lt;br /&gt;학교에서 배운 CS 배경지식을 다시 복습하는 과정입니다.&lt;br /&gt;- 기술적역량 ++&lt;br /&gt;- 메타기술 +&lt;br /&gt;- 소프트스킬 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;난이도 중(하루이틀 정도는 확실하게 집중해야 할 수 있는 일)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 셀프브랜딩&lt;br /&gt;거창하게 이름붙였지만, 현실적으로는 이력서 쓰기, 인성 면접을 위한 마인드셋 준비, 포트폴리오 준비 등을 말합니다. 당장할 필요는 없으니 회사 지원 직전에 시작해도 충분할 것 같습니다.&lt;br /&gt;- 기술적역량 0&lt;br /&gt;- 메타기술 0&lt;br /&gt;- 소프트스킬 +&lt;br /&gt;&lt;br /&gt;2. 오픈소스 읽기&lt;br /&gt;Next.js 같은 서버 프레임워크는 물론, Redis, git, vim, React.js, nginx 등 오픈소스들의 Github repository를 파악하는 일입니다. 개인적으로 한 번 쯤 해보고 싶었던 공부입니다.&lt;br /&gt;- 기술적역량 +++&lt;br /&gt;- 메타기술 +++&lt;br /&gt;- 소프트스킬 +&lt;br /&gt;&lt;br /&gt;3. 시스템 아키텍쳐 해부&lt;br /&gt;Youtube, Spotify, Instagram 등 세계적인 IT 서비스들의 아키텍쳐를 공부하는 일입니다. 아직 해보지 않아서, 가능한지 모르겠으나 당장 실무 면접에도 직접적인 도움이 될 공부일 것입니다.&lt;br /&gt;- 기술적역량 +++&lt;br /&gt;- 메타기술 +++&lt;br /&gt;- 소프트스킬 +&lt;br /&gt;&lt;br /&gt;4. 알고리즘 문제 풀기&lt;br /&gt;가성비가 좋지는 않지만, 알고리즘 문제를 풀며 얻는 스킬들은 잠재적으로도 저에게 도움이 되고, 코딩테스트를 보는 일부 회사의 프로세스에 크게 도움이 될 것입니다.&lt;br /&gt;- 기술적역량 +&lt;br /&gt;- 메타기술 +&lt;br /&gt;- 소프트스킬 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;난이도 상(꽤나 신경쓰고 성실해야 할 수 있는 일)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 유튜브, 블로그 기록&lt;br /&gt;셀프브랜딩 면에서도 도움이 되지만, 저는 휘발되는 지식을 지속가능하게 기록하고 남들에게 공유/설명하는 일의 가치에 집중해서 하고 싶습니다.&lt;br /&gt;- 기술적역량 ++&lt;br /&gt;- 메타기술 +&lt;br /&gt;- 소프트스킬 ++&lt;br /&gt;&lt;br /&gt;2. 개인 프로젝트&lt;br /&gt;프로젝트 규모에 따라 '난이도 중'으로 배치할 수도 있을 것 같습니다. 개인적으로 그럴듯한 제 소유의 공개 프로젝트 하나 없는 것은 저에게 큰 아쉬움이라 생각합니다.&lt;br /&gt;- 기술적 역량 +++&lt;br /&gt;- 메타기술 +++&lt;br /&gt;- 소프트스킬 0&lt;br /&gt;&lt;br /&gt;3. 오픈소스 기여&lt;br /&gt;오픈소스에 제가 짠 코드가 한 줄이라도 들어갈 수 있다면, 저에게 큰 스펙이 될 것입니다.&lt;br /&gt;- 기술적 역량 ?&lt;br /&gt;- 메타기술 ?&lt;br /&gt;- 소프트스킬 +&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;제 앞으로 6개월의 할 일거리들을 거창하게 정리해봤습니다.&lt;br /&gt;더 이상은 구체적으로 계획하기 보다, 뭐든 일단 해보는 것이 중요할 것이라 생각합니다.&lt;br /&gt;앞으로 제 인생에 언제 또 찾아올지 모를 여유로운 6개월인 만큼, 후회없이 사용하고 싶습니다.&lt;/p&gt;</description>
      <category>일상이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/57</guid>
      <comments>https://junbyeol.tistory.com/57#entry57comment</comments>
      <pubDate>Mon, 22 Dec 2025 21:57:21 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 5. 정규 언어의 성질(미완성)</title>
      <link>https://junbyeol.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 정규언어의 다양한 성질들에 대해 배운다. 첫째로, 다룰 것은 정규 언어라면 항상 성립하는 pumping lemma이다. 이 것을 통해, 우리는 어떤 언어가 정규언어인지 아닌지 쉽게 증명할 수 있다. 둘째는, 정규 언어의 닫힘성(closure)이다. 앞서, union, concatenation, Kleene star의 닫힘성을 보였듯이, 더 많은 연산자에 대한 닫힘성을 살펴본다. 셋째로, 두 오토마타의 동등성을 비교하는 방법에 대해 알아본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pumping Lemma&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;어떤 정규언어 A에 대해, 항상 상수 p(pumping length라고 부름)가 존재하여:&lt;br /&gt;- 길이가 p 이상인 A의 모든 문자열 w는&lt;br /&gt;- w를 $w=xyz$ 세 부분으로 나눌 수 있고,&lt;br /&gt;- 다음 조건을 만족한다.&lt;br /&gt;&lt;br /&gt;1. $|y| \ge 1 $&lt;br /&gt;2. $|xy| \le p $&lt;br /&gt;3. 모든 $0$ 이상의 i에 대해 $xy^iz \in A$&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다소 난해하게 느껴질 수 있지만, 이 pumping lemma는 정규언어라면 항상 만족하는 성질이다. 그래서, 어떤 언어가 정규언어가 아님을 증명할 때 아주 유용하다. Pumping Lemma의 증명 이전에, 예제를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Q. B = $\{0^n1^n|n \ge 0\}$이 정규언어가 아님을 보이시오.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 모든 p에 대해서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. w의 길이가 p 이상이도록, $w=0^p1^p$라고 잡자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 그러면 &lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;$|y| \ge 1 $,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;$|xy| \le p $를 만족하는&amp;nbsp;&lt;/span&gt;모든 w=xyz 세 부분으로의 분할에서, y는 항상 $0^i$로 표현된다. $(i \ge 1)$&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 그러면 $xy^2z=0^{p+i}1^p$가 되므로, $xy^iz \notin B$가 되는 어떤 $i$가 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Pumping lemma에 의해서, B는 정규언어가 아님!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적으로도 $0^n1^n$은 0의 개수에 따라 무한하게 많은 상태가 준비되어야 하므로, 유한 오토마타로 표현이 불가능함을 알 수 있지만, pumping lemma를 통해 수학적으로 엄밀하게 증명했다. Pumping Lemma를 이용하면 어떤 언어가 정규언어가 아님을 이렇게 보일 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pumping Lemma의 증명에는 비둘기집 원리를 이용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blAMSe/dJMb9QkYtCT/LIVT0E1yE8geDR5VQ7ta0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blAMSe/dJMb9QkYtCT/LIVT0E1yE8geDR5VQ7ta0K/img.png&quot; data-alt=&quot;Figure 1. Proof of Pumping Lemma, 출처 [1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blAMSe/dJMb9QkYtCT/LIVT0E1yE8geDR5VQ7ta0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblAMSe%2FdJMb9QkYtCT%2FLIVT0E1yE8geDR5VQ7ta0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;193&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 1. Proof of Pumping Lemma, 출처 [1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ xyz $는 문자열 $w$를 분할한것이다. 그림에서도 알 수 있듯이 문자열이 $xyz$가 아니라 $xz$, $xy^2z$, ..., $xy^nz$ 들로 y를 pumping 해도 모두 오토마타가 인식하는 언어일 것이다. 그런데, 충분히 길이가 긴 문자열에 대해서, y와 같은 사이클은 항상 존재한다! FA의 상태의 개수는 유한하므로, 상태의 전체 개수보다 길이가 긴 computation history에는 적어도 하나의 상태는 두 번 방문하게 되기 때문이다. 첫 방문과, 두번째 방문 사이가 그림의 y처럼 사이클을 이룬다. 이런 상황을 만드는 pumping length p는 항상 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규 언어에 대한 닫힘성(Closure)들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 두 정규 언어의 합집합(Union)은 정규언어다.&lt;br /&gt;2. 두 정규 언어의 교집합(Intersection)은 정규언어다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3.&lt;span&gt;&amp;nbsp;어느&amp;nbsp;&lt;/span&gt;&lt;/span&gt;정규 언어의 여집합(Complement)은 정규언어다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;두 정규 언어의 차집합(Difference)은 정규언어다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5.&lt;span&gt;&amp;nbsp;어느&lt;/span&gt;&lt;/span&gt; 정규 언어의 역(Reversal)은 정규언어다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;6.&lt;span&gt;&amp;nbsp;어느&lt;/span&gt;&lt;/span&gt; 정규 언어의 Kleene Star 연산결과는 정규언어다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;7.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;두 정규 언어의 결합(Concatenation)은 정규언어다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;8.&lt;span&gt;&amp;nbsp;어느&lt;/span&gt;&lt;/span&gt; 정규 언어의 Homomorphism 연산 결과 정규언어다. (어떤 심볼 하나를 문자열로 치환)&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;9.&lt;span&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;어느&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;정규 언어의 Inverse Homomorphism 연산 결과 정규언어다. (어떤 문자열을 다른 심볼 하나로 치환)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;정규언어는 위 연산들에 대하여 닫혀있다. 위 연산들의 닫힘성을 엄밀하게 증명하는 요령은 모두 동일하다. 일단, 두 정규 언어를 인식하는 오토마타 $M_1, M_2$를(혹은 $M$ 하나를) 정의하고, 그 성분들을 활용해서 새로운 오토마타 $M'$을 정의하면 된다. 그리고, 그 오토마타 $M'$이 정말 우리가 원하는 연산결과가 맞다는 정당성(correctness)을 보이면 된다. 기존 오토마타가 수용하는 $w=w_1w_2...w_n$이 이 문자열이 $M'$에서도 최종상태(final state)로 도착함을 보이는 것이다. 이미 &lt;a href=&quot;https://junbyeol.tistory.com/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;에서 동일한 증명을 소개한 바 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;두 오토마타의 동등성&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;두 오토마타가 동등(equivalent)하다는 것은, 두 오타마타의 언어가 같다는 것을 의미한다고 이전 글에서 소개했다. 주어진 두 오토마타가 동등한지 어떻게 알 수 있을까? 우리는 어떤 오토마타와 동등하되, 기존 오토마타보다 상태를 줄인 오토마타를 구하는 방법을 배울것이다. 그리고, 지금 소개할 마법같은 과정은 주어진 오토마타의 상태를 가능한 최소로 줄인 minimum-state 오토마타를 구한다. 신기하게도, 이 마법의 과정을 거쳐 나오는 오토마타의 minimum-state는 유일하다. 그래서, 서로 다른 오토마타들의 각각의 minimum-state 오토마타를 구했는데, 그 결과가 같다면 두 오토마타는 동등한 것이다. 두 오토마타가 동등한데, minimum-state 오토마타가 다르게 나오는 일은 없다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;상태들의 동등성&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;어느 오토마타에 서로 다른 두 상태 $p,q$에 대하여 아래의 동시에 조건을 만족하면 p와 q는 동등하다(equivalent) 혹은 구분불가능(indistinguishable)하다고 한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- $\hat{\delta(p,w)} \in F$ 인 모든 w에 대하여 $\hat{\delta(q,w)} \in F$.&lt;br /&gt;- 역도 성립.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;만약 문자열 w가 어떤 상태는 최종상태로 보내고, 나머지 상태는 보내지 않아서 위 조건의 반례가 되는 경우, w가 p와 q를 구분한다(distinguish)고 한다. 예를 보면 이해하기 쉽다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxEiSl/dJMb9Ycatt9/9s8aoZPu2hwwrBbOXCblC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxEiSl/dJMb9Ycatt9/9s8aoZPu2hwwrBbOXCblC0/img.png&quot; data-alt=&quot;Figure 2. Example for Indistinguishable States. 출처[1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxEiSl/dJMb9Ycatt9/9s8aoZPu2hwwrBbOXCblC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxEiSl%2FdJMb9Ycatt9%2F9s8aoZPu2hwwrBbOXCblC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;320&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 2. Example for Indistinguishable States. 출처[1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 다이어그램에서 A와 E를 보자.&lt;br /&gt;- 문자열 &quot;1&quot;은 A도 E도 최종상태인 C로 보내지 않는다. 그러므로, &quot;1&quot;은 A와 E를 구분하지 않는다.&lt;br /&gt;- 문자열 &quot;0&quot;이나 $\epsilon$도 A와 E를 구분하지 않는다.&lt;br /&gt;- 문자열 &quot;01&quot;은 A와 E 모두 최종상태인 C로 보낸다. 그러므로, &quot;01&quot;도 A와 E를 구분하지 않는다.&lt;br /&gt;- 사실 모든 문자열은 A와 E를 동시에 최종상태로 보내거나, 동시에 보내지 않는다. 그래서, A와 E는 동등하다.&lt;br /&gt;- 마찬가지로, B와 H도 동등하다.&lt;br /&gt;- 한편, &quot;0&quot;은 A를 최종상태로 보내지 않지만, B는 최종상태로 보내므로, A와 B는 동등하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 존재하는 모든 두 상태들끼리의 동등성을 알고 싶기 때문에, 이것을 체크하는 표를 만들것이다. 구구단의 표처럼 가로세로축에 모든 상태들이 포함된 2차원의 표이다. 대신 $p=q$인 경우는 의미가 없고, p와 q가 동등하면, 당연히 q와 p도 동등하므로 표 절반만 채우면 된다. 만약 상태가 5개 있는 오토마타라면, 전체 25칸중 5칸은 제외하고, 그 절반인 10칸을 채우면 되는것이다. 아래는 그 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. $q_0$에서 어떻게 해도 도달할수 없는 상태들을 지운다.(들어가는 화살표가 없는 상태)&lt;br /&gt;2. 일단, $F$에 속하는 상태와 와 $Q-F$에 속하는 상태는 동등할수 없으므로 X라고 표시한다.&lt;br /&gt;3. 어떤 심볼 a에 대하여, $\delta(p,a)$와 $\delta(q,a)$가 X로 표시되어 있으면, $(p,q)$도 X표시한다.&lt;br /&gt;4. 더 이상 X표시 할게 없을떄까지 반복한다.&lt;br /&gt;5. 남은 것은 모두 동등하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;이제 동등한 것 끼리 묶음을 구할 수 있다. 예를 들어 Figure 2의 오토마타에 이 과정을 수행하면 Figure 3과 같다. 이 표를 통해서 $\{A,E\}, \{,B,H\},\{D,F\}$가 각각 서로 동등하다는 것을 알 수 있다. 동등한 것을 하나의 블럭(block)으로 생각하면, A~H는 $\{A,E\},\{B,H\},\{C\},\{D,F\},\{G\}$ 5개의 블럭으로 나눠진 분할(partition)이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUY5Dz/dJMb81UhPyp/Vn3ucHSCcVhcKUE5h8BUT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUY5Dz/dJMb81UhPyp/Vn3ucHSCcVhcKUE5h8BUT1/img.png&quot; data-alt=&quot;Figure 3. The Table of Distinguishability, 출처[1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUY5Dz/dJMb81UhPyp/Vn3ucHSCcVhcKUE5h8BUT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUY5Dz%2FdJMb81UhPyp%2FVn3ucHSCcVhcKUE5h8BUT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;285&quot; height=&quot;244&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 3. The Table of Distinguishability, 출처[1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오토마타의 동등성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdsmaw/dJMb9L42nCw/JGk7cvWfzJ9p5jyLHpBVYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdsmaw/dJMb9L42nCw/JGk7cvWfzJ9p5jyLHpBVYk/img.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;316&quot; data-is-animation=&quot;false&quot; style=&quot;width: 23.2801%; margin-right: 10px;&quot; data-widthpercent=&quot;23.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdsmaw/dJMb9L42nCw/JGk7cvWfzJ9p5jyLHpBVYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcdsmaw%2FdJMb9L42nCw%2FJGk7cvWfzJ9p5jyLHpBVYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;224&quot; height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkGdMX/dJMb9YcawU9/r9sOACaDrLzUlt8BNV1mX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkGdMX/dJMb9YcawU9/r9sOACaDrLzUlt8BNV1mX0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;153&quot; data-origin-width=&quot;352&quot; data-widthpercent=&quot;76.45&quot; style=&quot;width: 75.5571%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkGdMX/dJMb9YcawU9/r9sOACaDrLzUlt8BNV1mX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkGdMX%2FdJMb9YcawU9%2Fr9sOACaDrLzUlt8BNV1mX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Figure 4. Equivalence of Automata, 출처 [1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Figure 4의 왼쪽의 두 오토마타는 동등하다. 두 오토마타를 하나의 오토마타로 생각하고(시작 상태가 두 개인 오토마타라는게 말이 안되긴 하지만), 오른쪽 그림의 동등성 표를 완성한다. 이를 통해 A와 C가 동등함을 알 수 있다. 그런데 어느 두 오토마타의 시작 상태가 동등하다는 것은 두 오토마타가 동등하다는 것을 의미한다! 왜냐하면 그말인즉슨, A를 최종상태로 보내는 어떤 문자열 w는 C도 최종상태로 보낸다는 뜻이고, 역도 성립할 것이며, 그것은 두 오토마타의 언어가 같다는 말과 같기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오토마타의 최소화(Minimization)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 Figure 3의 분할 ${A,E},{B,H},{C},{D,F},{G}$를 논의로 데려오자. 우리는 이 분할의 각 블럭들을 하나의 상태로 생각하고 새로운 오토마타를 만들 수 있다. 기존 오토마타의 시작상태와 최종상태는 $A$와 $G$였으므로, 새로운 오토마타의 시작블럭도 $\{A,E\}$와 $\{G|}$로 한다. 전이들도 기존 오토마타들의 전이를 그대로 합치면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IkM3Z/dJMb9LRvgy2/B2O4qd3pvFIG48YKKY9kO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IkM3Z/dJMb9LRvgy2/B2O4qd3pvFIG48YKKY9kO1/img.png&quot; data-alt=&quot;Figure 5. Minimization of Automata, 출처 [1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IkM3Z/dJMb9LRvgy2/B2O4qd3pvFIG48YKKY9kO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIkM3Z%2FdJMb9LRvgy2%2FB2O4qd3pvFIG48YKKY9kO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;356&quot; height=&quot;317&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 5. Minimization of Automata, 출처 [1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 DFA로부터 이 과정을 거쳐 만들어진 새로운 DFA는 기존 DFA와 항상 동등하다. 또, 이렇게 만들어진 DFA보다 더 적은 종류의 상태를 가진 동등한 DFA는 없다. 달리 말하면, 이렇게 만들어진 DFA 상태 개수는 최소다. 왜 그런지 해소되지 않은 의문들을 정리해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정 전후의 오토마타가 동등한가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 오토마타가 정말 최소개수의 상태를 갖는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 오토마타는 유일한가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동등하지만 서로 다른 DFA로부터 각각 이 과정을 거쳐 만든 최소 DFA는 항상 같을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Myhill-Nerode&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/56</guid>
      <comments>https://junbyeol.tistory.com/56#entry56comment</comments>
      <pubDate>Sun, 14 Dec 2025 19:15:44 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 19. 모든 언어는 튜링-인식 가능한가?: 대각선 논법(작성중)</title>
      <link>https://junbyeol.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모든 언어는 튜링-인식(Turing-recognizalbe)한가? 답은 &lt;b&gt;&quot;아니다&quot;&lt;/b&gt;. 전체적인 증명 과정을 먼저 소개하고, 하나하나 관련 개념을 설명해보도록 하겠다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;$\{0,1\}$의 알파벳에서&lt;br /&gt;1. $\{0,1\}^*$, 혹은 가능한 모든 튜링머신들의 집합은 $\mathbb{N}$과 같은 크기를 같는다.&lt;br /&gt;2. $\{0,1\}$으로 만들 수 있는 모든 문자열의 집합, 혹은 가능한 모든 언어들의 집합은 $2^\mathbb{N}$과 같은 크기를 같는다.&lt;br /&gt;3. $2^\mathbb{N}$은 불가산집합(uncountable set)이지만, $\mathbb{N}$은 가산집합(countable set)이다.&lt;br /&gt;4. 따라서, 가능한 모든 언어가 가능한 모든 튜링머신보다 그 종류가 많다.&lt;br /&gt;5. 따라서, 튜링-인식기(Turing-recongizer)가 없는 언어가 존재한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;countable과 uncountable의 정의&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;1. $\{0,1\}^*$, 혹은 가능한 모든 튜링머신들의 집합은 $\mathbb{N}$과 같은 크기를 같는다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;2. $\{0,1\}$으로 만들 수 있는 모든 문자열의 집합, 혹은 가능한 모든 언어들의 집합은 $2^\mathbb{N}$과 같은 크기를 같는다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;3. $2^\mathbb{N}$은 불가산집합(uncountable set)이지만, $\mathbb{N}$은 가산집합(countable set)이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;4. 따라서, 가능한 모든 언어가 가능한 모든 튜링머신보다 그 종류가 많다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;5. 따라서, 튜링-인식기(Turing-recongizer)가 없는 언어가 존재한다.&lt;/span&gt;&lt;/p&gt;</description>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/55</guid>
      <comments>https://junbyeol.tistory.com/55#entry55comment</comments>
      <pubDate>Sat, 13 Dec 2025 23:15:33 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 18. 범용 튜링머신(Universal TM)과 알고리즘</title>
      <link>https://junbyeol.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 지금까지 특정 기능만 수행하는 튜링머신을 다뤘다. 이것을 고정 튜링머신(Hardwired TM)이라고 부른다. 어떤 1번 TM이 w에 대해서 수행하는 일도, 2번 TM이 y에 대해 수행하는 일도, 3번 TM이 z에 대해 수행하는 일도 다 해내는 범용 튜링머신(Universal TM)에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;범용 튜링머신(Universal TM)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 어떤 튜링머신 M에 대한 정보와 M이 원래 입력으로 받는 문자열 w를 함께 입력으로 받아서 동작하는 프로그래머 TM(programmable TM)이 있다면, 그것이 곧 범용 튜링머신(UTM)이 될 것이다. UTM은 M이 하는일을 흉내내서 w에 대한 연산을 처리한다. 이 UTM의 동작 방식은 쿠르트 괴델(Kurt Friedrich G&amp;ouml;del)에 의해 제시되었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN1utm/dJMcafrwq08/OcvVDislSrhswuIMR3AKUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN1utm/dJMcafrwq08/OcvVDislSrhswuIMR3AKUK/img.png&quot; data-alt=&quot;Figure 1. Universal TM, Sipser 2012&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN1utm/dJMcafrwq08/OcvVDislSrhswuIMR3AKUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN1utm%2FdJMcafrwq08%2FOcvVDislSrhswuIMR3AKUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;390&quot; height=&quot;364&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 1. Universal TM, Sipser 2012&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 입력으로는 M과 w를 받는다. 어떻게 받는지는 잠시 뒤에 논의한다.&lt;br /&gt;2. 원래 M이 w를 연산할 때 테이프의 상태를 흉내내는 테잎이 있다.&lt;br /&gt;3. M이 w를 연산할때 의 상태의 전이를 흉내낼 수 있는 테잎이 있다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알고리즘과 튜링 머신&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘은 어떤 지시사항들의 나열을 의미한다. 알고리즘에 대한 수학적인 엄밀한 정의는 앨런 튜링(Alan Turing)과 알론조 처치(Alonzo Church)로부터 제시되었다. 둘은 각각 알고리즘의 정의에 튜링머신과 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;lambda;-계산법(&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;lambda;-calculus)를 사용했는데, 이 둘은 실질적으로 동일했다. 그래서 이 알고리즘의 엄격한 정의에 관한 내용을 처치-튜링 명제(Church-Turing Thesis)라고 부른다.&lt;br /&gt;&lt;br /&gt;튜링머신을 일상적인 수준으로 데려올 수 있다. 어떤 언어 $D=\{p|\text{p는 변수 x에 대한 정수 근을 갖는 다항식}\}$를 인식하는 TM은 존재할까? 예를 들어, $x^3+2x^2-1=0$을 입력으로 받아, 정수근이 있는지 없는지를 판단하는 TM이 존재할까? $x=0,1,-1,2,-2,3,-3,4,...$ 순으로 값을 넣어보다 보면, 언젠가 정수근을 찾을 수 있을테니 인식가능한 TM은 존재한다. 정수근이 없다면 정지하지 않고 영원히 TM은 연산을 수행할 것이다. 즉, 인식 가능한(recognizable) TM은 있지만, 결정 가능한(decidable) TM은 없다. TM이 결정 가능하다는 것은 모든 연산이 정지된다는 뜻이라고 &lt;a href=&quot;https://junbyeol.tistory.com/50&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;앞선 글&lt;/a&gt;에서 다뤘다. 한편, &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;$D'=\{p|\text{p는 변수 x에 대한 -5에서 5사이의 정수 근을 갖는 다항식}\}$으로 구간이 주어진다면, $D'$에 대한 결정기 TM은 존재함을 유추할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;이런식으로, 알고리즘은 TM에 대응된다. 알고리즘은 3가지 수준으로 나눠서 표현할 수 있다.&lt;br /&gt;1. 가장 낮은 수준: 우리가 지금까지 해왔던 것처럼, TM의 전이함수와 상태 등 모든 엄격한 정의(formal definition)으로 표현하기&lt;br /&gt;2. 중간 수준: TM이 어떻게 동작하는지를 일상 언어로 표현하기&lt;br /&gt;3. 가장 높은 수준: TM의 테이프나 헤드는 전혀 언급하지 않고, 그냥 일상 언어나 수도코드(pseudo-code)로 알고리즘을 표현하기&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;입력을 문자열로 인코딩하기&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;높은 수준에서는 입력을 문자열이 아닌 어떤 형태로 받아도 상관없다. 하지만, TM은 입력을 문자열로 밖에 받을수 없다. 앞서 UTM에 대해 논의할때도 튜링머신을 입력으로 어떻게 받을지에 대한 논의를 생략했었다. 입력을 문자열로 변환할 수 있으면, UTM의 입력에 대한 문제도, 알고리즘의 고수준 표현과 저수준 표현 사이의 간극도 해결된다. 이 절에서는 입력을 문자열로 변환하는 방법에 대해 다룬다.&lt;br /&gt;&lt;br /&gt;만약 입력으로 어떤 객체 $O_1$를 입력으로 한다면, 인코딩된 입력은 $&amp;lt;O_1&amp;gt;$으로 표기하기로 한다. 여러개라면 $&amp;lt;O_1, O_2&amp;gt;$로 표기한다.&lt;br /&gt;&lt;br /&gt;예를 들어, $A=\{&amp;lt;G&amp;gt;| G는 연결 그래프\}$라는 언어가 있다고 하자. &amp;lt;G&amp;gt;는 Figure 2처럼 인코딩된다. 앞 부분 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;$(1,2,3,4)$&lt;/span&gt;은 노드의 나열이다. 뒷부분 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;$((1,2),(2,3),(3,1),(1,4))$은 엣지(edge)의 나열이다. 이러면 그래프 정보인 G를 문자열 &amp;lt;G&amp;gt;로 변환하였기 때문에, 인식하는 TM M을 정의하는데에 문제가 없다.&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DRsaC/dJMb99SnDaq/g41Zg4oQYIUnj91TRBfzn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DRsaC/dJMb99SnDaq/g41Zg4oQYIUnj91TRBfzn0/img.png&quot; data-alt=&quot;Figure 2. Encoding, Hopcroft 3rd edition&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DRsaC/dJMb99SnDaq/g41Zg4oQYIUnj91TRBfzn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDRsaC%2FdJMb99SnDaq%2Fg41Zg4oQYIUnj91TRBfzn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;207&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 2. Encoding, Hopcroft 3rd edition&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;M = &quot;입력 ⟨G⟩에 대해, 그래프 G의 인코딩:&lt;br /&gt;1. G의 첫 번째 노드를 선택하고 표시한다.&lt;br /&gt;2. 새로운 노드가 표시되지 않을 때까지 다음 단계를 반복한다:&lt;br /&gt;3. G의 각 노드에 대해, 이미 표시된 노드에 가장자리로 연결되어 있으면 표시한다.&lt;br /&gt;4. G의 모든 노드를 스캔하여 모두 표시되었는지 확인한다. 모두 표시되었으면 수락하고, 그렇지 않으면 거부한다.&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜링머신의 정의를 문자열로 변환하는 과정은 다음과 같다. 모든 $Q, \Gamma$에 순서를 할당한다. 대신, 1,2,3번 상태는 초기 상태, 수용상태, 거부 상태로 고정한다. 하나의 전이 $\delta(q_h, a_i) = (q_j,a_k, L)$는 $(h,i,j,k,L)$ 5개의 정수로 설명가능하다. L은 1, R은 0에 대응된다. 그러면 $(h,i,j,k,L)$을 $0^h10^i10^j10^k1L$로 변환한다. 그리고 가능한 모든 전이를 $11$을 구분자 삼아 나열하면 변환이 끝난다.&lt;br /&gt;&lt;br /&gt;그러면, UTM은 아래의 언어를 인식한다.&lt;br /&gt;$A = \{&amp;lt;M,w&amp;gt;| \text{ M은 튜링머신이고, M이 w를 수용한다.}\}$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UTM U에 대한 고수준의 설명은 다음과 같다(AI로 생성함).&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;U = &quot;입력 &lt;span&gt;⟨&lt;/span&gt;M, w&lt;span&gt;⟩&lt;/span&gt;에 대해, 여기서 M은 튜링 머신이고 w는 입력 문자열이다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 입력 &lt;span&gt;⟨&lt;/span&gt;M, w&lt;span&gt;⟩&lt;/span&gt;가 올바른 형식인지 검증한다. 즉, M이 유효한 튜링 머신의 인코딩이고 w가 유효한 문자열 인코딩인지 확인한다.&lt;br /&gt;2. M의 전이 함수를 테이프에 저장한다.&lt;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;3. M의 초기 구성을 시뮬레이션한다:&lt;br /&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;- M의 시작 상태를 현재 상태로 설정한다.&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;- w를 테이프에 기록하고 헤드를 w의 첫 번째 심볼 위에 위치시킨다.&lt;br /&gt;4. 다음을 반복한다:&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;- 현재 상태와 헤드 아래의 심볼을 확인한다.&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;- M의 전이 함수를 참조하여 다음 전이를 결정한다.&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;- 전이에 따라:&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &lt;/span&gt;- 상태를 변경한다.&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &lt;/span&gt;- 테이프 심볼을 쓴다.&lt;br /&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &lt;/span&gt;- 헤드를 이동시킨다.&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;- M이 수락 상태에 도달하면 수락한다.&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;- M이 거부 상태에 도달하면 거부한다.&lt;br /&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;- (M&lt;/span&gt;이&lt;span&gt; &lt;/span&gt;무한&lt;span&gt; &lt;/span&gt;루프에&lt;span&gt; &lt;/span&gt;빠지면&lt;span&gt; U&lt;/span&gt;도&lt;span&gt; &lt;/span&gt;무한&lt;span&gt; &lt;/span&gt;루프에&lt;span&gt; &lt;/span&gt;빠진다&lt;span&gt;)&quot;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/52</guid>
      <comments>https://junbyeol.tistory.com/52#entry52comment</comments>
      <pubDate>Sat, 29 Nov 2025 23:28:19 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 17. 변형된 튜링머신(Variants of TM)</title>
      <link>https://junbyeol.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 튜링머신에 변형을 가해서, 새로운 튜링머신을 만든다. 새로운 튜링머신들은 기존 튜링머신보다 좋아보이지만, 우리의 직관과 추론에 도움을 줄 뿐, 기존 튜링머신과 같은 언어를 인식한다. 달리 말하면, 동등하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;헤드를 움직이지 않아도 되는 튜링머신&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;앞선 튜링머신은 매 전이마다 헤드를 왼쪽이나 오른쪽으로 움직여야했다. 하지만, 변형된 튜링머신은 헤드를 가만히 있어도 된다. 원래 튜링머신의 전이 함수는 $Q\times\Gamma \to Q\times\Gamma\times\{L, R\}$ 이었다면, $Q\times\Gamma \to Q\times\Gamma\times\{L, R, S\}$의 함수다.&lt;br /&gt;&lt;br /&gt;왜 $S$를 추가하고도 기존 튜링머신과 동등할까? $\delta(q, a) = (q',b,S)$를 기존 튜링머신의 정의로도 시뮬레이션 가능하기 때문이다. 새로운 상태 $q''$를 추가하야, $\delta(q,a) = (q'', b, L)$와 $\delta(q'',b) = (q', b, R)$을 수행한다. 헤드의 심볼은 업데이트 하면서, 헤드는 움직이지 않은 효과를 낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다중 테잎 튜링머신(Multitape TM)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜링머신은 테잎이 하나였지만, 여러 개의 테잎을 갖는 튜링머신도 생각할 수 있다. 원래 튜링머신의 전이 함수는 $Q\times\Gamma \to Q\times\Gamma\times\{L, R, S\}$ 이었다면, $Q\times\Gamma \to Q\times \Gamma^k \times\{L, R, S\}^k$의 함수다. 여전히 두 튜링머신이 인식하는 언어는 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테잎이 하나인 튜링머신의 전이는 $\delta(q, a) = (q',a',L)$ 처럼 표현했다면, 테잎이 두개인 튜링머신은 $\delta(q, a, b) = (q',a',b',L,R)$ 처럼 표현할 수 있다. 두 개의 테잎이 있는 튜링머신을 시뮬레이션 하는 단일 테잎 튜링머신을 설계해보자. 아래의 특징을 갖는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 심볼들을 갖는다. 구분자 $\#$과 모든 심볼에 대하여  $\bar{a}$ 처럼 마킹된 심볼이 추가된다.&lt;/li&gt;
&lt;li&gt;멀티테잎 TM의 테잎 초깃값이 각각 $w$와 $z$라면, 새로운 TM은 $\#w\#z\#$을 초깃값으로 갖는다.&lt;/li&gt;
&lt;li&gt;멀티테잎 TM에서 두개의 헤더가 가르키고 있는 심볼은 마킹으로 표현한다. $\#w\#z\#$에서 각 헤더가 가리키는 심볼은 원래 $a$였다면 $\bar{a}$로 바꿔둔다.&lt;/li&gt;
&lt;li&gt;이제 $\delta(q, a, b) = (q',a',b',L,R)$은 다음과 같이 실행한다.&lt;br /&gt;- $\bar{a}$를 찾아서 $a'$로 업데이트하고, 헤더를 왼쪽으로 옮긴다. 이동한 헤더가 가리키고 있는 심볼을 다시 마킹한다. 이 과정은 첫번째 테잎의 $\delta(q, a)=(q',a',L)$전이를 시뮬레이션한 효과다.&lt;br /&gt;- $\#$가 나올때까지 헤더를 오른쪽으로 이동한다.&lt;br /&gt;- $\bar{b}$를 찾아서 $b'$로 업데이트하고, 헤더를 오른쪽으로 옮긴다. 이동한 헤더가 가리키고 있는 심볼을 다시 마킹한다. 이 과정은 두번째 테잎의 $\delta(q,b)=(q',b',R)$전이를 시뮬레이션한 효과다.&lt;br /&gt;- 혹시나 매 테잎의 시뮬레이션 과정에서 헤더를 옮겼는데 옮긴 자리에 $\#$가 있다면, $\bar{\sqcup}$를 추가한다. 매 테잎은 오른쪽으로 무한하고, 모두 $\sqcup$로 채워져 있는 것을 시뮬레이션 하는것이다. (테잎에 $\bar{\sqcup}$를 추가하는 과정은 생략했다.)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비결정적 튜링머신(Non-deterministic TM)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 튜링머신은 주어진 상태와 심볼에 따라 (다음 상태, 다음 심볼, 다음 헤더의 이동방향)이 결정적(deterministic)했다. 이제는 (다음 상태, 다음 심볼, 다음 헤더의 이동방향)이 없거나 여러개일 수 있다. $Q\times\Gamma \to Q\times\Gamma\times\{L, R, S\}$ 이었다면, $Q\times\Gamma \to 2^{Q\times\Gamma\times\{L, R, S\}}$의 함수다. 어떤 입력의 계산 이력 중에서 하나라도 수용되는 계산이 있으면 그 입력은 수용된다. 하나도 수용되지 않으면 거부된다.&lt;br /&gt;&lt;br /&gt;비결정적 튜링머신(NTM)의 연산이력은 NFA처럼 트리 형태로 표현된다. 그리고 우리는 NTM을 시뮬레이션할 떄 BFS(너비 우선 탐색)로 트리를 순회할 것이다. DFS(깊이 우선 탐색)가 아닌 이유는, 정지(halt)하지 않는 연산을 무한히 수행하는 굴레에 빠질 수 있기 때문이다.&lt;br /&gt;&lt;br /&gt;NTM을 시뮬레이션 하려면 Figure 1과 같은 튜링머신이 필요하다.&lt;br /&gt;- 입력 테잎(input tape): 초깃값은 테잎의 입력과 같다. 시뮬레이션 중 절대 변경되지 않는다.&lt;br /&gt;- 주소 테잎(address tape): 트리를 순회하는 순서를 저장한다. 예를 들어, 심볼에 &quot;231&quot;이 들어있다면 루트, 2번째, 3번째, 1번째 연산을 입력값으로부터 수행하라는 뜻이다. 수행은 시뮬레이션 테잎에서 이뤄진다.&lt;br /&gt;- 시뮬레이션 테잎(simulation tape):&amp;nbsp; BFS로 순회하면서 새로운 노드를 시뮬레이션 할 때마다, 입력 테잎의 값을 이 테잎으로 옮겨온다. 그리고 현재 주소테잎(address tape)의 헤더가 가리키는 심볼의 흐름대로 연산을 수행한다. 하나라도 수용상태에 도달하면 해당 입력은 수용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgyDt/dJMcafyhTIr/QCLr0wYEYG26IqhOK6k1w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgyDt/dJMcafyhTIr/QCLr0wYEYG26IqhOK6k1w0/img.png&quot; data-alt=&quot;Figure 1. Sigser 2012&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgyDt/dJMcafyhTIr/QCLr0wYEYG26IqhOK6k1w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpgyDt%2FdJMcafyhTIr%2FQCLr0wYEYG26IqhOK6k1w0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;510&quot; height=&quot;165&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 1. Sigser 2012&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/51</guid>
      <comments>https://junbyeol.tistory.com/51#entry51comment</comments>
      <pubDate>Sat, 29 Nov 2025 21:52:30 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 16. 튜링머신(Turing Machine)</title>
      <link>https://junbyeol.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;튜링머신은 DFA, PDA와는 다른 또 새로운 오토마타다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JeLbb/dJMcadmSlZv/QygDte0wxzGmkoc078sVV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JeLbb/dJMcadmSlZv/QygDte0wxzGmkoc078sVV1/img.png&quot; data-alt=&quot;Figure 1. Turing machine, Sipser 2012&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JeLbb/dJMcadmSlZv/QygDte0wxzGmkoc078sVV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJeLbb%2FdJMcadmSlZv%2FQygDte0wxzGmkoc078sVV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;170&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 1. Turing machine, Sipser 2012&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 읽기-쓰기 테이프(Read-write tape)를 갖는다. 테이프는 오른쪽으로 무한하다. 테이프의 초깃값은 입력 문자열로 채워져 있으며, 나머지는 blank(⊔)로 채워져있다.&lt;br /&gt;2. 최초에 헤드(Head)는 가장 왼쪽 셀(cell)을 가리키고 있다.&lt;br /&gt;3. 이제 오토마타는 매 전이마다 아래의 행동을 해야한다.&lt;br /&gt;&amp;rarr; 헤드를 오른쪽 혹은 왼쪽으로 움직인다.&lt;br /&gt;&amp;rarr; 심볼(symbol)의 값을 다른 심볼로 바꾸거나 그대로 둔다.&lt;br /&gt;&amp;rarr; 상태를 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;튜링머신(TM)의 엄격한 정의(Formal Definition)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;$\text{TM&amp;nbsp;is&amp;nbsp;a&amp;nbsp;7-tuple}(Q,&amp;nbsp;\Sigma,&amp;nbsp;\Gamma,&amp;nbsp;\delta,&amp;nbsp;q_0,&amp;nbsp;q_{\text{accept}},&amp;nbsp;q_{\text{reject}})&amp;nbsp;\text{&amp;nbsp;while&amp;nbsp;}\\\\&lt;br /&gt;Q\text{:&amp;nbsp;states}\\&lt;br /&gt;\Sigma\text{:&amp;nbsp;input&amp;nbsp;alphabet}\\&lt;br /&gt;\Gamma\text{:&amp;nbsp;tape&amp;nbsp;alphabet,&amp;nbsp;while&amp;nbsp;}\Sigma\cup\{\sqcup\}\subseteq&amp;nbsp;\Gamma\\&lt;br /&gt;\delta\text{:&amp;nbsp;transition&amp;nbsp;function}(Q\times\Gamma&amp;nbsp;\to&amp;nbsp;Q\times\Gamma\times\{Left,&amp;nbsp;Right\})\\&lt;br /&gt;q_0\text{:&amp;nbsp;initial&amp;nbsp;state}\\&lt;br /&gt;q_\text{accept}\text{:&amp;nbsp;accept&amp;nbsp;state}\\&lt;br /&gt;q_\text{reject}\text{:&amp;nbsp;reject&amp;nbsp;state},&amp;nbsp;q_\text{accept}&amp;nbsp;\neq&amp;nbsp;q_\text{reject}&lt;br /&gt;$&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 전이함수의 정의를 풀어서 설명하면 아래와 같다.&lt;br /&gt;입력: 현재 상태, 현재 심볼&lt;br /&gt;출력: 다음 상태, 다음 심볼, 헤드 이동방향(좌, 우)&lt;br /&gt;튜링머신은 반드시 결정적(deterministic)해야한다. 다르게 말하면, 모든 가능한 상태와 가능한 심볼의 조합에 대해서 다음 행선지를 안내하는 전이함수가 정확히 하나씩 존재해야한다. 없거나 여러개여서는 안된다.&lt;br /&gt;&lt;br /&gt;예를 들어, &quot;상태는 $q$에서 $q'$로 전이하고, 테잎에 써있던 심볼 $b$는 $c$로 고치고, 헤드는 오른쪽으로 이동한다&quot; 를 아래와 같이 표기한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;$u\,q\,bv\text{ yields } uc\,q'\,v$&lt;br /&gt;또는&lt;br /&gt;$\delta(q,b) = (q',c,R)$&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈여겨 볼 점은, DFA나 PDA에서는 수용 상태(accept state)만을 정의하고, 나머지 상태는 모두 거부(reject)했다. 그러나, 튜링머신은 $q_\text{accept}$와 $q_\text{reject}$를 명시적으로 요구한다. 수용도, 거부도 아닌 상태가 존재하는 것이다. 그것은, 여러 상태를 전이하며 정지(halt)하지 못하고 빙빙 루프(loop)에 빠지는 입력이 있기 때문이다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;정확히 튜링머신 연산 결과, 수용 상태에서 종료되는 문자열 $w$들만을 튜링머신 $M$의 언어 $L(M)$이라고 한다. $M$은 $L(M)$을 인식(recognize)한다. $L(M)$이 아닌 문자열들은 거부되거나 루프에 빠진 경우다. 어떤 언어 A를 설명하는 튜링머신이 있으면, 언어 A는 재귀적으로 나열가능하다(recursively enumerable)고 한다.&lt;br /&gt;&lt;br /&gt;튜링머신 $M$이 주어진 알파벳 $\Sigma^{*}$의 모든 문자열 $w$에 대해 정지한다면, $M$은 $L(M)$을 결정(decide)한다. 이런 튜링머신이 있는 언어 A를, 재귀적(recursive)이라고 한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;튜링머신의 예시&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;언어 $A=\{w\#w: w\in\{0,1\}^*\}$를 인식하는 튜링머신 &lt;br /&gt;&lt;br /&gt;$\#$을 기준으로 앞 뒤 내용이 같은 문자열을 인식하는 튜링머신은 대강 이렇게 동작한다.&lt;br /&gt;1. 헤드가 첫 심볼을 읽고, $x$로 변경 후 헤드를 오른쪽으로 이동, 심볼이 0인지 1인지에 따라 서로 다른 상태로 전이해서 첫 심볼을 기억&lt;br /&gt;2. 심볼이&amp;nbsp;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;$\#$이 될때까지 아무것도 하지 않고 헤드를 계속 오른쪽으로 이동&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;3. 심볼이 &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;$x$가 아닐때까지 아무것도 하지 않고 헤드를 계속 오른쪽으로 이동&lt;/span&gt;&lt;br /&gt;3. 현재 심볼이 (1)에서 기억했던 심볼과 같으면 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;$x$로 심볼 변경 후, 헤드를 가장 왼쪽의 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;$x$가 아닌 심볼로 이동시킴. (1)에서 기억했던 심볼과 다르다면 즉시 거부&lt;br /&gt;4. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;$\#$&lt;/span&gt; 왼쪽에 모든 심볼이 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;$x$가 되면 수용&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zntwm/dJMcafrv2gm/3qkmsIJ8MOehNqa8rW5tgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zntwm/dJMcafrv2gm/3qkmsIJ8MOehNqa8rW5tgK/img.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;558&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.911%; margin-right: 10px;&quot; data-widthpercent=&quot;49.49&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zntwm/dJMcafrv2gm/3qkmsIJ8MOehNqa8rW5tgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzntwm%2FdJMcafrv2gm%2F3qkmsIJ8MOehNqa8rW5tgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1q0R6/dJMcabilRTw/B3K6cbYPhiOxaXQkExKrM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1q0R6/dJMcabilRTw/B3K6cbYPhiOxaXQkExKrM0/img.png&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;770&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.9262%;&quot; data-widthpercent=&quot;50.51&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1q0R6/dJMcabilRTw/B3K6cbYPhiOxaXQkExKrM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1q0R6%2FdJMcabilRTw%2FB3K6cbYPhiOxaXQkExKrM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;986&quot; height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Figure 2. # 앞뒤가 똑같은 문자열을 인식하는 튜링머신, Sipser 2012&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/50</guid>
      <comments>https://junbyeol.tistory.com/50#entry50comment</comments>
      <pubDate>Fri, 28 Nov 2025 18:10:28 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 15. 결정 가능한 문제(Decidable Problem)</title>
      <link>https://junbyeol.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;:)&quot; 문제가 있다고 생각해보자. &quot;:)&quot; 문제는 어떤 프로그램이 :)를 출력하는지 아닌지를 확인하는 문제다. 결론부터 말하자면, 컴퓨터는 이 단순한 문제를 항상 해결하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이런 프로그램이 입력으로 들어온다면 문제의 정답을 &quot;yes&quot;라는걸 쉽게 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1764310452447&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() {
 printf(&quot;:)&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이런 문제라면 어떨까? 코드의 내용을 간단히 요약하자면, n을 입력받고, 가능한 모든 정수쌍 (x,y,z)를 순회하면서 $x^n+y^n=z^n$가 성립하면 &quot;:)&quot;를 출력하는 코드다. 가령 $n=2$라면, $3^2+4^2=5^2$에서 &quot;:)&quot;가 출력될 가능성이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1764310729094&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() {
  int n, tot, x, y;
  scanf(&quot;%d&quot;, &amp;amp;n);

  while(1) {
    for(x=1; x&amp;lt;tot-2; x++) {
      for(y=1; y&amp;lt;tot-x-1; y++) {
        z = tot-x-y;
        if(pow(x,n)+pow(y,n) == pow(z,n) printf(&quot;:)&quot;);
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;참고로 위 코드에 사용된 $x^n+y^n=z^n$은 페르마의 마지막 정리에 등장하는 식이며, $n \ge 3$인 경우, 만족하는 x,y,z 정수해가 존재하지 않는다는 것이 증명되었다. 즉, 우리는 수학적인 증명을 통해 :)를 출력하지 않는다는 것을 알고 있다. 하지만 컴퓨터는 위 코드가 :)를 출력할지 않을지 알 수 없다. 코드는 무한히 실행되고, 컴퓨터는 기다리면 :)가 나올지를 목빠져라 기다릴 뿐이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;결정 가능한 문제(Decidable Problem)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&quot;:)&quot; 문제와 같은 문제를 결정 불가능한 문제(Undecidable Problem)라고 부른다. 모든 입력에 대해 유한시간 내에 &quot;yes&quot;나 &quot;no&quot;로 답할 수 없는 문제기 때문이다. 반대로 유한시간 내에 모든 입력에 대해서 &quot;yes&quot;나 &quot;no&quot;를 결정할 수 있으면 결정 가능한 문제(Decidable Problem)라고 한다. 이런 예시를 들 수 있다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;결정 가능한 문제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 이 사람은 성인인가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 주어진 정수가 소수인가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 주어진 문자열의 길이가 5 이상인가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;결정 불가능한 문제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 앞서 우리가 예로 든 &quot;:)&quot; 문제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 정지문제(Halting Problem): 주어진 프로그램이 종료하는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 문법 모호성: 주어진 문법이 모호한가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 프로그램 등가성: 두 프로그램이 같은 결과를 내는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&quot;:)&quot; 문제의 결정 불가능함에 대한 증명&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&quot;:)&quot; 문제는 정말 결정 불가능할까? 어떤 프로그램 P와 어떤 입력 I가 주어졌을 때 &quot;:)&quot;를 출력하는지 하지 않는지를 판단하는 &quot;:) 테스터&quot;가 존재한다면, :) 문제는 결정 가능할것이다. 하지만, 우리는 :) 테스터가 존재한다고 가정하면 모순이 발생함을 보여서(귀류법), :) 테스터는 존재할 수 없고, :) 문제는 결정 불가능하다는 것을 증명할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로그램 $H$의 스펙은 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로그램 $H$&lt;br /&gt;입력: 프로그램 $P$와 입력 $I$&lt;br /&gt;출력: $P(I)$를 시뮬레이션하고, &quot;:)&quot;가 출력되면 &quot;yes&quot; 출력, 출력되지 않으면 &quot;no&quot; 출력&lt;br /&gt;출력 종류: &quot;yes&quot; or &quot;no&quot;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로그램 $H$를 이용하여 구현한 프로그램 $H_1$의 스펙은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 $H_1$&lt;br /&gt;입력: 프로그램 $P$와 입력 $I$&lt;br /&gt;출력: $H(P,I)$를 시뮬레이션하고, &quot;yes&quot;가 출력되면 똑같이 &quot;yes&quot; 출력, &quot;no&quot;가  출력되면 &quot;:)&quot;를 출력&lt;br /&gt;출력 종류: &quot;yes&quot; or &quot;:)&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 프로그램 $H_2$를 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 $H_2$&lt;br /&gt;입력: 프로그램 $P$ (입력 I를 받지 않음)&lt;br /&gt;출력: $H_1(P,P)$를 시뮬레이션해서, 결과를 그대로 출력&lt;br /&gt;출력 종류: &quot;yes&quot; or &quot;:)&quot;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 우리는 $H_2(H_2)$를 실행할 때 모순을 발견할 수 있고, 그래서 프로그램 $H_2$는 존재하지 않고, 그로 인해 프로그램 $H_1$도 존재하지 않고, 그로 인해 프로그램 $H$도 존재하지 않는다는 결론이다. 그래서 &quot;:) 문제&quot;는 결정 불가능하다. 왜 $H_2(H_2)$가 모순인지는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 $H_2$은 &quot;yes&quot; 아니면 &quot;:)&quot;를 출력한다. &lt;br /&gt;프로그램 $H_2$는 $P$가 자신을 입력을 받았을 때($P(P)$) &quot;:)&quot;가 출력되면 &quot;yes&quot;, 출력되지 않으면 &quot;:)&quot;를 출력하는 프로그램이다. &lt;br /&gt;$H_2(H_2)$는 $H_2$가 자신을 입력을 받았을 때($P(P)$) &quot;:)&quot;가 출력되면 &quot;yes&quot;, 출력되지 않으면 &quot;:)&quot;를 출력하는 프로그램이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 만약 $H_2(H_2)$가 &quot;yes&quot;를 출력한다면, $H_2$가 자신을 입력받아 &quot;:)&quot;를 출력했다는 것을 의미한다. 모순이다.&lt;br /&gt;- 만약 $H_2(H_2)$가 &quot;:)&quot;를 출력한다면, $H_2$가 자신을 입력받아 &quot;:)&quot;를 출력하지 못했다는 뜻이다. 모순이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 &quot;:)&quot; 문제가 결정불가능함을 보이는 증명이다. 앞서 예로 등장한 정지 문제(Halting Problem)도 이런식으로 증명할 수 있는데, 좋은 영상과 설명이 있어 공유한다. &lt;a title=&quot;링크&quot; href=&quot;https://www.udiprod.com/halting-problem/#faq&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환원(reduction)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;:)&quot;문제가 결정 불가능하다는 것은 알았다. 다른 결정불가능한 문제들도 위 방법대로 직접 증명을 할 수도 있다. 하지만 이미 &quot;:)&quot; 문제가 결정 불가능하다는 것을 우리는 알고 있으니, 이 점을 이용하면 더 간단하게 증명할 수 있다. 환원(Reduction)을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 새로운 문제 P에 대해 유한시간 내에 yes/no를 답하는 프로그램 Q이 있다고 가정&lt;br /&gt;2. 프로그램 Q를 이용하면 &quot;:)&quot;문제도 유한시간 내에 yes/no를 답할 수 있다고 증명&lt;br /&gt;3. 그런데 &quot;:)&quot;문제는 결정 불가능하다는 것을 알고 있으므로 모순 발생&lt;br /&gt;4. 따라서, 새로운 문제 P를 해결하는 프로그램 Q는 존재하지 않음. 즉, P도 결정 불가능함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 'P를 &quot;:)&quot;문제로 환원'했다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Smile 호출&quot; 문제를 정의해보자. &quot;Smile 호출&quot; 문제는 &quot;어떤 프로그램이 'Smile'이라는 함수를 호출하는가?&quot; 에 답하는 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1. 만약 이 문제를 해결하는 테스터가 있다고 가정해보자.&lt;br /&gt;2. &quot;:)&quot; 문제 테스터가 판단하기 어려워하고 있는 프로그램 X가 있다고 생각하자. Smile 호출 문제 테스터에게 이런 프로그램을 만들어서 떠넘긴다: &quot;X가 :)를 출력하면 Smile을 호출하고, 아니면 호출하지 않는 프로그램&quot;&lt;br /&gt;3. 그런데 &quot;:)&quot; 문제는 결정 불가능하므로, 2. 의 프로그램을 해결할 수 있는 Smile 호출 문제 테스터는 존재하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면, &quot;:)&quot; 문제 테스터가 Smile 호출 문제 테스터를 걸고 넘어지면 되는거다. :) 문제 테스터는 Smile 테스터에게 이렇게 말하고 있다. &quot;내가 못 풀고 있는 이 문제를 너가 풀 수 있었다면, 너도 결정 가능한거고 나도 결정 가능하다고 했을거야! 그런데 너가 못풀었으니까 나도 어쩔수 없이 결정 불가능한거야!&quot;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/49</guid>
      <comments>https://junbyeol.tistory.com/49#entry49comment</comments>
      <pubDate>Fri, 28 Nov 2025 17:20:37 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 3. DFA와 NFA의 동등성</title>
      <link>https://junbyeol.tistory.com/48</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;기본 정규 연산&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는 지금까지, 결정적/비결정적 유한 오토마타의 정의를 배웠다. 앞으로는 오토마타와 정규언어들의 속성들에 대해 다룰 것이다. 그 이전에, 이 속성들을 익히거나 증명하는데에 유용한 도구상자들을 준비했다. 아래의 3가지 연산은 정규 연산(regular operation)이라 불리는 기본적인 언어들 간의 연산이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 언어 A,B에 대하여 아래처럼 정의된다. Kleene는 클레이니, 클리네 등으로 발음하는 듯 하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxADx/btsQ3d2yfnp/DsNNla79LNGQzOLKGx2Ux0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxADx/btsQ3d2yfnp/DsNNla79LNGQzOLKGx2Ux0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxADx/btsQ3d2yfnp/DsNNla79LNGQzOLKGx2Ux0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxADx%2FbtsQ3d2yfnp%2FDsNNla79LNGQzOLKGx2Ux0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;139&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는 자연수 $\times$ 자연수가 자연수라는 사실을 알고 있다. 이것을 수학에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;자연수가 곱셈에 대하여 닫혀있다(closed)&lt;/b&gt;고 말한다. 마찬가지로, 정규언어는 위 3가지 기본 정규 연산에 대하여 닫혀있다. 예를 들어, 정규언어와 정규언어의 union은 정규언어다. 달리 말하면, 어느 두 언어가 유한오토마타로 표현된다면, 그 두 언어의 정규연산 결과를 표현하는 유한오토마타도 반드시 존재한다. 어떻게 증명할까? 각 정규언어 A,B를 인식하는 유한오토마타 $M_1,M_2$를 이용해서, $A \cup B, A \circ B, A^{*}$을 인식하는 유한 오토마타 M을 만들수 있다면, 증명이 완료된다. 더 엄밀하게는, 그렇게 만들어진 유한 오토마타 M이 정말 $A \cup B, A \circ B, A^{*}$를 인식하는지 정당성(correctness)을 검증하는 과정을 보이면 증명이 완료된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Union의 닫힘성(closure)을 증명해보자. 두 정규언어 A,B를 인식하는 $N_1$과 $N_2$ NFA를 이용하여, $A \cup B$를 인식하는 $N을 만든다면 아래 Figure 4의 왼쪽처럼 만들 수 있다. 오른쪽은 그것을 수학적으로 표현한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFMJgb/btsQ5ho4tC5/qdLZuAFGLbfhbKKmfl2WOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFMJgb/btsQ5ho4tC5/qdLZuAFGLbfhbKKmfl2WOK/img.png&quot; style=&quot;width: 45.311%; margin-right: 10px;&quot; width=&quot;530&quot; height=&quot;416&quot; data-widthpercent=&quot;45.84&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;724&quot; data-origin-width=&quot;922&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFMJgb/btsQ5ho4tC5/qdLZuAFGLbfhbKKmfl2WOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFMJgb%2FbtsQ5ho4tC5%2FqdLZuAFGLbfhbKKmfl2WOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;922&quot; height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byb3DG/btsQ2ipKx8U/icfwdWxvSun9YRju1TmVbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byb3DG/btsQ2ipKx8U/icfwdWxvSun9YRju1TmVbk/img.png&quot; style=&quot;width: 53.5262%;&quot; data-widthpercent=&quot;54.16&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;686&quot; data-origin-width=&quot;1032&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byb3DG/btsQ2ipKx8U/icfwdWxvSun9YRju1TmVbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyb3DG%2FbtsQ2ipKx8U%2FicfwdWxvSun9YRju1TmVbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1032&quot; height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Figure 4. Closure of Union, 왼쪽 출처[2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;$A \circ B$와 $A^{*}$도 Figure 5에서 간단히 소개하고 넘어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cA8578/btsQ3F5wiKZ/RrPqWktWfVJ7axsLrM17Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cA8578/btsQ3F5wiKZ/RrPqWktWfVJ7axsLrM17Ak/img.png&quot; style=&quot;width: 38.7176%; margin-right: 10px;&quot; data-widthpercent=&quot;39.17&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;732&quot; data-origin-width=&quot;1094&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cA8578/btsQ3F5wiKZ/RrPqWktWfVJ7axsLrM17Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcA8578%2FbtsQ3F5wiKZ%2FRrPqWktWfVJ7axsLrM17Ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1094&quot; height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsmRej/btsQ5TVBz3h/efr9ybOvn5Sa2eCosvsutK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsmRej/btsQ5TVBz3h/efr9ybOvn5Sa2eCosvsutK/img.png&quot; style=&quot;width: 60.1196%;&quot; data-widthpercent=&quot;60.83&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;474&quot; data-origin-width=&quot;1100&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsmRej/btsQ5TVBz3h/efr9ybOvn5Sa2eCosvsutK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsmRej%2FbtsQ5TVBz3h%2Fefr9ybOvn5Sa2eCosvsutK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Figure 5. $A \circ B$ 왼쪽, $A^{*}$ 오른쪽, 출처[2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;NFA와 DFA의 동등성&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;같은 언어를 인식하는 두 유한 오토마타를 우리는 동등하다(equivalent)고 한다. 신기하게도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;모든&amp;nbsp;NFA는 항상 동등한 DFA를 갖는다&lt;/b&gt;. 그말인즉슨, 어떤 언어 L을 인식하는 NFA는 똑같은 언어를 인식하는 DFA로 항상 치환할 수 있다는 것이다. 아래와 같은 방식으로 증명한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 어떤 언어 L을 인식하는 NFA $N=(Q,\Sigma,\delta,q_0,F)$로부터 같은 언어 L을 인식하는 DFA $M=(Q',\Sigma',\delta',q_0',F')$을 만든다. 이 때, NFA의&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon; 전이를 허용하지 않는 경우에서 허용하는 경우로 생각을 발전시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 새로 만든 M이 정확히 언어 L을 인식한다는 정당성(correctness)을 증명한다. 먼저, $L(N) \subseteq L(M) \Leftrightarrow w \in L(N) \text{ then } w \in L(M)$를 증명한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 2의 반대 방향을 증명한다. $L(N) \supseteq L(M)&amp;nbsp;&amp;nbsp;\Leftrightarrow w \in L(M) \text{ then } w \in L(N)$&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1의 증명 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;가 없는 경우&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$&lt;br /&gt;\text{From&amp;nbsp;an&amp;nbsp;NFA&amp;nbsp;N}=(Q,\Sigma,\delta,q_0,F),&amp;nbsp;\text{&amp;nbsp;construct&amp;nbsp;a&amp;nbsp;DFA&amp;nbsp;M}&amp;nbsp;=(Q',\Sigma,\delta',q_0',&amp;nbsp;F')&amp;nbsp;\text{&amp;nbsp;s.t.&amp;nbsp;}&amp;nbsp;L(N)=L(M)&amp;nbsp;\\&lt;br /&gt;&lt;br /&gt;\begin{aligned}&lt;br /&gt;&lt;br /&gt;&amp;amp;&amp;nbsp;\text{1.&amp;nbsp;}&amp;nbsp;Q'&amp;nbsp;=&amp;nbsp;2^Q,&amp;nbsp;\text{which&amp;nbsp;is&amp;nbsp;power&amp;nbsp;set&amp;nbsp;of&amp;nbsp;Q}\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{2.&amp;nbsp;}&amp;nbsp;\Sigma&amp;nbsp;=&amp;nbsp;\Sigma&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{3.&amp;nbsp;}&amp;nbsp;q_0'={q_0}&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{4.&amp;nbsp;}&amp;nbsp;F'\subseteq&amp;nbsp;2^Q&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;}&amp;nbsp;F'=\{f|f&amp;nbsp;\cap&amp;nbsp;F&amp;nbsp;\neq&amp;nbsp;\phi&amp;nbsp;\}&amp;nbsp;\\&lt;br /&gt;&amp;amp; \text{5. } \delta'(R,a) = \bigcup_{r \in R}^{}\delta(r,a) \text{ for every } R \in 2^Q \text{ and every symbol } a \in \Sigma&lt;br /&gt;&lt;br /&gt;\end{aligned}&lt;br /&gt;$&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1의 증명 -&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;가 있는 경우&lt;br /&gt;앞서 설명했던&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;-closure를 활용한다. 이때, $ext(X) = \bigcup_{q \in X}^{}ext(q)$로, 집합에 대해서도 정의를 확장하여 사용하였다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;$\text{From&amp;nbsp;an&amp;nbsp;NFA&amp;nbsp;N}=(Q,\Sigma,\delta,q_0,F),&amp;nbsp;\text{&amp;nbsp;construct&amp;nbsp;a&amp;nbsp;DFA&amp;nbsp;M}&amp;nbsp;=&amp;nbsp;(Q',\Sigma,\delta',q_0',&amp;nbsp;F')&amp;nbsp;\text{&amp;nbsp;s.t.&amp;nbsp;}&amp;nbsp;L(N)=L(M)&amp;nbsp;\\&lt;br /&gt;\begin{aligned}&lt;br /&gt;&lt;br /&gt;&amp;amp;&amp;nbsp;\text{1.&amp;nbsp;}&amp;nbsp;Q'&amp;nbsp;=&amp;nbsp;2^Q,&amp;nbsp;\text{which&amp;nbsp;is&amp;nbsp;power&amp;nbsp;set&amp;nbsp;of&amp;nbsp;Q}\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{2.&amp;nbsp;}&amp;nbsp;\Sigma&amp;nbsp;=&amp;nbsp;\Sigma&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{3.&amp;nbsp;}&amp;nbsp;q_0'=ext(q_0)&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{4.&amp;nbsp;}&amp;nbsp;F'\subseteq&amp;nbsp;2^Q&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;}&amp;nbsp;F'=\{f|f&amp;nbsp;\cap&amp;nbsp;F&amp;nbsp;\neq&amp;nbsp;\phi&amp;nbsp;\}&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{5.&amp;nbsp;}&amp;nbsp;\delta'(R,a)&amp;nbsp;=&amp;nbsp;ext(\bigcup_{r&amp;nbsp;\in&amp;nbsp;R}^{}\delta(r,a))&amp;nbsp;\text{&amp;nbsp;for&amp;nbsp;every&amp;nbsp;}&amp;nbsp;R&amp;nbsp;\in&amp;nbsp;2^Q&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;every&amp;nbsp;symbol&amp;nbsp;}&amp;nbsp;a&amp;nbsp;\in&amp;nbsp;\Sigma&lt;br /&gt;&lt;br /&gt;\end{aligned}$&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2의 증명&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;N의 정의로부터 M을 만든것 처럼, 어떤 문자열 w을 N에 넘겼을 때의 accepting computation history로부터, M의 computation history를 만든다. 그 후, 만든 M의 computation history 최종 결과가 최종 상태(final state)임을 보이면, 즉 accepting 임을 보이면 된다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$&lt;br /&gt;&lt;br /&gt;\begin{aligned}&lt;br /&gt;&amp;amp;&amp;nbsp;\text{Let&amp;nbsp;}w=y_1y_2...y_s&amp;nbsp;\text{&amp;nbsp;for&amp;nbsp;}&amp;nbsp;y_i&amp;nbsp;\in&amp;nbsp;\Sigma&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;let&amp;nbsp;accepting&amp;nbsp;computation&amp;nbsp;history&amp;nbsp;of&amp;nbsp;N&amp;nbsp;for&amp;nbsp;w,&amp;nbsp;}&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\pi&amp;nbsp;=&amp;nbsp;(q_0,w=w_0),&amp;nbsp;(r_1,w_1),&amp;nbsp;...&amp;nbsp;(r_i,w_i),...,(r_s,w_s=\epsilon)&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{Then,&amp;nbsp;}r_0&amp;nbsp;\in&amp;nbsp;ext(\{q_0\})&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;}&amp;nbsp;r_i&amp;nbsp;\in&amp;nbsp;ext(\{\delta(r_{i-1},y_i)\})&amp;nbsp;\text{&amp;nbsp;for&amp;nbsp;every&amp;nbsp;i}&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{And,&amp;nbsp;}r_s\in&amp;nbsp;F&amp;nbsp;\\\\&lt;br /&gt;&lt;br /&gt;&amp;amp;&amp;nbsp;\text{Now,&amp;nbsp;construct&amp;nbsp;computation&amp;nbsp;history&amp;nbsp;for&amp;nbsp;w&amp;nbsp;in&amp;nbsp;DFA&amp;nbsp;M}&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\pi'&amp;nbsp;=&amp;nbsp;(Q_0,w=w_0),&amp;nbsp;(Q_1,w_1),&amp;nbsp;...&amp;nbsp;(Q_i,w_i),...,(Q_s,w_s=\epsilon)&amp;nbsp;\\\\&lt;br /&gt;&lt;br /&gt;&amp;amp;&amp;nbsp;Then,&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\bullet\,&amp;nbsp;Q_0&amp;nbsp;=&amp;nbsp;\{q_0\}&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\bullet\,&amp;nbsp;r_1&amp;nbsp;=&amp;nbsp;\delta(q_0,y_1)&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;}&amp;nbsp;Q_1&amp;nbsp;=&amp;nbsp;ext(\delta(Q_0,y_1)),&amp;nbsp;r_1\in&amp;nbsp;Q_1\\&lt;br /&gt;&amp;amp;&amp;nbsp;\bullet\,&amp;nbsp;\text{Inductively,&amp;nbsp;}&amp;nbsp;r_i&amp;nbsp;\in&amp;nbsp;Q_i&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\bullet\,&amp;nbsp;\text{Finally,&amp;nbsp;}&amp;nbsp;r_s&amp;nbsp;\in&amp;nbsp;Q_s&amp;nbsp;\text{&amp;nbsp;which&amp;nbsp;means&amp;nbsp;}\pi'&amp;nbsp;\text{accepting&amp;nbsp;computation&amp;nbsp;history}&lt;br /&gt;&lt;br /&gt;\end{aligned}&lt;br /&gt;$&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3의 증명&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$&lt;br /&gt;\begin{aligned}&lt;br /&gt;&amp;amp;&amp;nbsp;\text{Let&amp;nbsp;}w=y_1y_2...y_s&amp;nbsp;\text{&amp;nbsp;for&amp;nbsp;}&amp;nbsp;y_i&amp;nbsp;\in&amp;nbsp;\Sigma&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;let&amp;nbsp;accepting&amp;nbsp;computation&amp;nbsp;history&amp;nbsp;of&amp;nbsp;M&amp;nbsp;for&amp;nbsp;w,&amp;nbsp;}&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\pi'&amp;nbsp;=&amp;nbsp;(R_0,w=w_0),&amp;nbsp;(R_1,w_1),&amp;nbsp;...&amp;nbsp;(R_i,w_i),...,(R_s,w_s=\epsilon)&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\text{By&amp;nbsp;definition,&amp;nbsp;}&amp;nbsp;R_i=\delta'(R_{i-1},y_i)&amp;nbsp;\\\\&lt;br /&gt;&lt;br /&gt;&amp;amp;&amp;nbsp;\text{Now,&amp;nbsp;construct&amp;nbsp;computation&amp;nbsp;history&amp;nbsp;for&amp;nbsp;w&amp;nbsp;in&amp;nbsp;NFA&amp;nbsp;N}&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\pi&amp;nbsp;=&amp;nbsp;(q_0,w=w_0),&amp;nbsp;(q_1,w_1),&amp;nbsp;...&amp;nbsp;(q_i,w_i),...,(q_s,w_s=\epsilon)&amp;nbsp;\\\\&lt;br /&gt;&lt;br /&gt;&amp;amp; Then, \\&lt;br /&gt;&amp;amp;&amp;nbsp;\bullet\,&amp;nbsp;\text{There&amp;nbsp;exists&amp;nbsp;}q_s,&amp;nbsp;\text{&amp;nbsp;s.t.&amp;nbsp;}&amp;nbsp;q_s&amp;nbsp;\in&amp;nbsp;F&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;}&amp;nbsp;q_s&amp;nbsp;\in&amp;nbsp;R_s\\&lt;br /&gt;&amp;amp;&amp;nbsp;\bullet\,&amp;nbsp;\text{There&amp;nbsp;exists&amp;nbsp;}q_{s-1}\text{&amp;nbsp;s.t.&amp;nbsp;}&amp;nbsp;\delta(q_{s-1},y_s)=q_s&amp;nbsp;\text{&amp;nbsp;and&amp;nbsp;}&amp;nbsp;q_{s-1}&amp;nbsp;\in&amp;nbsp;R_{s-1}\\&lt;br /&gt;&amp;amp;&amp;nbsp;\bullet\,&amp;nbsp;\text{Inductively,&amp;nbsp;}&amp;nbsp;q_i&amp;nbsp;\in&amp;nbsp;R_i&amp;nbsp;\\&lt;br /&gt;&amp;amp;&amp;nbsp;\bullet\,&amp;nbsp;\text{Finally,&amp;nbsp;}&amp;nbsp;q_0&amp;nbsp;\in&amp;nbsp;R_0&lt;br /&gt;&lt;br /&gt;\end{aligned}$&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 우리는 모든 NFA는 동등한 DFA를 가짐을 알았다. DFA로 표현가능한 언어를 우리는 정규 언어라고 부른다. 즉, 아래의 결론을 얻었다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;NFA가 인식하는 언어는 정규 언어다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;출처&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[1] John E. Hopcroft, Rajeev Motwani, Jeffrey D. Ullman, Introduction to Automata Theory, Languages and Computation, 3rd Edition, 2006, Pearson/Addision-Wesley.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[2] Michael Sipser, Introduction to the Theory of Computation, 2nd Edition (International Edition), 2006, Course Technology. &lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/48</guid>
      <comments>https://junbyeol.tistory.com/48#entry48comment</comments>
      <pubDate>Thu, 27 Nov 2025 22:22:58 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 0. 목차</title>
      <link>https://junbyeol.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;카이스트 CS322 형식언어 및 오토마타 공부 내용입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규언어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://junbyeol.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;유한 결정적 오토마타(DFA, Deterministic Finite Automata)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;a href=&quot;https://junbyeol.tistory.com/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;유한 비결정적 오토마타(NFA, Non-deterministic Finite Automata)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;a href=&quot;https://junbyeol.tistory.com/48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DFA와 NFA의 동등성&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;a href=&quot;https://junbyeol.tistory.com/45&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;정규표현식과 NFA의 동등성&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Pumping Lemma&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 정규언어의 폐쇄성(Closure)과 동등성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 정규언어의 최소화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 마이힐-네로드 정리(Myhill-Nerode Theorem)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. Monadic Second-Order Logic(MSO)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문맥 자유 언어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 문맥 자유 언어(Context Free Language)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. 푸쉬다운 오토마타(PDA, Pushdown Automata)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. PDA와 CFG의 동등성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13. CFG의 폐쇄성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14. Pumping Lemma For CFG&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;튜링머신&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15. &lt;a href=&quot;https://junbyeol.tistory.com/49&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;결정 가능한 문제(Decidable Problem)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16. &lt;a href=&quot;https://junbyeol.tistory.com/50&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;튜링머신&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;17. &lt;a href=&quot;https://junbyeol.tistory.com/51&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;변형된 튜링머신&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;18. &lt;a href=&quot;https://junbyeol.tistory.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;범용 튜링머신(Universal TM)&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;.... 추가중&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/47</guid>
      <comments>https://junbyeol.tistory.com/47#entry47comment</comments>
      <pubDate>Thu, 27 Nov 2025 21:37:36 +0900</pubDate>
    </item>
    <item>
      <title>Firebase Hosting 도메인 설정 후 Site Not Found에 관하여</title>
      <link>https://junbyeol.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase Hosting에 도메인 주소를 추가했는데 아래와 같은 화면이 뜨면 당황스럽다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;1060&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chqAjl/dJMb9Pl4n9v/T6bzcssnoP368vaYz3so1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chqAjl/dJMb9Pl4n9v/T6bzcssnoP368vaYz3so1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chqAjl/dJMb9Pl4n9v/T6bzcssnoP368vaYz3so1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchqAjl%2FdJMb9Pl4n9v%2FT6bzcssnoP368vaYz3so1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;463&quot; height=&quot;355&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;1060&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용대로 3가지 가능성이 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 아직 배포를 안함(You haven't deployed an app yet)&lt;br /&gt;- 그렇지만 &lt;span&gt;website-xxx.web.app 주소는 정상적으로 동작하고 있었기에, 이것은 배제할수 있었다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. 빈 폴더를 배포함(You may have deployed an empty directory)&lt;br /&gt;- 역시 그럴리가 없다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. 커스텀 도메인이지? 우리가 아직 설정을 덜 끝냄.(This is a custom domain, but we haven't finished setting it up yet.)&lt;br /&gt;- 무슨 설정을 덜 끝냈다는 걸까? 아마도 저 페이지는 서빙에 뭔가 문제가 있을 때 땜빵으로 서빙하는 페이지로 보인다. 배포와 도메인 설정에 문제가 없다는 것을 아래의 명령어들을 통해서 확인할 수 있었다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760683895089&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; nslookup xxx.or.kr                                                                                                                          ok | at 03:21:10 PM
Server:		143.248.1.177
Address:	143.248.1.177#53

Non-authoritative answer:
Name:	xx.or.kr
Address: ???.???.???.??? (보안을 위해 가림, 숫자가 잘 나와야 함)

&amp;gt; curl -I http://xxx.or.kr
(내가 기대하는 응답내용...)

&amp;gt; curl -I https://xxx.or.kr
(내가 기대하는 응답내용...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 생각에 모든 설정이 잘 되었음에도 불구하고, 내가 만든 페이지가 서빙이 제대로 되지 않는 이유는 google firebase 측 CDN에 캐시 문제 때문이다. 내가 원하는 &quot;xxx.or.kr&quot;의 주소에 대응하는 페이지를 저 땜빵 페이지로 캐싱해두고는, 아직 갱신하지 않은 것이다. 시간이 지나면 해결될 것도 같았지만, 참을성이 부족한 나는 아래의 방법을 사용했다. 이 &lt;a href=&quot;https://stackoverflow.com/questions/58289019/firebase-custom-domain-showing-site-not-found&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;stackoverflow&lt;/a&gt;에서 힌트를 얻었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Firebase Hosting에서 도메인을 삭제했다가 다시 추가함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. curl&amp;nbsp;-X&amp;nbsp;PURGE&amp;nbsp;&quot;https://xxx.or.kr&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히 모르겠지만, 아마도 2번을 통해 캐시를 갱신한 것이 유효했던 것 같다.&amp;nbsp;&lt;/p&gt;</description>
      <category>개발이야기/토막글</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/46</guid>
      <comments>https://junbyeol.tistory.com/46#entry46comment</comments>
      <pubDate>Fri, 17 Oct 2025 15:56:14 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 4. 정규표현식(Regular Expression)</title>
      <link>https://junbyeol.tistory.com/45</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규표현식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 언어를 &quot;1로 시작하는 문자열&quot; 혹은 &quot;1이 짝수번 들어가는 문자열&quot; 등으로 주절주절 표현했다. 좀 더 편리하고, 수학적으로 표현하는 방법은 없을까? 앞서 배운 3가지 기본 연산, union, concatenation, Kleene star을 이용해서 표현할 수 있다. 어떤 정규표현식 R이 표현하는 언어는 $\mathcal{L}(R)$ 이라고 표기하고, 정규표현식 R의 언어(language of R)라고 부른다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;{1이 딱 한 번 들어가는 문자열} = $\mathcal{L}(0^*10^*)$&lt;/li&gt;
&lt;li&gt;{최소 1이 한 번 들어가는 문자열} = $\mathcal{L}(\{0,1\}^*1\{0,1\}^*)$&lt;/li&gt;
&lt;li&gt;{짝수 길이의 문자열} = $\mathcal{L}((\Sigma\Sigma)^*)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 정규표현식이다. 좀 더 수학적으로, 정규표현식의 연역적인 정의를 표현할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;Sigma;의 각 심볼과 &amp;epsilon; : 이 때, 정규표현식 $a \in \Sigma$ 혹은 &amp;epsilon;는 각각 $\mathcal{L}(a) = \{ a \}, \mathcal{L}( \epsilon ) = \{ \epsilon \}$임을 의미한다.&lt;/li&gt;
&lt;li&gt;$\phi$ : $\mathcal{L}(\phi) = \phi $ 를 의미한다.&lt;/li&gt;
&lt;li&gt;$R_1, R_2$가 정규표현식이라면, $R_1 \cup R_2$&lt;/li&gt;
&lt;li&gt;$R_1, R_2$가 정규표현식이라면, $R_1 \circ R_2$&lt;/li&gt;
&lt;li&gt;$R_1$가 정규표현식이라면, $R_1 ^*$&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헷갈리기 좋은 아래의 예시들을 고민해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$R \cup \phi = R $&lt;/li&gt;
&lt;li&gt;$R \circ \epsilon = R $&lt;/li&gt;
&lt;li&gt;$R \circ \phi = \phi $&lt;/li&gt;
&lt;li&gt;$R \cup \epsilon \neq R $&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정규표현식과 NFA의 동등성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 앞서, 같은 언어를 인식하는 두 오토마타를 동등하다(equivalent)고 정의하고, NFA는 항상 동등한 DFA를 가짐을 증명했다. 이로써, NFA로 표현 가능한 언어는 정규 언어(regular langauge)임을 유도했다. 그리고, 우리는 정규표현식과 NFA의 동등성을 보일 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;정규표현식의 언어를 인식하는 유한 오토마타 M이 존재한다. 또, 유한 오토마타 M이 인식하는 언어는 정규표현식으로 표현가능하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;또&quot;를 기준으로 전자와 후자의 명제를 각각 증명해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정규표현식 -&amp;gt; FA&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서, 정규표현식를 5가지 케이스로 나누어 연역적으로 정의했다. 기본적인 1, 2번 케이스의 정규표현식과 동등한 오토마타를 만든다면 증명이 끝난다. 왜냐하면, 정규언어는 union, concatenation, Kleene star에 대하여 닫혀있다는 사실을 &lt;a href=&quot;https://junbyeol.tistory.com/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전&lt;/a&gt;에 증명했기 때문이다. 이미 $R_1, R_2$가 정규언어라면 $R_1 \cup R_2 $도 정규언어이기 때문에, 1, 2번 케이스의 정규언어들이라는 전제만 확인하면 3,4,5번 케이스는 이걸로 증명이 끝난다. 그리고, $\mathcal{L}(a), \mathcal{L}(\epsilon), \mathcal{L}(\phi) $는 아래의 오토마타들과 동등하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baJ35U/btsQ2VnvXmi/NG0sQYeANZ8PPWN2n8tZlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baJ35U/btsQ2VnvXmi/NG0sQYeANZ8PPWN2n8tZlk/img.png&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;180&quot; data-is-animation=&quot;false&quot; style=&quot;width: 43.2866%; margin-right: 10px;&quot; data-widthpercent=&quot;44.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baJ35U/btsQ2VnvXmi/NG0sQYeANZ8PPWN2n8tZlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaJ35U%2FbtsQ2VnvXmi%2FNG0sQYeANZ8PPWN2n8tZlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;398&quot; height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfoMmb/btsQ5b3vQ0r/ZGgveyZj2SY5tsYjnj8LJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfoMmb/btsQ5b3vQ0r/ZGgveyZj2SY5tsYjnj8LJ1/img.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;170&quot; data-is-animation=&quot;false&quot; style=&quot;width: 25.7954%; margin-right: 10px;&quot; data-widthpercent=&quot;26.41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfoMmb/btsQ5b3vQ0r/ZGgveyZj2SY5tsYjnj8LJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfoMmb%2FbtsQ5b3vQ0r%2FZGgveyZj2SY5tsYjnj8LJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;224&quot; height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOOeta/btsQ3HCjwPK/i3fk2JBHlEdKCPE9KLSil0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOOeta/btsQ3HCjwPK/i3fk2JBHlEdKCPE9KLSil0/img.png&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;152&quot; data-is-animation=&quot;false&quot; style=&quot;width: 28.5925%;&quot; data-widthpercent=&quot;29.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOOeta/btsQ3HCjwPK/i3fk2JBHlEdKCPE9KLSil0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOOeta%2FbtsQ3HCjwPK%2Fi3fk2JBHlEdKCPE9KLSil0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;222&quot; height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Figure 1, $\mathcal{L}(a),&amp;nbsp; \mathcal{L}(\epsilon),&amp;nbsp; \mathcal{L}(\phi) $를 인식하는 오토마타들, 출처 [2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;FA -&amp;gt; 정규표현식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 어려운 증명이다. 어떤 오토마타를 정규표현식으로 변환하는 알고리즘을 찾을것이다. 우리의 목표 오토마타 M에서 목표 정규표현식 R을 구하는 여정에는, 또 새로운 개념이 등장한다. GNFA를 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;GNFA(Generalized NFA)는 알파벳 심볼 a나 $\epsilon$대신 정규표현식에 의해 전이가 일어나는 NFA다. NFA처럼 문자열을 입력받지만, NFA처럼 매 전이마다 하나의 심볼만 읽을 필요는 없다. NFA 이기 때문에 어느 상태로부터 뻗어나오는 화살표가 동일한 정규표현식을 가져도 괜찮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그리고 편의상, 우리가 다룰 GNFA들은 추가적인 특징을 갖는다. 항상 시작 상태 $q_\text{start}$와 유일한 최종 상태 $q_\text{accept}$를 가진다. 그리고 모든 두 상태 사이에는 서로를 가리키는 화살표가 존재한다. 스스로 뻗어나온 곳으로 들어가는 loop도 모든 상태에서 반드시 존재한다. 단, $q_\text{start}$에서는 뻗어나가는 화살표만 있는 반면, $q_\text{accept}$에는 들어가는 방향의 화살표만 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edlKH5/btsQ3Y4WStT/9cAlp7pMsg6Od7vM17CqFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edlKH5/btsQ3Y4WStT/9cAlp7pMsg6Od7vM17CqFk/img.png&quot; data-alt=&quot;Figure 2. GNFA의 예시, 출처 [2]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edlKH5/btsQ3Y4WStT/9cAlp7pMsg6Od7vM17CqFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedlKH5%2FbtsQ3Y4WStT%2F9cAlp7pMsg6Od7vM17CqFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;438&quot; height=&quot;319&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 2. GNFA의 예시, 출처 [2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 우리의 목적으로 돌아가자. 우리는 어떤 오토마타 M과 동등한 정규표현식을 구하는 법을 찾고 있다. 이제 GNFA를 알았으니, 그 방법을 소개할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. k개의 상태를 가진 유한 오토마타를 k+2개의 상태를 가진 GNFA로 변환한다. 더해진 2개는 $q_\text{start}$와 $q_\text{accept}$이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. k+2개의 상태의 GNFA에서 $q_\text{start}$, $q_\text{accept}$를 제외한 상태들 중 하나를 제거하여, k+1개의 상태의 GNFA로 치환한다.&lt;br /&gt;$\to$k+1개의 상태의 GNFA를 k개의 상태를 가진 GNFA로 치환한다.&lt;br /&gt;$\to$...&lt;br /&gt;$\to$$q_\text{start}$, $q_\text{accept}$ 2개의 상태만 남을때까지 계속 상태들을 제거하며 새로운 GNFA를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. $q_\text{start}$에서 나와 $q_\text{accept}$로 들어가는 화살표의 정규표현식이 우리가 찾는 정규표현식 R이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdw9bA/btsQ5APz9bY/u1jgRrE25vGozn6B0GKtNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdw9bA/btsQ5APz9bY/u1jgRrE25vGozn6B0GKtNK/img.png&quot; data-alt=&quot;Figure 3. 3-state DFA로부터 정규표현식을 얻기까지. 출처 [2]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdw9bA/btsQ5APz9bY/u1jgRrE25vGozn6B0GKtNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdw9bA%2FbtsQ5APz9bY%2Fu1jgRrE25vGozn6B0GKtNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;312&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 3. 3-state DFA로부터 정규표현식을 얻기까지. 출처 [2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1과 2의 과정을 좀 더 자세히 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. DFA -&amp;gt; GNFA&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GNFA는 다음과 같이 엄격하게 정의된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;$&lt;br /&gt;\begin{aligned} &amp;amp; G = (Q, \Sigma, \delta, q_\text{start}, q_\text{accept}), where \\\\ &amp;amp; \delta \text{ is a transition function }(Q - {q_\text{accept}}) \times (Q - {q_\text{start}}) \to R, \\ &amp;amp; \qquad R\text{ is collections of all regular expression over } \Sigma \end{aligned} &lt;br /&gt;$&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFA M으로부터 GNFA G를 아래와 같이 만들면, $L(M)=L(G)$가 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;$&lt;br /&gt;\begin{aligned} &amp;amp; \text{G accepts string }w=w_1w_2...w_k \text{ and the sequence of states }q_0,q_1,...,q_k\\\\ &amp;amp; Then, \\ &amp;amp; \text{1. } q_0 = q_\text{start} \\ &amp;amp; \text{2. } q_k = q_\text{accept]} \\ &amp;amp; \text{3. For each } i (0 \lt i \lt k), w_i \in L(R_i) \text{ where }R_i \text{ is the expression on arrow }q_{i-1} \to q_i, \text{or }\delta(q_{i-1}, q_i) \end{aligned}&lt;br /&gt;$&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. GNFA의 상태 제거하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 GNFA의 상태를 제거할 수 있을까? Figure 4를 보자. $q_\text{rip}$이 우리가 제거할 상태이고, $q_i$와 $q_j$는 그 $q_\text{rip}$과 연결된 상태이다. $q_i$에서 나와 $q_\text{rip}$을 거쳐 $q_j$에 도달하는 방법은 $R_1R_2^*R_3$로 표현된다. 이것을 곧장 $q_i$에서 $q_j$로가는 $R_4$와 union으로 합치면 된다. GNFA의 가능한 모든 상태쌍 $(q_i, q_j)$에 대해 해당 치환을 끝마치면, 마침내 $q_rip$은 삭제해도 되는 상태가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4uEmI/btsQ5bbonNI/qdlEQZMVgSlybBAzbp3fUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4uEmI/btsQ5bbonNI/qdlEQZMVgSlybBAzbp3fUk/img.png&quot; data-alt=&quot;Figure 4. GNFA의 상태 제거하기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4uEmI/btsQ5bbonNI/qdlEQZMVgSlybBAzbp3fUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4uEmI%2FbtsQ5bbonNI%2FqdlEQZMVgSlybBAzbp3fUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;278&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 4. GNFA의 상태 제거하기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k개의 상태를 가진 G가 $q_\text{start}$, $q_\text{accept}$ 2개의 상태만을 가진 CONVERT(G)가 되기까지의 과정을 알고리즘으로 포함하면 아래와 같다. 살펴보면, CONVERT(G)는 내부에서 CONVERT 함수를 다시 호출하는 재귀적인 형태로 정의되었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;CONVERT(G):&lt;br /&gt;1. $k$ 는 $G$의 상태 개수.&lt;br /&gt;2. $k = 2$; $G$ 는 시작/최종 상태만을 포함하며, 그 둘 사이는 정규표현식 $R$로 전이된다. 이 $R$을 반환한다.&lt;br /&gt;3. If $k &amp;gt; 2$, $q_{\text{start}}$ 나 $q_{\text{accept}}$ 가 아닌 아무 상태 $q_{\text{rip}} \in Q$를 잡는다. 그리고 GNFA $(Q', \Sigma, \delta', q_{\text{start}}, q_{\text{accept}})$를 잡는다.&lt;br /&gt;1. $Q' = Q - \{q_{\text{rip}}\}$&lt;br /&gt;2. 어떤 $q_i \in Q' - \{q_{\text{accept}}\}$와 어떤 $q_j \in Q' - \{q_{\text{start}}\}$에 대하여&lt;br /&gt;$\quad\delta'(q_i, q_j) = (R_1)(R_2)^*(R_3) \cup (R_4)$&lt;br /&gt;$\quad$이 때, $R_1 = \delta(q_i, q_{\text{rip}})$, $R_2 = \delta(q_{\text{rip}}, q_{\text{rip}})$, $R_3 = \delta(q_{\text{rip}}, q_j)$, $R_4 = \delta(q_i, q_j)$&lt;br /&gt;4. CONVERT(G')를 반환한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 목표했던 유한 오토마타 M에 대하여, $L(M)=L(G)$라는 사실은 보였다. 이제 $L(G)=L(CONVERT(G))$ 라는 사실을 보이면, 마침내 정규표현식과 NFA의 동등성에 대한 증명이 끝난다. 아래에 접어둔 내용을 펼치면 해당 증명을 볼 수 있다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L(G) = L(G')임을 보이자.&lt;br /&gt;&lt;br /&gt;1. $ L(G) \subseteq L(G') \Leftrightarrow \text{If string } w \in L(G), \text{ then } w \in L(G') $&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- $G'$은 $G$로부터 상태 $q_k$를 제거하여 얻은 GNFA, $ w \in L(G) $&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- $w=w_1...w_l$의 computation history 로부터 $r_0,...,r_l$의 상태들의 배열이 나타남&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;i) $q_k$가 이 상태들의 배열안에 없으면, G'에서도 똑같은 경로로 w를 수용가능. $ w \in L(G') $는 성립.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ii) $q_k$가 이 상태들의 배열에 있으면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 배열 안의 연속된 $q_k$ 부분배열 $r_a,...,r_b$를 잡는다. ($r_a=...=r_b=q_k$)&lt;br /&gt;&amp;nbsp; - 그러면, $r_{a-1} \rightarrow q_k$로 가는 레이블: $\delta(r_{a-1}, q_k)$&lt;br /&gt;&amp;nbsp; - $q_k \rightarrow q_k$로 가는 레이블들: $\delta(q_k, q_k)$ (여러 번 반복 가능)&lt;br /&gt;&amp;nbsp; - $q_k \rightarrow r_{b+1} $로 가는 레이블: $ \delta(q_k, r_{b+1})$&lt;br /&gt;&amp;nbsp; - 따라서, $ w_a...w_{b+1} $은 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;$\delta(r_{a-1},&amp;nbsp;q_k)\cdot\delta(q_k,&amp;nbsp;q_k)^*\cdot\delta(q_k,&amp;nbsp;r_{b+1})&amp;nbsp;$ 으로 표현 가능, 그리고 이것은 우리가 새로 잡은 $\delta'(r_{a-1}, r_{b+1})$과 일치한다. 그러면, 이 때도 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;$ w \in L(G') $.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. $ L(G) \supseteq L(G')&amp;nbsp;&amp;nbsp;\Leftrightarrow \text{If string } w \in L(G'), \text{ then } w \in L(G) $&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- G'에 대하여 (1에서는 G) $w=w_1...w_l$의 computation history 로부터 $r_0,...,r_l$의 상태들의 배열이 나타남&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- $w_i=\delta'(r_{i-1},r_i)$이고, $\delta'(r_{i-1},r_i)=\delta(r_{i-1},r_i) \cup \delta(r_{i-1},q_k)\cdot\delta(q_k,q_k)^*\cdot\delta(q_k,r_i)$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;i) 만약 G에도 $r_{i-1} \to r_i$ 간선이 그대로 있다면, $w_i \in L(\delta(r_{i-1}, r_i))$ 이므로 w_i를 유지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;ii) 아니라면, $w_i$를 $x_1, ..., x_m$으로 분할 후,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- $x_1 \in \delta(r_{i-1},q_k)$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;$x_j \in \delta(r_{q_k},q_k), 2 \leq j \lt m$&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;- $x_m \in \delta(q_k, r_i)$&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;이제 G'의 computation history로부터 G의 computation history를 얻을 수 있음.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출처&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;[1] John E. Hopcroft, Rajeev Motwani, Jeffrey D. Ullman, Introduction to Automata Theory, Languages and Computation, 3rd Edition, 2006, Pearson/Addision-Wesley.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;[2] Michael Sipser, Introduction to the Theory of Computation, 2nd Edition (International Edition), 2006, Course Technology. &lt;/p&gt;</description>
      <category>개인 공부</category>
      <category>오토마타</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/45</guid>
      <comments>https://junbyeol.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 9 Oct 2025 22:09:19 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 2. 유한 비결정적 오토마타(NFA, Non-deterministic Finite Automata)</title>
      <link>https://junbyeol.tistory.com/44</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;NFA&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFA의 전이함수 &lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot;&gt;&amp;delta;는 가능한 모든 상태와 알파벳에 대하여, 하나의 다음 상태를 반환한다고 했다. 이 특징은 DFA의 이름에 들어간 &quot;Deterministic(결정적인)&quot; 이라는 수식어에서도 알 수 있다. 반면 &quot;Non&quot;-deterministic한 유한 오토마타인 NFA(Nondeterministic Finite Automata)의 전이함수 &lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot;&gt;&amp;delta;는 반환값이 하나가 아닌 여러개이거나, 0개일수도 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8nTQF/btsQ2r1gB9l/3rQ0D4Tlzl9WvLXDWbyHd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8nTQF/btsQ2r1gB9l/3rQ0D4Tlzl9WvLXDWbyHd1/img.png&quot; data-alt=&quot;Figure 1. NFA diagram, 출처 [1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8nTQF/btsQ2r1gB9l/3rQ0D4Tlzl9WvLXDWbyHd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8nTQF%2FbtsQ2r1gB9l%2F3rQ0D4Tlzl9WvLXDWbyHd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;190&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 1. NFA diagram, 출처 [1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 diagram의, $q_0$에서 뻗어나오는 화살표를 보자. 0이라는 입력에 대해 두 개의 화살표로 뻗어나오고 있다. 반면 $q_1$의 경우, 0이라는 입력에 대해 어떤 뻗어나오는 화살표도 찾아볼 수 없다. 위 오토마타에 &quot;00101&quot;이라는 입력이 들어왔을 때를 생각해보자. 이제는 상태변화가 한 줄기가 아니라, 여러 가지로 갈라지는 트리의 모습으로 나타난다. 더 이상 뻗어나갈 수 없는 stuck 상태도 확인하자. 그리고, &quot;00101&quot;로부터 도달한 상태들 중 하나가 최종 상태(final state)인 $q_2$이므로, 이 오토마타는 &quot;00101&quot;을 수용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uZkUp/btsQ3nxfdQb/kuNcVECT2v9mZa2JmybY5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uZkUp/btsQ3nxfdQb/kuNcVECT2v9mZa2JmybY5K/img.png&quot; data-alt=&quot;Figure 2. computation history of NFA is tree&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uZkUp/btsQ3nxfdQb/kuNcVECT2v9mZa2JmybY5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuZkUp%2FbtsQ3nxfdQb%2FkuNcVECT2v9mZa2JmybY5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;588&quot; height=&quot;272&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 2. computation history of NFA is tree&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;NFA는 때로는 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;를 입력으로 허용하기도 한다. Figure 3의 오토마타는 $q_1$에서 $q_3$로 전이할 때, &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;을 허용한다. 입력이 'b'라면, $q_1 \to q_2$도 당연히 가능하지만, $q_1 \to q_3 \to \text{(stuck)}$의 computation history도 가능하다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;나중을 위해, &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;-closure의 개념을 소개한다. 어떤 상태 q로부터 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon; 전이만을 이용해서 도달가능한 상태들의 집합을 의미한다. 예를 들어, Figure 3에서 $q_1$의 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;-closure는 $\{q_1, q_3\}$&lt;/span&gt;이다. 반면, $q_2$는 가능한&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon; 전이가 없어, $q_2$의 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;-closure는 자기 자신 뿐인 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;$\{q_2\}$다. 이것을 $ext(q_2)=&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;\{q_2\}&lt;/span&gt;$ 로 표기하기로 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;※ [2]에서는 NFA에서&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;의 입력을 허용하지만, [1]에서는 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;의 입력을 허용하고, nondeterministic한 유한 오토마타를 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;-NFA라는 이름으로 구분하고 있다. 달리 말하면, [1]은 NFA의 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;를 허용하지 않으며, NFA를 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;-NFA의 부분집합으로 여긴다&lt;/span&gt;. 필자는 [2]의 흐름대로 &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon;-NFA와 NFA를 구분하지 않고 동일하게 다룬다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FNR1r/btsQ2hj3H67/erz7RkbD0KYOyajUYQMFa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FNR1r/btsQ2hj3H67/erz7RkbD0KYOyajUYQMFa0/img.png&quot; data-alt=&quot;Figure 3. NFA diagram, 출처 [2]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FNR1r/btsQ2hj3H67/erz7RkbD0KYOyajUYQMFa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFNR1r%2FbtsQ2hj3H67%2Ferz7RkbD0KYOyajUYQMFa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;358&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 3. NFA diagram, 출처 [2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;엄격한 정의&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wDems/btsQ3km133b/rRnX31yHILjozpKiiEPKnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wDems/btsQ3km133b/rRnX31yHILjozpKiiEPKnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wDems/btsQ3km133b/rRnX31yHILjozpKiiEPKnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwDems%2FbtsQ3km133b%2FrRnX31yHILjozpKiiEPKnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;228&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFA와는 다른 것은&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: left;&quot;&gt;&amp;delta;의 정의뿐이다. NFA의 전이함수 &lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: left;&quot;&gt;&amp;delta;에는 알파벳 뿐만 아니라, &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&amp;epsilon; 또한 입력이 가능하다. 그리고 출력은 Q의 부분집합들의 집합, 즉, 멱집합(power set)이다. 예를 들자면, Figure 3의 $q_2$에서 'a'에 대응하는 화살표는 두 개이므로, $\delta(q_2, a) = \{q_2, q_3\}$ 라고 표현할 수 있다. 반면 $q_1$에서는 $delta(q_1, a) = \phi%다. 그리고, 어떤 NFA N이 인식하는 언어는 아래와 같이 정의된다. Figure 2에서 $\delta(q_0, 00101)=\{q_0, q_2\}$이었고, F의 원소인 $q_2$를 포함했으니, 00101은 '수용'임을 떠올리면, 아래의 정의를 이해할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMpO4R/btsQ4CmU33N/tjHPWlfco9qQpiJDCdA0k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMpO4R/btsQ4CmU33N/tjHPWlfco9qQpiJDCdA0k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMpO4R/btsQ4CmU33N/tjHPWlfco9qQpiJDCdA0k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMpO4R%2FbtsQ4CmU33N%2FtjHPWlfco9qQpiJDCdA0k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;96&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/44</guid>
      <comments>https://junbyeol.tistory.com/44#entry44comment</comments>
      <pubDate>Thu, 9 Oct 2025 16:00:52 +0900</pubDate>
    </item>
    <item>
      <title>스킨을 적용한 티스토리 블로그에서 MathJax로 수식 입력하기</title>
      <link>https://junbyeol.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;티스토리 글에서 수식을 입력하기 위해 &lt;a href=&quot;https://docs.mathjax.org/en/latest/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MathJax&lt;/a&gt;를 이용하면 편리하다. 자바스크립트 MathJax 모듈 내 코드는 LaTeX 문법을 수식으로 변환해준다. 예를 들어 `1&amp;nbsp;\lt&amp;nbsp;2` 를 $ 1&amp;nbsp;\lt&amp;nbsp;2 $ 로 보이게끔 바꿔준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 MathJax를 적용하는 방법은 구글링을 통해 쉽게 찾을 수 있다. 그리고 대다수의 블로그는 티스토리의 스킨 HTML의 &amp;lt;head&amp;gt; 태그 안에 아래의 &amp;lt;script&amp;gt; 태그를 넣으면 된다고 간단히 설명한다.&lt;/p&gt;
&lt;pre id=&quot;code_1759905765585&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;https://polyfill.io/v3/polyfill.min.js?features=es6&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script id=&quot;MathJax-script&quot; async src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나, 이 방법은 나에게 동작하지 않았다. 정확히는 매번 화면에 진입할때마다 수식이 제대로 보이기도 했고, 안 보이기도 했다. 그 이유는 티스토리가 글을 로딩하는 시점과 MathJax 모듈을 로딩하는 시점의 차이 때문으로 분석된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 티스토리 글이 MathJax 모듈보다 먼저 로딩(실행)될 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 티스토리 글 로딩 -&amp;gt; 로딩된 티스토리 글이 DOM에 삽입 -&amp;gt; MathJax 모듈 로딩 -&amp;gt; MathJax의 DOM 내 수식 변환 로직 구동 -&amp;gt; 정상적으로 수식들이 보임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 티스토리 글보다 MathJax 모듈이 나중에 로딩(실행될 경우)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MathJax 수식 변환 로직 구동 -&amp;gt; DOM 내의 모든 수식 변환 완료(?) -&amp;gt; 티스토리 글 로딩 -&amp;gt; 티스토리 글 내에 LaTeX 구문이 변환되지 않은채로 그대로 남게됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로, MathJax 문서에서는 &lt;a href=&quot;https://docs.mathjax.org/en/latest/advanced/typeset.html#handling-new-content&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;동적인 컨텐츠를 포함한 페이지의 경우 별도의 셋팅이 필요&lt;/a&gt;함을 설명하고 있다. 이 문서에 의하면 수식 변환은 MathJax.typeset() 혹은 MathJax.typesetPromise() 함수를 호출하면 이루어진다고 한다. 그래서 나는 티스토리 글과 MathJax 모듈이 DOM에 모두 준비된 후에, typeset 함수를 실행해주면 되는것이었다. 그렇다면, 이것을 어떻게 구현할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티스토리는 [## title ##] 처럼 앞뒤가 이상한 괄호로 둘러쌓인 치환자가 포함된 HTML을 제공하면, 그것을 블로그 스킨으로 사용할 수 있도록 지원한다. 이 HTML 코드는 티스토리의 어드민 화면에서 직접 수정할 수 있는데, 여기에는 많은 문제점이 뒤따른다. 이 문제점들을 해소할 수 있는 &lt;a href=&quot;https://github.com/pronist/tidory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pronist/tidory&lt;/a&gt;라는 오픈소스 프레임워크가 있으며, 이 프레임워크로 개발된&amp;nbsp;&lt;a href=&quot;https://github.com/search?q=tistory&amp;amp;type=repositories&amp;amp;s=stars&amp;amp;o=desc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pronist/hello&lt;/a&gt; 스킨의 fork을 내 블로그는 2025년 현재 사용중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도, 이 코드베이스에는 글의 로딩이 완료된 시점에 호출되는 이벤트리스너(alpine:init)의 코드가 이미 있었다. 그래서 티스토리 글의 로딩이 완료된 시점을 파악하는 것은 매우 쉬웠다. 또, MathJax는 &amp;lt;script&amp;gt; 태그를 통해 호출되기에, 이 태그의 onload 를 이용하면, MathJax 모듈 로딩이 완료된 시점도 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 &lt;a href=&quot;https://github.com/junbyeol/hello_jb/commit/bc7e0c19d8b878bd5505ad1c6413b31cee57fd53&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;작업 커밋의 링크&lt;/a&gt;를 공유하고, 글을 마친다.&lt;/p&gt;</description>
      <category>일상이야기</category>
      <category>Mathjax</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/43</guid>
      <comments>https://junbyeol.tistory.com/43#entry43comment</comments>
      <pubDate>Wed, 8 Oct 2025 16:09:00 +0900</pubDate>
    </item>
    <item>
      <title>[오토마타] 1. 유한 결정적 오토마타(DFA, Deterministic Finite Automata)</title>
      <link>https://junbyeol.tistory.com/42</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터란 무엇인가?&lt;/li&gt;
&lt;li&gt;컴퓨터는 무엇을 할 수 있고, 무엇을 할 수 없는가?&lt;/li&gt;
&lt;li&gt;컴퓨터가 연산하기 쉬운 문제와 어려운 문제의 차이는 무엇인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 질문들은 컴퓨터 과학의 가장 근본적인 질문들이다. 연산 이론(computational theory)은 위 질문들을 고민하는 전산학의 한 꼭지다. 이 질문들이 어려운 이유는 현대의 컴퓨터는 수학적으로 정의하기 너무나도 복잡한 기계장치이기 때문이다. 컴퓨터란 무엇일까? 컴퓨터의 특징을 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터의 메모리는 유한하다. 즉 메모리가 표현할 수 있는 상태들도 유한하다.&lt;/li&gt;
&lt;li&gt;컴퓨터의 연산 결과 또한 유한한 메모리로 표현가능하다.&lt;/li&gt;
&lt;li&gt;컴퓨터는 연산을 수행하는 장치이다. 그러나 컴퓨터는 입력이 들어오기 전까지, 다음에 자신이 수행해야 할 연산에 대해 알 수 없다.&lt;/li&gt;
&lt;li&gt;컴퓨터는 몇 가지 기본 연산을 가지고, 복잡한 연산을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컴퓨터의 특징들을 공유하는 수학적인 모델, 유한 오토마타(Finite Automation)를 소개한다. 그 전에, 오토마타의 정의에 필요한 기본적인 개념들부터 소개한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;알파벳, 문자열, 언어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;알파벳(Alphabet)&lt;/b&gt;: 유한하고 비어있지 않은 문자들의 집합. &lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;&amp;Sigma;로 표기한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TuJsj/btsQKB2RfTn/R2M6mTeypOzsdfG25UKXF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TuJsj/btsQKB2RfTn/R2M6mTeypOzsdfG25UKXF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TuJsj/btsQKB2RfTn/R2M6mTeypOzsdfG25UKXF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTuJsj%2FbtsQKB2RfTn%2FR2M6mTeypOzsdfG25UKXF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;221&quot; height=&quot;137&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문자열(String)&lt;/b&gt;: 알파벳들의 유한히 나열한것(finite sequence). 나열된 알파벳의 개수는 길이(Length)라고 부른다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;길이가 0인 문자열은 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&amp;epsilon;로 표기한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;$ \Sigma^{i} $는&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt; 길이가 i인 문자열의 집합을 의미한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;$ \Sigma^{*} $&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;는&lt;/span&gt; 주어진 알파벳으로 만들 수 있는 모든 문자열의 집합을 의미한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgtXop/btsQKRdhWHn/poAXth9rKQgiueL2cKcYck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgtXop/btsQKRdhWHn/poAXth9rKQgiueL2cKcYck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgtXop/btsQKRdhWHn/poAXth9rKQgiueL2cKcYck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgtXop%2FbtsQKRdhWHn%2FpoAXth9rKQgiueL2cKcYck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;339&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;언어(Language)&lt;/b&gt;: 주어진 알파벳 &lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;&amp;Sigma; 위의 우리가 관심있는 문자열들의 집합이다. &lt;/span&gt;$ \Sigma^{*} $의 부분집합이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 우리가 어떤 언어 L를 &quot;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;주어진 알파벳&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;&amp;Sigma;={0,1} 위에서, 0과 1의 개수가 같은&lt;/span&gt; 문자열들의 집합&quot; 이라고 정의한다면, '0110'은 L의 원소이지만, '111'은 아닐 것이다. 우리는 0110에 대해서는 True(=1)를, 111에 대해서는 False(=0)을 반환하는 함수 f가 필요하다. 함수 f는 어떤 문자열 x가 주어지면, 그 x가 L의 원소인지 아닌지 판단하는 일종의&lt;b&gt; membership test&lt;/b&gt;다. 수학적으로 표현하면 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o9XXH/btsQKc3aqIP/U37IDnyExZOidNvk8EXCwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o9XXH/btsQKc3aqIP/U37IDnyExZOidNvk8EXCwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o9XXH/btsQKc3aqIP/U37IDnyExZOidNvk8EXCwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo9XXH%2FbtsQKc3aqIP%2FU37IDnyExZOidNvk8EXCwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;432&quot; height=&quot;112&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유한 오토마타&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n2tUb/btsQJaEKloy/dEjNIJgHXFXrodiYZmIxp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n2tUb/btsQJaEKloy/dEjNIJgHXFXrodiYZmIxp1/img.png&quot; data-alt=&quot;Figure 1. transition diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n2tUb/btsQJaEKloy/dEjNIJgHXFXrodiYZmIxp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn2tUb%2FbtsQJaEKloy%2FdEjNIJgHXFXrodiYZmIxp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;298&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 1. transition diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 transition diagram을 살펴보자. 동그라미 안의 $q_1,q_2$는 상태(states)라고 부른다. 상태가 2개 혹은 유한개 있는 이 오토마타는 &lt;b&gt;유한 오토마타(Finite Automata)&lt;/b&gt;라고 부른다. 어떤 문자열이 이 오토마타에 입력되면, 이 오토마타는 해당 문자열이 우리가 원하는 언어 A에 속하는지 아닌지 판단하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, &quot;0011&quot;이 입력 됐다고 하자. 화살표가 시작되는 $q_1$을 시작 상태(start state)라고 부른다. $q_1$ 부터 시작하여, 문자열의 앞글자씩 확인하며 $q_1 \to q_1 \to q_2 \to q_2$ 로 상태가 전환된다. 다이어그램의 $q_2$ 동그라미는 테두리가 두 겹으로 되어 있는데, 이것을 최종 상태(final state)라고 부른다. 문자열 &quot;0011&quot;은 최종상태 $q_2$를 마지막으로 도착하였기에 &quot;0011&quot;은 우리가 원하는 언어다. 하지만 &quot;010&quot;은 $q_1$에서 멈추기에, 언어가 아니다. 우리는 이것을 이렇게 표현한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0YBMo/btsQKHWkJCQ/Hd6y0Z9YT1WD8UUE9vYB1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0YBMo/btsQKHWkJCQ/Hd6y0Z9YT1WD8UUE9vYB1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0YBMo/btsQKHWkJCQ/Hd6y0Z9YT1WD8UUE9vYB1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0YBMo%2FbtsQKHWkJCQ%2FHd6y0Z9YT1WD8UUE9vYB1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;516&quot; height=&quot;60&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적으로, 우리는 위 오토마타가 수용(accept)하는 언어는 &quot;1로 끝나는 문자열&quot;임을 이해할 수 있다. 어떤 문자열이 0으로 끝나면 $q_1$에서 연산이 종료될 것이기 때문이다. 이렇게 유한 오토마타로 표현할수 있는 언어는 &lt;b&gt;정규 언어(regular language)&lt;/b&gt;라고 부른다. 언어가 유한 오토마타로 표현되는 정규언어인지 아닌지 판단하는 방법에 대해서는 나중에 다룬다. 언어 A = {1로 끝나는 모든 문자열}은 정규 언어다. 복잡해보이는 다이어그램을 &quot;1로 끝나는 문자열&quot;로 특징을 뽑아내는 것을 characterize한다고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 표현도 사용한다. 이때 L(M)은 M에 의해 수용되는 모든 문자열들의 집합이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OdxPc/btsQP9smZcQ/BWMrl2V7y8vMseNQVi43g0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OdxPc/btsQP9smZcQ/BWMrl2V7y8vMseNQVi43g0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OdxPc/btsQP9smZcQ/BWMrl2V7y8vMseNQVi43g0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOdxPc%2FbtsQP9smZcQ%2FBWMrl2V7y8vMseNQVi43g0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;75&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;엄격한 정의&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XwU72/btsQRIApbqA/zY9sfDFZzJL3EMMXFJWEp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XwU72/btsQRIApbqA/zY9sfDFZzJL3EMMXFJWEp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XwU72/btsQRIApbqA/zY9sfDFZzJL3EMMXFJWEp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXwU72%2FbtsQRIApbqA%2FzY9sfDFZzJL3EMMXFJWEp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;240&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;M은 5개의 요소로 정의된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Q는 가능한 모든 상태들의 집합이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;&amp;Sigma;는 가능한 알파벳이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;&amp;delta;는 현재 상태와 알파벳을 입력으로 받아 다음 상태를 출력하는 전이 함수다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;$q_0$는 시작 상태(start state)를 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;F는 가능한 최종 상태(final state)의 집합을 의미한다. 공집합도 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;그리고 &quot;Deterministic&quot;이라는 단어는 하나의 상태, 하나의 알파벳이 주어졌을때 가능한 다음 상태는 오직 하나임을 말한다. 다음 상태가 두 개 이상이거나 없는 경우는 &quot;Nondeterministic&quot;으로 구분된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;&amp;delta; 전이 함수를 정의하는 방법이 고민일 수 있다. 첫번째 방법은 앞서 소개한 &lt;b&gt;transition diagram&lt;/b&gt;을 이용하는 것이다. 두번째 방법은 &lt;b&gt;transition table&lt;/b&gt;을 이용하는 것이다. Figure 2와 같은 형태다. $\to$ 기호는 그 상태가 시작 상태임을, * 기호는 그 상태가 최종 상태임을 표현한다. Diagram을 이용하던, table을 이용하던 우리가 지금 다루는 DFA는 &quot;Deterministic&quot; 하기 때문에, 모든 상태와 모든 입력 알파벳에 대해서 더도말고 덜도말고 딱 하나의 다음 상태가 반드시 존재해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lutt9/btsQRvBNUwD/bI8iRxGu9QYBGmwEzJebPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lutt9/btsQRvBNUwD/bI8iRxGu9QYBGmwEzJebPK/img.png&quot; data-alt=&quot;Figure 2. transition table&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lutt9/btsQRvBNUwD/bI8iRxGu9QYBGmwEzJebPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flutt9%2FbtsQRvBNUwD%2FbI8iRxGu9QYBGmwEzJebPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;328&quot; height=&quot;224&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 2. transition table&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;&amp;delta; 전이함수는 입력으로 알파벳을 받는다. 즉, 지금으로써는 110이 오토마타에 입력되었을 때, 출력되는 오토마타의 상태를 수학적으로 표현하려면, $\delta(\delta(\delta(q_0, 110)))$ 처럼 복잡하게 표현해야 한다. &lt;b&gt;확장된 전이함수(extended transition function)&lt;/b&gt;을 이용해서, $\hat{\delta}(q_0, 110)$ 처럼 간단히 표현할 수 있다. 수학적으로 표현하면 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUcXDM/btsQ4P7lWAR/UMnG8LyQKnX8lc2gzoWx10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUcXDM/btsQ4P7lWAR/UMnG8LyQKnX8lc2gzoWx10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUcXDM/btsQ4P7lWAR/UMnG8LyQKnX8lc2gzoWx10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUcXDM%2FbtsQ4P7lWAR%2FUMnG8LyQKnX8lc2gzoWx10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;222&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 $\hat{\delta}$를 이용해서, 어떤 DFA M이 인식하는 언어 A를 아래처럼 담백하게 다시 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGBfrZ/btsQ3HPG9Ld/QQl6syO8KTZSzSK3XhUCk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGBfrZ/btsQ3HPG9Ld/QQl6syO8KTZSzSK3XhUCk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGBfrZ/btsQ3HPG9Ld/QQl6syO8KTZSzSK3XhUCk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGBfrZ%2FbtsQ3HPG9Ld%2FQQl6syO8KTZSzSK3XhUCk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;134&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/42</guid>
      <comments>https://junbyeol.tistory.com/42#entry42comment</comments>
      <pubDate>Mon, 29 Sep 2025 16:39:43 +0900</pubDate>
    </item>
    <item>
      <title>취미로 백준을 풀어보려는 사람에게</title>
      <link>https://junbyeol.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저는 고등학생 시절 정보올림피아드를 준비해본 적도 있고, 학교 전공 수업에서도 여러 이론들을 접했지만, 꽤 오래 개발만을 하면서 알고리즘과는 멀어져 있었습니다. 그러나 모종의 이유로, 최근 &lt;a href=&quot;https://www.acmicpc.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;백준&lt;/a&gt;을 시작했습니다. 언어는 가장 기본이라 생각되는 c++로 정했습니다. 약 한 달 정도 백준을 풀어본 지금, 백준을 시작하려고 하는 과거의 저에게 알려줬다면 관심있게 들었을 내용들을 소개해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;solved.ac&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백준을 시작하려는 마음은 먹었으나, 어떻게 시작해야할지 당혹스러울 수 있습니다. 처음에는 백준에서 제공하는 &lt;a href=&quot;https://www.acmicpc.net/step&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;단계별로 풀어보기&lt;/a&gt;탭을 이용하였고, 이 순서대로 문제를 풀어보는 것도 나쁘지 않아 보입니다. 하지만, &lt;a href=&quot;https://solved.ac/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;solved.ac&lt;/a&gt;는 훨씬 강한 동기부여를 제공합니다. solved.ac는 내가 해결한 문제들을 바탕으로 나의 티어를 책정합니다. 이 티어를 올리기 위해서라도 백준 문제를 꾸준히 풀게 되었습니다. 또한, 풀만한 문제들을 &lt;a href=&quot;https://solved.ac/class&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;클래스&lt;/a&gt;별로 제공하고, 문제들을 난이도와 태그로 구분하고 있어서, 어떤 문제를 풀어야할지 큐레이션해줍니다. 현재 저는 티어는 고작 골드4에, class 4*의 모든 문제를 풀었다고 이 글을 써보고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDltW6/btsPBMENbFb/DmT15NXId41GY37xKeeEj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDltW6/btsPBMENbFb/DmT15NXId41GY37xKeeEj0/img.png&quot; data-alt=&quot;제 티어입니다 ㅎㅎ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDltW6/btsPBMENbFb/DmT15NXId41GY37xKeeEj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDltW6%2FbtsPBMENbFb%2FDmT15NXId41GY37xKeeEj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;458&quot; height=&quot;250&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;제 티어입니다 ㅎㅎ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로드맵&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스4까지 풀어보니 거의 모든 알고리즘 문제들은 아래의 5가지 카테고리 안에 묶일 것 같았습니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재귀 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DFS, BFS로 그래프를 순회하거나, 스도쿠, N-queen 문제처럼 백트래킹을 잘해야하는 문제들입니다. '별그리기'처럼 분할정복을 요구하기도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다이나믹 프로그래밍
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배낭문제나 누적합구하기 등 다양한 유형들이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 두 카테고리는 기본적인 원리를 이해하고, 그 이후에는 프로그래밍적인 감각으로 풀어내야하는 유형들이라면, 아래는 기초가 되는 알고리즘들을 알고 잘 적용하는 것이 중요한 유형들입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래프
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래프의 간선(edge)들 중 일부를 택하여, 모든 정점(vertex)들을 연결하는 최소의 방법을 구하는 것은 최소신장트리(MST, Minimum Spanning Tree)를 구하라는 것과 같은 말입니다. 간선 중심의 크루스칼(Kruskal's) 알고리즘은 O(ElogE)의 시간복잡도를, 정점 중심의 프림(Prim's) 알고리즘은 O(V^2)의 시간복잡도를 가지니, 문제 상황에 따라 적절한 알고리즘을 골라야합니다.&lt;/li&gt;
&lt;li&gt;어느 정점에서 다른 모든 정점들 사이의 거리들을 모두 알고 싶으면 다익스트라(dijkstra) 알고리즘을 사용해야합니다. 그런데, 주어진 그래프에 음수 거리가 있거나, 음수 사이클의 유무를 알고 싶다면 밸만-포드(Bellman-Ford) 알고리즘을 사용합니다. 각각 O((V + E)logV)와 O(VE)의 시간복잡도를 갖습니다.&lt;/li&gt;
&lt;li&gt;모든 두 정점들간의 거리를 알고 싶다면 플로이드-워셜(Floyd-Warshall) 알고리즘을 사용합니다. 무려 O(V^3)의 시간복잡도입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전위/중위/후위순회를 알아야 합니다.&lt;/li&gt;
&lt;li&gt;트리의 지름을 구하는 법은 다음과 같습니다. 1. 임의의 한 지점에서 가장 먼 노드 a를 찾는다. 2. 다시 그 노드에서 가장 먼 노드 b 를 찾는다. 3. a와 b 간의 거리가 곧 트리의 지름이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;수학
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 카테고리는 배경 지식이 없다면 정말 풀기 어렵다고 생각합니다. 분할정복으로 피보나치 수를 O(logN)만에 구한다거나, 좌표가 주어졌을때 다각형의 넓이를 구한다거나, 선분들의 교차 여부를 확인한다거나, 정수론적인 지식들을 요구합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유용한 라이브러리와 팁&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 c는 사용해봤지만, c++은 이번에 처음 사용해봤습니다. 미리 알았다면 좋았을지도 모를, c++의 유용한 라이브러리 및 팁들을 소개합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;#include &amp;lt;vector&amp;gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선언할때 최대 크기를 지정하지 않아도 되는 동적인 배열입니다. 대표적으로, 그래프의 연결상태를 저장하는 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%B8%EC%A0%91_%EB%A6%AC%EC%8A%A4%ED%8A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;인접리스트(adjacent list)&lt;/a&gt;를 선언하기 위해 자주 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;#include &amp;lt;queue&amp;gt;, #include &amp;lt;stack&amp;gt;, #include &amp;lt;unordered_map&amp;gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각종 알고리즘 문제를 풀려면 사용해야하는 자료구조들입니다. priority_queue도 queue 라이브러리 안에 포함되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;#include &amp;lt;climits&amp;gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;INT 범위의 최소/최대값인 INT_MAX와 INT_MIN이 있습니다. 꽤 쓸 일이 많습니다. 물론 #define INF 1e9처럼 적당히 큰/작은 수를 사용하는 것이 더 유용할 때도 있지만요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;#include &amp;lt;cstring&amp;gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int, char, bool 처럼 string 타입을 사용하고 싶을때 import 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;#include &amp;lt;tuple&amp;gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2개의 원소쌍인 pair&amp;lt;X, Y&amp;gt; 타입은 따로 import 하지 않아도 기본적으로 사용할 수 있으나, 3개의 원소쌍인 tuple&amp;lt;X, Y, Z&amp;gt;는 tuple 라이브러리를 요구합니다. pair나 tuple을 이용하면, 조건문 속에서 크기 비교를 할 때 사전순으로 편히 쓸수 있어서 유용합니다. 상황에 따라 그냥 아래처럼 struct를 만들어 사용하는 것이 더 유용할 때도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1753710232750&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Edge
{
    int u, v, w;
};&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;#include&amp;lt;algorithm&amp;gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sort 함수를 제공합니다. 생각보다 쓸 일이 많지는 않았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기타&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내 코드가 왜 틀렸는지 모르겠을 때에는 &lt;a href=&quot;https://testcase.ac/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;testcase.ac&lt;/a&gt;에서 반례를 찾을 수 있을지도 모릅니다. 유용하기는 하나, 코딩테스트 등의 실전에서는 쓸 수 없으니 너무 의존하지 않으려고 하고 있습니다.&lt;/li&gt;
&lt;li&gt;저는 &lt;a href=&quot;https://github.com/junbyeol/baekjoon-ps&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이렇게&lt;/a&gt; 해결 코드들을 모으고 있습니다. 어차피 해결한 문제라면 백준에서 확인할 수 있기는 하지만, 문제를 푸는것을 커밋으로 남기는 것이 또 다른 동기부여가 되기도 하고, 어차피 로컬에서 반복적으로 실행해야할 코드를 편히 돌려볼 수 있도록 하였습니다.&lt;/li&gt;
&lt;li&gt;입출력 양이 매우 많고, 주어진 시간이 짧은 문제들은 입출력에서도 최적화가 필요합니다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;main 함수의 첫 두줄은 scanf와 printf 사용을 포기하는 대신, cin과 cout의 성능을 최적화합니다.&lt;/li&gt;
&lt;li&gt;cout 에서 줄바꿈을 출력할 때, endl 대신 &quot;\n&quot;을 사용해야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1754311668800&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    // ...
    
    cout &amp;lt;&amp;lt; &quot;...&quot; &amp;lt;&amp;lt; &quot;\n&quot;; // endl 사용금지
    
    return 0;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>일상이야기</category>
      <category>백준</category>
      <category>입문</category>
      <category>취미</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/41</guid>
      <comments>https://junbyeol.tistory.com/41#entry41comment</comments>
      <pubDate>Mon, 28 Jul 2025 23:13:00 +0900</pubDate>
    </item>
    <item>
      <title>개발 생산성을 높이는 MCP의 개념과 Cursor AI 설정법</title>
      <link>https://junbyeol.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;바이브 코딩&quot;이라는 단어와 함께 MCP에 대한 관심이 뜨겁습니다. Cursor AI라는 코드 에디터는 LLM이 직접 로컬 프로젝트 파일들을 직접 수정하고 코드를 작성하며 바이브 코딩을 가능케 합니다(Cursor AI에 대해서는 &lt;a href=&quot;https://junbyeol.tistory.com/17&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;을 참고하세요). 그러나, LLM은 간혹 '할루시네이션' 문제를 일으키거나, 잘못된/버그가 있는 코드를 생성하거나, 최신 정보를 알지 못하여 낡은 답변을 내놓는 등의 한계를 보여줍니다. MCP는 이런 한계점들을 돌파할 수 있도록 LLM에게 쥐어줄 수 있는 도구와 같습니다. 이 글에서는 MCP가 무엇인지와 Cursor AI에서 어떻게 설정할 수 있는지를 소개합니다&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MCP란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP(Model Context Protocol)는 LLM과 외부의 도구를 연결하는 방법에 대한 규약입니다. 기존 웹 생태계에서 브라우저가 서버에 HTTP 요청을 하듯이, AI 에이전트는 MCP 서버에 MCP 요청을 할 수 있습니다. Claude의 개발사인 Antropic에서 &lt;a href=&quot;https://www.anthropic.com/news/model-context-protocol&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;제안&lt;/a&gt;한 프로토콜이며, 구체적인 작동 방식과 명세는 &lt;a href=&quot;https://github.com/modelcontextprotocol&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 링크&lt;/a&gt;를, MCP 클라이언트/서버를 직접 개발하고 싶다면 &lt;a href=&quot;https://modelcontextprotocol.io/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 링크&lt;/a&gt;를 참고하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #45413e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이해하기 쉽게 예를 들어보겠습니다. LLM에게 오늘 날씨를 물어보면, LLM은 대답하지 못하거나 웹 검색 결과를 바탕으로 대답하게 됩니다. 하지만, AI 에이전트에 &quot;오늘의 날씨&quot; MCP 서버를 연결시켜 두었다면, LLM은 필요한 정보를 해당 서버로부터 받아와 답변할 수 있게 됩니다. 마치 HTTP GET 요청을 한 것처럼요. 만약 Github MCP server와 연결한다면, 우리가 github에서 하는 커밋 조회, 브랜치 생성, 이슈 생성 등을 LLM을 통해 수행할 수 있습니다. 당연히, 권한을 인증할 수 있는 접근 토큰을 요구합니다. Slack 메시지를 보내거나, Notion 문서를 정리하는 일도 대신 수행할 수 있습니다. 이미 웬만한 대형 서비스들은 MCP 서버를 지원합니다.&lt;/p&gt;
&lt;p style=&quot;color: #45413e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #45413e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;HTTP와 다른 점은, MCP 서버를 향한 MCP 요청들이 하나의 게이트웨이를 통한다는 것입니다. 대표적으로, &lt;a href=&quot;https://smithery.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Smithery&lt;/a&gt;가 있습니다. Smithery는 다양한 MCP 서버들을 통합하는 플랫폼입니다. Smithery에서 특정 MCP 서버의 키를 발급받아 Cursor에 전달해두면, 후에 Cursor는 Smithery를 통하여 여러 MCP 서버들에 접근할 수 있게됩니다. 그림으로 표현하면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BP9a7/btsPkuSQXpN/mvzgkE9PyFaV5J2TnxsrEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BP9a7/btsPkuSQXpN/mvzgkE9PyFaV5J2TnxsrEk/img.png&quot; data-alt=&quot;MCP 동작 다이어그램&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BP9a7/btsPkuSQXpN/mvzgkE9PyFaV5J2TnxsrEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBP9a7%2FbtsPkuSQXpN%2FmvzgkE9PyFaV5J2TnxsrEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;655&quot; height=&quot;376&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MCP 동작 다이어그램&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cursor에 MCP 설정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 우리의 Cursor에서 MCP를 사용할 수 있도록 설정하는 방법을 소개합니다. 이 글에서는 포켓몬 정보를 가져오는 MCP를 Cursor에 추가해보겠습니다. Smithery에 로그인한 후, &lt;a href=&quot;https://smithery.ai/server/@smithery-ai/github&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 링크&lt;/a&gt;로 들어가봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt4ZVv/btsPlfALswT/XRichpdTiUXZgzH8QDi5Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt4ZVv/btsPlfALswT/XRichpdTiUXZgzH8QDi5Kk/img.png&quot; data-alt=&quot;포켓몬 MCP 서버&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt4ZVv/btsPlfALswT/XRichpdTiUXZgzH8QDi5Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt4ZVv%2FbtsPlfALswT%2FXRichpdTiUXZgzH8QDi5Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1414&quot; height=&quot;912&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;포켓몬 MCP 서버&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 이용할 MCP 서버에 대한 개괄(About)과 기능(tools)을 소개하고 있습니다. 연결 방법(Connect)은 여러가지가 있지만, 저는 JSON 방식을 선호합니다. 우측 하단에서 JSON 탭을 누르고, Connect 버튼을 누르면 설정에 필요한 JSON을 받을 수 있습니다. 이 pokemcp는 특별한 인증 수단을 요구하지 않지만, mcp 서버에 따라서는 connect 버튼을 누르기 전에 다른 토큰을 요구할 수도 있습니다. 용도에 맞게 전달하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커서에서는 맥 기준, Cursor &amp;gt; Preferences &amp;gt; Cursor Settings에서 MCP 탭으로 이동합니다. 그러면 나오는 아래의 화면 우측 상단의 파란색 &quot;+Add new global MCP server&quot;를 누릅니다. 그러면 .cursor/mcp.json 파일이 생성됩니다. Smithery에서 복사해온 JSON 값을 붙여넣기 하면 설정은 끝납니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tDt94/btsPk3f9SsI/FhneXBXtsYU22EniM3h1n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tDt94/btsPk3f9SsI/FhneXBXtsYU22EniM3h1n0/img.png&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;685&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.5&quot; style=&quot;width: 48.9223%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tDt94/btsPk3f9SsI/FhneXBXtsYU22EniM3h1n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtDt94%2FbtsPk3f9SsI%2FFhneXBXtsYU22EniM3h1n0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1242&quot; height=&quot;685&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v9rPX/btsPkxhNAD9/TakJQlDzwqKZyZOd78oKhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v9rPX/btsPkxhNAD9/TakJQlDzwqKZyZOd78oKhK/img.png&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;653&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.9149%;&quot; data-widthpercent=&quot;50.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v9rPX/btsPkxhNAD9/TakJQlDzwqKZyZOd78oKhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv9rPX%2FbtsPkxhNAD9%2FTakJQlDzwqKZyZOd78oKhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1208&quot; height=&quot;653&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;세팅하는 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 커서 세팅 화면으로 들어가면 poke-mcp가 추가된 것을 확인할 수 있습니다. 저는 이미지 캡쳐를 위해 토큰을 hidden 값으로 가리느라, 사용불가라고 보이는 상태지만, smithery에서 만들어 준 값을 그대로 사용하면 문제 없이 사용가능합니다. 이제, 채팅에 포켓몬 관련 프롬프트를 넘기면, cursor가 필요성을 파악하고 추가한 mcp 요청을 알아서 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MagEe/btsPkIwG3nu/ByvSxAkkfWUbTAvcgMvFCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MagEe/btsPkIwG3nu/ByvSxAkkfWUbTAvcgMvFCk/img.png&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;681&quot; data-is-animation=&quot;false&quot; style=&quot;width: 62.7351%; margin-right: 10px;&quot; data-widthpercent=&quot;63.47&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MagEe/btsPkIwG3nu/ByvSxAkkfWUbTAvcgMvFCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMagEe%2FbtsPkIwG3nu%2FByvSxAkkfWUbTAvcgMvFCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1215&quot; height=&quot;681&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIle2L/btsPl9NkDkM/z0nyZqdcq9ansQVtirqD5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIle2L/btsPl9NkDkM/z0nyZqdcq9ansQVtirqD5K/img.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;524&quot; data-is-animation=&quot;false&quot; style=&quot;width: 36.1021%;&quot; data-widthpercent=&quot;36.53&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIle2L/btsPl9NkDkM/z0nyZqdcq9ansQVtirqD5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIle2L%2FbtsPl9NkDkM%2Fz0nyZqdcq9ansQVtirqD5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;오늘의 포켓몬은 플라이곤&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발이야기</category>
      <category>Ai</category>
      <category>Cursor</category>
      <category>llm</category>
      <category>MCP</category>
      <category>바이브코딩</category>
      <category>커서</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/40</guid>
      <comments>https://junbyeol.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 16 Jul 2025 19:49:46 +0900</pubDate>
    </item>
    <item>
      <title>OCaml에 대해 실전 속성 압축으로 익혀보기</title>
      <link>https://junbyeol.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저는 지난 봄학기 어느 전공과목에서, OCaml이라는 언어를 이용하여 과제를 해야했습니다. 당연히 이전까지 OCaml을 몰랐고, 딱 과제를 하는데에 지장없는 수준의 실력을 갖게 된 것 같습니다. OCaml의 모든 것을 알고싶지는 않지만, 당장 OCaml로 뭔가를 해야하는 누군가에게 이 글이 도움이 되기를 바라며 작성해봅니다. 저도 절대 OCaml의 전문가가 아님을 다시 한 번 밝힙니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 코드들을 VSCode에서 실행해보고 싶으시면, &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ocamllabs.ocaml-platform&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 익스텐션&lt;/a&gt;을 설치하시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwqsaF/btsOK3A3aad/I7e4DyhHuSFnkGQDpmL0l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwqsaF/btsOK3A3aad/I7e4DyhHuSFnkGQDpmL0l1/img.png&quot; data-alt=&quot;[오-카믈] 이라고 읽는듯하다. Camel과 비슷해서 낙타인가보다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwqsaF/btsOK3A3aad/I7e4DyhHuSFnkGQDpmL0l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwqsaF%2FbtsOK3A3aad%2FI7e4DyhHuSFnkGQDpmL0l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;600&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[오-카믈] 이라고 읽는듯하다. Camel과 비슷해서 낙타인가보다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OCaml의 컨셉과 문법&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;함수형 프로그래밍&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCaml은 철저히 함수형 프로그래밍 언어입니다. 함수형 프로그래밍이 무엇인지도 익숙치 않을 독자를 위해 간단히 예를 들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 일반적인 C나 python등 우리에게 익숙한 명령형 프로그래밍 언어들은 명령어의 나열식으로 코드를 작성합니다.&lt;br /&gt;&lt;b&gt;&quot;일단 냄비에 물을 부어. 스프도 넣어. 불을 올리고, 물이 끓으면 면을 넣어. 3분 정도 지나면 계란을 넣고, 다시 1분 뒤에는 라면을 완성해&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 함수형 프로그래밍 언어인 OCaml은 값 위주로 코드를 작성합니다.&lt;br /&gt;&lt;b&gt;&quot;라면은 냄비에 물과 스프를 넣고 끓던 것에, 추가로 면을 3분 넣고 끓여진 것에, 추가로 계란을 넣고 1분 더 기다려진 것이야&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절한 비유였는지 모르겠습니다. 핵심은 명령형 프로그래밍은 명령의 나열이라는 것입니다. 아래의 python 코드를 생각해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1750514719332&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a = 1
b = 6
if b &amp;gt; 5:
    a = a + 1

def sum(a, b):
    return (a + b)

print(f'Sum of {a} and {b} is {sum(a, b)}')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 동일한 구현체를 함수형 프로그래밍 스타일의 OCaml로 작성해보겠습니다. 함수형 프로그래밍은 값.값.값.의 나열입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750515025909&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let b = 6
let a = if b &amp;gt; 5 then 1 + 1 else 1

let sum x y = x + y
  
let () = Printf.printf &quot;Sum of %d and %d is %d\n&quot; a b (sum a b)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let은 변수의 선언입니다. OCaml에서 변수에 담긴 값은 변경할 수 없는 성질, &lt;b&gt;불변성&lt;/b&gt;을 갖습니다. &lt;i&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(ref 키워드를 이용하면 변경가능한 변수를 만들수는 있으나, 함수형 프로그래밍의 철학에는 맞지 않습니다.)&lt;/span&gt;&lt;/i&gt; 그래서, 위의 Python 코드와는 달리 한 번 선언한 변수 a의 값이 변경되는 것을 허용하지 않았습니다. 또한, b의 크기 조건에 따라 a의 값을 업데이트 하는 코드 역시, a의 값을 정하는 표현식에 in-line으로 포함되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 역시 &quot;값&quot; 으로 취급됩니다. Python에서는 변수와 함수를 선언하는 방법이 각각 다릅니다. 그러나, 함수형 프로그래밍에서는 함수도 값이며, &lt;b&gt;일급 함수&lt;/b&gt;라고 부릅니다. 위의 코드에 표현하지는 않았으나, 함수가 값이기 때문에, 어떤 함수는 인자로 다른 함수를 받을 수도 있습니다. 그 함수는 &lt;b&gt;고차 함수&lt;/b&gt;라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈여겨 볼 점은 함수 옆의 인자가 sum(x,y) 처럼 괄호로 묶이지 않고, sum x y로 나열되기만 했다는 것입니다. 보통 sum 함수의 타입 혹은 인터페이스를 표현하라고 하면, 우리는 &lt;b&gt;(int, int) -&amp;gt; int&lt;/b&gt; 처럼 표현하고 생각하는 것이 익숙합니다. 그러나 OCaml에서는 &lt;b&gt;int -&amp;gt; int -&amp;gt; int&lt;/b&gt; 로 표현됩니다. 인자1-&amp;gt;인자2-&amp;gt;반환값의 형태입니다. 함수형 프로그래밍에서 &lt;b&gt;커링(currying)&lt;/b&gt;이라는 용어로 불립니다. 이것을 이해하고, 곧 설명할 OCaml의 인터페이스 선언하는 법과 모듈 시스템을 읽어주세요. 잠깐 모듈 시스템을 설명하기 전에 몇개의 OCaml 코드조각들을 더 소개하고 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;패턴 매칭&lt;/h4&gt;
&lt;pre id=&quot;code_1750516372570&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let describe_list my_list =
    match my_list with
    | [] -&amp;gt; &quot;비어있는 리스트&quot;
    | [x] -&amp;gt; &quot;원소가 하나&quot;
    | x :: y :: [] -&amp;gt; &quot;앞에서부터 각각 x, y 원소 두 개&quot;
    | x :: xs -&amp;gt; &quot;x는 맨앞 원소, xs는 x를 제외한 나머지 리스트&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령형 프로그래밍의 if-else나 switch 문을 in-line으로 표현할 수 있게끔 지원하는 기능입니다. 위의 describe_list 함수는 인자로 my_list를 받아서, my_list의 특징을 설명하는 문자열을 반환합니다. 아래 코드처럼 패턴매칭에 타입을 이용할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750516757350&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type shape =
    | Point
    | Circle of float
    | Rectangle of float * float

  let calculate_area s =
    match s with
    | Point -&amp;gt; 0.0
    | Circle r -&amp;gt; Float.pi *. r *. r
    | Rectangle (w, h) -&amp;gt; w *. h&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;재귀&lt;/h4&gt;
&lt;pre id=&quot;code_1750516798229&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  let rec sum list =
    match list with
    | [] -&amp;gt; 0
    | head :: tail -&amp;gt; head + (sum tail)

  let total = sum [1; 2; 3; 4; 5] (* 결과: 1 + (2 + (3 + (4 + (5 + 0)))) 이므로 15 *)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 구현 속에서 함수 스스로를 호출하는 재귀 함수는, &quot;rec&quot; 키워드를 붙여줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;let...in 키워드&lt;/h4&gt;
&lt;pre id=&quot;code_1750517070836&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let value1 = sqrt((3.0 * 3.0) +. (4.0 *. 4.0))

let value2 =
  let a = 3.0 in
  let b = 4.0 in
  let a_squared = a *. a in
  let b_squared = b *. b in
  sqrt (a_squared +. b_squared)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCaml에서 *. 와 +. 연산자는 실수간의 연산, *와 +는 정수간의 곱셈을 의미합니다. 위 코드에서 value1과 value2가 의미하는 바는 같습니다. 하지만, 가독성 확보를 위해 value2처럼 작성하고 싶은 프로그래머를 위해, &lt;b&gt;let...in&lt;/b&gt; 키워드가 존재합니다. let의 표현식 내부에서만 사용할 수 있습니다. &quot;내부&quot;의 의미를 같는 in 키워드이니, 저는 OCaml 코드를 처음 읽을 때 이렇게 이해하고 했습니다.&lt;br /&gt;&lt;b&gt;&quot;value2는 a가 3.0인 세계관 안에서, b가 4.0인 세계관 안에서, a_squared, b_squared 라는 값은 a,b의 제곱인 세계관 안에서, a_squared와 b_squared에 루트(sqrt)를 씌운 값이야!&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모듈 시스템&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 예고했던, 모듈 시스템을 소개해보겠습니다. 어느 언어나 그렇듯이, OCaml도 리스트, 스택, 문자열, 입출력 등 유용한 표준 라이브러리들을 제공합니다. OCaml에서는 주로 모듈이라는 용어를 이용합니다. OCaml 5.3 기준 제공되는 &lt;a href=&quot;https://ocaml.org/manual/5.3/api/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;모든 라이브러리의 링크&lt;/a&gt;를 달아둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCaml에서 헷갈리는 것 중 하나가 리스트를 사용하는 법입니다. 보통 다른 언어들은 a라는 리스트가 있다면, a[0] 처럼 a의 첫 원소에 접근 가능하지만, OCaml은 그렇지 않기 때문입니다. OCaml은 반드시 List.nth 함수 혹은 List.hd를 이용해 접근해야 합니다.&amp;nbsp; List 모듈의 대표적인 함수들을 소개합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750518404568&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let my_list = [10;20;30;40]

let second_elem = List.nth my_list 2
let head_elem = List.hd my_list
let length_of_list = List.length my_list
let exist_20 = List.mem 20 my_list (* true, 리스트에 20이 있으므로 *)

let combined_list1 = List.append my_list [50;60]
let combined_list2 = my_list @ [50;60] (* 두 코드는 동일한 의미 *)

let double = List.map (fun x -&amp;gt; 2 * x) my_list (* [20;40;60;80] *)
let filtered = List.filter (fun x -&amp;gt; x &amp;lt; 25) my_list (* [10;20] *)
let sum = List.fold_left (fun acc x -&amp;gt; acc + x) 0 my_list 	(* 100, 0+10+20+30+40 *)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다시피 List 모듈을 이용할때는 &quot;List.&quot; 을 붙이고, 모듈 내의 함수를 호출하면 됩니다. 내가 만든 모듈도 마찬가지 입니다. OCaml 파일의 확장자는 &quot;.ml&quot;입니다. 내가 &quot;util.ml&quot; 파일에 유틸함수들을 작성했다면(sum 함수를 만들었다면), 다른 곳에서 &quot;Util.sum&quot; 처럼 호출하면 됩니다. 그런데, util.ml에 선언한 모든 값을 외부에 노출하고 싶지 않을 수도 있습니다. 그럴때는 OCaml의 인터페이스 파일인 &quot;.mli&quot;파일을 이용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750519122131&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(* Util.ml *)
let squared_sum x y = (x * x) + (y * y)

let pitagoras x y = sqrt(squared_sum x y)

(* Util.mli *)
val pitagoras : int -&amp;gt; int -&amp;gt; int&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 다른 파일에서, Util.pitagoras는 이용할 수 있지만, Util.squared_sum은 이용할 수 없습니다. 인터페이스 파일에 pitagoras 만이 정의되었기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, &quot;List.&quot;나 &quot;Util.&quot; 같은 접두어를 붙이는 것이 번거롭게 느껴질 때 사용할 수 있는 문법입니다. &quot;Open&quot; 키워드를 이용하면 됩니다. 다만, 서로 다른 모듈에 같은 이름의 함수가 있는 경우가 충분히 가능하므로, 주의해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750519317307&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(* open 문법 이용 X *)
let value = Util.pitagoras 3 4

(* open 문법 이용 *)
open Util
let value = pitagoras 3 4&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Dune과 Opam: 코드 실행 및 외부 의존성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.ml과 util.ml의 코드를 작성했다면, 컴파일 시에 둘을 합쳐야 합니다. C 언어에서 gcc를 사용하듯이, OCaml은 ocamlc를 이용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;ocamlc -o myapp util.ml main.ml&quot;&lt;/b&gt; 은 두 OCaml 파일을 순서대로 합쳐, myapp이라는 이름의 목적 파일을 생성합니다. 하지만, 명령어도 너무 복잡할 뿐더러, 프로젝트가 복잡하면 해당 명령어를 이용하는 것은 정말 복잡해질 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dune은 이런 문제를 해결합니다. 프로젝트의 루트 경로에, 프로젝트 전반의 설정(dune-project)과 파일 빌드구조(dune)를 서술해두면, &quot;dune build&quot; 명령어로 프로젝트의 모든것을 빌드할 수 있습니다. 이 과정은 직접할 필요 없이 &quot;dune init&quot;을 통해 생성하면 됩니다. 뿐만 아니라 dune은, 빌드 결과물을 지우는 &quot;dune clean&quot;, 테스트 코드를 실행하는 &quot;dune test&quot;, 코드의 포맷팅(코드를 시각적으로 정돈하는 일)을 돕는 &quot;dune fmt&quot; 등을 지원합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, npm이나 pip 같은 외부 라이브러리나 의존성을 설치/관리 해주는 도구가 OCaml에도 역시 있습니다. Opam입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실은 &quot;dune fmt&quot;를 실행할 때, &quot;ocamlformat&quot;이라는 의존성을 필요로 합니다. &quot;opam install ocamlformat&quot; 명령어로 설치할 수 있습니다.&lt;/li&gt;
&lt;li&gt;JSON 파싱 라이브러리 &quot;yojson&quot;을 코드에서 사용하고 싶다면, 역시 &quot;opam install yojson&quot;을 하면 됩니다. 단, 코드에서 사용하려고 할때는 dune 파일에 해당 의존성을 사용하겠다는 내용을 명시해야합니다. 그 이후에는 다른 모듈처럼, &quot;Yojson.&quot; 혹은 &quot;open 키워드&quot;로 사용하면 됩니다.&lt;/li&gt;
&lt;li&gt;코드에 의존성들에 대한 구체적인 정보는 &quot;.opam&quot; 파일에 기술됩니다. Node.js 진영의 package.json이나, Python 진영의 pyproject.toml 같은 파일을 떠올리시면 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발이야기</category>
      <category>Ocaml</category>
      <category>가성비</category>
      <category>속성</category>
      <category>실전압축</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/39</guid>
      <comments>https://junbyeol.tistory.com/39#entry39comment</comments>
      <pubDate>Sun, 22 Jun 2025 00:47:30 +0900</pubDate>
    </item>
    <item>
      <title>VSCode 환경에서 make로 빌드되는 c파일 gdb로 디버깅하기</title>
      <link>https://junbyeol.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학기 전공 과목에서 과제를 디버깅하기 위해 설정해 둔 VSCode의 GDB 설정 파일들을 공유합니다(Cursor AI에서도 당연히 동작합니다). 과제는 C 언어로 작성해야 했으며, 미리 정의된 Makefile을 통해 코드의 빌드, 테스트, 클린 작업이 가능했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;282&quot; data-start=&quot;175&quot; data-ke-size=&quot;size16&quot;&gt;Makefile 구성은 대략 아래와 같았습니다. program.c 파일과 관련 헤더 파일들을 하나의 목적 파일(program.o)로 컴파일한 후, 이를 실행 파일로 빌드하는 방식입니다. 특별히 주의할 점은, gcc 명령어를 실행할 때 &quot;-g&quot; 옵션을 넘겨주어야 디버깅가능한 파일로 빌드됩니다. 또, &quot;-O2&quot; 등의 옵션은 디버깅 시에 일부 값을 필터링할 수 있으니 디버깅 목적으로는 사용하지 않아야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;HEADERS = program.h headers.h&lt;br /&gt;&lt;br /&gt;default: program&lt;br /&gt;&lt;br /&gt;program.o: program.c $(HEADERS)&lt;br /&gt;&amp;nbsp; &amp;nbsp; gcc -c program.c -g -o program.o&lt;br /&gt;&lt;br /&gt;program: program.o&lt;br /&gt;&amp;nbsp; &amp;nbsp; gcc program.o -o program&lt;br /&gt;&lt;br /&gt;clean:&lt;br /&gt;&amp;nbsp; &amp;nbsp; -rm -f program.o&lt;br /&gt;&amp;nbsp; &amp;nbsp; -rm -f program&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GDB가 생소하여 이 글을 찾게 된 분들을 위해 간단히 설명해보겠습니다. GDB는 C나 C++ 등의 언어로 작성된 프로그램을 디버깅하는 도구 입니다. 본래 터미널 환경에서 CLI로, 혹은 터미널 안에서 제공하는 GUI를 통해 디버깅을 해야하나, vscode의 확장을 사용하면 간편합니다. 원하는 위치에서 코드의 실행을 멈추기, 특정 시점에 변수에 저장된 값들을 읽기, 함수들의 호출 스택을 확인하기 등을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 .vscode 경로에 launch.json 이라는 파일을 추가해주어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750074939924&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;version&quot;: &quot;0.2.0&quot;,
    &quot;configurations&quot;: [
        {
            &quot;name&quot;: &quot;Debug with sample1&quot;,
            &quot;type&quot;: &quot;cppdbg&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;program&quot;: &quot;${workspaceFolder}/program&quot;,
            &quot;args&quot;: [&quot;-c&quot;, &quot;1&quot;, &quot;-d&quot;, &quot;2&quot;],
            &quot;stopAtEntry&quot;: false,
            &quot;cwd&quot;: &quot;${workspaceFolder}&quot;,
            &quot;environment&quot;: [],
            &quot;externalConsole&quot;: false,
            &quot;MIMode&quot;: &quot;gdb&quot;,
            &quot;setupCommands&quot;: [
                {
                    &quot;description&quot;: &quot;Enable pretty-printing for gdb&quot;,
                    &quot;text&quot;: &quot;-enable-pretty-printing&quot;,
                    &quot;ignoreFailures&quot;: true
                },
                { &quot;text&quot;: &quot;set output-radix 16&quot; }
            ],
            &quot;preLaunchTask&quot;: &quot;build&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 라인은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;version: VSCode의 디버깅 설정이 사용하는 버전 형식.&lt;/li&gt;
&lt;li&gt;name: 디버깅 설정의 이름. 여러개의 설정을 만들어서 원하는대로 실행할 수 있음.&lt;/li&gt;
&lt;li&gt;type: 디버거 종류. &quot;cppdbg&quot;는 C++ Debugger를 사용한다는 뜻.&lt;/li&gt;
&lt;li&gt;request: 디버깅 방식. &quot;launch&quot;는 새로운 프로그램을 실행하는 형식, &quot;attach&quot;는 이미 실행중인 프로세스에 붙는 형식.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;program&lt;/b&gt;: 실행 파일의 이름. gcc가 빌드해준 실행파일을 의미하며, 디버깅의 대상이 됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;args&lt;/b&gt;: 실행할 때 명령줄의 인자. 위 예시의 설정대로 디버깅하면, 다음의 명령을 실행하는 것과 동일.&lt;br /&gt;&lt;b&gt;&quot;program -c 1 -d 2&quot;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;stopAtEntry: 프로그램의 시작점에서 멈출지. false면 멈추고, true면 main()진입 전에 일단 정지.&lt;/li&gt;
&lt;li&gt;cwd: 디버깅 시 사용할 현재 작업 디렉토리&lt;/li&gt;
&lt;li&gt;environment: 실행시 설정할 환경변수&lt;/li&gt;
&lt;li&gt;externalConsole: vscode의 내장 터미널을 사용할지 말지.&lt;/li&gt;
&lt;li&gt;MIMode: &quot;gdb&quot; 외에도 &quot;lldb&quot;(macOS)나 &quot;cppvsdbg&quot;(window) 사용 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;setUpCommands&lt;/b&gt;: GDB 시작시 자동으로 실행되는 명령
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-enable-pretty-printing: 디버거의 변수 출력을 보기 좋게 해줌&lt;/li&gt;
&lt;li&gt;set output-radix 16: 숫자 값들을 16진수로 바꿔줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;preLaunchTask&lt;/b&gt;: 디버깅 시작전에 실행할 &lt;b&gt;태스크(task)&lt;/b&gt;의 이름(태스크에 대해서는 후술)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 중요하다고 생각되고, 위의 설정을 복붙하되 따로 설정해주어야 하는 값들을 볼드처리해 뒀습니다. 더 자세한 옵션들은 &lt;a href=&quot;https://code.visualstudio.com/docs/cpp/launch-json-reference&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 레퍼런스&lt;/a&gt;를 참고하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 마지막 preLaunchTask 옵션에서 설정한 태스크를 설정해주어야 합니다. .vscode 폴더에 tasks.json을 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750076275179&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;version&quot;: &quot;2.0.0&quot;,
    &quot;tasks&quot;: [
        {
            &quot;label&quot;: &quot;build&quot;,
            &quot;type&quot;: &quot;shell&quot;,
            &quot;command&quot;: &quot;make clean &amp;amp;&amp;amp; make&quot;,
            &quot;group&quot;: {
                &quot;kind&quot;: &quot;build&quot;,
                &quot;isDefault&quot;: true
            }
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Makefile에서 정의했던 &quot;make clean&quot; 명령어와 &quot;make&quot; 명령어를 그대로 사용합니다. 위 예제는 이전 빌드 결과를 지우고, 매번 새로운 빌드로 디버깅을 수행하기 위해, 이 태스크를 사전 작업(preLaunchTask)으로 설정하였습니다.&lt;/p&gt;</description>
      <category>개발이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/38</guid>
      <comments>https://junbyeol.tistory.com/38#entry38comment</comments>
      <pubDate>Mon, 16 Jun 2025 21:19:20 +0900</pubDate>
    </item>
    <item>
      <title>[전산기조직] 메모리 계층 구조(Memory Hierarchy)</title>
      <link>https://junbyeol.tistory.com/37</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.1 Introduction&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책꽂이에서 매번 책을 찾아 읽는것보다는, 많이 읽는 책을 책상 위에 두면 시간을 절약할 수 있다. 우리가 필요한 정보는 모든 책에 균등하게 분포되어있지 않기 때문이다. 프로그램 또한, 필요한 코드와 데이터가 균등하게 분포되어 있지 않다. 비슷한 방법으로, 프로그램에게 우리의 메모리가 더 빨라진듯한 환상을 느끼게 해줄 수 있다. 하지만, 빠르면서 큰 메모리는 존재할 수 없다. 책꽂이를 책상위에 얹을수는 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램의 실행은 지역성의 원리(principle of locality)를 따른다. 프로그램은 시간적/공간적으로 이미 참조한 데이터와 가까운 데이터를 다시 참조할 것이라는 의미이다. 구체적으로 설명하자면, 지역성에는 두 가지 종류가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간적 지역성(Temporal locality): 한 번 참조된 데이터는 조만간 다시 참조될 가능성이 높다.&lt;/li&gt;
&lt;li&gt;공간적 지역성(Spatial locality): 어떤 주소공간이 참조되면, 그 주변 주소공간도 곧 참조될 가능성이 높다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램들이 대개 반복문이나 데이터 구조들을 포함하여 작성된다. 그리고, 인스트럭션은 순차적으로 실행되기에, 지역성의 원리는 현실에서도 높은 빈도로 관찰된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역성의 원리를 이용하기 위해, 컴퓨터의 메모리는 계층적으로 구현된다. 메모리 계층(Memory hierarchy)은 서로 다른 크기와 속도의 메모리들로 구성된다. 빠른 메모리일수록 비트당 가격이 비싸고, 그래서 크기도 작다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IBfz2/btsOD4rTJKO/VAMccekXrnX5Qatw7Kkkqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IBfz2/btsOD4rTJKO/VAMccekXrnX5Qatw7Kkkqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IBfz2/btsOD4rTJKO/VAMccekXrnX5Qatw7Kkkqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIBfz2%2FbtsOD4rTJKO%2FVAMccekXrnX5Qatw7Kkkqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;347&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 작지만 프로세서와 가깝고 빠른 위 계층과 그렇지 않은 아래 계층으로 메모리를 추상화할 것이다. 여기서 메모리에 저장되는 최소 단위의 정보를 블럭(block, or line)이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;processor가 찾으려는 데이터가 위 계층에 있으면 히트(hit), 아니면 미스(miss)됐다고 표현한다. 메모리 접근 중 히트 비율(hit rate)는 메모리 계층의 퍼포먼스 척도로 사용된다. 또 다른 퍼포먼스의 척도로는 히트 타임(hit time)이 있다. 히트 타임은 데이터 접근이 히트인지 미스인지를 결정하기 까지 걸리는 시간이다. 미스 페널티(Miss penalty)는 윗 계층의 블럭을 대응하는 아래 계층의 블럭으로 교체하고, 프로세서에 이 블럭을 가져다주기까지의 시간이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.2 Memory Technologies&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘날의 메모리 계층구조를 구현하는 네 가지 핵심 기술에 대해 알아보자. 첫째로, 메인 메모리의 구현에 쓰이는 DRAM과, 둘째로, 프로세서(캐시)에 가까운 계층에서 사용되는 SRAM이 있다. DRAM은 비트 당 가격이 SRAM보다 저렴하지만, 느리다. 가격이 차이나는 이유는, DRAM이 같은 비트를 저장하는데에 특히 더 적은 양의 면적, 즉, 더 적은 양의 실리콘을 사용하기 때문이다. 속도에서 차이가 나는 이유는 부록 B를 참고하라. 세번째 기술은 플래시 메모리(flash memory)이다. 플래시 메모리는 비휘발성으로, 모바일 기기의 2차 기억장치(secondary memory)로 사용된다. 네 번째로, 가장 크고 느린 메모리인 자기 디스크(magnetic disk)이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beLRBf/btsOB1pRF3l/BK4RoAY004QyTo3NPPG0ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beLRBf/btsOB1pRF3l/BK4RoAY004QyTo3NPPG0ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beLRBf/btsOB1pRF3l/BK4RoAY004QyTo3NPPG0ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeLRBf%2FbtsOB1pRF3l%2FBK4RoAY004QyTo3NPPG0ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;109&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;109&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SRAM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SRAM은 보통 읽기와 쓰기를 모두 지원하는 하나의 접근 포트를 갖는다. SRAM은 어떤 데이터에 접근하든 동일한 접근 시간을 가진다.(읽기와 쓰기 시간은 다르다.) SRAM은 새로고침(refresh)이 필요없어서, 접근 시간이 사이클 시간과 거의 동일하다. 보통 비트당 6~8개의 트랜지스터를 가지며, 준비상태에서도 최소한의 전력만으로 충전이 유지된다. 과거에는 여러 캐시들이 별도의 SRAM 칩으로 구분되었으나, 이른바 무어의 법칙 덕에 프로세서 칩 하나로 통합되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DRAM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SRAM에서 전원이 공급되는 한 데이터 값을 무한정 유지할 수 있다. 반면, DRAM은 저장된 값이 축전기(capacitor)의 전하로 보관된다. 비트 당 6~8개의 트랜지스터를 요구한 SRAM과 달리, DRAM은 비트 당 하나의 트랜지스터가 읽기와 쓰기를 모두 가능케 한다. 그래서 SRAM보다 높은 밀도와 저렴한 가격을 갖는다. DRAM은 축전기에 전하를 보존하기 때문에, 무한정 저장할 수 없다. 주기적으로 새로고침(refresh)해주어야 하며, 그것이 이 RAM의 이름에 &amp;ldquo;Dynamic&amp;rdquo;이 붙은 이유이다. 그 반대의 이유로 SRAM에는 &amp;ldquo;Static&amp;rdquo;이라는 이름이 붙었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로고침하는 방법은 간단하다. 값을 읽고, 그대로 다시 쓰기 하면 된다. 몇 밀리초안에 끝난다. 만약 모튼 비트를 DRAM에서 각각 읽고 다시 써야 한다면, DRAM을 끊임없이 고치느라 막상 데이터에 접근할 시간이 부족했을지도 모른다. 하지만, DRAM은 2단계의 디코딩 구조를 사용하므로, 하나의 행(동일 워드 라인 공유)을 한꺼번에 읽고 쓸수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행 구조는 새로 고침뿐 아니라 성능에도 도움을 준다. 성능 향상을 위해, DRAM은 자주 사용되는 행을 버퍼에 저장해준다. 버퍼는 SRAM처럼 작동하며, 다른 행에 접근하기 전까지 해당 버퍼 내의 임의의 비트에 접근하게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능향상을 위해 클락이 추가된 DRAM을 Synchonronous DRAM 또는 SDRAM이라고 부른다. 이를 통해 메모리와 프로세서의 동기화에 드는 시간을 제거할 수 있다. 또한, 클록은 연속적인 비트들을 버스트(burst) 방식으로 전송한다. 가장 빠른 형태는 DDR SDRAM(Double Data Rate SDRAM)이며, 클록의 상승 엣지와 하강 엣지에서 모두 데이터를 전송한다. 두배의 대역폭이 되는것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Flash Memory&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플래시 메모리는 EEPROM(Electrically Erasable Programmable Read-Only Memory)의 일종이다. 디스크나 DRAM과는 달리, 이 종류의 메모리는 쓰기 작업에 의해 마모(wear out)될 수 있다. 그래서, 메모리에 포함된 컨트롤러가 쓰기 작업을 덜 마모된 블럭으로 분산 시키는 웨어 레벨링(wear leveling) 기술을 이용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Disk Memory&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 디스크는 여러 개의 원판으로 구성되어, 분당 5,400~15,000번 회전한다. 움직이는 암(arm)에 달린 읽기-쓰기 헤드(read-write head)는 표면 바로 위에 위치한다. 디스크 표면은 트랙(track)이라고 불리는 동심원들로 나눠져 있고, 각 트랙은 섹터(sector)로 다시 나뉜다. 섹터의 크기는 512~4096바이트이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.3 The Basics of Caches&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시라는 이름은 프로세서와 메인 메모리 사이의 추가 계층을 표현하기 위한 단어로 주로 사용된다. 또한, 지역성 측면의 이점을 누리기 위한 부가적인 저장소를 표현하는 단어로 사용된다. 이 챕터에서는 프로세서 요청은 하나의 워드, 하나의 블럭 또한 하나의 워드로 구성된 매우 단순한 캐시를 다룬다. 아래의 그림에서 프로세서가 워드 Xn을 요청하기 전까지는 캐시에 없었으나, 미스가 난 후 워드 Xn은 메모리로부터 캐시로 옮겨졌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GvItk/btsODjXFLzq/ZhnqmpI6dzdusXS2LusY3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GvItk/btsODjXFLzq/ZhnqmpI6dzdusXS2LusY3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GvItk/btsODjXFLzq/ZhnqmpI6dzdusXS2LusY3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGvItk%2FbtsODjXFLzq%2FZhnqmpI6dzdusXS2LusY3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;337&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 해결해야 할 질문이 있다. 캐시에 원하는 데이터가 있는지 어떻게 알 수 있는가? 있다면, 어떻게 찾을 수 있는가? 모든 워드의 메모리 주소가 모두 캐시의 어느 한 곳으로 정확이 연결된다면, 우리는 곧장 캐시의 워드를 찾아갈 수 있다. 이런 캐시 구조를 direct-mapped 구조라고 부른다. 거의 모든 direct-mapped 캐시는 아래의 방법으로 블럭을 찾는다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;(Block 주소) mod (cache의 블럭 개수)
= 블럭 주소를 cache의 블럭 개수로 나눈 나머지
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시의 블럭의 개수가 2의 n제곱 형태로 표현되면, 특히 편리하다. 8-블럭 캐시의 경우, mod를 계산하려면 블럭 주소의 맨 아래 3비트만 보면 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로, 하나의 캐시 공간은 여러개의 메모리 주소와 매칭되므로, 캐시 공간 속 지금 들어있는 데이터가 어떤 메모리 주소와 연결되는지 알 필요가 있다. 그래서 캐시에는 태그(tags)가 필요하다. 태그는 지금 캐시에 있는 워드가 어느 메모리 주소와 매칭되는지 식별한다. 앞서 8-블럭 캐시는 블럭 주소의 맨 아래 3비트를 매칭에 이용했는데, 블럭 주소의 나머지 윗쪽 비트들(32비트 주소에서는 앞쪽 29비트)이 태그가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 캐시에 들어있는 블럭이 유효한지 아닌지를 판단하는 식별자도 필요하다. 그것을 유효 비트(valid bit)라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종합하면 캐시 메모리는 아래의 모양처럼 생겼다. 메모리 주소 10110(2)의 데이터를 요청하고 캐시 미스가 발생하여, 인덱스는 110, 태그는 10, 가져온 데이터를 캐시에 저장하고, 유효 비트도 true로 갱신한 모습이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ad2pq/btsOBggK1Ze/B4chQmIq23iZr5s1OkhAcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ad2pq/btsOBggK1Ze/B4chQmIq23iZr5s1OkhAcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ad2pq/btsOBggK1Ze/B4chQmIq23iZr5s1OkhAcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAd2pq%2FbtsOBggK1Ze%2FB4chQmIq23iZr5s1OkhAcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;187&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MIPS에서 워드는 4바이트의 배수로 정렬된다. 그래서, 캐시 인덱스 계산을 위해 나머지(mod) 연산을 할때, 맨 아래의 2비트는 무시하고, 그 다음 비트들부터 따져 계산해도 문제가 없다. 32비트 주소 체계를 이용하는 MIPS 구조는 아래와 같을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6ySe9/btsOBPpSUTx/tDnAh2B4pcssvzsNWAEKZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6ySe9/btsOBPpSUTx/tDnAh2B4pcssvzsNWAEKZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6ySe9/btsOBPpSUTx/tDnAh2B4pcssvzsNWAEKZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6ySe9%2FbtsOBPpSUTx%2FtDnAh2B4pcssvzsNWAEKZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;503&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시블럭은 2^10(=1024)개를 사용했다. 태그는 주소 32비트 중, 맨 아래 2비트와 캐시 인덱스용 10비트를 제외한 20비트의 길이를 갖게 됐다. 여기서는 캐시 블럭 하나당 워드 하나를 저장하고 있어서 데이터는 32비트 안에 저장된다. 캐시 블럭에 여러개의 워드를 저장할 수도 있으며, 이것 역시 2의 m제곱 개의 워드를 저장한다. 그러면, 데이터 영역은 32*m 비트가 필요할 것이고, tag는 10비트보다 m비트 만큼 덜 필요할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 블럭의 크기가 커질수록, 공간적 지역성을 더 활용하기에 미스 비율은 낮아진다. 그러나, 캐시 블럭의 크기가 너무 커져도, 전체 캐시 블럭의 수를 줄여서 블럭끼리의 경쟁을 심화하기 때문에, 오히려 미스 비율이 다시 높아진다. 아래 차트에서 4개의 선은 각각 다른 캐시 크기를 의미하고, x 축은 캐시 블럭 크기를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beQxR9/btsOBCYcmAZ/8MJS2TPDeXH3jKu9pWaEG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beQxR9/btsOBCYcmAZ/8MJS2TPDeXH3jKu9pWaEG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beQxR9/btsOBCYcmAZ/8MJS2TPDeXH3jKu9pWaEG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeQxR9%2FbtsOBCYcmAZ%2F8MJS2TPDeXH3jKu9pWaEG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;257&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블럭 사이즈를 늘리는 것은 미스 발생 시 패널티를 늘리는 데에도 일조한다. 미스 패널티는 메모리로부터 데이터를 가져오고(fetch), 그것을 캐시에 저장(load)하는 시간이다. 메모리의 구조 자체를 바꾸지 않는 이상, 당연히 블록 사이즈가 커질 수록 캐시에 데이터를 옮겨담는 시간도 늘어나므로, 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chapter4에서 배운 파이프라인을 떠올려보자. 데이터 파이프라인에서 배웠던 메모리는 우리가 지금껏 다룬 캐시로 대체된다. 인스트럭션을 로딩할 때 캐시 미스를 핸들링 하는법은 단순하다. 파이프라인에 정지(stall)를 걸고, 캐시 하위 계층인 메모리에서 데이터를 읽어온 후, 캐시에 기록하고 다시 EX 과정으로 보낸다. 파이프라인은 이제 캐시에서 인스트럭션을 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 쓰기는 좀 더 복잡하다. Store 인스트럭션을 실행할 때, 캐시에만 쓰기 작업을 해준다면, 메모리와 캐시의 값이 일관되지 않는 문제가 발생한다. 가장 단순한 방법은 캐시와 메모리에 항상 같이 쓰기 작업을 해주는 것이다. 이 방법을 write-through라고 한다. 그러나, store 인스트럭션을 실행할 때 마다 메모리에도 쓰기 작업을 하는 것은 비효율적이다. 그래서 쓰기 버퍼(write buffer)를 도입한다. 메모리에 값을 쓰기 전에 쓰기 버퍼에 값을 담아두었다가, 차차 메모리에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 쓰기 접근으로 write-back 방법이 있다. 이 방법에서는 새로운 데이터는 캐시에만 기록하되, 수정된 블럭임을 dirty bit로 기억한다. 이 수정된 블럭이 다시 다른 블럭으로 대체되는 때에, 메모리에 기록된다. 성능 면에서 뛰어나지만, 구현은 복잡하다. 또, 한 사이클만에 처리되는 write-through에 비해 write-back은 처리하는데 두 사이클이 걸린다.(히트 여부 확인 + 실제로 데이터 저장 2사이클)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시미스가 난 경우 write-back의 작동방식이 조금 헷갈린다. 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;캐시에서 읽어야할 데이터가 없는 것을 확인(미스)&lt;/li&gt;
&lt;li&gt;메모리에서 캐시로 필요한 값을 로드, 이 때 캐시 블럭에 이미 다른 값이 차있을 수 있음.&lt;/li&gt;
&lt;li&gt;만약 다른 값이 차있다면, dirty 여부를 확인하여, dirty 하면 메모리에 다시 이 값을 갱신해야함. 이 때도, 메모리에 즉시 갱신을 하는 것이 아니라 write-back buffer를 둬서 작업을 지연시킬 수 있음.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 미스 상황에서도 두가지 전략이 있다. Write-allocate 전략은메모리에 쓰기 된 데이터를 캐시에도 가져와서, 적절한 곳에 덮어쓴다. No-write-allocate 전략은 메모리에만 데이터를 쓰고, 캐시는 갱신하지 않는다. 이 전략은 어떤 프로그램이 블럭 전체를 0으로 덮는 상황 등 블럭들을 한꺼번에 업데이트 하려는 상황에 유효하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.4 Measuring and Improving Cache Performances&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 챕터에서는 캐시 성능을 측정하고 분석하는 방법을 다룬다. 또, 캐시 성능을 개선하는 두 가지 방법을 다룬다. 첫번째 방법은, 두 가지 메모리 블록이 하나의 캐시 블록을 두고 경합하는 상황을 줄이는 법을 고민한다. 두번째 방법은, 캐시를 다시 여러 계층으로 나누는, 이른바 멀티레벨 캐싱(multilevel caching)을 통해 미스 패널티를 줄이는 법을 고민한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 실행 시간은 아래와 같이 계산된다고 앞선 챕터에서 다뤘다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;(CPU 실행시간) = IC(인스트럭션 수) X CPI(인스트럭션 당 요구 사이클) X CC(사이클당 실행시간)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스트럭션 당 요구 사이클은 memory-stall 사이클을 고려하여 이렇게 표현된다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;(CPU 실행시간) = IC X CC X (이상적일때의 CPI + 캐시 미스로 인한 stall 사이클)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 미스로 인한 stall 사이클은 read-stall 사이클과 write-stall 사이클로 나뉜다. 이 두 사이클은 또 각각 아래와 같이 표현된다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;(Read-stall 사이클) = (프로그램당 읽기 횟수) X (읽기 미스 비율) X (읽기 미스 패널티)
										
(Write-stall 사이클) = (프로그램당 쓰기 횟수) X (쓰기 미스 비율) X (쓰기 미스 패널티) + 쓰기 버퍼 stall&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 읽기와 쓰기의 미스패널티가 동일하고, 쓰기 버퍼 stall은 무시할 정도라고 현실적인 가정을 보태어, 아래의 결론을 얻을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;(memory-stall 사이클) = (access/program) x 미스 비율 X 미스패널티
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, 히트 타임은 캐시 성능에 무관하다고 가능했으나, 사실은 그렇지 않다. 예를 들어, 캐시 사이즈가 커진다면 히트 타임이 늘어날 것이고, 프로세서의 사이클 시간을 늘릴 수 있다.히트 타임과 미스 타임 모두를 고려하기 위해 평균 메모리 접근 시간(AMAT, average memory access time)이라는 개념을 이용한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;AMAT = 히트 타임 + 미스 비율 X 미스 패널티
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 지금까지 direct-mapped 구조 만을 가정했다. 실은 이것은 하나의 메모리 주소가 하나의 캐시 블럭에만 들어가는 극단적인 경우이다. 메모리주소가 캐시 블럭의 모든 곳에 들어갈 수 있는 구조를 fully associative라고 한다. 그리고, 메모리 주소가 n개의 캐시 블럭에 속할 수 있는 구조를 set associative, 특히 n-way set-associative라고 부른다. 이제 메모리 주소가 캐시 블럭에 매핑되는 식을 아래처럼 바꿀수 있다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;(Block 주소) mod (cache의 set 개수)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;associativity의 차수를 늘리는 것은 미스 비율을 낮출 수 있지만, 히트 타임을 낮춘다. 아래의 그림을 생각하면 된다. Direct mapped 에서는 히트 여부 판단을 위해 하나의 태그만 확인 하면 되지만, 2-way set associative에서는 2개의 태그를, fully associative에서는 8개 모두의 태그를 확인해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjWYDj/btsODkWAoup/dsW6gAkhGGuk7Wboa5q4i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjWYDj/btsODkWAoup/dsW6gAkhGGuk7Wboa5q4i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjWYDj/btsODkWAoup/dsW6gAkhGGuk7Wboa5q4i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjWYDj%2FbtsODkWAoup%2FdsW6gAkhGGuk7Wboa5q4i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;257&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Set associative에서 tag들을 비교하는 것은 병렬적으로 일어난다. N-way set associative라면 n개의 태그 비교가 병렬적으로 일어나며, n-to-1 Mux에 의해 데이터가 가져와진다. 아래는 4-way set의 경우를 그림으로 표현한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;996&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYIHyX/btsODLe2OtV/Ypp6oUtCKdaahbUYNpeC01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYIHyX/btsODLe2OtV/Ypp6oUtCKdaahbUYNpeC01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYIHyX/btsODLe2OtV/Ypp6oUtCKdaahbUYNpeC01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYIHyX%2FbtsODLe2OtV%2FYpp6oUtCKdaahbUYNpeC01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;425&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;996&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Direct-mapped 에서는 고민할 필요 없었지만, set-associative에서는 미스가 발생하여 데이터를 캐시에 덮어쓸때, 어떤 블럭을 대체될 블럭으로 결정할지도 고민할 수 있다. Direct-mapped에서는 후보 블럭이 1개 뿐이였고, n-way set-associative에서는 n개의 블럭이 후보가 된다. 가장 많이 쓰이는 방식은 LRU(least recently used)이다. n개의 블럭 중 가장 쓰인지 오래된 블럭을 대체한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대의 컴퓨터는 1차 캐시에서 미스가 나면 2차 캐시에서 데이터를 탐색하는 멀티레벨 캐싱을 지원한다. L1, L2 캐시 라고 표현한다. L1 캐시는 히트 타임을 최소화 하는데에 집중하는 반면, L2 캐시는 미스 비율을 줄이는 데에 집중한다. L1 캐시의 미스 패널티는 L2 캐시 덕에 줄어드는 효과를 얻는다. 그래서 L1 캐시는 미스 비율이 높아지더라도, 블럭 사이즈는 작게, associativity는 적게 설정하여 히트 타임을 줄인다. 반면 L2 캐시는 큰 블럭 사이즈와 큰 associativity를 갖는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;s&gt;5.5 Dependable Memory Hierarchy&lt;/s&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;s&gt;5.6 Virtual Machines&lt;/s&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.7 Virtual Memory&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 섹션에서, 캐시가 어떻게 최근에 사용된 프로그램의 코드와 데이터에 대한 접근시간을 개선하는지 알아봤다. 비슷하게, 메인 메모리가 2차 저장소(보통 자기 디스크)의 캐시 역할도 해줄 수 있다. 이 기술을 가상 메모리(virtual memory)라고 부른다. 가상메모리는 두 가지 목적이 있다. 먼저, 서로 다른 프로그램들 간의 안전하고 효율적인 메모리 공유를 담당한다. 둘째로, 작고 제한된 메인 메모리에서 불필요한 프로그래밍 조각들을 제거한다.&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vSJat/btsOCOXGAKa/OlARrvHUtOJYQF6IO6fmG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vSJat/btsOCOXGAKa/OlARrvHUtOJYQF6IO6fmG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vSJat/btsOCOXGAKa/OlARrvHUtOJYQF6IO6fmG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvSJat%2FbtsOCOXGAKa%2FOlARrvHUtOJYQF6IO6fmG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1420&quot; height=&quot;450&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상메모리와 캐시는 개념은 동일하지만, 서로 다른 역사적 배경으로 인해 용어가 다르다. 가상메모리에서의 캐시블럭은 페이지(page), 캐시 미스는 페이지 폴트(page fault)라고 한다. 가상 메모리에서는 가상 주소가 생성되어, 메인 메모리의 물리주소와 매핑된다. 이 과정을 주소 매핑(address mapping), 혹은 주소 변환(address translation)이라고 한다. 프로그램의 실행을 위해 로드하는 과정에서, 프로그램이 사용하는 가상주소를 서로다른 물리주소에 매핑하는 것은 재배치(relocation)라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 주소는 페이지 넘버(page number)와 페이지 오프셋(page offset)으로 나뉜다. 가상 페이지 넘버는 물리 페이지 넘버보다 비트수가 많아서, 가상 페이지 수가 물리 페이지 수보다 많은 환상을 제공할 수 있다. 페이지 오프셋은 페이지의 크기를 결정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqj2OF/btsOBYs8jzd/EH05t1jjweJbrKyRC84Vn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqj2OF/btsOBYs8jzd/EH05t1jjweJbrKyRC84Vn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqj2OF/btsOBYs8jzd/EH05t1jjweJbrKyRC84Vn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqj2OF%2FbtsOBYs8jzd%2FEH05t1jjweJbrKyRC84Vn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;444&quot; height=&quot;316&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크로의 접근 시간은 메인 메모리로의 접근 시간보다 10만 배 오래 걸린다. 이런 엄청난 미스패널티는 아래의 디자인 결정을 하게 만든다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이지는 너무 긴 접근 시간을 상쇄할 수 있도록, 충분히 커야함. (4KiB~16KiB)&lt;/li&gt;
&lt;li&gt;페이지 폴트를 줄여야 함. 그래서 페이지들을 메모리에 fully associative하게 배치함.&lt;/li&gt;
&lt;li&gt;너무 긴 접근 시간을 겪을 바에, 페이지를 배치하는 방법을 알고리즘을 돌려서 계산 비용을 소모하더라도 페이지 폴트율을 줄임.&lt;/li&gt;
&lt;li&gt;쓰기작업이 너무 오래걸리기 때문에 write-through는 불가능함. write-back만 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 크기 블록을 사용하는 페이징(paging)에 대해서 지금 까지 다루었는데, 가변 크기 블록을 사용하는 세그멘테이션(segmentaion)이라는 방식도 있다. 세그멘테이션은 페이징과 달리, 가상 주소 관련 맥락을 사용자 프로그램에도 노출시켜야한다는 물편이 있다. 많은 아키텍쳐들이 주소 공간을 영역을 나누어 사용하는 세그멘트(segment)와는 다른 매커니즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fully associative 방식은 캐시 히트 여부를 판단할 때, 검색해야하는 엔트리가 너무 많다는 점이다. 그래서, 메모리를 인덱싱한 테이블을 참고하여, 원하는 페이지를 찾아가도록 하는데, 이 구조를 페이지 테이블(page table)이라고 한다. 페이지 테이블은 페이지 번호로 인덱싱되어, 해당하는 물리 주소를 매핑한다. 이 페이지 테이블이 어디에 있는지를 또 알기 위하여, 페이지 테이블의 시작 위치를 가리키는 레지스터를 페이지 테이블 레지스터(page table register)라고 부른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWhyj8/btsOCL7TgWm/oManwcLEC9btuJF6jEHKik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWhyj8/btsOCL7TgWm/oManwcLEC9btuJF6jEHKik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWhyj8/btsOCL7TgWm/oManwcLEC9btuJF6jEHKik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWhyj8%2FbtsOCL7TgWm%2FoManwcLEC9btuJF6jEHKik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;490&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 페이지의 유효비트가 꺼져있으면, 페이지 폴트가 발생한다. 그러면, 4장에서 다룬 예외(exception) 처리 매커니즘에 의해, OS가 제어권을 얻는다. OS는 페이지를 디스크로부터 가져와서, 메모리에 배치해야한다. 그런데, 가상 주소 만으로는 디스크에서 페이지가 어디 있는지 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 있는 페이지는 언제 교체될지 알 수 없다. 그래서, OS는 프로세스를 생성할 때, 해당 프로세스의 모든 페이지에 대해 디스크에 공간을 만든다. 이 공간을 스왑 공간(swap space)이라고 한다. 각 가상 페이지가 디스크의 어디에 있는지를 기록하는 데이터 구조도 만든다. 좀 전, 페이지 테이블은 페이지 번호를 인덱스로, 해당하는 물리 주소를 저장한다고 했는데, 메모리 물리 주소를 저장한다고 했는데, 디스크의 주소 또한 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 폴트가 발생했을때, 메인 메모리의 어떤 페이지를 교체할지 선택해야 한다. 이 때, LRU(Least Recently Used)방식을 주로 사용하며, 교체된 페이지는 디스크의 스왑공간에 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 테이블 자체도 메모리에 저장되므로, 저장공간을 줄이는 것이 좋다. 32bit 가상주소, 4KiB(2^12비트)의 페이지의 아키텍쳐에서, 2^20 개의 페이지 번호가 등장한다. 페이지 테이블 엔트리 하나당 4 바이트라면 페이지 테이블은 2^24비트, 4MiB 나 차지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장공간을 줄이기 위해 첫번째로, 리미트 레지스터(limit register)를 사용한다. 리미트 레지스터는 특정 프로세스의 페이지 테이블 크기를 제한하여, 실제로 프로세스가 사용하는 페이지의 수 만큼만 페이지 테이블을 만들도록 제한한다. 보통 프로그래밍 언어는 스택/힙 양방향으로 데이터를 저장하므로, 두 개의 페이지 테이블과 두 개의 리미트 레지스터가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 방법은 인버티드 페이지 테이블(inverted page table)을 이용하는 것이다. 가상 페이지 수는 너무 많이 때문에 페이지 테이블의 엔트리 수를 물리 페이지 수에 맞추는 것이다. 가상 페이지 번호를 인덱스로 물리 주소를 저장한 페이지 테이블과 달리, 물리 주소에 저장된 페이지를 인덱스로 가상 페이지 번호를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 페이지 테이블 자체를 다단계(multi-level)로 구성할 수 있다. 페이지 테이블 중에서도 쪼개서, 필요한 테이블만 메모리에 올려두는 것이다. 그 말인 즉슨, 페이지 테이블 자체도 페이징한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 테이블이 메모리에 저장되어 있기 때문에, 프로그램의 메모리 접근은 페이지 테이블로부터 물리 주소를 얻는데에 한 번, 실제로 그 주소에서 데이터를 얻는데 한 번, 총 두번의 접근이 필요하다. 그래서 페이지 테이블의 내용을 다시 캐싱한다. 이 캐시를 TLB(translation-lookaside buffer)라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOXooL/btsOBZFBwr8/dKriIFDPBzVdvOKNG2aUnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOXooL/btsOBZFBwr8/dKriIFDPBzVdvOKNG2aUnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOXooL/btsOBZFBwr8/dKriIFDPBzVdvOKNG2aUnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOXooL%2FbtsOBZFBwr8%2FdKriIFDPBzVdvOKNG2aUnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;397&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 참조에서 페이지 테이블보다 TLB에 먼저 접근하게 된다. 읽기 작업에서 TLB 히트가 발생하면 참조(ref) 비트를 켜고, 쓰기 작업에서 히트가 발생하면 더티(dirty) 비트도 켠다. 만약 TLB 미스가 발생하면 단순 TLB 미스인지, 페이지 폴트인지 구분해야 한다. 단순 TLB 미스라면 페이지 테이블을 참조하여, TLB를 갱신하고, 페이지 폴트라면 OS의 예외처리에 맡긴다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4MsDQ/btsOCQOF6Zw/DbBxLPU9xv3e6dJkqClwJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4MsDQ/btsOCQOF6Zw/DbBxLPU9xv3e6dJkqClwJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4MsDQ/btsOCQOF6Zw/DbBxLPU9xv3e6dJkqClwJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4MsDQ%2FbtsOCQOF6Zw%2FDbBxLPU9xv3e6dJkqClwJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;177&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/37</guid>
      <comments>https://junbyeol.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 16 Jun 2025 15:31:11 +0900</pubDate>
    </item>
    <item>
      <title>[전산기조직] 프로세서(The Processor)</title>
      <link>https://junbyeol.tistory.com/36</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.1 Introduction&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 챕터는 프로세스의 구현방법에 대해서 다룬다. 아주 추상화/단순화된 관점에서부터 datapath를 만들고 간단한 버전의 MIPS를 구현하기에 이른다. 또한, 현실 세계에서 x86같은 복잡한 ISA 구현에 필수적인, 병렬적인(pipelined) 구현 방법 또한 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 일부 정수 인스트럭션이나, 부동 소수점 인스트럭션 등을 제외하고 아래의 3종류로 분류되는, 간단한 MIPS 인스트럭션들만을 구현할 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 참조 인스터럭션: lw(load word), sw(store word)&lt;/li&gt;
&lt;li&gt;산수연산 인스트럭션: add, sub, AND, OR, slt&lt;/li&gt;
&lt;li&gt;브랜치 인스트럭션: beq(branch equal), j(jump)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;큰 그림&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 인스터럭션은 아래의 두 과정을 거친다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Program Counter(PC)를 메모리에 전달하여, 갖고 있는 코드를 꺼내온다.&lt;/li&gt;
&lt;li&gt;인스트럭션의 필드를 이용하여, 해당하는 1개 또는 두개의 레지스터를 읽는다. lw 연산자는 하나의 레지스터만 읽지만, 대다수의 인스트럭션은 두개의 레지스터를 읽는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 과정은, 인스트럭션의 종류에 따라 달라진다. 다행히도 MIPS 인스트럭션의 설계는 단순하고(simplicity) 규칙적(regularity)이기에 대다수의 과정은 비슷하게 진행된다. jump를 제외한 모든 인스트럭션은 레지스터를 읽은 후에 ALU(arithmetic-logical unit)로 넘어간다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 참조 인스트럭션은 ALU로 주소를 계산한다. 이후, 메모리에 접근하여 데이터를 읽거나 쓴다. load 인스터럭션의 경우, 읽은 데이터를 레지스터에 다시 저장한다.&lt;/li&gt;
&lt;li&gt;산수연산 인스트럭션은 연산 수행에 ALU를 사용한다. 수행된 결과는 다시 레지스터에 저장된다.&lt;/li&gt;
&lt;li&gt;브랜치 인스트럭션은 ALU로 비교연산을 수행한 후, 다음 주소를 PC로 넘긴다. 특별한 주소로 이동하지 않는다면, 평소에 PC는 4가 더해져 다음 인스터럭션으로 이동해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj8CN7/btsOCLNznuT/jynGwBI5ROoq1DzIw7pRv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj8CN7/btsOCLNznuT/jynGwBI5ROoq1DzIw7pRv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj8CN7/btsOCLNznuT/jynGwBI5ROoq1DzIw7pRv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj8CN7%2FbtsOCLNznuT%2FjynGwBI5ROoq1DzIw7pRv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;308&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 설명을 토대로 MIPS 구현체의 대략적인 모습이다. 눈여겨 볼점은 두 곳에서 나온 데이터가 한 곳으로 몰리는 곳이 있다는 것이다. 그림에서 실선의 한 중간에 화살표가 꽂혀있는 곳들이다. 이 곳에는 어떤 소스에서 나온 데이터를 이용할지 결정하는 multiplexor라는 장치가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 어느 장치들은 인스트럭션에 따라 다른 연산을 수행해야 하므로, 이것을 통제할 컨트롤 유닛(control unit)이 필요하다. 메모리가 이번에 데이터를 읽어야할지 써야할지, ALU가 어떤 연산을 수행해야 할지 등을 입력으로 전달해야 한다. 이 두가지 포인트를 적용하면 그림은 아래와 같이 복잡해진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LkZNO/btsOBQPR78A/iLvWCMTN5Z13G1zeKJATgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LkZNO/btsOBQPR78A/iLvWCMTN5Z13G1zeKJATgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LkZNO/btsOBQPR78A/iLvWCMTN5Z13G1zeKJATgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLkZNO%2FbtsOBQPR78A%2FiLvWCMTN5Z13G1zeKJATgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;591&quot; height=&quot;439&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.2 Logic Design Conventions&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appendix C를 요약한 챕터이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.3 Building a Datapath&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datapath는 우리의 프로세스에 필요한 구성요소들을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rWCdK/btsODpDhn3L/T0At7GaHq3exFJaDWmlru1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rWCdK/btsODpDhn3L/T0At7GaHq3exFJaDWmlru1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rWCdK/btsODpDhn3L/T0At7GaHq3exFJaDWmlru1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrWCdK%2FbtsODpDhn3L%2FT0At7GaHq3exFJaDWmlru1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;232&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fetching Instruction&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC는 매 클락마다 업데이트된다. 클락 신호 외에는 어떤 컨트롤 신호도 필요없다. Memory는 쓰기 작업은 할 필요 없이 읽기 작업이면 충분하다. 그래서, combinational 하게 동작한다. 별도의 읽기 신호가 필요없다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Executing R-format instruction&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R-format 인스트럭션은 3개의 피연산자를 가진다. 두 개는 레지스터로부터 읽고, 하나는 레지스터에 쓴다. 데이터를 읽을때는 읽을 레지스터 번호 두 개를 입력으로, 읽은 데이터 두개를 출력으로 반환한다. 데이터를 쓸 때는, 쓸 데이터와 쓸 레지스터 번호가 입력으로 있으면 충분하다. 그래서 레지스터 파일은 4개의 입력과 2개의 출력을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALU는 두 개의 32비트 입력을 받아, 32비트 출력을 반환한다. 만약, 출력값이 0이라면 1비트짜리 추가 시그널을 Zero에 반환한다. 또, ALU는 다양한 연산을 수행하기에, 4비트짜리 컨트롤 라인도 입력으로 받는다. 추후에 다시 다룬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bheVHS/btsOBZMlYD4/E1j2ioH7kYsU5k2IwhxEi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bheVHS/btsOBZMlYD4/E1j2ioH7kYsU5k2IwhxEi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bheVHS/btsOBZMlYD4/E1j2ioH7kYsU5k2IwhxEi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbheVHS%2FbtsOBZMlYD4%2FE1j2ioH7kYsU5k2IwhxEi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;220&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Executing Load and Store instruction&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store 작업에서는 저장할 데이터를 레지스터로부터 읽어와야하고, Load 작업에서는 데이터를 레지스터에 써야한다. 그리고, 인스트럭션의 인자로 넘어온 16비트의 부호가 있는 offset-value로부터 메모리의 실제 주소를 연산해야한다. 이 과정에서 16비트를 32비트로 부호를 살려 확장하는 sign-extension을 수행할 회로와 ALU의 도움이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T9Mvy/btsOD1Ppqvi/KVSklH6UZ8KyWBtbmzKc3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T9Mvy/btsOD1Ppqvi/KVSklH6UZ8KyWBtbmzKc3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T9Mvy/btsOD1Ppqvi/KVSklH6UZ8KyWBtbmzKc3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT9Mvy%2FbtsOD1Ppqvi%2FKVSklH6UZ8KyWBtbmzKc3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;243&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWPc8V/btsOCMTmzxy/ZAP2F88PhfvoJr8jiTCSlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWPc8V/btsOCMTmzxy/ZAP2F88PhfvoJr8jiTCSlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWPc8V/btsOCMTmzxy/ZAP2F88PhfvoJr8jiTCSlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWPc8V%2FbtsOCMTmzxy%2FZAP2F88PhfvoJr8jiTCSlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;389&quot; height=&quot;294&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;beq instruction&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 16비트 offset-value로부터 branch target address를 연산해야한다. 이 연산 과정은 챕터2에서 소개했지만, 여기서 다시 두 가지 중요한 디테일을 상기해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Offset과 더하는 값은 현재의 PC가 아니라 PC+4와 같은 다음에 실행될 인스트럭션의 주소값이다. 이미 우리 구현상 PC+4를 fetch instruction 단계에서 가져오는 것이 간편하기 때문에 그렇다.&lt;/li&gt;
&lt;li&gt;Offset은 byte가 아닌 word 단위로 들어온다. 즉, 4배 혹은 shift left 2 연산을 해주어야 우리가 찾는 byte address를 구할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 beq 안의 조건이 일치돼서, 다음 PC가 계산된 branch target address로 교체되는 것을, &amp;ldquo;branch is taken&amp;rdquo;이라고 표현한다. 아까 ALU에서 &amp;ldquo;Zero output&amp;rdquo;을 만들어 둔 것이 이때 사용된다. 두 값이 같은지 여부를 ALU가 출력할 때, 두 값의 차를 구하여 그 값이 0 이면, 즉, zero 아웃풋이 1로 출력되면 같다고 판단하기 떄문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jump instruction&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;28비트의 오프셋에 4배 혹은 shift left 2 연산을 하면 그만이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 언급한 datapath들을 모두 합하면 다음과 같다. 이 때, 두 개의 소스로부터 서로 다른 데이터가 온 상황에서 원하는 데이터를 선택하기 위해 multiplexor(Mux)를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXKE55/btsOD97w8Wt/KnhubQMBShFicDZ4KRNSZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXKE55/btsOD97w8Wt/KnhubQMBShFicDZ4KRNSZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXKE55/btsOD97w8Wt/KnhubQMBShFicDZ4KRNSZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXKE55%2FbtsOD97w8Wt%2FKnhubQMBShFicDZ4KRNSZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;464&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.4 A Simple Implementation Scheme&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 메인 컨트롤 유닛의 구현을 따라가보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntcEo/btsOBsatb7m/nZakUsjkFaRF0kv6Z7DF00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntcEo/btsOBsatb7m/nZakUsjkFaRF0kv6Z7DF00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntcEo/btsOBsatb7m/nZakUsjkFaRF0kv6Z7DF00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FntcEo%2FbtsOBsatb7m%2FnZakUsjkFaRF0kv6Z7DF00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;322&quot; height=&quot;164&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;The ALU Control&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALU가 어떤 연산을 할지 입력 받는 4비트 컨트롤 인풋이 있었다. 비트 값에 따라 5개의 연산 중 하나로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 4비트 컨트롤 인풋은 ALUOp라고 하는 2비트의 필드로부터 변환된다. ALUOp가 00인 LW, SW 연산에서는 ALU가 덧셈 연산을 해야하며, 01인 Beq 연산에서는 뺄셈 연산을 해야한다. 10인 R-type의 경우, 또 다른 필드인 Funct 필드를 추가로 확인해야한다. 우리는 ALU와 ALUOp에 어떤 비트가 들어왔을 때, 어떤 4비트 아웃풋을 만들어야 하는지 아래와 같은 truth table을 만들 수 있다. 우리의 메인 컨트롤 유닛이 구현해야할 명세다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GSwe4/btsODqPKR5D/YN5vXql3UKeTKe1sMrOJdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GSwe4/btsODqPKR5D/YN5vXql3UKeTKe1sMrOJdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GSwe4/btsODqPKR5D/YN5vXql3UKeTKe1sMrOJdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGSwe4%2FbtsODqPKR5D%2FYN5vXql3UKeTKe1sMrOJdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;217&quot; data-origin-width=&quot;1222&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;instruction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OAr3Q/btsODV9w2Ep/MkYXV1STZOxxR8QhMnEmUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OAr3Q/btsODV9w2Ep/MkYXV1STZOxxR8QhMnEmUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OAr3Q/btsODV9w2Ep/MkYXV1STZOxxR8QhMnEmUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOAr3Q%2FbtsODV9w2Ep%2FMkYXV1STZOxxR8QhMnEmUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1234&quot; height=&quot;482&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 3가지 종류의 인스트럭션을 보자. rs, rt, rd는 레지스터의 번호이다. rs는 항상 읽기에만 사용된다. rt는 대부분 읽기이지만, load 연산에 대해서는 쓰기로 사용된다. rd는 항상 쓰기에만 사용된다. 어찌됐든, 쓰기 레지스터는 인스트럭션 당 하나다. 그리고, address를 나타내는 16비트 오프셋은 sign-extend 연산을 필요로 한다. 종합하면, 아래처럼 인스트럭션은 분리되어 다음 회로에 전달된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBiJmT/btsOD9T0bX7/LiGG8k6KidmjlDHq31P781/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBiJmT/btsOD9T0bX7/LiGG8k6KidmjlDHq31P781/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBiJmT/btsOD9T0bX7/LiGG8k6KidmjlDHq31P781/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBiJmT%2FbtsOD9T0bX7%2FLiGG8k6KidmjlDHq31P781%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;321&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALUOp는 ALU control unit이 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXunqJ/btsODLF6wcs/uKyx6rFgywBvLiixn4vTYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXunqJ/btsODLF6wcs/uKyx6rFgywBvLiixn4vTYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXunqJ/btsODLF6wcs/uKyx6rFgywBvLiixn4vTYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXunqJ%2FbtsODLF6wcs%2FuKyx6rFgywBvLiixn4vTYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;477&quot; height=&quot;510&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Control unit은 두가지 방법으로 구현된다. 첫번째 방법은 &amp;ldquo;microcoded control&amp;rdquo;이라고 하여, 컨트롤 유닛이 안에 작은 메모리를 갖고 연산을 하여 컨트롤 신호를 생성한다. 과거 CISC 프로세서들이 사용하던 방법으로 유연하지만 느리다. 반면 두번째 방법은 &amp;ldquo;hardwired control&amp;rdquo;로, combinational logic으로 신호를 생성한다. 빠르지만, 수정할수 없다. RISC 프로세서들이 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.5 An Overview of Pipelining&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 우리는 single-cycle 구현 방식을 사용했다. 모든 인스트럭션은 같은 길이의 클락 사이클 안에서 수행된다. 그러다 보니, 클락 사이클 주기는 가장 오래 걸리는 인스트럭션의 critical path에 의존하게 된다. 그래서 CPI(Cycle Per Instruction)가 1이여도, CC(Clock Cycle)이 길어져 전체적인 퍼포먼스가 떨어지게 된다. 그래서 나온 아이디어가 파이프라이닝(pipelining)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 인스트럭션의 실행과정을 다섯 단계로 나눈다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;IFetch: Fetch Instruction from Memory&lt;/li&gt;
&lt;li&gt;Dec: Read Registers from the Decoded Instruction&lt;/li&gt;
&lt;li&gt;Exec: Execute the Operation.&lt;/li&gt;
&lt;li&gt;Mem: Access an Operand in Data Memory&lt;/li&gt;
&lt;li&gt;WB: Write the Result into a Register&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E77me/btsODVIIerZ/lz2TngROAhPLxguf8J1rak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E77me/btsODVIIerZ/lz2TngROAhPLxguf8J1rak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E77me/btsODVIIerZ/lz2TngROAhPLxguf8J1rak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE77me%2FbtsODVIIerZ%2Flz2TngROAhPLxguf8J1rak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;516&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨래를 하는 과정을 생각해보자. 빨래는 4단계의 과정으로 수행된다. 1. 세탁하기. 2. 건조하기. 3. 개기. 4. 수납하기. 위 피규어의 첫번째 차트처럼 4번 수납하기 과정 후에야 1번 세탁하기 과정을 진행하는 것은 단순하다. 하지만, 네 과정이 일어나는 리소스가 세탁기, 건조기, 마룻바닥, 옷장으로 다 다르기에 네 과정을 동시에 수행해도 문제가 없다. 즉 피규어의 두번째 차트처럼 동시에 진행할 수 있다. 위 차트보다 아래의 파이프라이닝된 빨래 스케쥴링은 4배 더 빠르게 빨래들을 처리한다. 정확히는, 시작과 끝에 놀고 있는 리소스들 때문에 4배가 안되지만, 해야하는 빨래 양이 많아질수록, 이 효율은 4배에 가까워질것이다. 마찬가지로, 우리는 다섯 단계로 구성된 인스트럭션을 파이프라이닝할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byb7TC/btsODMSxbQx/BZ8avkhzXNsrNbfHdcRILk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byb7TC/btsODMSxbQx/BZ8avkhzXNsrNbfHdcRILk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byb7TC/btsODMSxbQx/BZ8avkhzXNsrNbfHdcRILk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyb7TC%2FbtsODMSxbQx%2FBZ8avkhzXNsrNbfHdcRILk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;368&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;908&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨래가 파이프라이닝이 가능했던 이유는 빨래의 4가지 스테이지가 일어나는 장소가 다르기 때문이였다. 하지만, 우리의 인스트럭션들은 안타깝게도 그렇지 않다. 아무 생각 없이 매번 다음, 다음, 다음 인스트럭션을 수행하면 문제가 생기는 상황이 생기는데 이를 해저드(hazard, 충돌)이라고 부른다. 3가지 종류가 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구조적 해저드(Structural Hazard)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 세탁기와 건조기가 별개의 기기였지만, 만약 두 과정이 같은 기기에서 일어난다면 우리는 파이프라이닝을 할 수 없었을 것이다. 안타깝게도 우리의 인스트럭션 실행과정에서 이런 일이 일어난다. 첫번째로, 첫단계인 IF(Instruction Fetch)와 Mem(Memory Access)과정은 모두 메모리 참조를 요구한다. 또, 2번째 과정인 ID(Instruction Decode)와 WB(Write Back)과정도 동시에 레지스터 파일로의 접근을 요구한다. 서로 다른 인스트럭션들이 동시에 같은 리소스를 요구하려 해서 발생하는 해저드를 구조적 해저드라고 부른다. 차차 이 문제를 해결해보겠다. 미리 스포일러를 하자면, IF와 MEM은 두개의 개념적 메모리로 분리하고, ID와 WB는 읽기/쓰기 연산을 시간적으로 분리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 해저드(Data Hazard)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 해저드는 후속 인스트럭션이 앞선 인스트럭션을 기다렸다가 실행될수 있을때 발생한다. 아래의 인스트럭션을 보면 아래의 sub 인스트럭션 과정 수행에 add 인스트럭션의 결과인 $s0을 요구한다. 즉, sub 인스트럭션은 add 인스트럭션이 끝날때까지 기다려야 한다. sub 인스트럭션의 레지스터의 값을 읽기하는 ID 과정을 add 인스트럭션이 레지스터에 값을 쓰기하는 WB 과정이 끝날때까지 기다리려면 3번의 사이클을 기다리면(stall) 된다. 하지만, 이런 사례는 현실에서 굉장히 자주 발생하는 사례이다. 이럴때마다 매번 사이클들을 기다리기만 하며 보내는 것은 만족스럽지 않다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7bSQu/btsODjwvZcE/5elGbshaKZLbUCLKzKs60K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7bSQu/btsODjwvZcE/5elGbshaKZLbUCLKzKs60K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7bSQu/btsODjwvZcE/5elGbshaKZLbUCLKzKs60K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7bSQu%2FbtsODjwvZcE%2F5elGbshaKZLbUCLKzKs60K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;448&quot; height=&quot;138&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내놓을 수 있는 다음 해결책은 포워딩(forwarding, or bypassing)이라고 부른다. add가 WB 과정까지 수행하고 나서야, sub의 ID가 실행되는 것은 너무 비효율적이다. add의 EX과정이 ALU에서 끝나자마자(덧셈 연산이 끝나자마자) 그 값을 sub 인스트럭션에서 넘겨 받는다면 좀 더 빠를 것이다. sub 인스트럭션이 일단 ID를 수행하고, 동시에 add는 EX 과정(덧셈)을 수행한다. 그리고, sub의 EX를 시작하기 전, 레지스터에서 읽어온 $s0 값 대신, add의 덧셈 결과를 덮어써서 연산에 이용한다면 파이프라이닝에 전혀 지연이 없다. 이것을 포워딩이라고 부른다. 아래의 피규어로 표현했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blHkks/btsODE1v7Ds/l7ML6Ielm9t6SKW96leIB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blHkks/btsODE1v7Ds/l7ML6Ielm9t6SKW96leIB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blHkks/btsODE1v7Ds/l7ML6Ielm9t6SKW96leIB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblHkks%2FbtsODE1v7Ds%2Fl7ML6Ielm9t6SKW96leIB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;218&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포워딩이 만능은 아니다. EX 단계 후 값을 포워딩할 수 있는 add와 달리, load 인스트럭션은 MEM 단계까지 끝나야 값을 포워딩해줄 수 있다. 그래서 어쩔수 없는 한 사이클의 기다림(stall)이 필요하다. 흔히 버블(bubble)로 아래의 그림처럼 표현한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qgzSn/btsOEe8P70X/WZwA3FR9IlsxASk6Wn0xBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qgzSn/btsOEe8P70X/WZwA3FR9IlsxASk6Wn0xBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qgzSn/btsOEe8P70X/WZwA3FR9IlsxASk6Wn0xBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqgzSn%2FbtsOEe8P70X%2FWZwA3FR9IlsxASk6Wn0xBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;613&quot; height=&quot;258&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨트롤 해저드(Control Hazards)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해저드의 세번째 종류이다. 인스트럭션의 실행결과에 따라, 다음에 어떤 인스트럭션을 실행할지가 정해지는 경우이다. 해결할 수 있는 첫번째 방법은 이번에도 &amp;ldquo;기다리기&amp;rdquo;이다. IF 단계에서, 이번에 실행해야할 인스트럭션이 beq(branch if equal) 임을 알았다면, beq가 EX 단계을 수행해야, 다음에 실행할 인스트럭션을 알 수 있다. 그떄까지 기다리면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 새로운 장치를 추가해본다면 어떨까? 이 장치는 ID 단계에서 beq의 결과를 빠르게 연산해서, 다음 인스트럭션을 알려주는 장치이다. 그러면 아래의 피규어처럼 단 한사이클만 기다려도 충분히 다음 인스트럭션을 알아내어 fetch할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wL3eF/btsOCDvwJIw/xTQsZ3yoGiiekW9d8uKFoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wL3eF/btsOCDvwJIw/xTQsZ3yoGiiekW9d8uKFoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wL3eF/btsOCDvwJIw/xTQsZ3yoGiiekW9d8uKFoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwL3eF%2FbtsOCDvwJIw%2FxTQsZ3yoGiiekW9d8uKFoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;259&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기다리기 방법 외에 또 다른 방법은 &amp;ldquo;예측하기&amp;rdquo;이다. 브랜치가 taken(바로 다음 인스트럭션이 아니라 다른 주소로 넘어감)되었는지, untaken되었는지를 예상하는 것이다. 만약 untaken 예측이 성공하면 우리는 최대 퍼포먼스로 파이프라이닝을 성공시킬 수 있다. (taken되는 경우는 여전히 기다림이 필요하다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhrvCZ/btsODH40D1T/xXeV16EM9a6QiJYmWYcKHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhrvCZ/btsODH40D1T/xXeV16EM9a6QiJYmWYcKHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhrvCZ/btsODH40D1T/xXeV16EM9a6QiJYmWYcKHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhrvCZ%2FbtsODH40D1T%2FxXeV16EM9a6QiJYmWYcKHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;527&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예측을 어떻게 할 수 있을까? 가장 흔한 방법은 과거의 인스트럭션 실행 결과를 참고하는 것이다. 이렇게 구현된 Dynamic branch predictors는 90% 이상의 정확도까지도 뽑아낼 수 있다. 4.8절에서 추가로 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째 방법은 &amp;ldquo;결정 미루기&amp;rdquo;(delayed decision) 이다. beq와 같은 컨트롤 인스트럭션과 관련없는 인스트럭션을 수행하면서, 브랜치 결과를 지켜보는 것이다. 그림에서 beq 인스트럭션과 add 인스트럭션은 관련이 없다. 그래서 순서를 바꿔 beq인스트럭션을 먼저 실행하고, 그 결과를 기다리면서 add 인스트럭션을 수행해도 프로그램 실행 결과는 달라지지 않는다. 이 해법은 branch delay가 한 사이클 정도라면 굉장히 유용하기에, MIPS 아키텍쳐도 이 방법을 이용한다. 만약 delay가 더 길다면, 예측하기 방법이 대개 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 4.5절 전체에서 파이프라이닝의 개념을 쭉 훑었다. 파이프라이닝은 병렬성(parellelism)을 이용한 테크닉이다. 다음 4.6, 4.7, 4.8, 4.9절에서는 앞서 훑어본 내용들을 더 깊게 파고든다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.6 Pipelined Datapath and Control&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;1116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIf8Q/btsOEgS7UQm/nSQKiyRs62UTVGDpV8g9gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIf8Q/btsOEgS7UQm/nSQKiyRs62UTVGDpV8g9gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIf8Q/btsOEgS7UQm/nSQKiyRs62UTVGDpV8g9gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIf8Q%2FbtsOEgS7UQm%2FnSQKiyRs62UTVGDpV8g9gK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1552&quot; height=&quot;1116&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;1116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리의 구조를 보자. 대부분의 데이터는 왼쪽에서 오른쪽으로 흐른다. 단, 위 피규어에서 파란색 화살표로 표시된 두 흐름은, 오른쪽에서 왼쪽으로 흐른다. 이 두 흐름은 각각 데이터 해저드와 컨트롤 해저드를 유발한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WB 단계에서, 결과 데이터는 레지스터 파일로 보내진다.&lt;/li&gt;
&lt;li&gt;MEM 단계에서, 브랜칭 인스트럭션 실행 결과에 따라, 다음에 실행할 인스트럭션이 달라진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFX4wX/btsOD1IEmZF/b2ookdCXZ4J9d72uaiccB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFX4wX/btsOD1IEmZF/b2ookdCXZ4J9d72uaiccB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFX4wX/btsOD1IEmZF/b2ookdCXZ4J9d72uaiccB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFX4wX%2FbtsOD1IEmZF%2Fb2ookdCXZ4J9d72uaiccB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1452&quot; height=&quot;704&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파이프라인을 완전히 독립적인 파이프라인으로 만들기 위해, 우리는 단계 사이사이의 상태를 저장할 레지스터가 필요하다. 빨래로 비유하자면, 세탁기에서 건조기, 건조기에서 마룻바닥, 마룻바닥에서 옷장으로 옷을 옮길 때 각 단계를 구분할 옷바구니가 필요하다. IF와 ID 사이에 필요한 레지스터 이름을 IF/ID라고 부른다. 마찬가지로, ID/EX, EX/MEM, MEM/WB 레지스터도 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BIkYJ/btsOCjRyyYT/iplkwJERahksXm7QZK1VKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BIkYJ/btsOCjRyyYT/iplkwJERahksXm7QZK1VKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BIkYJ/btsOCjRyyYT/iplkwJERahksXm7QZK1VKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBIkYJ%2FbtsOCjRyyYT%2FiplkwJERahksXm7QZK1VKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;539&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lw 인스트럭션 실행과정 중 쓰이는 리소스들을 파란색으로 표현했다. 리소스의 왼쪽 부분이 칠해진것은 쓰기 연산, 오른쪽이 칠해진것은 읽기 연산임을 의미한다. MEM 과정에서 읽기 연산을 수행하는 것을 볼 수 있다. 구체적으로는 아래와 같다. lw 인스트럭션을 기준으로 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;IF: 인스트럭션 정보를 IF/ID 레지스터에 저장한다. PC 주소에 4를 더하여, PC에 스스로 업데이트하고, IF/ID 레지스터에도 남긴다. beq같은 브랜칭 인스트럭션에 쓰이는데, 일단 이 단계에서는 이 인스트럭션의 종류를 알 수 없기때문에 무조건 일단 저장해야한다.&lt;/li&gt;
&lt;li&gt;ID: IF/ID 레지스터로부터 16비트 상수나 읽을 레지스터의 번호를 넘겨받아, 상수의 부호 확장(sign extend)처리 및 레지스터 읽기 연산을 수행하여 ID/EX 레지스터에 넘긴다.&lt;/li&gt;
&lt;li&gt;EX: ID/EX 레지스터로 부터 받아온 피연산자(operands)들을 적절히 연산하여 EX/MEM 레지스터에 저장한다.&lt;/li&gt;
&lt;li&gt;MEM: EX/MEM으로부터 받은 결과(lw 인스트럭션에서는 읽을 메모리 주소)로 메모리에서 값을 읽어 MEM/WB 레지스터에 저장한다.&lt;/li&gt;
&lt;li&gt;WB: MEM/WB 레지스터에 저장된 값을 레지스터 파일에 쓰기한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sw 인스트럭션도 유사하나 주의할 점이 있다. ID 과정에서 얻어지는 쓰기 레지스터의 번호를 WB과정까지 잘 운반해주어야한다. IF/ID &amp;rarr; ID/EX &amp;rarr; EX/MEM &amp;rarr; MEM/WB 까지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 내리 운반해주어야할 값이 있다. Control signal들이다. 각 단계마다 필요한 Control signal들을 보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;IF: 없음(매번 하는일이 같다.)&lt;/li&gt;
&lt;li&gt;ID: 없음(매번 하는일이 같다.)&lt;/li&gt;
&lt;li&gt;EX: RegDst(목적지 레지스터가 rt인지 rd인지), ALUOp(ALU에서 어떤 연산을 할지), ALUSrc(ALU에 들어갈 피연산자가 rt인지 상수인지)&lt;/li&gt;
&lt;li&gt;MEM: MemRead(할일이 메모리 읽기인지 아닌지), MemWrite(할일이 메모리 쓰기인지 아닌지), Branch(브랜치 명령어인지), PCSrc(taken인지 아닌지)&lt;/li&gt;
&lt;li&gt;WB: MemtoReg(메모리에 쓰기 할게 메모리에서 읽은 값인지, 읽기 전 주소인지), Reg-Write(레지스터에 쓰기 할지말지)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 그룹지어진 Control signal들은 IF/ID 레지스터부터 내리 운반된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqKcWN/btsOCNdytal/UmM5luu6CEhV8kRJk8IMGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqKcWN/btsOCNdytal/UmM5luu6CEhV8kRJk8IMGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqKcWN/btsOCNdytal/UmM5luu6CEhV8kRJk8IMGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqKcWN%2FbtsOCNdytal%2FUmM5luu6CEhV8kRJk8IMGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;381&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.7 Data Hazards: Forwarding versus Stalling&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 하자드에 대해 면밀히 살펴보자. 데이터 하자드의 정확한 발생 조건은 무엇인가?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/badgyE/btsODkCbD6U/sKjCwVlBSi8FkdMwPwMs2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/badgyE/btsODkCbD6U/sKjCwVlBSi8FkdMwPwMs2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/badgyE/btsODkCbD6U/sKjCwVlBSi8FkdMwPwMs2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbadgyE%2FbtsODkCbD6U%2FsKjCwVlBSi8FkdMwPwMs2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;415&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽의 다섯개의 인스트럭션 시퀀스를 실행하는 모습이다. $2 레지스터에는 원래 10이 담겨있었으나, 첫번째 인스트럭션의 실행 결과로, 5번째 사이클에서 -20으로 값이 달라졌다. 그다음 두번째 and 인스트럭션은 -20을 가지고 연산을 수행해야하지만, 레지스터에서 값을 읽은 3번째 사이클 때에는 $2의 값은 10이였다. 데이터 하자드가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 그림을 통해 이해할 수 있는것은, 앞선 인스트럭션의 WB 단계 이전에 이후 인스트럭션의 EX 단계가 실행될때, 문제가 발생한다는 것이다. 다르게 말하면, EX/MEM 혹은 MEM/WB 레지스터에 들어있는 rd 레지스터 넘버가, ID/EX 레지스터의 rs 혹은 rt 레지스터의 넘버와 같을 때 데이터 하자드가 일어남을 알 수 있다. 이 내용을 수식적으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qpPlp/btsOBjLm3ju/PrBHhNepZ0RQhWWs61Wl21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qpPlp/btsOBjLm3ju/PrBHhNepZ0RQhWWs61Wl21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qpPlp/btsOBjLm3ju/PrBHhNepZ0RQhWWs61Wl21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqpPlp%2FbtsOBjLm3ju%2FPrBHhNepZ0RQhWWs61Wl21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;298&quot; height=&quot;106&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 추론한 데이터 하자드의 발생 조건이지만, 아직 완전하지는 않다. 왜냐하면 모든 인스트럭션이 쓰기작업을 요하지는 않기 때문이다. 우리는 이것을 WB 단계의 컨트롤 신호인 RegWrite의 값으로 알 수 있다. 쓰기 작업을 요하는 인스트럭션은 RegWrite 값이 1이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, $0 레지스터는 항상 0의 값을 유지한다. 이 레지스터에는 쓰기 작업도 할 수 없고, 항상 0임을 보장하는 특이한 레지스터이다. 만약 &amp;ldquo;sll $0, $1, 2&amp;rdquo; 처럼 쓰기할 레지스터가 $0인 경우는 하자드를 걱정할 필요 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회로로 보자면, 아래의 그림과 같을 것이다. MEM/WB와 EX/MEM 레지스터로부터 나온 RegisterRd 값은, forwarding unit으로 넘겨진다. 이 forwarding unit은 rs와 rt 값을 다루는 두개의 Mux에 ForwardA, ForwardB라는 컨트롤 신호를 보내게 된다. 레지스터에서 읽어온 rs/rt값과 이전 인스트럭션이 연산하여 포워딩해준 rd값 중 어떤값을 연산에 사용할지 정할 수 있는 것이다. ForwardA, B는 2비트 신호 이다. 00, 10, 01은 각각 ALU에 넘겨줄 값을 ID/EX, EX/MEM, MEM/WB에서 가져온 값으로 이용함을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXDit6/btsODmUm1a0/LOjKhSmOC5wyiLb8zHUKRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXDit6/btsODmUm1a0/LOjKhSmOC5wyiLb8zHUKRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXDit6/btsODmUm1a0/LOjKhSmOC5wyiLb8zHUKRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXDit6%2FbtsODmUm1a0%2FLOjKhSmOC5wyiLb8zHUKRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;351&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;112&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oBQjg/btsODjDj2VW/udPTVhhgCbqRspjcZqGkY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oBQjg/btsODjDj2VW/udPTVhhgCbqRspjcZqGkY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oBQjg/btsODjDj2VW/udPTVhhgCbqRspjcZqGkY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoBQjg%2FbtsODjDj2VW%2FudPTVhhgCbqRspjcZqGkY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;112&quot; height=&quot;65&quot; data-origin-width=&quot;112&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또 다른 복잡한 상황은, EX/MEM과 MEM/WB에서 포워딩된 두 값이 다른 상황이다. 오른쪽과 같은 시퀀스에서 이런 일이 발생할 수 있다. EX/MEM.RegisterRd, MEM/WB.RegisterRd, ID/EX.RegisterRs 셋의 값이 모두 동일한 것이다. 이럴때는 더 이후에 연산된 값(두번째 add의 계산 결과), 즉, EX/MEM.RegisterRd에 더 최신의 $1 값이 들어있다. 따라서, EX/MEM.RegisterRd의 포워딩은 EX/MEM.RegisterRd와 MEM/WB.RegisterRd의 값이 같은 상황에서는 일어나면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용들을 모두 종합하면, 아래의 로직으로 forwarding unit이 ForwardA, ForwardB의 값을 결정한다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1349&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v3Hdf/btsOCnzH2ol/pQQkpBj4PaooB4DSfWOh1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v3Hdf/btsOCnzH2ol/pQQkpBj4PaooB4DSfWOh1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v3Hdf/btsOCnzH2ol/pQQkpBj4PaooB4DSfWOh1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv3Hdf%2FbtsOCnzH2ol%2FpQQkpBj4PaooB4DSfWOh1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1349&quot; height=&quot;338&quot; data-origin-width=&quot;1349&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.5절에서 이미 다뤘듯이, load 인스트럭션의 경우라면 얘기가 또 다르다. Load 연산은 MEM 과정이 지나고서야 쓰기할 값이 정해지므로, 한 번의 stall이 필연적이다. 아래의 그림을 통해 알 수 있듯이, load의 경우는 포워딩만으로도 문제를 해결할 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;449&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bndEXG/btsODYLW5Ao/cOncnTA5GGvk3H9qBXfxlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bndEXG/btsODYLW5Ao/cOncnTA5GGvk3H9qBXfxlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bndEXG/btsODYLW5Ao/cOncnTA5GGvk3H9qBXfxlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbndEXG%2FbtsODYLW5Ao%2FcOncnTA5GGvk3H9qBXfxlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;449&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;449&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 forwarding unit에 이어서, hazard detection unit을 추가할 것이다. 이것은 ID 단계에서 동작하며, load 연산과 그 다음 load된 값을 사용하는 연산 사이에 stall을 추가한다. 조건은 아래와 같을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l71yu/btsOEdIR2Hn/PIzK8m9uUqXdVGkuMGvUok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l71yu/btsOEdIR2Hn/PIzK8m9uUqXdVGkuMGvUok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l71yu/btsOEdIR2Hn/PIzK8m9uUqXdVGkuMGvUok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl71yu%2FbtsOEdIR2Hn%2FPIzK8m9uUqXdVGkuMGvUok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;77&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;and 연산의 ID 수행 이전에, 위 조건을 체크한다. 앞선 인스트럭션의 MemRead가 1이였다면, 즉 메모리를 읽는 load 연산이였다면, 그리고, 해당 load 연산 결과가 담기는 레지스터 rt와 내가 이번 add 연산에 사용할 rs, rt의 번호가 같다면, 해당 파이프라인을 stall 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID 단계가 stall 되면, 그 이전의 IF 단계도 당연히 stall 되어야 한다. PC 레지스터와 IF/ID 레지스터의 값을 갱신하지 않고 그대로 두면, 간단하게 stall은 구현된다. 비유하자면, 이미 세탁기와 건조기에 들어가있는 빨래를, 아무것도 건들지 않고 다시 세탁기와 건조기를 재가동하는 셈이다. 아무 효과도 없는 이런 인스트럭션을 nops라고 부른다. 그림으로 표현하자면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9d0wl/btsODNcQLcx/DBc0PsvVON5VFynmqnqh3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9d0wl/btsODNcQLcx/DBc0PsvVON5VFynmqnqh3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9d0wl/btsODNcQLcx/DBc0PsvVON5VFynmqnqh3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9d0wl%2FbtsODNcQLcx%2FDBc0PsvVON5VFynmqnqh3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;403&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.8 Control Hazards&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;언젠가 작성됩니다..&lt;/i&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.9 Exceptions&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;언젠가 작성됩니다..&lt;/i&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.10 Parallelism via Instructions&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라이닝은 인스트럭션은 병렬적으로 실행할 수 있게 한다. 이렇게 인스트럭션이 병렬적으로 실행되는것을 ILP(Instruction-level parellelism)이라고 한다. 병렬성을 높이는 두 가지 방법을 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째는 파이프라이닝의 단계를 늘리는 것이다. 빨래로 비유하면, &amp;ldquo;세탁&amp;rdquo; 과정을 &amp;ldquo;씻기&amp;rdquo;,&amp;rdquo;린스&amp;rdquo;,&amp;rdquo;헹구기&amp;rdquo; 3가지 과정으로 쪼개서, 4단계의 파이프라인을 6단계의 파이프라인으로 늘리는 것이다. 이후, 6개의 단계들의 실행시간을 재분배(rebalance)하여 클락 사이클을 줄여, 성능의 개선을 이뤄낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째는, 파이프라이닝에 사용되는 하드웨어 구성요소들을 복제하여, 동시에 여러개의 인스트럭션을 실행할 수 있도록 하는 것이다. 이 기술을 다중 내보내기(multiple issue)라고 한다. 다시 빨래로 비유하면, 원래 세탁기 하나, 건조기 하나가 있던 것을, 세탁기 3대, 건조기 3대로 늘려서 3배 많은 일을 하도록 하는 것이다. 이 방식의 단점이라고 하면, 모든 기계를 최대한 바쁘게 가동하고, 파이프라인 단계 간 결과물을 옮기는데에 추가적인 작업이 든다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 단계 안에서 여러 인스트럭션을 실행하는 일은, CPI를 1보다 낮출 수 있다. CPI(Clock cycle per instruction) 대신 IPC(Instruction per clock cycle)을 사용하는 것이 유용할수도 있다. 예를 들어, 4GHz 4-way multiple issue 프로세서는 1초당 160억 개의 인스터럭션을 최대 0.25의 CPI, 혹은 4의 IPC의 성능으로 실행해낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multiple issue를 구현하는 두 가지 방식이 있다. 동적(dynmaic)인 방식과 정적(static)인 방식이다. 아래의 두 관점이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인스트럭션을 이슈 슬롯(issue slot)에 어떻게 배치할지: 컴파일러가 컴파일 시에 미리 정하는 정적인 방식과, 런타임 시에 결정하는 동적인 방식&lt;/li&gt;
&lt;li&gt;컨트롤/데이터 해저드 대응: 동적인 방식은 정적인 방식 대비 하드웨어적인 기술을 이융하여 몇몇 종류의 해저드들을 완화한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ILP에서 중요한 개념 중 하나는 추측(speculation)이다. 분기(branch) 결과를 미리 추측하면 분기 이후의 명령어들을 더 빨리 실행할 수 있다. 앞선 store와 뒤따르는 load가 서로 다른 주소를 사용하는 것을 추측하면, load를 store보다 먼저 실행할 수도 있다. 추측은 성능을 크게&amp;nbsp;향상시킬&amp;nbsp;수&amp;nbsp;있지만, 잘못 사용하면&amp;nbsp;오히려 성능 저하나 예외 발생 등 문제를 일으킬&amp;nbsp;수&amp;nbsp;있으므로, 신중한 적용이 중요하다. 추측이 틀릴 경우를 대비해, 결과를 검증하고 잘못된 경우 효과를 되돌리는&amp;nbsp;복구 메커니즘이 반드시 필요하며, 이는 소프트웨어와 하드웨어에서 각각 다르게 구현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;언젠가 작성됩니다..&lt;/i&gt;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/36</guid>
      <comments>https://junbyeol.tistory.com/36#entry36comment</comments>
      <pubDate>Mon, 16 Jun 2025 15:08:57 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스 개론] 8. 쿼리 실행(Query Execution)</title>
      <link>https://junbyeol.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;15장과 16장은 쿼리 처리를 다룬다. SQL은 쿼리를 매우 높은 수준에서 표현하기 때문에, 쿼리 프로세서는 쿼리를 단순히 실행만 하기 보다, 어떻게 실행할지 고민하여 실행 효율을 높여야 한다. 15장에서는 쿼리 실행, 16장에서는 쿼리 컴파일에 집중한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ruNKj/btsOCAFcSTX/liG6kkiWvIqGDbudVD2YNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ruNKj/btsOCAFcSTX/liG6kkiWvIqGDbudVD2YNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ruNKj/btsOCAFcSTX/liG6kkiWvIqGDbudVD2YNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FruNKj%2FbtsOCAFcSTX%2FliG6kkiWvIqGDbudVD2YNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;227&quot; height=&quot;256&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 컴파일 단계는 세 가지 단계로 나뉜다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;파싱(parsing): 쿼리로부터 맥락 트리(parse treee)를 만듦&lt;/li&gt;
&lt;li&gt;논리적 계획 생성(Logical Plan Generation): 맥락 트리로부터 논리적 계획(보통 대수적 표현)을 생성한다. 실행시간을 최소화 하기 위해, 동등하지만 더 효율적인 계획으로 변경한다.&lt;/li&gt;
&lt;li&gt;물리적 계획 생성(Phyisical Plan Generation): 논리적 계획을 물리적 계획으로 변경한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번과 3번 단계를 흔히 쿼리 옵티마이저(optimizier)라고 하며, 가장 어려운 부분이다. 최적의 쿼리 계획을 선택하려면 아래의 고민이 필요하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;실행할 쿼리와 대수적으로 동등한 여려 형태 중 어떤것이 가장 효율적인가?&lt;/li&gt;
&lt;li&gt;선택된 형태의 각 연산을 어떻게 알고리즘으로 구현할 것인가?&lt;/li&gt;
&lt;li&gt;연산 간 데이터 전달을 어떻게 할 것인가? (파이프라인, 메인 메모리 버퍼, 디스크 등)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL은 bag 모델을 사용하기 때문에, bag 연산자 버전의 관계 대수를 이용한다. 위의 고민들은 데이터베이스의 메타데이터들을 통해 결정된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;15.1 Introduction to Physical-Query-Plan Operators&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리적 계획은 연산자들로 구성된다. 연산자들은 특정 관계 대수를 위한 연산자도 있지만, 직접 관련없는 scan() 같은 연산자도 있다. 이 절에서는 물리적 계획을 구성하는 기본 블럭들을 다루고, 이후에는 관계 대수 연산은 효율적으로 구현하는 알고리즘을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리적 계획의 기본은 릴레이션 R의 전체 내용을 읽는 것이다. 기본적으로 두가지 방법이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;디스크에 블럭 단위로 저장된 릴레이션들을 블럭 단위로 하나씩 가져오는 테이블 스캔(table-scan)&lt;/li&gt;
&lt;li&gt;인덱스를 사용해 모든 튜플을 가져오는 인덱스 스캔(index-scan)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 데이터를 가져옴과 동시에, 정렬된 릴레이션이 필요하다면 소트 스캔(sort-scan)을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 쿼리가 좋은 쿼리인지 선택하려면, 쿼리의 비용을 추산해야 한다. 이 때 비용의 척도로는, 디스크 I/O 횟수가 사용된다. 이 비용을 추산하기 위한 파라미터들도 소개한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;M: 연산에 사용가능한 메모리의 블럭 수. 보통 M은 실행중인 다른 프로세스 등에 의해 실행중에 결정되므로, 정확한 값보다는 추정값이다.&lt;/li&gt;
&lt;li&gt;B(R): R의 모든 튜플을 저장하는데 필요한 블럭 수. R은 B개의 블럭에 저장된다.&lt;/li&gt;
&lt;li&gt;T(R): R의 튜플 개수&lt;/li&gt;
&lt;li&gt;V(R,a): R의 a컬럼에 나타나는 서로 다른 값의 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터를 적용해보자. 릴레이션 R이 클러스터링 되어있다면, 테이블 스캔 연산자의 디스크 I/O 횟수는 B이다. R이 메인메모리에 들어가는데에 문제가 없다면 소트 스캔도 마찬가지다. 하지만 클러스터링 되어있지 않다면, 튜플 횟수만큼 I/O를 해야하므로 테이블 스캔과 소트 스캔 비용은 T가 된다. 인덱스 스캔도 인덱스를 미리 살펴봐야하기는 하나, 인덱스의 크기는 B(R)보다 훨씬 작은 수이므로 동일하게 클러스터링 유무 각각 B와 T로 비용을 추산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 물리적 연산자는 이터레이터(Iterator)로 구현된다. 연산자의 결과를 한번에 생성하는 물질화(materization) 전략에 비해 이터레이터 방식은 저장공간 관리에 유리하다. 물리적 연산자의 결과를 한 튜플씩 소비자에게 전달하는 구현은 세 가지 메소드로 구성된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Open(): 튜플을 가져오는 과정을 시작함. 필요한 데이터구조들을 초기화한다.&lt;/li&gt;
&lt;li&gt;GetNext(): 결과에서 다음 튜플을 반환하고, 다음 튜플을 준비한다.&lt;/li&gt;
&lt;li&gt;Close(): 소비자가 원하는 모든 튜플을 얻은 후, 반복을 종료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 연산자마다 구현할 알고리즘을 정할것이다. 방법에 따라 세 가지로 나눌 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;정렬 기반&lt;/li&gt;
&lt;li&gt;해시 기반&lt;/li&gt;
&lt;li&gt;인덱스 기반&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난이도와 비용에 따라서도 세 단계로 나눌 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터를 디스크에서 한 번만 읽는 one-pass&lt;/li&gt;
&lt;li&gt;데이터를 디스크에서 두 번 읽는 two-pass, 큰 데이터의 크기 때문에 한 번에 메모리 크기 M안에 올릴 수 없기 때문이다.&lt;/li&gt;
&lt;li&gt;Two-pass의 확장 개념으로, 재귀적으로 계속 디스크를 읽는 multi-pass&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산자의 종류도 세 가지로 분류할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;튜플 단위, 단항 연산
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;종류: 선택(selection), 사영(projection)&lt;/li&gt;
&lt;li&gt;블록 단위로 읽고, 메모리 버퍼 하나만으로 연산 가능하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;릴레이션 단위, 단항 연산
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;종류: 중복제거(duplicate-elimination), 그룹화(grouping)&lt;/li&gt;
&lt;li&gt;릴레이션 크기가 M보다 작으면 one-pass로도 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;릴레이션 단위, 이항 연산
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;종류: 집합연산, 곱연산(union, intersection, difference, join, production)&lt;/li&gt;
&lt;li&gt;적어도 하나의 릴레이션 크기가 M보다 작으면 one-pass로도 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15.2는 one-pass 알고리즘을 다룬다. 15.3~15.6은 two-pass 알고리즘을 다루며, 각각 iteration-based, sort-based, hash-based, index-based 알고리즘이다. 15.8은 multi-pass 알고리즘을 다루지만 이 글에서는 생략한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15.2... 이후로는 언젠가 작성됩니다&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/35</guid>
      <comments>https://junbyeol.tistory.com/35#entry35comment</comments>
      <pubDate>Mon, 16 Jun 2025 14:36:01 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스 개론] 7. 인덱스 구조(Index Structures)</title>
      <link>https://junbyeol.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT * FROM R이라는 쿼리를 실행 하기 위해서 어떻게 해야할까? 저장소의 모든 블럭을 검사해야한다면, 너무 비효율적이다. 일부 블록과 실린더를 따로 R을 위해 예약할 수도 있지만, 여전히 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 하나 이상의 필드 값을 받아서, 그 값을 가진 레코드를 빠르게 찾을 수 있게 해주는 자료구조다. 인덱스를 사용하면 전체 레코드 중 일부만 보고, 원하는 레코드를 모두 찾을 수 있다. 인덱스가 기반하는 필드를 검색 키(search key)라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 챕터에는 가장 흔한 형태의 인덱스를 다룬다. B-tree 이다. 또 다른 중요한 인덱스 구조인 2차 저장소의 해시 테이블도 다룬다. 마지막으로, 다차원의 데이터를 다루기 위해 디자인 된 인덱스 구조도 다룬다. 이 이덱스 구조들은 여러 값이나 범위의 속성들을 한 번에 다룬다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14.1 Index-Structure Basics&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 다룰 몇가지 개념을 소개한다. 저장소는 파일(file)로 구성된다. 운영체제의 파일과 비슷하다. 데이터 파일(data file)은 릴레이션을 저장하기 위해 사용된다. 데이터 파일은 하나 이상의 인덱스 파일(index files)을 가질 수 있다. 인덱스 파일은 검색 키의 값과 해당 값을 가진 데이터 파일의 레코드를 가리키는 포인터를 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 파일의 모든 레코드가 인덱스 파일에 항목이 존재하면 밀집하다(dense), 몇몇의 레코드만 항목으로 존재하면 희소하다(sparse)고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 주/보조로도 나눈다. 주 인덱스는 레코드의 위치를 결정하지만, 보조는 그렇지 않다. 예를 들어, 1차 인덱스는 릴레이션의 주요 키(primary key)와 나머지 속성으로 2차 인덱스를 구성하는 것이 흔하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 우리는 역-인덱스(Inverted index)에 대해 소개한다. 역-인덱스는 문서속에서 특정 키워드가 있는지 검색을 도와준다. 웹에서 검색어에 응답하는 등의 사례에 필수적인 기능이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순차 파일(Sequential file): 릴레이션을 주요 키로 소팅하여 만든 파일. 튜플들은 블럭들안에 정렬되어 자리잡는다.&lt;/li&gt;
&lt;li&gt;밀집 인덱스(Dense Index): 레코드의 키와 해당하는 레코드의 포인터를 담은 블럭들의 묶음이다. 보통 레코드를 저장하는 것보다, 키와 포인터가 훨씬 적은 저장공간을 차지하기 때문에, 데이터 파일보다 훨씬 적은 수의 블럭이면 충분하다. 그래서, 데이터 파일과 달리 메인 메모리에 올릴 수 있어서, 비싼 디스크 I/O 횟수를 줄일 수 있다. 키가 정렬되어 있다면, 이진 탐색(binary search)를 통해 탐색 시간도 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvYhgn/btsOBa1WEKv/O5TJBYnqo2Hpm5tWMYg8UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvYhgn/btsOBa1WEKv/O5TJBYnqo2Hpm5tWMYg8UK/img.png&quot; data-alt=&quot;밀집인덱스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvYhgn/btsOBa1WEKv/O5TJBYnqo2Hpm5tWMYg8UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvYhgn%2FbtsOBa1WEKv%2FO5TJBYnqo2Hpm5tWMYg8UK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;531&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;밀집인덱스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;희소 인덱스(Sparse Index): 보통 하나의 블럭당 하나의 키와 포인터 쌍만 갖는다. 밀집 인덱스보다 공간을 덜차지하지만, 더 많은 레코드 탐색 시간을 요구한다. 그리고 모든 레코드에 해당하는 인덱스가 있는 밀집 인덱스와 달리, 일부 레코드만 인덱스에 의해 참조되므로, 키로 sorting되는 것이 반드시 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CsVxS/btsOBqcwfDg/zeLnRVq1h9Z4zQJq4ukBVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CsVxS/btsOBqcwfDg/zeLnRVq1h9Z4zQJq4ukBVk/img.png&quot; data-alt=&quot;희소인덱스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CsVxS/btsOBqcwfDg/zeLnRVq1h9Z4zQJq4ukBVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCsVxS%2FbtsOBqcwfDg%2FzeLnRVq1h9Z4zQJq4ukBVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;483&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;희소인덱스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티 레벨 인덱스(Multiple level index): 인덱스 위에 또 다른 인덱스를 두어 이진탐색 이상으로 두 빠른 탐색 속도를 만들 수도 있다. 그러나, 일반적으로 너무 많은 레벨의 인덱스를 두는 것보다는 B-tree 자료구조를 이용하는 것이 더 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k7boZ/btsODoEiXt9/Riq1TeTKiw0KkokRO6mB50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k7boZ/btsODoEiXt9/Riq1TeTKiw0KkokRO6mB50/img.png&quot; data-alt=&quot;멀티 레벨 인덱스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k7boZ/btsODoEiXt9/Riq1TeTKiw0KkokRO6mB50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk7boZ%2FbtsODoEiXt9%2FRiq1TeTKiw0KkokRO6mB50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;454&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;멀티 레벨 인덱스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보조 인덱스(Secondary Index): 주 인덱스(Primary Index)와의 차이는 데이터 파일 내에 레코드의 위치를 결정하지 않는다는 것. 멀티 레벨로 구현될수 있지만, 첫번째 레벨 인덱스는 반드시 밀집 인덱스여야함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wzvHJ/btsOCfux2nN/9KeXCkANCOtzdB2deQJ331/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wzvHJ/btsOCfux2nN/9KeXCkANCOtzdB2deQJ331/img.png&quot; data-alt=&quot;보조 인덱스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wzvHJ/btsOCfux2nN/9KeXCkANCOtzdB2deQJ331/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwzvHJ%2FbtsOCfux2nN%2F9KeXCkANCOtzdB2deQJ331%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;425&quot; height=&quot;473&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;보조 인덱스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보조 인덱스는 언제 필요할까? Movie와 Studio가 다대일 관계라고 하자. Movie의 정보를 Movie들끼리 저장할수도 있지만, Movie가 제작된 Studio 정보와 연속되게 배치하면 더 효율적이다. 이렇게 배치된게 클러스터드 파일이다. 하지만 Studio 정보가 아닌, 다른 Movie 필드의 조건으로 검색하려고 하면 비효율적이다. 그래서 해당 속성에 대한 보조 인덱스가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C7jaV/btsOCL7LVSN/KeI7zUx6sff4Fkvh8liwck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C7jaV/btsOCL7LVSN/KeI7zUx6sff4Fkvh8liwck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C7jaV/btsOCL7LVSN/KeI7zUx6sff4Fkvh8liwck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC7jaV%2FbtsOCL7LVSN%2FKeI7zUx6sff4Fkvh8liwck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;171&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 키값을 갖는 인덱스들이 여러번 저장되는 보조 인덱스는 공간 비효율적이다. 이것을 해결하는 버킷(bucket)이 있다. 버킷은 같은 인덱스 키값을 갖는 데이터 파일을 별도의 버킷 파일에 모아둔다. 인덱스는 이제 (키, 버킷위치) 만을 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버킷은 문서 속 키워드를 검색하는데에도 사용된다. 아래 버킷파일에는 cat을 포함하는 문서들과 dog를 포함하는 문서들이 함께 모여있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH6T1k/btsOChTqdJA/G6sMyFVloGnZJ2fZdPZqrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH6T1k/btsOChTqdJA/G6sMyFVloGnZJ2fZdPZqrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH6T1k/btsOChTqdJA/G6sMyFVloGnZJ2fZdPZqrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH6T1k%2FbtsOChTqdJA%2FG6sMyFVloGnZJ2fZdPZqrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;460&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버킷 파일 스스로가 레코드의 역할도 할 수 있다. 아래의 그림은 버킷 파일이 cat과 dog라는 키워드의 포함여부 뿐만 아니라 위치와 타입까지 저장하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BulyU/btsOCytSEDs/ja5HAzCma12UUriaicaMMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BulyU/btsOCytSEDs/ja5HAzCma12UUriaicaMMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BulyU/btsOCytSEDs/ja5HAzCma12UUriaicaMMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBulyU%2FbtsOCytSEDs%2Fja5HAzCma12UUriaicaMMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;365&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14.2 B-Trees&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 언급했듯이, 3개 이상의 너무 많은 멀티 레벨 인덱스보다는 B-Tree 자료 구조가 효율적이다. 그 중에서도 B+ Tree가 가장 보편적이다. B+ Tree는 인덱스되는 파일 크기에 맞게 인덱스 레벨을 조절하고, 사용되는 블럭들이 최소 절반이상 채워지도록 조절한다. 앞으로 얘기하는 B-Tree는 모두 B+ Tree에 관한 내용이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk0L1y/btsOB6SmU8c/dXF4m0fFwsysbDSZ1zcRk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk0L1y/btsOB6SmU8c/dXF4m0fFwsysbDSZ1zcRk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk0L1y/btsOB6SmU8c/dXF4m0fFwsysbDSZ1zcRk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk0L1y%2FbtsOB6SmU8c%2FdXF4m0fFwsysbDSZ1zcRk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;343&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree는 3개의 계층(루트, 중간, 리프)로 구성되며, 모든 리프와 루트의 거리가 같은 균형잡힌 트리이다. 중간계층은 수에는 제한이 없다. 각 트리 인덱스는 n개의 검색 키와 n+1개의 포인터 저장공간으로 구성된다. 블럭의 크기에 따라 n을 최대한 크게 결정하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프노드의 포인터는 레코드를 가리킨다. 최소 (n+1)/2개(버림)의 포인터가 있어야 하며, 마지막 포인터는 예외적으로 다음 리프 노드를 기리킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간노드와 루트노드는 아래 계층의 블럭을 가리킨다. 중간 노드는 최소 (n+1)/2개(올림)의 포인터가 있어야 하지만, 루트 노드는 2개만 있어도 된다. 또, 중간노드와 루트노드는 갖고 있는 검색 키를 기준으로 크기를 비교하여 참조할 블럭을 정한다. 예를 들어, 23, 31, 43을 키로 갖는 중간 노드의 4개의 포인터는 각각 23미만, 23이상 31미만, 31이상 43미만, 43이상의 레코드가 있는 부분을 가리킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-tree는 다양하게 응용할 수 있다. B-tree 키를 레코드의 주요키로 쓸지말지/리프를 희소로할지 말집으로 할지/B-tree 키의 중복을 허용할지(이러면 null도 허용되어야 함) 등에 따라 데이터 파일의 정렬 여부도 다르다.&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/34</guid>
      <comments>https://junbyeol.tistory.com/34#entry34comment</comments>
      <pubDate>Mon, 16 Jun 2025 14:34:03 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스 개론] 6. 2차 저장소 관리(Secondary Storage Management)</title>
      <link>https://junbyeol.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스는 항상 디스크 등의 대용량의 데이터를 저장하는 2차 저장소를 갖습니다. 이 챕터에서는 전통적인 컴퓨터가 어떻게 스토리지를 관리하는지 요약합니다. 또, 릴레이션의 튜플들을 어떻게 저장하는지 다룹니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13.1 메모리 계층(The Memory Hierarchy)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 속 메모리 계층을 시작으로 챕터를 시작해봅시다. 메모리를 저장하는 부품들은 7종류로 나뉩니다. 크기와 접근 속도가 모두 다르며, 크기가 작을수록 접근속도는 빠르고, 바이트 당 가격도 올라갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYt9lZ/btsOBr3v7MV/UfW5Z52GH9JjjJ9K9XwWY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYt9lZ/btsOBr3v7MV/UfW5Z52GH9JjjJ9K9XwWY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYt9lZ/btsOBr3v7MV/UfW5Z52GH9JjjJ9K9XwWY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYt9lZ%2FbtsOBr3v7MV%2FUfW5Z52GH9JjjJ9K9XwWY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;411&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;캐시(Cache): 내장 캐시(On-board cache)는 프로세서 그 자체 안에 포함되어 있고, 추가적인 level-2 캐시는 다른 칩에 포함됩니다. 프로세서가 요구하는 데이터는 메인 메모리에서 캐시로 옮겨져서, 몇 나노초 안에 데이터를 프로세서에 제공합니다.&lt;/li&gt;
&lt;li&gt;메인 메모리(Main memory): 명령어 실행 및 데이터 수정 등에 필요한 정보들은 메인 메모리에 상주합니다. 메인 메모리에서 프로세서나 캐시로 데이터를 옮기는 데에는 10~100 나노초가 필요합니다.&lt;/li&gt;
&lt;li&gt;2차 저장소(Secondary Storage): 보통 자기 디스크를 의미한다. 디스크에서 메인 메모리로 데이터를 옮기는 데에는 10 밀리초 정도가 소요된다. 대신, 한번에 많은 바이트의 데이터가 옮겨지므로, 이 프로세스를 빠르게 개선하는 작업은 복잡하다. 비휘발성이므로 전원이 사라져도 데이터가 보존된다.&lt;/li&gt;
&lt;li&gt;3차 저장소(Tertiary Storage): 디스크보다도 훨씬 큰 데이터들을 저장하는 하나 혹은 여러개의 머신들이다. 읽기/쓰기 시간이 2차 저장소보다도 오래걸리지만, 바이트 당 비용은 더더욱 저렴하다. 보통 자기 테이프(magnetic tape)나 DVD 같은 광학 디스크(optical disk)를 읽기 장치로 사용한다. 조회에는 몇 초 에서 몇 분이 걸리지만 페타바이트의 용량까지 감당 가능하다. 비휘발성이므로 전원이 사라져도 데이터가 보존된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 메모리(virtual memory)는 일부는 메인 메모리에, 나머지는 디스크에 디스크 블록(페이지) 단위로 소프트웨어를 저장하는 방법이다. 운영체제와 하드웨어의 상호작용이다. 이것은 데이터베이스가 일반적으로 사용하는 방식은 아니다. DBMS는 스스로 데이터를 관리하기 때문이다. 그러나, 가상 메모리를 이용하여, 메인 메모리에서 데이터를 다루는 것에 대한 관심도가 증가하고 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13.2 디스크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에는 두 가지 움직이는 부품이 있습니다. 디스크 어셈블리와 헤드 어셈블리 입니다. 디스크 어셈블리는 회전하는 디스크들이며, 디스크는 동심원 모양으로 그려진 트랙(track)으로 나뉩니다. 트랙은 다시 sector로 나뉘며, 비자기화된 틈으로 구분됩니다. 헤드 어셈블리는 디스크 헤드를 고정하는 역할을 하며, 헤드 표면에 매우 가깝지만 닿지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크들은 작은 프로세서인 디스크 컨트롤러에 의해 작동한다. 1. 헤드가 어떤 트랙을 가리킬지 2. 트랙의 어떤 섹터를 읽을지 3. 섹터와 메인 메모리 사이의 비트를 전달 4. 디스크 자체의 로컬 버퍼 역할, 이렇게 4가지 역할을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크를 읽는 작업은 3단계로 나뉜다. 1. 헤드 어셈블리를 원하는 트랙으로 옮기는 탐색 시간(seek time) 2. 섹터를 헤드 밑으로 이동시키는 회전 지연(rotational latency) 3. 섹터를 읽거나 쓰는 전송 시간(transfer time). 이 때, 전송시간은 전송률(transfer rate)이라는 시간당 읽기/쓰기 하는 블럭의 시간으로 계산된다. HDD의 전송률은 200MB/sec, SSD의 전송률은 5~6GB/sec 수준이다. 이 세 시간을 합친 시간을 디스크 접근 시간(disk access time)이라고 부른다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13.3 2차 저장소로의 접근시간 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 접근시간이 보통 10밀리초 정도라고 해서, 10밀리초마다 정보를 받아볼 수 있는것은 아니다. 이 주기를 앞당기는 기술을 소개한다. 디스크가 하나밖에 없어서, 모든 요청이 10밀리초 이상 걸리는 상황은 스케쥴링 지연(scheduling latency)가 무한대인 상황이다. 우리는 대역폭(throughput)을 늘리기 위해, 아래의 기술들을 사용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같이 접근되는 데이터들을 같은 디스크에 저장하여 탐색 시간과 회전 지연을 줄인다.&lt;/li&gt;
&lt;li&gt;데이터들을 하나의 큰 디스크가 아니라, 작은 여러개의 디스크에 분리한다. 여러개의 헤드 어셈블리가 독립적으로 접근하여 시간당 접근 블록 수를 늘릴 수 있다.&lt;/li&gt;
&lt;li&gt;데이터를 여러 디스크에 복제한다. 디스크에 문제가 생겼을 때에도, 데이터를 보호할 뿐만 아니라, 여러 블럭에 동시에 접근할 수 있게 해준다.&lt;/li&gt;
&lt;li&gt;디스크-스케쥴링 알고리즘을 이용하여, 읽거나 쓸 블럭의 순서를 조정한다.&lt;/li&gt;
&lt;li&gt;나중에 쓰일 데이터를 미리 메인메모리에 가져다둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 디스크에는 보통 4KB 단위로, 데이터들을 한꺼번에 읽는다. 이 데이터들이 랜덤하게 디스크에 배치된 Random I/O가 아니라, 연속적으로 배치된 Sequential I/O이기 때문에, 매 블럭마다 탐색 시간과 회전 지연을 소모하지 않고, 4KB 당 한번씩만 소모할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼(캐시)를 이용해서, 전체 실행시간을 개선할 수도 있다. 핵심은 디스크 읽기와 버퍼에서 실행하기 작업을 독립된 작업으로 보는 것이다. 첫번째 블럭의 데이터를 읽고, 실행하고, 다음 블럭을 읽고, 실행하고.. 를 반복하는 single buffering technique보다, 첫번째 블럭의 데이터를 실행하는 동안 두번째 블럭의 데이터를 읽기를 병렬적으로 동시에 실행하는 double buffering technique는 더 빠르다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13.4 디스크 실패&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 실패(Disk Failure)는 여러가지 이유로 발생한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가장 흔한 실패는 간헐적 실패(intermittent failure)이다. 읽기나 쓰기 시도가 실패해도, 다시 시도하면 성공 할 수 있다.&lt;/li&gt;
&lt;li&gt;심각한 실패는 비트의 영구적인 손상이 일어나는 것이다. 몇 번을 다시 시도해도, 읽기를 성공할 수 없기 때문이다. 이것을 미디어 부패(media decay)라고 부른다.&lt;/li&gt;
&lt;li&gt;쓰기 실패(write failure)는 쓰기를 시도할때 쓰기를 성공하지도 못하고, 이전에 쓴 섹터를 검색할 수도 없는 경우이다.&lt;/li&gt;
&lt;li&gt;가장 심각한 실패는 디스크 충돌(disk crash)이다. 모든 디스크가 영구적으로 갑자기 읽을수 없게 된 경우이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 실패들을 대비한 테크닉들이 있다. 간헐적 실패는 패리티체크(parity check)를 통해 보완한다. 미디어 부패와 쓰기 실패에 대비하여 안정적인 저장(stable storage) 전략을 사용할 수도 있다. 우리는 다음 내용에서, 디스크 충돌에 대비하는 RAID(Redundant Arrays of Independent) 전략에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAID는 데이터들을 여러 디스크에 나눠 저장하는 방법이다. 가장 기본적인 RAID level 0 (=RAID 0)이 그렇다. 디스크들이 병렬적으로 동작할 수 있어서, 하나의 디스크일때보다 읽기/쓰기 모두 빠르지만, 디스크 충돌에 대응이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 충돌에 대비하는 가장 쉬운 방법은, 디스크의 데이터를 복제하여, 디스크가 고장나도 다시 데이터를 복구할 수 있도록 하는 것이다. 이 것을 RAID level 1(=RAID 1)이라고 한다. 실패네 내성이 있지만, 데이터 디스크 수만큼 중복 디스크가 필요한 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAID 4는 데이터 디스크 2개 이상과 패리티 디스크 하나로 구성된다. 데이터들은 데이터 디스크들에 저장하되, 쓰기 작업을 할때마다 패리티 디스크에 오류 검출/데이터 복구에 필요한 패리티 비트들을 쓴다. 데이터 디스크 하나가 고장나도, 패리티 디스크 하나만으로 복원이 가능하지만, 매 쓰기 작업마다 패리티 디스크 쓰기 작업을 해야해서 병목이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAID 5는 데이터 디스크를 3개 이상 두고, 패리티 정보를 각 디스크에 나눠서 저장하는 것이다. RAID 4의 패리티 쓰기 병목 문제를 해결하지만, 두 개 이상의 디스크 충돌에 대응이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAID 10은 RAID 1과 0을 합한 전략이다. 최소 4개 이상의 디스크가 필요하다. RAID 0처럼 2개 이상의 디스크에 데이터를 나눠 저장하고, 각 디스크의 미러 디스크를 만드는 것이다. 그래서, 전체 디스크는 짝수개가 된다. 고성능/고안전성을 모두 확보하지만, 비싸다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13.5 디스크의 데이터 정렬&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 디스크가 데이터베이스를 저장하는 방식에 대해 알아본다. 튜플이나 객체는 레코드(record)라고 표현되며, 레코드는 디스크 블럭 내에 연속된 바이트로 저장된다. 이런 정렬 방식을 행-기반 레이아웃(row-oriented layout)이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 형태의 레코드는 고정 길이 필드(fixed-length field)로 구성된다. 필드는 튜플의 속성을 의미한다. 대부분의 시스템은 4나 8의 배수의 메모리 주소에서 값을 읽기 시작해야 효율적이므로, 각 필드들은 낭비하는 공간이 생기더라도 4/8의 배수번째 메모리에 저장된다. 어차피 데이터의 조작은 메인 메모리로 가져와서 일어나기 때문에, 2차 저장소인 디스크에서는 접근을 효율적으로 하는것이 가장 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레코드는 헤더를 포함한다. 헤더는 레코드 자체에 대한 정보를 저장하는 고정된 길이의 영역이다. 4가지 정보를 담는다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터스키마를 가리키는 포인터&lt;/li&gt;
&lt;li&gt;레코드의 길이&lt;/li&gt;
&lt;li&gt;레코드가 마지막으로 읽기/쓰기된 타임스탬프(트랜잭션 구현에 중요)&lt;/li&gt;
&lt;li&gt;필드에 대한 포인터&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 레코드들은 다시 블럭에 모인다. 블럭의 크기 또한 고정되어 있으므로, 레코드들을 저장하고 남는 공간은 버려진다. 또, 블럭은 블럭 헤더를 자체로 갖는다. 블럭 헤더에는 또 5가지 정보가 담긴다. 여기서 네트워크는 블럭들이 모여 구성하는 개념으로, 인덱스의 구현에 사용된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;네트워크 내 다른 블럭들과의 연결 정보&lt;/li&gt;
&lt;li&gt;네트워크에서 블록의 역할&lt;/li&gt;
&lt;li&gt;릴레이션 정보&lt;/li&gt;
&lt;li&gt;디렉토리 정보&lt;/li&gt;
&lt;li&gt;타임스탬프&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13.6 블럭과 레코드 주소 표현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 데이터베이스에서는 포인터, 즉 주소값이 레코드에 들어가는 경우가 많다. 포인터는 블럭이나 레코드의 주소를 가리킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블럭과 레코드의 주소는 두 가지로 표현될 수 있다. 첫번째는 물리 주소(physical address)로, 데이터가 있는 호스트, 디스크 id, 실린더 넘버, 트랙, 블럭, 오프셋 등의 구체적으로 데이터가 어딨는지를 나타낸다. 마치 &amp;ldquo;서울시 강남구 테헤란로 123, 5층 502호&amp;rdquo; 같은 집주소같다. 반면, 두번째 논리주소(logical address)는 &amp;ldquo;20180419&amp;rdquo;처럼 학번같은 개념이다. 이 학번을 가진 학생이 앉은 자리를 찾으려면 맵 테이플(Map table)을 참조하여 실제 물리 주소로 변환하는 과정을 거쳐야한다. 논리주소는 왜 필요한가? 물리주소는 매우 길고 복잡하지만, 논리주소는 간단하다. 그리고, 레코드들은 이동되거나 삭제될 수 있는데, 이 때 테이블의 내용을 수정하는 것이 포인터들을 수정하는것보다 쉽기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리 주소/논리 주소와 헷갈리기 좋은 데이터베이스 주소/메모리 주소의 개념이 있다. 데이터가 아직 메모리에 로딩되지 않았다면, 데이터는 데이터베이스 주소로 접근해야한다. 하지만, 데이터가 메모리에 로딩되었다면 데이터베이스 주소와 메모리 주소 모두 접근 가능한 주소이다. 앞서 따진 물리 주소/논리 주소는 데이터베이스의 주소를 표현하기 위한 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 입장에서 포인터를 참조할때 데이터베이스 주소를 참조하는 것보다는, 메모리 주소를 참조하는 것이 빠르다. 그래서, 데이터베이스 주소를 메모리 주소로 변환할 수 있도록 매핑해둔 값을 저장하는 번역 테이블(Translation table)이 필요하다. 이것을 포인터 스위즐링(pointer swizzling)이라 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Yqt9/btsODmmr4JJ/ms1OMoOre3LBmcTRzkfTQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Yqt9/btsODmmr4JJ/ms1OMoOre3LBmcTRzkfTQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Yqt9/btsODmmr4JJ/ms1OMoOre3LBmcTRzkfTQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Yqt9%2FbtsODmmr4JJ%2Fms1OMoOre3LBmcTRzkfTQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;390&quot; height=&quot;303&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서, 블럭1의 첫번째 레코드는 두개의 포인터를 갖고 있다. 이 레코드가 메모리에 올라갈 때, 포인터의 주소는 그대로 데이터베이스 주소를 갖는 것이 아니라, 메모리 주소를 가리킬수 있도록 스위즐(swizzle)되었다. 이때 메모리에 따라 로딩된 레코드를 고정(pinned)되었다고 한다. 번역 테이블에 새로운 행이 추가된 것이다. 반면, 블럭2는 아직 메모리에 올라가지 않았기 때문에 스위즐 되지 못했다. 나중에 블럭2가 올라갈 시점에 다시 그 포인터를 스위즐할지말지 정할 수 있게 되고 그것은 전략의 차이이다. 3가지 전략이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포인터가 메모리에 저장될 때, 참조할 값들을 전부 메모리에 올리고 번역 테이블에 추가하는 자동 스위즐링(automatic swizzling)&lt;/li&gt;
&lt;li&gt;포인터를 참조하려고 할 때, 그제서야 번역테이블에 매핑을 추가하는 on-demand swizzling&lt;/li&gt;
&lt;li&gt;하지 않는 no swizzling&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;No swizzling 전략은 고정된 레코드로 인한 비용 때문에 선택된다. 고정된(Pinned) 블럭은 디스크로 되돌려지는데에 많은 비용이 든다. 원래, 그 블럭을 가리키는 포인터가 가리키는 값이 없어져 외톨이 포인터(dangling pointer)가 되기 때문이다. 고정된 블럭을 디스크로 보낼때는 반드시 역-스위즐(unswizzle)과정을 거쳐야한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13.7 가변 길이의 데이터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 레코드의 스키마가 고정된 길이만을 가졌지만, 아래의 경우 그렇지 않을 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;255바이트를 할당 해둔 필드가 사실은 50바이트 정도에서 끝난다면, 남는 공간은 여유 공간으로 활용하는 것이 유리할 것이다.&lt;/li&gt;
&lt;li&gt;다대다 관계를 구현하려면, 반복적으로 포인터 값을 저장하게 된다.&lt;/li&gt;
&lt;li&gt;XML이나 JSON은 길이에 아무런 제약이 없다.&lt;/li&gt;
&lt;li&gt;오디오, 이미지, 영상 처럼 매우 큰 필드가 있을수도 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 필드들을 위해 일단 고정길이의 필드들은 가장 앞쪽에 배치해야 한다. 그리고, 레코드 헤더에 가변 길이의 필드가 시작하는 곳을 가리키는 포인터를 저장한다. 아래 그림에서는 address가 가변길이 필드이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSQ8m8/btsOBqjesGw/gwJfiK4BNIhhzZDvbCVF81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSQ8m8/btsOBqjesGw/gwJfiK4BNIhhzZDvbCVF81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSQ8m8/btsOBqjesGw/gwJfiK4BNIhhzZDvbCVF81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSQ8m8%2FbtsOBqjesGw%2FgwJfiK4BNIhhzZDvbCVF81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;461&quot; height=&quot;187&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은, 아예 가변길이의 필드를 별개의 블럭에 둘 수도 있다. 레코드를 탐색하고 이동시키는 비용은 줄지만, 디스크 I/O 횟수가 늘어난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8DIMW/btsOCxV3WQg/BE69zPh1JweJg9H6ri5K3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8DIMW/btsOCxV3WQg/BE69zPh1JweJg9H6ri5K3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8DIMW/btsOCxV3WQg/BE69zPh1JweJg9H6ri5K3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8DIMW%2FbtsOCxV3WQg%2FBE69zPh1JweJg9H6ri5K3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;361&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML 같은 가변길이의 레코드는 태그된 필드(tagged fields)들을 이용한다. 태그에는 필드의 이름, 필드의 타입, 필드의 길이 등이 포함된다. 아래 그림은 name과 restaurant owned 필드 정보를 갖는 XML을 저장한 결과이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yBUxc/btsOCLmtD05/dKaeuWWidjvEFstpFvXfwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yBUxc/btsOCLmtD05/dKaeuWWidjvEFstpFvXfwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yBUxc/btsOCLmtD05/dKaeuWWidjvEFstpFvXfwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyBUxc%2FbtsOCLmtD05%2FdKaeuWWidjvEFstpFvXfwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;186&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지나 오디오 처럼 아주 큰 레코드는 BLOB(Binary, Large OBject)로 불린다. 이 들은 여러개의 블럭에 걸쳐 저장된다(spanned record). 주로 연속적인 블럭에 배치되지만, 영상 재생 등의 이유로 BLOB을 매우 빠르게 검색해야할 수도 있다. 이럴 때는 BLOB을 여러 디스크에 나눠 저장하는 스트라이핑(striping)을 해야한다. 또, 영화의 특정 구간부터 요청 할 경우를 대비해서 적절한 인덱스 구조를 짜는것도 중요하다. 아래는 spanned record 의 모습이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qPZ1Q/btsOCidLBXK/bMVZ1b0m3caeoI2VD2RetK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qPZ1Q/btsOCidLBXK/bMVZ1b0m3caeoI2VD2RetK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qPZ1Q/btsOCidLBXK/bMVZ1b0m3caeoI2VD2RetK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqPZ1Q%2FbtsOCidLBXK%2FbMVZ1b0m3caeoI2VD2RetK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;587&quot; height=&quot;236&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/33</guid>
      <comments>https://junbyeol.tistory.com/33#entry33comment</comments>
      <pubDate>Mon, 16 Jun 2025 14:33:51 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스 개론] 5. 데이터베이스의 언어 SQL(The Database Language SQL)</title>
      <link>https://junbyeol.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SQL은 관계형 데이터베이스에서 널리 쓰이는 쿼리언어이다. SQL은 쿼리의 기능 뿐 아니라 데이터를 수정하는 DML(Data Manipulation Language)의 기능과 스키마를 정의하는 DDL(Data Definition Language)의 기능도 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL도 다양한 버전이 있다. ANSI, SQL-92(SQL2, SQL:1992), SQL-99(SQL3, SQL:1999), SQL:2023 등이다. 이 글에서 다루는 것은 SQL-92이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.1 Simple Queries in SQL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT, FROM, WHERE을 가지고 기초적인 쿼리를 할 수 있다. 관계대수(relational algebra)로 치자면, SELECT는 사영(projection) 조건, WHERE은 선택(selection) 조건을 담당한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SELECT&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT * FROM Movies WHERE year = 1990 and studioName = &amp;lsquo;Disney&amp;rsquo;;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AS로 alias를 지정할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT title AS name, length AS duration FROM&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산을 하거나, 상수를 넣을 수도 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT title AS name, length * 0.01667 AS lengthInHours, &amp;lsquo;hrs.&amp;rsquo; AS inHours FROM &amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WHERE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 언어처럼 다양한 조건문을 넣을 수 있다. 숫자 뿐 아니라 문자열도 연산 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부등호: =, &amp;gt;, &amp;lt;, &amp;lt;&amp;gt;, &amp;hellip;&lt;/li&gt;
&lt;li&gt;산수: +, *, &amp;hellip;&lt;/li&gt;
&lt;li&gt;문자열: || (concatenation)&lt;/li&gt;
&lt;li&gt;논리: AND, OR, NOT&lt;/li&gt;
&lt;li&gt;패턴매칭: LIKE,
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;s는 문자열, p는 패턴일때, s LIKE p로 표현한다.&lt;/li&gt;
&lt;li&gt;p에는 %와 _를 넣어 표현한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;%: 길이가 0 또는 1 이상의 문자열&lt;/li&gt;
&lt;li&gt;_: 딱 한글자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Values&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DATE: (키워드 DATE) + &amp;lsquo;yyyy-MM-dd&amp;rsquo;형태&lt;/li&gt;
&lt;li&gt;TIME: (키워드 TIME) + &amp;lsquo;HH:mm:ss&amp;rsquo; 형태&lt;/li&gt;
&lt;li&gt;TIMESTAMP: (키워드 TIMESTAMP) + &amp;lsquo;yyyy-MM-dd HH:mm:ss&amp;rsquo; 형태&lt;/li&gt;
&lt;li&gt;NULL
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3가지 해석을 할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정말 값을 알수 없을때&lt;/li&gt;
&lt;li&gt;값이 들어있는 것이 부적절할 때&lt;/li&gt;
&lt;li&gt;값을 알 수 있는 권한이 없을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;만약 이 NULL이 WHERE문 안에서 연산이 된다면, 다음을 따른다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;*나 + 같은 산수 결과는 NULL이다.&lt;/li&gt;
&lt;li&gt;나 = 같은 비교 결과는 BOOLEAN 타입인 UNKNOWN이다.&lt;/li&gt;
&lt;li&gt;참고로, NULL이 상수로 쓰일 순 없다. x+3을 연산할때 x가 NULL일수는 있지만, NULL+3은 틀린 SQL 문법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UNKNOWN: 논리적으로 이해하면 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AND 연산
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(UNKNOWN) AND (TRUE)는 UNKNOWN이다. (순서가 달라져도)&lt;/li&gt;
&lt;li&gt;(UNKNOWN) AND (FALSE)는 FALSE이다. (순서가 달라져도)&lt;/li&gt;
&lt;li&gt;(UNKNOWN) AND (UNKNOWN)은 UNKNOWN이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OR 연산
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(UNKNOWN) OR (TRUE)는 TRUE이다. (순서가 달라져도)&lt;/li&gt;
&lt;li&gt;(UNKNOWN) OR (FALSE)는 UNKNOWN이다. (순서가 달라져도)&lt;/li&gt;
&lt;li&gt;(UNKNOWN) OR (UNKNOWN)은 UNKNOWN이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NOT 연산
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NOT (UNKNOWN)은 UNKNOWN이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;만약 WHERE 문의 표현식 전체 결과가 UNKNOWN이면, 해당 튜플은 쿼리 결과에 포함되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ORDER BY&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY는 순서를 정렬한다. FROM과 WHERE이 다 실행된 후, SELECT 직전에 연산된다. 디폴트는 오름차순(ASC)이다. 표현식이 들어갈수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT * FROM R ORDER BY A+B DESC;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.2 Queries Involving More Then One Relation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개 이상의 릴레이션에 동시에 쿼리할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkOisa/btsODkBDsp9/ixiNtEztHllYV0GdQZpkPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkOisa/btsODkBDsp9/ixiNtEztHllYV0GdQZpkPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkOisa/btsODkBDsp9/ixiNtEztHllYV0GdQZpkPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkOisa%2FbtsODkBDsp9%2FixiNtEztHllYV0GdQZpkPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;834&quot; height=&quot;158&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히, 이 경우는 Movies와 MovieExec 사이에 겹치는 이름의 속성이 없었다. 만약 있다면 명시적으로 구분해줘야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OK59y/btsOB9HSIbR/FThlIcNkApdkqRkCI243W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OK59y/btsOB9HSIbR/FThlIcNkApdkqRkCI243W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OK59y/btsOB9HSIbR/FThlIcNkApdkqRkCI243W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOK59y%2FbtsOB9HSIbR%2FFThlIcNkApdkqRkCI243W1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;138&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어는 릴레이션 안에서 서로 다른 튜플끼리 연산을 시킬수도 있다.&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot; data-token-index=&quot;1&quot;&gt;Star1.name &amp;lt; Star2.name&lt;/span&gt;&lt;/b&gt; 절은 순서만 바뀐 두 튜플이 서로 다른 결과로 나오는 것을 막기 위해 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHIOY6/btsODGYNWAQ/iw2ggd399Ry9nIQkYoi4Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHIOY6/btsODGYNWAQ/iw2ggd399Ry9nIQkYoi4Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHIOY6/btsODGYNWAQ/iw2ggd399Ry9nIQkYoi4Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHIOY6%2FbtsODGYNWAQ%2Fiw2ggd399Ry9nIQkYoi4Xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;194&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 SELECT-FROM-WHERE 쿼리 결과 간의 연산도 할 수 있다. 순서대로 교집합, 차집합, 합집합이다. 만약 왼편의 속성은 title, 오른편의 속성은 movieTitle이라면 &lt;b&gt;movieTitle as title&lt;/b&gt; 로 alias해주어야 의도대로 동작할 것이다. 이때, 실행결과는 bags가 아닌 집합이다. 중복이 제거된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(select-from-where) INTERSECT (select-from-where)&lt;/li&gt;
&lt;li&gt;(select-from-where) EXCEPT (select-from-where)&lt;/li&gt;
&lt;li&gt;(select-from-where) UNION (select-from-where)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.3 Subqueries&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 쿼리의 결과를 하나의 릴레이션처럼 사용해서 INTERSECT같은 연산의 피연산자(operand)로 사용하는 예시를 보았다. (select-from-where)문을 괄호로 감싸면, 서브쿼리(subquery)로 사용가능하다. 서브쿼리의 결과가 무엇이냐에 따라, 3가지 사용방법이 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 하나의 상수를 반환할 때&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkgAtW/btsODjQhvtg/6A5OthX6kkbgNZVV7X6uBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkgAtW/btsODjQhvtg/6A5OthX6kkbgNZVV7X6uBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkgAtW/btsODjQhvtg/6A5OthX6kkbgNZVV7X6uBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkgAtW%2FbtsODjQhvtg%2F6A5OthX6kkbgNZVV7X6uBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;282&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 서브쿼리는 정확히 하나의 상수만을 반환해야한다. 만약 하나의 상수만을 반환했다면, 그 값으로 서브쿼리 전체를 치환하면 된다. 그러나 반환된 상수가 없거나 두개 이상이면 런타임 에러를 발생시킨다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 컬럼 1개짜리 릴레이션을 반환할때&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE문 안에서 연산자와 함께 사용되며 BOOLEAN값을 반환할 수 있다. 서브쿼리 결과 R에 대한, 새로운 연산자들을 소개한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EXISTS R: R이 비어있지 않으면 TRUE&lt;/li&gt;
&lt;li&gt;s IN R: s 라는 값이 R에 존재하면 TRUE&lt;/li&gt;
&lt;li&gt;s NOT IN R: s 라는 값이 R에 없으면 TRUE&lt;/li&gt;
&lt;li&gt;s &amp;gt; ALL R: 모든 R의 값이 s 보다 작으면 TRUE&lt;/li&gt;
&lt;li&gt;s &amp;gt; ANY R: 어떤 R의 값이 s보다 작으면 TRUE&lt;/li&gt;
&lt;li&gt;EXISTS, ALL, ANY는 NOT을 붙여서 부정할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 컬럼 2개 이상의 릴레이션을 반환할때&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 (2)에서 말한 연산자 중 IN 을 사용할 수 있다. 대신 s와 R의 속성 개수가 맞아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 알아본 서브쿼리들은 일단 쿼리를 실행한 후, 나온 결과를 부모 쿼리에 치환해두면 끝나는 것처럼 보였다. 그러나, 지금 소개할 &amp;ldquo;Correlated subquery&amp;rdquo;는 부모 쿼리의 검사하려는 튜플마다 매번 새롭게 연산해야하는 경우이다. 서브쿼리의 두번째에 등장한 title 앞에 Old. 를 붙여서 scope를 명시한 것을 주의해서 봐야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buKGwY/btsOB6RUcCW/xKXtGKqTHohPo2NKeToAUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buKGwY/btsOB6RUcCW/xKXtGKqTHohPo2NKeToAUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buKGwY/btsOB6RUcCW/xKXtGKqTHohPo2NKeToAUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuKGwY%2FbtsOB6RUcCW%2FxKXtGKqTHohPo2NKeToAUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;288&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브쿼리를 FROM 문에서 사용할 수도 있다. 반드시 alias를 해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vmgNy/btsOBsufSRS/IKU7fDvoOTlkdzCAP7G2bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vmgNy/btsOBsufSRS/IKU7fDvoOTlkdzCAP7G2bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vmgNy/btsOBsufSRS/IKU7fDvoOTlkdzCAP7G2bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvmgNy%2FbtsOBsufSRS%2FIKU7fDvoOTlkdzCAP7G2bK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;226&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/32</guid>
      <comments>https://junbyeol.tistory.com/32#entry32comment</comments>
      <pubDate>Mon, 16 Jun 2025 13:04:17 +0900</pubDate>
    </item>
    <item>
      <title>크롬 익스텐션 개발 아이디어: 드롭다운에서 한국을 찾아주는 KoreaDropdown</title>
      <link>https://junbyeol.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://news.hada.io/topic?id=21396&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;긱뉴스&lt;/a&gt;에 소개된 크롬 익스텐션, &lt;a href=&quot;https://dropdown.ilez.xyz&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KoreaDropdown&lt;/a&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3284&quot; data-origin-height=&quot;1582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR1SpL/btsOx4sr9hv/naJBQ7KNhonqMb2dlwkIv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR1SpL/btsOx4sr9hv/naJBQ7KNhonqMb2dlwkIv1/img.png&quot; data-alt=&quot;랜딩 페이지. 이 익스텐션이 다룬 문제와 해결 방법을 간단하지만 확실하게 보여준다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR1SpL/btsOx4sr9hv/naJBQ7KNhonqMb2dlwkIv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR1SpL%2FbtsOx4sr9hv%2FnaJBQ7KNhonqMb2dlwkIv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;775&quot; height=&quot;373&quot; data-origin-width=&quot;3284&quot; data-origin-height=&quot;1582&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;랜딩 페이지. 이 익스텐션이 다룬 문제와 해결 방법을 간단하지만 확실하게 보여준다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드롭다운 메뉴에서 한국을 찾기 어려운, 많은 사람들이 공감할 만한 경험을 재치 있게 해결해주는 간단하지만 인상적인 익스텐션이다. 특히 인상 깊었던 점 몇 가지를 적어본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;크롬 익스텐션의 랜딩 페이지&lt;/h4&gt;
&lt;p data-end=&quot;392&quot; data-start=&quot;182&quot; data-ke-size=&quot;size16&quot;&gt;작은 규모의 익스텐션임에도 별도의 랜딩 페이지를 제공한다는 점이 제품의 완성도를 한층 끌어올린 느낌이었다. 이 익스텐션이 무엇을 하는지 바로 보여주고, 사용자가 체험해볼 수 있도록 한 뒤 &quot;지금 무료로 설치하기&quot; 버튼을 통해 크롬 웹스토어로 자연스럽게 연결되는 플로우가 인상적이었다. 수직 플렉스박스 구조로 구성된 랜딩 화면에는 자주 묻는 질문과 후원 링크까지 포함되어 있다.&lt;/p&gt;
&lt;h4 data-end=&quot;392&quot; data-start=&quot;182&quot; data-ke-size=&quot;size20&quot;&gt;익스텐션 자체의 재치&lt;/h4&gt;
&lt;p data-end=&quot;612&quot; data-start=&quot;414&quot; data-ke-size=&quot;size16&quot;&gt;드롭다운 메뉴에서 한국이 &quot;South Korea&quot;, &quot;Korea, South&quot;, &quot;Republic of Korea&quot; 등 다양한 이름으로 표기되어 있어, 수많은 나라 이름 중에서 한국을 찾는 것이 번거롭고 불편하다는 &amp;mdash; 일종의 밈처럼 여겨지는 &amp;mdash; 한국 한정의 짜증나는 UX 문제를 다뤘다는 것이 이목을 끈다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;707&quot; data-start=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;제품 자체는 구현 난이도나 분량 면에서 가볍고 간단하지만, 랜딩 페이지까지 포함해 하나의 완결된 형태로 잘 정리된 제품 개발 사례라는 생각이 들어 이렇게 글로 남겨둔다.&lt;/p&gt;</description>
      <category>생각과 기록</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/31</guid>
      <comments>https://junbyeol.tistory.com/31#entry31comment</comments>
      <pubDate>Thu, 12 Jun 2025 02:11:52 +0900</pubDate>
    </item>
    <item>
      <title>윈도우 PC와 맥북을 듀얼모니터로 함께 사용하던 책상 근황</title>
      <link>https://junbyeol.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junbyeol.tistory.com/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://junbyeol.tistory.com/4&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1747750737640&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;윈도우 PC와 맥북을 듀얼모니터로 함께 사용하기&quot; data-og-description=&quot;안녕하세요. 저는 게임을 좋아하는 백엔드 개발자로서, 줄곧 맥북과 윈도우 데스크탑을 함께 사용하고 싶은 욕구가 있었습니다.그리고, 최근에 꽤나 만족스러운 퀄리티로 이 둘을 모두 사용하&quot; data-og-host=&quot;junbyeol.tistory.com&quot; data-og-source-url=&quot;https://junbyeol.tistory.com/4&quot; data-og-url=&quot;https://junbyeol.tistory.com/4&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eoBfgI/hyYYEoVoPY/Adi0kRqxX8DID5wgfJA0wK/img.png?width=800&amp;amp;height=599&amp;amp;face=0_0_800_599,https://scrap.kakaocdn.net/dn/8TxeX/hyYTeeMtmd/JNdrTSesCUutoDWs7VqGnK/img.png?width=800&amp;amp;height=599&amp;amp;face=0_0_800_599,https://scrap.kakaocdn.net/dn/AVyWD/hyYW4IkfUI/zOlhAQDZKO7GF01DdjPFek/img.jpg?width=1411&amp;amp;height=1058&amp;amp;face=0_0_1411_1058&quot;&gt;&lt;a href=&quot;https://junbyeol.tistory.com/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junbyeol.tistory.com/4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eoBfgI/hyYYEoVoPY/Adi0kRqxX8DID5wgfJA0wK/img.png?width=800&amp;amp;height=599&amp;amp;face=0_0_800_599,https://scrap.kakaocdn.net/dn/8TxeX/hyYTeeMtmd/JNdrTSesCUutoDWs7VqGnK/img.png?width=800&amp;amp;height=599&amp;amp;face=0_0_800_599,https://scrap.kakaocdn.net/dn/AVyWD/hyYW4IkfUI/zOlhAQDZKO7GF01DdjPFek/img.jpg?width=1411&amp;amp;height=1058&amp;amp;face=0_0_1411_1058');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;윈도우 PC와 맥북을 듀얼모니터로 함께 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 저는 게임을 좋아하는 백엔드 개발자로서, 줄곧 맥북과 윈도우 데스크탑을 함께 사용하고 싶은 욕구가 있었습니다.그리고, 최근에 꽤나 만족스러운 퀄리티로 이 둘을 모두 사용하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junbyeol.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 글을 작성한지 2년이 지난 지금 제 책상 상태를 공개합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1411&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btlw1d/btsN32pPFiD/ODj4JCAIQA7PehnlbupwL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btlw1d/btsN32pPFiD/ODj4JCAIQA7PehnlbupwL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btlw1d/btsN32pPFiD/ODj4JCAIQA7PehnlbupwL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtlw1d%2FbtsN32pPFiD%2FODj4JCAIQA7PehnlbupwL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1411&quot; height=&quot;1058&quot; data-origin-width=&quot;1411&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2년 사이에 이사를 하여, 컴퓨터 책상 배경이 달라졌고, 깔끔헀던 선정리가 다시 엉망으로 돌아갔습니다. 이것들은 논외로 하고, 다른 변경점들을 소개합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모니터암과 모니터 받침대&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 카멜마운트의 듀얼모니터암 제품을 사용하고 있었는데, 지금은 사용하고 있지 않습니다. 제가 모니터암을 쓰면서 기대했던 효과는 이런 것이었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모니터 아래쪽 책상 공간을 활용&lt;br /&gt;-&amp;gt; 나름 효과를 봤음&lt;/li&gt;
&lt;li&gt;모니터 각도를 틀어서 근처 침대나 쇼파에서 모니터 보기&lt;br /&gt;-&amp;gt; 당시 원룸에 거주해서, 유효한 사용방법이였으나, 1.5룸으로 이사하게 되면서 이렇게 사용할 일이 없어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불편한 점들도 여럿 겪었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;심미적으로 보기 안좋음&lt;br /&gt;모니터암과 책상의 연결부가 썩 시각적으로 깔끔해 보이지 않았습니다.&lt;/li&gt;
&lt;li&gt;모니터를 움직일 일이 거의 없음&lt;/li&gt;
&lt;li&gt;책상을 벽에 딱 붙이기 못하고, 모니터암이 있을 공간 확보가 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터암 모델을 바꾸면 해결될수도 있나 싶지만, 차라리 모니터 받침대 위에 모니터를 올려두고 사용하는 것이 더 편리할 것 같았습니다. 모니터 받침대로도, 모니터 공간과 책상공간이 분리되어, 공간을 꽤나 깔끔하고 여유있게 쓸 수 있었습니다. 또. 받침대의 위나 아래에 추가적인 수직 공간이 생겨 지저분하게 책상에 널려있는 주변기기들의 자리를 찾아줄 수 있게 되었습니다. 모니터암을 쓸때보다 시각적으로도 더 안정감 있어 보입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가된 주변기기들&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 6.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MHo0P/btsN6o5C7Uf/SMl2kHbG78Ow0jZocdxwX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MHo0P/btsN6o5C7Uf/SMl2kHbG78Ow0jZocdxwX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MHo0P/btsN6o5C7Uf/SMl2kHbG78Ow0jZocdxwX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMHo0P%2FbtsN6o5C7Uf%2FSMl2kHbG78Ow0jZocdxwX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;395&quot; data-filename=&quot;Frame 6.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2년전과 달리 각종 휴대기기 충전기나 탁상용 선풍기/시계, 게임용 조이스틱, 블루투스 동글 등 컴퓨터 책상에 두고 사용하고 싶은 것들이 늘어났습니다. 이 주변기기들을 연결하기 위해 USB 허브를 구매했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BU4o5/btsN5IQ2UUQ/Iu5z29FmvwW6sB3azSGjs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BU4o5/btsN5IQ2UUQ/Iu5z29FmvwW6sB3azSGjs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BU4o5/btsN5IQ2UUQ/Iu5z29FmvwW6sB3azSGjs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBU4o5%2FbtsN5IQ2UUQ%2FIu5z29FmvwW6sB3azSGjs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;356&quot; height=&quot;335&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전력을 많이 필요로 하는 기기들 위주라, 일부러 외부 전력을 추가로 요구하는 허브로 선택했습니다. 그리고, usb 포트가 충분히 많은 제품으로 선택했습니다.&lt;/p&gt;</description>
      <category>일상이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/30</guid>
      <comments>https://junbyeol.tistory.com/30#entry30comment</comments>
      <pubDate>Tue, 20 May 2025 23:42:57 +0900</pubDate>
    </item>
    <item>
      <title>TailwindCss로 도막도막 끊기는 스크롤 화면 구현하기: Full Page Scroll과 Scroll snap</title>
      <link>https://junbyeol.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트들을 돌아다니다보면, 내린만큼 화면이 스크롤 되는 것이 아니라 특정구간마다 탁탁 걸리면서 정지하는 스크롤을 이용한 사이트들이 많이 보인다. &lt;a href=&quot;https://alvarotrigo.com/fullPage/#page1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 사이트&lt;/a&gt;는 스크롤을 내릴때마다 화면 전체의 배경색이 바뀌면서 극적인 효과를 준다. 눈으로 보면 쉽지만, 말로 설명하자면 어려운 이런 스크롤 방식을 &quot;scroll snap&quot;이라고 부른다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Scroll Snap&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS의 scroll-snap-type을 이용해서 구현된다. 스크롤이 멈출 수 있는 지점을 지정하는 것이다. 이 멈춤 지점을 앵커(anchor)라고 부른다. 예를 들어, &lt;a href=&quot;https://tailwindcss.com/docs/scroll-snap-align&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tailwind&lt;/a&gt; 에서는 가로로 이미지를 스크롤하는 예시를 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xq1b7/btsN2qoSC4f/RWkD2pGVtcYrTK23UZSuxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xq1b7/btsN2qoSC4f/RWkD2pGVtcYrTK23UZSuxK/img.png&quot; data-alt=&quot;가로로 화면을 스크롤하면, 이미지의 중간 지점에서만 스크롤이 멈춘다. 위의 tailwind 링크를 눌러 해 볼 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xq1b7/btsN2qoSC4f/RWkD2pGVtcYrTK23UZSuxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxq1b7%2FbtsN2qoSC4f%2FRWkD2pGVtcYrTK23UZSuxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;702&quot; height=&quot;381&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가로로 화면을 스크롤하면, 이미지의 중간 지점에서만 스크롤이 멈춘다. 위의 tailwind 링크를 눌러 해 볼 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 용어로 &quot;Full Page Scroll&quot;이라는 표현이 있다. 이는 scroll snap을 활용해 구현되며, 페페이지 전체가 스크롤할 때마다 한 화면씩 전환되는 형식이다. 글의 초반에 링크한 &lt;a href=&quot;https://alvarotrigo.com/fullPage/#page1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 사이트&lt;/a&gt;가 그 예시이다. 이러한 Full Page Scroll 형식의 사이트들을 보면, 대개 주소에 #pageN와 같이 #으로 시작하는 식별자가 달려있다. 이것이 바로 앵커이며, 이 앵커 값을 포함한 주소를 브라우저에 전달하면, 특정 화면으로 스크롤된 상태로 바로 진입할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오해할 수 있는 용어로 &quot;smooth scroll&quot;이 있다. Smooth scoll은 scroll snap과 함께 자주 사용되지만, 다른 개념이다. CSS에서는 'scroll-behavior' 를 통해 구현한다. a 태그안에 앵커를 포함한 주소를 심어, 사용자가 링크를 누르면 특정 화면으로 이동시키는 기능을 구현하고자 할 때, 해당 화면으로 곧장 전환되는게 아니라 부드러운 트랜잭션으로 전환시키는 기능이다. &lt;a href=&quot;https://www.w3schools.com/howto/howto_css_smooth_scroll.asp#section2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 사이트&lt;/a&gt;를 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Full Page Scroll을 구현하는 방법을 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라이브러리 이용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Full Page 기능을 쉽게 이용할 수 있게 해주는 라이브러리들이 많이 있다. 나도 이 기능이 들어간 사이트를 이번에 구현해보면서 조사해보았다. 결론부터 말하자면, 이거다! 싶은 라이브러리를 찾지 못하여, 직접 구현했다. 사실 라이브러리를 꼭 써야할 정도로 구현난이도가 높지도 않다고 생각한다. 탐색해본 라이브러리들을 소개한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alvarotrigo/fullpage.js?tab=readme-ov-file&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;fullpage.js&lt;/a&gt;&lt;br /&gt;라이브러리를 사용하고 싶다면, 이 도구가 적절한 선택이 될 수 있다. 특히 lazy-loading처럼 페이지에 동영상 등의 리소스가 많이 포함된 경우에는 유용하게 활용할 수 있다. 다만, 이 라이브러리는 React element에 CSS 속성을 암묵적으로 추가한다는 점에서 아쉬움이 있었다.&lt;br /&gt;구현 방식상 class=&quot;section&quot;이라는 CSS selector를 기준으로 다양한 스타일 속성을 설정하는데, 이로 인해 원하지 않는 영역에 Flex 속성이나 가운데 정렬 같은 스타일이 적용되는 문제가 발생했다. 만약 이러한 스타일을 피하고 싶다면 overrides.css 같은 별도의 파일을 만들어 더 강한 selector로 덮어써야 한다. 이는 나에게 번거로울 뿐 아니라, 어떤 속성이 자동으로 추가되었는지 명확히 알기 어려워 찝찝하게 느껴졌다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@fullpage/react-fullpage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@fullpage/react-fullpage&lt;/a&gt;&lt;br /&gt;위 fullpage.js를 react component로 쓰기 좋게 감싼 공식 라이브러리라고 한다. 확실히 react에서 쓰기 좋게 간결한 docs와 문법을 갖고 있어서, 금방 적용할 수 있었다. 그러나 여전히 fullpage.js의 단점을 답습하고 있거니와, 유료라서 무료로 사용하면 사이트에 &quot;Made by @fullpage/react-fullpage&quot;같은 플로팅이 떠다녀서 거슬린다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-fullpage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react-fullpage&lt;/a&gt;&lt;br /&gt;Deprecated되었다. 쓰지말자. 그럼에도 불구하고 이 글에서 소개한 이유는, GPT 한테 full page scroll 구현하는 법을 물어보면 이 라이브러리를 알려준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;직접 TailwindCSS로 구현하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 남긴다.&lt;/p&gt;
&lt;pre id=&quot;code_1747463620474&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function App() {
  return (
    &amp;lt;div className=&quot;min-h-screen w-full&quot;&amp;gt;
      &amp;lt;main className=&quot;container h-screen w-full overflow-y-scroll snap-y snap-mandatory scroll-smooth mx-auto&quot;&amp;gt;
        &amp;lt;Section1 /&amp;gt;
        &amp;lt;Section2 /&amp;gt;
      &amp;lt;/main&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

function Section1() {
  return (
    &amp;lt;section id=&quot;section_1&quot; className=&quot;h-screen w-full snap-center&quot;&amp;gt;
        &amp;lt;div&amp;gt;
        ...
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
   );
 }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;snap-mandatory: 반드시 지정한 지점에서만 스크롤이 멈추게 한다. 약간 유도리 있게 snap-proximity로 설정할수도 있다.&lt;/li&gt;
&lt;li&gt;scroll-smooth: 글 초반에 설명했듯이, 글 내에서 a 태그 등으로 앵커가 변경되어 보여지는 화면이 달라져야할때, 부드럽게 이동한다.&lt;/li&gt;
&lt;li&gt;id=&quot;section_1&quot;: 이렇게 id를 설정하면 /#section_1의 앵커로 이 화면에 접근할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 이렇게 하면 문제가 있는것이, 화면 오른쪽에 못생긴 스크롤 바가 생긴다. 이것은 스택오버플로우에서 긁어온 아래의 코드를 index.css 혹은 다른 import하는 css 파일에 추가함으로써 해결한다.&lt;/p&gt;
&lt;pre id=&quot;code_1747463812611&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.container {
    -ms-overflow-style: none;  /* Internet Explorer 10+ */
    scrollbar-width: none;  /* Firefox */
}
.container::-webkit-scrollbar { 
    display: none;  /* Safari and Chrome */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, 아래와 같은 니즈가 더 있을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금 보여지고 있는 화면이 무엇인지 알고 싶음.&lt;/li&gt;
&lt;li&gt;특정화면이 보여질때 실행되는 훅을 만들고 싶음.&lt;/li&gt;
&lt;li&gt;사이트에 진입할때, 모든 화면을 랜더링 해두지 않고, 그 화면이 보여야할때 랜더링 하는 lazy-loading을 구현하고 싶음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때에는 IntersectionObserver API를 활용하여 구현할 수 있다. 다음에 또 다른 글로 소개해보겠다.&lt;/p&gt;</description>
      <category>개발이야기</category>
      <category>프론트엔드</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/29</guid>
      <comments>https://junbyeol.tistory.com/29#entry29comment</comments>
      <pubDate>Sat, 17 May 2025 15:37:11 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스 개론] 4. 대수적/논리적 쿼리 언어(Algebraic and Logical Query Language)</title>
      <link>https://junbyeol.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 챕터는 두개의 추상적인 언어를 다룬다. 하나는 대수적(algebraic)인것, 하나는 논리적인것(logic-based)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대수적인 것은 챕터2에서 다룬 관계대수학과 같지만, 그 범위를 확장할 것이다. 챕터2에서는 집합에 대해서 연산했다면, 이 챕터에서는 중복을 허용한 Bags로 연산할 것이다. 논리적인 언어는 데이터로그(Datalog)라고 부른다. 우리는 원하는 결과를 구하는 알고리즘을 찾기보다, 우리가 원하는 쿼리를 표현하는데에 집중할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.1 Relational Operations on Bags&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말했듯이 Bags는 집합에서 같은 튜플의 중복을 허용한 것이다. 왜 Bags를 사용할까? 두 릴레이션의 합집합을 구할때, 중복되는 원소를 제거하는 연산을 하지 않아도 된다. 그저 두 릴레이션을 복사해서 합하면 그만이다. 릴레이션을 사영(projection)시킬때도 마친가지이다. 불필요한 컬럼을 제거하기만 하면 그만이다. 혹은, 집합에서와 bags에서 어떤 컬럼의 평균을 구한다고 하면, 두 결과는 다를 것이고 우리가 원하는 결과가 후자일 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 원래 우리가 알던 관계대수적 연산들이 어떻게 바뀌는지 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 집합 연산들이다. 튜플 t가 두 릴레이션 R과 S에서 각각 n개, m개 들어있다고 하면, 각 연산의 결과는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;합집합 RuS: t가 n+m개&lt;/li&gt;
&lt;li&gt;교집합 RnS: t가 min(n,m)개&lt;/li&gt;
&lt;li&gt;차집합 R-S: t가 max(0, n-m)개&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;1128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7U4Wf/btsNvcdCnfr/yF5MvV5lgpnjFDJbfOdrL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7U4Wf/btsNvcdCnfr/yF5MvV5lgpnjFDJbfOdrL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7U4Wf/btsNvcdCnfr/yF5MvV5lgpnjFDJbfOdrL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7U4Wf%2FbtsNvcdCnfr%2FyF5MvV5lgpnjFDJbfOdrL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;412&quot; height=&quot;1128&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;1128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선택(Selection): 중복을 처리하지 않을뿐, 기존 연산과 동일&lt;/li&gt;
&lt;li&gt;조인(Join): 중복을 처리하지 않을뿐, 기존 연산과 동일&lt;/li&gt;
&lt;li&gt;곱(Product): 중복을 처리하지 않을뿐, 기존연산과 동일. R과 S의 튜플 개수의 곱 만큼의 row가 생길 것이다.&lt;/li&gt;
&lt;li&gt;사영(Projection): 원래 사영은 컬럼들 중 우리가 원하는 컬럼들 만을 고르고, 관심밖의 컬럼은 삭제하는 연산이였다. 이제는 그 기능이 확장되어, 컬럼의 이름을 바꿀 수 있다. 또한, 두 컬럼을 조합하여 새로운 연산을 만드는 등의 연산도 가능하다. 아래의 표현은 A컬럼은 유지하고, B 와 C 컬럼은 합쳐서 X 라는 새로운 컬럼을 만들라는 표현식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BmbKN/btsNvIwy6ju/qJFHErNlHJCwBAu07d2WEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BmbKN/btsNvIwy6ju/qJFHErNlHJCwBAu07d2WEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BmbKN/btsNvIwy6ju/qJFHErNlHJCwBAu07d2WEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBmbKN%2FbtsNvIwy6ju%2FqJFHErNlHJCwBAu07d2WEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;56&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.2 Extended Operators of Relational Algebra&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bags가 생김에 따라 정의할 수 있게된 새로운 연산자들을 소개한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복 제거 연산자(Duplicate-elimination operator): &lt;span style=&quot;background-color: #f5f5f5; color: #212529; text-align: start;&quot;&gt;&amp;delta;(R)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bags의 중복을 제거해서 Set으로 바꾼다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;집계 연산자(Aggregation operator)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SUM, AVG, MIN, MAX 등 어느 한 컬럼을 요약해서 하나의 값을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분류 연산자(Grouping operator): &lt;span style=&quot;background-color: #f5f5f5; color: #212529; text-align: start;&quot;&gt;&amp;gamma;(R)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;튜플들을 종류에 따라 묶는다. Grouping이 이루어지면, 새로운 튜플들로 묶인 튜플들이 표현된다. 새로운 튜플은 묶은 기준이 된 속성들과 aggregation된 기존 속성들, 두 종류로 표현된다. 아래는 릴레이션을 starName으로 묶고, year와 title을 각각 aggregate하라는 표현식이다. starName, minYear, ctTitle이 새로운 튜플의 속성들이 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XNkDh/btsNuZl2c0L/WnvuncP8UyJkFuED3A2R01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XNkDh/btsNuZl2c0L/WnvuncP8UyJkFuED3A2R01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XNkDh/btsNuZl2c0L/WnvuncP8UyJkFuED3A2R01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXNkDh%2FbtsNuZl2c0L%2FWnvuncP8UyJkFuED3A2R01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;674&quot; height=&quot;88&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정렬 연산자(Sorting operator): &lt;span style=&quot;background-color: #f5f5f5; color: #212529; text-align: start;&quot;&gt;&amp;tau;(R)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;튜플들을 순서에 맞게 정렬한다. 이 연산의 결과물은 튜플의 집합이 아니라, 순서가 있는 배열이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;외부조인 연산자(Outerjoin operator)&lt;br /&gt;외톨이 튜플(Dangling tuple)을 제거하지 않는 변형된 조인 연산자이다. 기존 조인은 다른 릴레이션과 공통 속성이 없으면, 버려지는 튜플이 생겼다. 이것을 외톨이 튜플이라 부른다. 두 릴레이션 R과 S에 대하여 아래의 연산들을 할 수 있다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Left Outer Join: R의 외톨이 튜플을 남기고, 빈 속성은 NULL로 남긴다, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;(R &lt;span style=&quot;color: #202122; text-align: start;&quot;&gt;⟕ S)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Right Outer Join: S의 외톨이 튜플을 남기고, 빈 속성은 NULL로 남긴다, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;(R &lt;span style=&quot;background-color: #dddddd; color: #202122; text-align: start;&quot;&gt;⟖&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #202122; text-align: start;&quot;&gt;&amp;nbsp;S)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Full Outer Join: 양쪽의 외톨이 튜플을 남기고, 빈 속성은 NULL로 남긴다, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;(R &lt;span style=&quot;background-color: #dddddd; color: #202122; text-align: start;&quot;&gt;⟗&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #202122; text-align: start;&quot;&gt;&amp;nbsp;S)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
조건을 추가해서 세타조인(theta join)으로 사용할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.3 A Logic for Relations&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datalog(&amp;rdquo;database logic&amp;rdquo;)는 if-then 문을 포함하는 논리적 쿼리 언어이다. Datalog에서 릴레이션은 명제로 표현되며, 명제 안의 변수들은 아톰(atom)으로 불린다. 마치 전통적인 프로그래밍 언어에서 함수같은 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R(a1, a2, &amp;hellip;, an) 은 (a1, a2, &amp;hellip;, an)이 R에 속하는 튜플이면 TRUE, 아니면 FALSE를 반환한다. 아래의 릴레이션 R에서 R(1,2)와 R(3,4)는 TRUE지만, R(1,3)은 FALSE다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;172&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCifd8/btsNvHR0diW/awcvkTKhI4GZ21e57KWWik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCifd8/btsNvHR0diW/awcvkTKhI4GZ21e57KWWik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCifd8/btsNvHR0diW/awcvkTKhI4GZ21e57KWWik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCifd8%2FbtsNvHR0diW%2FawcvkTKhI4GZ21e57KWWik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;131&quot; height=&quot;152&quot; data-origin-width=&quot;172&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 종류의 아톰도 있다. Arithmetic atoms는 x&amp;lt;y, x+1 &amp;ge; y + z처럼 두 arithmetic expression의 비교식으로 표현된다. 구분을 위해, 앞서 언급한 숫자 아톰은 관계형 아톰(relational atoms)으로 부르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 우리가 &amp;ldquo;길이가 100분이 넘는 영화&amp;rdquo;들을 모아 LongMovies라는 릴레이션을 만들것이라면 아래와 같이 표현 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDVchk/btsNvpRQM4B/6OSLqRRpnw5rlMp5ZmFdDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDVchk/btsNvpRQM4B/6OSLqRRpnw5rlMp5ZmFdDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDVchk/btsNvpRQM4B/6OSLqRRpnw5rlMp5ZmFdDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDVchk%2FbtsNvpRQM4B%2F6OSLqRRpnw5rlMp5ZmFdDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;58&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표 왼편의 관계형 아톰을 head, 오른편을 body라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Body는 relational/arithmetic atoms 모두가 올 수 있고, 위에서는 이 두 아톰이 AND로 묶여서 등장했다. AND로 묶인 각각의 아톰을 &amp;ldquo;subgoals&amp;rdquo;라고 부른다. 이 식을 관계대수로도 아래처럼 표현할 수 있음을 기억하자. 5.4절에서 다시 다룬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byG8pi/btsNvgt5Gv0/s6iYVTQTn86q5KqOLiBFyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byG8pi/btsNvgt5Gv0/s6iYVTQTn86q5KqOLiBFyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byG8pi/btsNvgt5Gv0/s6iYVTQTn86q5KqOLiBFyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyG8pi%2FbtsNvgt5Gv0%2Fs6iYVTQTn86q5KqOLiBFyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;541&quot; height=&quot;74&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이 Datalog 규칙의 안전함(Safety)을 논할 수 있다. 먼저 쿼리 결과는 항상 유한해야 한다. 만약 body에 NOT Movies(t,y,l,,,_)가 왔다면, 그것은 유한하지 않음으로 안전하지 않다. 또, l&amp;ge;100이라는 두번쨰 subgoal에서 나올 수 있었던 이유는, 첫번째 relational subgoal에서 l이 정의되었기 때문이다. l이 여기서 정의되지 않았다면, 규칙은 안전하지 않았을 것이다. 마지막으로, LongMovie(t,y)의 t와 y 또한, 첫번째 subgoal에서 등장했기에 이 규칙이 안전한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datalog 규칙은 여러번 시행될 수 있다. 또, 집합이 아닌 bags에도 적용가능하다. 아래의 규칙을 시행하고 나면 H(x,y)에는 S(x,y)의 튜플 중에서 x가 1보다 크거나, y가 5보다 작은 튜플 모두가 담겨 있게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ4rVr/btsNuf4bakr/7fvIvzMWGGIokjg3pHAXLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ4rVr/btsNuf4bakr/7fvIvzMWGGIokjg3pHAXLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ4rVr/btsNuf4bakr/7fvIvzMWGGIokjg3pHAXLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ4rVr%2FbtsNuf4bakr%2F7fvIvzMWGGIokjg3pHAXLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;128&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.4 Relational Algebra and Datalog&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datalog 규칙과 관계대수는 서로 표현이 가능함을 앞서 잠깐 살펴봤다. 안전한 하나의(multiple이 아닌) Datalog 규칙은 관계대수로 표현이 가능하다.(증명은 교재에서 생략했다.) 그러나, datalog 규칙은 multiple 표현이 가능하기에 모든 관계대수를 표현을 대신할 수 있다. 특히, datalog는 재귀를 표현할 수 있다. 관계대수와 Datalog 규칙의 변환을 알아보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;집합연산
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;합집합&lt;/li&gt;
&lt;li&gt;교집합&lt;/li&gt;
&lt;li&gt;차집합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사영(Projection)&lt;/li&gt;
&lt;li&gt;선택(Selection)&lt;/li&gt;
&lt;li&gt;곱(Product)&lt;/li&gt;
&lt;li&gt;조인(Join)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1iJK3/btsNs4t4WqZ/Cn5an8vVEx08fQtSR1QJ1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1iJK3/btsNs4t4WqZ/Cn5an8vVEx08fQtSR1QJ1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1iJK3/btsNs4t4WqZ/Cn5an8vVEx08fQtSR1QJ1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1iJK3%2FbtsNs4t4WqZ%2FCn5an8vVEx08fQtSR1QJ1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1734&quot; height=&quot;994&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯이, datalog는 재귀적인 표현도 가능하다. 아래는 노드간 이어진 edge들의 relation으로부터, 노드간의 path를 정의한 규칙이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU9YRX/btsNunutK8l/YAmXCVD52qjJY8FIZ05cTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU9YRX/btsNunutK8l/YAmXCVD52qjJY8FIZ05cTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU9YRX/btsNunutK8l/YAmXCVD52qjJY8FIZ05cTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU9YRX%2FbtsNunutK8l%2FYAmXCVD52qjJY8FIZ05cTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;498&quot; height=&quot;85&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개인 공부</category>
      <category>데이터베이스</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/28</guid>
      <comments>https://junbyeol.tistory.com/28#entry28comment</comments>
      <pubDate>Tue, 22 Apr 2025 17:14:39 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스 개론] 3. 고급 데이터베이스 모델(High-Level Database Model)</title>
      <link>https://junbyeol.tistory.com/27</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 DATABASE SYSTEMS:The Complete Book(2nd Edition, Hector Garcia-Molina, Jeffrey D. Ullman, Jennifer Widom)의 Chapter4: High-Level Database Model의 4.1~4.6절을 요약/재구성한 글입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실의 정보들을 데이터베이스에 담으려고 하면, 어떤 정보를 어느 릴레이션에 담고, 서로 어떻게 참조 관계를 구성할지 고민이 될 수 있다. 데이터베이스 디자인을 관계형 모델(Relational model)을 이용해서 바로 접근할 수도 있지만, 그것을 추상화하는 한단계 높은 레벨의 모델을 사용할 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.1 The Entity/Relationship Model&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;E/R Model은 데이터 구조를 시각적으로 표현해준다. 세가지로 구성된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;엔티티 셋(Entity Sets)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Relational model에서의 릴레이션과 비슷한 역할이다. 엔티티가 하나의 추상적인 오브젝트라면, 그 집합이 엔티티 셋이다. 직사각형으로 표현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;속성(Attributes)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Relational model에서의 속성과 비슷한 역할이다. 타원으로 표현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;관계성(Relationships)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 셋들간의 관계이다. Movies와 Stars의 두 엔티티 셋간에는 &amp;ldquo;Stars-in&amp;rdquo;이라는 커넥션이 있다. 세 개 이상의 엔티티들 끼리도 관계를 가질 수 있다. 마름모로 표현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 셋을 조합하면 이런식으로 그려진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWE8O2/btsNt57WQGm/2kkSjsBCPrj0SrYY8UFL4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWE8O2/btsNt57WQGm/2kkSjsBCPrj0SrYY8UFL4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWE8O2/btsNt57WQGm/2kkSjsBCPrj0SrYY8UFL4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWE8O2%2FbtsNt57WQGm%2F2kkSjsBCPrj0SrYY8UFL4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1052&quot; height=&quot;658&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관계성(Relationships)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계형 모델과는 다르게, E/R 모델에는 인스턴스라는 개념은 없다. 하지만 관계성이 뭔지 이해하기 위해 관계성 셋(relationship set)이라는 개념이 있다. 어떤 관계성 R이 엔티티셋 E1과 E2간의 관계라면, 각각의 셋에서 튜플을 하나씩 골라 (e1, e2)의 새로운 튜플로 만들 수 있다. 아래는 Movies와 Stars 엔티티셋에서 연결된(connected) 관계성 셋 이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mknw5/btsNr6ZZa1f/2L6xSWewMTJjKqXDhnSrk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mknw5/btsNr6ZZa1f/2L6xSWewMTJjKqXDhnSrk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mknw5/btsNr6ZZa1f/2L6xSWewMTJjKqXDhnSrk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmknw5%2FbtsNr6ZZa1f%2F2L6xSWewMTJjKqXDhnSrk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;143&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 엔티티셋 E, F에 대해서, E의 엔티티 하나가 최대 1개의 F의 엔티티와 연결된다고 하자. 다르게 말하면, F의 입장에서는 여러개의 E와 연결될 수 있다. 이 관계성을 다대일(Many-to-one) 관계성이라 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;E와 F의 엔티티가 서로 최대의 한개씩만 연결된다면 일대일(one-to-one)관계성이라고 부른다. 그 외에 E와 F의 엔티티가 아무렇게나 연결되어 있다면 다대다(many-to-many) 관계성이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;E/R 다이어그림을 그릴때, 화살표의 머리가 향한 곳은 관계성 셋에서 최대 한개만 있을 수 있다는 뜻이다. 아래의 그림들 처럼 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yeY7n/btsNtS7JChE/HfxC13gIqaymkhkwmWObk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yeY7n/btsNtS7JChE/HfxC13gIqaymkhkwmWObk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yeY7n/btsNtS7JChE/HfxC13gIqaymkhkwmWObk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyeY7n%2FbtsNtS7JChE%2FHfxC13gIqaymkhkwmWObk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1850&quot; height=&quot;378&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 관계성안에 동일한 엔티티셋이 두번 등장할 수도 있다. 그 경우에는 화살표 위에 반드시 역할(role)을 적어주어야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xPFy7/btsNut8fSME/UkS9rm31eihZQoFcgJsHS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xPFy7/btsNut8fSME/UkS9rm31eihZQoFcgJsHS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xPFy7/btsNut8fSME/UkS9rm31eihZQoFcgJsHS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxPFy7%2FbtsNut8fSME%2FUkS9rm31eihZQoFcgJsHS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1496&quot; height=&quot;620&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티셋 뿐만 아니라 관계성도 속성을 가질 수 있다. 아래가 그 예시이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhlgIu/btsNuYzVquW/zrph5RPf0j6hXjUT8wwpq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhlgIu/btsNuYzVquW/zrph5RPf0j6hXjUT8wwpq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhlgIu/btsNuYzVquW/zrph5RPf0j6hXjUT8wwpq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhlgIu%2FbtsNuYzVquW%2Fzrph5RPf0j6hXjUT8wwpq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;367&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 지금까지 관계성 위에 3개 이상의 선을 그어 엔티티셋과 연결했다. 그런데 종종, UML, ODL과 같이 다른 다이어그램으로 표현하고자 할때는 3개 이상의 선은 문제가 된다. 그래서 아래와 같은 시도도 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관계성이 속성을 갖고 있을 경우, 그 속성을 옮길 새로운 엔티티 셋을 만들어 옮겨준다.&lt;/li&gt;
&lt;li&gt;관계성과 엔티티 셋 간의 연결이 여러개 일 경우, 관계성을 대신할 새로운 엔티티 셋인, 연결 엔티티셋(connecting entity set)을 만들어, 기존 엔티티셋과 새로운 관계성을 맺는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdJ7Q2/btsNu5yRQXy/2exdNXvY8KYiaCEz3sOnwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdJ7Q2/btsNu5yRQXy/2exdNXvY8KYiaCEz3sOnwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdJ7Q2/btsNu5yRQXy/2exdNXvY8KYiaCEz3sOnwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdJ7Q2%2FbtsNu5yRQXy%2F2exdNXvY8KYiaCEz3sOnwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2168&quot; height=&quot;956&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티셋 안에서, 몇몇 엔티티에게만 특별한 속성이나 관계성을 주고 싶을 수도 있다. 그럴때는 서브클래스(subclass)를 이용한다. &amp;ldquo;A is a B&amp;rdquo;에서 따온 &amp;ldquo;isa&amp;rdquo;라는 관계성으로 또 아래처럼 표현할 수 있다. Movies는 Cartoons일수도 있고, Murder-mysteries일수도 있다. 둘 다 아닐수도 있고, 둘 다 일수도 있다. 그리고, 화살표는 그리지 않았지만, isa는 일대일 관계성과 같음을 생각해 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TsPjK/btsNudYVnnV/LaEpFjYyJJZi0ZtH1c2tY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TsPjK/btsNudYVnnV/LaEpFjYyJJZi0ZtH1c2tY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TsPjK/btsNudYVnnV/LaEpFjYyJJZi0ZtH1c2tY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTsPjK%2FbtsNudYVnnV%2FLaEpFjYyJJZi0ZtH1c2tY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;571&quot; height=&quot;340&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4.2절은 스킵합니다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.3 Constraints in the E/R Model&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티셋 또한, 릴레이션 처럼 키를 갖는다. 키는 밑줄을 그어 표현한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;314&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3krya/btsNuD3Rx6O/VXEbDh3poFG8ytWyi6f5Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3krya/btsNuD3Rx6O/VXEbDh3poFG8ytWyi6f5Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3krya/btsNuD3Rx6O/VXEbDh3poFG8ytWyi6f5Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3krya%2FbtsNuD3Rx6O%2FVXEbDh3poFG8ytWyi6f5Mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;179&quot; height=&quot;162&quot; data-origin-width=&quot;314&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 참조무결성(Referential Integrity)을 표현할 수도 있다. 화살표는 해당 엔티티가 최대 1개 있다는 뜻이지만, 끝이 둥그런 아래의 화살표는 반대편 엔티티가 존재하면 항상 해당 엔티티도 존재해야함을 의미한다. Owns에서 studios는 모든 movies에 대해서 항상 존재한다. Runs에서도 studios는 모든 presidents에 대해서 항상 존재한다. 하지만, studios에 presidents는 1개 있거나 없을 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blc8ol/btsNr0MfNqn/nesJfIImkCFQZoXJXWAuPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blc8ol/btsNr0MfNqn/nesJfIImkCFQZoXJXWAuPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blc8ol/btsNr0MfNqn/nesJfIImkCFQZoXJXWAuPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblc8ol%2FbtsNr0MfNqn%2FnesJfIImkCFQZoXJXWAuPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;98&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 개수를 표현할 수도 있다. 아래 그림에서는, Movies 엔티티 하나가 10개가 넘는 stars 엔티티와 연결될 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqDTpo/btsNt4VvTTU/qrJo58KJqkUJZKWfQ3jg00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqDTpo/btsNt4VvTTU/qrJo58KJqkUJZKWfQ3jg00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqDTpo/btsNt4VvTTU/qrJo58KJqkUJZKWfQ3jg00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqDTpo%2FbtsNt4VvTTU%2FqrJo58KJqkUJZKWfQ3jg00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;104&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.4 Weak Entity Sets&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 엔티티셋의 키가 다른 엔티티 셋의 속성들로 이루어지면, 그것을 약한 엔티티 셋(Weak entity set)이라고 부른다. 언제 이 개념이 필요할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째로, 두 엔티티가 계층적인 관계에 있을때를 의미한다. 앞서 말한 서브클래스와는 다르다. 현실의 종(species)과 속(genus)같은 분류체계가 예시가 될 수 있다. 혹은 studio에 crew1, crew2, &amp;hellip; 가 속한다고 생각해보자. Crew의 숫자는 키가 될 수 없고, studio의 속성을 빌려와야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째로, 관계성이 엔티티 셋으로 표현된 connecting entity set의 경우이다. 앞선 예시에서, movies와 stars 사이의 contracts에 salary라는 속성이 있었는데, salary가 contracts의 키가 될 수 있을리는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약한 엔티티셋은 스스로 키를 가지지 않으므로, 그들에게 키를 제공하는 supporting entity set과 다대일의 supporting relationship을 맺어야한다. 다이어그램 상으로는, supporting entity set을 향한 둥근 화살표가 적어도 하나는 있어야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다이어그램 상으로는 약한 엔티티셋과 supporting relationship은 테두리를 두 줄로 그어 표현한다. 아래의 그림은 앞서 설명한 약한 엔티티셋의 개념을 모두 설명한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcq5L7/btsNuaNOTnl/uQ07wTy9uiGxqYrwRp4qZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcq5L7/btsNuaNOTnl/uQ07wTy9uiGxqYrwRp4qZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcq5L7/btsNuaNOTnl/uQ07wTy9uiGxqYrwRp4qZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcq5L7%2FbtsNuaNOTnl%2FuQ07wTy9uiGxqYrwRp4qZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;420&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.5 From E/R Diagrams to Relational Designs&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ER 다이어그램을 관계형 모델로 변환할것이다. 아래의 두 규칙을 따른다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;엔티티셋을 같은 속성을 가진 릴레이션으로 바꾼다.&lt;/li&gt;
&lt;li&gt;관계성(relationship)을 릴레이션으로 바꾼다. 속성은 relationship set 에 있는 속성과 연결된 엔티티셋의 키들이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AMMs0/btsNtUdqU1M/wIZzqG9goIbU9evuDXyQZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AMMs0/btsNtUdqU1M/wIZzqG9goIbU9evuDXyQZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AMMs0/btsNtUdqU1M/wIZzqG9goIbU9evuDXyQZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAMMs0%2FbtsNtUdqU1M%2FwIZzqG9goIbU9evuDXyQZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;427&quot; height=&quot;283&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 다이어그램은 아래와 같은 관계형 모델로 변환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Stars(&lt;u&gt;name&lt;/u&gt;, address)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Movies(&lt;u&gt;title&lt;/u&gt;, &lt;u&gt;year&lt;/u&gt;, length, genre, studioName)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Studios(&lt;u&gt;name&lt;/u&gt;, address)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;StarsIn(&lt;u&gt;starName&lt;/u&gt;, &lt;u&gt;movieTitle&lt;/u&gt;, &lt;u&gt;movieYear&lt;/u&gt;)&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Owns가 없고, Movies에 studioName이라는 속성이 들어간 이유는 아래의 예외 케이스에 포함되기 때문이다. 예외는 아래의 3가지 경우가 존재한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;다대일 관계에서는 관계성으로부터 변환된 릴레이션을 &amp;ldquo;다&amp;rdquo; 쪽의 엔티티와 합칠 수 있다.&lt;/li&gt;
&lt;li&gt;약한 엔티티셋은 릴레이션으로 바꾸지 않는다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Isa&amp;rdquo; 관계성은 4.6절에서 다루는 방법을 이용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 예외. &amp;ldquo;다&amp;rdquo;쪽인 엔티티셋과 관계성셋의 릴레이션을 합칠 수 있다. 위의 예시에서 Owns는 다대일 관계이다. 그래서, Owns 릴레이션은 &quot;다&quot;쪽인 Movies로 합쳐져, studioName이라는 속성으로 남아있다. 혹여나 Owns 관계가 자체로 속성을 갖고 있었다면, 그 역시 Movies 쪽으로 합쳐 줄 수 있다. 혹여, &amp;ldquo;다&amp;rdquo;쪽 엔티티가 &quot;일&quot;쪽 엔티티셋과 연결되어있지 않은 경우도 있는데(studio가 없는 movie가 있다거나), 이 경우에는 &amp;ldquo;다&amp;rdquo;쪽 속성들 외의 값은 모두 NULL인 튜플로써 표현하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 예외. 별개의 릴레이션으로 만들되, supporting entity의 key를 속성으로 포함한다. 또, supporting relationship은 릴레이션으로 만들지 않는다. 아래의 예시에서, Crews에 studioName이 있음과 Unit-of 릴레이션은 없음에 주목하라. 사실 이 예외는 1번 예외의 연장선으로 볼 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cITumd/btsNuYGRa7o/FWjt5zZKrEugEA7PxaKsEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cITumd/btsNuYGRa7o/FWjt5zZKrEugEA7PxaKsEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cITumd/btsNuYGRa7o/FWjt5zZKrEugEA7PxaKsEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcITumd%2FbtsNuYGRa7o%2FFWjt5zZKrEugEA7PxaKsEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;156&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Studios(&lt;u&gt;name&lt;/u&gt;, addr)&lt;br /&gt;Crews(&lt;u&gt;number&lt;/u&gt;, &lt;u&gt;studioName&lt;/u&gt;, crewChief)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3번 예외는 바로 아래의 4.6절로 분리해서 다룬다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.6 Convertin Subclass Structures to Relations&lt;/h2&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b17Vpa/btsNr4nE7y4/3av5Tmizhtfad0onHuKu41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b17Vpa/btsNr4nE7y4/3av5Tmizhtfad0onHuKu41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b17Vpa/btsNr4nE7y4/3av5Tmizhtfad0onHuKu41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb17Vpa%2FbtsNr4nE7y4%2F3av5Tmizhtfad0onHuKu41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;469&quot; height=&quot;321&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 movie 다이어그램을 3가지 방법으로 변환할 수 있다.&lt;br /&gt;첫번째는 ER-스타일 변환방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Movies(title, year, length, genre)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;MurderMysteries(title, year, weapon)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Cartoons(title, year)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Voices(title, year, starname)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈여겨 볼점은, MurderMysteries와 Cartoons에 length와 genre 속성이 없다는 것이다. MurderMysteries나 Cartoons에 있는 튜플은, 모두 대응되는 튜플이 Movies에도 있을것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, Cartoons가 Voices의 부분집합이라는 점도 볼만하다. 이 설계에서는, Cartoons에 존재하지 않지만, voices에만 존재하는 숨은 튜플이 존재할 수 있다. 이 경우, 삭제한 voices가 해당 cartoons의 정보를 담은 마지막 voices 였다면, cartoons 라는 정보를 유실하게되는 삭제 이상이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째는 객체지향적인 접근(object-oriented approach)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Movies(title, year, length, genre)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;MoviesMM(title, year, length, genre,weapon)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;MoviesC(title, year, length, genre)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;MoviesCMM(title, year, length, genre, weapon)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Voices(title, year, starname)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Voices가 MoviesC와 VoicesCMM 모두와 연결되어있을 수 있다는 점이 특이하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째는 Null을 이용한 접근이다. 모든 속성을 Movies 하나에 때려넣고 없으면 Null을 값으로 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Movies(title, year, length, genre, weapon)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Voices(title, year, starName)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 접근이 가장 좋은가? 당연하게도 접근마다 장단점이 존재한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;쿼리 연산량&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정보를 쿼리할 때, 릴레이션이 하나뿐인 Null 접근은 매우 간단하다. 반면, 나머지 두 접근은 쿼리의 종류에 따라 복잡해질 수 있다. 단순히, &amp;ldquo;150분보다 긴 영화&amp;rdquo;를 찾고 싶다면, ER-스타일에서는 Movies만 확인하면 그만이지만, OO-스타일은 무려 4개의 릴레이션을 뒤져야 한다. 반면, &amp;ldquo;150분보다 긴 카툰에 쓰인 무기&amp;rdquo;라고 하면 OO-스타일에서는 MoviesCMM만 확인하면 될 것을, ER-스타일에서는 3개의 릴레이션을 다 뒤져야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;릴레이션의 수&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N개의 서브클래스가 있다고하자. Null 접근은 릴레이션이 무조건 하나다. ER-스타일은 (n+1)개다. OO-접근은 무려 2^n개다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;저장공간과 데이터의 중복&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OO-접근은 엔티티당 튜플이 하나이므로, 공간과 데이터 중복도 측면에서 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Null 접근은 엔티티당 튜플이 하나이기는 하나, 튜플 하나의 속성이 많아서, 공간의 낭비가 발생한다. 서브클래스가 많아질수록 이 공간 낭비는 심해질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ER-접근은 엔티티 하나에 튜플이 여러개이므로, 불리해보이지만, 중복되는 정보는 키뿐이다. Null 접근과 비교하면 상황에 따라 다르다.&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/27</guid>
      <comments>https://junbyeol.tistory.com/27#entry27comment</comments>
      <pubDate>Tue, 22 Apr 2025 14:00:10 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스 개론] 2. 관계형 데이터베이스 설계(Design Theory for Relational Databases)</title>
      <link>https://junbyeol.tistory.com/26</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 DATABASE SYSTEMS:The Complete Book(2nd Edition, Hector Garcia-Molina, Jeffrey D. Ullman, Jennifer Widom)의 Chapter3: Design Theory for Relational Databases를 요약/재구성한 글입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 설계 단계에서, 초안으로 나온 스키마는 개선의 여지가 많다. 제거해야 할 스키마의 중복(redundancy)이 대표적이다. 이런 관게형 데이터베이스의 이상(Anomalies)들을 쉽게 설명하기 위하여, 의존성(dependencies)이라는 개념은 좋은 수단이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 함수의존성(FD, functional dependencies)의 아이디어부터 시작해서, 정규화(normalization)와 다치종속성(multivalued dependencies)이라는 이상 제거를 위한 개념들을 소개한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.1 함수의존성(Functional Dependencies)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 릴레이션 R의 함수의존성이라는 개념을 풀어 설명하자면, 이렇게 말할 수 있다.&lt;br /&gt;&amp;ldquo;어떤 R의 두 튜플의 속성들 A1, A2, &amp;hellip;, An이 모두 같으면(agree), 다른 속성들 B1, B2, &amp;hellip;, Bn도 같아야한다.&amp;rdquo;&lt;br /&gt;요약하자면 이렇다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2u28l/btsNtL2xnIF/uysV26kTx7IMbjsTk7AsPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2u28l/btsNtL2xnIF/uysV26kTx7IMbjsTk7AsPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2u28l/btsNtL2xnIF/uysV26kTx7IMbjsTk7AsPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2u28l%2FbtsNtL2xnIF%2FuysV26kTx7IMbjsTk7AsPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;513&quot; height=&quot;78&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이션 R의 두 튜플 t와 u에 대하여, 그림으로 표현하자면 아래와 같은 상황이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLna73/btsNt4N2Ilx/BG3kMi4mMmIRL6PtfrwMD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLna73/btsNt4N2Ilx/BG3kMi4mMmIRL6PtfrwMD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLna73/btsNt4N2Ilx/BG3kMi4mMmIRL6PtfrwMD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLna73%2FbtsNt4N2Ilx%2FBG3kMi4mMmIRL6PtfrwMD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;324&quot; height=&quot;268&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이렇게 표현한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dynwGl/btsNqofIotL/yuWs8SOksRe0o4fGYz6701/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dynwGl/btsNqofIotL/yuWs8SOksRe0o4fGYz6701/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dynwGl/btsNqofIotL/yuWs8SOksRe0o4fGYz6701/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdynwGl%2FbtsNqofIotL%2FyuWs8SOksRe0o4fGYz6701%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;38&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2t3Ot/btsNr3oom0x/eTGkYillrnlQw1xDUZTdY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2t3Ot/btsNr3oom0x/eTGkYillrnlQw1xDUZTdY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2t3Ot/btsNr3oom0x/eTGkYillrnlQw1xDUZTdY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2t3Ot%2FbtsNr3oom0x%2FeTGkYillrnlQw1xDUZTdY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;108&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사례를 살펴보자. 오른쪽의 릴레이션은 chapter2에서 살펴본 Movie relation 보다 더 많은 속성을 포함하고 있고, 함수의존성도 존재한다. 우리는 title, year 값이 같은 두 튜플은 length, genre, studioName도 같을 것이라는걸 알 수 있다. 그래서 title과 year는 length, genre, studioName을 결정한다. 반면, starName은 length, year값이 같음에도 서로 다른 값을 가질 수 있다. 즉, 아래의 첫번째 FD(Functional Dependency)는 맞지만, 두번째는 틀렸다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;title year -&amp;gt; length genre studioName (맞다)&lt;br /&gt;title year -\-&amp;gt; starName (아니다)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 릴레이션에서 키는 {title, year, starName}이다. 어느 두 튜플들의 title, year, startName 값이 같다면, length, genre, studioName 또한 같을 것이다. 우리는 키를 두 가지 특징으로 다시 정의할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키의 속성들이 그 외의 나머지 모든 속성들을 함수적으로 결정(functionally determine)해야만 한다.&lt;/li&gt;
&lt;li&gt;키의 어떤 부분집합도, 나머지 모든 속성들을 함수적으로 결정하지 못해야한다. {title, year} 만으로는 starName을 결정하지 못하는 것 처럼. 이것을 우리는 키의 미니멀리티(minimality)라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 집합을 포함하는 더 큰 속성들의 집합을 우리는 슈퍼키(superkey)라고 부른다. 슈퍼키는 앞선 첫번째 키의 조건은 성립하지만, 두번째 미니멀리티가 성립하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키는 한 릴레이션에 여러개일 수 있다. 이 키들을 후보키(candidate keys)라고 부르고, 그중에서 우리는 기본키(primary key)를 선택해야한다. 기본키는 FD 이론적으로는 다른 후보키들과 달리 취급될게 없지만, 현실의 데이터베이스 구현에서는 중요하다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3.2 FD의 규칙들(Rules About Functional Dependencies)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FD와 관련된 몇가지 유용한 규칙과 용어들을 소개한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이행규칙(Transitive Rule)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;어느 릴레이션 R(A,B,C)에 대해서 FD A&amp;rarr;B와 B&amp;rarr;C가 성립한다면, R은 A&amp;rarr;C도 만족한다.&amp;rdquo;&lt;br /&gt;속성 하나가 아니라, 여러개에 대해서도 만족한다. 증명은 아래에서 소개할 개념인 클로져를 살펴 본 후에 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHWSed/btsNr7Eh6wa/joISr7rOVCCRmTor7CVxT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHWSed/btsNr7Eh6wa/joISr7rOVCCRmTor7CVxT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHWSed/btsNr7Eh6wa/joISr7rOVCCRmTor7CVxT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHWSed%2FbtsNr7Eh6wa%2FjoISr7rOVCCRmTor7CVxT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;104&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분배/결합규칙(Splitting/Combining rule)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Yrxw/btsNrs22E0y/MoaYjtwlgFDzRjVDzx62FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Yrxw/btsNrs22E0y/MoaYjtwlgFDzRjVDzx62FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Yrxw/btsNrs22E0y/MoaYjtwlgFDzRjVDzx62FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Yrxw%2FbtsNrs22E0y%2FMoaYjtwlgFDzRjVDzx62FK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;324&quot; height=&quot;54&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lOW22/btsNqpyWZPz/l3WQiuCcKLeF9Dfo5AtvF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lOW22/btsNqpyWZPz/l3WQiuCcKLeF9Dfo5AtvF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lOW22/btsNqpyWZPz/l3WQiuCcKLeF9Dfo5AtvF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlOW22%2FbtsNqpyWZPz%2Fl3WQiuCcKLeF9Dfo5AtvF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;65&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 FD가 성립하면 우변의 B들을 쪼개어, 아래의 FD들도 성립한다. 아래의 FD들이 성립하면 우변의 B들을 합쳐진, 위의 FD도 성립한다. B들에 대해서는 분배/결합이 가능하지만, A에 관해서는 불가능함에 주의한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동등성(Equivalence)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 두 FD 들의 집합을 비교할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S = { A &amp;rarr; B, B &amp;rarr; C }&lt;br /&gt;T = { A &amp;rarr; C, A &amp;rarr; B }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S와 T의 원소들은 달라 보이지만, S의 두 원소를 보면 A&amp;rarr;C도 성립함을 알 수 있다. S를 만족하는 모든 인스턴스는, T도 만족할 것이다. 반대로 T를 만족하는 모든 인스턴스는, S도 만족할 것이다. 이것을 우리는 S와 T가 동등하다고 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R={ A&amp;rarr;C }인 집합을 생각해보자. S로부터 R은 유도가능하다. 우리는 이것을 S가 R을 따른다(S follows from T)라고 한다. 반면, 이 경우는 R이 S를 따르지는 않는다. 두 집합이 서로를 따른다면, 그 두 집합은 동등하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자명성(Triviality)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 식이 성립한다면, A1, A2, &amp;hellip; An &amp;rarr; B1, B2, &amp;hellip;, Bn의 FD는 자명(trivial)하게 성립한다. 반사규칙(Reflexivity) 이라고도 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RRkSk/btsNtrDeVKB/Q4rnPzoOHEZF0OkUTq6YH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RRkSk/btsNtrDeVKB/Q4rnPzoOHEZF0OkUTq6YH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RRkSk/btsNtrDeVKB/Q4rnPzoOHEZF0OkUTq6YH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRRkSk%2FbtsNtrDeVKB%2FQ4rnPzoOHEZF0OkUTq6YH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;383&quot; height=&quot;48&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, title year &amp;rarr; title 은 당연히 성립할 것이 예상가능하다. {title}은 {title, year}의 부분집합이다. 즉, 자명성을 쉽게 말하면, 오른편의 속성들이 왼편에도 똑같이 있으면, 자명히 성립한다는 뜻이다. 반대로 왼편과 오른편에 같은 속성이 없으면 자명하지 않다(non-trivial).&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;title year -&amp;gt; title (trivial FD)&lt;br /&gt;title year -&amp;gt; length (non-trivial FD)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 경우는 trivial 하지는 않지만, 성립하는 또 다른 명제이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vsEs9/btsNt7wyoCz/0DIRmqw1Isr7gKHas9RsQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vsEs9/btsNt7wyoCz/0DIRmqw1Isr7gKHas9RsQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vsEs9/btsNt7wyoCz/0DIRmqw1Isr7gKHas9RsQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvsEs9%2FbtsNt7wyoCz%2F0DIRmqw1Isr7gKHas9RsQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;361&quot; height=&quot;218&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렵게 써있지만, title year -&amp;gt; title genre가 맞다면, 겹치는 title은 제외하고, title year -&amp;gt; genre도 맞을 거라는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클로져(Closure)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 속성 집합 {A1, A2, &amp;hellip;, An}으로부터 어떤 FD 집합 S를 가지고, 함수적으로 결정할수 있는 모든 속성들의 집합을 {A1, &amp;hellip;, An}의 클로져(Closure)라고 부른다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;S = { A-&amp;gt;B, B-&amp;gt;C }&lt;br /&gt;X = { A } &lt;br /&gt;X+ = { A, B, C } // X의 클로져&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;X는 속성 A만을 원소로 갖지만, FD 집합 S에 의해, B와 C의 값도 결정할 수 있다. A가 결정되는 것은 자명(trivial)하다. 따라서 X의 클로져는 {A, B, C}이며, X+로 표기한다.(+는 윗첨자에 있어야 하지만 편의상 이렇게 쓰겠다)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클로져 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 속성집합 A과 FD집합 S로부터 A의 클로져를 구하는 알고리즘을 소개한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FD 집합 S에서 분배할 수 있는 것들을 모두 분배한다.&lt;/li&gt;
&lt;li&gt;새로운 집합 X를 정의한다. 초깃값은 A 그대로 한다.&lt;/li&gt;
&lt;li&gt;S에서 X의 원소들을 가지고, 새로운 원소를 결정할 수 있는 FD를 찾는다. 원소를 찾으면 X에 추가하고, 반복한다.&lt;/li&gt;
&lt;li&gt;더 이상할게 없으면 이 때의 X가 주어진 A의 클로져다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;입력: 속성 집합 A, FD 집합 S&lt;br /&gt;출력: A의 클로저 X = A+ X &amp;larr; A&lt;br /&gt;반복:&lt;br /&gt;&amp;nbsp; &amp;nbsp; X에 새로운 속성이 추가되지 않을 때까지&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; S의 각 FD B &amp;rarr; C에 대해&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; B &amp;sube; X이면, C를 X에 추가&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 알고리즘이 왜 성립하는지는 어떻게 증명할 수 있을까? 두가지를 증명하면 될것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알고리즘의 출력값 X의 모든 원소들이, A와 S를 통해 결정될 수 있음. (정당성)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3의 과정에서 수학적 귀납법을 사용하면 증명가능하다. 초깃값인 A는 당연히 A로 부터 결정되고, 원소 하나를 추가해도 이건 A와 S로부터 결정할 수 있는 원소들임을 보인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A와 S로 결정할 수 있는 모든 원소들이 X 안에 들어있음. (완전성)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;귀류법으로 증명한다. Z는 두 속성 A와 S로부터 결정되는 속성이지만, X에는 빠져있다고 가정하면, 모순이 발생한다.&lt;br /&gt;원소가 튜플 t와 s 두개 뿐인 인스턴스 I를 잡는다. t와 s는 A의 속성들에 대해서는 모두 같지만, 나머지에 대해서는 모두 다르다. 즉, A의 Z속성값과 S의 Z속성값은 다르다. 그런데, 우리는 Z가 A로부터 결정된다고 했기때문에, A의 속성들이 모두 같은 t와 s는 Z도 같아야한다. 모순이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기저(Basis)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R(A,B,C)에 대해서 모두가 서로 결정적인 FD의 집합 S = { A&amp;rarr;B, B&amp;rarr;A, B&amp;rarr;C, C&amp;rarr;B, A&amp;rarr;C, C&amp;rarr;A }를 상상해보자. S의 원소들간의 중복을 확인할 수 있다. B = { A&amp;rarr;B, B&amp;rarr;C, C&amp;rarr;A } 는 S와 동등하다. 이 때, B는 아래의 3가지 조건을 만족하는데, 이것을 최소기저(minimal basis)라고 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 FD의 오른쪽 편에 속성이 하나뿐이다.&lt;/li&gt;
&lt;li&gt;FD 중에서 하나라도 사라지면, 더 이상 S와 동등하지 않다. 혹은 더 이상 S의 기저(basis)가 아니게 된다.&lt;/li&gt;
&lt;li&gt;FD 중에서 왼쪽 편의 속성 하나라도 사라지면, 더이상 S와 동등하지 않다.(기저가 아니다)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신기하게도, 어떤 릴레이션 R과 FD집합은 항상 하나의최소기저를 갖지는 않는다. 위의 S는 B 뿐아니라, 아래의 집합도 S의 최소기저이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C = { A&amp;rarr;B, B&amp;rarr;A, B&amp;rarr;C, C&amp;rarr;B }&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;암스트롱의 공리(Armstrong&amp;rsquo;s axioms)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 세가지 공리이다. 1과 3은 이미 언급했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;반사규칙(Reflexivity): 앞서 자명성에서 언급한 성질이다. X &amp;supe; Y 이면, X &amp;rarr; Y&lt;/li&gt;
&lt;li&gt;부가규칙(Augmentation): X &amp;rarr; Y 이면, X Z &amp;rarr;Y Z&lt;/li&gt;
&lt;li&gt;이행규칙(Transivity): X &amp;rarr;Y 이고 Y &amp;rarr;Z 이면, X &amp;rarr; Z&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FD 사영하기(Projecting FD)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 릴레이션 R과 그 릴레이션의 사영 R1을 생각하자. R에서 성립하는 FD들의 집합 S가 있을때, R1에서 성립하는 FD들의 집합 S1을 구할 수 있을까? 예시와 함께 알고리즘을 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Input: R(A,B,C,D)와 그 사영 R1(A,C,D), 그리고 R에서 성립하는 S = { A&amp;rarr;B, B&amp;rarr;C, C&amp;rarr;D }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Output: R1에서 성립하는 FD의 집합 S1&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;초기 S_1은 빈 집합이다.&lt;/li&gt;
&lt;li&gt;R의 속성들의 모든 부분집합들에 대하여, 클로져를 찾는다. 그리고, 어떤 속성이 추가되었는지를 보고 새로운 FD들을 만들어, S1에 추가한다. 단, R1에 있는 속성들만 추가하면 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;{A}+ = {A,B,C,D} 이므로 A&amp;rarr;C, A&amp;rarr;D 를 R_1에 추가. {A}+가 이미 R_1의 모든 속성을 포함하므로, A는 더이상 생각하지 않아도 좋다. 예를 들어, {A,C}+도 {A,B,C,D} 일 것이고, 여기서 새로운 FD를 찾을 순 없을것이다. 만약 {A}+가 {A,B,C,D}가 아니였더라면, AC&amp;rarr;D를 R_1에 추가했어야 할 것이다.&lt;/li&gt;
&lt;li&gt;{C}+ = {C,D} 이므로 C &amp;rarr; D를 추가.&lt;/li&gt;
&lt;li&gt;{D}+ = {D} 이므로 새로운 비자명한(non-trivial) FD는 없음. 추가할 것 없음.&lt;/li&gt;
&lt;li&gt;{C, D} + = {C, D}이므로 추가할 것 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위 과정으로 S1을 구했지만 이것은 최소기저(minimal basis)가 아니다. 암스트롱의 공리로 중복된 기저들을 정리하면 A&amp;rarr;D는 제거되고, {A&amp;rarr;C, C&amp;rarr;D}가 남는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.3 관계형 데이터베이스 설계(Design of Relational Database Schemas)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsPrO3/btsNudRAPIH/FKzIopbZCI7As5z2BWRKD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsPrO3/btsNudRAPIH/FKzIopbZCI7As5z2BWRKD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsPrO3/btsNudRAPIH/FKzIopbZCI7As5z2BWRKD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsPrO3%2FbtsNudRAPIH%2FFKzIopbZCI7As5z2BWRKD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;556&quot; height=&quot;314&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 릴레이션에는 3가지의 이상(anomalies)가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;중복(Redundancy): year, length, genre가 중복으로 포함됨&lt;/li&gt;
&lt;li&gt;수정이상(Update Anomalies): Star Wars의 length를 124에서 125로 수정하려 하니, 3번이나 수정해줘야한다.&lt;/li&gt;
&lt;li&gt;삭제이상(Deletion Anomalies): Gone With the Wind의 Vivien Leigh 배우 정보를 삭제하려고 하니, Gone With the Wind 튜플 전체가 사라져 영화 자체의 정보도 남지 않게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제들을 해결할 접근 방법 중 하나는 릴레이션을 분해(decompose)하는 것이다. 위 Movies1 relation을 Movies2와 3로 분해한 것은 아래와 같다. Movies2와 Movies3의 속성들의 합집합은 Movies1과 같아야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvQjQZ/btsNuwDjtYg/X5txN237KgmlGmghYuZwlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvQjQZ/btsNuwDjtYg/X5txN237KgmlGmghYuZwlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvQjQZ/btsNuwDjtYg/X5txN237KgmlGmghYuZwlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvQjQZ%2FbtsNuwDjtYg%2FX5txN237KgmlGmghYuZwlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;746&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위 3가지 이상를 다시 살펴보자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;year, length, genre는 더 이상 중복되지 않는다.&lt;/li&gt;
&lt;li&gt;이제 한번만 Star Wars의 length를 변경해도 충분하다.&lt;/li&gt;
&lt;li&gt;Vivien Leigh 정보를 삭제해도, 영화에 대한 정보는 남아있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹자는 Movies3에 아직 title과 year의 중복이 남아있다고 할 수 있지만, 그 둘은 키이기 때문에 경우가 다르다. 또는, Star Wars의 year를 수정하고자 하면 여전히 문제가 남아있다고 할 수 있지만, 마찬가지로 그 둘은 키이다. 키들을 수정하거나 삭제하려고 하면, 이 릴레이션이 만족해야하는 FD 집합도 달라지게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Boyce-Codd Normal Form(BCNF)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 이상들이 존재하지 않는 형태의 릴레이션이 있다. 그 예로 BCNF를 소개한다. BCNF의 정의는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;릴레이션 R이 BCNF &amp;harr; 모든 R의 자명하지 않은 FD에 대해서, 왼편의 속성들이 R의 슈퍼키여야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서, Movies1 relation에서 키는 {title, year, starName} 이였는데, title year &amp;rarr; length genre FD 역시 성립했다. 이 왼편의 속성들은 슈퍼키가 아니므로, Movies1은 BCNF가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속성이 두 개 뿐인 릴레이션은 항상 BCNF다. 속성 A,B에 대해 A&amp;rarr;B, B&amp;rarr;A이 두 FD가 각각 성립할때/하지 않을때 2x2 가지 경우를 따져보면 모두 BCNF일수 밖에 없음을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 릴레이션을 BCNF들로 분해하고 싶다면, 어떻게 해야 할까? 아래의 예를 통해 살펴보자&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;R(title, year, studioName, president, pressAddr)에 대하여,&lt;br /&gt;title year -&amp;gt; studioName&lt;br /&gt;studioName -&amp;gt; president&lt;br /&gt;president -&amp;gt; presAddr이 성립함.&lt;br /&gt;이 때 키는 {title, year}임을 알 수 있다.&lt;br /&gt;&lt;br /&gt;1. BCNF의 조건이 성립하지 않는 FD 하나를 골라, 왼편 속성들의 클로져를 찾는다.&lt;br /&gt;예)&lt;br /&gt;&amp;nbsp; &amp;nbsp; - studioName -&amp;gt; president를 고르고&lt;br /&gt;&amp;nbsp; &amp;nbsp; - studioName의 클로져는 {studio, president, pressAddr}&lt;br /&gt;2. 방금 고른 FD의 왼편은 제외하고, 위 속성들을 기존 릴레이션에서 분리한다.&lt;br /&gt;예)&lt;br /&gt;&amp;nbsp; &amp;nbsp; - studioName만 제외하고, 분리한다.&lt;br /&gt;&amp;nbsp; &amp;nbsp; - R1(title, year, studioName)&lt;br /&gt;&amp;nbsp; &amp;nbsp; - R2(studioName, president, pressAddr)&lt;br /&gt;3. 모든 릴레이션이 BCNF 일때까지 R_1과 R_2에서 반복한다.&lt;br /&gt;예)&lt;br /&gt;&amp;nbsp; &amp;nbsp; - R1은 이제 BCNF&lt;br /&gt;&amp;nbsp; &amp;nbsp; - R2는 president-&amp;gt;presAddr 때문에 아직 BCNF가 아님. 이 FD를 골라서 한 번 더 분해하면&lt;br /&gt;&lt;br /&gt;R1(title, year, studioName) R2(studioName, president) R3(president, pressAddr)&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.4 분해(Decomposition)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분해(Decomposition)는 항상 좋은가? 아래의 세 가지 속성을 살펴보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이상 제거(Elimination of Anomalies)&lt;/li&gt;
&lt;li&gt;정보 복구가능성(Recoverability of Information): 분해된 튜플들을 조인하면 기존 릴레이션의 정보를 복구할 수 있는가?&lt;/li&gt;
&lt;li&gt;의존성 보존(Preservation of Dependencies): 분해된 릴레이션들을 다시 조인하면, 기존 원래의 FD 집합과 같아지는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면, 이 3가지를 모두 만족하는 분해는 없다. 앞서 소개한 BCNF는 속성 1, 2 만을 만족한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정보 복구가능성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이션 R(A,B,C)가 R1(A,B)와 R2(B,C)로 분해됐다고 하자. 이 때, R1과 R2를 조인했을 때, 원래의 릴레이션과 동일한 결과로 복구된다면, 정보 복구가 가능한 것이다. 기존 튜플들보다 많아서도 안되고, 적어서도 안된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 보존&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bookings(title, theater, city)&lt;br /&gt;{ theater &amp;rarr; city, title city &amp;rarr; theater }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 릴레이션을 살펴보자. 영화관은 어느 한 도시에 위치해 있고, 보고싶은 영화와 볼 도시가 정해지면 영화관이 정해지니, 현실적으로도 위 FD는 충분히 말이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 릴레이션의 키는 무엇인가? 키의 정의에 의하면 두가지가 가능하다. {title, city}, {theater, title}.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;theater&amp;rarr;city를 기준으로 Bookings를 Bookings1(theater,city), Bookings2(theater,title)로 분해한다. 그러면, 뭔가 이상하다는 것을 알 수 있다. 이 두 릴레이션을 다시 조인하면 {title, city}는 더 이상 키도 아니고, title city &amp;rarr; theater FD는 성립하지 않게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.5 Third Normal Form&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 BCNF에는 의존성 보존에 있어서 문제가 있다는 사실을 알았다. 제3정규화(Third Normal Form, 3NF)는 BCNF보다 한층 완화된 릴레이션의 형태이다. 제3정규화를 소개하기 전, 제1정규화(1NF)와 제2정규화(2NF)를 먼저 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제1정규화는 릴레이션 안에 배열이나 집합 등 어토믹(atomic)하지 않는 값이 들어있지 않은 릴레이션을 말한다. 만약 어떤 튜플의 필드가 {a,b,c}라면 각각 a, b, c를 값으로 갖는 3개의 튜플로 분해하여 릴레이션을 1NF로 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제2정규화는 어느 속성이 키의 전체 속성이 아니라 속성 일부에만 의존하지 않는 릴레이션을 말한다. 다른 속성들은 {A,B} 키에 의존하는데, 어느 속성은 {A} 키에만 의존한다면 부분 의존(partially dependent)이 남아있어서, 2NF가 아니게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제3정규화는 BCNF의 정의와 비슷하다. 모든 R의 자명하지 않은 FD에 대해서, 왼편의 속성들이 R의 슈퍼키여야 하는것이 BCNF의 정의였다. 3NF는 여기에 오른편의 속성들이 키의 일부인 것을 허용한다. 다르게 말하면, FD의 왼편이 슈퍼키가 아니고, 오른편도 키와 상관 없으면 3NF가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3NF로의 분해과정은 (1)이상제거, (2)정보복구가능성, (3)의존성보존 중 (2)와 (3)을 만족한다. 아래의 알고리즘이 바로 그것이다.&lt;br /&gt;&lt;br /&gt;추가예정&lt;br /&gt;...(3NF 분해과정)&lt;br /&gt;...(MVN, 4NF)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/26</guid>
      <comments>https://junbyeol.tistory.com/26#entry26comment</comments>
      <pubDate>Tue, 22 Apr 2025 13:10:22 +0900</pubDate>
    </item>
    <item>
      <title>[데이터베이스 개론] 1. 관계형 데이터 모델(Relational Data Model)</title>
      <link>https://junbyeol.tistory.com/25</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 DATABASE SYSTEMS:The Complete Book(2nd Edition, Hector Garcia-Molina, Jeffrey D. Ullman, Jennifer Widom)의 Chapter2: Relational Database Modeling를 요약/재구성한 글입니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.1 데이터 모델 개요(An Overview Of Data Models)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 모델이란 무엇인가? 이것을 정의하기에 앞서, 데이터 모델을 정의하는 세가지 관점을 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 데이터의 구조(structure of data): 프로그래밍 언어의 배열, 객체 같은 것과 유사하다. 그러나, 데이터베이스에서 얘기하는 데이터의 구조는 그것보다는 상위 계층에서 일어나는 일이다. 그래서, 물리적 모델(physical model)보다는 &lt;b&gt;개념적 모델(conceptual model)&lt;/b&gt;이라는 말이 더 잘 들어맞는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, 데이터의 연산(operations on the data): 프로그래밍 언어랑은 다르게, 데이터베이스의 데이터에 할 수 있는 연산은 제한적이다. &lt;b&gt;쿼리(queries)&lt;/b&gt;와 &lt;b&gt;변경(modification)&lt;/b&gt;, 두 종류다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, 데이터의 제한조건(constraints on the data): 데이터는 &amp;ldquo;아무거나&amp;rdquo;로 표현되지는 않는다. 그 종류가 &amp;ldquo;숫자&amp;rdquo; 라거나 &amp;ldquo;1부터 10사이의 정수&amp;rdquo; 라거나 &amp;ldquo;영화제목&amp;rdquo; 같은 타입(type)이나 범위(range)가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 모델은 두 종류로 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관계형 모델(relational model)&lt;/li&gt;
&lt;li&gt;비정형 모델(semistructured-data model)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관계형 모델(Relational Model)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQlAZX/btsNuJvmVsp/snvUUu4RhGhyKI0ywur6OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQlAZX/btsNuJvmVsp/snvUUu4RhGhyKI0ywur6OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQlAZX/btsNuJvmVsp/snvUUu4RhGhyKI0ywur6OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQlAZX%2FbtsNuJvmVsp%2FsnvUUu4RhGhyKI0ywur6OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;387&quot; height=&quot;214&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구조의 관점: 프로그래밍 언어로 생각한다면, 그냥 struct 객체의 배열 정도로 상상하면 된다. 가능은 하겠다만, 실제로 그런식으로 구현되지는 않는다. 그 이유는 데이터의 규모 때문이다. 보통 데이터베이스는 대량의 데이터를 상상하고, RAM 보다는 디스크에 저장하기 좋은 형태로 데이터를 다룬다.&lt;/li&gt;
&lt;li&gt;연산의 관점: 우리는 이 표에서 &amp;ldquo;장르가 comedy&amp;rdquo;인 row 만을 골라낼 수 있다.&lt;/li&gt;
&lt;li&gt;제한조건의 관점: 예를 들어, &amp;ldquo;title과 year가 동시에 같은 영화는 없다&amp;rdquo; 같은 조건이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비정형 모델(Semistructured-data Model)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7W2sC/btsNs4ttj22/N5vyxizploL2OmwEqL0wKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7W2sC/btsNs4ttj22/N5vyxizploL2OmwEqL0wKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7W2sC/btsNs4ttj22/N5vyxizploL2OmwEqL0wKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7W2sC%2FbtsNs4ttj22%2FN5vyxizploL2OmwEqL0wKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;389&quot; height=&quot;656&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구조의 관점: 대개 테이블, 배열 같은 느낌보단 트리(tree)나 그래프(graph)를 상상하면 된다. 위 Figure는 Semistructured-data Model의 대표적 예시인 XML이다.&lt;/li&gt;
&lt;li&gt;연산의 관점: 여기서의 연산은 원소(element)의 하위원소(subelement)들을 타고타고 내려가며 진행된다. 장르가 &amp;ldquo;comedy&amp;rdquo;인 영화를 찾으려면 태그 하위의 태그 안의 값을 확인해야 한다.&lt;/li&gt;
&lt;li&gt;제한조건의 관점: &amp;ldquo; 태그 안에 항상 태그가 존재하는가?&amp;rdquo; 같은 조건이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비정형 모델이 관계형 모델에 비해 더 유연하다는 장점은 있다. 그러나, 최근의 DBMS는 더 많은 데이터를 더 효과적으로 접근하고 수정할 수 있게 하기 위해 관계형 모델을 선택한다. 남은 글에서는 관계형 모델을 집중적으로 소개한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.2 관계형 모델 기초(Basics of the Relational Model)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blpfve/btsNtZszRBX/cEEZTqNjrosKxdafWtr9fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blpfve/btsNtZszRBX/cEEZTqNjrosKxdafWtr9fK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blpfve/btsNtZszRBX/cEEZTqNjrosKxdafWtr9fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblpfve%2FbtsNtZszRBX%2FcEEZTqNjrosKxdafWtr9fK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;387&quot; height=&quot;109&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 아까의 figure를 가져왔다. 우리는 이런 2차원의 테이블을 릴레이션(relation)이라고 부른다. 이 &amp;ldquo;Movies relation&amp;rdquo;을 보며, relational model에서 사용되는 주요 용어들을 소개한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;속성(Attributes)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표에서 title, year, length, genre 같은 세로열(column)들을 말한다. 필드(field), 피쳐(feature)도 비슷한 표현이다. 어느 릴레이션의 attributes의 개수를 차수(degree) 혹은 차원(dimension) 혹은 애리티(arity)라고 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스키마(Schemas)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이션의 이름과 속성들의 집합을 우리는 스키마(schema)라고 부른다. 예를 들어, 위의 Movies의 스키마는 아래처럼 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Movies(title, length, year, genre)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속성들의 &amp;ldquo;집합&amp;rdquo;이라고 표현한 것은, 그들의 순서는 중요치 않기 때문이다. 또, 데이터베이스는 대개 여러개의 스키마로 구성된다. 그것을 우리는 데이터베이스 스키마(database schema)라고 부른다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;튜플(Tuples)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이션에서 하나의 가로행(row), 레코드(record)나 오브젝트(object)라는 표현도 가능하다. 그리고 어느 릴레이션의 tuples의 개수를 크기(cardinality)라고 부른다. 아래처럼 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(Star Wars, 1939, 231, drama)&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;도메인(Domains)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터타입(data type)도 동일한 표현이다. 각 속성들은 atomic하다, 혹은 elementary type을 갖는다. 더 이상 쪼갤 수 없다는 뜻으로, 집합, 배열 같은 데이터 구조들은 포함되지 않는다. 도메인을 살려서 스키마를 표현하면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Movies(title: string, year: integer, length: integer, genre: string)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이션은 튜플들의 배열이 아닌 집합이다. 앞서 말했듯이, 속성들의 순서 또한 중요치 않다. 즉 아래처럼 데이터의 가로와 세로를 마구 섞어도, 우리는 동일한 릴레이션의 다른 표현방법일 뿐으로 본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCMdku/btsNuE10yOW/uQvwjOefkqnQN80ufMcsWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCMdku/btsNuE10yOW/uQvwjOefkqnQN80ufMcsWk/img.png&quot; data-alt=&quot;앞선 피규어와 속성들의 순서가 다르다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCMdku/btsNuE10yOW/uQvwjOefkqnQN80ufMcsWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCMdku%2FbtsNuE10yOW%2FuQvwjOefkqnQN80ufMcsWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;130&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;앞선 피규어와 속성들의 순서가 다르다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인스턴스(instance)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이션은 멈춰있는 것이 아니다. 새로운 튜플이 수정되거나, 기존 튜플이 변경되거나, 삭제된다. 비용이 크긴 할테지만, 스키마가 변경될수도 있다. 예를 들어, 새로운 속성이 추가될 수 있다. 그래서, 지금 당장 이순간의 릴레이션의 튜플들의 집합을 인스턴스(current instance)라고 부른다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;키(key)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서, 제목과 연도가 같은 영화는 없다고 하기로 했다. 이 경우 제목 속성과 연도 속성의 집합을 키(key)라고 부른다. 키는 이렇게 밑줄을 그어 표현한다.Genre가 키가 될수는 없을 것이다. 당장의 인스턴스에는 영화들의 genre가 다 다르지만, 앞으로 새로운 tuple들이 추가되면, 아니게 될 것이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Movies(&lt;u&gt;title&lt;/u&gt;, &lt;u&gt;length&lt;/u&gt;, year, genre)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실 세계 에서는 인공키(aritifical key)를 사용하기도 한다. 주민등록번호처럼 영화마다 고유한 임의의 문자열이나 숫자를 영화등록번호로 설정하는 것이다. 아래의 MovieID는 튜플에게 인위적으로 부여된 고유한 문자열 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Movies(&lt;u&gt;MovieID&lt;/u&gt;, title, length, year, genre)&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.3 Defining a Relation Schema in SQL&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL(&amp;rdquo;시퀄&amp;rdquo;, &amp;ldquo;sequel&amp;rdquo;이라고 발음)은 관계형 데이터베이스에서 사용하는 주된 언어중 하나이다. 두 가지의 기능이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스키마를 정의하는 DDL(Data-Definition Language)로써의 기능&lt;/li&gt;
&lt;li&gt;데이터를 쿼리(query, 질의)하거나 수정하는 DML(Data-Manipulation Language)로써의 기능&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL은 릴레이션을 세 종류로 구분한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;테이블(Tables)&lt;/b&gt;: 일반적으로 데이터베이스에 저장된(stored) 수정이나 쿼리가 가능한 데이터들이 있는 릴레이션을 말한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;뷰(Views)&lt;/b&gt;: 데이터베이스에 저장된 데이터들 중 필요에 따라 연산을 해서 나온 결과물로 나온 릴레이션이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;임시 테이블(Temporary tables)&lt;/b&gt;: query processor가 임시로 생성한, 저장되지 않는 테이블이다. (사용자가 목적을 두고 만든건 뷰, 의도하지 않았으나 데이터베이스 내의 소프트웨어가 명령 수행을 위해 만들어진 부산물은 임시테이블 인듯하다 - 글쓴이의 이해)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 에는 &lt;b&gt;원시 타입(primitivie data types)&lt;/b&gt;이 존재한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CHAR(n): 길이가 n으로 고정된 문자열, 길이를 맞추기 위한 패딩(padding)이 들어감&lt;/li&gt;
&lt;li&gt;VARCHAR(n): 길이 최대 n의 가변 길이 문자열, 패딩 없이 문자열 끝에 마커(endmarker, \0)나 길이가 따라 붙는다.&lt;/li&gt;
&lt;li&gt;BIT(n): 고정 길이 비트&lt;/li&gt;
&lt;li&gt;BIT VARYING(n): 가변 길이 비트&lt;/li&gt;
&lt;li&gt;BOOLEAN: TRUE, FALSE, 그리고.. UNKNOWN 3가지 값이 가능함&lt;/li&gt;
&lt;li&gt;INT(=INTEGER): 4bytes 정수, SHORTINT 같은 것도 있으며, C의 타입 체계와 동일&lt;/li&gt;
&lt;li&gt;FLOAT(=REAL): 부동소수점(floating-point) 숫자, C와 동일하며, DOUBLE PRECISION도 있음&lt;/li&gt;
&lt;li&gt;DECIMAL(n,d): 유효숫자 n개, 소숫점 아래 d개의 숫자가 있는 숫자. NUMERIC과 거의 유사하나, DBMS마다 구현의 차이가 있을 수 있음&lt;/li&gt;
&lt;li&gt;DATE, TIME: 각각 &amp;lsquo;yyyy-mm-dd&amp;rsquo;와 &amp;lsquo;hh:mm:ss.s&amp;rsquo; 의 문자열 다뤄진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리의 Movies Relation에 몇개의 속성을 추가하여, 테이블로 생성하자면 아래의 문법으로 SQL 선언을 하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1745224816562&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE Movies (  
    title 	CHAR(100),
    year	INT,
    length	INT,
    genre	CHAR(10),
    studioName	CHAR(30),
    producerCNum	INT,
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇가지 스키마를 수정하는 SQL 문(statements)을 더 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블의 삭제: &lt;code&gt;DROP TABLE Movies;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에 속성 추가: &lt;code&gt;ALTER TABLE Movies ADD title CHAR(100);&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에 속성 삭제: &lt;code&gt;ALTER TABLE Movies DROP title;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 스키마에 새로운 속성을 추가한 경우를 생각해보자. 기존의 tuple들은 그 속성에 할당할 값이 필요하다. 보통 이럴때는 &lt;code&gt;NULL&lt;/code&gt; 이라는 값이 자리잡게 된다. 그게 싫다면, 기본(default) 값을 추가하면 된다. 앞서 소개한 &lt;code&gt;ALTER TABLE&lt;/code&gt; 과 &lt;code&gt;ADD&lt;/code&gt; 문 뒤에 &lt;code&gt;DEFAULT 'any default title'&lt;/code&gt; 을 추가하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은, 테이블을 생성할때 키를 설정하는 방법이다. 두가지 키워드가 있다. PRIMARY KEY는 NULL을 허용하지 않으며, 테이블에 하나만 존재할 수 있다(여러개의 속성이 하나의 primary key를 이룰 수는 있다). UNIQUE는 NULL을 허용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법은 두가지가 있다. 속성을 정의할때, 그 데이터 타입 옆에 키워드를 붙이거나, 아예 별개의 키 정의 키워드를 선언문 말미에 붙이는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1745225114356&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE MovieStar (  
    name	CHAR(30)	PRIMARY KEY,
    address	VARCHAR(255),
    gender	CHAR(1),
    birthdate	DATE
);

# 또는

CREATE TABLE Movies (  
    title 	CHAR(100),
    producerCNum	INT,
    PRIMARY KEY (title, year)
);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.4 대수적 쿼리언어(An Algebraic Query Language)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 관계형 모델을 위한 특별한 대수학인 관계대수학(relational algebra)을 배운다. 이것은, 주어진 릴레이션으로부터 새로운 릴레이션을 생성하는 방법을 제시한다. 초창기 쿼리언어들은 이 대수학을 직접 차용하기도 했고, 현대의 SQL 또한 이 대수학에 문법적인 양념이 가해진(syntactically sugared) 것일 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C나 Java같은 훌륭한 프로그래밍 언어들이 있는데도, 왜 이런것을 배워야할까? 관계대수학에 비해 C나 Java는 너무 똑똑해서 그렇다. C나 Java는 관계대수학이 못하는 일들을 많이 할 수 있다. 예를 들어, 숫자의 홀짝을 판단하는 것 등이 있다. 그렇지만 관계대수학은 관계형 모델 아래서 동작하기에는 충분하며, 이런 부족한 능력이 오히려 두 가지 장점을 갖는다. 사용자에게는 프로그래밍의 편리함(ease of programming)을, DBMS에게는 컴파일러가 최적의 코드(highly optimized code)를 만들수 있게끔하는 능력을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대수학이 무엇인가? 대수학은 연산자(operators)와 어토믹 피연산자(atomic operands)로 구성된다. 예를 들어, 산수대수학(arithmetic algebra)은 변수 &lt;i&gt;x &lt;/i&gt;나 상수 &lt;i&gt;15&lt;/i&gt; 같은 피연산자와, 덧셈, 뺼셈 같은 연산자를 합쳐 &lt;i&gt;x+15 &lt;/i&gt;같은 표현식(expressions)을 만들어낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계대수학에서 피연산자는 변수 혹은 상수로써의 릴레이션들이다. 그렇다면 연산에는 무엇이 있는가? 크게 4종류로 나뉜다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;집합 연산(set operations): 합집합(union), 교집합(intersection), 차집합(difference)&lt;/li&gt;
&lt;li&gt;릴레이션의 일부를 제거함: 속성들을 지우는 사영(projection)과 튜플들을 지우는 선택(selection)&lt;/li&gt;
&lt;li&gt;서로 다른 릴레이션의 튜플을 합침: 가능한 모든 쌍을 짝짓는 데카르트곱(Cartesian Product), 선택적으로 짝짓는 조인(join)&lt;/li&gt;
&lt;li&gt;이름을 바꿈: 릴레이션이나 속성의 이름을 바꿈. 튜플에 변화는 없으나, 스키마에 변화는 생김&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 4종류의 연산들에 대해 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;집합 연산(set operations)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;합집합&lt;/b&gt;, &lt;b&gt;교집합&lt;/b&gt;, &lt;b&gt;차집합&lt;/b&gt;이 있다. 주의할 점은 연산하려는 두 집합의 스키마는 같아야한다. 속성들의 이름과 타입, 그리고 순서도 같아야 이 연산을 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQVKVq/btsNtriVoQp/QDAPZqu71jkUKR4bk3SLvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQVKVq/btsNtriVoQp/QDAPZqu71jkUKR4bk3SLvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQVKVq/btsNtriVoQp/QDAPZqu71jkUKR4bk3SLvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQVKVq%2FbtsNtriVoQp%2FQDAPZqu71jkUKR4bk3SLvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1708&quot; height=&quot;518&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제거하는 연산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사영(Projection)&lt;/b&gt;은 릴레이션 R로부터, 몇개의 속성들만을 취해서 새로운 릴레이션을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선택(Selection)&lt;/b&gt;은 relation R로부터, 어떤 조건 C를 만족하는 몇 개의 튜플들만을 취해서 새로운 릴레이션을 만든다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2134&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvVObQ/btsNt3uO2Sg/zbf9BoefR0VwBFS1hzGwZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvVObQ/btsNt3uO2Sg/zbf9BoefR0VwBFS1hzGwZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvVObQ/btsNt3uO2Sg/zbf9BoefR0VwBFS1hzGwZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvVObQ%2FbtsNt3uO2Sg%2Fzbf9BoefR0VwBFS1hzGwZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2134&quot; height=&quot;462&quot; data-origin-width=&quot;2134&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;곱하는 연산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데카르트곱(Cartesian Product, or cross-product, or product)&lt;/b&gt;은 두 relation R과 S 사이에 가능한 모든 쌍을 합친다. 만약, R과 S에 공통의 속성 A가 있다면, R.A, S.A로 합쳐진 후의 두 속성을 구분한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자연조인(Natural Join)&lt;/b&gt;은 두 Relation R과 S 간 공통의 속성들이 있어야 한다. 그 속성들이 같은 튜플들끼리 짝짓는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcdJqj/btsNtjrU3hN/zMW4PpedonE9nj1A9ocSI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcdJqj/btsNtjrU3hN/zMW4PpedonE9nj1A9ocSI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcdJqj/btsNtjrU3hN/zMW4PpedonE9nj1A9ocSI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcdJqj%2FbtsNtjrU3hN%2FzMW4PpedonE9nj1A9ocSI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1254&quot; height=&quot;520&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세타조인(Theta Join)&lt;/b&gt;은 주어진 조건 C를 만족하는 두 튜플을 짝짓는다. 공통의 속성이 있다면, 데카르트 곱처럼 R.A, S.A 같은 이름의 속성들이 생길 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8kNkw/btsNtjZJ7yJ/EICaOwlLTF1KRvUTY1AHv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8kNkw/btsNtjZJ7yJ/EICaOwlLTF1KRvUTY1AHv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8kNkw/btsNtjZJ7yJ/EICaOwlLTF1KRvUTY1AHv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8kNkw%2FbtsNtjZJ7yJ%2FEICaOwlLTF1KRvUTY1AHv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1354&quot; height=&quot;654&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이름바꾸는 연산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이션의 이름과 속성들의 이름을 바꿀 수 있다. 밑에는 새로운 스키마명과 속성들을, 항에는 릴레이션 명을 넣어 표현한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bltrfG/btsNt1D3O5s/5bWPM9hdhS2ZCeiKKORhQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bltrfG/btsNt1D3O5s/5bWPM9hdhS2ZCeiKKORhQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bltrfG/btsNt1D3O5s/5bWPM9hdhS2ZCeiKKORhQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbltrfG%2FbtsNt1D3O5s%2F5bWPM9hdhS2ZCeiKKORhQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;284&quot; height=&quot;49&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 4가지의 연산들은 합쳐져서 사용가능하다. 가령, &lt;b&gt;&amp;ldquo;What are the titles and years of movies made by Fox that are at least 100 minutes long?&amp;rdquo;&lt;/b&gt; 라는 쿼리는 이렇게 쪼갤 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Select those Movies tuples that have &lt;i&gt;length &amp;gt;&lt;/i&gt; 100&lt;/li&gt;
&lt;li&gt;Select those Movies tuples that have &lt;i&gt;studioName&lt;/i&gt; = &amp;rsquo;Fox&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Compute the intersection of (1) and (2).&lt;/li&gt;
&lt;li&gt;Project the relation from (3) onto attributes t i t l e and year.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이것은 아래의 표현 나무(expression tree)와 선형 표현식(linear notation)으로 나타낼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/44b9u/btsNr6rLQhM/8Yv2Lr8sqJLQa5LNPdGYm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/44b9u/btsNr6rLQhM/8Yv2Lr8sqJLQa5LNPdGYm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/44b9u/btsNr6rLQhM/8Yv2Lr8sqJLQa5LNPdGYm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F44b9u%2FbtsNr6rLQhM%2F8Yv2Lr8sqJLQa5LNPdGYm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;324&quot; height=&quot;289&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m50wc/btsNtM1qwEN/eJnXhkPnTps2gZLfrSTVG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m50wc/btsNtM1qwEN/eJnXhkPnTps2gZLfrSTVG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m50wc/btsNtM1qwEN/eJnXhkPnTps2gZLfrSTVG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm50wc%2FbtsNtM1qwEN%2FeJnXhkPnTps2gZLfrSTVG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;136&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 복잡한 선형 표현식을 단순화 하기 위해, 적절한 항끼리 묶어 임시 릴레이션으로 표기하면, 더욱 간결하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VfhY4/btsNufocX5A/nQTSMkhQwKxXgkM9MfpvS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VfhY4/btsNufocX5A/nQTSMkhQwKxXgkM9MfpvS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VfhY4/btsNufocX5A/nQTSMkhQwKxXgkM9MfpvS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVfhY4%2FbtsNufocX5A%2FnQTSMkhQwKxXgkM9MfpvS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;184&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 4가지 분류의 연산들을 소개했다. 그런데, 사실 이 중 3가지 연산은 나머지의 연산들로 표현이 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;교집합&lt;/li&gt;
&lt;li&gt;세타 조인&lt;/li&gt;
&lt;li&gt;자연 조인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 교집합(union), 차집합(difference), 선택(selection), 사영(projection), 곱(product), 개명(renaming), 이렇게 6개의 기본연산들은 서로서로 표현할 수 없는 독립적인 연산들이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.5 릴레이션의 제한조건(Constraints on Relations)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서, &amp;ldquo;키&amp;rdquo;에 관한 제한조건들은 살펴보았다. 이 절에서는 참조 무결성(referential-integrity) 제한조건를 추가로 소개한다. 이 조건은 한 릴레이션의 어느 한 속성이 다른 릴레이션의 속성에서도 발견될 때 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Movies relation과 MovieExec(영화 제작자, executive) relation에는 각각 producerC#, cert#(고유번호, certificate number) 이라는 속성이 있다. 우리는 이 데이터베이스의 Movies의 제작자 정보들은 MovieExec relation에도 존재할 것이라고 가정할 수 있다. 그렇지 않으면, 뭔가 문제가 있는 상황이라고 생각할 수 있다. 이걸 표현식으로 나타내면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nMdRW/btsNuB5nz8k/CpWDKzGYpb3VCY3VuKRmek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nMdRW/btsNuB5nz8k/CpWDKzGYpb3VCY3VuKRmek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nMdRW/btsNuB5nz8k/CpWDKzGYpb3VCY3VuKRmek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnMdRW%2FbtsNuB5nz8k%2FCpWDKzGYpb3VCY3VuKRmek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;96&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키에 관해서도 이런 관계대수 표현식으로 나타낼 수 있다. Name을 키로 삼고, address라는 속성을 갖는 MovieStar relation을 상상하자. Name은 고유한 키여야하기 때문에, 어떤 임의의 두 튜플의 name이 같다면, address는 물론 모든 속성의 다른 값들도 같아야 할 것이다. 즉, 두 튜플은 같은 튜플이여야만한다. 혹은, 임의의 두 튜플의 name은 같고, address는 다른 경우는 존재해서는 안된다. MovieStar relation의 두 복사본인 MS1, MS2 relation을 만들고, 우리는 아래 처럼 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beFQdr/btsNutmeGum/4adU3CKsXbKKtFErgBKmhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beFQdr/btsNutmeGum/4adU3CKsXbKKtFErgBKmhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beFQdr/btsNutmeGum/4adU3CKsXbKKtFErgBKmhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeFQdr%2FbtsNutmeGum%2F4adU3CKsXbKKtFErgBKmhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;633&quot; height=&quot;67&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MS1의 정의도 관계대수 표현식으로 나타내자면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rAj0w/btsNtPXdf5s/EMJwTYf4C7kyIK2b2PrYb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rAj0w/btsNtPXdf5s/EMJwTYf4C7kyIK2b2PrYb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rAj0w/btsNtPXdf5s/EMJwTYf4C7kyIK2b2PrYb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrAj0w%2FbtsNtPXdf5s%2FEMJwTYf4C7kyIK2b2PrYb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;505&quot; height=&quot;74&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 다양한 제한조건들을 관계대수 표현식으로 작성할 수 있다. 마지막 예시를 보자, MovieStar의 gender 속성은 반드시 &amp;lsquo;M&amp;rsquo; 혹은 &amp;lsquo;F&amp;rsquo; 값을 갖는다는 것은 아래처럼 표현된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zTqSy/btsNti0Nbuy/XrlNkWjD8xIOo9s4vvtevK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zTqSy/btsNti0Nbuy/XrlNkWjD8xIOo9s4vvtevK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zTqSy/btsNti0Nbuy/XrlNkWjD8xIOo9s4vvtevK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzTqSy%2FbtsNti0Nbuy%2FXrlNkWjD8xIOo9s4vvtevK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;73&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <category>데이터베이스</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/25</guid>
      <comments>https://junbyeol.tistory.com/25#entry25comment</comments>
      <pubDate>Mon, 21 Apr 2025 18:33:00 +0900</pubDate>
    </item>
    <item>
      <title>Github CLI로 터미널에서 git 인증 편하게 하기</title>
      <link>https://junbyeol.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;새로운 터미널 환경에서 새로운 레포지토리(repository)라도 클론(clone)받으려고 하면 인증이 참 귀찮다. 웹에서 쓰는 비밀번호를 입력하면, 아래의 이미지처럼 비밀번호 인증(Password Authentication)이 종료되었다는 오류로 실패하게 되기 떄문이다. 따로 깃헙의 개인 액세스 토큰(Personal Access Token)을 발급받거나, SSH key를 생성해야 하는데, 토큰의 권한도 관리해줘야하고, 여러모로 복잡하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wOtY9/btsMIGhiMMR/yAJJR6MXFhJAeFtCspXBi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wOtY9/btsMIGhiMMR/yAJJR6MXFhJAeFtCspXBi1/img.png&quot; data-alt=&quot;비밀번호 인증이 2021년에 종료되었다고 한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wOtY9/btsMIGhiMMR/yAJJR6MXFhJAeFtCspXBi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwOtY9%2FbtsMIGhiMMR%2FyAJJR6MXFhJAeFtCspXBi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1618&quot; height=&quot;180&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비밀번호 인증이 2021년에 종료되었다고 한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래서 나는 Github CLI로 로그인하는 방법을 선호한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Github CLI를 이용하면 여러가지 편리한 방법으로 터미널에서 로그인을 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1741858218607&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install gh // macOS
apt install gh // Linux

gh auth login&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHzXWU/btsMKQWwWLa/B5kEm1rZvJ43J6U33214U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHzXWU/btsMKQWwWLa/B5kEm1rZvJ43J6U33214U0/img.png&quot; data-alt=&quot;내가 사용한 옵션, 웹으로 연결돼서 아주 편하다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHzXWU/btsMKQWwWLa/B5kEm1rZvJ43J6U33214U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHzXWU%2FbtsMKQWwWLa%2FB5kEm1rZvJ43J6U33214U0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1144&quot; height=&quot;368&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내가 사용한 옵션, 웹으로 연결돼서 아주 편하다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 대화형 CLI는 몇가지 프롬프트 질문으로, 사용자가 편한 방법으로 로그인할 수 있게끔 해준다. 나는 웹 브라우저로 로그인하는 것을 선호한다.&lt;/p&gt;</description>
      <category>개발이야기/토막글</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/23</guid>
      <comments>https://junbyeol.tistory.com/23#entry23comment</comments>
      <pubDate>Thu, 13 Mar 2025 18:32:47 +0900</pubDate>
    </item>
    <item>
      <title>[이산구조] 명제(Proposition)</title>
      <link>https://junbyeol.tistory.com/22</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;&amp;nbsp;이 글은 Discrete Mathematics and its Applications by Kenneth H. Rosen, 8th Edition의 1.1~1.3 절을 재구성한 내용입니다.&lt;br /&gt;&lt;br /&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt; 이 글은 LaTeX 수식이 포함되어 있습니다. $\sum$ &amp;lt;- 이 기호가 제대로 보이지 않으면 새로고침 해주세요.&lt;/i&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명제(Proposition)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참/거짓을 판단할 수 있는 평서문(Declarative sentence)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;x+5=10&quot;은 명제가 아니다.&amp;nbsp; x의 값에 따라 참/거짓이 달라지므로, 문장만으로, 진위를 판단할 수 없기 때문이다. 나중에 다루지만, 이 문장은 predicate라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명제를 부정(Negation)하면 새로운 명제를 만들 수 있다. 혹은, 두개 이상의 명제를 합쳐서 하나의 새로운 명제(compound proposition)를 만들 수 있다. 이 때 쓰이는 논리연산자(logical operator)를 connectives라고도 부른다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span&gt; 명제 p의 부정(Negation)은 $\neg p$, $\overline{p}$, $\sim p$, !p, p', Np 로 표현한다. &lt;br /&gt;명제 p와 q의 논리곱(Conjunction)은 $ p \mathbin{\text{and}} q$이다. $p \wedge q$로 표현한다. p와 q 가 모두 참일때, 참이다.&lt;br /&gt;명제 p와 q의 논리합(Disjunction)은 $ p \mathbin{\text{or}} q$이다. $p \vee q$로 표현한다. p 또는 q 둘 중 하나가 참일때, 참이다.&lt;br /&gt;명제 p와 q의 배타적 논리합(Exclusive or)은 $ p \bigoplus q $ 혹은 $ p \mathbin{\text{XOR}} q $로 표현한다. p와 q의 참/거짓여부가 다를때, 참이다. &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조건문(Conditional Statements / Implication)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;p와 q가 명제일때, 조건문(conditional statement / implication) $ p \to q $ 는 명제 &quot;If p, then q&quot; 를 의미한다. 이 때, p를 가설(hypothsis), 혹은 전제(premise), 혹은 선행(antecedent)이라고 하고, q를 결론(conclusion), 혹은 결과(consequence)이라고 한다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 때, &lt;span style=&quot;text-align: left;&quot;&gt;$ p \to q $ 는 p는 참, q는 거짓일때만, 거짓이 된다. 즉, p가 거짓이라면 q의 진위여부는 &lt;span style=&quot;text-align: left;&quot;&gt;$ p \to q $ 의 값에 영향을 주지 않음에 주의한다. 또, 아래의 표현은 모두, &quot;If p, then q&quot;와 같은 의미이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;q if p&lt;/li&gt;
&lt;li&gt;p only if q&lt;/li&gt;
&lt;li&gt;when p, q&lt;/li&gt;
&lt;li&gt;q when p&lt;/li&gt;
&lt;li&gt;a necessary condition for p is q&lt;/li&gt;
&lt;li&gt;a sufficient condition for q is p&lt;/li&gt;
&lt;li&gt;p implies q&lt;/li&gt;
&lt;li&gt;q whenever p&lt;/li&gt;
&lt;li&gt;q provided that p&lt;/li&gt;
&lt;li&gt;p is sufficient for q&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;$ p \to q \mathbin{\text{and}} q \to p$ 를 $ p \leftrightarrow q $로 표현하며, 이중조건문(biconditional statement / bi-implication) &quot;p if and only if q&quot; 를 의미한다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;$ p \leftrightarrow q $ 는 &lt;span style=&quot;text-align: left;&quot;&gt;$ p \to q $와 &lt;span style=&quot;text-align: left;&quot;&gt;$ q \to p $ 의 논리곱이므로, p와 q가 모두 참이거나 거짓일때만 참이다. 전제(premise)가 거짓이면, 결론에 상관없이 전체 명제가 참이 되기 떄문이다. 아래는 또 다시 다 같은 의미이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;p is necessary and sufficient for q&lt;/li&gt;
&lt;li&gt;if p then q, and conversely&lt;/li&gt;
&lt;li&gt;p iff q&lt;/li&gt;
&lt;li&gt;p exactly when q&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ \mathrel{\neg}, \mathrel{\wedge}, \mathrel{\vee}, \mathrel{\to}, \mathrel{\leftrightarrow} $는 지금 나열한 순서대로 연산순서가 정해진다. 예를 들어, $ p \vee q \wedge r $은 $ (p \vee q) \wedge r $ 보다는 $ p \vee (q \wedge r) $ 로 연산되는 것이 일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명제적 동등성(Propositional Equivalence)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tautology&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;어떤 Compound Proposition을 구성하는 p, q등 변수들의 값에 관계없이, 항상 참이 되는 명제&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모순(Contradiction)&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;어떤 Compound Proposition을 구성하는 p, q등 변수들의 값에 관계없이, 항상 거짓인 명제&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 표는 가장 간단하게 만든 tautology와 contradiction의 예시이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 69px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 35px;&quot;&gt;$ p $&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 35px;&quot;&gt;$ \neg p $&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 35px;&quot;&gt;$ p \vee \neg p $&lt;br /&gt;(Tautology)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 35px;&quot;&gt;$ p \wedge \neg p $&lt;br /&gt;(Contradiction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;T&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;F&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;T&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;F&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;F&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;T&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;T&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;F&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;$ p \leftrightarrow q $ 가 tautology이면, p와 q는 동치(logically equivalent)라고 하고, $ p \equiv q $ 라고 표기한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역(Converse), 이(Inverse), 대우(Contrapositive / Transposition)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ p \to q $ 일때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역: $ q \to p $&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이: $ \neg p \to \neg q$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대우: $ \neg q \to \neg p$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 명제 $ p \to q $와 대우 $ \neg q \to \neg p$ 는 항상 동치이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 표는 기본적인 동치들이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mjWsW/btsMyTUWA8i/XOnrkqGuOWLQtPnL1tvcM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mjWsW/btsMyTUWA8i/XOnrkqGuOWLQtPnL1tvcM0/img.png&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;1472&quot; data-is-animation=&quot;false&quot; style=&quot;width: 28.4662%; margin-right: 10px;&quot; data-widthpercent=&quot;28.8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mjWsW/btsMyTUWA8i/XOnrkqGuOWLQtPnL1tvcM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmjWsW%2FbtsMyTUWA8i%2FXOnrkqGuOWLQtPnL1tvcM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1008&quot; height=&quot;1472&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0h6KN/btsMzPkbPs7/89gs4NK34AjY0twWgIVp01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0h6KN/btsMzPkbPs7/89gs4NK34AjY0twWgIVp01/img.png&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;866&quot; data-is-animation=&quot;false&quot; style=&quot;width: 70.371%;&quot; data-widthpercent=&quot;71.2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0h6KN/btsMzPkbPs7/89gs4NK34AjY0twWgIVp01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0h6KN%2FbtsMzPkbPs7%2F89gs4NK34AjY0twWgIVp01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1466&quot; height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만족가능(Satisfiable), 불능(Unsatisfiable)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명제를 참으로 만드는 변수들의 조합이 존재하면 만족가능(Satisfiable), 존재하지 않으면 불능(Unsatisfiable)이다.&lt;/p&gt;</description>
      <category>개인 공부</category>
      <category>명제</category>
      <category>이산구조</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/22</guid>
      <comments>https://junbyeol.tistory.com/22#entry22comment</comments>
      <pubDate>Sun, 2 Mar 2025 23:44:42 +0900</pubDate>
    </item>
    <item>
      <title>[전산기조직] 논리 디자인의 기초 - Combinational Logic 편</title>
      <link>https://junbyeol.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&amp;nbsp;이 글은 Computer Organization And Design: The Hardware/Software Interface, David A. Patterson and John L Hennessy 6th edition 중, Appdendix C: The Basics of Logic Design의 C.2~C.3를 읽고 재구성한 글입니다. 이 글 속의 이미지들도 이 책의 이미지를 사용했습니다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;code_1740902443647&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;i&amp;gt;이 글은 LateX 수식이 포함되어 있습니다. $ \sum $ &amp;lt;- 이 시그마 기호가 제대로 보이지 않으면 새로고침 해주세요.  &amp;lt;/i&amp;gt;&quot;&gt;&lt;i&gt;이 글은 LateX 수식이 포함되어 있습니다. $ \sum $ &amp;lt;- 이 시그마 기호가 제대로 보이지 않으면 새로고침 해주세요. &lt;/i&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;컴퓨터 세상의 신호는 디지털(Digital)로 통한다. 디지털은 모든 신호를 두 가지로 표현한다. &lt;b&gt;True, 1, asserted&lt;/b&gt; 등으로 표현하는 신호와 &lt;b&gt;false, 0, deasserted&lt;/b&gt; 등으로 표현하는 신호이다. 이 신호는 회로 위에서 고전압(High voltage)과 저전압(Low voltage)로 구현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우리는 지금부터 다양한 논리 블럭(Logic Block)들을 알아 볼 것이다. 논리 블럭은 &lt;b&gt;크게 두 종류로 나눌 수 있다.&lt;/b&gt; 메모리(Memory)가 있는 것과 없는 것이다. 논리 블럭에서 나오는 출력(Output)이 입력(Input)에만 의존한다면, 그 블럭은 메모리가 없는 블럭이라고 할 수 있다. 우리는 이것을 &lt;b&gt;Combinational Logic&lt;/b&gt; 이라고 한다. 반면, 논리 블럭의 상태(State)에 따라 같은 입력에도 다른 출력을 반환할 수도 있다. 이 상태가 저장되는 곳을 우리는 메모리라고 부르고, 입력과 상태 모두에 출력이 의존하는 회로를 &lt;b&gt;sequential logic&lt;/b&gt; 이라고 부른다. 이번 글에서는 Combinational Block들의 예시를 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Boolean Algebra&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다양한 logic block들을 알기 이전에 Boolean Algebra에 대한 기호 약속이 필요하다. 먼저 3가지 가장 기본적인 연산을 정의한다. 우리가 다룰 모든 logic block들은 이 연산들만을 가지고 모두 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;code_1740901477933&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;span&amp;gt;
AND: $ out = A \cdot B = AB $
&amp;lt;br/&amp;gt;
OR: $ out = A + B $
&amp;lt;br/&amp;gt;
NOT: $ out = \overline{A} $
&amp;lt;/span&amp;gt;&quot;&gt;&lt;span&gt; AND: $ out = A \cdot B = AB $ &lt;br /&gt;OR: $ out = A + B $ &lt;br /&gt;NOT: $ out = \overline{A} $ &lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위 3가지 연산은 이렇게 gate 기호로 표현된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxXuq9/btsMzsuWGSw/bwRVwbosnUuC9FFK1HaHc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxXuq9/btsMzsuWGSw/bwRVwbosnUuC9FFK1HaHc1/img.png&quot; data-alt=&quot;AND, OR, NOT gate&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxXuq9/btsMzsuWGSw/bwRVwbosnUuC9FFK1HaHc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxXuq9%2FbtsMzsuWGSw%2FbwRVwbosnUuC9FFK1HaHc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;549&quot; height=&quot;81&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AND, OR, NOT gate&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래의 기본적인 law들을 따른다. 다른 law들은 익숙하나 distributative law에서 or 연산을 분배하는 규칙이 낯설 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;code_1740901805776&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;span&amp;gt;
Identity law: $A + 0 = A$ and $A \cdot 1 = A$.
&amp;lt;br/&amp;gt;
Zero and One laws: $A + 1 = 1$ and $A \cdot 0 = 0$.
&amp;lt;br/&amp;gt;
Inverse laws: $A + \overline{A} = 1$ and $A \cdot \overline{A} = 1$.
&amp;lt;br/&amp;gt;
Commutative laws: $A + B = B + A$ and $A \cdot B = B \cdot A$.
&amp;lt;br/&amp;gt;
Associative laws: $A + (B + C) = (A + B) + C$ and $A \cdot (B \cdot C) = (A \cdot B) \cdot C$.
&amp;lt;br/&amp;gt;
Distributive laws: $A \cdot (B + C) = (A \cdot B) + (A \cdot C)$ and $A + (B \cdot C) = (A + B) \cdot (A + C)$. 
&amp;lt;br/&amp;gt;
DeMorgans' laws: $\overline{A + B} = \overline{A} \cdot \overline{B}$ and $ \overline{AB} = \overline{A} + \overline{B}$.
&amp;lt;/span&amp;gt;&quot;&gt;&lt;span&gt; Identity law: $A + 0 = A$ and $A \cdot 1 = A$. &lt;br /&gt;Zero and One laws: $A + 1 = 1$ and $A \cdot 0 = 0$. &lt;br /&gt;Inverse laws: $A + \overline{A} = 1$ and $A \cdot \overline{A} = 1$. &lt;br /&gt;Commutative laws: $A + B = B + A$ and $A \cdot B = B \cdot A$. &lt;br /&gt;Associative laws: $A + (B + C) = (A + B) + C$ and $A \cdot (B \cdot C) = (A \cdot B) \cdot C$. &lt;br /&gt;Distributive laws: $A \cdot (B + C) = (A \cdot B) + (A \cdot C)$ and $A + (B \cdot C) = (A + B) \cdot (A + C)$. &lt;br /&gt;DeMorgans' laws: $\overline{A + B} = \overline{A} \cdot \overline{B}$ and $ \overline{AB} = \overline{A} + \overline{B}$. &lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;두 가지 신기한 연산을 추가한다. NAND 와 NOR 이다. 신기하게도, NAND 와 NOR 둘 중 하나의 연산만 가지고도 위의 기본적인 AND, OR, NOT 3가지 연산을 모두 구현할 수 있다. 즉, NAND나 NOR gate 둘 중 하나만 있으면 우리가 앞으로 다룰 모든 logic block들을 모두 구현할 수 있다. 그래서 우리는 NAND와 NOR 연산을 &lt;b&gt;universal&lt;/b&gt; 하다고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;code_1740903111595&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;span&amp;gt;
$ \mathbin{\text{nand}} $: $ A \mathbin{\text{nand}} B = \overline{A \mathbin{\text{and}} B} $ &amp;lt;br/&amp;gt;
$ \mathbin{\text{nor}} $: $ A \mathbin{\text{nor}} B = \overline{A \mathbin{\text{or}} B} $ &amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
$ \mathop{\text{not}} A = A \mathbin{\text{nand}} A = A \mathbin{\text{nor}} A $ &amp;lt;br/&amp;gt;
$ A \mathbin{\text{and}} B = \overline{A \mathbin{\text{nand}} B} = \overline{A} \mathbin{\text{nor}} \overline{B} $ &amp;lt;br/&amp;gt;
$ A \mathbin{\text{or}} B = \overline{A} \mathbin{\text{nand}} \overline{B} = \overline{A \mathbin{\text{nor}} B} $ &amp;lt;br/&amp;gt;

&amp;lt;/span&amp;gt;&quot;&gt;&lt;span&gt; $ \mathbin{\text{nand}} $: $ A \mathbin{\text{nand}} B = \overline{A \mathbin{\text{and}} B} $ &lt;br /&gt;$ \mathbin{\text{nor}} $: $ A \mathbin{\text{nor}} B = \overline{A \mathbin{\text{or}} B} $ &lt;br /&gt;&lt;br /&gt;$ \mathop{\text{not}} A = A \mathbin{\text{nand}} A = A \mathbin{\text{nor}} A $ &lt;br /&gt;$ A \mathbin{\text{and}} B = \overline{A \mathbin{\text{nand}} B} = \overline{A} \mathbin{\text{nor}} \overline{B} $ &lt;br /&gt;$ A \mathbin{\text{or}} B = \overline{A} \mathbin{\text{nand}} \overline{B} = \overline{A \mathbin{\text{nor}} B} $ &lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 연산 정의도 끝났으니, 이제 combination logic block들을 하나씩 알아본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Decoder&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디코더는 n개의 입력과 2&lt;sup&gt;n&lt;/sup&gt;개의 출력을 가지고 있다. 디코더는 이 입력 값에 따라 2&lt;sup&gt;n&lt;/sup&gt;개의 출력 중 하나만을 true 로 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 3개의 입력과 8개의 출력을 갖는 아래의 디코더는 입력을 마치 3bit의 2진수처럼 해독하여, 원하는 Out Port 에만 신호를 보낼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebgAaf/btsMz665gd3/t76upwkE6r5qYXOtN5Hba0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebgAaf/btsMz665gd3/t76upwkE6r5qYXOtN5Hba0/img.png&quot; data-alt=&quot;디코더(Decoder)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebgAaf/btsMz665gd3/t76upwkE6r5qYXOtN5Hba0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebgAaf%2FbtsMz665gd3%2Ft76upwkE6r5qYXOtN5Hba0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2250&quot; height=&quot;688&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디코더(Decoder)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Multiplexor&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플렉서(Mux)는 여러개의 입력 중에서 하나의 입력 값만을 출력으로 반환한다. 아래 그림에서 A와 B 입력에는 각각 0과 1이 input으로 들어오고 있다. 그리고 S(Selector)의 값이 0인지 1인지에 따라서, 출력인 C의 값은 A와 같은 0이 될수도, B와 같은 1이 될수도 있다. 아래의 회로를 논리식으로 표현하면 아래와 같다. 또, 아래의 예시에서는 셀렉터가 S 하나였기에 두 개의 입력만 다룰 수 있었지만, 셀렉터가 n개로 확장된다면, 그만큼 입력 또한, 2&lt;sup&gt;n&lt;/sup&gt;개로 늘려서 확장할 수 있다. 디코더를 이용하여 n개의 셀렉터 값들을 2&lt;sup&gt;n&lt;/sup&gt;개의 출력으로 반환한후, 멀티플렉서의 입력과 1대1로 AND 연산 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;code_1740904745481&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;span&amp;gt;
$$ C = A \cdot S + B \cdot \overline{S} $$
&amp;lt;/span&amp;gt;&quot;&gt;&lt;span&gt; $$ C = A \cdot S + B \cdot \overline{S} $$ &lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMwTSV/btsMAkcXbWp/HUnZca2LqheRNeNi2LAEsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMwTSV/btsMAkcXbWp/HUnZca2LqheRNeNi2LAEsK/img.png&quot; data-alt=&quot;멀티플렉서(Multiplexor, Mux)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMwTSV/btsMAkcXbWp/HUnZca2LqheRNeNi2LAEsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMwTSV%2FbtsMAkcXbWp%2FHUnZca2LqheRNeNi2LAEsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;257&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;멀티플렉서(Multiplexor, Mux)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Progammable Logic Array(PLA)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서, 거의 모든 logic function은 AND, OR, NOT 3가지 기본 연산으로 구현가능하다고 했다. 실은, 더 일반적으로 모든 logic function들은 합의 곱(Product of Sums)이나 곱의 합(Sum of Products) 같은 정규식(Canonical Form)으로 표현가능하다. 예를 들어, A, B, C 3개의 입력 중 정확히 2개의 입력이 true일 때 true를 반환하는 출력 E를 생각해보자. 아래 식처럼 표현 할 수 있다. 아래의 등식이 성립하는 것은 앞서 언급했던 boolean algebra의 law들을 이용하여 확인가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;code_1740905744447&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;span&amp;gt;
$$
E = ((A \cdot B) + (A \cdot C) + (B \cdot A)) \cdot (\overline{A \cdot B \cdot C}) \\
  = (A \cdot B \cdot \overline{C}) + (A \cdot C \cdot \overline{B}) + (B \cdot C \cdot \overline{A}) \\ 
 = \overline{(\overline{A} + \overline{B} + C)\cdot(\overline{A} + B + \overline{C})\cdot(A + \overline{B} + \overline{C})}
$$
&amp;lt;/span&amp;gt;&quot;&gt;&lt;span&gt; $$ E = ((A \cdot B) + (A \cdot C) + (B \cdot A)) \cdot (\overline{A \cdot B \cdot C}) \\ = (A \cdot B \cdot \overline{C}) + (A \cdot C \cdot \overline{B}) + (B \cdot C \cdot \overline{A}) \\ = \overline{(\overline{A} + \overline{B} + C)\cdot(\overline{A} + B + \overline{C})\cdot(A + \overline{B} + \overline{C})} $$ &lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 줄의 식이 곱의 합(Sum of Products), 세번째 줄의 식이 합의 곱(Product of Sums)이다. 보통의 우리가 다루는 다항식과 가까운 모양인 곱의 합이 major한 정규식으로 널리 쓰인다. 이런 정규식으로 표현했을 때 큰 이점이 있는데, 아래 그림의 회로처럼 일반적으로 모든 회로를 그려낼 수 있다는 것이다. 이 회로를 우리는 PLA(Programmable Logic Array)라고 부른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0tazM/btsMyB1f6JE/eajdfPUAZhZgluPhBg0CHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0tazM/btsMyB1f6JE/eajdfPUAZhZgluPhBg0CHK/img.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;786&quot; data-is-animation=&quot;false&quot; width=&quot;486&quot; height=&quot;378&quot; style=&quot;width: 41.0315%; margin-right: 10px;&quot; data-widthpercent=&quot;41.51&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0tazM/btsMyB1f6JE/eajdfPUAZhZgluPhBg0CHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0tazM%2FbtsMyB1f6JE%2FeajdfPUAZhZgluPhBg0CHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1010&quot; height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NNr9h/btsMA5sSuJB/7ai7pREDFuU4VD56rLXu3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NNr9h/btsMA5sSuJB/7ai7pREDFuU4VD56rLXu3K/img.png&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;854&quot; data-is-animation=&quot;false&quot; style=&quot;width: 57.8057%;&quot; data-widthpercent=&quot;58.49&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NNr9h/btsMA5sSuJB/7ai7pREDFuU4VD56rLXu3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNNr9h%2FbtsMA5sSuJB%2F7ai7pREDFuU4VD56rLXu3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1546&quot; height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;일반적인 PLA 모식도와 실제 구현예시, 오른쪽 그림에서 E가 앞서 예시로 든 E와 같다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개인 공부</category>
      <category>전산기조직</category>
      <category>회로</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/21</guid>
      <comments>https://junbyeol.tistory.com/21#entry21comment</comments>
      <pubDate>Sun, 2 Mar 2025 18:02:13 +0900</pubDate>
    </item>
    <item>
      <title>[정보보호개론] 보안의 속성(CIA triad, AAA triad)</title>
      <link>https://junbyeol.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 보안의 속성은 CIA triad 와 AAA triad로 설명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CIA triad는 Confientiality(기밀성), Integrity(무결성),&amp;nbsp; Availability(가용성),&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AAA triad는 Assurance(보장성), Authenticity(진위여부), Anonymity(익명성) 을 포함한다.*&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CIA triad&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3가지 속성을 잘 갖춘 서비스가 보안이 튼튼한 서비스라고 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Confidentiality(기밀성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인증되지 않은 사람이 데이터를 읽지 못하게 해야함을 의미한다. 기밀성은 3가지 방법으로 구현될 수 있다. 첫째로 암호화(Encryption)는 대칭/비대칭 키를 이용하여 데이터의 내용을 숨긴다. 둘째로 인증(Authentication)은 유저의 신원을 확인하는 과정이다. 셋째로 접근제어(Access Control)은 권한에 따라 누가, 어떤 자원에, 어떻게 접근가능한지를 조절한다. 리눅스에서 chmod로 유저/리소스 마다 rwx로 나뉘는 권한을 관리하는 것이 예시이다. 인가(Authorization) 또한 접근제어에 포함되는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Integrity(무결성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;데이터가 인증되지 않은 방법으로 변조되어서는 안된다. 데이터의 변조에는 온건한 변조(Benign Compromise)와 위험한 변조(Malicious Compromise)가 있다. 온건한 변조는 단순한 사고로 인해 데이터가 변조되는 것을 말하고, 위험한 변조는 공격자(attackers)에 의해 데이터가 변조되는 것을 말한다. 백업(Backup)과 체크섬(Checksum) 등이 무결성을 확보하는 방법의 예시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Availability(가용성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;사용자가 시기적절하게(in a timely fashion) 서비스를 이용할 수 있어야한다. 24시간 동작한다고 했던 서비스가 갑자기 접근이 되지 않는 다면 가용성 확보에 실패한것이다. 이게 왜 보안에 속하는지 의문이 들 수 있는데, 아주 어려운 비밀번호를 설정한 아주 단단한 금고를 화성에 묻어놓았다고 하자. 이러면, 어떤 공격자들도 이 금고안의 데이터를 접근할 엄두를 못내기야 하겠지만, 사용자들 조차 이용할 수 없으므로 좋은 보안이 아닌것이다. 서비스들은 물리적인 재난에 대응하고, 전산이중화(&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Computational redundancies) 대책을 세우는 등의 방법으로 가용성을 확보해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;AAA triad&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현대 컴퓨터 보안에서 중요시하게 여겨지는 속성 3가지이다. Netflix, Youtube 등의 현대(Modern) IT 서비스들을 생각하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Assurance(보장성)&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;유저와 서비스, 상호간의 신뢰가 보장되어야 한다. 보장성은 3가지 방법으로 확보할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;- 정책(Policy): 고객에게 어떻게 서비스를 제공할 것 인가? (e.g. 유저마다 다른 요금제)&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 권한 부여(Permission): 인증된 고객만이 서비스를 이용 가능한가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보호(Protection): 어떻게 남용(Abuse)을 방지할것인가? (e.g. ddos 공격으로부터 가용성 확보)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Authenticity(진위여부)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디지털 서명(Digital Signature)등의 방법으로 눈속임(repudiation, 오리발)을 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Anonymity(익명성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인이 서비스를 이용하는 것이 특정되면 안된다. 개인의 의료정보나 연락처 등 개인정보나, 개인의 서비스 이용기록 등을 숨겨야한다. 집계(Aggregation)로, 평균/총합 등으로 집단을 표현하는 것, 프록시(Proxy) 서버로 유저와 애플리케이션 서버를 직접 연결하지 않는것, 가명(Pseudonyms)을 사용하는 것&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;* 이 글에서 설명하는 AAA triad는 major하게 사용되는 의미는 아니다. Authentication, Authorization, Accounting이 더 널리 쓰이는 약칭이다.&lt;/i&gt;&lt;/p&gt;</description>
      <category>개인 공부</category>
      <category>AAA</category>
      <category>CIA</category>
      <category>정보보호개론</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/20</guid>
      <comments>https://junbyeol.tistory.com/20#entry20comment</comments>
      <pubDate>Fri, 28 Feb 2025 16:23:23 +0900</pubDate>
    </item>
    <item>
      <title>TinyMCE로 이미지/파일 다루기</title>
      <link>https://junbyeol.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;게시글 작성 화면을 구현하려면, WYSIWYG 에디터를 사용하게 된다. ChatGPT의 추천으로 나는 TinyMCE라는 에디터를 선택했다. 이 에디터를 이용해 게시판 작성 화면을 구현하면서, 이미지와 파일을 다루는 방법에 대해 내가 작업한 내용을 이 글에서 소개한다. 나도 대부분의 코드 작성을 claude-3.5-sonnet(cursor AI)이라는 친구에게 맡기고, 이후 생긴 버그들을 디버깅한 정도라, 코드에는 개선의 여지가 더 많을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 사진/파일 업로드 하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제상황&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FhU6k/btsMw6FB7SR/UuKKRtev7PNkIR8GO72rM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FhU6k/btsMw6FB7SR/UuKKRtev7PNkIR8GO72rM1/img.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;710&quot; data-is-animation=&quot;false&quot; width=&quot;476&quot; height=&quot;268&quot; style=&quot;width: 49.2428%; margin-right: 10px;&quot; data-widthpercent=&quot;49.82&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FhU6k/btsMw6FB7SR/UuKKRtev7PNkIR8GO72rM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFhU6k%2FbtsMw6FB7SR%2FUuKKRtev7PNkIR8GO72rM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sO7B7/btsMwpFtT77/Ga1cUWQBRt9NCOHeGerWD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sO7B7/btsMwpFtT77/Ga1cUWQBRt9NCOHeGerWD1/img.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;772&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.5944%;&quot; data-widthpercent=&quot;50.18&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sO7B7/btsMwpFtT77/Ga1cUWQBRt9NCOHeGerWD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsO7B7%2FbtsMwpFtT77%2FGa1cUWQBRt9NCOHeGerWD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1382&quot; height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;TinyMCE의 이미지업로더(ImageUploadPicker) 와 파일업로더(FileUploadPicker)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;TinyMCE의 이미지업로더(ImageUploadPicker)와 파일업로더(FileUploadPicker) 기능을 활용하고자 한다면, 이미지/파일을 어딘가에 업로드하고, 그 업로드된 URL을 반환해주는 핸들러 함수가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 아래의 코드로 위 목적을 달성했다. 더보기 버튼을 누르면 펼쳐진다. 설명하자면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. firestore/storage에 이미지와 파일을 저장하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이미지의 캐시 주기를 1년으로 했다(목적이 있어서 매우 길게 설정했다).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. plugins와 toolbar에 image, file 관련 필요한 것들을 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이미지는 images_upload_handler, 파일은 file_picker_callback 인자를 통해 tinymce-react.Editor에게 전달해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1740472323028&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Editor } from '@tinymce/tinymce-react';
import { getStorage, uploadBytes, getDownloadURL } from 'firebase/storage';

...

function RichTextEditor({ value = '', onChange }: RichTextEditorProps) {
    const handleImageUpload = async (blobInfo: any) =&amp;gt; {
        const storage = getStorage();
        const fileName = `images/${Date.now()}-${blobInfo.filename()}`;
        const storageRef = ref(storage, fileName);

        const metadata = {  
          cacheControl: 'public,max-age=31536000' // cache-control 헤더의 캐싱 주기를 1년으로 설정함
        };

        try {
          const snapshot = await uploadBytes(storageRef, blobInfo.blob(), metadata);
          const url = await getDownloadURL(snapshot.ref);
          return url;
        } catch (error) {
          console.error('이미지 업로드 실패:', error);
          throw error;
        }
      };

      const handleFileUpload = async (file: File) =&amp;gt; {
        const storage = getStorage();
        const fileName = `files/${Date.now()}-${file.name}`;
        const storageRef = ref(storage, fileName);

        try {
          const snapshot = await uploadBytes(storageRef, file);
          return await getDownloadURL(snapshot.ref);
        } catch (error) {
          console.error('파일 업로드 실패:', error);
          throw error;
        }
      };
	...
    return (
    &amp;lt;Editor
		...
      init={{
		...
        plugins: [
          ..., 'image', 'media'
        ],
        toolbar: ... + 'image | link | file',
        images_upload_handler: handleImageUpload, // 이미지 핸들링 함수 추가
        file_picker_types: 'file image media',
        file_browser_callback_types: 'file',
        automatic_uploads: true,
        file_picker_callback: function(callback, _, meta) {
          const input = document.createElement('input');
          input.setAttribute('type', 'file');
          
          if (meta.filetype === 'image') {
            input.setAttribute('accept', 'image/*');  
          }
          
          input.onchange = async function() {
            const file = (input.files as FileList)[0];
            try {
              const url = await handleFileUpload(file); // 파일 핸들링 함수 추가
              console.log('파일 업로드 완료:', {
                fileName: file.name,
                fileUrl: url,
                fileType: meta.filetype
              });
              callback(url, { text: file.name });
            } catch (error) {
              console.error('파일 업로드 실패:', error);
            }
          };
          
          input.click();
        },
      }}
	/&amp;gt;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 파일 업로드 시, 기본 &amp;lt;a&amp;gt; 태그 대신 커스텀 태그로 대체하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 글 안에 첨부하고 싶을 수 있다. 하지만, TinyMCE는 파란글자에 밑줄로 파일이름만 보여주고, 티스토리 처럼 아래의 이쁜 요소로 보여주지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WDW3J/btsMwyPScWy/tkcS4VGo8kEsEYuk3FkJXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WDW3J/btsMwyPScWy/tkcS4VGo8kEsEYuk3FkJXk/img.png&quot; data-alt=&quot;티스토리 에디터에서 파일을 글에 첨부하면 생기는 DOM 요소. 파일을 첨부하면 이런 박스가 생긴다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WDW3J/btsMwyPScWy/tkcS4VGo8kEsEYuk3FkJXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWDW3J%2FbtsMwyPScWy%2FtkcS4VGo8kEsEYuk3FkJXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;516&quot; height=&quot;137&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;티스토리 에디터에서 파일을 글에 첨부하면 생기는 DOM 요소. 파일을 첨부하면 이런 박스가 생긴다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에디터의 내용이 수정될때마다 호출되는 함수인 onEditorChange에서, FileUploadPicker의 소행으로 인해 삽입된 a태그를 인식하여, 내가 원하는 DOM 요소로 교체하도록 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 코드와 주석을 참고할 수 있다. 더보기 버튼을 누르면 펼쳐진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. extractNewFiles 함수는 수정된 컨텐츠와, 수정 이전 컨텐츠를 비교하여 새로운 url이 추가되었는지를 검사한다. 그리고 isFilePickerUrl 함수를 이용하여, 그 URL이 FileUploadPicker의 동작으로 추가된 URL인지를 검사한다. 이 때, URL만 반환하지 않고, FileInfo라는 인터페이스로 변환해서 반한해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. isFilePickerUrl 함수는 앞서 설명했듯, URL이 FileUploadPicker의 동작으로 추가된 URL인지를 검사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. escapeRegExp는 특수문자를 다루는 것을 돕는 함수이다. onEditorChange 내부에서 사용하는 곳을 보면 href 안의 주소를 인자로 사용하고 있다. 주소에는 ?, . 등 특수문자들이 있는데 백슬래시(\)를 붙여, 특수문자가 특수한 의미로 해석되지 않고 그냥 문자 그 자체로 인식되도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 앞서 설명한 도구 함수들을 이용해서 onEditorChange 함수를 구현한다. 대체해야될 태그를 발견하면 그 안의 URL과 title 정보만 추출하여, 새로운 커스텀 태그로 재구성 하여 삽입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1740472353327&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface FileInfo {
  url: string;
  title: string;
}

// 필요한 도구 함수 1 
const extractNewFiles = (currentContent: string, prevContent: string): FileInfo[] =&amp;gt; {
  // 파일 업로더를 통해 추가된 링크 패턴 찾기
  const fileUploaderPattern = /&amp;lt;a[^&amp;gt;]*href=&quot;([^&quot;]+)&quot;[^&amp;gt;]*&amp;gt;(.*?)&amp;lt;\/a&amp;gt;/g;
  
  const currentMatches = Array.from(currentContent.matchAll(fileUploaderPattern));
  const prevMatches = Array.from(prevContent.matchAll(fileUploaderPattern));
  
  const currentFiles = currentMatches
    .map(match =&amp;gt; ({
      url: match[1],
      title: match[2]
    }))
    .filter(file =&amp;gt; isFilePickerUrl(file.url));
    
  const prevUrls = new Set(
    prevMatches
      .map(match =&amp;gt; match[1])
      .filter(url =&amp;gt; isFilePickerUrl(url))
  );
  
  return currentFiles.filter(file =&amp;gt; !prevUrls.has(file.url));
};

// 필요한 도구 함수 2
const isFilePickerUrl = (url: string): boolean =&amp;gt; {
  // file_picker에서 추가된 URL인지 확인하는 로직
  return url.includes('o/files') &amp;amp;&amp;amp; url.startsWith('https://firebasestorage.googleapis.com');
};

// 필요한 도구 함수 3
const escapeRegExp = (string: string): string =&amp;gt; {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&amp;amp;');
};

// Editor에 Props로 넘겨줌

function RichTextEditor({ value = '', onChange }: RichTextEditorProps) {
  return (
	&amp;lt;Editor
        onEditorChange={(content, editor) =&amp;gt; {
            const currentContent = editor.getContent();
            const prevContent = value || '';

            // 새로운 파일이 추가되었는지 확인
            const newFiles = extractNewFiles(currentContent, prevContent);

            if (newFiles.length &amp;gt; 0) {
              console.log('새로운 파일 링크가 추가됨:', newFiles);

              let updatedContent = currentContent;
              newFiles.forEach(({url, title}) =&amp;gt; {
                if (isFilePickerUrl(url)) {
                  const customElement = `&amp;lt;div class=&quot;custom-file-element&quot; 
                    style=&quot;
                      border: 1px solid #e0e0e0; 
                      border-radius: 8px; 
                      padding: 8px 16px;
                      margin: 8px 0;
                      display: flex;
                      align-items: center;
                      gap: 8px;
                      cursor: pointer;
                      background-color: #f8f9fa;
                      max-width: fit-content;
                    &quot;
                    contenteditable=&quot;false&quot;
                    data-url=&quot;${url}&quot;
                    onclick=&quot;window.open('${url}', '_blank')&quot;
                  &amp;gt;
                    &amp;lt;span class=&quot;file-icon&quot; style=&quot;font-size: 1.2em;&quot;&amp;gt; &amp;lt;/span&amp;gt;
                    &amp;lt;span class=&quot;file-name&quot; style=&quot;color: #2c3e50;&quot;&amp;gt;${title}&amp;lt;/span&amp;gt;
                  &amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;`;

                  const regex = new RegExp(`&amp;lt;p&amp;gt;\\s*&amp;lt;a[^&amp;gt;]*href=&quot;${escapeRegExp(url)}&quot;[^&amp;gt;]*&amp;gt;.*?&amp;lt;\\/a&amp;gt;\\s*\`?\\d*\\s*&amp;lt;\\/p&amp;gt;`, 'g');
                  updatedContent = updatedContent.replace(regex, customElement);
                }
              });

              editor.setContent(updatedContent);
              onChange(updatedContent);
              return;
            }

            onChange(content);
          }}
      /&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;항상 이런 외부 라이브러리를 사용하는 것은 어렵다. 특히 위지윅 에디터를 사용할때면 문서를 꼼꼼히 읽고 골치아픈 디버깅을 매번 하게 되는 것 같다. 그런 누군가를 위해 내 위지윅 관련 코드 전체를 올리는 것으로 글을 마무리한다. 더보기 버튼을 누르면 펼쳐진다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1740472251327&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Editor } from '@tinymce/tinymce-react';
import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage';

interface RichTextEditorProps {
  value?: string;
  onChange: (content: string) =&amp;gt; void;
}

interface FileInfo {
  url: string;
  title: string;
}

const extractNewFiles = (currentContent: string, prevContent: string): FileInfo[] =&amp;gt; {
  // 파일 업로더를 통해 추가된 링크 패턴 찾기
  const fileUploaderPattern = /&amp;lt;a[^&amp;gt;]*href=&quot;([^&quot;]+)&quot;[^&amp;gt;]*&amp;gt;(.*?)&amp;lt;\/a&amp;gt;/g;
  
  const currentMatches = Array.from(currentContent.matchAll(fileUploaderPattern));
  const prevMatches = Array.from(prevContent.matchAll(fileUploaderPattern));
  
  const currentFiles = currentMatches
    .map(match =&amp;gt; ({
      url: match[1],
      title: match[2]
    }))
    .filter(file =&amp;gt; isFilePickerUrl(file.url));
    
  const prevUrls = new Set(
    prevMatches
      .map(match =&amp;gt; match[1])
      .filter(url =&amp;gt; isFilePickerUrl(url))
  );
  
  return currentFiles.filter(file =&amp;gt; !prevUrls.has(file.url));
};

const isFilePickerUrl = (url: string): boolean =&amp;gt; {
  // file_picker에서 추가된 URL인지 확인하는 로직
  return url.includes('o/files') &amp;amp;&amp;amp; url.startsWith('https://firebasestorage.googleapis.com');
};

const escapeRegExp = (string: string): string =&amp;gt; {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&amp;amp;');
};

function RichTextEditor({ value = '', onChange }: RichTextEditorProps) {
  const handleImageUpload = async (blobInfo: any) =&amp;gt; {
    const storage = getStorage();
    const fileName = `images/${Date.now()}-${blobInfo.filename()}`;
    const storageRef = ref(storage, fileName);
    
    const metadata = {  
      cacheControl: 'public,max-age=31536000' // 1년
    };
    
    try {
      const snapshot = await uploadBytes(storageRef, blobInfo.blob(), metadata);
      const url = await getDownloadURL(snapshot.ref);
      return url;
    } catch (error) {
      console.error('이미지 업로드 실패:', error);
      throw error;
    }
  };

  const handleFileUpload = async (file: File) =&amp;gt; {
    const storage = getStorage();
    const fileName = `files/${Date.now()}-${file.name}`;
    const storageRef = ref(storage, fileName);
    
    try {
      const snapshot = await uploadBytes(storageRef, file);
      return await getDownloadURL(snapshot.ref);
    } catch (error) {
      console.error('파일 업로드 실패:', error);
      throw error;
    }
  };

  return (
    &amp;lt;Editor
      apiKey={import.meta.env.VITE_TINYMCE_API_KEY}
      value={value}
      init={{
        height: 500,
        menubar: true,
        plugins: [
          'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
          'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
          'insertdatetime', 'media', 'table', 'help', 'wordcount'
        ],
        extended_valid_elements: 'span[*],div[*|onclick|style|class|contenteditable|data-url]',
        toolbar: 'undo redo | formatselect | ' +
          'bold italic backcolor | alignleft aligncenter ' +
          'alignright alignjustify | bullist numlist outdent indent | ' +
          'removeformat | image | link | file',
        images_upload_handler: handleImageUpload,
        file_picker_types: 'file image media',
        file_browser_callback_types: 'file',
        automatic_uploads: true,
        file_picker_callback: function(callback, _, meta) {
          const input = document.createElement('input');
          input.setAttribute('type', 'file');
          
          if (meta.filetype === 'image') {
            input.setAttribute('accept', 'image/*');  
          }
          
          input.onchange = async function() {
            const file = (input.files as FileList)[0];
            try {
              const url = await handleFileUpload(file);
              console.log('파일 업로드 완료:', {
                fileName: file.name,
                fileUrl: url,
                fileType: meta.filetype
              });
              callback(url, { text: file.name });
            } catch (error) {
              console.error('파일 업로드 실패:', error);
            }
          };
          
          input.click();
        },
      }}
      onEditorChange={(content, editor) =&amp;gt; {
        const currentContent = editor.getContent();
        const prevContent = value || '';

        // 새로운 파일이 추가되었는지 확인
        const newFiles = extractNewFiles(currentContent, prevContent);
        
        if (newFiles.length &amp;gt; 0) {
          console.log('새로운 파일 링크가 추가됨:', newFiles);
          
          let updatedContent = currentContent;
          newFiles.forEach(({url, title}) =&amp;gt; {
            if (isFilePickerUrl(url)) {
              const customElement = `&amp;lt;div class=&quot;custom-file-element&quot; 
                style=&quot;
                  border: 1px solid #e0e0e0; 
                  border-radius: 8px; 
                  padding: 8px 16px;
                  margin: 8px 0;
                  display: flex;
                  align-items: center;
                  gap: 8px;
                  cursor: pointer;
                  background-color: #f8f9fa;
                  max-width: fit-content;
                &quot;
                contenteditable=&quot;false&quot;
                data-url=&quot;${url}&quot;
                onclick=&quot;window.open('${url}', '_blank')&quot;
              &amp;gt;
                &amp;lt;span class=&quot;file-icon&quot; style=&quot;font-size: 1.2em;&quot;&amp;gt; &amp;lt;/span&amp;gt;
                &amp;lt;span class=&quot;file-name&quot; style=&quot;color: #2c3e50;&quot;&amp;gt;${title}&amp;lt;/span&amp;gt;
              &amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;`;
            
              const regex = new RegExp(`&amp;lt;p&amp;gt;\\s*&amp;lt;a[^&amp;gt;]*href=&quot;${escapeRegExp(url)}&quot;[^&amp;gt;]*&amp;gt;.*?&amp;lt;\\/a&amp;gt;\\s*\`?\\d*\\s*&amp;lt;\\/p&amp;gt;`, 'g');
              updatedContent = updatedContent.replace(regex, customElement);
            }
          });
          
          editor.setContent(updatedContent);
          onChange(updatedContent);
          return;
        }
        
        onChange(content);
      }}
      onInit={() =&amp;gt; {  
        console.log('Editor is ready');

        if (import.meta.env.VITE_TINYMCE_API_KEY) {
            console.log('API Key is set');
        } else {
            console.log('API Key is not set');
        }
      }}
    /&amp;gt;
  );
}

export default RichTextEditor;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>개발이야기/토막글</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/19</guid>
      <comments>https://junbyeol.tistory.com/19#entry19comment</comments>
      <pubDate>Tue, 25 Feb 2025 17:31:04 +0900</pubDate>
    </item>
    <item>
      <title>속터지는 Google Play Console 본인인증하기</title>
      <link>https://junbyeol.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 적자면 &lt;b&gt;주민등록등본&lt;/b&gt;을 내니까 성공했다.&lt;br /&gt;&lt;br /&gt;Google 플레이스토어에 내가 만든 앱을 업로드하려면, 개발자 계정을 추가해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 개발자 계정을 생성하는데에 25달러를 요구했고(하필 환율도 높을 때...), 주소지 입력, 결제계정 생성 등 꽤나 답답한 과정을 거친 후에 도착하는 최종관문이 바로 &quot;본인인증&quot;이다. 이 본인인증이 답답한 이유는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 실제 거주지의 전기/수도 요금 명세서나 은행 명세서 등의 서류를 요구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 캡쳐/스크린샷이면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 서류에 반드시 실제 본인의 이름과 주소지가 들어가 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 90일 이내여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 아마 사람이 직접 승인하는 구조라, 근무일에만 승인된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 대충 되겠지 생각하고, 도시가스 명세서 화면을 캡쳐해서 보냈는데, 반려당했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 5번 정도 아래의 본인인증 실패 화면을 본 이후에야, 겨우 본인인증을 받을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하필, 설 연휴가 껴있어서... 본인인증을 &quot;도전&quot; 하고 매번 결과를 받아보는데도 한참이 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말이 5번이지 실제론 2주 정도가 걸렸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czSlyB/btsL7AhlrGH/hjJd65XH66dHqsXe7RML00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czSlyB/btsL7AhlrGH/hjJd65XH66dHqsXe7RML00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czSlyB/btsL7AhlrGH/hjJd65XH66dHqsXe7RML00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczSlyB%2FbtsL7AhlrGH%2FhjJd65XH66dHqsXe7RML00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;496&quot; height=&quot;380&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 서류 준비가 어려웠던 이유는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 나는 공동주택에 전세살이 중이라 전기, 수도 등 관리비 청구서 명의가 내 명의가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 나는 신용카드를 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 농협은행의 체크카드를 사용하는데 온라인으로 받아볼 수 있는 명세서에 주소지가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 임대 계약서는 90일이 지났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;90일은 지났지만 내 실명과 주소지가 임대차 계약서와, 실명은 없지만 주소지는 확실한 90일 이내의 관리비 청구서를 합쳐서 보내도 기각당했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어, 파일을 두 개 올릴 수 있도록 되어있지도 않아서, 이미지들을 따로 pdf로 묶어서 보냈는데도.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 정부24에서 발급받은 주민등록등본을 제출했더니, 다음날 오전 본인인증완료 메일을 받을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피곤한 놈들..&lt;/p&gt;</description>
      <category>개발이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/18</guid>
      <comments>https://junbyeol.tistory.com/18#entry18comment</comments>
      <pubDate>Thu, 6 Feb 2025 12:50:22 +0900</pubDate>
    </item>
    <item>
      <title>Cursor AI 사용 후기 - 위기의 내 밥그릇</title>
      <link>https://junbyeol.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;약 한 달간 Cursor AI를 사용해보았습니다. 간단한 React 웹 페이지와 NestJS CRUD 서버, 그리고 React-Native 앱의 거의 개발 착수 단계에 사용하였습니다. 결론부터 말하자면, 새로운 프로젝트를 시작할 때에 있어서는 너무나도 편리하고, 압도적으로 생산성을 늘려줄 수 있는 도구라고 느꼈습니다. Gpt나 Github Copilot을 처음 경험했을 때보다도 큰 충격을 준 도구였습니다. 이 글에서는 Cursor AI에 대한 간단한 소개와 느낀 점들을 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;cursor.jpg&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lb8TY/btsLMsB4RJ6/5jL2iks8V427KdhdzaZu60/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lb8TY/btsLMsB4RJ6/5jL2iks8V427KdhdzaZu60/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lb8TY/btsLMsB4RJ6/5jL2iks8V427KdhdzaZu60/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flb8TY%2FbtsLMsB4RJ6%2F5jL2iks8V427KdhdzaZu60%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;686&quot; height=&quot;386&quot; data-filename=&quot;cursor.jpg&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cursor AI란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.cursor.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cursor AI&lt;/a&gt;는 VScode의 포크 프로젝트이며, 갖가지 AI 기능이 탑재되어 있습니다. 제가 주로 사용한 기능들은 아래와 같습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Tab - line 수정 제안&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성하다보면 Cursor가 특정 라인의 코드 수정을 제안합니다. Tab 키를 눌러서, 이 제안을 바로 코드에 적용할 수 있습니다. 이를 통해 indentation이나, 반복적인 작업들을 Tab키 한 번으로 대신할 수 있습니다. Copilot과 거의 동일한 동작입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cmd + K - Code Chunk 관련 AI에게 질문하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드라인을 드래그하고, Cmd + K를 누르면 AI에게 코드 관련 질문을 할 수 있습니다. 주로 &quot;이 부분의 에러를 해결해줘&quot; 같은 목적으로 사용합니다. AI는 코드의 수정을 제안하고, 이 수정을 받아들이거나, 후속 질문을 이어갈 수도 있습니다. 개인적인 경험으로, 정확도는 아래에서 소개할 Cmd+L 기능이 더 높은 것 같아, 잘 사용하지 않게 되는 기능이였습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cmd + L - Codebase로 AI와 채팅하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cursor AI의 가장 핵심 기능입니다. 간단히 설명하자면, Claude 혹은 GPT 모델의 LLM 서버와 chat을 하는 것에 불과하지만, 웹에서 이용하는 것보다 훨씬 많은 Context를 넘겨줄 수 있어서 아주 강력합니다. Chat 모델에게 내 코드의 파일, 디렉토리 전체, 외부 웹사이트 docs, 이미지 등을 넘겨주며 구체적인 요구사항을 전달하면 꽤 높은 정확도로 답변을 생성합니다. 이렇게 생성된 답변은 단계적으로 Accept 할 수 있습니다. 터미널에서 명령어 실행, 새로운 디렉토리와 파일 생성 및 새로운 코드 추가 등등의 작업도 답변을 accept하기만 하면 거의 사람이 하는 일이 없었습니다. 후속질문 또한 Cursor가 제시한 단계들 각각에 할 수 있어서, 깜빡 잊은 요구사항이 있다면 수정을 요청할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ei3fk/btsLLRI4JSo/oZCQ2MPP9OVBplLMs7bKV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ei3fk/btsLLRI4JSo/oZCQ2MPP9OVBplLMs7bKV1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;364&quot; data-filename=&quot;스크린샷 2025-01-12 오후 11.03.45.png&quot; style=&quot;width: 61.7003%; margin-right: 10px;&quot; data-widthpercent=&quot;62.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ei3fk/btsLLRI4JSo/oZCQ2MPP9OVBplLMs7bKV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEi3fk%2FbtsLLRI4JSo%2FoZCQ2MPP9OVBplLMs7bKV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;792&quot; height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWuQDu/btsLK3cfAd6/mIjMsZCTJhX3Ri7sT606rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWuQDu/btsLK3cfAd6/mIjMsZCTJhX3Ri7sT606rk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;562&quot; data-filename=&quot;스크린샷 2025-01-12 오후 11.04.07.png&quot; style=&quot;width: 37.1369%;&quot; data-widthpercent=&quot;37.57&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWuQDu/btsLK3cfAd6/mIjMsZCTJhX3Ri7sT606rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWuQDu%2FbtsLK3cfAd6%2FmIjMsZCTJhX3Ri7sT606rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;736&quot; height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;질문을 적으면 Cursor가 코드들을 제시한다. 이후에는 Accept 딸깍딸깍만 하면 끝&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Vscode와 거의 유사하여, 입문 후 적응하는데 cost가 거의 없다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vscode의 단축키와 레이아웃, extension들을 모두 공유하기 때문에 처음 설치하고 사용법을 익히는데 필요한 시간이 거의 없었습니다. Cursor AI의 기능들도 사용법이 단순하고, 기존 AI 툴들과 크게 다르지 않기 때문에 익히는데에 큰 시간이 들지 않았습니다. 어떻게 하면 AI가 더 나은 답변을 주는지 정도의 학습이 필요하나, 코드를 작성하며 사용해보면서 알아가는 재미가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 프로젝트 초기 코드 작성시 압도적인 퍼포먼스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cursor의 가장 강력한 장점으로 느껴집니다. Github Copilot은 단순한 반복작업이나 작은 모듈함수를 작성하는데에 특화되어있어서, 내귀찮은 작업들에 손을 빌려주는 도구 같은 느낌에 불과했습니다. 다만, Cursor는 터미널 명령어를 실행하고, 수정해야할 코드 위치에 찾아가서 코드를 수정해주고, 파일을 생성하기도 해준다는 점에서 코드 생산속도의 차원을 높인 느낌이였습니다. 참고할 Codebase들을 context로 넘겨줄 수 있어서 정확도도 꽤나 높았습니다. React, React Native, tailwind css, nestjs 등의 프레임워크/라이브러리에 대한 답변도 잘 해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 Cursor를 활용하여 React native 앱을 하나 개발해보았는데, 약 4시간동안의 대부분을 Cursor에게 제가 짜고 싶은 코드를 설명하는 일과, 그 코드를 accept한 후 실행해보고 검수하는 작업만 하면서 보내도 꽤 괜찮은 앱이 금세 나올 수 있었습니다. 코드에 문제가 전혀 없지는 않아서, 안드로이드와 웹 환경에서 지속적으로 코드를 실행하며 확인하고 디버깅하는 과정을 거쳐야하기는 했지만, 프론트엔드의 귀찮은 state 관리 작업과 스타일링/퍼블리싱 작업을 거의 다 날로 먹을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 가격(?)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT의 한 달 구독료는 20달러인데, Cursor AI의 한 달 구독료도 같습니다. 어차피 ChatGPT를 구독하려고 한다면, Cursor AI를 구독해보는 것을 매우 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 속도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 적용 속도가 약간 답답한 느낌이 있습니다. 기분좋게 탁탁 적용되지는 않고, 약간의 딜레이가 소요됩니다. 특히, Tab 으로 이용하는 Line 수정 제안 기능의 속도가 좀 답답했습니다. 여러 줄의 수정 제안을 빨리 받고, 바로바로 적용하고 싶은데 마치 높이가 애매한 계단을 걷듯이 애매한 속도가 한국인에게는 조금 아쉬웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 프로젝트를 시작하고자 한다면, 프로젝트 세팅 시간과 초기 코드 작성 시간을 압도적으로 줄여줄 수 있는 도구입니다. AI가 어디까지 발전할지, 앞으로 나의 밥그릇을 얼마나 빼앗아갈지 저를 한 번 더 걱정하게 만들었습니다. 앞으로는, 이런 도구들을 빠르게 내 것으로 흡수하고 적재적소에 활용할 줄 아는 능력이 더욱 중요해지겠구나를 다시 한 번 실감하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/17</guid>
      <comments>https://junbyeol.tistory.com/17#entry17comment</comments>
      <pubDate>Sun, 12 Jan 2025 23:31:14 +0900</pubDate>
    </item>
    <item>
      <title>나의 2025년 목표: 매주 창작하는 사람 되기</title>
      <link>https://junbyeol.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;나의 2025년 목표는 1주일에 적어도 하나씩 컨텐츠를 창작하는 사람이 되는것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텐츠의 범주에는 여러가지가 포함된다. 글, 영상, 코드 모든 것이 다 가능하고, 컨텐츠의 퀄리티는 나의 양심에 맡긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적으로는 이런것들이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;블로그 글&lt;/li&gt;
&lt;li&gt;유튜브 영상/숏폼&lt;/li&gt;
&lt;li&gt;개인 웹/앱 프로젝트 개발&lt;/li&gt;
&lt;li&gt;그 외...&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 하나라도 매주 창작하는 것이 목표다. 원래 목표를 이루려면 여기저기 입방정을 떨고다녀야 한다고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 말쯤, 이 글을 다시 보게 되었을 때, 형편없는 목표 달성률에 창피할수도 있겠지만, 일단 이 글을 통해 공표한다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 이런 목표를 생각하게 되었는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5년이 안되게 직장생활을 하며 느낀 점은, 월급쟁이 생활은 매우 힘들다는 것이다. 매일 9시간 이상을 사무실에서 남들과 조별과제를 하며 지내는 일을 즐기는 사람은 적어도 나는 아닌듯하다. 하는 일, 동료, 급여 등의 토끼들을 모두 잡아주는, 적어도 타협할 수 있는 평생직장을 찾는다면 더할 나위없이 좋겠으나, 쉽지 않을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;168&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KVylE/btsLE5tQXDM/1AEQEMVydkynziGhyv2av0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KVylE/btsLE5tQXDM/1AEQEMVydkynziGhyv2av0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KVylE/btsLE5tQXDM/1AEQEMVydkynziGhyv2av0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKVylE%2FbtsLE5tQXDM%2F1AEQEMVydkynziGhyv2av0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;168&quot; height=&quot;300&quot; data-origin-width=&quot;168&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리랜서의 삶.. 상상해보면 정말 좋을 것 같다. 자유로운 시간관리, 출퇴근 스트레스로부터의 해방, 시키는 일이 아닌 스스로 찾아서 하는 일을 한다는 자부심 등이 이유이다. 프리랜서 개발자를 검색해보면 그 꿈을 이룬 사람들의 이야기도 꽤 찾아볼 수 있다. 특히, &lt;a href=&quot;https://soulduse.tistory.com/&quot;&gt;프로그래밍 좀비&lt;/a&gt; 님의 글들은 나에게 정말 큰 자극을 주었다. 내가 꿈꾸는 라이프스타일을 실현하고 계셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 할 수 있을까? 솔직히 안될 것 같다. 내가 생각해도 철없는 생각처럼 느껴진다. 그렇지만 적어도, 도전은 해봐야겠다고 생각했다. 그래서 이런 2025년 목표를 설정했다. 그래야 나중에 직장생활을 다시 시작하더라도 후회가 남지 않을 것 같다. 꼭 프리랜서가 되지 않더라도, 내가 올해 창작한 것들을 돌아보면 나중에 큰 힘이 되지 않을까 싶다. 목표를 이뤄가는 과정에서 얻어가는 습관과 능력들도 훗날 나에게 도움이 될 것이라 믿는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 목표를 달성하면 무엇이 좋은가?&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 창작 능력을 기를 수 있다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;살아오면서 느낀 점이 있다. 대충 만든 컨텐츠는 남들도 봐주지 않는다. 성의있고 내 기준에 정말 잘 만든 컨텐츠일지라도, 사람들은 관심이 없을 수 있다. 사람들의 반응을 이끌어낼 수 있는 컨텐츠를 만드는 제작자가 되고 싶다. 그 능력을 얻기 위해, 필요한 시행착오들을 겪어보고 싶다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 내가 만든 앱/웰을 사람들이 쓰면 좋을 것 같다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 우연히 새로 알게 된 사람이 내가 만든 앱을 사용하고 있는 유저라면 정말 뿌듯할 것 같다. 그 사람도 놀라지 않을까?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 사람들과 교류하고 싶고 인정받을 수 있다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그에 첫 글을 쓴지 1년이 넘었는데, 한 번 써놓은 글이 꾸준히 조회수가 나오고 댓글도 종종 달리는 걸 보면 굉장히 신기하다. 하지만, 내 블로그의 글을 보러 오는 사람들은 한 번 들어온 사람들이다. 나를 꾸준히 지켜봐주는 사람들이 있다면, 그 자체로 엄청난 동기부여가 될 것 같다. 그런 사람들을 만들기 위해서는 내가 꾸준히 활동해야 할 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 돈을 벌수 있다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 작은 돈이지만, 블로그에 광고를 다니 돈이 들어오는 것을 경험했다. 직장인들 월급만큼의 수익을 올려주는 앱/웹 프로젝트를 만들 수 있다면, 정말 좋겠지만, 매우 어려울 것이다. 내가 &lt;a href=&quot;https://jeho.page/about/&quot;&gt;k리그 프로그래머&lt;/a&gt;님의 커피한잔 같은 앱을 만들 수 있다면 얼마나 좋을까! 이런 목표는 어렵겠지만, 적어도 용돈벌이가 될 수준의 프로젝트는 만들 수 있을지도 모른다는 근자감이 있다. 일단 타석에 100번 서면 홈런은 못쳐도 안타 한 번쯤은 나오겠지! 일단 타석에 100번 설 수 있을 끈기를 가진 사람이 먼저 되어보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 내 몸값을 올릴 수 있다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;저 사람이 그거 만든 사람이야?&quot; 같은 소리를 듣고 싶다. 지금의 내 인생이야기야 뭐, 남들에게 밥이나 술을 사주면서 들려줘도 관심이 없겠지만, 내 이야기를 듣고 싶어하는 사람이 생긴다면, 투머치토커인 나는 얼마든지 해줄 수 있다. 강단에 서거나, 유명 유튜브 채널에 등장하는 나를 상상해본다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 실행하는 습관을 들일 수 있다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상상만 하고 행동으로 옮기지 않으면 그냥 망상이다. 실행하는 것도 습관이라고 믿는다. 완벽하지 않아도 일단 할 줄 아는 사람이 되자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 목표 달성을 성공할 수 있을 것 같은 이유&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 폭넓은 개발 경험&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 직장에서 웹과 앱, 크롬 익스텐션이나 데스크탑 앱을 모두 만들어본 적 있고, 프론트/백부터 인프라 레벨까지 폭넓게 경험해봤다. 나의 이 얕지만 폭넓은 경험이 장점으로 발휘된다면 좋겠다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 남은 학기가 주는 동기부여&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 대학생이고, 졸업까지 3학기가 남았다. 아마 이 3학기가 끝나갈때쯤이면 나는 취업준비를 하고 있어야 할 것이다. 다시 직장생활이 시작된다면 이런 목표를 설정하는 것은 불가능할 것이다. 올해가 도전해볼 수 있는 내 인생의 마지막 한 해가 될지도 모른다는 위기감이 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 항상 그 생각밖에 안함&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌이켜보면 거의 초등학생/중학생 때 부터 아는것과 공부하는 것은 쥐뿔도 없지만, 소프트웨어 개발에 막연한 관심은 많았다. 소프트웨어를 개발할 수 있는 능력을 어느정도 갖추면서, 시도때도 없이 항상 프로젝트 아이템을 상상했다. 뭔가 살다가 불편한게 생기거나, 재밌어보이는게 있으면 개발을 시작하는 망상으로 이어진다. 그렇게 평소에 생각해둔 것들이 많으니, 이제 수행만 하면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 목표 달성을 실패할 것 같은 이유&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 게으름/의지력의 부족&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말로만 하는 것은 뭐든 쉽다. 행동하는 사람이 되자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 운동욕심&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wexzG/btsLELh5UT6/r4DwfdABZqCzG3jUXOKB8k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wexzG/btsLELh5UT6/r4DwfdABZqCzG3jUXOKB8k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wexzG/btsLELh5UT6/r4DwfdABZqCzG3jUXOKB8k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwexzG%2FbtsLELh5UT6%2Fr4DwfdABZqCzG3jUXOKB8k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;341&quot; height=&quot;507&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몸도 안좋으면서 운동욕심은 왜 이렇게 많은지 모르겠다. 운동을 즐기되, 운동만으로 하루를 만족해버리는 사람은 되지 말자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 습관들음을 방해하는 것들&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;건강이 안좋아지면, 악순환에 빠진다. 끼니를 제 때 챙겨먹고, 너무 늦게자지 말고, 과음하지 말고, 잘 쉬는 사람이 되자.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 2025년 1주차, 야심차게 이 글로써 컨텐츠쟁이의 시작을 다짐한다.&lt;/p&gt;</description>
      <category>생각과 기록</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/16</guid>
      <comments>https://junbyeol.tistory.com/16#entry16comment</comments>
      <pubDate>Sun, 5 Jan 2025 16:35:02 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발시, 휴대폰과 맥북을 와이파이 환경에서 무선연결하기</title>
      <link>https://junbyeol.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 개발을 할 때, 안드로이드 휴대폰을 맥북과 연결해서 사용합니다. 하지만 유선연결은 여러가지 이유로 불편하죠.&lt;br /&gt;이 글에서는 두 기기를 무선으로 연결해보겠습니다. 아래의 과정을 따라갑니다.&lt;br /&gt;미리 말하자면, 휴대폰과 맥북이 같은 와이파이 위에 있어야합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;adb(Android Debugger Bridge) 설치&lt;/li&gt;
&lt;li&gt;휴대폰 무선 디버깅 켜고 페어링/연결&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. adb 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;adb는 Android Debugger Bridge의 약자로, 안드로이드 개발환경과 안드로이드 실기기의 연결을 도와주는 cli 입니다.&lt;br /&gt;안드로이드 스튜디오를 설치하고, 설치 과정 중 sdk도 정상적으로 설치되었다면, 이미 설치가 되어있을것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에 &lt;code&gt;adb&lt;/code&gt; 를 입력했을 때, adb의 버전넘버와 매뉴얼이 나온다면 이미 사용가능한 상태입니다.&lt;br /&gt;만약, 그렇지 않다면 &lt;code&gt;~/Library/Android/sdk/platform-tools&lt;/code&gt; 경로를 확인하여, adb 바이너리가 있는지 확인합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;adb가 있다면.. adb의 경로가 환경변수로 등록되어있지 않은 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;adb가 이미 로컬에 설치되어 있는 것이므로, 환경변수에 등록해주면 됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 쉘 환경에 따라 ~/.bashrc 혹은 ~/.zshrc에 아래의 라인을 추가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;export PATH=$HOME/Library/Android/sdk/platform-tools:$PATH&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;adb가 없다면.. adb가 설치되지 않은 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안드로이드 스튜디오 내에서 sdk를 다운 받을 수 있습니다.&lt;/li&gt;
&lt;li&gt;더 구체적인 방법은 이 글에서 다루지 않겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 휴대폰 무선 디버깅 켜고 페어링/연결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 개발자모드 켜기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 &amp;gt; 휴대전화 정보 &amp;gt; 소프트웨어 정보 &amp;gt; 빌드번호 로 가서 빌드번호를 7번 탭하면, 숨겨져 있던 &quot;개발자 옵션&quot; 항목이 생깁니다.&lt;br /&gt;여기서 USB 디버깅이 유선 연결을 할때 켜줘야하는 설정이고, 우리가 할 것은 무선 디버깅입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIWaPS/btsLoKqKwiH/0MYk4rwC8Hn6sPzc6mJXw0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIWaPS/btsLoKqKwiH/0MYk4rwC8Hn6sPzc6mJXw0/img.jpg&quot; width=&quot;346&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;931&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;59.81&quot; style=&quot;width: 59.1157%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIWaPS/btsLoKqKwiH/0MYk4rwC8Hn6sPzc6mJXw0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIWaPS%2FbtsLoKqKwiH%2F0MYk4rwC8Hn6sPzc6mJXw0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;931&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IOiG6/btsLoC7nOwM/dInxuv5KhQs90R2KPRLq8K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IOiG6/btsLoC7nOwM/dInxuv5KhQs90R2KPRLq8K/img.jpg&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;1383&quot; data-is-animation=&quot;false&quot; width=&quot;353&quot; style=&quot;width: 39.7215%;&quot; data-widthpercent=&quot;40.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IOiG6/btsLoC7nOwM/dInxuv5KhQs90R2KPRLq8K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIOiG6%2FbtsLoC7nOwM%2FdInxuv5KhQs90R2KPRLq8K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1078&quot; height=&quot;1383&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. 페어링하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QR 코드와 페어링 코드 두 가지 방법으로 페어링 할 수 있습니다. QR 코드는 안드로이드 스튜디오에서 확인 할 수 있습니다.&lt;br /&gt;이 글에서는 페어링 코드 방법으로 연결해봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RUXM8/btsLozW9trE/7yN3WitOcU84fKPXBOEsLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RUXM8/btsLozW9trE/7yN3WitOcU84fKPXBOEsLk/img.png&quot; width=&quot;333&quot; height=&quot;740&quot; data-filename=&quot;edited_004.jpeg&quot; data-origin-height=&quot;867&quot; data-origin-width=&quot;645&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.1435%; margin-right: 10px;&quot; data-widthpercent=&quot;35.56&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RUXM8/btsLozW9trE/7yN3WitOcU84fKPXBOEsLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRUXM8%2FbtsLozW9trE%2F7yN3WitOcU84fKPXBOEsLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;867&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba2vdF/btsLoqe2RQL/SiADlGAwG8JDt4422Qh4AK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba2vdF/btsLoqe2RQL/SiADlGAwG8JDt4422Qh4AK/img.jpg&quot; width=&quot;320&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;801&quot; data-origin-width=&quot;1080&quot; style=&quot;width: 63.6937%;&quot; data-widthpercent=&quot;64.44&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba2vdF/btsLoqe2RQL/SiADlGAwG8JDt4422Qh4AK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba2vdF%2FbtsLoqe2RQL%2FSiADlGAwG8JDt4422Qh4AK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;801&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;192.168.xxx.xxx ip는 제 방 로컬 NAT ip이므로 허튼생각하셔도 별 수 없습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페어링 코드로 기기 페어링을 누르면 임시 페어링 정보가 뜹니다. 터미널에 오른쪽 ip:port 를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 이미지와 오른쪽 이미지의 포트 넘버가 다르므로 주의해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페어링 코드까지 입력한 후, 페어링이 잘 되었는지 확인까지 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734597151930&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; adb pair 192.168.0.130:44767 # 오른쪽 이미지에 있는 ip:addr
&amp;gt; Enter pairing code: (페어링 코드 6자리 입력)

&amp;gt; adb devices
&amp;gt; (페어링된 디바이스 정보 노출, 아무것도 안나오면 페어링이 안 된 것)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2-3. 연결하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 왼쪽 이미지의 ip:port를 입력하면 연결이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1734597306113&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; adb connect 192.168.0.130:44513 # 왼쪽 이미지에 있는 ip:port&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 완료!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 안드로이드 스튜디오에서 코드를 수정하고, 오른쪽의 실행 버튼으로 내 연결한 기기에서 개발 결과물을 즉시 확인 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1USM/btsLoJS1tru/LIocggTgdr1D3jdh44NNL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1USM/btsLoJS1tru/LIocggTgdr1D3jdh44NNL1/img.png&quot; data-alt=&quot;연결 성공!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1USM/btsLoJS1tru/LIocggTgdr1D3jdh44NNL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1USM%2FbtsLoJS1tru%2FLIocggTgdr1D3jdh44NNL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;373&quot; height=&quot;161&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;연결 성공!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발이야기/토막글</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/15</guid>
      <comments>https://junbyeol.tistory.com/15#entry15comment</comments>
      <pubDate>Thu, 19 Dec 2024 17:37:48 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes 사용자라면 설치해야 할 보조 도구 모음</title>
      <link>https://junbyeol.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스와 kubectl 커맨드 관련 작업을 행복하게 만들어주는 보조 도구들을 소개합니다. 쿠버네티스를 처음 사용하거나, 새로 개발 환경을 세팅해야 할 때, 읽어보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;k9s&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://k9scli.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://k9scli.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704533039289&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;K9s - Manage Your Kubernetes Clusters In Style&quot; data-og-description=&quot;Who Let The Pods Out? K9s is a terminal based UI to interact with your Kubernetes clusters. The aim of this project is to make it easier to navigate, observe and manage your deployed applications in the wild. K9s continually watches Kubernetes for changes &quot; data-og-host=&quot;k9scli.io&quot; data-og-source-url=&quot;https://k9scli.io/&quot; data-og-url=&quot;https://k9scli.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cOQ2pL/hyUXWoxCeC/wFpzkWi0JcwbKV7hfXh5P1/img.png?width=2520&amp;amp;height=1320&amp;amp;face=0_0_2520_1320,https://scrap.kakaocdn.net/dn/bbmFED/hyU2rmRXDD/y6WovLYacincxofDRkslD0/img.png?width=2524&amp;amp;height=1316&amp;amp;face=0_0_2524_1316,https://scrap.kakaocdn.net/dn/tipx5/hyUXVJWCK5/jdx3NjnAp9U0J9teigZIOk/img.png?width=2514&amp;amp;height=1318&amp;amp;face=0_0_2514_1318&quot;&gt;&lt;a href=&quot;https://k9scli.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://k9scli.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cOQ2pL/hyUXWoxCeC/wFpzkWi0JcwbKV7hfXh5P1/img.png?width=2520&amp;amp;height=1320&amp;amp;face=0_0_2520_1320,https://scrap.kakaocdn.net/dn/bbmFED/hyU2rmRXDD/y6WovLYacincxofDRkslD0/img.png?width=2524&amp;amp;height=1316&amp;amp;face=0_0_2524_1316,https://scrap.kakaocdn.net/dn/tipx5/hyUXVJWCK5/jdx3NjnAp9U0J9teigZIOk/img.png?width=2514&amp;amp;height=1318&amp;amp;face=0_0_2514_1318');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;K9s - Manage Your Kubernetes Clusters In Style&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Who Let The Pods Out? K9s is a terminal based UI to interact with your Kubernetes clusters. The aim of this project is to make it easier to navigate, observe and manage your deployed applications in the wild. K9s continually watches Kubernetes for changes&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;k9scli.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널 환경에서 interactive하게 쿠버네티스 클러스터를 만지기 편리한 터미널 UI 툴입니다. 수차례의 kubectl 명령어를 간단한 단축키로 수행할 수 있습니다. 쿠버네티스 사용자라면 필수라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kubectx &amp;amp; kubens&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/ahmetb/kubectx&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/ahmetb/kubectx&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704533182108&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - ahmetb/kubectx: Faster way to switch between clusters and namespaces in kubectl&quot; data-og-description=&quot;Faster way to switch between clusters and namespaces in kubectl - GitHub - ahmetb/kubectx: Faster way to switch between clusters and namespaces in kubectl&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ahmetb/kubectx&quot; data-og-url=&quot;https://github.com/ahmetb/kubectx&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCc7IF/hyU2eA3D0k/F6X8Ns4qkei3N9YUxmq0gK/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_136_1057_201&quot;&gt;&lt;a href=&quot;https://github.com/ahmetb/kubectx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ahmetb/kubectx&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCc7IF/hyU2eA3D0k/F6X8Ns4qkei3N9YUxmq0gK/img.png?width=1200&amp;amp;height=600&amp;amp;face=997_136_1057_201');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - ahmetb/kubectx: Faster way to switch between clusters and namespaces in kubectl&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Faster way to switch between clusters and namespaces in kubectl - GitHub - ahmetb/kubectx: Faster way to switch between clusters and namespaces in kubectl&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 context와 namespace를 쉽게 변경할 수 있도록 도와주는 cli 툴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kubecolor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/hidetatz/kubecolor/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/hidetatz/kubecolor/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704533226083&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - hidetatz/kubecolor: colorizes kubectl output&quot; data-og-description=&quot;colorizes kubectl output. Contribute to hidetatz/kubecolor development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/hidetatz/kubecolor/&quot; data-og-url=&quot;https://github.com/hidetatz/kubecolor&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NjeJq/hyUXSsSlFI/1ojlp8wwDXKaD4jmzs9Wu0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/hidetatz/kubecolor/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/hidetatz/kubecolor/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NjeJq/hyUXSsSlFI/1ojlp8wwDXKaD4jmzs9Wu0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - hidetatz/kubecolor: colorizes kubectl output&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;colorizes kubectl output. Contribute to hidetatz/kubecolor development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl 결과물이 단일 색상이라 인지하기가 쉽지 않은데, 그걸 컬러풀하게 만들어주는 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 추천할 만한 툴이 있다면 댓글로 알려주세요.&lt;/p&gt;</description>
      <category>개발이야기/토막글</category>
      <category>k8s</category>
      <category>k9s</category>
      <category>kubernetes</category>
      <category>쿠버네티스</category>
      <category>툴</category>
      <category>필수툴</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/14</guid>
      <comments>https://junbyeol.tistory.com/14#entry14comment</comments>
      <pubDate>Sat, 6 Jan 2024 18:29:25 +0900</pubDate>
    </item>
    <item>
      <title>Node.js에서 csv 파일 다루기 및 ios-윈도우 간 한글 깨짐 문제 해결</title>
      <link>https://junbyeol.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀 또는 스프레드시트 파일을 Node.js 환경에서 다루고자 할 때, csv 파일을 사용합니다. Csv 파일은 여러 필드가 쉼표와 줄바꿈으로 표현된, 간단한 파일이므로, &lt;a href=&quot;https://nodejs.org/api/fs.html&quot;&gt;fs&lt;/a&gt; 모듈을 이용하여 쉽게 읽고쓰기 구현이 가능합니다. 하지만, 저는 아래의 라이브러리의 도움을 자주 받는 편입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://csv.js.org/&quot;&gt;https://csv.js.org/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704532272817&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CSV Project - Node.js CSV package&quot; data-og-description=&quot;This is a full-featured CSV parsing tool running entirely on your browser. No data leave your computer ! Use it also to learn how to use our packages and to test the various options interactively.&quot; data-og-host=&quot;csv.js.org&quot; data-og-source-url=&quot;https://csv.js.org/&quot; data-og-url=&quot;https://csv.js.org/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://csv.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://csv.js.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CSV Project - Node.js CSV package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This is a full-featured CSV parsing tool running entirely on your browser. No data leave your computer ! Use it also to learn how to use our packages and to test the various options interactively.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;csv.js.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 라이브러리를 이용하면, Node.js 환경에서 엑셀 파일을 편리하게 다룰 수 있습니다. 확장자가 &lt;code&gt;.xlsx&lt;/code&gt; 가 아님에 주의해야합니다. 엑셀에서 다른 이름으로 저장 하여 &lt;code&gt;.csv&lt;/code&gt; 로 저장을 하거나, &lt;code&gt;.xlsx&lt;/code&gt; 를 &lt;code&gt;.csv&lt;/code&gt;로 변환하는 과정을 거쳐야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종종, ios(맥) 환경에서 이렇게 생성한 csv 파일을 윈도우에서 열었을 때, 한글이 깨지는 현상이 발생할 수 있습니다. 인코딩 방식의 차이 때문입니다. 맥에서는 csv 파일의 기본 인코딩이 &lt;b&gt;utf8 방식인 반면, 윈도우에서는 ANSI 방식&lt;/b&gt;이기 때문입니다. 고로, 이를 해결하는 방법은 두가지가 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;맥에서 csv 파일을 작성할 때, ANSI 방식을 사용하기&lt;/li&gt;
&lt;li&gt;윈도우에서 csv 파일을 열 때, utf8 방식을 사용하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 2의 방식을 주로 사용합니다. csv 파일을 활용하고자 하는 윈도우 PC에서 기본 csv 파일 인코딩 설정을 바꿀수도 있지만, csv 파일 자체에 이 파일은 &quot;&lt;i&gt;utf8 방식으로 여세요.&quot;&lt;/i&gt; 하고 명시하는 방식이 좋습니다. BOM 문자열을 이용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BOM(Byte Order Mar) 문자열&lt;/b&gt;은 파일의 가장 앞머리에 있는 문자열이며, (생략도 가능합니다) 인코딩 방식에 따라 여러가지 값으로 표현됩니다. 우리는 이 파일이 utf 방식으로 읽힐 수 있도록, \xef\xbb\xbf 혹은 \uFEFF 을 파일 말머리에 달아주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 처음 소개드린 csv 라이브러리에서는 &lt;a href=&quot;https://csv.js.org/stringify/options/bom/&quot;&gt;bom 옵션을 제공&lt;/a&gt;하고 있으니, 편리하게 이용하기만 하면 됩니다. 직접 csv 파일을 생성하는 코드를 짠다면, 아래처럼 구현해주면 됩니다. Node.js 가 아니라, 브라우저 환경에서도 정상 동작하는 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const rows = [{ name: '준성', point: 1 }, { name: '민수', point: 2 }];

const BOM = &quot;\uFEFF&quot;;
let csvContent = &quot;data:text/csv;charset=utf-8,&quot; + BOM;

csvContent += ['이름', '점수'].join(',') + '\n';
csvContent += rows.map(row =&amp;gt; [row.name, row.point].join(',')).join('\n');

// 브라우저 환경에서 엑셀 다운로드 용으로 사용가능
console.log(encodeURI(csvContent));&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/13</guid>
      <comments>https://junbyeol.tistory.com/13#entry13comment</comments>
      <pubDate>Sat, 6 Jan 2024 18:11:50 +0900</pubDate>
    </item>
    <item>
      <title>mysqldump 치트 시트</title>
      <link>https://junbyeol.tistory.com/12</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;dump 가져오기&lt;/h3&gt;
&lt;pre id=&quot;code_1704527338114&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysqldump --column-statistics=0 -h [host] -u [user] -p [데이터베이스명] &amp;gt; [파일명].sql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;-p : 패스워드를 입력하겠다는 옵션&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;--column-statistics=0:&amp;nbsp; mysql 8.0부터 새로 지원하는 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/information-schema-column-statistics-table.html&quot;&gt;COLUMN_STATISTICS&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;&lt;span&gt; 테이블 관련 기능을 끄는(0)옵션&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dump 밀어넣기&lt;/h3&gt;
&lt;pre id=&quot;code_1704527347102&quot; class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;mysql -h [host] -u [user] -p [데이터베이스명] &amp;lt; [파일명].sql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;-p : 패스워드를 입력하겠다는 옵션&lt;/i&gt;&lt;/p&gt;</description>
      <category>개발이야기/토막글</category>
      <category>db</category>
      <category>MySQL</category>
      <category>mysqldump</category>
      <category>덤프</category>
      <category>백업</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/12</guid>
      <comments>https://junbyeol.tistory.com/12#entry12comment</comments>
      <pubDate>Sat, 6 Jan 2024 16:54:03 +0900</pubDate>
    </item>
    <item>
      <title>타입스크립트: ts2322 error 해결을 위한 서브타입 관련 개념 총정리</title>
      <link>https://junbyeol.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트를 사용하다보면 흔히 볼 수 있는 에러가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KWxwy/btsATP92emJ/c3ReCjp6vTmvMbX5ewToQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KWxwy/btsATP92emJ/c3ReCjp6vTmvMbX5ewToQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KWxwy/btsATP92emJ/c3ReCjp6vTmvMbX5ewToQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKWxwy%2FbtsATP92emJ%2Fc3ReCjp6vTmvMbX5ewToQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;198&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Type A is not assignable to type B. (2322)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 케이스는 이해하기 너무나도 직관적이지만, 종종 저 타입 정의가 복잡해지게 되면 에러 해결에 어려움을 겪곤 합니다. 이 글에서는 이 에러 해결에 도움을 줄 수 있는 타입스크립트의 이론적인 개념들을 훑어봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서브타입과 슈퍼타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dog는 Animal인가요? 네 맞습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Animal이 Dog인가요? 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로, Dog는 Animal의 부분집합입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 세상에서는 더 큰 집합인 Animal을 슈퍼타입, 그 부분집합인 Dog를 서브타입이라 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Animal을 담는 변수에 Dog를 담을수는 있지만, Dog를 담는 변수에 Animal을 담을 수는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dog는 Animal에 호환되고, 이것을 업 캐스팅이라 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Animal은 Dog에 호환되지 않습니다. 다운캐스팅은 일반적으로 불가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgJ1Df/btsATfVAUBU/gwtdRMQVYLIvHClOP8fxP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgJ1Df/btsATfVAUBU/gwtdRMQVYLIvHClOP8fxP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgJ1Df/btsATfVAUBU/gwtdRMQVYLIvHClOP8fxP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgJ1Df%2FbtsATfVAUBU%2FgwtdRMQVYLIvHClOP8fxP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;332&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입 계층 트리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트가 제공하는 타입들간의 호환 관계는 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUhC7K/btsATZEI8Jm/qxDWP4gd3122yETjWbGUyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUhC7K/btsATZEI8Jm/qxDWP4gd3122yETjWbGUyk/img.png&quot; data-alt=&quot;https://velog.io/@jeris/TypeScript-타입스크립트의-타입&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUhC7K/btsATZEI8Jm/qxDWP4gd3122yETjWbGUyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUhC7K%2FbtsATZEI8Jm%2FqxDWP4gd3122yETjWbGUyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;307&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://velog.io/@jeris/TypeScript-타입스크립트의-타입&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Unknown&lt;/b&gt;은 최상위의 타입입니다. 비유하자면 모든 Animal은 물론, Plant, Bacteria 등 살아있는 모든 것을 받아들일 수 있는 Organism 같은 가장 큰 범주라고 할 수 있습니다. 모든 타입의 아버지이자, 전체집합입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, &lt;b&gt;never&lt;/b&gt;는 최하위 집합입니다. Never 타입으로 정의된 변수에는 그 무엇도 담길 수 없습니다. 심지어 null이나 undefined 같은 비어있는 값조차도 안됩니다. Never는 공집합 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Any&lt;/b&gt;는 지금까지 설명한 내용과는 다른 예외적인 타입입니다. 위 그림만 보면 그저 unknown의 하위타입으로 보입니다. 그러나 Any는 never를 제외한 모든 변수에 할당될 수 있고, any로 정의된 변수는 모든 값을 받아들일 수 있습니다. 즉, 다운캐스팅이 가능합니다. Any 변수에는 Dog가 담길수 있고, Dog 변수에도 Any를 담을 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700989647566&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let unknownVar: unknown;
let neverVar: never;
let anyVar: any = 1;
let booleanVar: boolean = true;

neverVar = anyVar; // Error!!
unknownVar = anyVar;
booleanVar = anyVar; //이게 되다니, any의 사용을 주의해야 하는 이유를 알 것 같다.
booleanVar = unknownVar; // Error!!

anyVar = unknownVar;
anyVar = neverVar;
anyVar = booleanVar;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Type Assertion&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Typescript에게 타입 정보를 넣어주는 방법으로 &lt;b&gt;as&lt;/b&gt; 키워드를 사용하곤 합니다. 그러나 이 as 는 assertion 전 후의 두 타입이 서로 캐스팅 가능해야만 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Number와 string은 아무 관계도 아니므로 number를 string으로 assertion할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dog와 Animal은 서로 assertion이 가능하지만, Dog와 Plant, Animal과 Plant는 assertion이 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면, 위 타입 계층 트리 이미지 상에서 부모관계끼리는 assertion 가능하나, 형제 관계에서는 assertion이 불가능한 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700989674230&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let num: number = 1;

num as string; // Error!
num as unknown; // Unknown은 모든 타입의 아버지
num as never; // never는 모든 타입의 아들래미
num as any; // any는 어디에나 사용가능한 돌연변이

undefined as null; // Error!

const a: number = '1' as any as number; // 타입오류는 없으나, 당연히 런타임 오류를 유발할 수 있으므로, 절대 지양해야함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nominal Typing 과 구조적 타이핑&lt;/h2&gt;
&lt;pre id=&quot;code_1700989686362&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Animal = {
  age: number;
}

//1. Nominal Typing
type Dog = Animal &amp;amp; {
  bark: () =&amp;gt; void;
}

//2. Structural Typing
type Dog = {
  age: number;
  bark: () =&amp;gt; void;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 복잡한 호환성을 따져봅시다. Animal은 age라는 프로퍼티를 갖습니다. 우리는 Dog라는 타입을 추가하고 싶고, Dog는 bark같은 Dog만의 프로퍼티를 추가하고 싶습니다. 동시에, Animal과의 호환성도 놓치고 싶지 않습니다. 관련하여, 프로그래밍 언어론에서 사용하는 두 가지 서브타이핑 개념을 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 번 방법처럼 직접 Animal 타입과 명시적으로 관계가 있음을 밝혔기에 호환을 허용하는 것을을 &lt;b&gt;Nominal subtyping(명목적 서브타이핑)&lt;/b&gt; 이라 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 2 번 방법처럼 Animal 타입과 같은 age 프로퍼티를 추가함으로써, 구조적인 공통점 만으로도 호환을 허용하는 것을 &lt;b&gt;Structural subtyping(구조적 서브타이핑)&lt;/b&gt;이라 합니다. 즉, Nominal subtyping이 더 엄격한 조건입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Typescript는 두가지 서브타이핑을 모두 허용합니다!&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700989714562&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//1. Nominal Typing
type DogA = Animal &amp;amp; {
  bark: () =&amp;gt; void;
}

const dogA: DogA = {
    age: 1,
    bark: () =&amp;gt; console.log(&quot;멍!&quot;),
}

const animalA: Animal = dogA; // 문제 없음!

//2. Structural Typing
type DogB = {
  age: number;
  bark: () =&amp;gt; void;
}

const dogB: DogB = {
    age: 2,
    bark: () =&amp;gt; console.log(&quot;왈!&quot;),
}

const animalB: Animal = dogB; // 문제 없음!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나, 구조적 서브타이핑이 예외적으로 동작하지 않는 경우가 있습니다. &lt;b&gt;어떤 객체가 Fresh한 객체라면&lt;/b&gt;, 구조적 서브타이핑은 허용되지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700989733113&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예제 1
function howOldAnimalIs(animal: Animal) {
  return animal.age;
}

howOldAnimalIs(dogA); // 가능
howOldAnimalIs(dogB); // 가능
howOldAnimalIs({ age: 1 }); // 가능
howOldAnimalIs({ age: 1, bark: () =&amp;gt; console.log(&quot;깽&quot;) }); // Error!
// Argument of type '{ age: number; bark: () =&amp;gt; void; }' is not assignable to parameter of type 'Animal'.
// Object literal may only specify known properties, and 'bark' does not exist in type 'Animal'.(2345)



//예제 2

const animal: Animal = {
    age: 1,
    bark: () =&amp;gt; console.log(&quot;멍!&quot;),
} // Error!
// Type '{ age: number; bark: () =&amp;gt; void; }' is not assignable to type 'Animal'.
// Object literal may only specify known properties, and 'bark' does not exist in type 'Animal'.(2322)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 에러 모두 구조적 서브타이핑이 허용되었다면, Animal의 서브타입으로써 에러가 나지 않았을 것입니다. 그러나, 위 두 예제에서는 객체를 만들자마자(!) Animal의 서브타입으로 캐스팅하려 하여 실패한 것입니다. 이렇게 갓 만든 따끈따끈한 객체를 우리는 Fresh 하다고 하며, Fresh한 객체에 대해서는 구조적 서브타이핑을 허용하지 않는 것입니다. 이 객체의 Freshness에 대해서는 &lt;a href=&quot;https://toss.tech/article/typescript-type-compatibility&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 링크&lt;/a&gt;를 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해를 돕기 위해 추가코드를 적자면, 아래의 코드들은 모두 정상 동작 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700989751396&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const dogC = { age: 1, bark: () =&amp;gt; console.log(&quot;깽&quot;) }; // 이제 Fresh하지 않다.
howOldAnimalIs(dogC); 

const dogD = {
    age: 1,
    bark: () =&amp;gt; console.log(&quot;멍!&quot;),
}; // 이제 Fresh하지 않다.
const animalD: Animal = dogD;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dog와 Animal의 호환성은 알았는데, Dog[] 와 Animal[]의 호환성은 어떨까요? 혹은 Record&amp;lt;Dog, string&amp;gt;과 Record&amp;lt;Animal, string&amp;gt;은 어떨까요? (dog: Dog) &amp;rArr; void와 (animal: Animal) &amp;rArr; void는 어떨까요? 이 케이스들을 어떻게 허용할지에 대한 규칙을 프로그래밍 언어론에서는 변성(Variance)이라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공변성(Covariance)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;A &amp;le; B 일 때, T&amp;lt;A&amp;gt; &amp;le; T&amp;lt;B&amp;gt; 이다.&lt;/span&gt; 라는 성질입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 상황에서 만족합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Dog &amp;le; Animal 이므로, Dog[] &amp;le; Animal[]입니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;예시2) Dog &amp;le; Animal 이므로, Record&amp;lt;Dog, string&amp;gt; &amp;le; Record&amp;lt;Animal, string&amp;gt; 입니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;예시3) string &amp;le; string | number 이므로, () &amp;rArr; string &amp;le; () &amp;rArr; string | number 입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반공변성(Contravariance)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;A &amp;le; B 일 때, T&amp;lt;A&amp;gt; &amp;ge; T&amp;lt;B&amp;gt; 이다.&lt;/span&gt; 라는 성질입니다. (공변성과 방향이 반대)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 인수로 사용될 때 만족합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Dog &amp;le; Animal 이므로, (x: Dog) &amp;rArr; void &amp;ge; (x: Animal) &amp;rArr; void 입니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;string &amp;le; string | number 이므로, (x: string) &amp;rArr; void &amp;ge; (x: string | number) &amp;rArr; void 입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, 타입스크립트에서는 함수의 인수에 대해서는 반공변성을 갖고, 나머지 케이스에서는 모두 공변성을 갖습니다. 왜 그렇게 되어 있을까요? 직관적으로 이해할 수 있도록 아래의 예시를 적어두었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;string | number 타입의 변수에 string을 넣는 것은 문제가 되지 않으나, string 타입의 변수에 string | number를 넣는 것은 뭔가 이상하다. String 타입의 변수에 number가 들어갈 수 있다는 뜻이 되기 때문이다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;(x: string) &amp;rArr; void 타입의 변수에 (x: string | number) &amp;rArr; void를 넣는 것은 문제가 되지 않으나, (x: string | number) &amp;rArr; void에 (x: string) &amp;rArr; void 타입을 넣는 것은 뭔가 이상하다. x에 number 타입의 값을 넣어도 동작해야하는데, 실제로 동작하지 않게 될 것이기 때문이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데.. 사실 이 반공 변성에 대한 이야기도 예외가 있습니다. Typescript compiler 설정 항목 중 &amp;mdash;strictFunctionTypes 모드는 True로 설정되어 있습니다. 이 항목의 값을 False로 변경하면 함수의 인자는 반공변성이 아닌 이변성(Bivariance)을 갖게 됩니 다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이변성(Bivariance)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;A &amp;le; B 일 때, T&amp;lt;A&amp;gt; &amp;le; T&amp;lt;B&amp;gt; 와 T&amp;lt;A&amp;gt; &amp;ge; T&amp;lt;B&amp;gt; 모두 가능하다.&lt;/span&gt; 라는 성질입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;string &amp;le; string | number 이므로, (x: string) &amp;rArr; void &amp;ge; (x: string | number) &amp;rArr; void 일수도 있고, (x: string) &amp;rArr; void &amp;le; (x: string | number) &amp;rArr; void 일수도 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 이상하게 느껴질 수 있습니다. string과 number를 모두 처리할 수 있을 것으로 보이는 함수의 자리를 string만 처리할 있는 함수로 채워넣으면 런타임 에러가 날 것이 분명하다. 그럼에도 불구하고, typescript는 왜 이런 옵션을 제공하는걸까요? 이 비하인드 스토리는 &lt;a href=&quot;https://driip.me/d230be64-df1d-4e9a-a8c2-cba6bbc0ae15&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 링크&lt;/a&gt;를 참고해주세요&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;참고한 글&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B3%B5%EB%B3%80%EC%84%B1-%EB%B0%98%EA%B3%B5%EB%B3%80%EC%84%B1-%F0%9F%92%A1-%ED%95%B5%EC%8B%AC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B3%B5%EB%B3%80%EC%84%B1-%EB%B0%98%EA%B3%B5%EB%B3%80%EC%84%B1-%F0%9F%92%A1-%ED%95%B5%EC%8B%AC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://itchallenger.tistory.com/447&quot;&gt;https://itchallenger.tistory.com/447&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://www.typescriptlang.org/ko/docs/handbook/2/everyday-types.html&quot;&gt;https://www.typescriptlang.org/ko/docs/handbook/2/everyday-types.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://toss.tech/article/typescript-type-compatibility&quot;&gt;https://toss.tech/article/typescript-type-compatibility&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://driip.me/d875a384-3fb9-471b-a53b-b3ca52f8238e&quot;&gt;https://driip.me/d875a384-3fb9-471b-a53b-b3ca52f8238e&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://driip.me/d230be64-df1d-4e9a-a8c2-cba6bbc0ae15&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://driip.me/d230be64-df1d-4e9a-a8c2-cba6bbc0ae15&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발이야기</category>
      <author>준별</author>
      <guid isPermaLink="true">https://junbyeol.tistory.com/11</guid>
      <comments>https://junbyeol.tistory.com/11#entry11comment</comments>
      <pubDate>Sun, 26 Nov 2023 19:07:13 +0900</pubDate>
    </item>
  </channel>
</rss>