<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>iOS 꼭꼭 씹어먹기</title>
        <link>https://velog.io/</link>
        <description>블로그 이사중 🚚 byukbyak.tistory.com</description>
        <lastBuildDate>Wed, 28 Jun 2023 12:25:52 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>iOS 꼭꼭 씹어먹기</title>
            <url>https://velog.velcdn.com/images/debby_/profile/5eb793ce-8621-4447-a519-77e6cfc52469/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. iOS 꼭꼭 씹어먹기. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/debby_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Sort 되긴 하는거야 ..? -> 됨!]]></title>
            <link>https://velog.io/@debby_/Sort-%EB%90%98%EA%B8%B4-%ED%95%98%EB%8A%94%EA%B1%B0%EC%95%BC-</link>
            <guid>https://velog.io/@debby_/Sort-%EB%90%98%EA%B8%B4-%ED%95%98%EB%8A%94%EA%B1%B0%EC%95%BC-</guid>
            <pubDate>Wed, 28 Jun 2023 12:25:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이전글 : <a href="https://velog.io/@debby_/SwiftData-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-2">SwiftData 알아보기 (2)</a></p>
</blockquote>
<p>위 글에서 <code>@Query</code> 에 대해 짚어보다가
<code>@Query는 정렬, 정렬, 필터링 및 애니메이션 변경을 구성하는 간단한 구문을 제공합니다.</code></p>
<pre><code class="language-swift">@Query (sort: \.created) private var cards: [Card]</code></pre>
<p>이 코드를 만났는데요!</p>
<p><code>sort: \.created</code></p>
<p>이 부분! 샘플 코드의 Card 구조체에는 created 라는 프로퍼티가 없고 creationDate은 존재했는데 뭘 기준으로 정렬하는건지 궁금해서 이것저것 찾아봤습니다!</p>
<p>그래서 <a href="https://developer.apple.com/documentation/swiftdata/query">Query</a> 공식문서를 탐방했고..?</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/8a842fe6-074f-456a-910e-3fc957a92aab/image.png" alt=""></p>
<p>&#39;음 sort는 [SortDescriptor&lt;Element&gt;] 형식이구나..&#39;</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/8b1c33de-61bd-4ca0-a603-1c46736d0bcd/image.png" alt=""></p>
<p>&#39;Compared.type에 의해 결정한다고?&#39; 그건 또 뭐지..? 하면서 타고타고 가다가</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/c5ccda44-f93f-44d5-8cf0-ea84ff04f372/image.png" alt=""></p>
<p>SortOrder까지 왔는데요.</p>
<p>  현재는 <code>forward</code>(오름차순)와 <code>reverse</code>(내림차순) 정도만 정의되어 있었고</p>
<p>  영상에서 소개됐던 <code>created</code>는 볼 수 없었습니다. 😅</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/ab8669fd-14bb-4f25-abe8-df0c6d42e11f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/debby_/post/89f8dac0-9127-4176-9efc-a24da8382d97/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/debby_/post/08523848-5b06-4f48-9c68-876b49c4a744/image.png" alt=""></p>
<p>  셋 다 코드를 쳐봐도 제대로 동작하지 않았고, 오류는 동일했습니다.</p>
<pre><code>  Cannot infer key path type from context; consider explicitly specifying a root type
  컨텍스트에서 키 경로 유형을 유추할 수 없으므로 루트 유형을 명시적으로 지정하는 것이 좋습니다.</code></pre><p>결과적으로 문제가 해결되진 않았지만 보라색, 하얀색을 보아하니 어느정도는 공식 문서에 있는대로 forward, reverse 만 있는 상태가 맞는 것 같고 created 는 추후에 업데이트 되지 않나 기대해봅니다. 오류를 어떻게 해결하면 좋은지 아직 몰라서 때문에 패스! SortDescriptor를 이용한 코드가 없었기 때문인지 아닌지도 아직 모르겠네요. 조금 더 사용해봐야겠습니다 ㅎㅎ 삽질 끝!</p>
<p><del>짧은 결론 : 아직 안되더라!</del></p>
<hr>
<h2 id="230702-수정--됩니다">23.07.02 수정 : 됩니다!</h2>
<pre><code class="language-swift">import Foundation
import SwiftData

@Model
class Note {
    @Attribute(.unique) var id: String
    var content: String
    var createdAt: Date

    @Relationship(inverse: \Tag.notes) var tags: [Tag]

    init(id: String, content: String) {
        self.id = id
        self.content = content
        self.createdAt = .now
    }
}</code></pre>
<p>라는 모델 객체를 선언하고</p>
<pre><code class="language-swift">struct NoteListView: View {

    @Environment(\.modelContext) private var context
    @Query(sort: \.createdAt , order: .reverse) var allNotes: [Note]

    var body: some View {
        // 생략
    }
}</code></pre>
<p>이렇게 Note 객체에서 Sort할 수 있는 기준인 Date 타입을 선택하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftData 알아보기 (2) ]]></title>
            <link>https://velog.io/@debby_/SwiftData-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-2</link>
            <guid>https://velog.io/@debby_/SwiftData-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-2</guid>
            <pubDate>Wed, 28 Jun 2023 10:11:17 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://velog.io/@debby_/SwiftData-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-2-Build-an-app-with-SwiftData&count_bg=%2362B0CE&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=visited&edge_flat=false" alt="Hits"></a></p>
<blockquote>
<ul>
<li>영상 : <a href="">Build an app with SwiftData</a></li>
<li>이전글 : <a href="https://velog.io/@debby_/SwiftData-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-1">SwiftData 알아보기 (1)</a></li>
</ul>
</blockquote>
<p>안녕하세요! 오늘부터 닉네임을 바꾼 구 비비(BIBI) 현 _<strong>토브(Tov)</strong>_입니다.
<br><br></p>
<p align="center"> iOS 개발자 커뮤니티에서 활동하시는 `비비`분들이 저 포함 4명이더라구요..<br>
'흔들리는 비비들속에서 내 샴푸향 ... 느낄 수 있을까 ...?'</p>
<p align="center"><span style="font-size:24px; font-weight:bold;">?? : 아니</span></p>

<p align="center"> ... 그래서 바꿨습니다...</p>

<p><img src="https://velog.velcdn.com/images/debby_/post/95447502-ca9b-4435-916c-5725a490cb7b/image.png" alt=""></p>
<p align="center"> ...네 </p>

<p><br><br>
각설하고 이전글에 이어 SwiftData를 마저 탐방해보겠습니다!
이번엔 SwiftUI 환경에서 앱을 빌드하는 방법에 대해 설명해볼게요!</p>
<hr>
<h1 id="🌲-meet-the-app--flashcard">🌲 Meet the app : FlashCard</h1>
<p><img src="https://velog.velcdn.com/images/debby_/post/77069ed0-9ddd-4c4e-83af-01f163c10366/image.png" alt=""></p>
<p>Apple에서 제공하는 <a href="https://developer.apple.com/documentation/SwiftUI/Building-a-document-based-app-using-SwiftData">샘플코드</a>와 함께 살펴볼게요!
SwiftData를 사용하여 카드 덱 데이터를 유지하고, 카드를 터치할 때마다 앞면과 뒷면을 Filp 할 수 있는 간단한 단어장 앱이에요!</p>
<hr>
<h1 id="🖐🏻-잠깐">🖐🏻 잠깐!</h1>
<p>들어가기에 앞서 SwiftData를 적용하는데에 딱 3가지 매크로를 기억하면 됩니다!</p>
<ol>
<li>@Model</li>
<li>@Binable</li>
<li>@Query</li>
</ol>
<p>이외에는 아래 개념도 같이 다뤄볼게요!</p>
<ul>
<li>ModelContainer</li>
<li>ModelContext</li>
</ul>
<hr>
<h1 id="🌲-model--bindable--query">🌲 <code>@Model</code> &amp; <code>@Bindable</code> &amp; <code>@Query</code></h1>
<p>먼저 아주 간단하게 모델 객체를 만들어볼게요!</p>
<p>파일명 : <code>Card.swift</code></p>
<blockquote>
<ol>
<li><code>import SwiftData</code></li>
<li><code>@Model</code></li>
</ol>
</blockquote>
<p>이 2가지만 하면됩니다.</p>
<h3 id="🪄-model-적용-코드">🪄 @Model 적용 코드</h3>
<pre><code class="language-swift">// BEFORE Not using SwiftData
import Foundation

final class Card: ObservableObject {
    @Published var front: String
    @Published var back: String
    var creationDate: Date
    // ...
}</code></pre>
<pre><code class="language-swift">// AFTER: Using SwiftData
import Foundation
import SwiftData

@Model
final class Card {
    var front: String
    var back: String
    var creationDate: Date
    // ...
}</code></pre>
<p>위와같이 <code>@Model</code> 매크로를 사용하면 Card가 <code>Observable</code> 프로토콜을 준수하게 되므로 <code>ObservableObject</code>로 사용할 수 있습니다. 따라서 <code>Observable</code> 및 <code>@Published</code> 속성 래퍼를 사용할 필요가 없어집니다.</p>
<hr>
<blockquote>
<p>@Observable</p>
</blockquote>
<p>새롭게 업데이트된 <code>@Observable</code> 매크로로 인해 조금 더 쉽게 데이터 바인딩 방식이 바뀌었다고 하네요! <code>ObservedObject</code> 속성 래퍼를 <code>Bindable</code>로 변경하는것만으로 적용된 걸 확인할 수 있었습니다!</p>
<p><a href="https://developer.apple.com/videos/play/wwdc2023/10149">이와 관련된 세션</a>이 따로 있어서 보긴 했지만, 직접적으로 SwiftData 와의 관련 여부를 설명해 주진 않았던 것으로 기억해요. 하지만 <code>@Binable</code>을 채택하여 바인딩 하는 걸로 보아서는 <code>@Model</code> 매크로 안에 <code>@Observable</code> 매크로가 포함되어 있을 수도 있겠다고 추측됩니다. 개인적인 견해일 뿐입니다!ㅎㅎ</p>
<hr>
<p>파일명 : <code>CardEditorView.swift</code></p>
<blockquote>
<ol>
<li><code>import SwiftData</code></li>
<li><code>@ObservedObject</code> → <code>@Bindable</code></li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/debby_/post/6c36eccc-fb45-4c0c-b2a2-332103617b10/image.jpeg" alt=""></p>
<p>어때요, 참 쉽죠?</p>
<h3 id="🪄-bindable-적용-코드">🪄 @Bindable 적용 코드</h3>
<pre><code class="language-swift">// BEFORE Not using SwiftData
struct CardEditorView: View {
    @ObservedObject var card: Card</code></pre>
<pre><code class="language-swift">// AFTER: Using SwiftData
struct CardEditorView: View {
    @Bindable var card: Card</code></pre>
<hr>
<p>@Query를 적용하기 전에 간단하게 개념을 짚고 넘어가보겠습니다!</p>
<p><span style="font-size:30px; font-weight:bold;">🌿 <code>@Query</code> 란?</span></p>
<ul>
<li>SwiftData에서 모델을 쿼리하는 새로운 <code>Property wrapper</code> 입니다.</li>
<li><code>@State</code>가 수행하는 방식과 유사하게 _<strong>모델이 변경될 때마다 업데이트된 뷰를 트리거</strong>_합니다.</li>
<li>모든 View는 필요한 만큼 <code>@Query</code> 프로퍼티를 가질 수 있습니다.</li>
<li><code>@Query</code>는 모델 컨테이너에서 모델 컨텍스트를 가져와 데이터 소스로 사용합니다.</li>
<li>@Query는 정렬, 정렬, 필터링 및 애니메이션 변경을 구성하는 간단한 구문을 제공합니다.</li>
</ul>
<pre><code class="language-swift">@Query (sort: \.created) private var cards: [Card]</code></pre>
<hr>
<p>🙄 Sort 되긴 하는거야 ..?
갑자기 이런 궁금증이 생겨 알아보았는데요. 삽질이 조금 길어졌네요 😅
접기 기능을 이용하려고 했는데 velog는 적용이 안되네요...! <a href="https://velog.io/@debby_/Sort-%EB%90%98%EA%B8%B4-%ED%95%98%EB%8A%94%EA%B1%B0%EC%95%BC-">궁금하신 분들만 보세요 !</a></p>
<hr>
<h3 id="🪄-query-적용-코드">🪄 @Query 적용 코드</h3>
<p>파일명 : <code>ContentView.swift</code></p>
<ol>
<li><code>import SwiftData</code></li>
<li><code>@State</code> → <code>@Query</code></li>
</ol>
<pre><code class="language-swift">// BEFORE Not using SwiftData
import SwiftUI

struct ContentView: View {
    @State private var cards: [Card] = SampleDeck.contents
    // ...
}</code></pre>
<pre><code class="language-swift">// AFTER: Using SwiftData
import SwiftUI
import SwiftData

struct ContentView: View {
    @Query private var cards: [Card]
    // ...
}</code></pre>
<p>위와같이 <code>@Query</code> 를 통해 <code>Observable</code> 유형의 속성이 변경되면 데이터를 자동으로 View에 업데이트 됩니다.</p>
<p>우선 여기까지는 SwiftData와 관련된 매크로 3가지를 사용하는 방법에 대해서 소개했습니다! 이 아래부터는 <code>모델 컨테이너</code>와 <code>모델 컨텍스트</code>를 이용하여 실질적인 데이터 처리를 다뤄보겠습니다.</p>
<hr>
<h1 id="🌲-modelcontainer--modelcontext--preview">🌲 ModelContainer &amp; ModelContext &amp; Preview</h1>
<h2 id="🌿-modelcontainer">🌿 <code>modelContainer</code></h2>
<p>새로운 View Modifier입니다.</p>
<p><strong>🌱 모델 컨테이너 설정하기</strong></p>
<p>모델 컨테이너를 지정하지 않으면 뷰에서 데이터를 가져올 수 없습니다.</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/4565a13c-74b6-420d-9900-bc78f01d6b4b/image.png" alt=""></p>
<p>루트뷰에서 설정하는 경우 하위뷰에서 모델 컨텍스트를 이용해 데이터를 바인딩할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/3fc8b25f-ff51-4fef-acdf-8f4429776e5e/image.png" alt=""></p>
<p>하위 뷰에서 설정할 경우 그보다 더 하위에 해당하는 뷰에 한하여 모델 컨테이너가 적용됩니다.</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/ea7ddaa5-763b-4dc8-ab01-b03bba672c0d/image.png" alt=""></p>
<p><strong>🌱 스토리지 스택 생성하기</strong></p>
<pre><code class="language-swift">.modelContainer(for: Card.self)</code></pre>
<p>이 코드만 작성하면 하위 뷰에서 <code>@Query</code>가 사용할 컨텍스트를 포함하여 전체 스토리지 스택을 생성합니다.</p>
<p>🌱 <strong>하나의 뷰는 하나 이상의 모델 컨테이너를 포함할 수 있습니다.</strong></p>
<pre><code class="language-swift">.modelContainer(for: [Trip.self, BucketListItem.self, LivingAccommodation.self])
</code></pre>
<hr>
<h2 id="🌿-modelcontext">🌿 modelContext</h2>
<p>새롭게 출시된 Swift의 Environment variable 입니다.</p>
<pre><code class="language-swift">@Environment (\.modelContext) private var modelContext</code></pre>
<p>이건 modelContainer를 지정할 때 자동으로 환경 변수가 설정된다고 하네요.</p>
<p><strong>🌱 모델 컨텍스트에 대한 액세스를 제공합니다.</strong></p>
<p><code>modelContext.뭐시기</code> 로 접근할 수 있습니다. </p>
<pre><code class="language-swift">let newCard = Card(front: &quot;Sample Front&quot;, back: &quot;Sample Back&quot;)
modelContext.insert(object: newCard)</code></pre>
<p><strong>🌱 원하는 만큼 가질 수 있습니다.</strong></p>
<p>이것또한 modelContainer와 마찬가지로 각 View에는 단일 컨텍스트가 있지만,
일반적으로 Application은 필요한 만큼 많이 가질 수 있다고 합니다.</p>
<p><a href="https://developer.apple.com/videos/play/wwdc2023/10195"><strong>Model your schema with SwiftData</strong></a>세션에서 modelContainer를 배열형태로 선언하는 걸 봤는데 modelContext는 다중으로 어떻게 사용하는건지 아직 못봤네요! 다음 글에서 설명할 수 있게 찾아보겠습니다!</p>
<p>특별한점은 <code>CoreData</code>에서 <code>save()</code>를 작동시켜야만 저장되었던것과 달리,
insert, update, delete 와 같은 UI 관련 이벤트에 의해 자동 저장 트리거가 발생합니다! 편리하네요 😀</p>
<hr>
<h2 id="🌿-preview에-dummydata-띄우기">🌿 Preview에 DummyData 띄우기</h2>
<pre><code class="language-swift">import SwiftData

@MainActor
let previewContainer: ModelContainer = {
    do {
        let container = try ModelContainer(
            for: Card.self, ModelConfiguration(inMemory: true)
        )
        for card in SampleDeck.contents {
            container.mainContext.insert(object: card)
        }
        return container
    } catch {
        fatalError(&quot;Failed to create container&quot;)
    }
}()</code></pre>
<p><code>@MainActor</code> 매크로를 통해 프리뷰에서 보여줄 ModelContainer 를 정의합니다.
매크로에 대해서는 <a href="https://developer.apple.com/videos/play/wwdc2023/10166/"><strong>Write Swift macros</strong></a> 세션을 통해서 깊이 공부해봐야겠습니다.</p>
<p>Apple이 미리 만들어놓은 샘플 데이터는 아래와 같았습니다.</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/031f36f1-2f7c-409f-a369-559145a4965c/image.png" alt=""></p>
<pre><code class="language-swift">#Preview {
    ContentView()
        .frame(minWidth: 500, minHeight: 500)
        .modelContainer(previewContainer)
}</code></pre>
<p>위에서 정의한 previewContainer를 불러오기만 프리뷰에서 볼 수 있게됩니다!</p>
<blockquote>
<p><strong>하지~만? 버그인지 현재 해당 코드는 프리뷰가 작동하지 않아서 피드백 넣어놓은 상태입니다…🥲</strong></p>
</blockquote>
<hr>
<h1 id="🌲-bonus-document-based-apps">🌲 [Bonus] Document-based apps</h1>
<p>SwiftData를 통해 간편하게 문서 기반 앱을 개발 할 수 있게되었습니다.</p>
<p>문서 기반 앱은 사용자가 다양한 유형의 문서를 작성, 열기, 보기, 편집할 수 있는 애플리케이션입니다.</p>
<p>예를 들어 pages, keynote 와 같이 하나의 파일 형태로 FlashCard 묶음을 저장할 수 있게 된다는거죠!</p>
<p>문서 기반 앱을 설정하기 위해 DocumentGroup 이니셜라이저를 사용하여 앱을 해당 형식의 앱으로 전환합니다. 모델 유형(Card.self), 콘텐츠 유형 및 뷰 빌더를 전달합니다.</p>
<pre><code class="language-swift">import SwiftUI
import SwiftData

@main
struct SwiftDataFlashCardSample: App {
    var body: some Scene {
        #if os(iOS) || os(macOS)
        DocumentGroup(editing: Card.self, contentType: .flashCards) {
            ContentView()
        }
        #else
        WindowGroup {
            ContentView()
                .modelContainer(for: Card.self)
        }
        #endif
    }
}</code></pre>
<p>contentType은 SwiftData 문서의 고유한 표현을 나타내는 데 사용됩니다. 이는 파일 확장자와 연결됩니다. </p>
<p>잠깐 ! 여기서 콘텐츠 유형이란 크게 2가지로 나누어 볼 수 있는데</p>
<p>JPEG 와 같은 <code>Binary data document</code> 와 Xcode 프로젝트 파일 같은 <code>Package document</code>가 있습니다.</p>
<p>SwiftData 문서는 <code>패키지 형식</code>이며, 외부에 표시된 속성은 문서 패키지의 일부가 됩니다.</p>
<pre><code class="language-swift">import UniformTypeIdentifiers

extension UTType {
    static var flashCards = UTType(exportedAs: &quot;com.example.flashCards&quot;)
}</code></pre>
<p>UTType을 init 할 때 <a href="https://developer.apple.com/documentation/uniformtypeidentifiers/uttypereference/3600608-init/">exportedAs</a> 파라미터에는 앱의 identifier String을 넣어주면 됩니다.</p>
<p>그리고 info.plist - Exported Type Identifiers 를 아래와 같이 설정합니다.</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/1def08e3-73f5-4ea5-b832-35915690dbcd/image.png" alt=""></p>
<p>*이때 extension에서 작성한 identifier와 String이 일치하지 않으면 에러가 발생할 수 있습니다.</p>
<p>설정을 마치면</p>
<img src="https://velog.velcdn.com/images/debby_/post/a93b540d-8f43-45ae-9d30-50043f17ee3a/image.png" width=300px>

<p><code>.sampledeck</code> 이라는 확장자를 가진 패키지 형식의 Flash Cards Deck 파일을 따로 저장할 수 있게됩니다.</p>
<hr>
<h1 id="🌲-마무리하며-">🌲 마무리하며 …</h1>
<p>설명이 좀 길었지만, 생각보다 SwiftData를 사용하는 것 자체는 쉽다는 생각을 했습니다!
하지만 아직 베타라서 그런지 WWDC 세션에서 제공된 프리뷰 코드나, sort 코드가 잘 작동하지 않아서 아쉬웠습니다.
포럼에도 프리뷰 오류에 대한 질문들이 있어서 적용해봤고 그것도 해결되진 않았습니다..ㅎㅎ
얼른 안정적으로 자리 잡아서 관계형 데이터를 이용한 앱을 조금 더 쉽게 만들어보고 싶네요!</p>
<p>긴 글 읽어주셔서 감사합니다..! 언제나 지적이나 질문 환영합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftData 알아보기 (1)]]></title>
            <link>https://velog.io/@debby_/SwiftData-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-1</link>
            <guid>https://velog.io/@debby_/SwiftData-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-1</guid>
            <pubDate>Tue, 20 Jun 2023 08:13:55 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fvelog.io%2F%40debby_%2FSwiftData-%25EC%2595%258C%25EC%2595%2584%25EB%25B3%25B4%25EA%25B8%25B0-1&count_bg=%2362B0CE&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=visited&edge_flat=false" alt="Hits"></a></p>
