<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>juhee-dev.log</title>
        <link>https://velog.io/</link>
        <description>개: 개롭지만 발: 발전하는중</description>
        <lastBuildDate>Wed, 28 May 2025 11:49:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>juhee-dev.log</title>
            <url>https://velog.velcdn.com/images/juhee-dev/profile/97faccb0-1c44-406b-a3d6-6b047052e8e7/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. juhee-dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/juhee-dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.1] 가장 가까운 같은 글자]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.1-%EA%B0%80%EC%9E%A5-%EA%B0%80%EA%B9%8C%EC%9A%B4-%EA%B0%99%EC%9D%80-%EA%B8%80%EC%9E%90</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.1-%EA%B0%80%EC%9E%A5-%EA%B0%80%EA%B9%8C%EC%9A%B4-%EA%B0%99%EC%9D%80-%EA%B8%80%EC%9E%90</guid>
            <pubDate>Wed, 28 May 2025 11:49:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/2c4e4fd9-e0de-4b70-9773-0524481efda3/image.png" alt=""></p>
<p>[문제 링크] (<a href="https://school.programmers.co.kr/learn/courses/30/lessons/142086">https://school.programmers.co.kr/learn/courses/30/lessons/142086</a>)</p>
<hr>
<h1 id="풀이">풀이</h1>
<pre><code class="language-swift">import Foundation

func solution(_ s:String) -&gt; [Int] {
    var lastIndex: [Character : Int] = [:]
    var result = Array(repeating: -1, count: s.count)

    for (i, c) in s.enumerated() {
        result[i] = lastIndex[c] == nil ? -1 : i - lastIndex[c]!
        lastIndex[c] = i
    }

    return result
}</code></pre>
<h3 id="설명">설명</h3>
<ul>
<li><p>딕셔너리로 각 문자의 마지막 인덱스를 저장</p>
</li>
<li><p><code>result = Array(repeating: -1, count: s.count)</code>:
문자열의 개수만큼 -1 값으로 배열 생성</p>
</li>
<li><p><code>enumerated()</code>:
문자열의 <code>(인덱스, 문자)</code> 튜플을 반환. <code>(i, c)</code> 튜플로 for문에 활용</p>
</li>
<li><p><code>lastIndex[c]</code>가 <code>nil</code>이면 해당 문자는 처음 나온 문자이므로 -1 저장, 아니라면 <code>현재 인덱스 - 가장 최근에 나왔던 인덱스</code></p>
</li>
</ul>
<blockquote>
<p>💡 딕셔너리의 반환값이 <code>nil</code>인 경우?
Swift 딕셔너리에서 섭스크립트(<code>[key]</code>)로 접근하면 해당 키가 존재하지 않을 수 있기에 항상 <strong>Optional Type</strong>을 반환한다.</p>
</blockquote>
<pre><code class="language-swift">var dict = [&quot;apple&quot;: 5, &quot;banana&quot;: 3]
let apple = dict[&quot;apple&quot;]    // Optional(5)</code></pre>
<hr>
<p>처음에는 이차원 배열로 해결하려고 했는데, 동일한 문자에 대해 값을 수정해야 해서 딕셔너리가 더 직관적이라고 판단했다.
다른 사람의 풀이에 비교해봐도 내 코드가 효율적이라고 느껴서 오늘은 다른 사람의 풀이 스킵! 👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.0] 문자 개수 세기]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%AC%B8%EC%9E%90-%EA%B0%9C%EC%88%98-%EC%84%B8%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%AC%B8%EC%9E%90-%EA%B0%9C%EC%88%98-%EC%84%B8%EA%B8%B0</guid>
            <pubDate>Mon, 12 May 2025 09:10:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/0ab7ca98-71d4-49f6-abc6-d092a4f3fc61/image.png" alt=""></p>
<p>[문제 링크] (<a href="https://school.programmers.co.kr/learn/courses/30/lessons/181902">https://school.programmers.co.kr/learn/courses/30/lessons/181902</a>)</p>
<hr>
<h1 id="풀이-이중-foreach문-순회">풀이: 이중 forEach문 순회</h1>
<pre><code class="language-swift">import Foundation

func solution(_ my_string:String) -&gt; [Int] {
    var result = Array(repeating: 0, count: 52)
    var alpha = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;
    alpha += alpha.lowercased

    my_string.forEach { str in
        alpha.forEach {
            if str == $0 {
                result[Array(alpha).firstIndex(of: $0)!] += 1
            }
        }
    }

    return result
}</code></pre>
<h3 id="설명">설명</h3>
<pre><code class="language-swift">var result = Array(repeating: 0, count: 52)</code></pre>
<ul>
<li>배열 선언과 동시에 초기화</li>
<li>52 길이의 배열 생성, 초기값은 0</li>
</ul>
<pre><code class="language-swift">result[Array(alpha).firstIndex(of: $0)!] += 1</code></pre>
<ul>
<li><code>Array.firstIndex(of: Element)</code>: 반환값은 <code>Int?</code> 이므로 강제언래핑</li>
</ul>
<hr>
<h1 id="다른-사람의-풀이">다른 사람의 풀이</h1>
<pre><code class="language-swift">import Foundation

func solution(_ my_string:String) -&gt; [Int] {
    var result = Array(repeating: 0, count: 52)

    my_string.forEach {
        result[Int($0.asciiValue!) - ($0.isUppercase ? 65 : 71)] += 1
    }

    return result
}</code></pre>
<h3 id="설명-1">설명</h3>
<h4 id="asciivalue">asciiValue</h4>
<pre><code class="language-swift">let a: Character = &quot;A&quot;
if let ascii = a.asciiValue {
    print(ascii)  // 출력: 65
}</code></pre>
<ul>
<li>Character 타입에서 제공하는 속성으로, 문자의 ASCII 코드값을 반환한다.</li>
<li><code>UInt8?</code>(옵셔널 UInt8) 타입이다. → <code>Int</code>와 사용 시 형변환 필요</li>
<li>ASCII 문자에 대해서만 값을 반환하고, ASCII가 아닌 문자(예: 이모지, 한글 등)에 대해서는 <code>nil</code>을 반환한다.</li>
</ul>
<blockquote>
<p>💡 주요 아스키 코드를 알아두자.</p>
</blockquote>
<ul>
<li>대문자 <strong>A</strong>-Z: <strong>65</strong>-90</li>
<li>소문자 <strong>a</strong>-z: <strong>97</strong>-122</li>
<li>숫자 0-9: 48-57</li>
<li>공백: 32
<img src="https://velog.velcdn.com/images/juhee-dev/post/a84a22fe-7716-4890-9d92-e3a84598a1e9/image.jpg" alt=""></li>
</ul>
<h4 id="isuppercase">isUppercase</h4>
<pre><code class="language-swift">let a: Character = &quot;A&quot;
print(a.isUppercase)  // 출력: true</code></pre>
<ul>
<li>Character 타입에서 제공하며 문자의 대소문자 여부를 확인한다.</li>
</ul>
<hr>
<p>이중 forEach문 코드를 작성하면서도 이게 맞나, 긴가민가했는데 문자를 아스키코드로 바꿔주는 함수가 있다니! 갓위프트..👍 시간복잡도가 훨씬 축약됐다.</p>
<p>배열 선언과 동시에 초기화하는 코드도 많이 사용할 것 같으니 외워두기.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.0] 배열 만들기 5]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%B0%B0%EC%97%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0-5</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%B0%B0%EC%97%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0-5</guid>
            <pubDate>Mon, 05 May 2025 10:54:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/da959e6c-d506-4df5-9d3c-728ec8ab54e7/image.png" alt=""></p>
<p>[문제 링크] (<a href="https://school.programmers.co.kr/learn/courses/30/lessons/181912">https://school.programmers.co.kr/learn/courses/30/lessons/181912</a>)</p>
<hr>
<h1 id="풀이-1-배열-인덱스">풀이 1: 배열 인덱스</h1>
<pre><code class="language-swift">import Foundation

func solution(_ intStrs:[String], _ k:Int, _ s:Int, _ l:Int) -&gt; [Int] {
    return intStrs.compactMap {
        Int(String(Array($0)[s...s+l-1]))
    }.filter { $0 &gt; k }
}</code></pre>
<h3 id="설명">설명</h3>
<ul>
<li><p>String을 Array로 변환하면 각 요소는 Character 타입이 된다.</p>
</li>
<li><p><code>Character</code> ↔️ <code>Int</code> 형변환은 불가
  → <code>Character</code> 요소를 <code>String</code> 타입으로 변환 후, <code>Int</code>로 변환</p>
</li>
<li><p><code>map</code>을 사용하면 <code>[Int?]</code> 타입이 반환되기에 강제언래핑 대신 <code>compactMap</code> 사용</p>
</li>
</ul>
<blockquote>
<p>💡 <strong>map VS compactMap</strong>
<code>map</code>: 옵셔널 타입 결과를 그대로 유지
<code>compactMap</code>: nil 값은 제거하여 결과는 항상 <strong>non-optional</strong> 값</p>
</blockquote>
<hr>
<h1 id="다른-사람의-풀이-prefix--suffix">다른 사람의 풀이: prefix / suffix</h1>
<pre><code class="language-swift">func solution(_ intStrs: [String], _ k: Int, _ s: Int, _ l: Int) -&gt; [Int] {
    return intStrs.map { Int($0.prefix(s + l).suffix(l))! }.filter { $0 &gt; k }
}</code></pre>
<h3 id="설명-1">설명</h3>
<h4 id="prefix와-suffix-작동-방식">prefix와 suffix 작동 방식</h4>
<ol>
<li><p><code>prefix(n)</code> 함수:</p>
<ul>
<li>문자열의 시작부터 지정된 개수(n)만큼의 문자를 포함하는 부분 문자열을 반환한다.</li>
<li>예: <code>&quot;Hello&quot;.prefix(2)</code>는 <code>&quot;He&quot;</code>를 반환한다.</li>
</ul>
</li>
<li><p><code>suffix(n)</code> 함수:</p>
<ul>
<li>문자열의 끝에서부터 지정된 개수(n)만큼의 문자를 포함하는 부분 문자열을 반환한다.</li>
<li>예: <code>&quot;Hello&quot;.suffix(2)</code>는 <code>&quot;lo&quot;</code>를 반환한다.</li>
</ul>
</li>
</ol>
<h4 id="코드-실행-과정">코드 실행 과정</h4>
<ul>
<li><p><code>$0.prefix(s + l)</code>: 문자열의 처음부터 <code>s + l</code>개의 문자를 가져온다.
  → 필요없는 뒷 문자열 버리기</p>
</li>
<li><p><code>.suffix(l)</code>: 그 결과에서 마지막 l개의 문자를 가져온다.
  → 필요없는 앞 문자열 버리기</p>
</li>
<li><p>결합하면, 인덱스 <code>s</code>부터 시작하여 길이 <code>l</code>만큼의 부분 문자열을 얻게 된다.</p>
</li>
</ul>
<hr>
<p>이전 포스팅에서 문자열 인덱스를 처리하려면 배열로 만든 후 배열의 인덱스를 사용하면 편하다고 작성했다.
오늘은 다른 사람의 코드를 보며 그 방법만 있는게 아니구나, <code>prefix</code> / <code>suffix</code>를 이용하면 번거로운 형변환 작업을 줄일 수 있구나 깨달았다.
까다로운 swift 문자열을 상대할 무기를 하나 더 얻은 기분이다.👊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.0] 수열과 구간 쿼리 4]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EC%88%98%EC%97%B4%EA%B3%BC-%EA%B5%AC%EA%B0%84-%EC%BF%BC%EB%A6%AC-4</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EC%88%98%EC%97%B4%EA%B3%BC-%EA%B5%AC%EA%B0%84-%EC%BF%BC%EB%A6%AC-4</guid>
            <pubDate>Thu, 01 May 2025 10:12:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/52cfde3b-8769-494e-8d1b-bcf758336e6e/image.png" alt=""></p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/181922">문제 링크</a></p>
<hr>
<h1 id="풀이-1-for문">풀이 1: for문</h1>
<pre><code class="language-swift">import Foundation

func solution(_ arr:[Int], _ queries:[[Int]]) -&gt; [Int] {
    var result = arr

    for query in queries {
        let (s, e, k) = (query[0], query[1], query[2])

        for idx in s...e {
            result[idx] = idx % k == 0 ? result[idx] + 1 : result[idx]
        }
    }

    return result
}</code></pre>
<h3 id="고민">고민</h3>
<ul>
<li>for문을 사용해 풀기는 했지만, filter나 map 등의 함수를 이용해 더 간단하게 풀 수 있을까?
→ filter를 사용해 k의 배수인 arr[i] 값을 찾아내더라도, 그 값에 +1을 어떻게 하지?</li>
</ul>
<hr>
<h1 id="다른-사람의-풀이">다른 사람의 풀이</h1>
<pre><code class="language-swift">import Foundation

func solution(_ arr:[Int], _ queries:[[Int]]) -&gt; [Int] {
    return queries.reduce(into: arr) { arr, query in
        (query[0]...query[1]).filter { $0 % query[2] == 0 }.forEach { arr[$0] += 1 }
    }
}</code></pre>
<h3 id="설명">설명</h3>
<h4 id="1-reduceinto-arr-함수의-역할">1. reduce(into: arr) 함수의 역할</h4>
<pre><code class="language-swift">queries.reduce(into: arr) { arr, query in ... }</code></pre>
<ul>
<li><code>reduce(into:)</code>: 초기값(arr)을 시작으로 컬렉션의 각 요소에 대해 누적 작업을 수행</li>
<li><code>into: arr</code>: 초기값으로 arr 배열의 복사본을 사용하고, 이를 직접 수정함</li>
<li>첫 번째 파라미터 <code>arr</code>: 현재까지의 누적 결과 배열 (mutable)</li>
<li>두 번째 파라미터 <code>query</code>: 현재 처리 중인 쿼리 정보</li>
</ul>
<blockquote>
<h4 id="💡-reduce-함수를-축약형으로-사용하지-못하는-이유">💡 reduce 함수를 축약형으로 사용하지 못하는 이유</h4>
</blockquote>
<h4 id="reduce-함수-축약형은-주로-다음과-같은-경우에-사용한다">reduce 함수 축약형은 주로 다음과 같은 경우에 사용한다.</h4>
<ol>
<li>연산이 단일 연산자(<code>+</code>, <code>*</code> 등)로 표현 가능</li>
<li>복잡한 조건문이나 다른 함수 호출이 없음</li>
<li>누적값과 요소 모두 동일한 방식으로 처리됨<h4 id="reduceinto는-일반-reduce와-달리-다음과-같은-특성이-있다">reduce(into:)는 일반 reduce와 달리 다음과 같은 특성이 있다.</h4>
</li>
<li>누적값이 mutable로 처리됨 (직접 수정 가능)</li>
<li>각 단계에서 반환값 필요 없음</li>
<li>내부에서 컬렉션 조작이 자주 이루어짐
→ 복잡한 작업의 경우 <code>reduce(into:)</code> 사용</li>
</ol>
<h4 id="2-범위-생성-및-필터링">2. 범위 생성 및 필터링</h4>
<pre><code class="language-swift">(query[0]...query[1]).filter { $0 % query[2] == 0 }</code></pre>
<ul>
<li><code>query[0]...query[1]</code>: s부터 e까지의 인덱스 범위를 생성</li>
<li><code>.filter { $0 % query[2] == 0 }</code>: 범위 내에서 k(query[2])의 배수인 인덱스만 선택</li>
</ul>
<h4 id="3-선택된-인덱스의-배열-요소-수정">3. 선택된 인덱스의 배열 요소 수정</h4>
<pre><code class="language-swift">.forEach { arr[$0] += 1 }</code></pre>
<ul>
<li><span style="color:pink"/><code>forEach:</code></span> 선택된 각 인덱스에 대해 작업 수행</li>
<li>직접 원본 배열(참조로 전달된)을 수정함</li>
</ul>
<p>→ 첫 풀이에서의 고민 해결!</p>
<blockquote>
<p>💡 forEach 함수의 특징</p>
</blockquote>
<ul>
<li><strong>반환 값이 없음</strong>: <code>forEach</code>는 <code>Void</code>를 반환한다. 즉, 결과를 모으거나 변환된 컬렉션을 반환하지 않는다.</li>
<li><strong>원본 변경 없음</strong>: 원본 컬렉션을 변경하지 않는다.</li>
<li><strong>순서 보장</strong>: 배열과 같은 순서가 있는 컬렉션에서는 요소를 순서대로 처리한다.</li>
<li><strong><code>break</code>/<code>continue</code> 사용 불가</strong></li>
<li><strong><code>return</code>의 동작</strong>: 클로저 내에서 <code>return</code>을 사용하면 현재 요소의 처리만 종료하고 다음 요소로 진행한다 (전체 반복 종료 안 됨).</li>
</ul>
<hr>
<p>forEach 함수를 새롭게 알았다. method chaining을 통해 더 간결한 코드를 사용할 수 있을 것 같다.</p>
<p>reduce 함수는 축약형으로만 사용해오다 보니, 기본형을 사용할 생각은 못했다. 복잡한 처리가 필요한 경우에도 사용할 수 있구나 깨달았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.0] 이어 붙인 수]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EC%9D%B4%EC%96%B4-%EB%B6%99%EC%9D%B8-%EC%88%98</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EC%9D%B4%EC%96%B4-%EB%B6%99%EC%9D%B8-%EC%88%98</guid>
            <pubDate>Tue, 29 Apr 2025 02:03:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/76371ec1-e5f7-4f39-a49f-246498d016b5/image.png" alt=""></p>
