https://www.acmicpc.net/problem/2206

 

2206번: 벽 부수고 이동하기

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로

www.acmicpc.net

골드 난이도의 BFS문제를 여러 개 풀어보면서 느끼는 공통점은 기본적으로 3차원 배열을 통해 문제를 풀어야 한다는 점이다. 

 

풀이

크게 3가지 케이스로 나누어 경로탐색을 진행해야 한다.

  • 벽을 아직 부수지 않은 상태에서 길을 만났을 경우
  • 벽을 아직 부수지 않은 상태에서 벽을 만났을 경우
  • 이미 벽을 부순상태에서 길을 만났을 경우

경로를 확인하는 visited 배열은 지난번 [1600] 말이 되고픈 원숭이 문제와 같다. 지난번에는 말의 움직임을 몇 번 사용했는지를 확인했다면, 이번에는 벽을 부쉈는지(1), 부수지 않았는지(0) 두 가지만 판별하면 되므로 visited[x][y][2]의 형태로 선언하면 된다.

 

정답 코드

import Foundation

let nm = readLine()!.split(separator: " ").map{Int($0)!}
let n = nm[0]
let m = nm[1]

var map = Array(repeating: [Int](), count: n)
var visited = Array(repeating: Array(repeating: Array(repeating: false, count: 2), count: m), count: n)
for i in 0..<n{
    map[i] = readLine()!.map{Int(String($0))!}
}

let dx = [-1,1,0,0]
let dy = [0,0,-1,1]
var ans = -1

func bfs(){
    var q = [[0,0,0,1]]
    var dq = [[Int]]()
    visited[0][0][0] = true
    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            let wall = curr[2]
            let cnt = curr[3]
            
            if x==n-1 && y==m-1{
                ans = cnt
                return
            }
            
            for i in 0..<4{
                let nx = x + dx[i]
                let ny = y + dy[i]
                if nx<0 || nx>=n || ny<0 || ny>=m {continue}
                
                if wall==0 && map[nx][ny]==0 && !visited[nx][ny][0]{
                    visited[nx][ny][0] = true
                    q.append([nx,ny,0,cnt+1])
                }
                if wall==0 && map[nx][ny]==1 && !visited[nx][ny][1]{
                    visited[nx][ny][1] = true
                    q.append([nx,ny,1,cnt+1])
                }
                if wall==1 && map[nx][ny]==0 && !visited[nx][ny][1]{
                    visited[nx][ny][1] = true
                    q.append([nx,ny,wall,cnt+1])
                }
            }
        }
    }
}
bfs()
print(ans)

'Problem Solving > BOJ' 카테고리의 다른 글

[2146] 다리 만들기  (2) 2022.10.13
[17071] 숨바꼭질 5  (0) 2022.10.12
[1600] 말이 되고픈 원숭이  (2) 2022.10.05
[4179] 불!  (0) 2022.09.29
[2294] 동전 2  (0) 2022.09.22

https://www.acmicpc.net/problem/1600

 

1600번: 말이 되고픈 원숭이

첫째 줄에 정수 K가 주어진다. 둘째 줄에 격자판의 가로길이 W, 세로길이 H가 주어진다. 그 다음 H줄에 걸쳐 W개의 숫자가 주어지는데, 0은 아무것도 없는 평지, 1은 장애물을 뜻한다. 장애물이 있

www.acmicpc.net

[4179] 불! 문제보다는 체감상 쉽게 다가왔다. 불! 문제에서 너무 고생해서 그랬나 보다. 물론 한 번에 통과하진 못했고, 결국 답을 찾아보았지만 조금만 더 깊이 생각했으면 답에 도달하지 않았을까 싶다.

 

풀이

늘 bfs문제를 풀듯이, 상하좌우 좌표의 확인과 말의 이동방식의 좌표 확인 과정을 추가해서 경로를 탐색하면 된다. 중요한 점은 말의 움직임은 k번까지만 가능하고, 말의 움직임과 원숭이의 움직임에 대해 순서가 뒤섞여서 움직일 수 있다는 점이다. 해당 부분에 대한 처리가 어설퍼서 처음에는 오답 판정을 받았다. 

 