<blockquote>
<p>WWDC 영상 바로가기 : <a href="https://developer.apple.com/videos/play/wwdc2023/10187/">Meet SwiftData</a></p>
</blockquote>
<p>안녕하세요 비비입니다!
WWDC23 영상을 탐방하다가 <a href="https://developer.apple.com/documentation/swiftdata/">SwiftData</a>에 관심이 생겨 보던 중
보기만하면 기억에 남지 않을 것 같아서 기록을 시작해봅니다.</p>
<p>개인적으로는 <a href="https://www.codestates.com/blog/content/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8">JAVA의 SpringBoot 프레임워크</a>가 생각나서
뭔가 CoreData 보다 친숙한 느낌이라 얼른 사용해보고싶네요!</p>
<p>정리한 내용 중 오역이 있다면 언제든지 말씀해주세요 😄</p>
<hr>
<h1 id="🌲-swiftdata란">🌲 SwiftData란?</h1>
<p>데이터 모델링 및 관리를 위한 프레임워크입니다.
SwiftUI와 마찬가지로 <strong><em>외부 파일 형식이 없는 코드</em></strong>에 전적으로 집중하고,
원활한 API 경험을 위해 _<strong>새로운 Swift 매크로</strong>_가 제공하는 표현력에 의존합니다.
또한 SwiftUI와 자연스럽게 통합되며 <code>CloudKit</code> 및 <code>Widgets</code>와 같은 다른 플랫폼 기능과 함께 작동합니다.</p>
<hr>
<h1 id="🌲-using-the-model">🌲 Using the model</h1>
<p><code>@Model</code> 매크로 사용하기</p>
<ul>
<li>Swift 코드에서 모델의 스키마를 정의하는 데 사용됩니다.</li>
<li>클래스를 장식하는걸로 간단하게 스키마를 생성할 수 있습니다.</li>
</ul>
<pre><code class="language-swift">import SwiftData

@Model
class Trip {
    var name: String
    var destination: String
    var endDate: Date
    var startDate: Date

    var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}</code></pre>
<p>*스키마(Schema): Database의 구조와 제약조건에 관해 전반적인 명세를 기술한 것. 즉 어떤 속성(Attribute)들로 이루어진 개체(Entity)들과, 그 개체들끼리의 관계(Relation)에 대한 정의 및 제약조건들을 기술한 것 입니다.</p>
<h2 id="🌿-속성attributes">🌿 속성(Attributes)</h2>
<p><strong>🌱 Attributes inferred from properties</strong>
속성으로 즉시 사용할 수 있도록 값 유형 속성을 조정합니다.</p>
<p><strong>🌱 Support for basic value types</strong>
String, Int 및 Float와 같은 기본 값 유형이 포함됩니다.</p>
<p><strong>🌱 Complex value types</strong></p>
<ul>
<li>Struct</li>
<li>Enum</li>
<li>Codable</li>
<li>Collections of value types</li>
</ul>
<p>컬렉션을 포함하여 구조체, 열거형 및 코딩 가능한 유형과 같은 보다 복잡한 값 유형을 포함할 수 있습니다.
즉, <code>@Model</code> 매크로만으로 속성을 쉽게 정의할 수 있습니다.</p>
<h2 id="🌿-관계relationships">🌿 관계(Relationships)</h2>
<p><strong>🌱 Relationships are inferred from reference types</strong></p>
<ul>
<li>Other model types</li>
<li>Collections of model types</li>
</ul>
<p>SwiftData 모델은 유형을 관계로 참조합니다.
관계 및 모델 유형 컬렉션을 사용하여 모델 유형 간에 연결할 수 있습니다.</p>
<h2 id="🌿-additional-metadata">🌿 Additional metadata</h2>
<p>🌱 <code>@Model</code> 은 저장된 모든 속성을 수정할 수 있습니다.</p>
<p>🌱 아래의 매크로를 이용해 속성과 관계를 정의합니다.</p>
<ul>
<li><code>@Attribute</code></li>
<li><code>@Relationship</code></li>
</ul>
<p>🌱 <code>@Transient</code>로 속성을 제외할 수 있습니다.
👉🏻 이건 <a href="https://developer.apple.com/videos/play/wwdc2023/10195">Model your schema with SwiftData</a> 에서 자세히 봐야겠습니다!</p>
<pre><code class="language-swift">@Model
class Trip {
    @Attribute(.unique) var name: String // Unique 지정
    var destination: String
    var endDate: Date
    var startDate: Date

    @Relationship(.cascade) var bucketList: [BucketListItem]? = [] // Trip 객체 삭제시 같이 삭제되도록 설정
    var livingAccommodation: LivingAccommodation?
}</code></pre>
<hr>
<h1 id="🌲-working-with-your-data">🌲 Working with your data</h1>
<p>데이터 바인딩에 사용되는 2가지 주요 개체 <code>ModelContainer</code> 및 <code>ModelContext</code> 를 살펴볼게요!</p>
<h2 id="🌿-model-container">🌿 Model Container</h2>
<p>저장하려는 모델 유형 목록을 지정하기만 하면 모델 컨테이너를 만들 수 있습니다.
컨테이너를 설정하여 모델 컨텍스트로 데이터를 가져오고 저장할 준비를 할 수 있습니다.</p>
<p><strong>🌱 Persistence Backend</strong>
모델 유형에 대한 영구 백엔드 제공</p>
<p><strong>🌱 Customized with configurations &amp; Provides schema migration options</strong>
<code>configurations</code> 속성으로 <code>URL</code>, <code>CloudKit</code> 및 <code>그룹 컨테이너 식별자</code>, <code>마이그레이션 옵션</code> 등을 커스터마이징할 수 있습니다.</p>
<pre><code class="language-swift">// Initialize with only a schema
let container = try ModelContainer([Trip.self, LivingAccommodation.self])

// Initialize with configurations
let container = try ModelContainer(
    for: [Trip.self, LivingAccommodation.self],
    configurations: ModelConfiguration(url: URL(&quot;path&quot;))
)</code></pre>
<p>🧐 이 부분에서는 Trip과 LivingAccommodation을 동시에 container에 담는 것 같은데
DeepDive하면서 어떻게 정의되는건지 한번 더 살펴봐야겠습니다.</p>
<p><strong>🌱 SwiftUI에서 사용하는 방법</strong></p>
<pre><code class="language-swift">import SwiftUI

@main
struct TripsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(
            for: [Trip.self, LivingAccommodation.self]
        )
    }
}</code></pre>
<h2 id="🌿-model-context">🌿 Model Context</h2>
<p><strong>🌱 Tracking updates</strong>
<strong>🌱 Fetching model</strong>
<strong>🌱 Saving changes</strong>
<strong>🌱 Undoing changes</strong></p>
<p>업데이트 추적, 데이터 가져오기, 변경 사항 저장하기, 변경 사항 실행 쉬소하기 기능의 인터페이스입니다.</p>
<pre><code class="language-swift">import SwiftUI

struct ContextView : View {
    @Environment(\.modelContext) private var context
}</code></pre>
<pre><code class="language-swift">import SwiftData

// 1. 일반적으로 모델 컨테이너를 생성한 후 뷰의 환경에서 modelContext를 가져오는 경우
let context = container.mainContext

// 뷰 계층 구조 외부에서 공유된 기본 액터 바인딩 컨텍스트를 제공하도록 모델 컨테이너에 요청하거나
// 2. 주어진 모델 컨테이너에 대한 새 컨텍스트를 간단히 인스턴스화 하는 경우
let context = ModelContext(container)</code></pre>
<p>컨텍스트가 있으면 데이터를 가져올 준비가 된 것입니다.</p>
<p>🧐 라고 소개는 되어있지만, 정확하게 <code>context</code>가 뭘 의미하는건지는 조금 더 알아봐야겠습니다. 왜냐하면 위에서 <code>modelContainer</code> 가 <code>[Trip.self, LivingAccommodation.self]</code>으로 2개의 <code>model</code>을 동시에 가지고 있는데 <code>context</code>를 활용하여 데이터를 어떻게 가져온다는건지 이 코드만 보고 확신할 수는 없네요..!</p>
<h2 id="🌿-fetching-your-data">🌿 Fetching your data</h2>
<p>🌱  새로운 Swift 기본 유형</p>
<ul>
<li>Predicate</li>
<li>FetchDescriptor</li>
</ul>
<p>🌱  SortDescriptor의 개선 사항</p>
<h3 id="🪴-predicate">🪴 Predicate</h3>
<pre><code class="language-swift">let today = Date()
let tripPredicate = #Predicate&lt;Trip&gt; { 
    $0.destination == &quot;New York&quot; &amp;&amp; // 목적지 : 뉴욕
    $0.name.contains(&quot;birthday&quot;) &amp;&amp; // 키워드 : 생일
    $0.startDate &gt; today // 오늘 이후, 계획된 여행 탐색
}</code></pre>
<p><code>메모리의 데이터를 필터링 하여 가져오기 위한 쿼리 조건</code>이라고 해야할까요
<strong>CoreData</strong> 에서 사용하던 NSPredicate의 iOS 17 버전이라고 할 수 있습니다.</p>
<h3 id="🪴-fetchdescriptor">🪴 FetchDescriptor</h3>
<pre><code class="language-swift">let descriptor = FetchDescriptor&lt;Trip&gt;(predicate: tripPredicate)

let trips = try context.fetch(descriptor)</code></pre>
<p><code>FetchDescriptor</code>는 <code>SwiftData 쿼리를 맞춤화</code>하는 방법을 제공합니다.
위와같이 _필터 조건을 지정_한 후 <code>FetchDescriptor</code>를 통해 가져올 수 있습니다.</p>
<h3 id="🪴-sortdescriptor">🪴 SortDescriptor</h3>
<p><strong>🌱 Updated to support all Comparable types Swift native keypaths</strong>
기본 Swift 유형 및 키 경로를 지원하기 위해 모든 비교 유형을 지원하도록 업데이트 되었습니다.</p>
<pre><code class="language-swift">let descriptor = FetchDescriptor&lt;Trip&gt;(
    sortBy: SortDescriptor(\Trip.name),
    predicate: tripPredicate
)

