반응형

Swift의 데이터 타입과 대수학(Algebra)의 관계에 대해서 알아보도록 하겠습니다.

데이터 타입과 대수학이 관계가 깊다는 얘기를 우연히 접한적이 있습니다. 데이터 타입은 제가 String, Int, Bool, Long, Short, unsigned Int 등 문자열, 실수 정수 등 여러 종류의 데이터를 나타내기 위한 것이다라는 생각을 바로 할 수 있었는데요

 

대수학은 뭘까요??…. 나무위키에 따르면 초중등교육에서는 미지수에 변수를 대입하는 기술이라고 하는데.... 그 이상의 개념은 저는 잘 모르겠어서 암튼 여기까지 하고 일단 넘어가 보겠습니다.

 

 

구조체와 대수학

 

먼저 아래와 같은 구조체를 정의해보겠습니다.

struct Some<T, U> {
	let first: T
	let second: U
}

위 구조체는 제네릭 타입으로 T, U를 받고 first와 second라는 2개의 필드에 각각 T, U의 타입을 가지고 있습니다.

 

만약 T와 U가 Boolean값이라면 Some이라는 구조체가 가질 수 있는 경우의 수는 총 4가지 입니다.

(true, true), (true, false), (false, true), (false, false)

 

만약 Boolean을 T에 3가지의 경우를 가질 수 있는 열거형을 U타입에 넘기면 어떻게 될까요?

enum SomeEnum {
	case a
	case b
	case c
}

Some<Bool, SomeEnum>(first: true, second: .a)
Some<Bool, SomeEnum>(first: true, second: .b)
Some<Bool, SomeEnum>(first: true, second: .c)
Some<Bool, SomeEnum>(first: false, second: .a)
Some<Bool, SomeEnum>(first: false, second: .b)
Some<Bool, SomeEnum>(first: false, second: .c)

이렇게 총 6가지의 경우가 나옵니다. 2*3이죠

 

만약 U의 위치에 Void를 넣으면 어떻게 될까요?? Void는 경우의 수가 () 한 개뿐이므로

2*1의 2가지 경우가 나오죠.

 

Void는 수학적으로 1을 의미한다고 해석하면 될 것 같기도 합니다?!

 

그렇다면 0은 어떻게 표현할까요??

 

간단합니다. Never라는 키워드를 Result타입의 Failure타입에서 보신적이 있으실 거에요.

Failure타입에 있을 경우 발생하지 않는다는 뜻을 나타내게 되는데요. Never의 실제 구현은 아래와 같습니다.

@frozen enum Never { }

케이스가 없는 열거형이네요.

실제로 아래와 같이 U에 Never를 넘길 경우 아예 구조체 인스턴스를 만들 수 조차 없습니다.

Some<Bool, Never>(first: true, second: ...???)

자 잠깐 정리하면

Bool은 2, Void는 1, Never는 0 이네요.

 

그리고 구조체에서는 T와 U의 경우의 수의 곱만큼 실제 구조체가 가질 수 있는 경우의 수가 존재하게 됩니다.

<Bool, Bool> = 2*2

<Bool, Void> = 2*1

<Void, Void> = 1*1

<Bool, Never> = 2*0

 

 

열거형과 대수학

열거형에서는 어떻게 될까요

enum ExampleEnum<T, U> {
	case a(T)
	case b(U)
}

위와 같은 열거형이 있다고 해봅시다. 그리고 구조체에서와 같이 아래의 총 4개의 열거형을 정의해보겠습니다.

ExampleEnum<Bool, Bool>
ExampleEnum<Bool, Void>
ExampleEnum<Void, Void>
ExampleEnum<Bool, Never>

그러면 열거형이 가질 수 있는 경우의 수는 아래와 같습니다

ExampleEnum<Bool, Bool>.a(true)
ExampleEnum<Bool, Bool>.a(false)
ExampleEnum<Bool, Bool>.b(true)
ExampleEnum<Bool, Bool>.b(false)
// 2+2

ExampleEnum<Bool, Void>.a(true)
ExampleEnum<Bool, Void>.a(false)
ExampleEnum<Bool, Void>.b(())
// 2+1

ExampleEnum<Void, Void>.a(())
ExampleEnum<Void, Void>.b(())
// 1+1

