이번시간에는 map의 특징에 대해서 알아보는 시간을 갖도록 하겠습니다. swit 4.1에서는 flatMap은 제외가 되었으니 여기서는 두개의 메소드에 대해서만 글을 쓰도록 하겠습니다.
정의
- Map: 시퀀스 요소에 대해 주어진 클로저를 매핑 한 결과가 포함 된 배열을 반환합니다.
- CompactMap: 주어진 호출에서 nil를 제외한 결과를 배열로 반환하며, 주어진 순서에 따라 각 요소로 변환합니다.
결론적으로, 3가지 메소드들은 클로저를 통해서 맵핑 한 결과를 배열로 반환한다는 원리입니다. 그렇다면, Map 과 FlatMap은 동일하게 nil을 포함한 결과로 반환한다는 점인데, 어떤점이 다를까요?
Map vs CompactMap
둘간의 차이점은 배열의 nil을 제외한 결과 인지, 아닌지에 대해서 반환한다는점입니다. 이부분은 문서에도 명시하였으며, 다른 블로그에서도 자세히 설명이 되었습니다.
import Foundation
let list = ["1", "2", "삼", "사", "오"]
let mapResult = list.map{ Int($0) }
let compackMapResult = list.compactMap { Int($0) }
let result: Bool = {
return mapResult == compackMapResult
}()
print(result) // False
만약에, 둘다 클로저에서 nil이 발견되지 않고 결과를 반환하였을 때, 동일 할까요?
문서의 예제에 나왔듯이, 일반적인 스위프트의 리스트(배열)에서는 둘간의 차이점은 위에서 언급한 nil을 제외시키냐 아니면 포함시키냐 입니다.
예를 들어, nil을 제외하지 않았는 경우에 두개의 값을 비교해봅시다.
import Foundation
let list = ["1", "2", "3", "4", "5"]
let mapResult = list.map{ Int($0) }
let compackMapResult = list.compactMap { Int($0) }
let result: Bool = {
return mapResult == compackMapResult
}()
print(result) // True
위에서 언급한 스위프트에서 제공하는 배열을 사용했을 경우, 둘의 결과 값은 동일합니다.
구조체를 만들어서 사용하는 경우의 예를 들어봅시다.
struct CustomList {
private var list = [Int]()
init(_ list: [Int]) {
self.list = list
}
mutating func pop() -> Int? {
if list.isEmpty {
return nil
}
return list.removeLast()
}
}
extension CustomList: Collection {
public typealias Index = Int
public var startIndex: Int {
return list.startIndex
}
public var endIndex: Int {
return list.endIndex
}
public subscript(i: Int) -> Int {
return list[i]
}
public func index(after i: CustomList.Index) -> CustomList.Index {
return list.index(after: i)
}
}
CustomList 구조체하나를 만들었습니다. 그리고 map과 compactMap을 사용할 수 있도록, Collection 프로토콜을 구현했습니다.
let list = CustomList([1, 5, 4, 3, 2])
let map = list.map { $0 } // [1, 5, 4, 3, 2]
let compactMap = list.compactMap { $0 } // [1, 5, 4, 3, 2]
let result: Bool = {
return map == compactMap
}()
print(result) // true
해당 결과 또한 두개의 값은 동일합니다.
여기서 CustomList에 추가로 반복기프로토콜과 시퀀스 프로토콜(IteratorProtocol, Sequence)를 추가해봅시다.
extension CustomList: IteratorProtocol {
mutating func pop() -> Int? {
if list.isEmpty {
return nil
}
return list.removeLast()
}
mutating func next() -> Int? {
return pop()
}
}
extension CustomList: Sequence {
func makeIterator() -> CustomList {
return self
}
}
IteratorProtocol 에서는 시퀀스와 달른 프로세스인 뒤에서 앞으로 반복하며 배열을 만드는 메서드를 사용해봅시다.
let list = CustomList([1, 5, 4, 3, 2])
let map = list.map { $0 } // [1, 5, 4, 3, 2]
let compactMap = list.compactMap { $0 } // [2, 3, 4, 5, 1]
let result: Bool = {
return map == compactMap
}()
print(result) // false
두개의 결과는 다르게 나옵니다.
이유는 프로토콜을 준수하는 확장이 다르기 때문입니다.
- map: Collection 프로토콜의 확장
- compactMap: Sequence 프로토콜의 확장이 있는 없는 경우, Collection 프로토콜로 대체
Sequence 프로토콜만 만족시키게 되면 결국, map과 compactMap은 위의 언급한 첫번째 차이점 처럼 nil 제외 유무밖에 없습니다. 반드시 두개의 성격을 다르게 구현하기 위해서는 Sequence, IteratorProtocol 두가지 모두 구현해야 다르게 돌아가게 됩니다.
결론
map 과 compactMap을 사용하려면 기본적으로 collection 프로토콜이 구현해야 합니다.
만약 LinkedList, Stack 등과 같이 데이터 순회하는 방식이 다르게 처리해야할 경우에는, 추가적으로 Sequence, IteratorProtocol 구현해야하며, compactMap을 사용해야합니다.
이상 Map과 CompactMap에 대해 알아봤습니다.