큐에는 다음 좌표와 말의 움직임을 몇번 사용하였는지, 그리고 지금까지의 총 이동 횟수를 담아 확인했다. 이후 방문 여부를 확인하는 visited 배열을 3차원으로 생성하는 것이 핵심이다. visited[k][x][y] 배열을 통해 k번째 말의 움직임과 일반적인 움직임의 조합으로 경로를 탐색한다. 각 k번째 visited 배열에는 해당 횟수에서 이동 가능한 말의 움직임 + 원숭이의 움직임에 대한 방문 여부를 확인하고 경로탐색을 수행하면 된다. 설명보단 코드로 보는 것이 이해하기 더 쉬울 것이다.

 

오답 코드

해당 코드에서는 말의 움직임에 대해 깊이 고민하지 않고, 일반적인 visited배열을 사용하여 오답 판정을 받았다.

import Foundation

let k = Int(readLine()!)!
let wh = readLine()!.split(separator: " ").map{Int($0)!}
let w = wh[0]
let h = wh[1]
var ans = -1
var length = 0

var map = Array(repeating: Array(repeating: 0, count: w), count: h)
var visited = Array(repeating: Array(repeating: false, count: w), count: h)
for x in 0..<h{
    let land = readLine()!.split(separator: " ").map{Int($0)!}
    for y in 0..<w{
        map[x][y] = land[y]
    }
}
let dx = [-1,1,0,0]
let dy = [0,0,-1,1]
let hx = [-1,-2,-2,-1,1,2,2,1]
let hy = [-2,-1,1,2,2,1,-1,-2]

func bfs(){
    var q = [[0,0,k,length]]
    var dq = [[Int]]()
    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            let cnt = curr[2]
            let len = curr[3]
            if x==h-1 && y==w-1{
                ans = len
                return
            }
            
            if cnt>0{
                for i in 0..<8{
                    let nx = x+hx[i]
                    let ny = y+hy[i]
                    if nx<0 || nx>=h || ny<0 || ny>=w || map[nx][ny]==1{continue}
                    if !visited[nx][ny]{
                        visited[nx][ny] = true
                        q.append([nx,ny,cnt-1,len+1])
                    }
                }
            }
            for i in 0..<4{
                let nx = x+dx[i]
                let ny = y+dy[i]
                if nx<0 || nx>=h || ny<0 || ny>=w || map[nx][ny]==1{continue}
                if !visited[nx][ny]{
                    visited[nx][ny] = true
                    q.append([nx,ny,cnt,len+1])
                }
            }
        }
    }
}
visited[0][0] = true
bfs()
print(ans)

 

정답 코드

import Foundation

let k = Int(readLine()!)!
let wh = readLine()!.split(separator: " ").map{Int($0)!}
let w = wh[0]
let h = wh[1]
var ans = -1

var map = Array(repeating: Array(repeating: 0, count: w), count: h)
var visited = Array(repeating: Array(repeating: Array(repeating: false, count: 31), count: w), count: h)
for x in 0..<h{
    let land = readLine()!.split(separator: " ").map{Int($0)!}
    for y in 0..<w{
        map[x][y] = land[y]
    }
}
let dx = [-1,1,0,0]
let dy = [0,0,-1,1]
let hx = [-1,-2,-2,-1,1,2,2,1]
let hy = [-2,-1,1,2,2,1,-1,-2]

func bfs(){
    var q = [[0,0,0,0]]
    var dq = [[Int]]()
    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            let cnt = curr[2]
            let length = curr[3]
            if x==h-1 && y==w-1{
                ans = length
                return
            }
            
            if cnt<k{
                for i in 0..<8{
                    let nx = x+hx[i]
                    let ny = y+hy[i]
                    if nx<0 || nx>=h || ny<0 || ny>=w || map[nx][ny]==1{continue}
                    if !visited[nx][ny][cnt+1]{
                        visited[nx][ny][cnt+1] = true
                        q.append([nx,ny,cnt+1,length+1])
                    }
                }
            }
            for i in 0..<4{
                let nx = x+dx[i]
                let ny = y+dy[i]
                if nx<0 || nx>=h || ny<0 || ny>=w || map[nx][ny]==1{continue}
                if !visited[nx][ny][cnt]{
                    visited[nx][ny][cnt] = true
                    q.append([nx,ny,cnt,length+1])
                }
            }
        }
    }
}
visited[0][0][0] = true
bfs()
print(ans)