let trips = try context.fetch(descriptor)</code></pre>
<p><code>SortDescriptor</code>를 사용하여 가져온 여행을 구성할 <code>순서를 지정</code>할 수 있습니다.</p>
<h2 id="🌿-more-fetchdescriptor-options">🌿 More FetchDescriptor options</h2>
<p><code>Predicate</code> 및 <code>SortDescriptor</code> 외에도 <code>prefetch</code>할 관련 개체를 지정하고
결과 수를 제한하고 결과에서 저장되지 않은 변경 사항을 제외하는 등의 작업을 수행할 수 있습니다.</p>
<p><strong>🌱 Relationships to prefetch</strong>
<strong>🌱 Result limits</strong>
<strong>🌱 Exclude unsaved changes</strong></p>
<h2 id="🌿-modifying-your-data">🌿 Modifying your data</h2>
<p>ModelContext를 사용하여 이러한 작업을 구동함으로써 데이터를 쉽게 생성, 삭제 및 변경할 수 있습니다.</p>
<p><strong>🌱 Basic operations</strong> (CRUD)</p>
<ul>
<li>Inserting</li>
<li>Deleting</li>
<li>Saving</li>
<li>Changing</li>
</ul>
<pre><code class="language-swift">var myTrip = Trip(name: &quot;Birthday Trip&quot;, destination: &quot;New York&quot;)

// Insert a new trip
context.insert(myTrip)

// Delete an existing trip
context.delete(myTrip)

// Manually save changes to the context
try context.save()</code></pre>
<p><code>@Model</code> 매크로는 ModelContext가 변경 사항을 자동으로 추적하고 다음 저장 작업에 포함하도록 돕는다고 합니다.</p>
<hr>
<h1 id="🌲-using-swiftdata-with-swiftui">🌲 Using SwiftData with SwiftUI</h1>
<ul>
<li>SwiftUI와의 원활한 통합</li>
<li>쉬운 구성</li>
<li>자동으로 데이터 Fetching</li>
</ul>
<h2 id="🌿-view-modifiers">🌿 View Modifiers</h2>
<p>데이터 저장소를 구성하고, 옵션을 변경하고, 실행 취소를 활성화하고, 자동 저장을 전환할 수 있습니다.</p>
<p><strong>🌱 Leverage scene and view modifiers</strong> <del>(뭐라고 해석하면 좋을지 혹시 아시는 분 계실까요 ?)</del>
<strong>🌱 <code>.modelContainer</code>를 사용하여 데이터 저장소 구성</strong>
<strong>🌱 SwiftUI environment로 등록하여 사용하기</strong></p>
<p>설정한 후 데이터 사용을 시작하는 가장 쉬운 방법은 새로운 <code>@Query</code> 속성 래퍼입니다.
한 줄의 코드로 데이터베이스에 저장된 모든 항목을 쉽게 로드하고 필터링할 수 있습니다.</p>
<pre><code class="language-swift">import SwiftUI

struct ContentView: View  {
    @Query(sort: \.startDate, order: .reverse) var trips: [Trip]
    @Environment(\.modelContext) var modelContext

    var body: some View {
       NavigationStack() {
          List {
             ForEach(trips) { trip in 
                 // ...
             }
          }
       }
    }
}</code></pre>
<h1 id="🌲-마무리하며">🌲 마무리하며…</h1>
<p>Meet SwiftData는 소개하는 내용이 많아 구체적인 코드를 살펴보기엔 짧은 감이 있네요 !
다음은 <a href="https://developer.apple.com/videos/play/wwdc2023/10154"><strong>Build an app with SwiftData</strong></a>를 보고 조금 더 DeepDive 해보겠습니다!
부족한 글이지만 읽어주셔서 감사합니다 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Screen Time API 알아보기 (2)]]></title>
            <link>https://velog.io/@debby_/Whats-new-in-Screen-Time-API</link>
            <guid>https://velog.io/@debby_/Whats-new-in-Screen-Time-API</guid>
            <pubDate>Tue, 20 Jun 2023 07:01:08 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fvelog.io%2F%40debby_%2FWhats-new-in-Screen-Time-API&count_bg=%2362B0CE&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=visited&edge_flat=false" alt="Hits"></a></p>
<blockquote>
<p>WWDC 영상 바로가기 : <a href="https://developer.apple.com/videos/play/wwdc2022/110336/">What&#39;s new in Screen Time API</a></p>
</blockquote>
<h1 id="🔵-지난-ios15-릴리즈-주요-사항">🔵 지난 iOS15 릴리즈 주요 사항</h1>
<h2 id="🔹-family-controls">🔹 Family Controls</h2>
<ul>
<li>Screen Time API에 대한 엑세스 권한을 부여하는 역할. 본질적 <code>게이트웨이</code></li>
<li>자녀 보호 앱 제거 &amp; 우회 방지</li>
<li>가족이 사용하는 앱을 위한 개인 정보 보호 토큰</li>
</ul>
<h2 id="🔹-managed-setting">🔹 Managed Setting</h2>
<ul>
<li>기기에 지속적인 제한 설정 기능</li>
<li>웹 콘텐츠 필터링 제공</li>
<li>맞춤형 UI로 앱&amp;웹 보호</li>
</ul>
<h2 id="🔹-device-activity">🔹 Device Activity</h2>
<ul>
<li>디바이스 활동 일정의 시작 및 종료 시 코드 실행</li>
<li>사용 한계치 도달 시 코드 실행</li>
</ul>
<h1 id="🔵-ios16-데모-앱--worklog">🔵 iOS16 데모 앱 : Worklog</h1>
<p>특정 수치들이 충족될 때까지 특정 앱의 사용을 제한하는 기능</p>
<h2 id="🔹-new-family-controls">🔹 <code>NEW</code> Family Controls</h2>
<p><code>iOS15</code> iCloud 승인을 통해서만 자녀의 기기를 승인할 수 있었음</p>
<p>→ <code>iOS16</code> 자신의 기기에서 개별 사용자를 승인할 수 있게 되었음</p>
<ul>
<li>개별 사용자가 스스로 인증</li>
<li>여러 개의 개별 인증</li>
<li>개별 인증에 대한 앱 제거 또는 iCloud 로그아웃 제한 없음</li>
</ul>
<pre><code class="language-swift">import FamilyControls

let center = AuthorizationCenter.shared

Task {
    do {
        try await center.requestAuthorization(for: .individual)
    } catch {
        print(error)
    }
}</code></pre>
<p>기존 코드보다 훨씬 간결해졌다.</p>
<h2 id="🔹-managed-settings-store">🔹 Managed Settings Store</h2>
<p>현재 사용자나 기기에 설정을 적용하는 데이터 저장소</p>
<p><code>iOS15</code> 프로세스당 하나의 ManagedSettingsStore 만 허용됨, 또한 앱과 DeviceActivity의 확장 프로그램은 별도로 ManagedSettingsStore가 필요해서 DeviceActivity에 대한 응답에서 설정을 변경하기 어려웠음</p>
<p>→ <code>iOS16</code> 프로세스마다 고유한 이름을 가진 ManagedSettingsStore를 최대 50개 만들 수 있음. 이렇게 명명된 ManagedSettingsStore들(Named stores)은 앱과 모든 앱 확장프로그램에서 자동으로 공유됨. 모든 Named stores에서 한번에 설정 제거도 가능해짐</p>
<pre><code class="language-swift">import ManagedSettings

extension ManagedSettingsStore.Name {
    static let youTube = Self(&quot;YouTube&quot;)
}

final class BlockingManager {
    func managedSettingStoreSetup() {
        let youTubeCategory = ActivityCategoryToken() // 이거 안됨
        let youTubeStore = ManagedSettingsStore(named: .youTube)
        youTubeStore.shield.applicationCategories = .specific(except: [youTubeCategory])
        youTubeStore.shield.webDomainCategories = .specific(except: [youTubeCategory])
    }
}</code></pre>
<p>영상에선 <code>ActivityCategoryToken</code>에 대한 사용 방법을 알려주지 않아서 따로 찾아봤다.</p>
<p><a href="https://developer.apple.com/forums/thread/722944">How to use categories in Managed S… | Apple Developer Forums</a></p>
<p><a href="https://developer.apple.com/documentation/managedsettings/activitycategorytoken">ActivityCategoryToken | Apple Developer Documentation</a></p>
<p><a href="https://developer.apple.com/documentation/FamilyControls/FamilyActivitySelection">FamilyActivitySelection | Apple Developer Documentation</a></p>
<p><a href="https://developer.apple.com/documentation/familycontrols/familyactivitypicker">FamilyActivityPicker | Apple Developer Documentation</a></p>
<p>여기까지 보고 이전에 <a href="https://velog.io/@debby_/WWDC-Screen-Time-API#-2-shield-discouraged-apps">작성했던 예제</a>를 살펴봤다.</p>
<p><strong><strong>FamilyActivityPicker</strong></strong> 에서 유저가 선택한 앱들의 정보를 <strong><strong>FamilyActivitySelection</strong></strong>에 <strong><strong>담아 model에 전달한다.</strong></strong></p>
<pre><code class="language-swift">// APP: Choose the Apps to Discourage

import FamilyControls
import SwiftUI

@StateObject var model = MyModel()
@State var isPresented = false

var body: some View {
        Button(&quot;Select Apps to Discourage&quot;) {
                isPresented = true
        }
        .familyActivityPicker(isPresented: $isPresented, selection: $model.selectionTpDiscourage)
}</code></pre>
<p><a href="https://developer.apple.com/forums/thread/691168">Is it possible to use familly acti… | Apple Developer Forums</a></p>
<p>문제는 SwiftUI 코드라서 <code>UIHostingController</code>를 사용해야한다.</p>
<p><a href="https://medium.com/@max.codes/use-swiftui-in-uikit-view-controllers-with-uihostingcontroller-8fe68dfc523b">Use SwiftUI in UIKit View Controllers with UIHostingController</a></p>
<p><a href="https://www.youtube.com/watch?v=z_9EOGDw5uk&amp;t=2s">UIHostingController in SwiftUI 2020 (Use in UIViewController) - How To Bridge UIKit with SwiftUI.</a></p>
<p>아무튼 나머지 코드를 먼저 살펴보자면</p>
<pre><code class="language-swift">import SwiftUI
import DeviceActivity

extension DeviceActivityName {
        static let activity = Self(&quot;activity&quot;)
}

let deviceActivityCenter = DeviceActivityCenter()

Button(&quot;Allow for Evening&quot;) {
                try? deviceActivityCenter.startMonitoring(
                    .activity,
                    during: DeviceActivitySchedule(
                        intervalStart: DateComponents(hour: 18),
                        intervalEnd: DateComponents(hour: 20),
                        repeats: true)
                )
            }</code></pre>
<pre><code class="language-swift">import DeviceActivity
import ManagedSettings

final class BlockingMonitor: DeviceActivityMonitor {

    // let database = BarkDataBase() //TODO: Make database for Token of the published category
    override func intervalDidStart(for activity: DeviceActivityName) {
        super.intervalDidStart(for: activity)
        let youTubeStore = ManagedSettingsStore(named: .youTube)
        youTubeStore.clearAllSettings() //youTube 제약이 시작되면 이전의 제약을 모두 clear한다.
    }
    override func intervalDidEnd(for activity: DeviceActivityName) {
        super.intervalDidEnd(for: activity)

        let appStore = ManagedSettingsStore(named: .youTube)
//        let appCategory = database.appCategoryToken //TODO: Make database for save&amp;load `youTubeCategoryToken`
        youTubeStore.shield.applicationCategories = .specific([youTubeStore])
        youTubeStore.shield.webDomainCategories = .specific([youTubeStore])
    }
}</code></pre>
<h2 id="🔹-deviceactivity">🔹 DeviceActivity</h2>
<p>현재 사용자나 기기에 설정을 적용하는 데이터 저장소</p>
<p><code>iOS15</code> 앱과 웹 사이트에 대한 사용량 허용치와 사용 시간대에 반응했음</p>
<p>→ <code>iOS16</code> SwiftUI를 사용한 사용자 지정 사용량 보고서를 만들 수 있게 됐음. 사용량 데이터는 새로운 확장 포인트를 통해 사용자에게 표시될 데이터와 화면에 표현될 방식을 사용자 정의 할 수 있음. 이를 <code>DeviceActivityReport</code>는 최종 사용자의 개인정보 보호를 보장함</p>
<p>이걸 이용해서 보고서를 개인화하여 뷰에 표시할 수 있고, 원하는 차트타입 뷰를 구현해서 보여주면 된다는데 끝까지 다 보고 적용시켜보려고 SwiftUI 프로젝트 예제도 해봤는데, 코드를 너무 일부만 보여주고 샘플 코드조차 공개를 해놓지 않아서 도대체 어떻게 사용하라는 건지 모르겠다!</p>
<p>나머지는 공식문서를 보고 직접 이해하고 적용해보는 수 밖에 없는 듯 하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Screen Time API 알아보기 (1)]]></title>
            <link>https://velog.io/@debby_/WWDC-Screen-Time-API</link>
            <guid>https://velog.io/@debby_/WWDC-Screen-Time-API</guid>
            <pubDate>Thu, 13 Apr 2023 01:32:14 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fvelog.io%2F%40debby_%2FWWDC-Screen-Time-API&count_bg=%2362B0CE&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=visited&edge_flat=false" alt="Hits"></a></p>
<h1 id="screen-time-api">Screen Time API</h1>
<blockquote>
<p>참고 자료 : <a href="https://developer.apple.com/videos/play/wwdc2021/10123/">원본 영상</a> / <a href="https://developer.apple.com/documentation/screentimeapidocumentation/">공식 문서</a></p>
</blockquote>
<p>스크린 타임은</p>
<ol>
<li>사용자와 가족이 앱과 웹사이트를 <strong>얼마나 자주 사용하는지 추적</strong>하고,</li>
<li><strong>제한을 설정</strong>하여 시간을 관리하고,</li>
<li>가족 구성원과 <strong>사용량을 공유</strong>하여 기기 사용 현황을 파악할 수 있으며,</li>
<li>마지막으로 <strong>자녀가 누구와 소통하는지 등을 관리</strong>할 수 있다.</li>
</ol>
<h2 id="📱-사용-환경">📱 사용 환경</h2>
<ul>
<li><code>iOS 15</code></li>
<li><code>iPadOS 15</code></li>
</ul>
<h2 id="✅-3가지-기본-원칙">✅ 3가지 기본 원칙</h2>
<ol>
<li>기존 제한에 대한 직접 API 액세스를 위한 최신 on Device 프레임워크 제공</li>
<li>사용자 개인 정보 보호</li>
<li>개발자가 새롭고 환상적인 동적 자녀 보호 환경을 만들 수 있도록 보장하는 것</li>
</ol>
<h2 id="🎞️-3가지-프레임워크">🎞️ 3가지 프레임워크</h2>
<p><img src="https://velog.velcdn.com/images/debby_/post/60b016a6-b4be-40a0-9049-4c0b5cabf99a/image.png" alt=""></p>
<h3 id="✨-1-managed-setting-framework관리-설정">✨ 1) Managed Setting Framework(관리 설정)</h3>
<p><img src="https://velog.velcdn.com/images/debby_/post/04a8b335-f680-439c-8550-618114f13f4e/image.png" alt=""></p>
<p>앱에서 스크린 타임에서 사용할 수 있는 제한 사항에 직접 액세스할 수 있다.</p>
<ul>
<li>제한 설정할 수 있는 것<ul>
<li>계정 잠금</li>
<li>비밀번호 변경 방지</li>
<li>휍 트래픽 필터링</li>
<li>애플리케이션 차단</li>
</ul>
</li>
</ul>
<h3 id="✨-2-family-controls-framework가족-제어">✨ 2) Family Controls Framework(가족 제어)</h3>
<p><img src="https://velog.velcdn.com/images/debby_/post/aad14550-3f86-475f-bedc-659b620c45a3/image.png" alt=""></p>
<p>가족 제어는 <strong>트위터의 개인정보 보호정책을 주도</strong>한다.
가족 공유를 활용하여 보호자 승인 없이 스크린 타임 API에 액세스 할 수 없도록 한다.
보호자의 승인을 받은 앱은 보호자 승인 없이 기기에서 제거할 수 없다.
또한 앱과 웹 사이트를 나타내는 _<strong>토큰</strong>_을 제공하여 _<strong>스크린 타임 API 전체에서 사용량을 모니터링하거나 제한</strong>_하는 데 사용된다. (가족 그룹 외부에서는 볼 수 없도록 정보가 모두 보호된다.)</p>
<h3 id="✨-3-device-activity디바이스-활동">✨ 3) Device Activity(디바이스 활동)</h3>
<p><img src="https://velog.velcdn.com/images/debby_/post/4a3d7b2c-98bf-4c9b-a3ea-a193c7d62cdc/image.png" alt=""></p>
<p>디바이스 활동은 _<strong>앱을 실행하지 않고도 코드를 실행할 수 있는 기능</strong>_을 제공한다.
앱에 웹 &amp; 앱 사용을 모니터링하고 필요할 때 코드를 실행할 수 있는 새로운 방법을 제공한다.
디바이스 활동 일정과 이벤트</p>
<ul>
<li><code>기기 활동 일정</code> : 시작될 때와 끝날 때 애플리케이션의 확장 프로그램을 호출하는 시간 창</li>
<li><code>이벤트</code> : 유저가 기기 활동 일정의 사용 임계값에 도달할 때 내선 번호를 호출하는 사용량 모니터.</li>
</ul>
<p>앱은 어떤 유형의 사용량을 언제 모니터링할지 선언하기만 하면 된다.</p>
<h2 id="🙄-세-가지-프레임워크를-결합하면">🙄 세 가지 프레임워크를 결합하면?</h2>
<ol>
<li>보호자와 자녀의 기기 모두 앱을 설치</li>
<li>보호자가 자녀 기기에서 앱을 열기</li>
<li>앱은 가족 제어를 통해 승인됨</li>
<li>추후 보호자 기기의 앱에서 설정, 제한 및 규칙을 선택</li>
<li>앱이 해당 정보를 자녀 기기로 보냄</li>
<li>자녀 기기에서 앱이 장치 활동으로 일정 및 이벤트를 생성</li>
<li>일정이 발생하거나 이벤트가 발생하면 앱의 장치 활동 확장이 호출됨</li>
<li>확장 프로그램에서 관리 설정으로 제한 설정</li>
</ol>
<h2 id="📲-demo-app--homework">📲 Demo App : Homework</h2>
<p><img src="https://velog.velcdn.com/images/debby_/post/ab0e8b4f-c891-4584-ae71-cb3b17043e61/image.png" alt=""></p>
<p>보호자가 원하는 다른 <strong><code>앱의 사용량이 누적될 때까지</code></strong> 특정 앱에 대한 자녀의 <strong><code>액세스를 제한</code></strong>하여 좋은 습관을 장려하는 것이 주 기능이다.</p>
<h3 id="✨-1-setup-and-authorizing-family-controls">✨ 1. Setup and authorizing Family Controls</h3>
<p>가족 제어에 대한 승인을 요청하는 방법: <em><strong><code>프로젝트 설정과 패밀리 컨트롤 승인</code></strong></em></p>
<p>먼저 <a href="https://developer.apple.com/documentation/Xcode/adding-capabilities-to-your-app">Xcode의 해당 Taget에서 Capability를 추가</a>한다.</p>
<pre><code class="language-swift">// APP: 권한 요청하기
import FamilyControls