ExampleEnum<Bool, Never>.a(true)
ExampleEnum<Bool, Never>.a(false)
// 2+0

구조체와 다르게 곱연산이 아니라 합연산만큼 경우의 수가 있는 것을 볼 수 있습니다.

 

Optional을 예로 들어보겠습니다.

enum Optional<A> {
	case none
	case some<A>
}

여기서 none은 none(Void)로 볼 수 있을 것 같습니다. Optional.none(())와 Optional.none은 언제나 한가지의 경우만 존재하니까요.

 

그래서 Optional<A>의 경우의 수는 1 + A의 경우의 수만큼 존재할 수 있음을 알 수 있고 이를

Optional<A> = A? = 1 + A = Void + A

이렇게 볼 수 있겠네요

 

이걸 아는게 왜 중요할까요???

결론부터 얘기하면 실제 서비스를 만들기 위해 데이터를 모델링할 때 존재하지 않을 케이스를 만들지 않아서 불필요한 작업을 사전에 방지하는데 도움이 되기 때문입니다.

 

유저 정보를 API호출로 받아오는데 거기에는 이름과 나이 전화번호를 준다고 해봅시다.

또 전화번호는 국가코드와 국가코드 번호 그리고 숫자 번호가 있다고 해봅시다. 그리고 전화번호는 유저가 회원가입 시 입력할 수도 있고 안 할수도 있습니다.

이를 모델링 해보겠습니다.

 

첫 번째 방법

struct UserInfo {
	let name: String
	let age: Int
	let phoneNationalCode: String?
	let phoneNationalCodeNumber: String?
	let phoneNumber: String?
}

두 번째 방법

struct UserInfo {
	let name: String
	let age: Int
	var phoneNumber: PhoneNumber?
}

struct PhoneNumber {
	let nationalCode: String
	let nationalCodeNumber: String
	let number: String
}

제 생각에는 2 번째 방법이 보다 잘 도메인을 잘 표현했다고 생각이 듭니다. 단순히 optional unwrapping의 횟수를 3번에서 1번으로 줄여서가 아니라 개념적으로 동시에 존재해야만 존재할 수 있는 3가지 개념들을 제대로 표현해낸 방법이 2번째이기 때문입니다.

 

 

다른 예를 또 들어보겠습니다.

 

대기열 기능을 만드려고 합니다

대기열에는 다음 3가지 상태가 있습니다.

  • 들어가기 전 ready상태
  • 대기열에 들어가서 기다리는 waiting 상태로 이 때는 내 앞에 남은 사람의 숫자 n을 같이 보내줍니다
  • 들어갈 수 있는 상태 finish

서버측에서 API Response 형상 예시를 보내왔습니다. 아래처럼 말이죠

{
	"status": "ready"
}

{
	"status": "waiting",
	"remaining_count": 6,
}

{
	"status": "finish"
}

어떻게 모델링하면 좋을까요??

 

 

첫 번째 방법

enum WaitingStatus {
	case ready
	case waiting
	case finish
}

struct Wating {
	case status: WaitingStatus
	case remainingCount: Int?
}

두 번째 방법

enum WaitingStatus {
	case ready
	case waiting(Int)
	case finish
}

당연히 2번째 방법이 좋습니다. 첫 번째 방법의 경우는 ready인데 9명이 기다리고 있다라는 상태를 가질 수 있는 가능성을 모델링에서 막아주고 있지 않기 때문입니다.

 

 

오랜만에 하는 포스팅이기 때문에 가벼운 글부터 시작해 보았는데요. 도움이 되셨을 지 모르겠네요ㅎㅎ

 

참고

https://namu.wiki/w/대수학

https://www.pointfree.co/episodes/ep4-algebraic-data-types

반응형
반응형

문제설명

리코쳇 로봇이라는 보드게임이 있습니다.

이 보드게임은 격자모양 게임판 위에서 말을 움직이는 게임으로, 시작 위치에서 목표 위치까지 최소 몇 번만에 도달할 수 있는지 말하는 게임입니다.

이 게임에서 말의 움직임은 상, 하, 좌, 우 4방향 중 하나를 선택해서 게임판 위의 장애물이나 맨 끝에 부딪힐 때까지 미끄러져 이동하는 것을 한 번의 이동으로 칩니다.다음은 보드게임판을 나타낸 예시입니다.