<p>[문제 링크] (<a href="https://school.programmers.co.kr/learn/courses/30/lessons/181928">https://school.programmers.co.kr/learn/courses/30/lessons/181928</a>)</p>
<hr>
<h1 id="풀이-1-반복문">풀이 1: 반복문</h1>
<pre><code class="language-swift">import Foundation

func solution(_ num_list:[Int]) -&gt; Int {
    var oddArr = num_list.filter { $0 % 2 == 1 }
    var evenArr = num_list.filter { $0 % 2 == 0 }
    var sumOfOdd = 0
    var sumOfEven = 0

    var square = Double(oddArr.count - 1)
    for n in oddArr {
        sumOfOdd += Int(Double(n) * pow(Double(10), square))
        square -= 1
    }

    square = Double(evenArr.count - 1)
    for n in evenArr {
        sumOfEven += Int(Double(n) * pow(Double(10), square))
        square -= 1
    }

    return sumOfOdd + sumOfEven
}</code></pre>
<h3 id="설명">설명</h3>
<ul>
<li><p><code>filter</code>를 사용해 홀수와 짝수를 별도의 배열로 분리</p>
</li>
<li><p>10의 제곱을 사용해 각 배열의 숫자들을 자릿수에 맞게 변환</p>
</li>
<li><p><code>pow()</code> 함수의 각 파라미터와 반환 타입은 Double 혹은 Float만 가능하므로 형변환 진행</p>
<blockquote>
<p><strong>💡 각 배열 값(<code>n</code>)에도 명시적 형변환이 필요한 이유?</strong>
Swift는 강한 타입(strongly typed) 언어로, 다른 타입 간의 자동 형변환을 허용하지 않는다.</p>
</blockquote>
</li>
</ul>
<hr>
<h1 id="다른-사람의-코드">다른 사람의 코드</h1>
<pre><code class="language-swift">import Foundation

func solution(_ num_list:[Int]) -&gt; Int {
    let even = Int(num_list.filter { $0 % 2 == 0 }.map { String($0) }.joined())!
    let odd = Int(num_list.filter { $0 % 2 != 0 }.map { String($0) }.joined())!
    return even + odd
}</code></pre>
<h3 id="설명-1">설명</h3>
<ul>
<li><p><code>.filter { $0 % 2 == 0 }</code>: num_list에서 짝수만 필터링 → 배열 반환</p>
</li>
<li><p><code>.map { String($0) }</code>: 각 숫자를 문자열로 변환 → 배열 반환</p>
</li>
<li><p><code>.joined()</code>: 모든 문자열 이어붙이기.
<code>joined(separator: String = &quot;&quot;)</code> 형식이지만 separator 생략 가능</p>
</li>
<li><p><code>Int(...)!</code>에서 강제 언래핑:
형변환이 실패할 가능성이 있는 경우 Swift는 안전을 위해 옵셔널을 반환한다.</p>
<blockquote>
<p><strong>💡 옵셔널 언래핑 두 가지 방법. <code>!</code> vs <code>??</code></strong>
<code>!</code>: 강제 언래핑(Force Unwrapping)
값이 확실히 존재할 때만 사용한다. 
옵셔널이 nil일 때 강제 언래핑하면 런타임 오류가 발생한다.</p>
<br>
`??`: Nil 병합 연산자
안전하게 옵셔널을 처리할 수 있는 방법이다.
왼쪽 피연산자가 `nil`이 아니면 그 값을 사용, `nil`이면 오른쪽 기본값을 사용한다.
<br>
→ `??` 언래핑이 안전하지만, 문제의 조건에 num_list 배열의 요소는 항상 정수이기에 `!` 언래핑을 사용했다.
</blockquote>
</li>
</ul>
<hr>
<p>filter 함수를 통해 홀수, 짝수 배열을 나누는 것까지는 생각했는데 Int 배열을 String으로 바꿔 문자열로 붙이는 생각을 못했다.
어쩐지 내 코드가 C언어스럽더라니... 다음엔 문자열로 변환 기억하기.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.0] 주사위 게임2]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EC%BD%94%EB%93%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EC%BD%94%EB%93%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 28 Apr 2025 03:48:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/b98abf4b-45b7-4d28-b177-e9341ccdfe0b/image.png" alt=""></p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/181930">문제 링크</a></p>
<hr>
<h1 id="풀이-1-오답">풀이 1: 오답</h1>
<pre><code class="language-swift">import Foundation

func solution(_ a:Int, _ b:Int, _ c:Int) -&gt; Int {
    var result = 0

    if (a == b) &amp;&amp; (a == c) {
        result = (a + b + c) * (a^2 + b^2 + c^2) * (a^3 + b^3 + c^3)
    } else if (a == b) || (b == c) || (a == c) {
        result = (a + b + c) * (a^2 + b^2 + c^2)
    } else {
        result = a + b + c
    }

    return result
}</code></pre>
<h3 id="오답-원인">오답 원인</h3>
<ul>
<li>Swift에서는 <code>^</code> 연산자가 제곱이 아닌 비트 XOR 연산자로 사용된다.</li>
<li>따라서 숫자를 제곱할 때 <code>n^2</code>와 같은 표기법을 사용할 수 없다.</li>
</ul>
<hr>
<h1 id="풀이-2-수정된-코드">풀이 2: 수정된 코드</h1>
<pre><code class="language-swift">import Foundation

func solution(_ a:Int, _ b:Int, _ c:Int) -&gt; Int {
    var result = 0
    let sum1 = a + b + c
    let sum2 = a*a + b*b + c*c
    let sum3 = a*a*a + b*b*b + c*c*c

    if (a == b) &amp;&amp; (a == c) {
        result = sum1 * sum2 * sum3
    } else if (a == b) || (b == c) || (a == c) {
        result = sum1 * sum2
    } else {
        result = sum1
    }

    return result
}</code></pre>
<h3 id="pow-함수를-사용하지-않은-이유">pow 함수를 사용하지 않은 이유</h3>
<ul>
<li><p><code>pow()</code> 함수는 <code>Double</code> 타입에서 작동한다.
이 함수를 사용하려면 a, b, c가 Int 타입이기에, 이를 Double로 변환해야 하는 번거로움이 있다.</p>
</li>
<li><p>모든 계산이 Double로 이루어지므로 결과도 Double이 된다. 
Int 타입의 결과를 위해 한 번 더 변환이 필요하다.</p>
</li>
<li><p>정밀도 문제
매우 큰 숫자의 경우 부동 소수점 연산의 정밀도 한계로 인해 정확한 결과를 얻지 못할 수 있다.</p>
</li>
<li><p>성능
pow() 함수는 일반 곱셈보다 계산 비용이 더 크다. 특히 2제곱과 3제곱은 곱셈(<code>*</code>)을 통해 직접 표현하는 것이 더 효율적이다.</p>
</li>
</ul>
<hr>
<p>^ 연산이 XOR 연산으로 처리된다니. C언어와는 달라서 당연히 풀리겠거니 생각한 문제가 오답처리 돼서 당황했다.
4제곱 이상 시에는 pow 함수 쓰기, 3제곱 이하라면 직접 곱하기 기억하기.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.0] 문자열 섞기]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%84%9E%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%84%9E%EA%B8%B0</guid>
            <pubDate>Fri, 25 Apr 2025 01:55:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/b4b0b23b-31e2-4d34-9385-57c0241aa390/image.png" alt=""></p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/181942">문제 링크</a></p>
<hr>
<h1 id="풀이-1-초기-풀이">풀이 1: 초기 풀이</h1>
<pre><code class="language-swift">import Foundation

func solution(_ str1:String, _ str2:String) -&gt; String {
    var result: [Character] = []

    for n in 0..&lt;str1.count {
        result.append(str1[str1.index(str1.startIndex, offsetBy: n)])
        result.append(str2[str2.index(str2.startIndex, offsetBy: n)])
    }

    return String(result)
}</code></pre>
<h3 id="설명">설명</h3>
<h4 id="문자열의-n번째-인덱스-접근">문자열의 n번째 인덱스 접근</h4>
<pre><code class="language-swift">result.append(str1[str1.index(str1.startIndex, offsetBy: n)])</code></pre>
<ul>
<li><code>str1.startIndex</code>: 문자열의 시작 인덱스</li>
<li><code>str1.index(str1.startIndex, offsetBy: n)</code>: 시작점에서 n만큼 떨어진 위치의 인덱스</li>
<li><code>str1[계산된 인덱스]</code>: 그 인덱스 위치의 문자</li>
</ul>
<blockquote>
<p>💡 <strong>왜 String은 인덱스 접근이 복잡할까?</strong>
Swift에서 String은 배열과 달리 정수 인덱스(n)로 직접 접근할 수 없다. 즉, str1[n]과 같은 방식으로 접근이 불가능하다.
Swift의 String은 내부적으로 유니코드를 사용하고, 각 문자가 가변적인 크기를 가질 수 있기 때문에 특별한 인덱스 타입(String.Index)을 사용한다. 따라서 위와 같은 단계가 필요하다.</p>
</blockquote>
<hr>
<h1 id="풀이-2-간단한-방법">풀이 2: 간단한 방법</h1>
<pre><code class="language-swift">import Foundation

func solution(_ str1: String, _ str2: String) -&gt; String {
    let str1Array = Array(str1)
    let str2Array = Array(str2)
    var result = &quot;&quot;

    for i in 0..&lt;str1.count {
        result.append(str1Array[i])
        result.append(str2Array[i])
    }

    return result
}</code></pre>
<h3 id="설명-1">설명</h3>
<ul>
<li>문자열을 먼저 Character 배열로 변환하여 인덱싱을 효율적으로 수행할 수 있다.
이렇게 하면 매번 <code>index(startIndex, offsetBy:)</code>를 호출하지 않아도 된다.</li>
</ul>
<hr>
<h1 id="다른-사람의-풀이">다른 사람의 풀이</h1>
<pre><code class="language-swift">import Foundation