AuthorizationCenter.shared.requestAuthorization { result in
        switch result {
        case .success():
                ...
        case .failure(let error):
                ...
        }
}</code></pre>
<p>내 앱이 이전에 이 iPhone에서 실행된 적이 없기 때문에 요청 승인은 경고와 함께 보호자의 승인을 요청한다. 허용을 탭하면 계속하려면 보호자에게 Apple ID와 비밀번호로 인증하라는 메시지가 표시된다.</p>
<p>이때 보호자가 인증에 성공하면 요청인증을 호출하면 다시 경고 메시지를 표시하지 않고 자동으로 성공 여부를 반환한다.</p>
<p>오용을 방지하기 위해 로그인한 iCloud가 가족 공유를 사용하는 자녀가 아닌 경우 요청 승인은 실패를 반환한다.</p>
<p>앱에서 스크린 타임 API를 사용할 수 있도록 준비하는 것은 이렇게 쉽다.</p>
<p>가족 제어로 인증하면 앱에 다른 권한도 부여된다.</p>
<ul>
<li>기기가 인증되면 사용자는 더 이상 iCloud에서 로그아웃할 수 없음</li>
<li>네트워크 확장 프레임워크로 구축된 <code>on-device web content filters</code>는 앱에 포함될 수 있으며 자동으로 설치되고 제거할 수 없음</li>
<li>이를 통해 앱에서 기기의 웹 트래픽을 필터링할 수 있음</li>
</ul>
<p>자녀 보호 앱은 자녀가 앱을 실행하지 않을 가능성이 높은데도 자녀의 기기에서 코드가 실행되도록 하는 것이 어려운 부분이다.</p>
<p>스크린 타임 API의 경우, 기기 활동으로 백그라운드 코드 실행을 수행할 수 있는 새로운 방법을 만들었다.</p>
<p>기기 활동 확장은 나머지 스크린 타임 API와 상호 작용하는 주요 방법이 될 것이다.</p>
<h3 id="✨-2-shield-discouraged-apps">✨ 2. Shield discouraged apps</h3>
<p>반복되는 일정에 따라 보호자가 선택한 사용 금지 앱을 보호</p>
<p>자녀의 기기에서 앱이 실행되지 않고있어도, 보호자가 금지한 앱이 실행되지 않도록 스케줄을 설정하여 반복적으로 작동하게 하는 기능이다.</p>
<p>이 Extension을 구현하려면 <code>DeviceActivityMonitor</code>를 기본 클래스로 서브 클래싱 하여 모니터를 만들어주자.</p>
<pre><code class="language-swift">// EXTENSION: Create a DeviceActivityMonitor

class MyMonitor: DeviceActivityMonitor {
        override func intervalDidStart(for activity: DeviceActivityName) {
                super.intervalDidStart(for: activity)
        }
        override func intervalDidEnd(for activity: DeviceActivityName) {
                super.intervalDidEnd(for: activity)
        }
}</code></pre>
<p>이 함수들은 내 일정이 시작되고 종료된 후 기기를 처음 사용할 때 호출된다.</p>
<p>이제 <code>DeviceActivityName</code>과 <code>DeviceActivitySchedule</code>을 만들어야 한다.</p>
<pre><code class="language-swift">// APP: Monitor a DeviceActivitySchedule

import DeviceActivity

// 1. 활동을 참조할 수 있는 이름 생성
extension DeviceActivityName {
        static let daily = Self(&quot;daily&quot;)
}

// 2. 활동을 모니터링할 시간 범위 설정
let schedule = DeviceActivitySchedule(
        intervalStart: DateComponents(hour: 0, minute: 0),
        intervalEnd: DateComponents(hour: 23, minute: 59),
        repeat: true
)

// 3. center를 통해 방금 생성한 이름과 일정으로 `startMonitoring` 호출
let center = DeviceActivityCenter()
try center.startMonitoring(.daily, during: schedule)</code></pre>
<p>위 코드를 통해 일정이 시작되고 끝날 때마다 <code>MyMonitor</code>가 활동 이름(<code>DeviceActivityName</code>)과 함께 호출된다.</p>
<pre><code class="language-swift">// APP: Choose the Apps to Discourage

import FamilyControls
import SwiftUI

@StateObject var model = MyModel()
@State var isPresented = false

var body: some View {
        Button(&quot;Select Apps to Discourage&quot;) {
                isPresented = true
        }
        .familyActivityPicker(isPresented: $isPresented, selection: $model.selectionTpDiscourage)
}</code></pre>
<p>FamilyControls 프레임워크에는 작업을 위한 SwiftUI 요소인 <code>familyActivityPicker</code> (가족 활동 선택기)가 있다.</p>
<p>기본 앱의 UI에서 이 피커를 표시하고 보호자가 앱 or 웹을 카테고리 목록에서 선택한다.</p>
<p>이 피커의 반환값인 불투명 토큰(?)을 사용하여 제한 설정을 할 수 있다.</p>
<p>버튼을 통해 불러온 피커는 반환값을 모델의 속성에 바인딩한다.(<code>$model.selectionTpDiscourage</code>)</p>
<pre><code class="language-swift">// EXTENSION: Shied the Discouraged Apps

import DeviceActivity
import ManagedSettings // 추가

class MyMonitor: DeviceActivityMonitor {
        let store = ManagedSettingsStore() // 추가

        override func intervalDidStart(for activity: DeviceActivityName) {
                super.intervalDidStart(for: activity)

                let model = MyModel() // 모델 인스턴스
                let applicationos = model.selectionToDiscourage.applications // 선택된 앱 List

                store.shield.applications = applications.isEmpty ? nil : applications // store 등록
        }

        override func intervalDidEnd(for activity: DeviceActivityName) {
                super.intervalDidEnd(for: activity)

                store.shield.applications = nil
        }
}</code></pre>
<pre><code class="language-swift">// APP: Adding a DeviceActivityEvent
import DeviceActivity

extension DeviceActivityName {
        static let daily = Self(&quot;daily&quot;)
}

extension DeviceActivityEvent.Name { // 이벤트 이름 등록
        static let encouraged = Self(&quot;encouraged&quot;)
}

let schedule = DeviceActivitySchedule(
        intervalStart: DateComponents(hour: 0, minute: 0),
        intervalEnd: DateComponents(hour: 23, minute: 59),
        repeat: true
)

let model = MyModel()
let events = [DeviceActivityEvent.Name: DeviceActivityEvent] = [
        .encouraged: DeviceActivityEvent( // 이벤트 이름에 맞는 이벤트 생성
                    applications: model.selectionToEncourage.applicationTokens
                    threshold: DataComponents(minute: minutes)
        )
]

let center = DeviceActivityCenter()
try center.startMonitoring(.daily, during: schedule, events: events) // 이벤트 등록</code></pre>
<h3 id="✨-3-remove-shields-for-meeting-goal">✨ 3. Remove shields for meeting goal</h3>
<p>권장 앱 사용 시간을 충분히 누적한 다음 차단을 해제하는 방법</p>
<pre><code class="language-swift">// EXTENSION: Implement theh Threshold Function
import DeviceActivity
import ManagedSettings

class MyMonitor: DeviceActivityMonitor {
        let store = ManagedSettingStore() // store 인스턴스
        ...

        // 허용된 앱의 사용량이 누적되었을 때 호출됨
        override func eventDidReachThreshold(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
                super.eventDidReachThreshold(event, activity: activity)

                store.shield.applications = nil // 해당 이벤트가 충족되면 제한 해제
        }
}</code></pre>
<h3 id="✨-4-customize-the-shields">✨ 4. Customize the shields</h3>
<p>앱의 브랜딩과 기능에 맞게 shields를 커스텀 가능</p>
<ul>
<li>Material</li>
<li>Title</li>
<li>Icon</li>
<li>Body</li>
<li>primaryButton → Ask for Access</li>
<li>secondaryButton → Open App</li>
<li>Completion Handler</li>
</ul>
<pre><code class="language-swift">// EXTENSION: Create a ShieldConfigurationProvider

import ManagedSettings
import ManagedSettingsUI

class MyShieldConfiguration: ShieldConfigurationProvider {
        override func configuration(for application: Application) -&gt; ShieldConfigutaion {
                return ShieldConfiguration(
                        backgroundEffect: ...
                        backgroundColor: ...
                        icon: ...
                        title: ShieldConfiguration.Label(
                                text: ...
                                color: ...
                        ),
                        subtitle: ShieldConfiguration.Label(
                                text: ...
                                color: ...
                        )
                )
        }
}</code></pre>
<p>위 함수를 통해 Block 되었을 때의 화면을 커스텀할 수 있다.</p>
<pre><code class="language-swift">// EXTENSION: Create a ShieldActionHandler

import ManagedSettings