...D..R
.D.G...
....D.D
D....D.
..D....

여기서 "."은 빈 공간을, "R"은 로봇의 처음 위치를, "D"는 장애물의 위치를, "G"는 목표지점을 나타냅니다.
위 예시에서는 "R" 위치에서 아래, 왼쪽, 위, 왼쪽, 아래, 오른쪽, 위 순서로 움직이면 7번 만에 "G" 위치에 멈춰 설 수 있으며, 이것이 최소 움직임 중 하나입니다.

게임판의 상태를 나타내는 문자열 배열 board가 주어졌을 때, 말이 목표위치에 도달하는데 최소 몇 번 이동해야 하는지 return 하는 solution함수를 완성하세요. 만약 목표위치에 도달할 수 없다면 -1을 return 해주세요.

 

 

제한사항

  • 3 ≤ board의 길이 ≤ 100
    • 3 ≤ board의 원소의 길이 ≤ 100
    • board의 원소의 길이는 모두 동일합니다.
    • 문자열은 ".", "D", "R", "G"로만 구성되어 있으며 각각 빈 공간, 장애물, 로봇의 처음 위치, 목표 지점을 나타냅니다.
    • "R"과 "G"는 한 번씩 등장합니다.

 

풀이

저는 bfs로 풀었습니다.

아이디어

- 시작지점에서 보드판 위에 도달이 가능한 위치는 정해져 있습니다.(아무런 장애물도 없고 보드의 끝도 아닌 곳에서 멈춰설 수는 없으니까요)

- 각 위치에 도달하기까지 bfs로 최단거리를 구해보자!!

1. 시작지점과 골인지점을 찾습니다. 시작지점 찾을 때 찾는 김에 Queue에 넣었습니다

2. 장애물을 만나거나 보드판의 끝에 도달할 때까지 (아래, 위, 오른쪽, 왼쪽) 방향으로  쭉쭉 확인하며 나아갑니다.

3. 도달한 위치에 해당하는 visited배열의 값과 현재 내 이동횟수의 값을 비교해서 현재 내 이동횟수가 작으면 visited를 업데이트 해주고 Queue에 도달한 위치를 넣습니다

4. 큐가 빌 때까지 반복합니다

5. 골인지점의 값을 반환합니다.

 

func solution(_ board: [String]) -> Int {
    var visited = Array(repeating: Array(repeating: Int.max, count: board[0].count), count: board.count)
    var queue = [[Int]]()
    var goalI = 0
    var goalJ = 0

    for i in 0 ..< board.count {
        for j in 0 ..< board[0].count {
            let row = Array(board[i])
            if row[j] == "R" {
                queue.append([i,j])
                visited[i][j] = 0
            } else if row[j] == "G" {
                goalI = i
                goalJ = j
            }
        }
    }

    repeat {
        let item = queue.removeFirst()

        // 아래로 Y값을 증가시키며 가봅시다.
        var curI = item[0]
        var curJ = item[1]
        repeat {
            let row = Array(board[curI])
            if row[curJ] == "D" {
                break
            }
            curI += 1
        } while (curI < board.count)
        if visited[curI-1][curJ] > visited[item[0]][item[1]] + 1 {
            visited[curI-1][curJ] = visited[item[0]][item[1]] + 1
            queue.append([curI-1, curJ])
        }

        // 위로
        curI = item[0]
        curJ = item[1]
        repeat {
            let row = Array(board[curI])
            if row[curJ] == "D" {
                break
            }
            curI -= 1
        } while (curI >= 0)
        if visited[curI+1][curJ] > visited[item[0]][item[1]] + 1 {
            visited[curI+1][curJ] = visited[item[0]][item[1]] + 1
            queue.append([curI+1, curJ])
        }

        // 오른쪽으로
        curI = item[0]
        curJ = item[1]
        var row = Array(board[curI])
        repeat {
            if row[curJ] == "D" {
                break
            }
            curJ += 1
        } while (curJ < board[0].count)
        if visited[curI][curJ-1] > visited[item[0]][item[1]] + 1 {
            visited[curI][curJ-1] = visited[item[0]][item[1]] + 1
            queue.append([curI, curJ-1])
        }

        // 왼쪽으로
        curI = item[0]
        curJ = item[1]
        row = Array(board[curI])
        repeat {
            if row[curJ] == "D" {
                break
            }
            curJ -= 1
        } while (curJ >= 0)
        if visited[curI][curJ+1] > visited[item[0]][item[1]] + 1 {
            visited[curI][curJ+1] = visited[item[0]][item[1]] + 1
            queue.append([curI, curJ+1])
        }

    } while (!queue.isEmpty)

    return visited[goalI][goalJ] == Int.max ? -1 : visited[goalI][goalJ]
}

 