func solution(_ str1:String, _ str2:String) -&gt; String {
    return zip(str1, str2).map { String($0) + String($1) }.joined()
}</code></pre>
<h3 id="설명-2">설명</h3>
<pre><code class="language-swift">zip(str1, str2)</code></pre>
<ul>
<li><code>zip</code> 함수는 두 시퀀스를 받아 각 시퀀스의 동일한 위치에 있는 요소들을 쌍(tuple)으로 묶어 새로운 시퀀스를 생성한다.</li>
<li>예를 들어, str1 = &quot;abc&quot;, str2 = &quot;xyz&quot; 라면, zip은 [(&#39;a&#39;,&#39;x&#39;), (&#39;b&#39;,&#39;y&#39;), (&#39;c&#39;,&#39;z&#39;)]와 같은 쌍들을 생성한다.</li>
</ul>
<pre><code class="language-swift">.map { String($0) + String($1) }</code></pre>
<ul>
<li><code>map</code>은 각 쌍에 클로저를 적용하여 변환한다.</li>
<li>클로저 내에서 $0은 쌍의 첫 번째 요소(str1의 문자), $1은 쌍의 두 번째 요소(str2의 문자).</li>
<li>각 문자를 String으로 변환하고 두 문자열을 합친다.</li>
<li>예를 들어, (&#39;a&#39;,&#39;x&#39;)는 &quot;ax&quot;가 된다.</li>
</ul>
<pre><code class="language-swift">.joined():</code></pre>
<ul>
<li>결과적으로 생성된 문자열 배열을 하나의 문자열로 합친다.</li>
<li>예를 들어, [&quot;ax&quot;, &quot;by&quot;, &quot;cz&quot;]는 &quot;axbycz&quot;가 된다.</li>
</ul>
<hr>
<p>문자열의 인덱스에 접근하고자 할 때에는 문자열을 배열로 만들어 쉽게 접근해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.0] 문자열 겹쳐쓰기]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B2%B9%EC%B3%90%EC%93%B0%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B2%B9%EC%B3%90%EC%93%B0%EA%B8%B0</guid>
            <pubDate>Wed, 02 Apr 2025 15:03:17 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/5cfa80ec-528f-452c-a5bb-8cb9f94a0ba5/image.png" alt=""><a href="https://school.programmers.co.kr/learn/courses/30/lessons/181943">https://school.programmers.co.kr/learn/courses/30/lessons/181943</a></p>
<hr>
<h1 id="풀이-1-오류">풀이 1: 오류</h1>
<pre><code class="language-swift">import Foundation

func solution(_ mystring:String, * overwrite*string:String,  s:Int) -&gt; String {
    let firstIdx = my_string.index(my_string.startIndex, offsetBy: s) // 덮어쓰기 시작 인덱스
    var result = String(my_string[..&lt;firstIdx]) + overwrite_string // 덮어쓴 문자열

    guard let lastIdx = my_string.index(my_string.startIndex, offsetBy: s + overwrite_string.count) {
        result += String(my_string[lastIdx]...)   
    } // 덮어쓰고 남은 원본 문자열을 result에 추가

    return result
}</code></pre>
<h3 id="문제점">문제점</h3>
<ul>
<li><p><strong>guard let 구문의 잘못된 사용</strong>:
<code>index(startIndex, offsetBy:)</code> 메서드는 옵셔널 값을 반환하지 않는다. 따라서 <code>guard let</code>으로 처리할 필요가 없다.
만약 요청한 인덱스가 문자열 범위를 벗어나면 이 메서드는 nil을 반환하는 것이 아니라 _런타임 오류_를 발생시킵니다.</p>
</li>
<li><p><strong>슬라이싱 문법 오류</strong>: 
<code>my_string[lastIdx]...</code>에서 <code>lastIdx</code>는 인덱스이지만, 범위 연산자를 사용하려면 <code>my_string[lastIdx...]</code>와 같이 작성해야 한다.</p>
</li>
</ul>
<hr>
<h1 id="풀이-2-오류">풀이 2: 오류</h1>
<pre><code class="language-swift">import Foundation

func solution(_ mystring:String, * overwrite*string:String,  s:Int) -&gt; String {
    let firstIdx = my_string.index(my_string.startIndex, offsetBy: s) // 덮어쓰기 시작 인덱스
    let lastIdx = my_string.index(my_string.startIndex, offsetBy: s + overwrite_string.count) // 덮어쓰기 마지막 인덱스 + 1

    var result = String(my_string[..&lt;firstIdx]) + overwrite_string
    if my_string.count &gt;= Int(lastIdx)  { // 남은 원본 문자가 있으면 결과 문자열에 추가
        result += String(my_string[lastIdx...])   
    }
    return result
}</code></pre>
<h3 id="문제점-1">문제점</h3>
<ul>
<li><p><strong>타입 변환 오류</strong>: 
<code>if my_string.count &gt;= Int(lastIdx)</code> 부분에서 <code>lastIdx</code>를 <code>Int</code>로 변환하려고 시도했다. 그러나 <code>lastIdx</code>는 <code>String.Index</code> 타입이으로 정수형으로 직접 변환할 수 없다.</p>
</li>
<li><p><strong>불필요한 조건 검사</strong>: 
문제의 제한사항에 따르면 <code>s + overwrite_string.count</code>는 항상 <code>my_string.count</code> 이하임이 보장된다. 따라서 이 조건 검사는 불필요하다.</p>
<blockquote>
<p>💡 <strong>항상 <code>lastIdx</code>가 <code>my_string</code>의 범위를 벗어나지 않는 이유?</strong>
<code>my_string.endIndex</code>는 <em>문자열의 길이</em>와 같다 (즉, my_string.count와 동일한 위치).
따라서 제한사항에 의해 <code>s + overwrite_string.count</code>(덮어쓰기 마지막 인덱스 + 1) <code>≤ my_string.count</code>가 보장되므로, <code>lastIdx ≤ my_string.endIndex</code>도 항상 성립한다.</p>
</blockquote>
</li>
</ul>
<hr>
<h1 id="풀이-3">풀이 3</h1>
<pre><code class="language-swift">import Foundation

func solution(_ my_string:String, _ overwrite_string:String, _ s:Int) -&gt; String {
    let firstIdx = my_string.index(my_string.startIndex, offsetBy: s) // 덮어쓰기 시작 인덱스
    let lastIdx = my_string.index(my_string.startIndex, offsetBy: s + overwrite_string.count) // 덮어쓰기 마지막 인덱스 + 1

    let result = String(my_string[..&lt;firstIdx]) + overwrite_string + String(my_string[lastIdx...])  

    return result
}</code></pre>
<h3 id="설명">설명</h3>
<ol>
<li>덮어쓰기 시작할 인덱스 이전까지는 원본 문자열을 저장한다.</li>
<li>덮어쓰기 시작할 인덱스부터 덮어쓸 문자열의 끝까지를 저장한다.</li>
<li>남은 원본 문자열을 저장한다. (혹은 남은 문자열이 없거나)</li>
</ol>
<hr>
<h1 id="다른-사람의-풀이">다른 사람의 풀이</h1>
<pre><code class="language-swift">import Foundation

func solution(_ myString: String, _ overwriteString: String, _ s: Int) -&gt; String {
    var myString = Array(myString)
    myString.replaceSubrange(s...(overwriteString.count+s-1), with: Array(overwriteString))
    return String(myString)
}</code></pre>
<h3 id="설명-1">설명</h3>
<ul>
<li><p><strong>문자열을 문자 배열로 변환</strong>: <code>var myString = Array(myString)</code>
원본 문자열을 <code>[Character]</code> 타입의 배열로 변환 → Swift의 <em>정수 인덱싱을 사용</em>할 수 있어 작업이 단순해짐</p>
</li>
<li><p><strong>부분 범위 교체</strong>:
<code>replaceSubrange</code> 메서드는 배열의 특정 범위를 다른 요소들로 교체한다.
범위 지정은 다음과 같다.</p>
</li>
<li><p><em>시작*</em>: <code>s</code> (정수 인덱스)</p>
</li>
<li><p><em>끝*</em>: <code>overwriteString.count+s-1</code> (덮어쓸 문자열의 길이를 고려한 마지막 인덱스)</p>
</li>
<li><p><em><code>...</code>*</em>는 끝 인덱스를 포함하는 닫힌 범위를 만든다.
<code>Array(overwriteString)</code>는 덮어쓸 문자열도 문자 배열로 변환한다.</p>
</li>
<li><p><strong>결과 변환 및 반환</strong>:
수정된 문자 배열을 다시 <code>String</code>으로 변환하여 반환</p>
</li>
</ul>
<hr>
<h1 id="알게-된-것-정리">알게 된 것 정리</h1>
<ul>
<li><p><code>my_string.index(my_string.startIndex, offsetBy: s)</code>
Int 인덱스 값으로 String.index 값 얻기</p>
</li>
<li><p><code>myString.replaceSubrange(s...(overwriteString.count+s-1), with: Array(overwriteString))</code>
원본 배열의 a번 째부터 b번 째의 요소를 with 배열의 요소로 교체</p>
</li>
<li><p>문자열은 <code>Character</code> 배열로 변경해서 다루면 편리할 때가 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Swift 문법] Substring 이해하기]]></title>
            <link>https://velog.io/@juhee-dev/Swift-%EB%AC%B8%EB%B2%95-Substring-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/Swift-%EB%AC%B8%EB%B2%95-Substring-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 02 Apr 2025 08:22:37 GMT</pubDate>
            <description><![CDATA[<p>swift 알고리즘 풀이로 몇 번 문자열을 다룰 때마다 substring 때문에 빨간 오류 메시지를 봐야 했다.
오늘 짚고 넘어가며, String, Substring, Character에 대해 확실히 이해하고자 한다.</p>
<hr>
<h2 id="substring이란">Substring이란?</h2>
<p>Substring은 기존 String의 메모리를 공유하는 부분 문자열이다. Swift에서 문자열의 일부분을 효율적으로 다루기 위해 설계되었다.</p>
<h2 id="substring이-생성되는-경우">Substring이 생성되는 경우</h2>
<p>다음과 같은 String 메서드들을 사용할 때 Substring이 반환된다.</p>
<ol>
<li><p>prefix/suffix 메서드 사용 시:</p>
<pre><code class="language-swift">let hello = &quot;Hello, World!&quot;
let prefix = hello.prefix(5) // &quot;Hello&quot;는 Substring 타입</code></pre>
</li>
<li><p>split 메서드 사용 시:</p>
<pre><code class="language-swift">let sentence = &quot;Apple Banana Cherry&quot;
let words = sentence.split(separator: &quot; &quot;) // [Substring] 타입 배열 반환</code></pre>
</li>
<li><p>dropFirst/dropLast 메서드 사용 시:</p>
<pre><code class="language-swift">let text = &quot;Swift Programming&quot;
let dropped = text.dropFirst(6) // &quot;Programming&quot;은 Substring 타입</code></pre>
</li>
<li><p>범위(Range) 연산자 사용 시:</p>
<pre><code class="language-swift">let str = &quot;Hello, World!&quot;
if let range = str.range(of: &quot;World&quot;) {
 let substring = str[range] // &quot;World&quot;는 Substring 타입
}</code></pre>
</li>
</ol>
<h2 id="string-character-substring의-차이점">String, Character, Substring의 차이점</h2>
<h4 id="1-character">1. Character</h4>
<ul>
<li>단일 표시 단위(하나의 시각적 문자)를 나타냄</li>
<li><em>독립적인 자체 메모리 공간</em> 사용</li>
</ul>
<h4 id="2-string">2. String</h4>
<ul>
<li>Character들의 컬렉션, 텍스트의 전체 시퀀스</li>
<li><em>자체 메모리 공간</em>에서 문자열 데이터 관리</li>
</ul>
<h4 id="3-substring">3. Substring</h4>
<ul>
<li>원본 String의 일부분을 나타내는 뷰(view)</li>
<li><em>원본 String과 메모리 공유</em></li>
<li>임시적인 사용 목적 (장기 저장에는 적합하지 않음)</li>
</ul>
<p>가장 큰 차이는 메모리 관리 방식이다. Substring은 원본의 <strong>메모리만 참조</strong>하므로 빠르게 생성할 수 있다.
하지만 Substring이 존재하는 한, 원본 String의 전체 메모리는 해제되지 않는다. </p>
<blockquote>
<p>원본 String의 메모리를 계속 유지한다는 것은 Substring이 참조하는 메모리가 갑자기 사라지는 것을 방지한다는 의미이다.</p>
</blockquote>
<p>따라서 Substring은 임시 작업에는 효율적이지만, 장기간 저장할 필요가 있는 데이터는 반드시 String으로 변환해야 한다. <code>let newString = Stirng(substring)</code></p>
<p>그렇다면 Substring이 String을 참조하고 있을 때, String을 변경하면 어떻게 될까?</p>
<h2 id="원본-string의-값은-바뀔-수-있을까">원본 String의 값은 바뀔 수 있을까?</h2>
<p>여기서 중요한 점은, Swift에서 String은 값 타입이라는 것이다. 값 타입은 변수에 할당될 때나 함수에 전달될 때 복사된다.</p>
<pre><code class="language-swift">var original = &quot;Hello, Swift Programming!&quot;
let sub = original.dropFirst(7) // &quot;Swift Programming!&quot; - Substring

// original 값을 변경
original = &quot;Changed string&quot;

print(sub) // 여전히 &quot;Swift Programming!&quot; 출력</code></pre>
<p>따라서 위 코드에서 <code>original</code> 변수의 값을 변경해도 <code>sub</code>은 여전히 원래 참조하던 &quot;Swift Programming!&quot; 부분을 가리킨다.
이는 Swift가 변수 <code>original</code>의 값을 변경할 때 <strong>새로운 메모리 공간</strong>을 할당하고 <code>original</code> 변수가 그 새 공간을 가리키게 만들기 때문이다.
원래의 메모리 공간(&quot;Hello, Swift Programming!&quot;)은 <code>sub</code>이 여전히 참조하고 있으므로 해제되지 않는다.</p>
<hr>
<h2 id="결론">결론</h2>
<p>Swift의 Substring은 C 언어의 포인터 개념을 생각하면 이해가 쉽겠다. <del>(C언어에 대한 지식이 있다면 말이지)</del>
다만 포인터와는 달리 원본 값이 바뀌면 바뀐 값은 새로운 메모리에 할당되고, 원본을 참조하던 Substring의 값은 바뀌지 않는다는 것이 큰 차이이겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 | Swift | Lv.0] 대소문자 바꿔서 출력하기]]></title>
            <link>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%8C%80%EC%86%8C%EB%AC%B8%EC%9E%90-%EB%B0%94%EA%BF%94%EC%84%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Swift-Lv.0-%EB%8C%80%EC%86%8C%EB%AC%B8%EC%9E%90-%EB%B0%94%EA%BF%94%EC%84%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 02 Apr 2025 08:18:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/bc0abd38-91d9-4bf0-9d18-9278dd1c80e3/image.png" alt=""></p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/181949">문제 링크</a></p>
<hr>
<h1 id="풀이-1-오류">풀이 1: 오류</h1>
<pre><code class="language-swift">let s1 = readLine()!

let upper = &quot;A B C D E F G H I J K L M N O P Q R S T U V W X Y Z&quot;.split(separator: &quot; &quot;)
let lower = &quot;a b c d e f g h i j k l m n o p q r s t u v w x y z&quot;.split(separator: &quot; &quot;)
var result = &quot;&quot;

for j in 0..&lt;s1.count {
    for i in 0..&lt;upper.count {
        if Character(upper[i]) == Character(s1[j]) {
            result.append(lower[i])
        } else if Character(lower[i]) == Character(s1[j]) {
            result.append(lower[i])
        }
    }
}

print(result)</code></pre>
<h3 id="문제점">문제점</h3>
<ol>
<li><p><strong>문자열 인덱싱 문제</strong>
Swift에서는 String 타입의 인덱싱이 직접적으로 지원되지 않는다. <code>s1[j]</code>와 같은 방식으로 문자열의 특정 위치에 접근할 수 없다.
<span style="color:skyblue">문자열의 특정 위치에 접근하려면 String.Index를 사용해야 한다.</span></p>
</li>
<li><p><strong>Substring을 Character로 변환 시도</strong>
<code>Character(upper[i])</code>와 <code>Character(lower[i])</code>에서 upper와 lower는 [Substring] 타입이므로, 각 요소는 Substring 타입이다.
<span style="color:skyblue"/>Substring을 직접 Character로 변환하는 것은 불가능하므로 먼저 String으로 변환한 후 Character로 변환해야 한다.</p>
</li>
<li><p><strong>비효율적인 알파벳 생성 방식</strong>
알파벳을 공백으로 구분된 문자열로 정의하고 split을 사용하는 것은 비효율적이다. 더 직관적인 방법으로는 문자 배열을 직접 정의하거나 ASCII 값을 활용하는 방법이 있다.</p>
</li>
</ol>
<hr>
<h1 id="풀이-2">풀이 2</h1>
<pre><code class="language-swift">import Foundation

let s1 = readLine()!

let upper = Array(&quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;)
let lower = Array(&quot;abcdefghijklmnopqrstuvwxyz&quot;)
var result = &quot;&quot;

for c in s1 {
    if let index = upper.firstIndex(of: c) { // 대문자인 경우
        result.append(lower[index]) // 소문자로 반환
    } else if let index = lower.firstIndex(of: c) { // 소문자인 경우
        result.append(upper[index]) // 대문자로 반환
    } else {
        result.append(c)
    }
}

print(result)</code></pre>
<h3 id="설명">설명</h3>
<ul>
<li>대소문자 알파벳 배열을 만들어 주어진 문자열을 for문으로 확인해가며 한 글자씩 변경한다.</li>
<li><code>if let</code> 구문을 사용한 이유는 <code>firstIndex</code>의 반환값이 nil 인 경우도 있기 때문이다. (해당 문제에는 nil인 경우가 없지만)</li>
</ul>
<hr>
<h1 id="풀이-3-최종-코드">풀이 3: 최종 코드</h1>
<pre><code class="language-swift">import Foundation

let s1 = readLine()!
var result = &quot;&quot;

for c in s1 {
    if c.isUppercase {
        result.append(c.lowercased()) // 대문자면 소문자로 변경
    } else {
        result.append(c.uppercased()) // 소문자면 대문자로 변경
    }
}