class MyShieldActionExtension: ShieldActionHandler {
        override func handle(action: ShieldAction,
                                                     for application: Application,
                                                     completionHandler:
                                @escaping (ShieldActionResponse) -&gt; Void {
                switch action {
                case .primaryButtonPressed:
                        completionHandler(.defer)
                case .secondaryButtonPressed:
                        completionHandler(.close)
                @unknown default:
                        fatalError()
                }
        }
}</code></pre>
<p>위 함수를 통해 커스텀 핸들러를 지정할 수 있다.</p>
<hr>
<h2 id="📝-회고">📝 회고</h2>
<p>현재 진행중인 Tublock 앱을 완성하기 위해 필요한 Screen Time API!
위 내용에서는 가족 공유가 사용된 예시만 있어서 없는 경우는 내가 직접 만들어봐야한다.
이 API를 사용하여 앱이 완성되면 어떻게 사용했는지도 문서화시켜 공유해볼 예정이다.</p>
<p>yeni가 초대해주신 WWDC 스터디 덕분에 한 주에 4개씩 공부할 수 있게됐다. 헤헤😁</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이진탐색] 떡볶이 떡 만들기]]></title>
            <link>https://velog.io/@debby_/%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89-%EB%96%A1%EB%B3%B6%EC%9D%B4-%EB%96%A1-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@debby_/%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89-%EB%96%A1%EB%B3%B6%EC%9D%B4-%EB%96%A1-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 16 Jan 2023 23:24:20 GMT</pubDate>
            <description><![CDATA[<h2 id="알고리즘-스터디">알고리즘 스터디</h2>
<p>이번엔 이진탐색 챕터를 공부하기로 했다.
4명 중 2명씩 나누어 2번 3번 실전 문제를 풀기로 했다.
나는 3번 문제에 당첨되어 파라메트릭 서치에 대해 배우게 됐다!</p>
<hr>
<h2 id="이진탐색에-대한-간략한-소개">이진탐색에 대한 간략한 소개</h2>
<p>이진탐색이란 이미 정렬되어있는 데이터 안에서 특정 값을 찾아내기 위해 반으로 나눠가며 찾는 탐색 알고리즘이다.</p>
<p>나는 CS50강의에서 <code>전화번호부 찾기</code>에 빗대어 설명하는 것을 들었다.
인덱스만으로 찾고자 하는 값에 금방 접근할 수 있으며,
찾고자 하는 값과 인덱스를 비교하여 앞으로 탐색할지,
뒤로 탐색할지 선택하고 반복하는 것 만으로 정보를 얻을 수 있기 때문이다.</p>
<p>(물론 이진탐색은 기본적으로 중간값을 기준으로 탐색해나가기 때문에 완벽하게 전화번호부 찾기 알고리즘이라고 단정지을 수는 없다.)</p>
<h2 id="이진탐색의-원리">이진탐색의 원리</h2>
<p>기본 원리는 입력된 정보의 반을 나누어 각각의 절반의 값에 더 근접한 쪽을 찾는 것이다. 즉 시작과 중간과 끝을 계속 바꿔가며 범위를 좁혀나간다.</p>
<h2 id="이진탐색의-구현">이진탐색의 구현</h2>
<ol>
<li>배열의 중간 값을 찾는다</li>
<li>찾고자 하는 타겟과 일치한다면 즉시 종료
2-1. 타겟보다 작다면 중간 값을 끝으로 설정하여 다시 탐색
2-2. 타겟보다 크다면 중간 값을 시작으로 설정하여 다시 탐색</li>
<li>위 과정을 반복한다</li>
</ol>
<hr>
<h2 id="파라메트릭-서치에-대한-간략한-소개">파라메트릭 서치에 대한 간략한 소개</h2>
<p>이진탐색 중 중 파라메트릭 서치 알고리즘은 <code>최적화 문제를 결정 문제로 바꾸어 해결</code>하는 과정이다. 즉, 많은 데이터를 가지고 최적화가 필요한 문제라면 이 방법을 고려해야한다.</p>
<h2 id="파라메트릭-서치의-원리">파라메트릭 서치의 원리</h2>
<ol>
<li>구하고자 하는 값을 얻을 때 시도해야하는 방법 정하기</li>
<li>그 값을 변경해가며 최적해인지 확인하기</li>
</ol>
<h3 id="🌀-뭐라는거야">🌀 뭐라는거야</h3>
<p>이건 문제를 보면서 설명하는게 좋을 것 같다.</p>
<hr>
<h2 id="문제-떡볶이-떡-만들기">[문제] 떡볶이 떡 만들기</h2>
<blockquote>
<p>동빈이네 떡볶이 떡은 길이가 일정하지 않다.
대신에 한 봉지 안에 들어가는 떡의 총 길이는 절단기로 잘라서 맞춰준다.
손님이 요청한 총 길이가 M일때, 적어도 M만큼의 떡을 얻기 위해 절단기에 설정할 수 있는 높이의 최댓값을 구하는 프로그램을 작성하시오.</p>
</blockquote>
<h3 id="입력조건">입력조건</h3>
<ul>
<li>첫째 줄에 떡의 개수 N과 요청한 떡의 길이 M이 주어진다. (1 &lt;= N &lt;= 1000000, 1 &lt;= M &lt;= 2000000000)</li>
<li>둘째 줄에는 떡의 개별 높이가 주어진다. 떡 높이의 총합은 항상 M 이상이므로, 손님은 필요한 양만큼 떡을 사갈 수 있다. 높이는 10억보다 작거나 같은 양의 정수 또는 0이다.</li>
</ul>
<h3 id="출력조건">출력조건</h3>
<p>적어도 M만큼의 떡을 집에 가져가기 위해 절단기에 설정할 수 있는 높이의 최댓값을 출력한다.</p>
<blockquote>
<p>입력 예시
<code>4 6</code></p>
</blockquote>
<blockquote>
<p>출력 예시
<code>19 15 10 17</code></p>
</blockquote>
<h2 id="🌀-풀이">🌀 풀이</h2>
<p>파라메트릭 서치의 원리를 들어 예를 설명해보자면</p>
<ol>
<li>구하고자 하는 값 : 6cm</li>
<li>떡의 개수 : 4개</li>
<li>시도해야하는 방법 : 절단기 높이 바꾸기</li>
</ol>
<h3 id="그럼-이걸-가지고-뭘-해야하나">그럼 이걸 가지고 뭘 해야하나?</h3>
<ol>
<li>절단기 높이를 계속 바꿔가며 떡을 자른다.</li>
<li>잘라진 떡의 길이가 6cm인지 확인한다.
2-1. 6cm보다 크다면? <code>절단기의 높이를 더 높게 설정</code>
2-2. 6cm보다 작다면? <code>절단기의 높이를 더 낮게 설정</code></li>
<li>위 과정을 반복한다.</li>
</ol>
<p>이진탐색을 요약한 과정과 유사하다.</p>
<h2 id="코드">코드</h2>
<pre><code class="language-swift">let input = readLine()!.split(separator: &quot; &quot;).map { Int(String($0))! }
let (n, target) = (input[0], input[1]) // 떡 개수 : n, 요청한 길이 : m
let array = readLine()!.split(separator: &quot; &quot;).map { Int(String($0))! }

print(parametricSearch(array, target))

func parametricSearch(_ array: [Int], _ target: Int) -&gt; Int {
    var result = 0
    var start = 0
    var end = array.max()!

    while start &lt;= end {
        var total = 0
        let mid = (start + end) / 2

        for ddeok in array {
            if ddeok &gt; mid { // 떡이 지금 자를 높이보다 크다면
                total += ddeok - mid // 잘라진 떡을 누적한다
            }
        }
        if total &lt; target {     // 누적된 떡의 양이 부족하다면
            end = mid - 1       // 높이를 더 낮게 설정한다 (떡이 더 많이 잘리도록)
        } else {                // 누적된 떡의 양이 충분하다면
            result = mid        // 결과를 초기화하고 (최적해)
            start = mid + 1     // 높이를 더 높게 설정한다 (떡이 덜 잘리도록)
        }
    }
    return result
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[정렬 알고리즘] 두 배열의 원소 교체 4. 계수 정렬로 풀어보기]]></title>
            <link>https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4-4.-%EA%B3%84%EC%88%98-%EC%A0%95%EB%A0%AC%EB%A1%9C-%ED%92%80%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4-4.-%EA%B3%84%EC%88%98-%EC%A0%95%EB%A0%AC%EB%A1%9C-%ED%92%80%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 16 Jan 2023 04:19:58 GMT</pubDate>
            <description><![CDATA[<h2 id="📝-계수-정렬-counting-sort">📝 계수 정렬 (Counting Sort)</h2>
<p>가장 작은 데이터와 큰 데이터를 모두 담을 수 있는 빈 배열을 생성한 후,
각 데이터에 맞는 인덱스에 몇개나 있는지 Count하여 새로운 배열을 만드는 정렬이다.</p>
<h3 id="1️⃣-순서">1️⃣ 순서</h3>
<pre><code class="language-swift">1. 가장 작은 데이터와 가장 큰 데이터의 범위를 담을 배열을 생성한다.
2. 배열을 1번 순회하여 아까 만든 배열의 index에 count 한다.</code></pre>
<h3 id="2️⃣-코드">2️⃣ 코드</h3>
<pre><code class="language-swift">var array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]

func countSort(_ array: inout [Int]) -&gt; [Int] {
    let maxValue = array.max() ?? 0                             // 1. 가장 큰 값을 구한다
    var countArray = [Int](repeating: 0, count: maxValue + 1)   // 2. 가장 큰 값 + 1 크기의 배열을 만든다
    for value in array {
        countArray[value] += 1                                  // 3. 순회하며 해당 값이 있는 index에 count한다
    }
    var sortedArray = [Int]()
    for (index, count) in countArray.enumerated() {             // 4. count만큼 index의 값을 append하여 정렬한다
        for _ in 0..&lt;count {
            sortedArray.append(index)
        }
    }
    return sortedArray
}

print(countSort(&amp;array))</code></pre>
<h3 id="3️⃣-정리">3️⃣ 정리</h3>
<ul>
<li>안정성 : Stable Sort<ul>
<li>각 원소끼리 비교하지 않기 때문에 안정성을 가진다.</li>
</ul>
</li>
<li>제자리성 : In-place<ul>
<li>기존 배열 이외의 메모리를 거의 사용하지 않는다.</li>
</ul>
</li>
<li>시간 복잡도<ul>
<li>최선 : 𝛰(𝛮) <code>𝛮이 𝛫보다 클 경우</code></li>
<li>최악 : 𝛰(𝛮 + 𝛫)</li>
</ul>
</li>
<li>장점 : 퀵 정렬의 평균 속도보다 빠를 수 있음. (단, 데이터의 차이가 1,000,000 미만인 경우에 적합)</li>
<li>단점 : 추가적인 메모리가 많이 필요할 수 있다.</li>
</ul>
<hr>
<h2 id="문제-두-배열의-원소-교체">[문제] 두 배열의 원소 교체</h2>
<p>N개의 원소로 이뤄진 두 배열 A, B가 있다. 최대 K번의 바꿔치기 연산을 할 수 있다. 바꿔치기 연산을 수행하여 만들수 있는 배열 A의 원소 합의 최댓값을 구하여라.</p>
<p>입력 조건</p>
<ol>
<li>첫째줄에는 N, K가 주어진다. (1 ≤ K ≤ N ≤ 100,000)</li>
<li>둘째줄부터 각각 배열 A, B의 원소들이 주어진다. 모든 원소는 10,000,000보다 작은 자연수이다.</li>
</ol>
<blockquote>
<p>입력 예</p>
</blockquote>
<pre><code>5 3
1 2 5 4 3
5 5 6 6 5</code></pre><blockquote>
<p>출력 예</p>
</blockquote>
<pre><code>26</code></pre><h2 id="🌀-풀이">🌀 풀이</h2>
<pre><code class="language-swift">// 계수 정렬
func ascendingCountSort(_ array: inout [Int]) -&gt; [Int] {
    let maxValue = array.max() ?? 0
    var countArray = [Int](repeating: 0, count: maxValue + 1)
    for value in array {
        countArray[value] += 1
    }
    var sortedArray = [Int]()
    for (index, count) in countArray.enumerated() {
        for _ in 0..&lt;count {
            sortedArray.append(index)
        }
    }
    return sortedArray
}

func descendingCountSort(_ array: inout [Int]) -&gt; [Int] {
    let maxValue = array.max() ?? 0
    var countArray = [Int](repeating: 0, count: maxValue + 1)
    for value in array {
        countArray[value] += 1
    }
    var sortedArray = [Int]()
    for (index, count) in countArray.enumerated().reversed() {
        for _ in 0..&lt;count {
            sortedArray.append(index)
        }
    }
    return sortedArray
}</code></pre>
<p>계수 정렬은 데이터의 범위가 제한적일 때 효과적인데, 현재 문제 기준으로는 적합하지 않다.
다만, 주어진 예시의 범위가 좁기 때문에 임의로 0부터 7까지로 제한한 후 적용시켜 보았다.</p>
<p>입력 배열에서 각 값이 몇 개 있는지 계수하여 각 값을 인덱스로 하는 countArray를 만들고, 그 다음 countArray를 이용하여 정렬된 배열을 만든다.</p>
<p>주의할 점은 입력 배열에서 최대 값이 maxValue라 가정하고 countArray를 생성하므로 countArray에 없는 값이 입력되면 이 함수는 제대로 작동하지 않는다.</p>
<h3 id="드디어">드디어...!</h3>
<p>정렬 시리즈가 끝이 났다 🥲🥲🥲
하지만 ... 다음은 이진 탐색 ... 갈길이 멀다.</p>
<hr>
<p>🫥 글의 복잡성을 줄이기 위해 <code>공통 input</code>과, <code>K번 바꾸기 연산</code>은 첫 글에만 명시했습니다! 궁금하다면 <a href="https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4">여기를 눌러주세요.</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[정렬 알고리즘] 두 배열의 원소 교체 3. 퀵 정렬로 풀어보기]]></title>
            <link>https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4-3.-%ED%80%B5-%EC%A0%95%EB%A0%AC%EB%A1%9C-%ED%92%80%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4-3.-%ED%80%B5-%EC%A0%95%EB%A0%AC%EB%A1%9C-%ED%92%80%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 16 Jan 2023 04:09:21 GMT</pubDate>
            <description><![CDATA[<h2 id="📝--퀵-정렬-quick-sort">📝  퀵 정렬 (Quick Sort)</h2>
<p><code>기준(pivot)을 설정</code>하고,
그 기준보다 큰지 작은지 판별하여 위치를 바꾸는 <code>분할 정복 알고리즘</code>이다.
분할 방법이 여러가지 존재하기 때문에 반드시 먼저 명시해야한다.
이번엔 첫번째 데이터를 기준으로 정하는 <code>호어 분할(Hoare Partition)</code>로 설정하여 진행해보자.</p>
<h3 id="1️⃣-순서">1️⃣ 순서</h3>
<pre><code class="language-swift">var data = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]
1. 탐색할 범위를 설정한다.
2. 피봇을 설정한다.
    data[1] 부터 탐색 -&gt; data[2] = 9
3. 값을 비교하여 피봇 보다 큰지 작은지 여부를 판단한다.
    data[9] 부터 탐색 -&gt; data[8] = 4
4. 피봇보다 큰 데이터와 작은 데이터의 위치를 Swap한다.
    data[2] = 4, data[8] = 9
5. 위의 과정을 재귀로 반복하여 pivot의 왼쪽과 오른쪽을 분할하여 계속 정렬해나간다.</code></pre>
<h3 id="2️⃣-코드">2️⃣ 코드</h3>
<pre><code class="language-swift">var array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]

func quickSort(_ array: inout [Int], start: Int, end: Int) {
    print(&quot;&lt;&lt; quickSort() 실행 &gt;&gt; &quot;)
    print(&quot;0. start: \(start), end: \(end)&quot;)
    print(&quot;실행 전 : \(array)&quot;)
    print()
    if start &gt;= end { return }          // -  원소가 1개인 경우 종료

    let pivot = start                   // 0. 기준  : 첫 번째 원소
    var left = start + 1                // 1. 왼쪽  : 시작위치 + 1
    var right = end                     // 2. 오른쪽 : 맨 끝
    print(&quot;&gt;&gt; pivot : \(pivot)&quot;)
    print(&quot;&gt;&gt; left : \(left)&quot;)
    print(&quot;&gt;&gt; right : \(right)&quot;)
    print()

    while left &lt;= right {               // 3. 왼쪽 인덱스가 오른쪽 인덱스와 작거나 같다면 반복
        print(&quot;&gt;&gt; array[left] : \(array[left])&quot;)
        print(&quot;&gt;&gt; array[right] : \(array[right])&quot;)
        print()
        while (left &lt;= end) &amp;&amp; (array[left] &lt;= array[pivot]) {
            left += 1                   // 4. 왼쪽부터 기준보다 큰 원소 찾기
            print(&quot;4. left += 1 &gt;&gt; \(left)&quot;)
        }
        while (right &gt; start) &amp;&amp; (array[right] &gt;= array[pivot]) {
            right -= 1                  // 5. 오른쪽부터 기준보다 작은 원소 찾기
            print(&quot;5. right -= 1 &gt;&gt; \(right)&quot;)
        }
        if left &gt; right {
            array.swapAt(right, pivot)  // 6. 엇갈렸다면 작은 데이터와 pivot 교체
            print(&quot;6. left가 right보다 커서 right, pivot 스왑 = \(pivot 기준 왼쪽은 작고, 오른쪽은 크도록 정렬 완료)&quot;)
        } else {
            array.swapAt(left, right)   // 7. 엇갈리지 않았다면 작은 데이터와 큰 데이터 교체
        }
        print(&quot;&gt;&gt; 분할 탐색 시작하기 전 : \(array)&quot;)
        print()
        print(&quot;------------------------------------------------------------&quot;)
        print()
        quickSort(&amp;array, start: start, end: right - 1) // 8. 나머지 왼쪽 탐색
        quickSort(&amp;array, start: right + 1, end: end)   // 9. 나머지 오른쪽 탐색
    }
}

quickSort(&amp;array, start: 0, end: array.count - 1)
print(array)</code></pre>
<ul>
<li>손으로 그리다가 너무 오래걸려서 그냥 print를 다 찍어버렸다.</li>
<li>아마 눈으로 보는게 가장 빠를 것 같으니 그대로 실행해보길 권장한다.</li>
</ul>
<h3 id="3️⃣-정리">3️⃣ 정리</h3>
<ul>
<li>안정성 : Unstable Sort<ul>
<li>동일한 원소에 대한 우선순위가 유지되지 않는다.</li>
</ul>
</li>
<li>제자리성 : In-place<ul>
<li>기존 배열 이외의 메모리를 거의 사용하지 않는다.</li>
</ul>
</li>
<li>시간 복잡도<ul>
<li>최선 : 𝛰(𝛮 𝑙𝑜𝑔 𝛮)</li>
<li>최악 : 𝛰(𝛮 ²)</li>
</ul>
</li>
</ul>
<hr>
<h2 id="문제-두-배열의-원소-교체">[문제] 두 배열의 원소 교체</h2>
<p>N개의 원소로 이뤄진 두 배열 A, B가 있다. 최대 K번의 바꿔치기 연산을 할 수 있다. 바꿔치기 연산을 수행하여 만들수 있는 배열 A의 원소 합의 최댓값을 구하여라.</p>
<p>입력 조건</p>
<ol>
<li>첫째줄에는 N, K가 주어진다. (1 ≤ K ≤ N ≤ 100,000)</li>
<li>둘째줄부터 각각 배열 A, B의 원소들이 주어진다. 모든 원소는 10,000,000보다 작은 자연수이다.</li>
</ol>
<blockquote>
<p>입력 예</p>
</blockquote>
<pre><code>5 3
1 2 5 4 3
5 5 6 6 5</code></pre><blockquote>
<p>출력 예</p>
</blockquote>
<pre><code>26</code></pre><h2 id="🌀-풀이">🌀 풀이</h2>
<p>글의 복잡성을 줄이기 위해 공통 input과, K번 바꾸기 연산은 첫 글에만 명시했습니다. 궁금하다면 <a href="https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4">여기를 눌러주세요.</a></p>
<pre><code class="language-swift">// 퀵 정렬
private func ascendingQuickSort(_ array: inout [Int], start: Int, end: Int) {
    if start &gt;= end { return }

    let pivot = start
    var left = start + 1
    var right = end

    while left &lt;= right {
        while (left &lt;= end) &amp;&amp; (array[left] &lt;= array[pivot]) {
            left += 1
        }
        while (right &gt; start) &amp;&amp; (array[right] &gt;= array[pivot]) {
            right -= 1
        }
        if left &gt; right {
            array.swapAt(right, pivot)
        } else {
            array.swapAt(left, right)
        }
        ascendingQuickSort(&amp;array, start: start, end: right - 1)
        ascendingQuickSort(&amp;array, start: right + 1, end: end)
    }
}