반응형
반응형

문제 설명

A 나라가 B 나라를 침공하였습니다. B 나라의 대부분의 전략 자원은 아이기스 군사 기지에 집중되어 있기 때문에 A 나라는 B 나라의 아이기스 군사 기지에 융단폭격을 가했습니다.
A 나라의 공격에 대항하여 아이기스 군사 기지에서는 무수히 쏟아지는 폭격 미사일들을 요격하려고 합니다. 이곳에는 백발백중을 자랑하는 요격 시스템이 있지만 운용 비용이 상당하기 때문에 미사일을 최소로 사용해서 모든 폭격 미사일을 요격하려 합니다.
A 나라와 B 나라가 싸우고 있는 이 세계는 2 차원 공간으로 이루어져 있습니다. A 나라가 발사한 폭격 미사일은 x 축에 평행한 직선 형태의 모양이며 개구간을 나타내는 정수 쌍 (s, e) 형태로 표현됩니다. B 나라는 특정 x 좌표에서 y 축에 수평이 되도록 미사일을 발사하며, 발사된 미사일은 해당 x 좌표에 걸쳐있는 모든 폭격 미사일을 관통하여 한 번에 요격할 수 있습니다. 단, 개구간 (s, e)로 표현되는 폭격 미사일은 s와 e에서 발사하는 요격 미사일로는 요격할 수 없습니다. 요격 미사일은 실수인 x 좌표에서도 발사할 수 있습니다.
각 폭격 미사일의 x 좌표 범위 목록 targets이 매개변수로 주어질 때, 모든 폭격 미사일을 요격하기 위해 필요한 요격 미사일 수의 최솟값을 return 하도록 solution 함수를 완성해 주세요.

제한 사항

  • 1 ≤ targets의 길이 ≤ 500,000
  • targets의 각 행은 [s,e] 형태입니다.
    • 이는 한 폭격 미사일의 x 좌표 범위를 나타내며, 개구간 (s, e)에서 요격해야 합니다.
    • 0 ≤ s < e ≤ 100,000,000

 

풀이

1. 타겟들을 오름차순으로 정렬한다.

2. 가장 작은 end값을 가진 타겟값을 기준으로 이 end값과 다음 타겟의 start값을 비교한다

3. end보다 start값이 크면 미사일을 발사해서 end보다 아래에 있는 모든 타겟을 요격하고 end값을 갱신한다

4. 계속 반복!

5. 반복문 돌고 나서 나머지 타겟들을 요격하기 위해 한 발 더 발사!!!

 

 

정답

func solution(_ targets:[[Int]]) -> Int {
    var ans = 0
    let sorted = targets.sorted(by: { $0[1] < $1[1] })    
    
    var end = sorted[0][1]

    for target in sorted { 
        if target[0] >= end {
            end = target[1]
            ans += 1
        }
    }
    return ans + 1
}
반응형
반응형

제목만 보고 뭔가 싶으실 것 같아서 뭘 얘기하고 싶은지 부가적인 설명을 해보겠습니다

 

작성날이 토요일이기에 Saturday라는 이름으로 프로젝트를 한 번 만들었습니다

Saturday라는 폴더아래에 3개

- SaturdayApp.swift

- ContentView.swift

- Assets.xcassets 하나(위에 보이는 Assets이라는 이름되어 있는 거)

 

Preview Content라는 폴더아래에 1개가 있네요. 간단하죠?

- Preview Assets.xcassets

 

호오옥시 몰라 터미널에서 확인해보았는데 정말 4개가 전부입니다(. 한개는 현재 폴더 .. 는 상위 폴더를 의미합니다)

 

 

xcassets??

