[Swift] 고차함수 개념 완전 정복하기: map, filter, reduce

2022. 1. 24. 20:02Swift, iOS Foundation

오늘 글에서는 Swift의 고차 함수(Higher-order function)에 대해서 배워보도록 하자.

고차 함수(Higher-order function)다른 함수를 전달인자(매개변수)로 받거나, 함수 실행의 결과를 함수로 반환하는 함수를 의미한다.
어 잠깐, 함수를 인자로 전달될 수 있고, 함수의 반환값이 될 수 있다는 말,,, 어디서 들어본 적이 있지 않는가....?
예전 내가 올린 글을 읽었으면 알겠지만, 이 부분은 클로저와 Swift의 일급객체(first-class object)라는 특성과 연관되어 있다!

(오랜만에 아래 글을 참고해서 읽어볼까..?)

 

[Swift] Closure 완전 정복하기: 일급 객체부터 작성법, 그리고 @escaping까지

1. 클로저(Closure)란? 솝트에서 서버 통신을 처음 배우다가 마주친 어려운 개념 2개가 있었다. 그중 하나가 Escaping Closure(탈출 클로저)였는데 (당연히, 클로저를 모르는데 탈출 클로저를 듣는다고

mini-min-dev.tistory.com

즉, 스위프트의 함수(이름 있는 함수)와 클로저(이름 없는 함수)는 일급객체(일급 시민)이기 때문에,
함수의 전달인자로 전달할 수 있으며, 함수의 결괏값으로 반환할 수 있다는 특징을 코드에서 사용하는 방법이 바로 이 고차 함수 방법인 것이다.

고차 함수(Higher-order function)에는 map, filter, reduce 세 가지 함수가 있으며,
이 고차 함수들은 Swift 표준 라이브러리의 컨테이너 타입(Array, Set, Dictionary 등)에 구현되어 있다고 한다.

그럼, 이제 map 함수부터 순서대로 어떻게 사용하는지 알아보도록 하자!

 

1️⃣ map -> 데이터를 변형할 때 사용하는 함수

map 함수는 컨테이너 내부의 기존 데이터를 변형(transform)하여 새로운 컨테이너를 생성하는 함수다.
여기서 말하는 컨테이너는 여러 개의 데이터를 담을 수 있는 Swift의 데이터 타입, Array, Set, Dictionary를 의미한다.

즉, 여러 개의 데이터들을 일괄적으로 바꿔서 새로운 변수에 담을 수 있는 방법이 바로 이 map 함수를 사용하는 방법이라고 생각하면 된다.

아래 예시 코드의 경우, numbers라는 변수에 Int형 데이터 5개를 담는 Array를 만들었다.
그리고 map 함수를 클로저 형태로 사용해서, numbers의 return값을 곱하기 2한 후, 새로운 변수 newNumbers에 담아준 것을 확인할 수 있다.

map 함수를 클로저 형태로 사용하기 때문에, 예전에 배웠던 클로저 표현법을 활용하는 것도 물론 가능하다.

// 변형하고자 하는 numbers와 변형 결과를 받을 doubledNumbers, strings
let numbers: [Int] = [0, 1, 2, 3, 4]
var newNumbers: [Int]

// numbers의 각 요소를 2배하여 새로운 배열 반환
newNumbers = numbers.map({ (number: Int) -> Int in
    return number * 2
})

print(numbers)    // map 적용 전, [0, 1, 2, 3, 4]
print(newNumbers) // map 적용 후, [0, 2, 4, 6, 8]

// 추가) 클로저의 특성 활용(매개변수, 반환 타입, 반환 키워드(return) 생략, 후행 클로저)
newNumbers = numbers.map { $0 * 2 }
print(newNumbers) // [0, 2, 4, 6, 8]

 

2️⃣ filter -> 데이터를 필터링할 때 사용하는 함수

filter 함수는 filter라는 이름에 걸맞게, 컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출해주는 기능을 가진 함수다.

아래는 0, 1, 2, 3, 4가 담겨있는 Int형 Array, numbers가 있다고 했을 때, 새로운 데이터를 담고 싶은 Int형 Array, evenNumbers에는
filter함수를 사용해 특정한 조건에 부합하는 값만 가져올 수도 있다는 것을 보여주는 아래 예제를 살펴보자.

// numbers의 요소 중 짝수를 걸러내어 새로운 배열로 반환
let evenNumbers: [Int] = numbers.filter { (number: Int) -> Bool in
    return number % 2 == 0
}
print(evenNumbers) // [0, 2, 4]

// 매개변수, 반환 타입, 반환 키워드(return) 생략, 후행 클로저
let oddNumbers: [Int] = numbers.filter {
    $0 % 2 != 0
}
print(oddNumbers) // [1, 3]

 

3️⃣ reduce -> 데이터를 결합할 때 사용하는 함수

reduce 함수는 컨테이너 내부의 콘텐츠를 하나로 통합할 때 사용하는 함수이다.
통합? 이라고 잠깐 고개를 갸웃하지 말고, 그냥 간단하게 모두 다 "합친다"라고 생각하면 된다!

대신, reduce 함수에서는 첫 번째 인자로 초깃값을 지정해줘야 한다는 것만 주의해서 사용하도록 하자 ^__^

// 통합하고자 하는 someNumbers
let someNumbers: [Int] = [2, 8, 15]

// 초깃값이 0이고 someNumbers 내부의 모든 값을 더합니다.
let sum: Int = someNumbers.reduce(0, { (first: Int, second: Int) -> Int in
    return first + second
})

print(sum)

"""
0 + 2 = 2
2 + 8 = 10
10 + 15 = 25
25
"""