'Problem Solving > BOJ' 카테고리의 다른 글

[17071] 숨바꼭질 5  (0) 2022.10.12
[2206] 벽 부수고 이동하기  (0) 2022.10.06
[4179] 불!  (0) 2022.09.29
[2294] 동전 2  (0) 2022.09.22
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20

https://www.acmicpc.net/problem/4179

 

4179번: 불!

입력의 첫째 줄에는 공백으로 구분된 두 정수 R과 C가 주어진다. 단, 1 ≤ R, C ≤ 1000 이다. R은 미로 행의 개수, C는 열의 개수이다. 다음 입력으로 R줄동안 각각의 미로 행이 주어진다.  각각의 문

www.acmicpc.net

정답에 닿을 듯 말듯해서 더욱 고생했던 문제다. 메모리 초과를 마주해서 고생하고, 이후 오답으로 인해 한번 더 고생했다.

 

풀이

문제를 보면 알겠지만 bfs를 두번 수행해야 한다. 본인도 거기까지는 생각했으나, 불의 경로와 지훈이의 경로를 어떻게 합칠 수 있을까에 대한 고민으로 시간을 소모했다. 항상 bfs문제를 만날 때면 Bool값을 통해 방문을 체크하는 visited 배열을 사용하는데, 이번 문제의 핵심은 해당 장소를 몇 초 후에 방문하는가를 저장하는 것이다. 불에 대한 경로탐색을 수행하고 나면 각 좌표에 몇 초 후에 불이 도달하는지 기록되어있다. 이후 지훈이의 경로탐색을 수행하여 불보다 먼저 해당 장소에 도착하는지를 확인하여 배열의 모서리까지 도달하면 된다.

메모리 초과가 일어났던 이유

지훈이의 경로탐색이 수행될 때, 해당 좌표의 방문 여부를 확인하지 않아서 방문했던 좌표도 큐에 담아버려 메모리 초과가 일어났다. 이후 지훈이의 경로탐색에 필요한 check:[Bool] 배열을 통해 방문하지 않았던 배열에 대해서만 경로탐색을 수행하도록 수정하였다.

오답 판정에 대한 이유

이후 71%에서 계속 오답이 발생했다. 원인은 또다시 지훈이의 경로탐색 부분에서 발생했는데, 단순히 불이 도달하는 시간보다 지훈이의 시간이 더 낮은 경우에 대해서만 경로탐색을 수행했으나( if visited[x][y] > current_time ), 애초에 해당 좌표에 불이 번지는 장소가 아닌 경우에는 visited[x][y]의 값은 0이다. 따라서 방문이 가능한 부분인데도 해당 부분에 대해서는 경로탐색을 수행하지 않아 올바르지 않은 답을 내놓게 된다. 해당 오류는 zero_woo님의 블로그를 보고 반례를 찾아 해결할 수 있었다. (해당글)

반례

3 4
###F
.J#.
###.

정답은 2가 출력되어야 하나, 고치기 전의 코드로는 "IMPOSSIBLE"이 출력된다. 불이 번지지 않는 위치에 대한 예외처리를 하지 않아서 오답처리를 받았던 것이다.

코드

import Foundation

let rc = readLine()!.split(separator: " ").map{Int($0)!}
let r = rc[0]
let c = rc[1]

var j = [[Int]]()
var f = [[Int]]()
var map = Array(repeating: Array(repeating: "", count: c), count: r)
var visited = Array(repeating: Array(repeating: 0, count: c), count: r)
var ans = Int.max
for i in 0..<r{
    let input = readLine()!.map{String($0)}
    for k in 0..<c{
        map[i][k] = input[k]
        if map[i][k] == "J"{
            j.append([i,k])
        }else if map[i][k] == "F"{
            f.append([i,k])
        }
    }
}

let dx = [-1,1,0,0]
let dy = [0,0,-1,1]

