Map vs CompactMap

같지만 다른 두 메소드에 대해 자세히 알아봅시다.

이번시간에는 map의 특징에 대해서 알아보는 시간을 갖도록 하겠습니다. swit 4.1에서는 flatMap은 제외가 되었으니 여기서는 두개의 메소드에 대해서만 글을 쓰도록 하겠습니다.

정의

  1. Map: 시퀀스 요소에 대해 주어진 클로저를 매핑 한 결과가 포함 된 배열을 반환합니다.
  2. 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에 대해 알아봤습니다.

26 Shares:
You May Also Like
Read More

Swift 메모리 관리 – 클로저 편

스위프트의 캡쳐 목록은 클로저 코드 내부에 폐쇄된 공간의 매개 변수 목록 앞에 표시되며, 메모리 참조환경에서 강한참조(strong), 약한참조(weak), 미소유(unowned) 참조 메모리값을 캡쳐합니다.
Read More

클래스 vs 구조체

클래스와 구조체의 차이점은 무엇이 있을까요? Swift에서는 두 타입간에 매우 비슷합니다. 자 그럼 두 타입을 비교분석해보도록 합시다.
Read More

Swift 메모리 관리 – 클래스편

해당 포스트에서는 Apple의 메모리 관리에 대해 설명합니다. 대부분 ARC를 사용하여 자동으로 메모리를 처리한다고 하더라도 여전히 몇 가지 함정이 있습니다. 객체간의 설명하는 올바른 참조 유형을 선택하면 메모리 누수를 막을 수 있습니다.