private func descendingQuickSort(_ array: inout [Int], start: Int, end: Int) {
    if start &gt;= end { return }

    let pivot = start
    var left = start + 1
    var right = end

    while left &lt;= right {
        while (left &lt;= end) &amp;&amp; (array[left] &gt;= array[pivot]) {
            left += 1
        }
        while (right &gt; start) &amp;&amp; (array[right] &lt;= array[pivot]) {
            right -= 1
        }
        if left &gt; right {
            array.swapAt(right, pivot)
        } else {
            array.swapAt(left, right)
        }
        descendingQuickSort(&amp;array, start: start, end: right - 1)
        descendingQuickSort(&amp;array, start: right + 1, end: end)
    }
}</code></pre>
<p>이번엔 그냥 함수자체를 나누어주었다!
이제 마지막 계수 정렬로 넘어가보자..!</p>
<hr>
<p>🫥 글의 복잡성을 줄이기 위해 <code>공통 input</code>과, <code>K번 바꾸기 연산</code>은 첫 글에만 명시했습니다! 궁금하다면 <a href="https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4">여기를 눌러주세요.</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[정렬 알고리즘] 두 배열의 원소 교체 2. 삽입 정렬로 풀어보기]]></title>
            <link>https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4-2.-%EC%82%BD%EC%9E%85-%EC%A0%95%EB%A0%AC%EB%A1%9C-%ED%92%80%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4-2.-%EC%82%BD%EC%9E%85-%EC%A0%95%EB%A0%AC%EB%A1%9C-%ED%92%80%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 16 Jan 2023 03:55:57 GMT</pubDate>
            <description><![CDATA[<h2 id="📝--삽입-정렬-insertion-sort">📝  삽입 정렬 (Insertion Sort)</h2>
<p>현재 데이터를 적절한 위치에 삽입하는 정렬이다.
자신보다 왼쪽에 있는 데이터는 정렬되어있다고 가정한다.</p>
<h3 id="1️⃣-순서">1️⃣ 순서</h3>
<pre><code class="language-swift">var data = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]
1. 1번째 데이터가 정렬되어 있다고 판단하고 2번째 데이터부터 탐색한다.
    data[0] = 7 &lt;-&gt; data[1] = 5
2. 이 때, 0번째 인덱스의 왼쪽 or 오른쪽 어디에 삽입될 지 그것만 판단한다.
    data[0] = 5가 삽입된다면 자연스럽게 data[1] = 7이 된다.
3. 나머지도 반복한다.
    data[2] = 9는 현재 data[1]보다 크기 때문에 그대로 있는다.
    data[3] = 0은 앞에있는 5, 7, 9 보다 작기 때문에 data[0]번째에 삽입한다.
    data[4] = 1, data[1]에 삽입한다.
    .
    .</code></pre>
<h3 id="2️⃣-코드">2️⃣ 코드</h3>
<pre><code class="language-swift">var array = [0, 5, 9, 7, 3, 1, 6, 2, 4, 8]
for i in 1 ..&lt; array.count {                    // 1. 1부터 배열 끝까지 탐색
    for j in stride(from: i, to: 0, by: -1) {   // 2. i부터 0까지 -1씩, 왼쪽 탐색
        if array[j] &lt; array[j - 1] {            // 3. 현재 값이 왼쪽 값보다 작다면 이동
            array.swapAt(j, j - 1)
        } else {                                // 4. 왼쪽 값이 나보다 작다면 멈춤
            break
        }
    }
}</code></pre>
<h3 id="3️⃣-정리">3️⃣ 정리</h3>
<ul>
<li>안정성 : Stable Sort<ul>
<li>비교대상의 원소가 새로운 원소보다 큰 경우에만 움직이므로 동일한 값의 위치는 변하지 않는다.</li>
</ul>
</li>
<li>제자리성 : In-place<ul>
<li>기존 배열 이외의 메모리를 거의 사용하지 않는다.</li>
</ul>
</li>
<li>시간 복잡도<ul>
<li>최선 : 𝛰(𝛮)</li>
<li>최악 : 𝛰(𝛮 ²)</li>
<li>최악의 경우 선택 정렬과 비슷하나 <code>거의 정렬 된 문제</code> 라면 퀵 정렬보다 빠르다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="문제-두-배열의-원소-교체">[문제] 두 배열의 원소 교체</h2>
<p>N개의 원소로 이뤄진 두 배열 A, B가 있다. 최대 K번의 바꿔치기 연산을 할 수 있다. 바꿔치기 연산을 수행하여 만들수 있는 배열 A의 원소 합의 최댓값을 구하여라.</p>
<p>입력 조건</p>
<ol>
<li>첫째줄에는 N, K가 주어진다. (1 ≤ K ≤ N ≤ 100,000)</li>
<li>둘째줄부터 각각 배열 A, B의 원소들이 주어진다. 모든 원소는 10,000,000보다 작은 자연수이다.</li>
</ol>
<blockquote>
<p>입력 예</p>
</blockquote>
<pre><code>5 3
1 2 5 4 3
5 5 6 6 5</code></pre><blockquote>
<p>출력 예</p>
</blockquote>
<pre><code>26</code></pre><h2 id="🌀-풀이">🌀 풀이</h2>
<pre><code class="language-swift">// 삽입 정렬
func insertionSort(_ array: inout [Int], _ oper: String) {
    if (oper == &quot;&lt;&quot;) || (oper == &quot;&quot;) { // 오름차순
        for i in 1..&lt;array.count {
            var j = i
            while (j &gt; 0) &amp;&amp; (array[j] &lt; array[j-1]) {
                array.swapAt(j, j-1)
                j -= 1
            }
        }

    } else if oper == &quot;&gt;&quot; { // 내림차순
        for i in 1..&lt;array.count {
            var j = i
            while (j &gt; 0) &amp;&amp; (array[j] &gt; array[j-1]) {
                array.swapAt(j, j-1)
                j -= 1
            }
        }
    }
}

insertionSort(&amp;a, &quot;&lt;&quot;)
insertionSort(&amp;b, &quot;&gt;&quot;)
print(&quot;답 : \(exchange(&amp;a, &amp;b, k).reduce(0, +))&quot;)</code></pre>
<p>여기서도 &quot;&lt;&quot;와 &quot;&gt;&quot;를 이용하여 오름차순, 내림차순을 구분해주었다.
코드가 예쁘진 않지만 🥲 일단 다음 퀵 정렬로 넘어가자..! 🧐</p>
<hr>
<p>🫥 글의 복잡성을 줄이기 위해 <code>공통 input</code>과, <code>K번 바꾸기 연산</code>은 첫 글에만 명시했습니다! 궁금하다면 <a href="https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4">여기를 눌러주세요.</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[정렬 알고리즘] 두 배열의 원소 교체 1. 선택 정렬로 풀어보기]]></title>
            <link>https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4</link>
            <guid>https://velog.io/@debby_/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%EB%B0%B0%EC%97%B4%EC%9D%98-%EC%9B%90%EC%86%8C-%EA%B5%90%EC%B2%B4</guid>
            <pubDate>Thu, 12 Jan 2023 00:10:33 GMT</pubDate>
            <description><![CDATA[<h2 id="알고리즘-스터디">알고리즘 스터디</h2>
<p>이코테 책에서는 python의 sort함수를 사용하여 비교적 문제 난이도가 낮았다.
따라서 조금 더 알찬 스터디를 위해 선택, 삽입, 퀵, 계수 정렬을 이용하여 문제를 풀어보기로 했다.
<del>( 분량 조절 실패로 인해 글을 4개로 나누게 되었다. )</del></p>
<hr>
<h2 id="📝-선택-정렬-selection-sort">📝 선택 정렬 (Selection Sort)</h2>
<p>매번 가장 작은 원소를 찾아 순차적으로 정렬하는 방법이다.</p>
<h3 id="1️⃣-순서">1️⃣ 순서</h3>
<pre><code class="language-swift">var data = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]
1. 배열을 순회하여 가장 작은 데이터를 선택한다. data[3] = 0
2. 맨 앞에 있는 요소와 바꾼다.
    data[0] = 7 &lt;-&gt; data[3] 0
    data = [0, 5, 9, 7, 3, 1, 6, 2, 4, 8]
3. 이제 0번째 index의 정렬을 완료했으니 1번째 index의 값도 위의 방식으로 정렬한다.
4. 위 과정을 반복하여 배열 끝까지 정렬한다.</code></pre>
<h3 id="2️⃣-코드">2️⃣ 코드</h3>
<pre><code class="language-swift">var array = [0, 5, 9, 7, 3, 1, 6, 2, 4, 8]

for i in 0 ..&lt; array.count {            // 1. 왼쪽부터
    var minIdx = i                      // 2. 가장 작은 원소 찾기
    for j in i + 1 ..&lt; array.count {    // 3. 현재 위치 + 1부터 끝까지
        if array[minIdx] &gt; array[j] {   // 4. 더 작은 값이 있다면
            minIdx = j                  // 5. 더 작은 인덱스로 초기화
        }
        array.swapAt(i, minIdx)         // 6. 현재 위치와 더 작은 값의 위치 Swap
    }
}</code></pre>
<h3 id="3️⃣-정리">3️⃣ 정리</h3>
<ul>
<li>안정성 : Unstable Sort<ul>
<li>동일한 값을 가지는 원소의 본래 순서를 보장하지 않는다.</li>
</ul>
</li>
<li>제자리성 : In-place<ul>
<li>기존 배열 이외의 메모리를 거의 사용하지 않는다.</li>
</ul>
</li>
<li>시간 복잡도 : 𝛰(𝛮 ²)<ul>
<li>방법이나 코드는 단순하지만 <code>2중 반복문</code>으로 인해 비효율적인 편이다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="문제-두-배열의-원소-교체">[문제] 두 배열의 원소 교체</h2>
<p>N개의 원소로 이뤄진 두 배열 A, B가 있다. 최대 K번의 바꿔치기 연산을 할 수 있다. 바꿔치기 연산을 수행하여 만들수 있는 배열 A의 원소 합의 최댓값을 구하여라.</p>
<p>입력 조건</p>
<ol>
<li>첫째줄에는 N, K가 주어진다. (1 ≤ K ≤ N ≤ 100,000)</li>
<li>둘째줄부터 각각 배열 A, B의 원소들이 주어진다. 모든 원소는 10,000,000보다 작은 자연수이다.</li>
</ol>
<blockquote>
<p>입력 예</p>
</blockquote>
<pre><code>5 3
1 2 5 4 3
5 5 6 6 5</code></pre><blockquote>
<p>출력 예</p>
</blockquote>
<pre><code>26</code></pre><pre><code class="language-swift">// 공통 input
let input = readLine()!.split(separator: &quot; &quot;).map { Int(String($0))! }
let (n, k) = (input[0], input[1])
var a = readLine()!.split(separator: &quot; &quot;).map { Int(String($0))! }
var b = readLine()!.split(separator: &quot; &quot;).map { Int(String($0))! }</code></pre>
<pre><code class="language-swift">// 기본 라이브러리 사용
a.sort()
b.sort(by: &gt;)

for i in 0 ..&lt; k {
    if a[i] &lt; b[i] {
        a[i] = b[i]
        b[i] = a[i]
    } else {
        break
    }
}
print(a.reduce(0, +))</code></pre>
<p>그렇다. 책에서는 기본 라이브만 이용했기 때문에
정렬 챕터에 있는 모든 문제 난이도가 낮았다...
각 정렬로 풀어보기 전까지 이게 이렇게 오래 걸릴 줄 몰랐다...</p>
<pre><code class="language-swift">// k번 바꾸기 연산 - 문제 풀이 로직
func exchange(_ a: inout [Int], _ b: inout [Int], _ k: Int) -&gt; [Int] {
    for i in 0 ..&lt; k {
        if a[i] &lt; b[i] {
            a[i] = b[i]
            b[i] = a[i]
        } else {
            break
        }
    }
    return a
}</code></pre>
<pre><code class="language-swift">// 선택 정렬
func selectionSort(_ array: inout [Int], _ oper: String) {
    if (oper == &quot;&lt;&quot;) || (oper == &quot;&quot;) {
        for i in 0..&lt;array.count {
            var minIndex = i
            for j in i+1..&lt;array.count {
                if array[j] &lt; array[minIndex] {
                    minIndex = j
                }
            }
            if i != minIndex {
                array.swapAt(i, minIndex)
            }
        }
    } else if oper == &quot;&gt;&quot; {
        for i in 0..&lt;array.count {
            var minIndex = i
            for j in i+1..&lt;array.count {
                if array[j] &gt; array[minIndex] {
                    minIndex = j
                }
            }
            if i != minIndex {
                array.swapAt(i, minIndex)
            }
        }
    }
}

selectionSort(&amp;a, &quot;&lt;&quot;)
selectionSort(&amp;b, &quot;&gt;&quot;)
print(&quot;답 : \(exchange(&amp;a, &amp;b, k).reduce(0, +))&quot;)</code></pre>
<p>오름차순과 내림차순을 구분해주기 위해 &quot;&lt;&quot;, &quot;&gt;&quot;를 사용했다.
바로 이어서 삽입 정렬로 넘어가보자...!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DFS/BFS] 음료수 얼려 먹기]]></title>
            <link>https://velog.io/@debby_/DFSBFS-%EC%9D%8C%EB%A3%8C%EC%88%98-%EC%96%BC%EB%A0%A4-%EB%A8%B9%EA%B8%B0</link>
            <guid>https://velog.io/@debby_/DFSBFS-%EC%9D%8C%EB%A3%8C%EC%88%98-%EC%96%BC%EB%A0%A4-%EB%A8%B9%EA%B8%B0</guid>
            <pubDate>Tue, 10 Jan 2023 10:15:37 GMT</pubDate>
            <description><![CDATA[<h2 id="dfs-bfs에-대한-간략한-소개">DFS, BFS에 대한 간략한 소개</h2>
<p>깊이 우선 탐색(Depth-First Search, DFS)과 너비 우선 탐색(Breadth-First Search, BFS)은 두 가지 널리 쓰이는 그래프 탐색 알고리즘이다.
그래프 탐색의 목적은 그래프 내에서 목표 노드를 찾는 것이다. 이 두가지 모두 사용이 가능한 경우라도 각각의 장단점에 따라 선택하여 문제를 푸는 것이 중요하다.</p>
<h2 id="dfs">DFS</h2>
<p>DFS는 그래프를 깊게 탐색하는 알고리즘이다.
즉, <code>한놈만 팬다</code>는 느낌으로 깊게 들어가서 탐색하는 것!</p>
<p>탐색시 시작 노드로부터 방문하지 않은 노드로 가기 위해 <code>스택</code>을 사용한다.
만약 가장 마지막 깊이까지 방문했다면 <code>스택의 가장 위에 있는 노드로 돌아가</code> 스택의 다른 노드들을 방문한다.
DFS는 깊이 들어가기 때문에 목표 노드를 빠르게 찾을 수 있지만 메모리 사용량이 많이 든다는 단점이 있다.</p>
<h2 id="bfs">BFS</h2>
<p>BFS는 그래프를 넓게 탐색하는 알고리즘이다.
DFS와는 다르게 그래프를 탐색할 때 <code>큐</code>를 사용한다.
그래프에서 꺼낸 방문하지 않은 노드는 큐에 넣고 방문한 노드는 큐에서 제거된다.
BFS는 메모리 사용량이 DFS보다 적지만 목표 노드를 찾는데 시간이 더 오래 걸린다는 단점이 있다.</p>
<h2 id="결론">결론</h2>
<p><code>DFS</code>는 목표 노드를 <code>빠르게</code> 찾을 수 있지만 <code>메모리 사용량이 많이</code> 든다.
<code>BFS</code>는 <code>메모리 사용량</code>이 DFS보다 <code>적지만</code> 목표 노드를 찾는데 <code>시간이 더 오래</code> 걸린다.</p>
<hr>
<h2 id="문제-음료수-얼려-먹기">[문제] 음료수 얼려 먹기</h2>
<blockquote>
<p>N × M 크기의 얼음 틀이 있다.
0은 얼음칸, 1은 칸막이로 표시한다.
얼음칸끼리 상, 하, 좌, 우로 붙어 있는 경우 서로 연결되어 있는 것으로 간주한다.
주어진 얼음 틀에서 생성되는 총 아이스크림의 개수를 구하는 프로그램 작성하기.</p>
</blockquote>
<h3 id="입력-조건">입력 조건</h3>
<ul>
<li>첫 번째 줄에 얼음 틀의 세로 길이 N과 가로 길이 M이 주어진다. (1 &lt;= N, M &lt;= 1,000)</li>
<li>두 번째 줄부터 N + 1 번째 줄까지 얼음 틀의 형태가 주어진다.</li>
<li>이때 구멍이 뚫려있는 부분은 0, 그렇지 않은 부분은 1이다.</li>
</ul>
<h3 id="출력-조건">출력 조건</h3>
<p>한 번에 만들 수 있는 아이스크림의 개수를 출력한다.</p>
<blockquote>
<h3 id="입력-예시-1">입력 예시 1</h3>
</blockquote>
<pre><code>4 5
00110
00011
11111
00000</code></pre><blockquote>
<h3 id="출력-예시-1">출력 예시 1</h3>
</blockquote>
<pre><code>3</code></pre><blockquote>
<h3 id="입력-예시-2">입력 예시 2</h3>
</blockquote>
<pre><code>15 14
00000111100000
11111101111110
11011101101110
11011101100000
11011111111111
11011111111100
11000000011111
01111111111111
00000000011111
01111111111000
00011111111000
00000001111000
11111111110011
11100011111111
11100011111111</code></pre><blockquote>
<h3 id="출력-예시2">출력 예시2</h3>
</blockquote>
<pre><code>8</code></pre><hr>
<h2 id="풀이">풀이</h2>
<ol>
<li>2차원 배열을 만들어 그래프를 입력 받는다.</li>
<li>그래프의 모든 노드를 방문하여 <code>dfs</code>로 탐색한다.</li>
<li><code>dfs</code> 함수 내에서 범위 안에 있고 방문되지 않은 노드를 모두 탐색한다. =&gt; <code>if graph[x][y] == 0</code>
 이 때 방문처리를 진행한다. =&gt; <code>graph[x][y] = 1</code></li>
<li>범위밖으로 벗어나거나 주변에 <code>0</code>이 없다면 <code>return false</code>로 재귀 함수들을 종료한다.</li>
<li>즉 <code>dfs</code>함수가 <code>true</code>로 반환될 때 뭉쳐있는 한 덩어리를 모두 방문했으므로 <code>result += 1</code>로 count한다.</li>
</ol>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-swift">import Foundation

let input = readLine()!.split(separator: &quot; &quot;).map { Int(String($0))! }
let (n, m) = (input[0], input[1])

var graph = [[Int]]()
for _ in 0 ..&lt; n {
    graph.append(readLine()!.map { Int(String($0))! })
}

func dfs(_ x: Int, _ y: Int) -&gt; Bool {
    // 범위 벗어나면 종료
    if !isInRange(x, y) {
        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 // 주변이 다 막혀있다면 true 반환하고 종료
    }
    return false
}

func isInRange(_ x: Int, _ y: Int) -&gt; Bool {
    return (0..&lt;n ~= x) &amp;&amp; (0..&lt;m ~= y)
}

var result = 0

for i in 0 ..&lt; n {
    for j in 0 ..&lt; m {
        if dfs(i, j) {
            result += 1
        }
    }
}

print(result)
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[구현 알고리즘] 왕실의 나이트]]></title>
            <link>https://velog.io/@debby_/%EA%B5%AC%ED%98%84-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%99%95%EC%8B%A4%EC%9D%98-%EB%82%98%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@debby_/%EA%B5%AC%ED%98%84-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%99%95%EC%8B%A4%EC%9D%98-%EB%82%98%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Thu, 05 Jan 2023 04:00:35 GMT</pubDate>
            <description><![CDATA[<h2 id="구현-알고리즘에-대한-소개">구현 알고리즘에 대한 소개</h2>
<p>어떤 공식이 있다기 보다는 말 그대로 &quot;구현&quot;에 초점을 두는 것이다.
아이디어를 떠올리기는 쉽지만 막상 구현하려면 코드가 길어진다거나 하는 문제들!</p>
<p>보통 N x M 칸을 주고 상,하,좌,우로 움직이는 <code>시뮬레이션 문제</code> 또는
00시 00분 00초 단위의 <code>시간 계산같은 완전탐색</code>으로 풀어야하는 문제들이 있을 수 있다.</p>
<h2 id="구현-알고리즘의-중요성">구현 알고리즘의 중요성</h2>
<p>프로그래밍은 기본적으로 어떤 아이디어를 구체화 시켜 문제를 해결하는 것이다.
이 능력을 판별하기 가장 쉬운 문제유형이기에 대기업에서 복잡하고 어려운 구현 문제가 많이 출제 되는 듯 하다.</p>
<h2 id="결론">결론</h2>
<p>구현 알고리즘은 구체적인 실행과 효과적인 코드를 만드는 데 도움이 된다.
잘 풀기 위해서는 문제를 빠르게 파악하고 그에 알맞은 해결 방법을 도출하는 연습이 필요하다.
특히 풀이에 맞는 라이브러리, 적절한 함수를 많이 알고있으면 도움이 된다.</p>
<hr>
<h2 id="문제-왕실의-나이트">[문제] 왕실의 나이트</h2>
<blockquote>
<p>행복 왕국의 왕실 정원은 체스판과 같은 8 × 8 좌표 평면이다.
왕실 정원의 특정한 한 칸에 나이트가 서있다.
나이트는 L자 형태로만 이동할 수 있으며 정원 밖으로는 나갈 수 없다.
나이트는 특정 위치에서 다음과 같은 2가지 경우로 이동할 수 있다.</p>
</blockquote>
<pre><code>1. 수평으로 두 칸 이동한 뒤에 수직으로 한 칸 이동하기
2. 수직으로 두 칸 이동한 뒤에 수평으로 한 칸 이동하기</code></pre><p>이 때, 나이트가 이동할 수 있는 경우의 수를 출력하는 프로그램을 작성하라.
<code>행 위치</code>를 표현할 때는 <code>1부터 8</code>로,
<code>열 위치</code>를 표현할 때는 <code>a 부터 h</code>로 표현한다.</p>
<h3 id="입력-조건">입력 조건</h3>
<ul>
<li>첫째 줄에 8x8 좌표 평면상에서 현재 나이트가 위치한 곳의 좌표를 나타내는 두 문자로 구성된 문자열이 입력된다. 입력 문자는 a1 처럼 열과 행으로 이뤄진다.</li>
</ul>
<h3 id="출력-조건">출력 조건</h3>
<ul>
<li>첫째 줄에 나이트가 이동할 수 있는 경우의 수를 출력하시오.</li>
</ul>
<hr>
<h2 id="풀이">풀이</h2>
<ol>
<li>나이트가 움직일 수 있는 8가지 경우를 튜플 형태의 배열로 설정한다.</li>
<li>현재 위치에서 한 번 이동 시 범위 내인지 확인한다.</li>
<li>범위 내라면 +1, 아니라면 through</li>
<li>모든 경우 탐색 후 결과 출력</li>
</ol>
<ul>
<li>시뮬레이션 + 완전탐색 문제</li>
<li>모든 이동 가능한 경우를 체크해야한다.</li>
<li>입력받는 문자가 2글자이기에 <code>map</code>은 사용하지 않았다.</li>
<li><code>input.last!</code>는 <code>Character</code> 형식이기에 바로 <code>Int</code>로 형변환이 불가능하여 <code>String</code>으로 한 번 더 바꿔주었다.</li>
<li><code>(1, 1)</code>이 편해서 &#39;a&#39;의 아스키코드값 97대신 96을 사용했다.</li>
<li><code>튜플</code> 처리 속도가 빠르기 때문에 dx, dy 배열로 각각 나누는 대신 한번에 처리했다.</li>
<li>가독성을 위해 <code>isInRange()</code> 를 만들었다.</li>
</ul>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-swift">import Foundation

let input = readLine() ?? &quot;&quot;
let row = Int(String(input.last!))!
let col = Int(input.first!.asciiValue!) - 96 // 97은 &#39;a&#39;의 아스키 코드, a일 경우 1로 표시 되어야 하기 때문에 96을 빼준다.

let moves = [(-2, -1), (-1,-2), (2,-1), (1,-2), (2,1), (1,2), (-1,2), (-2,1)]
var result = 0

for move in moves {
    var nextX = row + move.0
    var nextY = col + move.1

    if isInRange(nextX,nextY) {
        result += 1
    }
}

print(result)

func isInRange(_ x: Int, _ y: Int) -&gt; Bool {
    return (1...8 ~= x) &amp;&amp; (1...8 ~= y) // ~= 볌위 연산자 : Bool 값
}</code></pre>
<h2 id="그룹-스터디-피드백">그룹 스터디 피드백</h2>
<ul>
<li>처음엔 함수를 <code>(x &gt;= 1) &amp;&amp; (x &lt;= 8) ..</code> 이런식으로 작성했는데
<a href="https://github.com/mandos1995">만도스</a>님이 사용한 범위연산자 <code>~=</code>를 보고 고쳤다.</li>
<li>구현 문제는 함수를 쪼개서 푸는 방법에 익숙해지는게 좋을 것 같다는 결론을 얻었다.</li>
<li>백준의 삼성 기출 문제에 어려운 구현 문제가 많아서 다음엔 그 문제들을 연습해야겠다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[그리디 알고리즘] 큰 수의 법칙]]></title>
            <link>https://velog.io/@debby_/%EA%B7%B8%EB%A6%AC%EB%94%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%81%B0-%EC%88%98%EC%9D%98-%EB%B2%95%EC%B9%99</link>
            <guid>https://velog.io/@debby_/%EA%B7%B8%EB%A6%AC%EB%94%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%81%B0-%EC%88%98%EC%9D%98-%EB%B2%95%EC%B9%99</guid>
            <pubDate>Tue, 03 Jan 2023 01:26:19 GMT</pubDate>
            <description><![CDATA[<h2 id="그리디-알고리즘에-대한-소개">그리디 알고리즘에 대한 소개</h2>
<p>문제 풀이에 앞서 그리디 알고리즘을 간단하게 짚고 넘어가보자.
그리디 알고리즘은 비교적 간단하고 자주 사용되는 알고리즘이다.
이 알고리즘은 특정한 조건 내에서 <code>가능한 가장 좋은 해</code>를 찾기 위해 매번 다른 값을 시도해 보는 방식으로 접근한다.
일반적으로 시간 및 공간 복잡도가 낮아 <code>효율적인 솔루션</code>을 제공한다.</p>
<h2 id="그리디-알고리즘의-종류">그리디 알고리즘의 종류</h2>
<p>일반적으로는 <code>최적 분할 문제</code>, <code>최대 값 문제</code> 및 <code>최소 값 문제</code>에 적용할 수 있다. 이 외에도 다양한 다른 문제에 대해 그리디 알고리즘을 사용할 수 있다.</p>
<h2 id="예시-문제">예시 문제</h2>
<p>예를 들어 다음과 같은 숫자 배열이 있다고 가정해보자 <code>[2, 3, 7, 8, 10]</code>
여기에서 주어진 숫자들을 사용하여 <code>최대 합을 가지는 부분 집합</code>을 찾는 문제를 해결하고자 한다.
이 때 그리디 알고리즘이 적용되는데, 숫자가 적어도 하나 이상 포함되는 부분 집합을 구하기 위해 다음과 같은 과정을 따른다.</p>
<ol>
<li><code>첫 번째 원소</code>를 선택한다.</li>
<li><code>다음 원소</code>를 선택하고 지금까지 <code>선택한 원소의 합과 비교</code>한다.</li>
<li>두 숫자 중 <code>더 큰 값을 선택</code>한다.</li>
</ol>
<h2 id="결론">결론</h2>
<p>그리디 알고리즘은 비교적 간단하고 자주 사용되는 알고리즘이다.
가장 큰 장점은 <strong>시간 및 공간 복잡도가 낮아 효율적인 해를 찾는 것</strong>!</p>
<p>즉 모든 문제는 완전탐색으로 풀 수 있지만,
이 알고리즘을 채택한다면 더 빠르게 풀 수 있다!</p>
<p>하지만 이 알고리즘을 사용하기 전에 이 방법이 정말 맞는지 판별하기가 어렵기 때문에
꽤 어려운 알고리즘이다.</p>
<hr>
<h2 id="문제-큰-수의-법칙">[문제] 큰 수의 법칙</h2>
<p>입력받은 배열을 사용하여 가장 큰 수를 만드는 문제이다.</p>
<p>1. N개의 자연수를 가진 배열은 정렬되어있지 않고
2. 총 M번 더할 수 있으며
3. 하나의 인덱스 당 K번 초과하여 더할 수 없다.</p>
<h3 id="입력-조건">입력 조건</h3>
<ul>
<li>첫째 줄에 N(2 &lt;= N &lt;= 1,000), M(2 &lt;= M &lt;= 10,000), K(1 &lt;= K &lt;= 10,000)의 자연수가 주어지며,각 자연수는 공백으로 구분한다.</li>
<li>둘째 줄에 N개의 자연수가 주어진다. 각 자연수는 공백으로 구분한다. 단, 각각의 자연수는 1 이상 10,000이하의 수로 주어진다.</li>
<li>입력으로 주어지는 K는 항상 M보다 작거나 같다.</li>
</ul>
<h3 id="출력-조건">출력 조건</h3>
<ul>
<li>첫째 줄에 동빈이의 큰 수의 법칙에 따라 더해진 답을 출력한다.</li>
</ul>
<hr>
<h3 id="풀이">풀이</h3>
<ol>
<li>입력받은 배열을 정렬한다.</li>
<li>맨 마지막에 있는 수(가장 큰 수)를 K번 더한다.</li>
<li>K를 초과할 때 두번째로 큰 수를 한번 더하고 count를 초기화 한다.</li>
<li>2,3번을 M번 반복한다.</li>
</ol>
<hr>
<h3 id="코드">코드</h3>
<pre><code class="language-swift">let input = readLine()!.split(separator: &quot; &quot;).map{ Int(String($0))! }
var (N, M, K) = (input[0], input[1], input[2])
var array = readLine()!.split(separator: &quot; &quot;).map{ Int(String($0))! }

array.sort()
let first = array[N - 1] // 가장 큰 수
let second  = array[N - 2] // 두번째로 큰 수

var result = 0
var count = 1

for _ in 0 ..&lt; M {
    if count &lt;= K {
        result += first
        count += 1
    } else {
        result += second
        count = 1
    }
}

print(result)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LeetCode] [Swift] 1678. Goal Parser Interpretation]]></title>
            <link>https://velog.io/@debby_/LeetCode-Swift-1678.-Goal-Parser-Interpretation</link>
            <guid>https://velog.io/@debby_/LeetCode-Swift-1678.-Goal-Parser-Interpretation</guid>
            <pubDate>Fri, 09 Sep 2022 12:50:39 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://velog.io/@debby_/LeetCode-Swift-1678.-Goal-Parser-Interpretation&count_bg=%232E7CD5&title_bg=%23555555&icon=pocket.svg&icon_color=%23FFFFFF&title=visiters&edge_flat=false" alt="Hits"></a></p>
