티스토리 뷰

Tech/Swift

map, flatMap, compactMap 차이점

Ellie Kim 2019. 12. 3. 06:34

map(), compactMap(), flatMap() 모두 비슷하게 들리지만, 다르게 사용되어 정리해보려 합니다.  

세 단어 모두 map이라는 단어를 포함하고 이는 어떤 것에서 다른 것으로 변환한다는 의미로 사용됩니다.

 

먼저 map을 살펴보겠습니다.

numbers의 정수를 두배를 해주는 작업을 해봅니다.

let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }

numbers배열의 각 값을 가져와 클로저를 통해 실행합니다.

여기서 $0는 numbers의 각 숫자를 나타냅니다.

따라서 1,2,3,4,5 각각에 2를 곱해주는 것입니다.

이 경우 배열에서 숫자를 가져와 두 배로 늘리고 새 배열에 다시 넣는 것을 의미합니다.

doubled에는 1,2,3,4,5 각각에 2를 곱해준 [2,4,6,810]이 있습니다.

 

모든 데이터 유형에서 가능하므로 문자열 배열을 대문자로 변형해줍니다.

let wizards = ["Harry", "Hermione", "Ron"]
let uppercased = wizards.map { $0.uppercased() }

그럼 대문자로 모두 변경됩니다.

map은 원래 사용한 타입과 다른 타입을 반환할 수도 있습니다.

 

아래 예는 정수를 문자열 배열로 변환하는 경우입니다.

let numbers = [1, 2, 3, 4, 5]
let strings = numbers.map { String($0) }

strings에는 ["1", "2", "3", "4", "5"]가 담겨있습니다.

 

하지만 반대로 문자열을 정수로 변형하면 약간 까다로워집니다.

예를 들어 "1", "5", "500"등은 모두 안전하게 정수로 변환할 수 있지만, 

"fish"는 문자열을 정수로 변환이 불가합니다.

 

결과적으로 문자열을 정수로 변환하려면 옵셔널 정수가 반환됩니다.

let maybeNumbers = strings.map { Int($0) }

옵셔널인 이유는 fish와 같이 정수로 변환 불가한 것이 존재하기 때문입니다.


옵셔널을 편하게 처리하기 위해서 compactMap가 존재합니다.

문자열을 정수로 변형하는 경우는 compactMap을 사용하면 모든 옵셔널을 언래핑하고 nil도 포함하지 않습니다.

따라서 동일한 문자열을 정수로 변환하지만 옵셔널이 아닌 그냥 정수 배열을 생성합니다.

let definitelyNumbers = strings.compactMap { Int($0) }

스위프트는 옵셔널들을 반환하거나 try?, as? 나 문자열에서 정수를 생성하는 것 같이 초기화 실패할 가능성이 있는 것들이 많습니다. 

그때 compactMap을 사용하면 됩니다.

 

예를 들어 UIView에 imageView인 하위 뷰를 모두 읽어 들이고 싶다면 아래와 같이 사용할 수 있습니다.

let imageViews = view.subviews.compactMap { $0 as? UIImageView }

또는 문자열 배열이 있고 어떤 문자열이 유효한 URL인지 파악하고 싶다면 아래와 같이 사용할 수 있습니다.

let urls = urlStrings.compactMap { URL(string: $0) }

 

즉 map은 컨테이너에서 값을 가져와 지정한 코드를 사용해 변환하고 다시 컨테이너에 넣는 방법이며,

compactMap은 위와 동일한 작업을 수행하지만 optional이 있다면 언래핑하고 어떠한 nil을 포함하지 않습니다.


값이 있는 경우에만 변환 - 옵셔널 map() 작동 방식

옵셔널은 배열과 유사합니다. 둘 다 어떤 값을 갖는 컨테이너입니다. 

컨테이너 내부를 살펴보면(옵셔널을 벗겨내면) 값이 있거나 없을 수 있습니다. 

 

값을 컨테이너에서 꺼내고 (옵셔널), 제공하는 클로저로 변환 한 다음 컨테이너에 다시 넣습니다 (다른 옵셔널).

옵셔널이 비어있으면 map()은 자동적으로 아무것도 하지 않고 nil을 보냅니다. 

 

이를 설명하기 위해 getUser(id: ) 메서드가 있다고 가정합시다.

이는 정수를 받고 해당 ID가 있는 사용자의 이름을 리턴하는 메서드입니다. 

만약 해당 ID가 존재하지 않는다면 nil을 반환하기 때문에 이 메서드는 옵셔널 문자열을 반환합니다.

let name: String? = getUser(id: 97)
let greeting = name.map { “Hi, \($0)” }
print(greeting ?? “Unknown user”)

만약 name이 문자열을 가진다면 map()은 옵셔널에서 문자열을 가져와

Hi, 그리고 이름을 옵셔널로 다시 변환해 다시 greeting에 저장합니다.

 

값을 다시 옵셔널로 설정하여 값이 있을 수도 있고 없을 수도 있는 상황이 더 길어지고

나중에서야 코드가 의미하는 바를 결정할 수 있습니다.

이 경우에는 print() 기능에서야 인사말을 출력하거나 알 수 없는 사용자를 출력할 수 있습니다.


flatMap() - 변환 후 평탄화 (단순화) 

let number: String? = getUser(id: 97)
let result = number.map { Int($0) }

단계별로 살펴봅시다.

number은 옵셔널 스트링입니다.

map()은 옵셔널에서 값을 가져와 변환합니다.

이 경우 Int($0)이 문자열을 옵셔널 정수로 변환합니다.

왜 옵셔널 정수일까요? 아까 말했듯이 "fish"와 같이 정수로 변형 불가능한 게 있기 때문입니다.

그리고 map()은 옵셔널 값을 다른 옵셔널 값으로 되돌립니다. 

 

따라서 코드를 실행하면 result는 정수도 아니고, 옵셔널 정수(Int?)도 아니고

옵셔널 옵셔널 정수(Int??)가 될 것입니다.

 

명확하게, 옵셔널 옵셔널의 의미는 

- 외부 옵셔널이 존재할 수 있으며 내부 옵셔널이 존재할 수 있습니다.

- 외부 옵셔널은 존재하지만 내부 옵셔널은 nil일 수 있습니다. 

- 외부 옵셔널은 nil 일 수 있으며, 내부 옵서녈이 없습니다.

 

옵셔널 옵셔널이 혼란스럽기 때문에 flatMap()이 존재합니다.

flatMap()은 optional optional을 optional으로 만들어 줍니다.

 

즉 모든 것이 존재하거나 존재하지 않습니다.

이중 옵셔널을 단일 옵셔널로 만들어 줍니다.

궁극적으로 우리는 외부 또는 내부 옵셔널이 존재하는지 여부에 신경 쓰지 않아 flatMap()을 유용하게 사용할 수 있습니다.

let number: String? = getUser(id: 97)
let result = number.flatMap { Int($0) }

결과적으로 이 코드는 result가 Int?? 가 아닌 Int? 가 됩니다. 

 

resource: https://www.hackingwithswift.com/articles/205/whats-the-difference-between-map-flatmap-and-compactmap

'Tech > Swift' 카테고리의 다른 글

문자열 결합  (0) 2020.01.05
Int to String 그리고 String to Int  (0) 2020.01.05
스위프트 문자열에서 숫자 뽑아내기  (0) 2019.10.16
스위프트 Generic  (0) 2019.10.13
스위프트 정렬 sort sorted차이점  (0) 2019.09.17
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함