func bfs(j:[[Int]],f:[[Int]]){
    var level = 0
    var q = f
    var dq = [[Int]]()
    var v = Array(repeating: Array(repeating: false, count: c), count: r)

    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            v[x][y] = true
            visited[x][y] = level
            for i in 0..<4{
                let nx = dx[i] + x
                let ny = dy[i] + y
                if nx<0 || nx>=r || ny<0 || ny>=c || map[nx][ny]=="#"{continue}
                if visited[nx][ny]==0 && !v[nx][ny]{
                    v[nx][ny] = true
                    q.append([nx,ny])
                }
            }
        }
        level += 1
    }
    
    level = 0
    q = j
    dq = [[Int]]()
    v = Array(repeating: Array(repeating: false, count: c), count: r)
    
    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            v[x][y] = true
            if x==0||x==r-1||y==0||y==c-1{
                ans = min(ans, level+1)
            }
            for i in 0..<4{
                let nx = curr[0]+dx[i]
                let ny = curr[1]+dy[i]
                if nx<0 || nx>=r || ny<0 || ny>=c || map[nx][ny] == "#"{ continue }
                if visited[nx][ny] > level+1 && !v[nx][ny]{
                    v[nx][ny] = true
                    q.append([nx,ny])
                }
                if visited[nx][ny]==0 && !v[nx][ny]{
                    v[nx][ny] = true
                    q.append([nx,ny])
                }
            }
        }
        level += 1
    }
}
bfs(j: j, f: f)
print(ans == Int.max ? "IMPOSSIBLE":ans)

비록 해답을 찾아보고 풀게 된 문제이지만, 그래프 탐색에 대한 시야가 아주 조금은 넓어지게 된 문제다.

'Problem Solving > BOJ' 카테고리의 다른 글

[2206] 벽 부수고 이동하기  (0) 2022.10.06
[1600] 말이 되고픈 원숭이  (2) 2022.10.05
[2294] 동전 2  (0) 2022.09.22
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20
[1890] 점프  (0) 2022.09.17

https://www.acmicpc.net/problem/15988

 

15988번: 1, 2, 3 더하기 3

각 테스트 케이스마다, n을 1, 2, 3의 합으로 나타내는 방법의 수를 1,000,000,009로 나눈 나머지를 출력한다.

www.acmicpc.net

대표적인 동적 계획법 문제이다. 동적 계획법은 항상 마주할 때마다 느끼듯이 점화식을 고민하는 데에 너무 오랜 시간이 걸린다. 더 많은 문제들을 풀어봐야 하나 보다.

 

풀이

정답을 표현할 배열 dp는 n+1개의 요소를 가지는 배열이며, dp[n]은 "1,2,3을 사용하여 n을 만들 수 있는 식의 개수"라고 정의한다.

점화식을 사용하기 위해서 배열에 최소 3개의 요소가 담겨있어야 한다. 1부터 3까지의 경우의 수를 적어보면 다음과 같다.

 

n 식의 종류
0 표현 불가능(입력범위 아님)
1 1
2 1+1, 2
3 1+1+1, 2+1, 1+2, 3

이후 1,2,3을 이용해 4를 만드는 식을 만든다고 가정할 경우, 다음과 같은 생각을 해볼 수 있다.

  1. 1을 만들 수 있는 모든 식에 "+3"을 추가한다면 4를 만들 수 있다.
  2. 2를 만들 수 있는 모든 식에 "+2"를 추가한다면 4를 만들 수 있다.
  3. 3을 만들 수 있는 모든 식에 "+1"을 추가한다면 4를 만들 수 있다.

따라서 다음과 같은 점화식을 세울수있게된다.

dp[n] = dp[n-1] + dp[n-2] + dp[n-3]

다만 문제를 보면 알 수 있듯이 n의 크기가 1,000,000이므로 정답을 출력할 때 1,000,000,009로 나눈 나머지를 출력하라고 적혀있다.

모듈러 산술 연산법칙은 분배 법칙이 성립하므로 코드를 적을 때는 다음과 같이 적는다.

 

dp[n] = ((dp[n-1]%1,000,000,009) + (dp[n-2]%1,000,000,009) + (dp[n-3]%1,000,000,009))%1,000,000,009

 