일단 오늘 다룰 주제는 아니니 간단히 언급만 하자면 xcassets는 XCode에서 사용할 각종 에셋들을 모아놓은 곳입니다. 이미지나 색깔 등등이 있습니다

 

 

App

자 그럼 이제 살펴볼 파일은 2개 남았는데 결론부터 말하면 SaturdayApp.swift에 앱을 켰을 때의 시작화면(?)을 표시하는 부분이 있고 ContentView.swift가 그 시작화면입니다

 

그럼 이제 우리는 SaturdayApp.swift파일만 보면 됩니다

import SwiftUI

@main                            /// 1
struct SaturdayApp: App {        /// 2
    var body: some Scene {       /// 3
        WindowGroup {            /// 4
            ContentView()
        }
    }
}

짠! 되게 간단합니다! 한 줄씩 찬찬히 음미해봅시다ㅎㅎ

 

@main

모든 프로그램은 진입점이 있어야 합니다. 당연히 App도 진입점이 있는데요

시스템에 여기서 시작해라고 알려주는 역할을 이 @main이 합니다.

학부가 컴공이시라면 C++을 대학교에서 배울 때 아래와 같은 코드를 많이 보셨을 거에요.

int main(int argc, char* argv[]) {
    return 0;
}

사실 요런 main() 코드가 UIKit 내부에서 불리고 있습니다.

위 글을 읽어보시면 아시겠지만

main함수 부르지 마 시스템에서 알아서 호출하니까 대신 네 앱의 진입점을 @main로 표시하면 되라고 써있네요!

과거에는 @UIApplicationMain이 이 역할을 했었는데 Xcode 12부터는 @main키워드도 이 역할을 수행하고 있습니다.

지금은 Xcode버전이 14이상이니 UIKit으로 만들던 SwiftUI로 만들던 @main키워드를 자동으로 붙여줍니다.

왜 역할이 같은 키워드를 또 만들었는지에 대한 내용은 살짝 길어질 것 같으니 다음에 열심히 정리해보겠습니다

밑에 green1229님 블로그 링크를 타고 들어가면 설명이 나와있으니 참고하셔도 좋을 것 같아요 :)

 

 

struct 앱의 이름: App

일단 App은 프로토콜입니다

정의를 보면 앱의 구조와 행동을 나타내는 타입이라고 하네요

Command + 클릭을 통해 들어가보면 7만줄 가까이되는 코드가 보이는데요. 그 중 App은 요렇게 생겼습니다.

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public protocol App {
    associatedtype Body : Scene

    @SceneBuilder @MainActor var body: Self.Body { get }

    @MainActor init()
}

associatedtype Body : Scene

Scene프로토콜을 따르는 App 프로토콜에서 사용할 generic 타입을 정의하였네요

 

@SceneBuilder @MainActor var body: Self.Body { get }

단어별로 뜯어보면

@SceneBuilder

Builder는 뭔가를 만드는 객체를 Builder라고 하는데 그럼 Scene을 만드는 것이겠네?!라는 것을 알 수 있구

Scene은 내가 유저들에게 보여주고 싶은 View 계층을 담은 컨테이너입니다.

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public protocol Scene {
    associatedtype Body : Scene
    
    @SceneBuilder @MainActor var body: Self.Body { get }
}

View는 무엇이냐?! 우리가 앱 켰을 때 화면에 보이는 Label이나 Image같은 UI요소 전부를 말합니다

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {
    associatedtype Body : View

    @ViewBuilder @MainActor var body: Self.Body { get }
}

 

WWDC에 소개된 그림을 보면 아시겠지만 App은 Scene들로 구성되어 있고 각 Scene은 View의 계층을 담고 있네요

 

 

@MainActor

A singleton actor whose executor is equivalent to the main dispatch queue.

라고 공식문서에 나와있습니다. 우리가 iOS 면접볼 때 필수적으로 공부해야할 그 DispatchQueue.main(sync는 호출하면 안되며.. serial큐이며...)이 맞습니다.

자, MainActor가 무엇이냐? Actor개념을 먼저 살펴보아야합니다.

Swift 5.5.1부터 추가된 Concurrency(동시성)와 관련된 개념인데요.

동시성 프로그래밍에서 발생하는 문제 data race 문제를 다루기 위해 추가되었다고 합니다.