<h2 id="문제">문제</h2>
<blockquote>
<p><a href="https://leetcode.com/problems/goal-parser-interpretation/">1678. Goal Parser Interpretation</a></p>
</blockquote>
<h2 id="문제파악하기">문제파악하기</h2>
<ul>
<li>주어진 문자열 command를 Parse 하여 알맞게 출력하는 문제</li>
<li>만약 &quot;G()(al)&quot;으로 주어진다면
  G -&gt; G
  () -&gt; o
  (al) -&gt; al
  이렇게 바꿔주면 된다.</li>
</ul>
<p>내가 한 방법은</p>
<ol>
<li>문자열을 인덱스로 탐색하기 좋게 Array로 바꿔준다.</li>
<li>답을 담을 <code>ans</code> 변수 선언</li>
<li>문자열의 길이 -1 만큼 탐색한다.</li>
<li>현재 문자가 &quot;G&quot; 라면 <code>ans += &quot;G&quot;</code>
5-1. 현재 문자가 &quot;(&quot; 라면 그 다음 문자가 &quot;)&quot; 인지 아닌지 구별한다.
5-2. 그 다음 문자가 &quot;)&quot;라면 <code>ans += &quot;o&quot;</code>, &quot;a&quot;라면 <code>ans += &quot;al&quot;</code></li>
<li>for문은 맨 마지막 글자를 탐색하지 않기 때문에 한번 더 탐색해주고
 &quot;)&quot;이 아니라면 마지막 글자를 넣어준다.</li>