정답 코드

import Foundation

var dp = Array(repeating: 0, count: 1000001)
dp[1] = 1
dp[2] = 2
dp[3] = 4
let mod = 1000000009

for i in 4...1000000{
    dp[i] = ((dp[i-1]%mod) + (dp[i-2]%mod) + (dp[i-3]%mod))%mod
}

for _ in 0..<Int(readLine()!)!{
    let n = Int(readLine()!)!
    print(dp[n])
}

'Problem Solving > BOJ' 카테고리의 다른 글

[4179] 불!  (0) 2022.09.29
[2294] 동전 2  (0) 2022.09.22
[1890] 점프  (0) 2022.09.17
[6588] 골드바흐의 추측  (0) 2022.09.16
[1158] 요세푸스 문제  (0) 2022.01.20

https://www.acmicpc.net/problem/1890

 

1890번: 점프

첫째 줄에 게임 판의 크기 N (4 ≤ N ≤ 100)이 주어진다. 그 다음 N개 줄에는 각 칸에 적혀져 있는 수가 N개씩 주어진다. 칸에 적혀있는 수는 0보다 크거나 같고, 9보다 작거나 같은 정수이며, 가장

www.acmicpc.net

풀이

동적 계획법 문제이다. 처음에는 깊이 우선 탐색으로 시도했으나 시간 초과가 발생. 동적 계획법 문제로 분류되어있는 걸 확인하고 난 후 고민했으나, 결국 다른 사람의 풀이과정을 이해하고 코드를 다시 작성하였다.

 

시간 초과 코드

import Foundation

let n = Int(readLine()!)!

var map = Array(repeating: [Int](), count: n)
for i in 0..<n{
    map[i] = readLine()!.split(separator: " ").map{Int($0)!}
}
var ans = 0

func dfs(x:Int, y:Int){
    if map[x][y]==0{
        if x==n-1 && y==n-1{
            ans += 1
        }
        return
    }
    let length = map[x][y]
    if x+length<n{
        dfs(x: x+length, y: y)
    }
    if y+length<n{
        dfs(x: x, y: y+length)
    }
}
dfs(x: 0, y: 0)
print(ans)

 

해결 코드

import Foundation

let n = Int(readLine()!)!

var map = Array(repeating: [Int](), count: n)
var dp = Array(repeating: Array(repeating: 0, count: n), count: n)
for i in 0..<n{
    map[i] = readLine()!.split(separator: " ").map{Int($0)!}
}

dp[0][0]=1
for x in 0..<n{
    for y in 0..<n{
        if map[x][y]==0 || dp[x][y]==0{continue}
        let nx = x + map[x][y]
        let ny = y + map[x][y]
        if nx<n{ dp[nx][y] += dp[x][y]}
        if ny<n{ dp[x][ny] += dp[x][y]}
    }
}
print(dp[n-1][n-1])

'Problem Solving > BOJ' 카테고리의 다른 글

[2294] 동전 2  (0) 2022.09.22
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20
[6588] 골드바흐의 추측  (0) 2022.09.16
[1158] 요세푸스 문제  (0) 2022.01.20
[1021] 회전하는 큐  (0) 2022.01.19

https://www.acmicpc.net/problem/6588

 

6588번: 골드바흐의 추측

각 테스트 케이스에 대해서, n = a + b 형태로 출력한다. 이때, a와 b는 홀수 소수이다. 숫자와 연산자는 공백 하나로 구분되어져 있다. 만약, n을 만들 수 있는 방법이 여러 가지라면, b-a가 가장 큰

www.acmicpc.net

풀이

소수 판별 문제이다. 문제를 보고서 소수 배열과 투 포인터를 사용하여 풀이를 제출했으나 시간 초과가 나왔다.

문제를 해결하기 위해서는 소수 배열의 작은 숫자부터 n의 차를 구한 수를 소수인지 판별하는 방법으로 해결해야 한다는 게시글을 보고서 다시 제출하였더니 통과했다.

 

시간 초과 코드

import Foundation

