[iOS] Swift의 고차함수2 (compactMap, flatMap, forEach)

이전글에서 가장 많이 사용하는 고차함수들인 map, filter, reduce에 대해서 알아봤다. 이번 글에서는 상대적으로 덜 사용하지만, 그래도 자주 사용하는 고차함수들에 대해 알아보려고 한다.

 

 

CompactMap

 

compactMap은

'컨테이너의 각 요소에 조건을 지정하여 호출할 때, nil이 아닌 배열반환'하는 함수이다.

 

compactMap을 설명하기 앞서 아래 가정을 한번 살펴보자.

 

우선 nil 값이 있는 학생들의 이름을 모아둔 배열이 있다고 가정할 때

let students: [String?] = ["Greed", "Moana", "Zerom", nil, "Musk", nil]
let iOSStudents = students.map { "iOS_" + $0 }

 

만일, map을 통해 각자의 이름 앞에
"iOS_"를 붙이면 어떻게 될까?

 

 

바로 에러가 발생하게 된다.

 

그 이유는 students 안에 있는 요소들이 모두 옵셔널타입이기 때문이다.

 

let students: [String?] = ["Greed", "Moana", "Zerom", nil, "Musk", nil]
let iOSStudents = students.map { student -> String? in
    if let name = student {
        return "iOS_" + name
    } else {
        return nil
    }
}

 

만약 여기서 이를 해결하기 위해 옵셔널 바인딩을 사용한다면

복잡한 코드를 만들어줘야 한다.

 

 

BUT!

여기서 map이 아닌 compactMap을 쓴다면??

let students: [String?] = ["Greed", "Moana", "Zerom", nil, "Musk", nil]
let iOSStudents = students.compactMap { $0 }.map { "iOS_" + $0 }

print(iOSStudents) // ["iOS_Greed", "iOS_Moana", "iOS_Zerom", "iOS_Musk"]

 

compactMap에 의해 옵셔널 바인딩을 하면서

nil 값이 제외된 새로운 컬렉션을 만들 수 있다.

 

여기에 map을 활용해 내가 원하는 형태로 데이터를 가공하면 된다.

 그럼 원래 원하던 형태로 결과값을 얻을 수 있다.

 

 

다시 compactMap을 정리해자면

nil 값을 제거하고

옵셔널 바인딩을 해서 반환해 주는 함수이다.

 

 

 

flatMap

 

flatMap은

'컨테이너의 각 요소를 사용하여 지정된 조건을 호출할 때, 순차적인 결과의 배열을 반환'하는 함수이다.

 

순차적인 결과의 배열을 반환한다??

정의만으로는 이해가 안 되는 사람들이 많을 것이다.

 

바로 코드를 통해 알아보자.

let numbers = [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]

let flatMapped = numbers.flatMap { $0 }
print(flatMapped)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 

코드를 보면 2차원 배열이 있다.

2차원 배열에 flatMap 함수를 적용하니

1차원 배열반환되는 것을 알 수 있다.

 

 

아 그래서 flat인 건가? 싶은 사람도 있을 것이다.

추측으로는 그렇다.

평탄화(flatten)를 해주기 때문에 이런 이름으로 지어진 것 같다.

 

 

 

알아둬서 나쁘지 않은 정보가 있다.

궁금하다면 아래 접은 글을 펼쳐서 보길 바란다!

더보기

바로 flatMap이 compactMap과 같은 역할을 했다는 것이다.

 

?????

같은 역할을 했다고??

???

 

그렇다. 또한 했다는 포인트에서 알 수 있듯

과거형이다.

 

swift 4.1 버전 전까지는 flatMap 역시

1. nil 값을 제거

2. 옵셔널 바인딩

을 해주는 역할도 하였다.

(+ 본인의 역할인 평탄화 역할도)

 

지금 사용하면 어떻게 되는데??라고 의문이 들 텐데

아래 사진과 같이 된다.

Swift 4.1 버전 이후부터는 flatMap의 해당 기능이 deprecated 되었다고 알고 있다.

또 compactMap을 사용하라고 주의 메시지가 뜬다.

그리고 애플 또한 compactMap을 사용할 것을 권장한다.

 

 

그럼 만약 nil 값을 가지고 있는 2차원 배열이라면 어떻게 해야 할까???

 

let numbers = [[1], [nil, 3], [4, nil, 6]]

let flatMapped = numbers.flatMap { $0 }.compactMap { $0 }
print(flatMapped)
// [1, 3, 4, 6]

 

flatMap을 통해 평탄화를 해주고

compactMap을 사용하여

옵셔널 바인딩nil 값제거해 주면 된다.

 

 

그렇다면 3차원 배열에 flatMap을 적용하면???

let numbers = [[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]]

let flatMapped = numbers.flatMap { $0 }
print(flatMapped)
// [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]

 

2차원 배열로 바뀌게 된다.

 

즉, 3차원 배열에 flatMap을 2번 사용하면

평평한 배열(1차원 배열)이 되게 된다.

 

 

 

ForEach

 

forEach는

'for in 문과 동일한 순서로 컨테이너의 각 요소에 대해 주어진 클로저를 호출'하는 함수이다.

 

 

즉 반복문이라는 소리다!

 

let nums: [Int] = [1, 2, 3, 4]
 
nums.forEach {
    print($0)       // 1 2 3 4
}

 

for in 문과 같이 반복문으로 사용할 수 있다.

 

하지만 차이점은 존재한다.

우선, for문은 몇 번 출력할지 제어할 수 있다.

하지만 forEach는 컨테이너의 각 요소를 클로저에 던져 이용하기 때문에

요소만큼 출력된다.

 

즉, forEach문은 반복 횟수를 제어할 수 없다!!!

 

let nums: [Int] = [1, 2, 3, 4]

for num in nums {
    if num == 3 { break }
    print(num)      // 1 2
}
 
nums.forEach {
    if $0 == 3 { break }
    print($0)       // 1 2 3 4
}

 

또한, forEach문은 반복문이 아닌 클로저를 사용하기 때문에

break, continue 사용이 불가능하고

return을 사용해도 클로저에 대한 호출만 종료될 뿐

마지막 요소까지 계속 호출된다.