print(result)</code></pre>
<h3 id="설명-1">설명</h3>
<ul>
<li>알파벳 배열을 직접 만들지 않고 <code>isUppercase</code> 내장 속성을 사용해 대소문자 판별</li>
<li><code>lowercased()</code>, <code>uppercased()</code> 내장 메서드 사용해 대소문자 변경</li>
</ul>
<p>👉 스위프트다운 코드 완성</p>
<hr>
<p>매번 헷갈리던 Substring에 대한 포스트는 따로 적어두었다.
<a href="https://velog.io/@juhee-dev/Swift-%EB%AC%B8%EB%B2%95-Substring-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0">https://velog.io/@juhee-dev/Swift-문법-Substring-이해하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오맵 실시간 위치 마커에 프로필사진 띄우기]]></title>
            <link>https://velog.io/@juhee-dev/%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%9C%84%EC%B9%98-%EB%A7%88%EC%BB%A4%EC%97%90-%ED%94%84%EB%A1%9C%ED%95%84%EC%82%AC%EC%A7%84-%EB%9D%84%EC%9A%B0%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%9C%84%EC%B9%98-%EB%A7%88%EC%BB%A4%EC%97%90-%ED%94%84%EB%A1%9C%ED%95%84%EC%82%AC%EC%A7%84-%EB%9D%84%EC%9A%B0%EA%B8%B0</guid>
            <pubDate>Wed, 26 Feb 2025 13:53:31 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/juhee-dev/post/74362542-0c16-4daa-b6d8-19e2c480e0f6/image.png" alt=""></p>
<h1 id="이슈">이슈</h1>
<p>👉 사용자의 실시간 위치를 지도에 표시할 때, 심볼로 원형 위에 프로필이미지를 겹친 <strong>뷰</strong>를 이미지 대신 띄우는 것이 목표.</p>
<h1 id="원인">원인</h1>
<p>👉 카카오맵에서 지원하는 마커(Poi) 형식은 UIImage로 한정되어 있어, 커스텀 마커를 띄울 수 없었음</p>
<h1 id="해결">해결</h1>
<p>👉 심볼이 될 뷰를 만들고, Extention을 통해 뷰를 UIImage로 변환시키는 함수 작성</p>
<h2 id="최종-코드">최종 코드</h2>
<p><em>ProfileImageView:</em></p>
<pre><code class="language-swift">import SwiftUI

// 프로필 이미지를 위한 SwiftUI View
struct ProfileImageView: View {
    let image: Image

    var body: some View {
        ZStack {
            Image(&quot;icon-location-anchor&quot;)
                .frame(width: LayoutAdapter.shared.scale(value: 11), height: LayoutAdapter.shared.scale(value: 11))
                .padding(.top, 55)
            Image(&quot;icon-location-pin&quot;)
                .frame(width: LayoutAdapter.shared.scale(value: 45), height: LayoutAdapter.shared.scale(value: 56))

            image
                .resizable()
                .scaledToFill()
                .frame(width: LayoutAdapter.shared.scale(value: 37), height: LayoutAdapter.shared.scale(value: 37))
                .clipShape(Circle())
                .frame(width: LayoutAdapter.shared.scale(value: 10), height: LayoutAdapter.shared.scale(value: 10))
                .padding(.bottom, 10)
        }
    }
}</code></pre>
<br>

<p><em>Extensions:</em></p>
<pre><code class="language-swift">// MARK: View to UIImage - 실시간 위치 조회시 프로필사진 마킹용
extension View {
    func snapshot() -&gt; UIImage {
        let controller = UIHostingController(rootView: self.edgesIgnoringSafeArea(.all))
        let view = controller.view

        let targetSize = controller.view.intrinsicContentSize

        view?.bounds = CGRect(origin: .zero, size: targetSize)
        view?.backgroundColor = .clear

        let renderer = UIGraphicsImageRenderer(size: targetSize)

        return renderer.image { _ in
            view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
        }
    }
}

// MARK: 이미지 크기 조절
extension UIImage {
    func resizedForProfile(to size: CGSize) -&gt; UIImage {
        return UIGraphicsImageRenderer(size: size).image { _ in
            draw(in: CGRect(origin: .zero, size: size))
        }
    }
}</code></pre>
<br>

<p><em>MapPinView:</em></p>
<pre><code class="language-swift">// Poi 표시 스타일 생성
        func createPoiStyle() {
            guard let view = controller?.getView(mapViewName) as? KakaoMap else {
                print(&quot;view is nil in createPoiStyle&quot;)
                return
            }
            let manager = view.getLabelManager()

            // 내 마커용 스타일 생성: SwiftUI View를 UIImage로 변환
            let myMarker = ProfileImageView(image: Image(myLocation?.member?.profileImage ?? &quot;icon-profile-default&quot;))
            let mySymbolImage = myMarker.snapshot().resizedForProfile(to: CGSize(width: LayoutAdapter.shared.scale(value: 30), height: LayoutAdapter.shared.scale(value: 40.667)))
            let myIconStyle = PoiIconStyle(symbol: mySymbolImage, anchorPoint: CGPoint(x: 0.5, y: 1))
            let myPoiStyle = PoiStyle(styleID: &quot;myPoiStyle&quot;, styles: [
                PerLevelPoiStyle(iconStyle: myIconStyle, level: 12)
            ])
            manager.addPoiStyle(myPoiStyle)

            // 친구들 각각의 마커 스타일 생성
            for (index, friend) in friendsLocation.enumerated() {
                let profileImageName = friend.member?.profileImage ?? &quot;icon-profile-default&quot;
                let friendMarker = ProfileImageView(image: Image(profileImageName))
                let friendSymbolImage = friendMarker.snapshot().resizedForProfile(to: CGSize(width: LayoutAdapter.shared.scale(value: 30), height: LayoutAdapter.shared.scale(value: 40.667)))
                let friendIconStyle = PoiIconStyle(symbol: friendSymbolImage, anchorPoint: CGPoint(x: 0.5, y: 1))

                // 각 친구별로 고유한 styleID 생성
                let styleID = &quot;friendPoiStyle_\(index)&quot;
                let friendPoiStyle = PoiStyle(styleID: styleID, styles: [
                    PerLevelPoiStyle(iconStyle: friendIconStyle, level: 12)
                ])
                manager.addPoiStyle(friendPoiStyle)
            }
        }

        func createPois() {
            guard let view = controller?.getView(mapViewName) as? KakaoMap else {
                print(&quot;view is nil in createPois&quot;)
                return
            }
            let manager = view.getLabelManager()
            let layer = manager.getLabelLayer(layerID: &quot;PoiLayer&quot;)

            // 내 위치 POI 생성
            let myPoiOption = PoiOptions(styleID: &quot;myPoiStyle&quot;)
            myPoiOption.rank = 0

            if let myLocation {
                let myPoi = layer?.addPoi(
                    option: myPoiOption,
                    at: MapPoint(longitude: myLocation.x, latitude: myLocation.y)
                )
                myPoi?.show()
            }

            // 친구들 위치 POI 생성 - 각각 다른 스타일 적용
            for (index, friend) in friendsLocation.enumerated() {
                let friendPoiOption = PoiOptions(styleID: &quot;friendPoiStyle_\(index)&quot;)
                friendPoiOption.rank = 1

                let friendPoi = layer?.addPoi(
                    option: friendPoiOption,
                    at: MapPoint(longitude: friend.x, latitude: friend.y)
                )
                friendPoi?.show()
            }
        }</code></pre>