var numbers = Array(repeating: true, count: 1000001)
var primes = [Int]()
for i in 0..<1000001{
    if i <= 1{
        numbers[i] = false
        continue
    }
    if numbers[i]{
        if i != 2{
            primes.append(i)
        }
        for k in stride(from: i+i, to: 1000001, by: +i){
            numbers[k] = false
        }
    }
}

while let input = readLine(){
    let n = Int(input)!
    if n == 0{ break }
    
    var flag = false
    var start = 0
    var end = primes.count-1
    while start < end{
        let sum = primes[start] + primes[end]
        if sum > n{
            end -= 1
            continue
        }else if sum == n{
            flag = true
            break
        }else{
            start += 1
        }
    }
    if flag{
        print(n,"=",primes[start],"+",primes[end])
    }else{
        print("Goldbach's conjecture is wrong.")
    }
}

 

해결 코드

import Foundation

var primes = Array(repeating: true, count: 1000001)

for i in 0..<1000001{
    if i <= 1{
        primes[i] = false
        continue
    }
    if primes[i]{
        for k in stride(from: i+i, to: 1000001, by: +i){
            primes[k] = false
        }
    }
}

while let input = readLine(){
    let n = Int(input)!
    if n == 0{ break }
    
    var flag = false
    for i in 3..<1000001{
        if primes[i]{
            let chk = n-i
            if primes[chk]{
                flag = true
                print(n,"=",i,"+",chk)
                break
            }
        }
    }
    if !flag{
        print("Goldbach's conjecture is wrong.")
    }
}

 

심지어 문제를 풀고나서 알아낸 점은 같은 소수의 구성으로도 n을 만들 수 있다면 정답이 될 수 있다는 것이다. 투 포인터를 사용한 코드를 적을 때에 당연히 서로 다른 소수라는 가정을 한 채로 작성했었다. 대표적인 예시가 6을 입력할 경우 투 포인터를 사용한 코드는 불가능한 조합이라는 결과를 출력하지만, 통과했던 코드는 3 + 3 조합을 출력하게 된다.

'Problem Solving > BOJ' 카테고리의 다른 글

[2294] 동전 2  (0) 2022.09.22
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20
[1890] 점프  (0) 2022.09.17
[1158] 요세푸스 문제  (0) 2022.01.20
[1021] 회전하는 큐  (0) 2022.01.19

subscribe( on: (Event<type>) -> Void)

Rx의 여러 오퍼레이터로 이루어진 스트림의 마지막엔 subscribe()을 통해 가공된 데이터를 확인하게 된다. 여기서 Event<타입>형태의 데이터가 들어오게 되고, Event는 next, error, completed 3가지의 값을 가질 수 있다. 

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe({event in
        switch event{
        case .next:
            break
        case .error:
            break
        case .completed:
            break
        }
    })
    .disposed(by: disposeBag)

next

스트림에서 넘어온 자료가 있는 경우, next에 해당한다.

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe({event in
        switch event{
        case .next(let str):
            print("next: \(str)")    //자료 출력
            break
        case .error(let err):
            break
        case .completed:
            break
        }
    })
    .disposed(by: disposeBag)
    
// next: RxSwift
// next: In
// next: 4
// next: Hours

error

스트림에서 에러가 발생한 경우, error의 값을 가지게 된다.

Observable.from(["RxSwift", "In", "4", "Hours"])
    .single()    //하나의 데이터만 들어와야함.
    .subscribe({event in
        switch event{
        case .next(let str):
            print("next: \(str)")
            break
        case .error(let err):
            print("err: \(err.localizedDescription)")
            break
        case .completed:
            print("completed")
            break
        }
    })
    .disposed(by: disposeBag)
    
// next: RxSwift
// err: The operation couldn’t be completed. (RxSwift.RxError error 5.)

 

single()오퍼레이터의 경우 하나의 데이터만 입력을 받아야 하는데, 스트림의 앞쪽에 위치한 from() 오퍼레이터의 경우 이벤트가 여러번 발생되기 때문에 에러가 발생하게된다. 이렇게 고의로 에러를 발생시켜보면 에러의 내용이 출력되는 모습을 확인할 수 있고, completed 될 수 없다고 나온다. 즉 에러가 발생하면 completed 상태에 들어가지 않고 스트림은 마무리된다.