Actor전에는 concurrent 큐에서 작업 하나 실행할 때 flags로 barrier를 준다든가 serial큐에서 sync를 쓰던가 해서 한 변수에 동시 접근하는 것을 막아왔더랬죠. Actor에서는 Isolated access와 nonisolated access를 제공합니다. 기본값은 isolated이구요 이렇게 하면 내부적으로 알아서 data race를 막아준다고 하네요

 

암튼 샛길에서 다시 돌아와서 본론으로 가면 main이라고 써있으니까 왠지 UI관련이 있을것 같죠? 맞습니다.

DispatchQueue.global().async { [weak self] in
    DispatchQueue.main.async {
        self?.testhLabel.text = "UI 업데이트는 언제나 Main thread에서"
    }
}

설명을 위한 예제입니다. 위 코드를 MainActor를 사용하면 아주 예쁘게 바꿀 수 있습니다

DispatchQueue.global().async { [weak self] in
    self?.updateLabel()
}

@MainActor
func updateLabel() {
    recentSearchLabel.text = "요로코롬하면 보라색 버그를 보지 않을 수 있어요"
}

하나 더 주의깊게 볼 것은

@MainActor가 var body라는 프로퍼티 앞에도 붙을 수 있다는 것.

예상이 되시겠지만 이 프로퍼티는 Main Thread에서 접근하세요라는 뜻입니다

 

 

 

var Body: some Scene

방금 위에서 언급한 View들의 계층구조를 담고 있는 그 Scene입니다.

 

 

WindowGroup

// 공식문서에 나온 거
struct WindowGroup<Content> where Content : View

// Xcode에서 Jump to Definition 눌러서 나온 거
public struct WindowGroup<Content> : Scene where Content : View {

 

정의를 보면 WindowGroup은 Scene 프로토콜을 준수했습니다. WWDC에서는 WindowGroup을 User Interface를 관리하는 Scene이라고 말하네요.

iOS보다는 iPadOS, macOS에서 사용되는 기능이구 여러 윈도우를 띄운다던가 여러 윈도우를 하나의 탭으로 묶는다던가 하는 기능들을 제공한다네요.ㅎㅎ

그래서 어차피 iOS는 윈도우 하나만 띄울거니 WindowGroup대신 Window로 할 수 있나 해봤더니 왠걸.. 지원을 안하네요

Window가 뭔지 더 찾아보았지만 딱히 더 쓸 것이 안나와서 여기서 나름대로 정리를 해보자면 WindowGroup은 Window들을 관리하는 기능을 담당하고 각 Window는 View 계층을 담은 유니크한 인스턴스로 사용되는 것 같네요ㅎㅎ

 

 

 

 

 

이번 글을 쓰면서 느낀점은 너무 많은 개념들이 한 곳에 담겨있는 듯한 느낌을 받았습니다.

주제도 잘못 고른 것 같고 정리도 안되고 보기도 어렵고..

나중에 다시 개념 하나씩 하나씩 조급해하지 말고 다시 정리해야겠다는 생각이 드네요.

요즘 딱히 재밌는 것이 없어서 시간도 많은데 잘됐죠 뭐ㅎㅎㅎㅎ

 

아무튼 오늘도 읽어주셔서 감사합니다

 

 

참고 및 출처

https://developer.apple.com/videos/play/wwdc2020/10037/

https://medium.com/@abedalkareemomreyh/what-is-main-in-swift-bc79fbee741c

https://green1229.tistory.com/265

https://developer.apple.com/documentation/swiftui/app/main()

https://babbab2.tistory.com/180

https://ios-development.tistory.com/1063

https://itnext.io/mainactor-in-swift-detailed-walkthrough-94044c83118b

https://itnext.io/swift-actors-e80ff0dc1832

https://medium.com/hcleedev/swift-actor%EB%9E%80-f8f58c68dab9

https://itnext.io/thread-sanitizer-in-ios-8438ee3c8c76

https://developer.apple.com/videos/play/wwdc2022/10061/

 

 

이것들 말고도 많은데.... 도저히 정리가 안됨

반응형

'SwiftUI' 카테고리의 다른 글

SwiftUI - 선언형과 명령형  (0) 2023.04.10

+ Recent posts