</ol>
<h2 id="풀이">풀이</h2>
<pre><code class="language-swift">class Solution {
    func interpret(_ command: String) -&gt; String {
        var str = Array(command)
        var ans:String = &quot;&quot;

        for i in 0 ..&lt; str.count - 1 {
            if str[i] == &quot;G&quot; {
                ans += &quot;G&quot;
            } else if (str[i] == &quot;(&quot;) &amp;&amp; (str[i+1] == &quot;)&quot;) {
                ans += &quot;o&quot;
            } else if (str[i] == &quot;(&quot;) &amp;&amp; (str[i+1] == &quot;a&quot;) {
                ans += &quot;al&quot;
            }
        }
        if str.last! != &quot;)&quot; {
            ans += String(str.last!)
        }

        return ans
    }
}</code></pre>
<h2 id="🤔-feedback">🤔 FEEDBACK</h2>
<p>처음엔 while문으로 문자열을 마지막부터 하나씩 빼가면서 비교했는데 너무 느렸다.
이렇게되면 command의 전체를 확인해야하기 때문에 O(n),
거기에 removeLast()를 계속 반복하기 때문에  O(1)을 또 반복해서 그랬다.
단순 for문을 구현하지 않으려다가 더 늦은 알고리즘을 구현했었다;</p>
<p><img src="https://velog.velcdn.com/images/debby_/post/6fd7043e-f27b-4336-b973-1ab702648158/image.png" alt="">
<img src="https://velog.velcdn.com/images/debby_/post/8549b9ef-173b-4bee-a862-8bd971677cb4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [Lv.1] [Swift] 음양 더하기]]></title>
            <link>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%9D%8C%EC%96%91-%EB%8D%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%9D%8C%EC%96%91-%EB%8D%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 20 Aug 2022 23:59:59 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%9D%8C%EC%96%91-%EB%8D%94%ED%95%98%EA%B8%B0&count_bg=%232E7CD5&title_bg=%23555555&icon=pocket.svg&icon_color=%23FFFFFF&title=visiters&edge_flat=false" alt="Hits"></a></p>
<h2 id="문제">문제</h2>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/76501">음양 더하기</a></p>
</blockquote>
<h2 id="문제파악하기">문제파악하기</h2>
<ul>
<li>동일한 길이의 배열이 주어진다.</li>
<li>한쪽은 양수인지 음수인지만 판별하고 1 또는 -1을 곱하여 더한 것을 누적하면 된다.</li>
</ul>
<h2 id="풀이">풀이</h2>
<pre><code class="language-swift">import Foundation

func solution(_ absolutes:[Int], _ signs:[Bool]) -&gt; Int {
    var sum = 0
    for i in 0 ..&lt; absolutes.count {
        let sign = signs[i] == true ? 1 : -1
        sum += absolutes[i] * sign
    }
    return sum
}</code></pre>
<h2 id="🤔-feedback">🤔 FEEDBACK</h2>
<ul>
<li>다른 답들을 보니까 고차함수 써볼걸 하는 후회가 밀려온다 .. 😳</li>
<li>내일부터 백준 브론즈 5 도장깨기 하면서 고차함수 연습 실컷 해야겠다.</li>
</ul>
<h3 id="다른-풀이">다른 풀이</h3>
<pre><code class="language-swift">import Foundation

func solution(_ absolutes:[Int], _ signs:[Bool]) -&gt; Int {
    return (0..&lt;absolutes.count).map { signs[$0] ? absolutes[$0] : -absolutes[$0] }.reduce(0, +)
}</code></pre>
<ul>
<li></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [Lv.1] [Swift] 소수 만들기]]></title>
            <link>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%86%8C%EC%88%98-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%86%8C%EC%88%98-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sat, 20 Aug 2022 13:00:50 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%86%8C%EC%88%98-%EB%A7%8C%EB%93%A4%EA%B8%B0&count_bg=%232E7CD5&title_bg=%23555555&icon=pocket.svg&icon_color=%23FFFFFF&title=visiters&edge_flat=false" alt="Hits"></a></p>
<h2 id="문제">문제</h2>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12977">소수 만들기</a></p>
</blockquote>
<h2 id="문제파악하기">문제파악하기</h2>
<ul>
<li><code>sum</code>이라는 배열을 만들어 3개의 숫자 합을 전부 담아준다.</li>
<li>세개의 합을 구하기 위해 반복문 끝을 배열 길이 -2로 설정한다.</li>
<li><code>i</code>보다 한칸 뒤의 위치를 <code>j</code>, 그 뒤를 <code>k</code>로 설정하여 <code>sum.append()</code></li>
<li><code>isPrime</code> 함수를 통해 소수인 것만 배열에 담아 <code>.count</code>를 반환한다.</li>
</ul>
<h2 id="풀이">풀이</h2>
<pre><code class="language-swift">func solution(_ nums:[Int]) -&gt; Int {
    var sum = [Int]()

    for i in 0 ..&lt; nums.count - 2 {
        for j in i+1 ..&lt; nums.count {
            for k in j+1 ..&lt; nums.count {
                sum.append(nums[i] + nums[j] + nums[k])
            }
        }
    }
    return sum.filter{isPrime($0)}.count
}

func isPrime(_ n:Int) -&gt; Bool {
    for i in 2 ..&lt; n {
        if n % i == 0 {
            return false
        }
    }
    return true
}</code></pre>
<h2 id="🤔-feedback">🤔 FEEDBACK</h2>
<ul>
<li>소수 만들기 문제는 풀때마다 리셋되는 기분이다.</li>
<li>풀이 방법도 다양하기 때문에 이틀에 한번씩 복습하면서 다른 풀이를 연습해보는게 좋을 것 같다.</li>
</ul>
<h3 id="다른-풀이">다른 풀이</h3>
<pre><code class="language-swift">func solution(_ nums:[Int]) -&gt; Int {
    var answer: Int = 0
    var combi: [Int] = [Int](repeating: 0, count: 3)
    combination(nums, &amp;combi, 0, nums.count, 0, 3, &amp;answer)

    return answer
}

func checkPrime(_ num: Int) -&gt; Bool {
    var index: Int = 0
    for i in 2...num {
        if num % i == 0 {
            index = i
            break
        }
    }
    if index == num {
        return true
    }
    return false
}

func combination(_ nums: [Int], _ combi: inout [Int], _ target: Int,
                 _ n: Int, _ index: Int, _ k: Int, _ answer: inout Int)
{
    if k == 0 {
        var checkNum: Int = 0
        for i in combi {
            checkNum += nums[i]
        }
        // 소수이면 1 더함
        if checkPrime(checkNum) {
            answer += 1
        }
    }
    else if target == n {
        return
    }
    else {
        combi[index] = target
        combination(nums, &amp;combi, target + 1, n, index + 1, k - 1, &amp;answer)
        combination(nums, &amp;combi, target + 1, n, index, k, &amp;answer)
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [Lv.2] [Swift] K번째수]]></title>
            <link>https://velog.io/@debby_/Programmers-Lv.2-Swift-K%EB%B2%88%EC%A7%B8%EC%88%98</link>
            <guid>https://velog.io/@debby_/Programmers-Lv.2-Swift-K%EB%B2%88%EC%A7%B8%EC%88%98</guid>
            <pubDate>Fri, 19 Aug 2022 06:38:12 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=%EC%A3%BC%EC%86%8C&count_bg=%232E7CD5&title_bg=%23555555&icon=pocket.svg&icon_color=%23FFFFFF&title=visiters&edge_flat=false" alt="Hits"></a></p>
<h2 id="문제">문제</h2>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42748">K번째수</a></p>
</blockquote>
<h2 id="문제파악하기">문제파악하기</h2>
<h2 id="풀이">풀이</h2>
<pre><code class="language-swift">func solution(_ array:[Int], _ commands:[[Int]]) -&gt; [Int] {
    var answer = [Int]()

    for command in commands {
        let i = command[0] - 1
        let j = command[1] - 1
        let k = command[2] - 1

        let num = array[i...j].sorted()[k]
        answer.append(num)
    }

    return answer
}</code></pre>
<h2 id="🤔-feedback">🤔 FEEDBACK</h2>
<h3 id="다른-풀이">다른 풀이</h3>
<pre><code class="language-swift">func solution(_ array:[Int], _ commands:[[Int]]) -&gt; [Int] {
    return commands.map({(key) in
        return array[(key[0]-1)...(key[1]-1)].sorted()[key[2]-1]
    })
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [Lv.2] [Swift] 올바른 괄호]]></title>
            <link>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8</link>
            <guid>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8</guid>
            <pubDate>Thu, 18 Aug 2022 09:54:15 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B4%84%ED%98%B8&count_bg=%232E7CD5&title_bg=%23555555&icon=pocket.svg&icon_color=%23FFFFFF&title=visiters&edge_flat=false" alt="Hits"></a></p>
<h2 id="문제">문제</h2>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12909">올바른 괄호</a></p>
</blockquote>
<h2 id="방법">방법</h2>
<ol>
<li>문자열을 하나씩 탐색한다.</li>
<li><code>(</code> 일 때 +1, <code>)</code> 일 때 -1로 카운트하면</li>
<li>한 쌍을 이룰 때 0이 유지되고</li>
<li>왼쪽부터 카운트 하기 때문에 <code>)</code> 가 하나라도 더 많을 경우 음수가 되니 바로 false 반환</li>
<li>끝까지 확인한 경우 카운트가 0 이라면 true, 아니라면 false</li>
</ol>
<h2 id="풀이">풀이</h2>
<pre><code class="language-swift">import Foundation

func solution(_ s:String) -&gt; Bool {
    var count = 0
    for c in s {
        if c == &quot;(&quot; {
            count += 1
        } else {
            count -= 1
        }
        if count &lt; 0 {
            return false
        }
    }
    return count == 0 ? true : false
}</code></pre>
<h2 id="다른-풀이">다른 풀이</h2>
<pre><code class="language-swift"></code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [Lv.1] [Swift] 모의고사]]></title>
            <link>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</link>
            <guid>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC</guid>
            <pubDate>Wed, 17 Aug 2022 01:51:29 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://velog.io/@debby_/Programmers-Lv.1-Swift-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC&count_bg=%232E7CD5&title_bg=%23555555&icon=pocket.svg&icon_color=%23FFFFFF&title=visiters&edge_flat=false" alt="Hits"></a></p>
<h2 id="문제">문제</h2>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42840">모의고사</a></p>
</blockquote>
<h2 id="문제파악하기">문제파악하기</h2>
<ul>
<li>점수 [수포자 번호 : 맞은 개수]</li>
<li>그 value에 1을 누적해가면서 각 수포자의 맞은 개수를 카운팅한다.</li>
</ul>
<h2 id="풀이">풀이</h2>
<pre><code class="language-swift">import Foundation

func solution(_ answers:[Int]) -&gt; [Int] {
    let first = [1, 2, 3, 4, 5]
    let second = [2, 1, 2, 3, 2, 4, 2, 5]
    let third = [3, 3, 1, 1, 2, 2, 4, 4, 5, 5]
    var scores = [Int:Int]()

    for i in 0 ..&lt; answers.count {
        if answers[i] == first[i % first.count] { scores[1, default: 0] += 1 }
        if answers[i] == second[i % second.count] { scores[2, default: 0] += 1 }
        if answers[i] == third[i % third.count] { scores[3, default: 0] += 1 }
    }

    return scores.filter { $0.value == scores.values.max() }.keys.sorted()
}</code></pre>
<h2 id="🤔-feedback">🤔 FEEDBACK</h2>
<ul>
<li>한참 Array에 정답 맞춘 개수 카운트해서 따로 만들어서 넣다가 이게 아닌 것 같아서 방법을 바꿨다.</li>
<li><code>scores[1, default: 0]</code>은 scores[1] key가 없을 때 0으로 초기화</li>
<li></li>
</ul>
<h3 id="다른-풀이">다른 풀이</h3>
<pre><code class="language-swift">import Foundation

func solution(_ answers:[Int]) -&gt; [Int] {
    let answer = (
        a: [1, 2, 3, 4, 5], // index % 5
        b: [2, 1, 2, 3, 2, 4, 2, 5], // index % 8
        c: [3, 3, 1, 1, 2, 2, 4, 4, 5, 5] // index % 10
    )
    var point = [1:0, 2:0, 3:0]

    for (i, v) in answers.enumerated() {
        if v == answer.a[i % 5] { point[1] = point[1]! + 1 }
        if v == answer.b[i % 8] { point[2] = point[2]! + 1 }
        if v == answer.c[i % 10] { point[3] = point[3]! + 1 }
    }

    return point.sorted{ $0.key &lt; $1.key }.filter{ $0.value == point.values.max() }.map{ $0.key }
}</code></pre>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://velog.io/@loganberry/Swift-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AA%A8%EC%9D%98%EA%B3%A0%EC%82%AC-feat.-Dictionary">[Swift] 프로그래머스 모의고사 (feat. Dictionary)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Programmers] [Lv.1] [Swift] 약수의 개수와 덧셈]]></title>
            <link>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%95%BD%EC%88%98%EC%9D%98-%EA%B0%9C%EC%88%98%EC%99%80-%EB%8D%A7%EC%85%88</link>
            <guid>https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%95%BD%EC%88%98%EC%9D%98-%EA%B0%9C%EC%88%98%EC%99%80-%EB%8D%A7%EC%85%88</guid>
            <pubDate>Tue, 16 Aug 2022 14:08:34 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https://velog.io/@debby_/Programmers-Lv.1-Swift-%EC%95%BD%EC%88%98%EC%9D%98-%EA%B0%9C%EC%88%98%EC%99%80-%EB%8D%A7%EC%85%88&count_bg=%232E7CD5&title_bg=%23555555&icon=pocket.svg&icon_color=%23FFFFFF&title=visiters&edge_flat=false" alt="Hits"></a></p>
<h2 id="문제">문제</h2>
<blockquote>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/77884#">약수의 개수와 덧셈</a></p>
</blockquote>
<h2 id="문제파악하기">문제파악하기</h2>
<ul>
<li>주어진 <code>left</code>와 <code>right</code> 사이의 수를 탐색하여 약수의 수를 count하고
그 수가 짝수라면 더하기, 홀수라면 빼는 알고리즘이다.</li>
</ul>
<h2 id="풀이">풀이</h2>
<pre><code class="language-swift">import Foundation

func solution(_ left:Int, _ right:Int) -&gt; Int {
    var answer = 0
    for i in left ... right {
        var count = 0
        for j in 1...i {
            if i % j == 0 { // i의 약수 카운트
                count += 1
            }
        }
        if count % 2 == 0 {
            answer += i
        } else {
            answer -= i
        }
    }
    return answer
}
</code></pre>
<h2 id="🤔-feedback">🤔 FEEDBACK</h2>
<ul>
<li>처음엔 함수를 만들어서 각 수의 약수를 배열로 반환했고 길이로 판단했다.</li>
<li>나름대로 <code>sqrt</code>를 써서 시간복잡도를 줄여 구해보겠다는 발상이었는데
아무리 봐도 이렇게 복잡하게 풀 문제가 아니다 싶어서 문제를 다시 봤다.</li>
<li>약수의 개수가 짝수인지 홀수인지만 알면되니까 count가 낫겠다 싶어 방향을 바꿨다.</li>
</ul>
<h3 id="다른-풀이">다른 풀이</h3>
<pre><code class="language-swift">func solution(_ left: Int, _ right: Int) -&gt; Int {
    return (left...right).map { i in (1...i).filter { i % $0 == 0 }.count % 2 == 0 ? i : -i }.reduce(0, +)
}</code></pre>
<ul>
<li>난 언제 이렇게 고차함수를 능수능란하게 써보지...</li>
<li>left부터 right까지 한번에 map을 사용해서 반복해준다. -&gt; Array 생성</li>
<li>i를 Array에 넣어줄건데 이 i는 1부터 i까지 나눠지는 수일때만 담은 배열의 길이를 카운트</li>
<li>그래서 그게 짝수면 그냥 정수로 넣어주고 홀수면 음수로 넣어준다.</li>
<li>최종적으로 filter에 걸러진 배열의 총 합을 구한다.</li>
<li>아마 map, filter, reduce까지 써서 효율적인 코드는 아닐거라 예상하지만..</li>
<li>그래도 고차함수를 잘 다룰 수 있고 많이 풀지 않는 방식이라 높은 점수 받았을 것 같다.</li>
</ul>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://woodadada16.tistory.com/24">[Java] 약수 구하기 알고리즘</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>