completed

해당 스트림이 에러없이 모든 이벤트들을 내보냈을경우 completed 값을 가지게 된다.

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe({event in
        switch event{
        case .next(let str):
            print("next: \(str)")
            break
        case .error(let err):
            print("err: \(err.localizedDescription)")
            break
        case .completed:
            print("completed")
            break
        }
    })
    .disposed(by: disposeBag)
    
// next: RxSwift
// next: In
// next: 4
// next: Hours
// completed

해당 코드를 실행하게되면 from에 의해 생성된 4개의 자료들이 출력되고, 마지막으로 "completed"가 출력되는 모습을 볼 수 있다. 

즉 에러가 발생하지 않는다면, 발생되는 모든 이벤트를 처리하고 마지막으로 completed 상태가 되면서 마무리가 된다.


subscribe(onNext:  , onError: , onCompleted:  , onDisposed:  )

subscibe(_ on: )의 경우, switch문을 통해 event의 모든 조건에 대한 처리를 해주어야 했다면, subscribe(onNext:  ,onError:  ,onCompleted:  ,onDisposed:  )의 경우 원하는 경우의 분기만 골라서 작성이 가능하게 해주는 오퍼레이터이다.

또 다른 차이점은 onDisposed가 추가되었다는 점이다. 

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)
    
// RxSwift
// In
// 4
// Hours

모든 경우의 값에 대해 작성해야 하는 subscribe( _ on:  )과 달리 필요한 부분만 이벤트 처리하여 더 간결한 코드 작성이 가능하다.

Observable.from(["RxSwift", "In", "4", "Hours"])
    .single()
    .subscribe(onNext: { str in
        print(str)
    }, onError: { err in
        print(err.localizedDescription)
    }, onCompleted: {
        print("completed")
    }, onDisposed: {
        print("disposed")
    })
    .disposed(by: disposeBag)
    
// RxSwift
// The operation couldn’t be completed. (RxSwift.RxError error 5.)
// disposed
Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe(onNext: { str in
        print(str)
    }, onError: { err in
        print(err.localizedDescription)
    }, onCompleted: {
        print("completed")
    }, onDisposed: {
        print("disposed")
    })
    .disposed(by: disposeBag)
    
// RxSwift
// In
// 4
// Hours
// completed
// disposed

예제 코드에서 알 수 있듯, 스트림은 마지막으로 disposed 되면서 마무리된다. 강의에서 말하길, 많은 개발자들이 흔히 하는 실수로 프로세스의 종료처리를 onCompleted 부분에서 작성하게 되는데, 이렇게 되면 에러 발생시에 onCompleted 상태에 들어가지 않게 되어 프로세스의 종료처리가 일어나지 않게 된다. 따라서 프로세스의 마무리 처리를 하려면 onDisposed 부분에서 작성해야 한다.

 

[참고영상]

[유튜브] 곰튀김 RxSwift 4시간에 끝내기 - 7

'iOS > RxSwift' 카테고리의 다른 글

scheduler  (0) 2022.08.31
operator  (0) 2022.07.21
subscribe, dispose  (0) 2022.06.15
Observable stream  (0) 2022.06.08
ReactiveX  (0) 2022.05.30

operator

Observable이 수행하는 메소드. Observable에서 파생되는 스트림을 구성하는 rxSwift의 핵심요소라고 할 수 있다. 

Observable에는 수행가능한 여러가지 operator가 있는데, 그중 대표적인것들에 대해서 적어보려고 한다. 

 

just()

static func just(_ element: [String]) -> Observable<[String]>
//returns an Observable sequence that contains a single element
Observable.just(_ element: )

입력 요소에 대해 그대로 반환되는 연산자이다. 예시를 통해 보자. 

@IBAction func exJust1() {
    Observable.just("Hello World")
        .subscribe(onNext: { str in
            print(str)
        })
        .disposed(by: disposeBag)
}
//Hello World!    출력

@IBAction func exJust2() {
    Observable.just(["Hello", "World"])
        .subscribe(onNext: { arr in
            print(arr)
        })
        .disposed(by: disposeBag)
}
//["Hello","World"]    출력