<hr>
<p>참고
<a href="https://stickode.tistory.com/448">https://stickode.tistory.com/448</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[사용자의 실시간 위치가 카카오맵에 보이지 않는 문제 해결하기]]></title>
            <link>https://velog.io/@juhee-dev/%EC%82%AC%EC%9A%A9%EC%9E%90%EC%9D%98-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%9C%84%EC%B9%98%EA%B0%80-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5%EC%97%90-%EB%B3%B4%EC%9D%B4%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/%EC%82%AC%EC%9A%A9%EC%9E%90%EC%9D%98-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%9C%84%EC%B9%98%EA%B0%80-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5%EC%97%90-%EB%B3%B4%EC%9D%B4%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 24 Feb 2025 15:46:55 GMT</pubDate>
            <description><![CDATA[<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/juhee-dev/post/38686c19-63b0-4d9a-9b23-686a15718202/image.png" alt="">해결 전</th>
<th><img src="https://velog.velcdn.com/images/juhee-dev/post/0aa11304-b6f6-4066-ac78-d3e482733f89/image.png" alt="">해결 후</th>
</tr>
</thead>
</table>
<hr>
<h2 id="이슈">이슈</h2>
<p>👉 사용자의 실시간 위치를 지도에 띄울 때, 위치 좌표값은 정상적으로 받지만 카카오맵이 보이지 않음</p>
<h2 id="원인">원인</h2>
<p>👉 실제 위치를 사용할 때에는 <code>viewModel.userLatitude</code>와 <code>viewModel.userLongitude</code>가 초기값 0으로 시작하고, 위치 업데이트를 받은 후에야 실제 값으로 변경됨. MapPinView에는 <strong>위치가 업데이트될 때 지도를 새로 그리는 로직이 없음</strong></p>
<h2 id="해결">해결</h2>
<p>👉 위치를 보여주는 FriendsLocationView, 카카오맵 보여주는 컨테이너인 MapPinView에 위치 업데이트 로직 생성
<em>MapPinView.swift:</em></p>
<pre><code class="language-swift">class KakaoMapCoordinator: NSObject, MapControllerDelegate {
                ...

                // location 값이 변경될 때 지도 업데이트를 위한 메서드 추가
        func updateLocation(_ newLocation: Location) {
            self.location = newLocation

            guard let view = controller?.getView(&quot;mapview&quot;) as? KakaoMap else {
                print(&quot;view is nil in updateLocation&quot;)
                return
            }

            // 기존 POI 삭제
            let manager = view.getLabelManager()
            if let layer = manager.getLabelLayer(layerID: &quot;PoiLayer&quot;) {
                layer.clearAllItems() // TODO: 맞는 메서드인지 확인 필요
            }

            // 새로운 POI 생성
            createPois()

            // 카메라 이동
            let cameraUpdate = CameraUpdate.make(
                target: MapPoint(longitude: newLocation.x, latitude: newLocation.y),
                mapView: view
            )
            view.moveCamera(cameraUpdate)
        }
}</code></pre>
<p><em>FriendsLocationView.swift:</em></p>
<pre><code class="language-swift">var body: some View {
    ZStack {
                ...
    }
    .onChange(of: viewModel.userLatitude) { _, _ in
        updateLocation()
    }
    .onChange(of: viewModel.userLongitude) { _, _ in
        updateLocation()
    }
}</code></pre>
<hr>
<p>짧은 지식💡 x:0, y: 0 좌표는 널 아일랜드라고 한다. 하핫.</p>
<p>카카오맵은 좌표가 이상하면 스카이블루 스크린을 띄워주는 듯. 다음에 같은 화면이 보이면 가장 먼저 좌표 동기화 문제를 의심해봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftUI DatePicker 날짜를 동기화 시키자]]></title>
            <link>https://velog.io/@juhee-dev/SwiftUI-DatePicker-%EB%82%A0%EC%A7%9C%EB%A5%BC-%EB%8F%99%EA%B8%B0%ED%99%94-%EC%8B%9C%ED%82%A4%EC%9E%90</link>
            <guid>https://velog.io/@juhee-dev/SwiftUI-DatePicker-%EB%82%A0%EC%A7%9C%EB%A5%BC-%EB%8F%99%EA%B8%B0%ED%99%94-%EC%8B%9C%ED%82%A4%EC%9E%90</guid>
            <pubDate>Wed, 13 Nov 2024 13:59:35 GMT</pubDate>
            <description><![CDATA[<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/juhee-dev/post/15627bf3-bbea-4b9f-aca5-c3f489479392/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/juhee-dev/post/b9f84d82-0ef5-4da9-a6ec-dc2dcf8fcab9/image.png" alt=""></th>
</tr>
</thead>
</table>
<h1 id="이슈">이슈</h1>
<p>하루종일 토글을 키고 시작일 날짜를 바꿀 경우 화면에서는 종료일 날짜도 동일하게 변경되지만 실제 변수값은 오늘 날짜에서 바뀌지 않는다.</p>
<hr>
<h1 id="원인">원인</h1>
<p>초기화 부분에서는 기본 종료시간을 무조건 시작일 기준으로 1시간 후로 설정하고 있다.
그러나 하루종일 토글값이 변경될 때와 시작일이 변경될 때, 종료일을 시작일과 동기화하는 로직이 없기에 → 시작일을 10일 뒤로 바꿔도 종료일은 여전히 10일 전으로 설정되어 있던 것.</p>
<pre><code class="language-swift">// 일정 시작일, 종료일 초기화 코드
let endOfHour = calendar.date(byAdding: .hour, value: 1, to: startOfHour)!
self.endTime = endOfHour</code></pre>
<hr>
<h1 id="해결">해결</h1>
<p>하루종일 토글이 켜질 때와 일정 시작일이 바뀔 때, 종료일을 시작일과 동기화하도록 수정</p>
<pre><code class="language-swift">@Published var isAllDay: Bool = true {
    didSet {
        if isAllDay {
        // 하루종일이 켜지면 종료일을 시작일과 동일하게 설정
        endTime = Calendar.current.startOfDay(for: startTime)
        }
    }
}
@Published var startTime: Date {
    didSet {
        if isAllDay {
        // 하루종일일 때 시작일이 변경되면 종료일도 함께 변경
        endTime = Calendar.current.startOfDay(for: startTime)
        }
    }
}</code></pre>
<blockquote>
<p>💡 <strong>didSet 이란?</strong>
프로퍼티 옵저버의 종류. 프로퍼티의 값이 변경된 직후에 호출되는 코드 블록을 정의할 수 있게 한다.</p>
<p>프로퍼티 옵저버의 두 가지:</p>
</blockquote>
<ol>
<li><code>willSet</code>: 값이 변경되기 직전에 호출</li>
<li><code>didSet</code>: 값이 변경된 직후에 호출</li>
</ol>
<hr>
<p>변수의 초기화뿐 아니라 동기화 등 비즈니스 로직에 대해서도 세심하게 신경쓰자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftUI 원하지 않은 상단 여백 제거하기 - 뷰 계층구조 확인하는 방법]]></title>
            <link>https://velog.io/@juhee-dev/SwiftUI-%EC%9B%90%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%80-%EC%83%81%EB%8B%A8-%EC%97%AC%EB%B0%B1-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0-%EB%B7%B0-%EA%B3%84%EC%B8%B5%EA%B5%AC%EC%A1%B0-%ED%99%95%EC%9D%B8%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@juhee-dev/SwiftUI-%EC%9B%90%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%80-%EC%83%81%EB%8B%A8-%EC%97%AC%EB%B0%B1-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0-%EB%B7%B0-%EA%B3%84%EC%B8%B5%EA%B5%AC%EC%A1%B0-%ED%99%95%EC%9D%B8%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 30 Oct 2024 06:12:17 GMT</pubDate>
            <description><![CDATA[<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/juhee-dev/post/a795e666-73f4-46f6-9f63-6f88d7a3d83a/image.png" alt=""></th>
<th align="center"><img src="https://velog.velcdn.com/images/juhee-dev/post/322c6ecd-de23-42db-a2b4-6a9517b5b156/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td align="center">수정 전</td>
<td align="center">수정 후</td>
</tr>
</tbody></table>
<hr>
<h1 id="이슈">이슈</h1>
<p><strong>캘린더뷰 상단에 원하지 않는 여백이 생김.</strong> 프리뷰로 보면 정상적이나 시뮬레이터로 확인하면 나타나는 현상.</p>
<h3 id="시도한-방법들">시도한 방법들</h3>
<ol>
<li><p>프로젝트에서 SwiftUI와 UIKit을 함께 사용하고 있는데, 호환의 문제인가 싶어 ScheduleView를 UIKit으로 변환해서 해결하려고 했으나 <code>Force Cast Violation: Force casts should be avoided (force_cast)</code> 오류 발생.</p>
</li>
<li><p>여전히 호환 문제라고 생각해서 기존 UIKit 탭바를 래핑해서 하단 탭바를 SwiftUI로 사용하니 문제 해결됨. 다만 기존의 UIKit 뷰에서 상단 툴바가 사라지는 문제 발생(빈대 잡으려다 초가삼간 태우는 꼴)</p>
</li>
</ol>
<p>이외 여러가지 시도했으나 해결되지 않음</p>
<hr>
<h1 id="원인">원인</h1>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/e96d55cb-273d-4f44-9216-7cd5e58093f7/image.png" alt=""></p>
<p>뷰 계층구조를 확인하니 하단탭바에서 일정 탭을 클릭하면 <code>UINavigationBar</code> → <code>_UIBarBackground</code>가 쌓여서 추가적인 여백이 발생했다.</p>
<p>그래서 하단 탭바를 다른 방식으로 구현했을때 문제가 해결되었던 것.</p>
<hr>
<h1 id="해결🥳">해결🥳</h1>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/db228c2f-7efc-4103-ae76-93057bec9714/image.png" alt=""></p>
<p><code>_UIBarBackground</code>를 제거하거나 높이를 0으로 만들고 싶었지만 적용되지 않아서 상위 컨테이너인 <code>UINavigationBar</code>를 제거했다.</p>
<pre><code class="language-swift">// ScheduleView를 UINavigationController로 감싸서 사용
let scheduleHostingVC = UIHostingController(rootView: ScheduleView())
let scheduleNavVC = UINavigationController(rootViewController: scheduleHostingVC)  // UINavigationController로 감싸기
scheduleHostingVC.tabBarItem = UITabBarItem(...)

viewControllers = [mainVC, scheduleNavVC, friendsVC, myPageVC]  // NavigationController를 탭에 추가</code></pre>
<p>기존 코드
⬇️
수정된 코드</p>
<pre><code class="language-swift">// ScheduleView를 UIHostingController만 사용
let scheduleHostingVC = UIHostingController(rootView: ScheduleView())  // UINavigationController로 감싸지 않음
scheduleHostingVC.tabBarItem = UITabBarItem(...)

viewControllers = [mainVC, scheduleHostingVC, friendsVC, myPageVC]  // HostingController를 직접 탭에 추가</code></pre>
<h3 id="주요-차이점">주요 차이점</h3>
<ol>
<li>UINavigationController 레이어가 제거됨</li>
<li>불필요한 UINavigationBar가 생성되지 않음</li>
<li>뷰 계층이 더 단순해짐 (HostingController -&gt; ScheduleView)</li>
</ol>
<hr>
<p>앞으로는 뷰에 의도하지 않은 배치나 여백이 생기는 경우에는 뷰 계층 구조를 확인해보자.</p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/aba27a89-3b3c-4c87-8c9c-75141e4a9203/image.png" alt="">
시뮬레이터를 실행한 채 하단의 저 버튼을 누르면 뷰 구조를 볼 수 있고, 아무 컴포넌트를 클릭 후 드래그 하면 입체적으로 구조를 확인할 수 있다.</p>
<p>xcode에서 이런 기능을 지원하는 줄 몰랐다. 알려준 동료분께 무한 감사를...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftUI 카카오맵(v2) 그리기 - 맵 안보이는 문제 해결]]></title>
            <link>https://velog.io/@juhee-dev/SwiftUI-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5v2-%EA%B7%B8%EB%A6%AC%EA%B8%B0-%EB%A7%B5-%EC%95%88%EB%B3%B4%EC%9D%B4%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@juhee-dev/SwiftUI-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5v2-%EA%B7%B8%EB%A6%AC%EA%B8%B0-%EB%A7%B5-%EC%95%88%EB%B3%B4%EC%9D%B4%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Thu, 05 Sep 2024 11:46:52 GMT</pubDate>
            <description><![CDATA[<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/juhee-dev/post/92b1da95-0c71-433f-b733-26eb893d0b22/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/juhee-dev/post/0407649f-8e40-4fd8-9728-c102114ee45b/image.png" alt=""></th>
</tr>
</thead>
</table>
<h1 id="목표">목표</h1>
<ul>
<li>SwiftUI를 사용하여 카카오맵 띄우기</li>
<li><a href="https://apis.map.kakao.com/ios_v2/docs/getting-started/basics/04_drawmap/#swiftui">공식 가이드</a>대로 쓴 코드의 오류 해결하기</li>
</ul>
<hr>
<h1 id="기존-코드">기존 코드</h1>
<pre><code class="language-swift">import SwiftUI
import KakaoMapsSDK

struct MapView: View {
    @State var draw: Bool = true   // 뷰의 appear 상태를 전달하기 위한 변수.

    init() {
        SDKInitializer.InitSDK(appKey: Config.kakaoAppKey)
    }

    var body: some View {
        KakaoMapView(draw: $draw)
            .onAppear(perform: {
                self.draw = true
            })
            .onDisappear(perform: {
                self.draw = false
            })
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

struct KakaoMapView: UIViewRepresentable {
    @Binding var draw: Bool

    /// UIView를 상속한 KMViewContainer를 생성한다.
    /// 뷰 생성과 함께 KMControllerDelegate를 구현한 Coordinator를 생성하고, 엔진을 생성 및 초기화한다.
    func makeUIView(context: Self.Context) -&gt; KMViewContainer {
        let view: KMViewContainer = KMViewContainer()
        view.sizeToFit()
        context.coordinator.createController(view)
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            context.coordinator.controller?.prepareEngine()
            print(&quot;makeUIView - Engine prepared!&quot;)
        }

        return view
    }

    /// Updates the presented `UIView` (and coordinator) to the latest configuration.
    /// draw가 true로 설정되면 엔진을 시작하고 렌더링을 시작한다.
    /// draw가 false로 설정되면 렌더링을 멈추고 엔진을 stop한다.
    func updateUIView(_ uiView: KMViewContainer, context: Self.Context) {
        if draw {
            context.coordinator.controller?.activateEngine()
            print(&quot;updateUIView - Engine activated!&quot;)
        } else {
            context.coordinator.controller?.resetEngine()
        }
    }

    /// Coordinator 생성
    func makeCoordinator() -&gt; KakaoMapCoordinator {
        return KakaoMapCoordinator()
    }

    /// Cleans up the presented `UIView` (and coordinator) in anticipation of their removal.
    static func dismantleUIView(_ uiView: KMViewContainer, coordinator: KakaoMapCoordinator) {

    }

    /// Coordinator 구현. KMControllerDelegate를 adopt한다.
    class KakaoMapCoordinator: NSObject, MapControllerDelegate {
        var controller: KMController?
        var first: Bool

        override init() {
            first = true
            super.init()
        }

        // KMController 객체 생성 및 event delegate 지정
        func createController(_ view: KMViewContainer) {
            controller = KMController(viewContainer: view)
            controller?.delegate = self
        }

        // KMControllerDelegate Protocol method구현

        // 엔진 생성 및 초기화 이후, 렌더링 준비가 완료되면 아래 addViews를 호출한다.
        // 원하는 뷰를 생성한다.
        func addViews() {
            let defaultPosition: MapPoint = MapPoint(longitude: 127.108678, latitude: 37.402001)
            let mapviewInfo: MapviewInfo = MapviewInfo(viewName: &quot;mapview&quot;, viewInfoName: &quot;map&quot;, defaultPosition: defaultPosition)

            controller?.addView(mapviewInfo)
        }

        // addView 성공 이벤트 delegate. 추가적으로 수행할 작업을 진행한다.
        func addViewSucceeded(_ viewName: String, viewInfoName: String) {
            print(&quot;OK&quot;) // 추가 성공. 성공시 추가적으로 수행할 작업을 진행한다.
        }

        // addView 실패 이벤트 delegate. 실패에 대한 오류 처리를 진행한다.
        func addViewFailed(_ viewName: String, viewInfoName: String) {
            print(&quot;Failed&quot;)
        }

        func authenticationSucceeded() {
            print(&quot;auth succeed!!&quot;)
        }

        func authenticationFailed(_ errorCode: Int, desc: String) {
            print(&quot;auth failed&quot;)
            print(&quot;error code: \(errorCode)&quot;)
            print(desc)
            //            print(controller?.getStateDescMessage())
        }

        // KMViewContainer 리사이징 될 때 호출.
        func containerDidResized(_ size: CGSize) {
            let mapView: KakaoMap? = controller?.getView(&quot;mapview&quot;) as? KakaoMap
            mapView?.viewRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)
            if first {
                let cameraUpdate: CameraUpdate = CameraUpdate.make(target: MapPoint(longitude: 127.108678, latitude: 37.402001), zoomLevel: 10, mapView: mapView!)
                mapView?.moveCamera(cameraUpdate)
                first = false
            }
        }

    }
}</code></pre>
<hr>
<h1 id="트러블-슈팅">트러블 슈팅</h1>
<h2 id="이슈">이슈</h2>
<ul>
<li>카카오맵 키 인증까지는 정상적으로 되지만 지도 뷰가 뜨지 않고 연한 초록색으로만 표시됨</li>
</ul>
<h2 id="원인">원인</h2>
<p>1️⃣</p>
<ul>
<li>뷰를 처음 그리는 경우 <strong>명시적으로 엔진을 활성화</strong>해야 함.</li>
<li>공식 문서에 따르면 KakaoMapsSDK의 라이프 사이클은 <code>엔진 초기상태</code> → <code>인증 시작</code> → <code>엔진 대기</code> → <code>인증 성공</code> → <code>엔진 활성</code> → <code>지도 그리기</code> 순으로 진행됨.</li>
<li>인증 성공 후 직접 엔진 활성 코드를 호출하지 않았기 때문에 다음 작업인 지도 그리기가 진행되지 않았던 것.</li>
</ul>
<p>2️⃣</p>
<ul>
<li>엔진 대기, 인증하는 과정에서 <strong>SDK 라이프사이클이 꼬임</strong></li>
</ul>
<h2 id="🌟-해결">🌟 해결</h2>
<ul>
<li>인증이 완료되면 <strong>명시적으로 엔진을 활성화</strong> 함.</li>
<li>또한 <strong>엔진 대기 부분을 비동기적으로 처리</strong>해 라이프사이클 문제를 해결함.</li>
</ul>
<hr>
<h1 id="최종-코드">최종 코드</h1>
<p>기존 코드에서 달라진 부분은 🌟로 표시해두었다.</p>
<pre><code class="language-swift">import SwiftUI
import KakaoMapsSDK

struct MapView: View {
    @State var draw: Bool = false // 뷰의 appear 상태를 전달하기 위한 변수.

    // 🌟 SDKInitializer는 AppDelegate에서 이미 설정했으므로 중복된 기능 제외

    var body: some View {
        KakaoMapView(draw: $draw)
            .onAppear(perform: {
                self.draw = true
            })
            .onDisappear(perform: { self.draw = false   })
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

struct KakaoMapView: UIViewRepresentable {
    @Binding var draw: Bool

    /// UIView를 상속한 KMViewContainer를 생성한다.
    /// 뷰 생성과 함께 KMControllerDelegate를 구현한 Coordinator를 생성하고, 엔진을 생성 및 초기화한다.
    func makeUIView(context: Self.Context) -&gt; KMViewContainer {
        let view: KMViewContainer = KMViewContainer()
        view.sizeToFit()
        context.coordinator.createController(view)
        // 🌟 prepareEngine 비동기적 처리하지 않으면 맵 보이지 않음. 
        // 🌟 DispatchQueue.main.asyncAfter(deadline: .now() + 2) 사용해도 되지만 대기시간이 있어 async()보다 뷰가 느리게 뜸
        DispatchQueue.main.async() {
            context.coordinator.controller?.prepareEngine()
            print(&quot;makeUIView - Engine prepared!&quot;)
        }

        return view
    }

    /// Updates the presented `UIView` (and coordinator) to the latest configuration.
    /// draw가 true로 설정되면 엔진을 시작하고 렌더링을 시작한다.
    /// draw가 false로 설정되면 렌더링을 멈추고 엔진을 stop한다.
    func updateUIView(_ uiView: KMViewContainer, context: Self.Context) {
        if draw {
            context.coordinator.controller?.activateEngine()
            print(&quot;updateUIView - Engine activated!&quot;)
        } else {
            context.coordinator.controller?.resetEngine()
        }
    }

    /// Coordinator 생성
    func makeCoordinator() -&gt; KakaoMapCoordinator {
        return KakaoMapCoordinator()
    }

    /// Cleans up the presented `UIView` (and coordinator) in anticipation of their removal.
    static func dismantleUIView(_ uiView: KMViewContainer, coordinator: KakaoMapCoordinator) {

    }

    /// Coordinator 구현. KMControllerDelegate를 adopt한다.
    class KakaoMapCoordinator: NSObject, MapControllerDelegate {
        var controller: KMController?
        var first: Bool

        override init() {
            first = true
            super.init()
        }

        // KMController 객체 생성 및 event delegate 지정
        func createController(_ view: KMViewContainer) {
            controller = KMController(viewContainer: view)
            controller?.delegate = self
        }

        // KMControllerDelegate Protocol method구현

        // 엔진 생성 및 초기화 이후, 렌더링 준비가 완료되면 아래 addViews를 호출한다.
        // 원하는 뷰를 생성한다.
        func addViews() {
            let defaultPosition: MapPoint = MapPoint(longitude: 127.108678, latitude: 37.402001)
            let mapviewInfo: MapviewInfo = MapviewInfo(viewName: &quot;mapview&quot;, viewInfoName: &quot;map&quot;, defaultPosition: defaultPosition)

            controller?.addView(mapviewInfo)
        }

        // addView 성공 이벤트 delegate. 추가적으로 수행할 작업을 진행한다.
        func addViewSucceeded(_ viewName: String, viewInfoName: String) {
            print(&quot;OK&quot;) // 추가 성공. 성공시 추가적으로 수행할 작업을 진행한다.
        }

        // addView 실패 이벤트 delegate. 실패에 대한 오류 처리를 진행한다.
        func addViewFailed(_ viewName: String, viewInfoName: String) {
            print(&quot;Failed to add view&quot;)
        }

        func authenticationSucceeded() {
            print(&quot;auth succeed!!&quot;)
            print(controller?.isEnginePrepared)
            print(controller?.isEngineActive)
            // 🌟 인증이 완료되면 엔진을 활성화시킨다.
            if let controller = controller,
               !controller.isEngineActive {
                controller.activateEngine()
            }
        }

        func authenticationFailed(_ errorCode: Int, desc: String) {
            print(&quot;auth failed&quot;)
            print(&quot;error code: \(errorCode)&quot;)
            print(desc)
        }

        // KMViewContainer 리사이징 될 때 호출.
        func containerDidResized(_ size: CGSize) {
            let mapView: KakaoMap? = controller?.getView(&quot;mapview&quot;) as? KakaoMap
            mapView?.viewRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)
            if first {
                let cameraUpdate: CameraUpdate = CameraUpdate.make(target: MapPoint(longitude: 127.108678, latitude: 37.402001), zoomLevel: 10, mapView: mapView!)
                mapView?.moveCamera(cameraUpdate)
                first = false
            }
        }

    }
}

#Preview {
    MapView()
}
</code></pre>
<hr>
<p>며칠을 고생하다가 드디어 지도 안보이는 이슈를 해결했다.</p>
<p>KakaoMapsSDK가 최근에 V2로 바뀜 + 계속 업데이트 되는 중 이슈로 생각보다 자료가 적었는데 해결되어 다행이다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Moya 서버 통신 - request query를 이용한 get 방식]]></title>
            <link>https://velog.io/@juhee-dev/Moya-%EC%84%9C%EB%B2%84-%ED%86%B5%EC%8B%A0-request-query%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-get-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@juhee-dev/Moya-%EC%84%9C%EB%B2%84-%ED%86%B5%EC%8B%A0-request-query%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-get-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Sun, 01 Sep 2024 15:48:08 GMT</pubDate>
            <description><![CDATA[<h2 id="0-목표">0. 목표</h2>
<ul>
<li>Moya 라이브러리 사용</li>
<li>사용자 ID를 request query로 보내 서버와 통신</li>
<li>get 방식 사용</li>
<li>최종: 즐겨찾기 목록(배열) 가져오기<blockquote>
<p>💡 <em>request query?</em>
URI 주소 바깥 부분(? 이후)에 변수를 담는 방식
ex) https🩵://juhee.com/<strong>location?memberSeq=1</strong></p>
</blockquote>
</li>
</ul>
<hr>
<h2 id="1-어떤-통신을-할-것인가-service-api-작성">1. 어떤 통신을 할 것인가: Service API 작성</h2>
<pre><code class="language-swift">import Foundation
import Moya

enum LocationAPI {
    case getLocation(memberSeq: Int) // request query에 필요한 memberSeq
}

extension LocationAPI: TargetType {
    var baseURL: URL {
        return URL(string: Config.baseURL)! // 통신할 서버의 기본 주소
    }

    var path: String {
        switch self {
        case .getLocation(let memberSeq):
            return &quot;/location&quot; // 상세 주소
        }
    }

    var method: Moya.Method {
        switch self {
        case .getLocation:
            return .get // http method
        }
    }

    var task: Task {
        switch self {
        case .getLocation(let memberSeq): // [&quot;key&quot;: value] 형식으로 파라미터 작성
            return .requestParameters(parameters: [&quot;memberSeq&quot;: memberSeq], encoding: URLEncoding.queryString)
        }
    }

    var headers: [String: String]? {
        return [&quot;Content-Type&quot;: &quot;application/json&quot;]
    }

    var sampleData: Data {
        return Data()
    }
}</code></pre>
<p>💥 처음에는 path 리턴값을 <code>return &quot;/location?memberSeq=\(memberSeq)&quot;</code>로 작성했다가 오류가 났다.
🌟 원인은 task 부분에 이미 쿼리 파라미터를 작성해 <code>&quot;/location?memberSeq=1&amp;memberSeq=1&quot;</code>과 같은 형태의 URL이 되었던 것.</p>
<hr>
<h2 id="2-받아오는-데이터는-어떤-형식인가-response-model">2. 받아오는 데이터는 어떤 형식인가: Response Model</h2>
<p>JSON 파싱을 위한 모델링이 필요하다.
데이터가 사진과 같이 배열로 들어오기 때문에, <strong>큰 틀</strong>과 그 안에 담길 <strong>작은 틀</strong>(<code>data</code> 배열) 두 개로 모델링했다.
<img src="https://velog.velcdn.com/images/juhee-dev/post/46af9d4c-ad30-4b02-b768-79de661a75e8/image.png" alt=""></p>
<h3 id="21-큰-틀">2.1. 큰 틀</h3>
<pre><code class="language-swift">import Foundation

struct GenericResponse&lt;T: Codable&gt;: Codable {
    let status: Int
    let message: String
    let data: T
}</code></pre>
<h3 id="22-작은-틀">2.2. 작은 틀</h3>
<pre><code class="language-swift">import Foundation

struct FavLocation: Codable {
    let locationSeq: Int
    let location: String
    let streetName: String
}

typealias GetFavLocationResponse = [FavLocation]</code></pre>
<hr>
<h2 id="3-실제-작동부-viewmodel">3. 실제 작동부: ViewModel</h2>
<pre><code class="language-swift">import SwiftUI
import Moya

final class CreateScheduleViewModel: ObservableObject {
    @Published var favPlaces: [Place] = []

    func getFavoriteLocation() {
        let provider = MoyaProvider&lt;LocationAPI&gt;()
        let memberSeq = 1 // 실제 사용 시에는 현재 로그인한 사용자의 memberSeq를 사용해야 합니다.

        provider.request(.getLocation(memberSeq: memberSeq)) { result in
            switch result {
            case .success(let response):
                if response.statusCode == 200 {
                    do {
                        let decoder = JSONDecoder()
                        let genericResponse = try decoder.decode(GenericResponse&lt;GetFavLocationResponse&gt;.self, from: response.data)

                        DispatchQueue.main.async {
                            self.favPlaces = genericResponse.data.map { location in
                                Place(location: location.location,
                                      streetName: location.streetName)
                            }
                            print(&quot;즐겨찾기 위치 로드 성공: \(self.favPlaces.count)개의 위치를 받았습니다.&quot;)
                        }
                    } catch {
                        print(&quot;JSON 디코딩 실패: \(error.localizedDescription)&quot;)
                    }
                } else {
                    print(&quot;서버 오류: \(response.statusCode)&quot;)
                    if let json = try? response.mapJSON() as? [String: Any],
                       let detail = json[&quot;detail&quot;] as? String {
                        print(&quot;상세 메시지: \(detail)&quot;)
                    }
                }
            case .failure(let error):
                print(&quot;요청 실패: \(error.localizedDescription)&quot;)
            }
        }
    }
}</code></pre>
<p>작동부에서 오류가 가장 많이 나기 때문에 오류 확인 메시지는 자세하면 자세할 수록 좋다.</p>
<ul>
<li><code>case &quot;요청 실패&quot;</code>: <strong>서버 접근 불가</strong></li>
<li><code>case &quot;서버 오류&quot;</code>: <strong>요청이 서버에 도달했지만 올바르게 처리되지 못함.</strong> 
1번으로 돌아가 API 명세서와 내 코드가 올바르게 매칭되는지(http method 등) 확인해보자.</li>
<li><code>case &quot;JSON 디코딩 실패&quot;</code>: <strong>JSON 파싱 실패.</strong>
2번으로 돌아가 Response 모델링이 적절한지 다시 확인하자. 이것만 해결하면 통신 성공!</li>
</ul>
<p>💥 실제로 구현하며 URL의 쿼리 파라미터 중복 문제로 서버 오류가, <code>GenericResponse</code>를 만들어두고 사용하지 않고 <code>GetFavLocationResponse</code>로만 JSON 파싱해서 디코딩 실패 오류가 났었다.
🌟 오류 메시지를 통해 어느 단계에서 오류가 발생하는지 깨닫고, API 명세서와 코드를 비교해보니 문제를 해결할 수 있었다.</p>
<hr>
<p>참고
<a href="https://mini-min-dev.tistory.com/110">https://mini-min-dev.tistory.com/110</a>
<a href="https://www.youtube.com/watch?v=OKbJLGhUWZI&amp;t=612s">https://www.youtube.com/watch?v=OKbJLGhUWZI&amp;t=612s</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DFS & BFS]]></title>
            <link>https://velog.io/@juhee-dev/DFS-BFS</link>
            <guid>https://velog.io/@juhee-dev/DFS-BFS</guid>
            <pubDate>Thu, 25 Jul 2024 07:35:27 GMT</pubDate>
            <description><![CDATA[<p>유튜브 <a href="https://www.youtube.com/watch?v=7C9RgOcvkvo&amp;list=PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC&amp;index=3">참고</a></p>
<h1 id="스택">스택</h1>
<ul>
<li>선입후출: 박스 쌓기</li>
<li>리스트 선언 후 <code>append()</code>와 <code>pop()</code>으로 구현</li>
<li>컴퓨터 메모리 내부에 쌓이므로 스택을 사용해야할 때 구현상 재귀함수를 이용하는 경우가 많음</li>
</ul>
<h1 id="큐">큐</h1>
<ul>
<li>선입선출: 줄 서있는 사람들</li>
<li><code>deque</code> 라이브러리 사용 필요 -&gt; <code>append()</code>와 <code>popleft()</code>로 구현</li>
<li>리스트로도 사용할 수 있지만, pop 시 요소를 앞 인덱스로 하나씩 당겨오는 작업이 생겨 비효율적이며 O(n)만큼의 시간복잡도 발생 -&gt; 덱을 이용하자.<pre><code class="language-py">from collections import deque</code></pre>
</li>
</ul>
<h1 id="재귀함수">재귀함수</h1>
<h2 id="예시-문제-gcd---유클리드-호제법">예시 문제: GCD - 유클리드 호제법</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/4452c52b-ed66-481d-a0a2-904f65a4703c/image.png" alt=""></p>
<h3 id="코드">코드</h3>
<pre><code class="language-py">def gcd (a, b):
  if a % b== 0:
    return b
  else:
    return gcd(b, a % b)

print(gcd(192, 162))</code></pre>
<p>함수의 동작 구조 상 a와 b의 크기에 따른 순서는 상관이 없다.</p>
<h1 id="dfs-깊이-우선-탐색">DFS: 깊이 우선 탐색</h1>
<ul>
<li>깊은 부분 우선적으로 탐색</li>
<li>스택 자료구조 or 재귀함수 이용<h3 id="코드-1">코드</h3>
<pre><code class="language-py"># DFS 메서드 정의
def dfs(graph, v, visited):
# 현재 노드를 방문 처리
visited[v] = True
print(v, end=&#39; &#39;)
# 현재 노드와 연결된 다른 노드를 재귀적으로 방문
for i in graph[v]:
  if not visited[i]:
    dfs (graph, i, visited)
</code></pre>
</li>
</ul>
<h1 id="각-노드가-연결된-정보를-표현-2차원-리스트">각 노드가 연결된 정보를 표현 (2차원 리스트)</h1>
<p>graph = [
  [],
  [2, 3, 8],
  [1, 7], 
  [1, 4, 5], 
  [3, 5],
  [3, 4],
  [7],
  [2, 6, 8],
  [1, 7]
]</p>
<h1 id="각-노드가-방문된-정보를-표현-1차원-리스트">각 노드가 방문된 정보를 표현 (1차원 리스트)</h1>
<p>visited = [False] * 9</p>
<h1 id="정의된-dfs-함수-호출">정의된 DFS 함수 호출</h1>
<p>dfs(graph, 1, visited)</p>
<pre><code>
# BFS: 너비 우선 탐색
- 가까운 노드부터 탐색 -&gt; 간선의 비용이 모두 같을 때 최단거리 찾기 위해 사용
- 큐 자료구조 이용

### 코드
```py
from collections import deque

# BFS 메서드 정의
def bfs(graph, start, visited):
  # 큐(Queue) 구현을 위해 deque 라이브러리 사용
  queue = deque([start])
  # 현재 노드를 방문 처리
  visited[start] = True
  # 큐가 빌 때까지 반복
  while queue:
    # 큐에서 하나의 원소를 뽑아 출력하기
    v = queue.popleft()
    print(v, end=&#39;&quot;)
    # 아직 방문하지 않은 인접한 원소들을 큐에 삽입
    for i in graph[v]:
      if not visited[i]:
        queue.append (i)
        visited[i] = True

# 각 노드가 연결된 정보를 표현 (2차원 리스트)
graph = [
  [],
  [2, 3, 8],
  [1, 7], 
  [1, 4, 5], 
  [3, 5],
  [3, 4],
  [7],
  [2, 6, 8],
  [1, 7]
]

# 각 노드가 방문된 정보를 표현 (1차원 리스트)
visited = [False] * 9

# 정의된 BFS 함수 호출
bfs(graph, 1, visited)</code></pre><hr>
<h2 id="예시-문제-1-음료수-얼려-먹기">예시 문제 1: 음료수 얼려 먹기</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/d2ccc8a5-7ff7-4821-b7bb-647b5f8bf4c6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/49b6d138-6488-4e35-bdfd-629fd4e618c6/image.png" alt=""></p>
<h3 id="코드-2">코드</h3>
<pre><code class="language-py"># DFS로 특정 노드를 방문하고 연결된 모든 노드들도 방문
def dfs(x, y):
  # 주어진 범위를 벗어나는 경우에는 즉시 종료
  if x &lt;= -1 or x &gt;= N or y &lt;= -1 or y &gt;= M:
    return False

  # 현재 노드를 아직 방문하지 않았다면
  if graph[x][y] == 0:
    # 해당 노드 방문 처리
    graph[x][y] = 1
    # 상하좌우의 위치들도 모두 재귀적으로 호출
    dfs(x - 1, y)
    dfs(x, y - 1)
    dfs(x + 1, y)
    dfs(x, y + 1)
    return True
  return False

N, M = map(int, input().split())

graph = []
for _ in range(N):
  graph.append(list(map(int, input())))

result = 0
for n in range(N):
  for m in range(M):
    # 현재 위치에서 dfs 수행
    if dfs(n, m) == True:
      result += 1

print(result)</code></pre>
<h2 id="예시-문제-2-미로-탈출">예시 문제 2: 미로 탈출</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/a3c85b11-c177-4667-b1c9-a335437062cd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/7e005512-fbd6-4262-bd1a-a9c484fb110b/image.png" alt=""></p>
<h3 id="코드-3">코드</h3>
<pre><code class="language-py">from collections import deque

def bfs(x, y):
  # 큐 구현을 위해 deque 라이브러리 사용
  queue = deque()
  queue.append((x, y))

  # 큐가 빌 때까지 반복하기
  while queue:
    x, y = queue.popleft()
    # 현재 위치에서 4가지 방향으로서의 위치 확인
    for i in range(4):
      nx = x + dx[i]
      ny = y + dy[i]
      # 미로 찾기 공간을 벗어난 경우 무시
      if nx &lt; 0 or nx &gt;= N or ny &lt; 0 or ny &gt;= M:
        continue
      # 벽인 경우 무시
      if graph[nx][ny] == 0:
        continue
      # 해당 노드를 처음 방문하는 경우에만 최단 거리 기록
      if graph[nx][ny] == 1:
        graph[nx][ny] = graph[x][y] + 1
        queue.append((nx, ny))
  # 가장 오른쪽 아래까지의 최단 거리 반환
  return graph[N - 1][M - 1]


N, M = map(int, input().split())

graph = []
for _ in range(N):
  graph.append(list(map(int, input())))

# 이동할 네 가지 방향 정의 - 상, 하, 좌, 우
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

print(bfs(0, 0))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[그리디 & 구현 (시뮬레이션)]]></title>
            <link>https://velog.io/@juhee-dev/2.-%EA%B7%B8%EB%A6%AC%EB%94%94-%EA%B5%AC%ED%98%84-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@juhee-dev/2.-%EA%B7%B8%EB%A6%AC%EB%94%94-%EA%B5%AC%ED%98%84-%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98</guid>
            <pubDate>Wed, 24 Jul 2024 11:57:04 GMT</pubDate>
            <description><![CDATA[<p>참조 <a href="https://www.youtube.com/watch?v=2zjoKjt97vQ&amp;list=PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC&amp;index=2">유튜브</a></p>
<h1 id="그리디-알고리즘">그리디 알고리즘</h1>
<h2 id="개념">개념</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/6390c50c-85e4-4182-8696-536790e3311c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/4ca33971-4606-432f-aa11-ce412c5a576e/image.png" alt=""></p>
<p>➡️ 그리디 알고리즘으로 나온 결과는 19. 그러나 최대값은 21</p>
<p>이처럼 최적의 해를 보장할 수 없을 때가 많음
But,
코딩 테스트에서의 대부분의 그리디 문제는 <em>탐욕법으로 얻은 해가 최적의 해가 되는 경우</em>에 한해 문제 출제됨</p>
<h2 id="예시-문제-1-거스름-돈">예시 문제 1: 거스름 돈</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/2b36a9c4-075f-4883-b1b1-ad009a1da7ad/image.png" alt=""></p>
<h3 id="해결-아이디어">해결 아이디어</h3>
<ul>
<li>최적의 해를 빠르게 구하기 위해서 가장 큰 화폐 단위부터 돈을 거슬러 주기<ul>
<li>N원을 거슬러 줘야 할 때, 가장 먼저 500원으로 거슬러 줄 수 있을 만큼 거슬러 주기</li>
<li>이후에 100원, 50원, 10원짜리 동전을 차례대로 거슬러 줄 수 있을 만큼 거슬러 주기</li>
</ul>
</li>
</ul>
<h3 id="정당성-분석">정당성 분석</h3>
<ul>
<li>가장 큰 화폐 단위부터 돈을 거슬러 주는 것이 최적의 해를 보장하는 이유는?<ul>
<li>가지고 있는 동전 중에서 <em>큰 단위가 항상 작은 단위의 배수이므로</em> 작은 단위의 동전들을 종합해 다른 해가 나올 수 없기 때문  </li>
</ul>
</li>
</ul>
<ul>
<li>만약에 800원을 거슬러 주어야 하는데 화폐 단위가 500원, 400원, 100원이라면?<ul>
<li>그리디 알고리즘으로는 500x1 + 100x3, 즉 4개를 거슬러줘야 함</li>
<li>그러나 최적의 방법은 400x2, 즉 2개</li>
</ul>
</li>
</ul>
<ul>
<li>그리디 알고리즘 문제에서는 이처럼 문제 풀이를 위한 최소한의 아이디어를 떠올리고 이것이 정당한지 검토할 수 있어야 함</li>
</ul>
<h3 id="답안-예시">답안 예시</h3>
<pre><code class="language-py">n = 1260
count = 0

#큰 단위의 화폐부터 차례대로 확인하기
array = [500, 100, 50, 10J

for coin in array:
    count += n // coin # 해당 화폐로 거슬러 줄 수 있는 동전의 개수 세기
    n %= coin

print(count)</code></pre>
<ul>
<li>화폐의 종류가 K개 할 때, 소스코드의 시간 복잡도는 <strong>O(K)</strong></li>
</ul>
<ul>
<li>이 알고리즘의 시간 복잡도는 거슬러줘야 하는 금액과는 무관하며, 동전의 총 종류에만 영향 받음</li>
</ul>
<h2 id="예시-문제-2-1이-될-때까지">예시 문제 2: 1이 될 때까지</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/4a0e3ae2-7068-440f-b31a-737f56150252/image.png" alt=""></p>
<h3 id="해결-아이디어-1">해결 아이디어</h3>
<ul>
<li>주어진 N에 대하여 최대한 많이 나누기를 수행<ul>
<li>N의 값을 줄일 때 2 이상의 수로 나누는 작업이 1을 빼는 작업보다 수를 훨씬 많이 줄일 수 있음</li>
</ul>
</li>
</ul>
<ul>
<li>우선 N이 K로 나누어 떨어지면 나누고, 그렇지 않으면 1씩 빼며 나누어 떨어지는 수로 만들어 나누기</li>
</ul>
<h3 id="답안-예시-1">답안 예시</h3>
<pre><code class="language-py"># N, K을 공백을 기준으로 구분하여 입력 받기
n, k = map(int, input().split())

result = 0

while True:
    #N이 K로 나누어 떨어지는 수가 될 때까지 빼기
    target = (n // k) * k # N이 K로 나누어 떨어지지 않을 경우 N과 가장 가까운, K로 나누어 떨어지는 수 구하기
    result += (n - target) # 1을 빼는 연산 수행의 횟수 구하기
    n = target

    # N이 K보다 작을 때 (더 이상 나눌 수 없을 때) 반복문 탈출
    if n &lt; k:
        break

    # K로 나누기
    result += 1
    n //= k

# 마지막으로 남은 수에 대하여 1씩 빼기
result += (n - 1)
print (result)</code></pre>
<ul>
<li>반복문 안에서 조건문을 사용해 뺄지, 나눌지 정할 수도 있음. O(n)</li>
</ul>
<ul>
<li>그러나 위 예시코드는 반복문을 거칠 떄마다 나눗셈 수행하므로 기하급수적으로 빨라짐. <code>O(logn)</code></li>
</ul>
<hr>
<h1 id="구현-알고리즘">구현 알고리즘</h1>
<h2 id="예시-문제-왕실의-나이트">예시 문제: 왕실의 나이트</h2>
<p>그리디 aka 구현 aka 시뮬레이션 aka 완전 탐색 문제
<img src="https://velog.velcdn.com/images/juhee-dev/post/8079fb5e-a1e8-4e41-9bf3-9317d098b27a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/c53f1a56-bb85-4401-9094-6097c8dfd9a0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/89965a73-555a-4029-a3c5-d93e3299a255/image.png" alt=""></p>
<h3 id="답안-예시-2">답안 예시</h3>
<pre><code class="language-python"># 현재 나이트의 위치 입력받기
pos = input()
row = int(pos[1])
col = int(ord(pos[0]) - ord(&#39;a&#39;)) + 1

# 나이트가 이동할 수 있는 8가지 방향 정의
steps = [(-2, -1), (-2, 1), (2, -1), (2, 1), (1, -2), (1, 2), (-1, 2), (-1, -2)]

# 8가지 방향에 대하여 각 위치로 이동이 가능한지 확인
result = 0
for step in steps:
  # 이동하고자 하는 위치 확인
  next_row = row + step[0]
  next_col = col + step[1]
  # 해당 위치로 이동이 가능하다면 카운트 증가
  if next_row &lt; 1 or next_row &gt; 8 or next_col &lt; 1 or next_col &gt; 8:
    continue
  result += 1 

print(result)</code></pre>
<hr>
<p>좌표를 움직이는 시뮬레이션 문제의 경우 steps와 같이 한 번 움직일 시 변경되는 좌표를 튜플로 만들어놓고, for문을 돌리는 방식으로 풀어가면 될 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩 테스트 출제 경향 분석 및 파이썬 문법 부수기]]></title>
            <link>https://velog.io/@juhee-dev/1.-%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%B6%9C%EC%A0%9C-%EA%B2%BD%ED%96%A5-%EB%B6%84%EC%84%9D-%EB%B0%8F-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%AC%B8%EB%B2%95-%EB%B6%80%EC%88%98%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/1.-%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%B6%9C%EC%A0%9C-%EA%B2%BD%ED%96%A5-%EB%B6%84%EC%84%9D-%EB%B0%8F-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%AC%B8%EB%B2%95-%EB%B6%80%EC%88%98%EA%B8%B0</guid>
            <pubDate>Mon, 24 Jun 2024 16:30:04 GMT</pubDate>
            <description><![CDATA[<p>본 게시글은 작성자가 파이썬으로 알고리즘 기초부터 공부하기 위해 <a href="https://youtube.com/playlist?list=PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC&amp;si=bTGzHLnV_jSDvHmo">나동빈님 유튜브 강의</a>를 보며 선택적으로 정리한 글임.</p>
<h1 id="알고리즘-성능-평가">알고리즘 성능 평가</h1>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/95fdab05-f782-4ed7-925f-f360b4298613/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/fd54690d-73ca-46e8-ad95-5e4df733eb2a/image.png" alt=""></p>
<hr>
<h1 id="수-자료형">수 자료형</h1>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/0043edd2-acfe-49cc-8e67-c2a55e350e05/image.png" alt=""></p>
<ul>
<li><p>해당 방법은 실수형으로 표현됨</p>
</li>
<li><p>정수형으로 바꾸려면 <code>a = int(1e9)</code></p>
<h2 id="실수형-반올림">실수형 반올림</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/3d4e35e3-8aa3-485c-bee7-026180c30cd6/image.png" alt=""></p>
</li>
</ul>
<hr>
<h1 id="리스트-자료형">리스트 자료형</h1>
<p>리스트 = 배열 = 테이블</p>
<pre><code class="language-py"># 크기가 N이고, 모든 값이 0인 1차원 리스트 초기화
n = 10
a = [0] * n
print(a)

a= [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 뒤에서 세 번째 원소 출력: 인덱싱
print(a[-3])

# 두 번째 원소부터 네 번째 원소까지: 슬라이싱(:) 사용
print(a[1 : 4]) # 결과: [2, 3, 4]
</code></pre>
<h2 id="리스트-컴프리헨션">리스트 컴프리헨션</h2>
<pre><code class="language-py"># 0부터 9까지의 수를 포함하는 리스트
array = [i for i in range(10)]
print(array)
# 결과: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 0부터 19까지의 수 중에서 홀수만 포함하는 리스트
array = [i for i in range(20) if i % 2 == 1]

# 1부터 9까지의 수들의 제곱 값을 포함하는 리스트
array = [i * i for i in range(1, 10)]

# N X M 크기의 2차원 리스트 초기화
n = 4
m = 3
array = [[0] * m for _ in range(n)] # 길이가 m인 리스트를 n개 만든다.
print(array)
# 결과: [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]</code></pre>
<blockquote>
<p>💡 언더바는 언제 사용하나요?<img src="https://velog.velcdn.com/images/juhee-dev/post/3e0a9bb0-a116-4d57-b588-dfd2418913d2/image.png" alt=""></p>
</blockquote>
<h2 id="리스트-관련-기타-메서드">리스트 관련 기타 메서드</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/17de5844-0550-4b08-a2f5-17d7f4ae42fb/image.png" alt=""></p>
<pre><code class="language-py">a = [1, 2, 3, 4, 5, 5, 5]
remove_set = {3, 5} # 집합 자료형 (집합 자료형은 추후에 다시 다릅니다.)

# remove_list에 포함되지 않은 값만을 저장
result = [i for i in a if i not in remove_set]
print(result)
# 결과: [1, 2, 4]</code></pre>
<hr>
<h1 id="문자열-자료형">문자열 자료형</h1>
<ul>
<li>리스트와같이 인덱싱, 슬라이싱 가능 but, <strong>변경은 불가능(immutable)</strong></li>
</ul>
<hr>
<h1 id="튜플-자료형">튜플 자료형</h1>
<ul>
<li>리스트와 비슷 but, <strong>변경은 불가능</strong></li>
<li>리스트에 비해 상대적으로 <strong>공간 효율적</strong><pre><code class="language-py">array = [(&#39;홍길동&#39;, 50), (&#39;이순신&#39;, 32), (&#39;아무개&#39;, 74)]</code></pre>
</li>
</ul>
<h2 id="튜플을-사용하면-좋은-경우">튜플을 사용하면 좋은 경우</h2>
<ul>
<li><p>서로 다른 성질의 데이터를 묶어서 관리해야 할 때</p>
<ul>
<li>최단 경로 알고리즘에서는 (비용, 노드 번호)의 형태로 튜플 자료형을 자주 사용합니다.</li>
</ul>
</li>
<li><p>데이터의 나열을 해싱(Hashing)의 키 값으로 사용해야 할 때</p>
<ul>
<li>튜플은 변경이 불가능하므로 리스트와 다르게 키 값으로 사용될 수 있습니다.</li>
</ul>
</li>
<li><p>리스트보다 메모리를 효율적으로 사용해야 할 때</p>
</li>
</ul>
<hr>
<h1 id="사전-자료형">사전 자료형</h1>
<ul>
<li><p>키(Key)와 값(Value)의 쌍</p>
</li>
<li><p>순서가 없음</p>
</li>
<li><p>원하는 &#39;변경 불가능한(Immutable) 자료형&#39;을 키로 사용 가능</p>
</li>
<li><p>파이썬의 사전 자료형은 해시 테이블(Hash Table)을 이용하므로 데이터의 조회 및 수정에 있어서 <strong>0(1)</strong>의 시간에 처리</p>
<pre><code class="language-py">data = dict()
data[&#39;사과&#39;] = &#39;Apple&#39; # 키 : 값
data[&#39;바나나&#39;] = &#39;Banana&#39;
datal&#39;코코넛&#39;] = &#39;Coconut&#39;
</code></pre>
</li>
</ul>
<h1 id="키-데이터만-담은-리스트">키 데이터만 담은 리스트</h1>
<p>key_list = data.keys()</p>
<h1 id="값-데이터만-담은-리스트">값 데이터만 담은 리스트</h1>
<p>value_list = data.values()</p>
<h1 id="각-키에-따른-값을-하나씩-출력">각 키에 따른 값을 하나씩 출력</h1>
<p>for key in key_list:
print(data[key])</p>
<pre><code>
---

# 집합 자료형
- **중복을 허용하지 않음**

- 순서가 없음
- **리스트 혹은 문자열**을 이용해서 초기화 가능
  - `set()` 함수 이용
- 혹은 **중괄호** 안에 각 원소를 콤마(,)를 기준으로 구분하여 삽입함으로써 초기화 가능
- 데이터의 조회 및 수정에 있어서 **O(1)**의 시간에 처리할 수 있습니다.
```py
# 집합 자료형 초기화 방법 1
data = set([1, 1, 2, 3])

# 집합 자료형 초기화 방법 2
data = {1, 1, 2, 3}

# 새로운 원소 추가
data.add(4)

# 새로운 원소 여러 개 추가
data.update([5, 6])

# 특정한 값을 갖는 원소 삭제
data.remove(3)</code></pre><pre><code class="language-py">a = set([1, 2, 3, 4, 5])
b = set([3, 4, 5, 6, 7])

# 합집합
print(a l b)

# 교집합
print(a &amp; b)

# 차집합
print(a - b)</code></pre>
<h2 id="사전-자료형과-집합-자료형의-특징">사전 자료형과 집합 자료형의 특징</h2>
<ul>
<li><p>리스트나 튜플은 순서가 있기 때문에 인덱싱을 통해 자료형의 값을 얻을 수 있음</p>
</li>
<li><p>사전 자료형과 집합 자료형은 <strong>순서가 없기 때문에</strong> 인덱싱으로 값을 얻을 수 없음</p>
<ul>
<li>사전의 키(Key) 혹은 집합의 원소(Element)를 이용해 <strong>O(1)</strong>의 시간 복잡도로 조회</li>
</ul>
</li>
</ul>
<hr>
<h1 id="기본-입출력">기본 입출력</h1>
<h2 id="자주-사용되는-입력-방법">자주 사용되는 입력 방법</h2>
<ul>
<li><p>input() 함수는 <em>한 줄의 문자열</em>을 입력 받는 함수</p>
</li>
<li><p>map() 함수는 <em>리스트의 모든 원소에 각각 특정한 함수를 적용할 때</em> 사용</p>
<ul>
<li><p>ex) 공백을 기준으로 구분된 데이터를 입력 받을 때는 다음과 같이 사용
<code>list(map(int, input().split()))</code></p>
</li>
<li><p>ex) 공백을 기준으로 구분된 데이터의 개수가 많지 않다면, 단순히 다음과 같이 사용
<code>a, b, c = map(int, input().split())</code></p>
</li>
</ul>
</li>
</ul>
<h2 id="빠르게-입력-받기">빠르게 입력 받기</h2>
<ul>
<li><p>사용자로부터 입력을 최대한 빠르게 받아야 하는 경우</p>
</li>
<li><p>파이썬의 경우 Sys 라이브러리에 정의되어 있는 <code>sys.stdin.readline()</code> 메서드를 이용</p>
<ul>
<li>단, 입력 후 엔터(Enter)가 줄 바꿈 기호로 입력되므로 <code>rstrip()</code> 메서드를 함께 사용: <code>data = sys.stdin.readline().rstrip()</code></li>
</ul>
</li>
</ul>
<h2 id="자주-사용되는-출력-방법">자주 사용되는 출력 방법</h2>
<ul>
<li><p>자동 줄바꿈을 원하지 않는 경우
<code>print(1, end=&quot; &quot;)</code></p>
</li>
<li><p>정수형을 문자열과 함께 출력하는 경우
<code>print(정답은 + str(answer) + &quot;입니다.&quot;</code>
or
<code>print(f&quot;정답은 {answer}입니다.&quot;)</code> (f-string)</p>
</li>
</ul>
<hr>
<h1 id="조건문과-반복문">조건문과 반복문</h1>
<h2 id="조건문">조건문</h2>
<pre><code class="language-py">if score &gt;= 90:
    print(&quot;학점: A&quot;)
elif score &gt;= 80:
    pass # 나중에 작성할 소스코드
else:
    print(&quot;학점: F&quot;)</code></pre>
<blockquote>
<p>💡 <code>in</code> 연산자와 <code>not in</code> 연산자
다수의 데이터를 담는 자료형을 위해 사용한다.
리스트, 튜플, 문자열, 딕셔너리 모두에서 사용 가능</p>
</blockquote>
<blockquote>
<p>💡 <code>pass</code> 키워드
형태만 만들어놓고 아무것도 처리하고 싶지 않을 때</p>
</blockquote>
<h3 id="조건문-편의성-제공">조건문 편의성 제공</h3>
<ul>
<li><p>조건문 간소화: if ~ else문을 한줄에 작성</p>
<pre><code class="language-py">result = &quot;Success&quot; if score ›= 80 else &quot;Fail&quot;</code></pre>
</li>
<li><p>다른 언어와는 다르게 <em>수학적 부등식</em> 사용 가능</p>
<pre><code class="language-py">if 0 &lt; x &lt; 20:
    print(&quot;×는 0 이상 20 미만의 수입니다.&quot;)</code></pre>
</li>
</ul>
<h2 id="반복문">반복문</h2>
<ul>
<li><p><code>while</code>문</p>
<pre><code class="language-py">while i &lt;= 9:
    result += i
    i += 1</code></pre>
</li>
<li><p><code>for</code>문</p>
<pre><code class="language-py">array = [9, 8, 7, 6, 5]
for x in array:
    print(x)

# 연속적 값 순회 시 range(시작 값, 끝 값 + 1)
for i in range(1, 10):
    result += i</code></pre>
<blockquote>
<p>💡 <code>for i in range(1, 11)</code>와 <code>for i in range(10)</code>의 반복 횟수는 동일</p>
</blockquote>
</li>
</ul>
<hr>
<h1 id="함수">함수</h1>
<pre><code class="language-py">def add(a, b):
    print(&#39;함수의 결과: &#39;, a + b)

add (3, 7)</code></pre>
<pre><code class="language-py"># 파라미터의 변수 직접 지정 시 매개변수의 순서 달라도 ok
def add (a, b):
  print(&#39;함수의 결과:&#39;, a + b)

add(b = 3, a = 7)</code></pre>
<h2 id="global-키워드">global 키워드</h2>
<p>함수 내에서 <code>global</code> 키워드로 변수를 지정하면 지역변수를 만들지 않고, 함수 바깥에 선언된 변수를 바로 참조</p>
<pre><code class="language-py">a = 0

def func():
    global a
    a += 1

for i in range(10):
    func()

print(a)

# 결과: 10</code></pre>
<blockquote>
<p>💡 값을 변경하지 않고 단순 참조만 하는 경우, global 키워드 없이 참조 가능</p>
</blockquote>
<pre><code class="language-py">a = 10
def func():
    print(a)
func()
# 결과: 10</code></pre>
<blockquote>
<p>💡 전역변수로 선언된 리스트 객체의 내부 메소드도 global 키워드 없이 사용 가능</p>
</blockquote>
<pre><code class="language-py">array = [1, 2, 3, 4, 5]
def func():
    array.append(6)
print(array)
func()
# 결과: [1, 2, 3, 4, 5, 6]</code></pre>
<h2 id="여러-개의-반환-값">여러 개의 반환 값</h2>
<p>파이썬 함수는 여러 개의 반환 값을 가질 수 있음</p>
<pre><code class="language-py">def operator(a, b):
    add_var = a + b
    subtract_var = a - b
    multiply_var = a * b
    divide_var = a / b
    return add_var, subtract_var, multiply_var, divide_var

a, b, c, d = operator (7, 3)
print(a, b, c, d)</code></pre>
<h2 id="람다-표현식">람다 표현식</h2>
<p>이름 없는 함수: 함수를 간단하게 작성 가능
사용 방식: <code>print((lambda 지역변수 : return값)(매개변수))</code></p>
<pre><code class="language-py"># 일반적인 add() 메서드 사용
def add(a, b):
    return a + b

print(add(3, 7))


# 람다 표현식으로 구현한 add() 메서드
print((lambda a, b: a + b) (3, 7))</code></pre>
<p>내장 함수에서 자주 사용됨.
ex) <code>sorted</code> 함수에서 <code>key</code> 속성은 정렬 기준. 함수로 정렬 기준 선언 가능</p>
<pre><code class="language-py">array = [(&#39;홍길동&#39;, 50), (&#39;이순신&#39;, 32), (&#39;아무개&#39;, 74)]

def my_key (x) :
    return x[1]

print(sorted(array, key=my_key))
# x 배열의 두번째 요소, 즉 나이를 기준으로 정렬하겠다. 위와 동일한 작업
print(sorted(array, key=lambda x: x[1]))</code></pre>
<p>여러 개의 리스트에 적용</p>
<pre><code class="language-py">list1 = [1, 2, 3, 4, 5]
list2 = [6, 7, 8, 9, 10]

# a와 b에 대해 a+b를 반환하는 함수 정의(lambda), 이것을 list1과 list2에 적용(map) - 원소를 확인하며 각 위치에 맞는 결과가 담김 
result = map(lambda a, b: a + b, listl, list2)

print(list(result))</code></pre>
<hr>
<h1 id="실전에서-유용한-표준-라이브러리">실전에서 유용한 표준 라이브러리</h1>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/460218e9-d71b-4ea3-96cb-4436be9b340c/image.png" alt=""></p>
<h2 id="자주-사용되는-내장-함수">자주 사용되는 내장 함수</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/a7b1cae3-bce9-4d81-852a-a0be98258718/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/abea2304-b0b1-444b-b992-f2295d3bb1df/image.png" alt=""></p>
<h2 id="순열과-조합">순열과 조합</h2>
<p><code>itertools</code>의 라이브러리 사용</p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/252f3800-925c-4d14-8d33-d8d64c84d16b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/aa184269-3fc8-4781-babb-f16c336260a5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/978b44b8-c5d9-4c0b-b059-c470d7160a9c/image.png" alt=""></p>
<h2 id="그-외">그 외</h2>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/35a43cb9-0d5f-49fd-9ccc-e49103459bee/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/cf274b75-5517-469f-ac59-5364788c5fbb/image.png" alt=""></p>
<hr>
<h1 id="느낀-점">느낀 점</h1>
<p>쇼킹하다.
내가 C언어로 순열, 조합 구현하느라 얼마나 힘들었는데 🚬🫠
솔직히 강의 들으면서도 &#39;그냥 익숙한 C언어로 알고리즘 풀까...&#39; 싶었지만 1강 듣고 나니 그냥 파이썬에 충성해야겠다는 생각뿐</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬으로 웹크롤링한 데이터 MySql에 저장하기]]></title>
            <link>https://velog.io/@juhee-dev/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9C%BC%EB%A1%9C-%EC%9B%B9%ED%81%AC%EB%A1%A4%EB%A7%81%ED%95%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-MySql%EC%97%90-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@juhee-dev/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9C%BC%EB%A1%9C-%EC%9B%B9%ED%81%AC%EB%A1%A4%EB%A7%81%ED%95%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-MySql%EC%97%90-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 21 Jun 2024 03:16:12 GMT</pubDate>
            <description><![CDATA[<p>목표: 웹사이트 크롤링으로 저장한 csv 데이터를 MySql에 insert하기</p>
<h1 id="csv-파일-불러오기">csv 파일 불러오기</h1>
<pre><code class="language-python">import pandas as pd

df = pd.read_csv(&quot;songs.csv&quot;, header=None, nrows=1000) # csv 저장할때 header 없이 저장했으므로 읽어들일 때에도 명시 필요
df = df.where(pd.notnull(df), None)</code></pre>
<ul>
<li>엑셀 파일 불러오는 경우 <code>pd.read_excel()</code> 사용</li>
<li>파일 내에 column에 대한 헤더가 존재한다면 <code>header=None</code> 삭제
💡 해당 코드 없으면 csv 파일의 첫번째 row를 자동으로 제거해 불러옴</li>
</ul>
<hr>
<h1 id="mysql-연동">MySql 연동</h1>
<pre><code class="language-python">import pymysql

# DB 정보
host = &quot;localhost&quot;
user = &quot;root&quot;
password = &quot;0000&quot;
database = &quot;songmmelier&quot;

# DB 연결
conn = pymysql.connect(host=host, user=user, password=password, db=database)
curs = conn.cursor(pymysql.cursors.DictCursor)</code></pre>
<p><img src="https://velog.velcdn.com/images/juhee-dev/post/27b319a9-2b6e-459b-be5d-dce4ce942900/image.png" alt=""></p>
<hr>
<h1 id="sql문-작성">sql문 작성</h1>
<pre><code class="language-python"># DB delete: &#39;rank&#39; 컬럼에 값이 있는 모든 데이터 삭제
sql_delete = &quot;&quot;&quot;DELETE FROM song WHERE `rank` IS NOT NULL;&quot;&quot;&quot;
curs.execute(sql_delete)

# DB insert
sql = &quot;&quot;&quot;insert into song (`rank`, tj_number, title, artist) 
VALUES(%s, %s, %s, %s);&quot;&quot;&quot; # 주의: int type이어도 파이썬에서 쿼리 짤 때에는 문자열 자료형(%s)으로 지정

for idx in range(len(df)):
    curs.execute(sql, tuple(df.values[idx]))

conn.commit()

curs.close()
conn.close()</code></pre>
<ul>
<li>코드를 실행할 때마다 song 테이블의 탑100 노래 모두 삭제, 다시 데이터 삽입</li>
<li>mysql에 &#39;rank&#39; 함수가 존재하므로 <code>rank</code> column 이름 작성 시 따옴표로 묶음 필요</li>
<li><code>curs.execute(sql문)</code>: sql문 실행</li>
<li><code>conn.commit()</code>: 커밋</li>
<li><code>curs.close()</code>, <code>conn.close()</code>: 커서 닫기 및 연결 종료</li>
</ul>
<hr>
<h1 id="최종-코드">최종 코드</h1>
<pre><code class="language-python">import pymysql.cursors
import requests
from bs4 import BeautifulSoup as bs
import pandas as pd
from concurrent.futures import ThreadPoolExecutor
import csv
import pymysql

# 데이터 크롤링
def fetch_data(url):
    response = requests.get(url)
    # 한글 데이터 안깨지게 가져오려면 인코딩 필요
    response.encoding = &#39;utf-8&#39;
    html = response.text
    soup = bs(html, &#39;html.parser&#39;)

    # .board_type1&gt;tbody&gt;tr:nth-child(n+2) 선택자를 사용하여 첫 번째 tr을 제외
    items = soup.select(&quot;.board_type1&gt;tbody&gt;tr:nth-child(n+2)&quot;)
    data = []
    for item in items:
        rank = item.select_one(&quot;td:nth-child(1)&quot;).text
        number = item.select_one(&quot;td:nth-child(2)&quot;).text
        title = item.select_one(&quot;td:nth-child(3)&quot;).text
        artist = item.select_one(&quot;td:nth-child(4)&quot;).text
        data.append((rank, number, title, artist))
    return data

# 수집할 URL 리스트 생성
urls = [&quot;https://www.tjmedia.com/tjsong/song_monthPopular.asp&quot; for _ in range(10)]  # URL을 10번만 호출하도록 수정

# 동시 요청 수행
data = []
with ThreadPoolExecutor(max_workers=10) as executor:
    results = executor.map(fetch_data, urls)

    for result in results:
        data.extend(result)

# 동시 요청으로 인한 데이터 중복 제거
data = list(set(data))

# 데이터를 DataFrame으로 변환
df = pd.DataFrame(data, columns=[&quot;Rank&quot;, &quot;Number&quot;, &quot;Title&quot;, &quot;Artist&quot;])

# DataFrame을 CSV 파일로 저장
df.to_csv(&quot;songs.csv&quot;, header=False, index=False, quoting=csv.QUOTE_ALL)

print(&quot;Data saved successfully to songs.csv&quot;)

# DB 정보
host = &quot;localhost&quot;
user = &quot;root&quot;
password = &quot;0308&quot;
database = &quot;songmmelier&quot;

# DB 연결
conn = pymysql.connect(host=host, user=user, password=password, db=database)
curs = conn.cursor(pymysql.cursors.DictCursor)

# DB delete: &#39;rank&#39; 컬럼에 값이 있는 모든 데이터 삭제
sql_delete = &quot;&quot;&quot;DELETE FROM song WHERE `rank` IS NOT NULL;&quot;&quot;&quot;
curs.execute(sql_delete)

# DB insert
sql = &quot;&quot;&quot;insert into song (`rank`, tj_number, title, artist) 
VALUES(%s, %s, %s, %s);&quot;&quot;&quot; # 주의: int type이어도 파이썬에서 쿼리 짤 때에는 문자열 자료형(%s)으로 지정

for idx in range(len(df)):
    curs.execute(sql, tuple(df.values[idx]))

conn.commit()

curs.close()
conn.close()

print(&quot;Data saved successfully to mysql&quot;)</code></pre>
<ul>
<li>데이터를 수집하면서 이미 DataFrame이 생성되었으므로 따로 csv 파일을 읽어들이는 부분은 생략</li>
</ul>
<hr>
<h1 id="느낀점">느낀점</h1>
<ul>
<li><p>이전 포스팅에서 각 속성값이 쌍따옴표로 묶여 csv 파일로 저장되었기에 db 저장시 처리 방법을 고민했으나 실제로 db에 들어가는 값은 쌍따옴표 제거되어 들어감 👍</p>
</li>
<li><p>겁먹었었는데 나름 간단하게 코드를 완성했다.</p>
</li>
<li><p>비상비상...🚨 song 테이블에 기존 노래 데이터 + 노래방 탑100 데이터를 합치려고 했는데 지금 코드로는
<code>원래 순위권에 없던 노래를 song 테이블에 추가</code> ➡️ <code>이후 노래방 탑100 진입</code> ➡️ <code>새 레코드로 노래 데이터가 추가됨</code>
이런 문제가 있다. 탑100 노래 저장하는 테이블을 분리시켜야 할까?</p>
</li>
</ul>
<hr>
<p>참조: <a href="https://lifesteps.tistory.com/119">https://lifesteps.tistory.com/119</a></p>
]]></description>
        </item>
    </channel>
</rss>