배열을 넣어도 배열이 통째로 출력되는 모습을 볼 수 있다.


from()

public static func from(_ array: [Element]) -> Observable<Element>
//Converts an array to an observable sequence.
Observable.from(_ array: [_])

배열의 원소들을 Observable의 형태로 리턴해준다.

@IBAction func exFrom1() {
    Observable
        .from(["RxSwift", "In", "4", "Hours"])
        .subscribe(onNext: { str in
            print(str)
        })
        .disposed(by: disposeBag)
}
//RxSwift
//In
//4
//Hours 출력

from을 통해 나오는 Observable<String>형태의 원소들이 subscribe(OnNext: )부분에서 출력이 된다.


map()

func map<Result>(_ transform: @escaping (String) throws -> Result) -> Observable<Result>
//Projects each element of an observable sequence into a new form.

swift에서의 map()과 같은 동작을 하는 operator이다. Observable에서 생성되어 내려오는 데이터들에 closure에 적힌 동작에 따라 가공하여 리턴해준다. 

@IBAction func exMap1() {
    Observable.just("Hello")
        .map { str in "\(str) RxSwift" }
        .subscribe(onNext: { str in
            print(str)
        })
        .disposed(by: disposeBag)
}
//Hello RxSwift 출력

@IBAction func exMap2() {
    Observable.from(["RxSwift", "ReactiveX"])
        .map { $0.count }
        .subscribe(onNext: { str in
            print(str)
        })
        .disposed(by: disposeBag)
}
//7
//9 출력

아마도 가장 많이 쓰게될 operator가 아닐까 생각된다.


filter()

func filter(_ predicate: @escaping (Int) throws -> Bool) -> Observable<Int>
//Filters the elements of an observable sequence based on a predicate.

swift의 filter()함수와 동일하게 동작하는 오퍼레이터이다. 서술한 조건에 맞는 경우에만 Observable을 내보낸다.

@IBAction func exFilter() {
    Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
        .filter { $0 % 2 == 0 }
        .subscribe(onNext: { n in
            print(n)
        })
        .disposed(by: disposeBag)
}
//2
//4
//6
//8
//10 출력

배열요소들 중에 짝수인 요소만 출력된다.


stream

우리는 이러한 여러가지 오퍼레이터들을 통해 Observable을 가공하여 비동기 프로그래밍을 할 수 있게 된다. 앞의 글에서도 설명했지만 이러한 가공의 과정을 stream이라 부르기로 했고, 이러한 Observable을 통한 stream을 통해 비동기 프로그래밍을 할 수 있게 해주는 것이 RxSwift이다.

@IBAction func exMap3() {
    Observable.just("800x600")
        .map { $0.replacingOccurrences(of: "x", with: "/") }    //"800/600"
        .map { "https://picsum.photos/\($0)/?random" }          //"https://picsum.photos/\800/600/?random"
        .map { URL(string: $0) }                                //URL?
        .filter { $0 != nil }
        .map { $0! }                                            //URL!
        .map { try Data(contentsOf: $0) }                       //Data
        .map { UIImage(data: $0) }                              //UIImage?
        .subscribe(onNext: { image in
            self.imageView.image = image
        })
        .disposed(by: disposeBag)
}

Observable이 여러가지 오퍼레이터를 통해 어떤식으로 변화하는지 주석으로 적어놓았다. 

 

이외에도 정말 많은 오퍼레이터들이 있으며, 사용처에 따라 알맞은 오퍼레이터를 사용하면 된다. 각 오퍼레이터에 대한 상세한 내용들도 홈페이지에서 열람 가능하다.

 

[참고영상]

[유튜브] 곰튀김 RxSwift 4시간에 끝내기 - 4

[유튜브] 곰튀김 RxSwift 4시간에 끝내기 - 5

'iOS > RxSwift' 카테고리의 다른 글

scheduler  (0) 2022.08.31
next, error, complete  (0) 2022.08.19
subscribe, dispose  (0) 2022.06.15
Observable stream  (0) 2022.06.08
ReactiveX  (0) 2022.05.30

+ Recent posts