이번 포스팅에서는 스위프트의 고차함수인 filter, reduce를 이용하여 데이터 연산을 쉽게 수행하는 방법을 알아보고자 한다.
filter와 reduce는 어떤 함수이고, 어떻게 작동하는지 살펴보자
filter
필터는 말 그대로 컨테이너 내부의 값을 걸러서 추출하는 역할을 하는 고차함수이다.
맵과 마찬가지로 새로운 컨테이너에 값을 담아 반환해준다. 다만 맵처럼 기존 콘텐츠를 변형하는 것이 아니라,
특정 조건에 맞게 걸러내는 역할을 한다는 점에서 차이가 있다.
filter 함수의 매개변수로 전달되는 함수의 반환 타입은 Bool형이다.
해당 콘텐츠의 값으로 새로운 컨테이너에 포함될 항목이라고 판단하면 true를, 포함하지 않으려면 false를 반환해주면 된다.
사용 예제
다음은 필터 메서드의 간단한 사용 예제이다.
let numbers: [Int] = [1, 2, 3, 4, 5, 6]
let evenNumbers: [Int] = numbers.filter { (number: Int) -> Bool in
return number % 2 == 0
}
print(evenNumbers) // [2, 4, 6]
let oddNumbers: [Int] = numbers.filter { (number: Int) -> Bool in
return number % 2 == 1
}
print(oddNumbers) // [1, 3, 5]
이처럼 컨테이너 안에서 특정 조건을 충족하는 데이터만 추출하고자 할 때 유용하게 사용할 수 있다.
만약 콘텐츠의 변형 후에 필터링하고 싶다면, 다음과 같이 메서드를 체인처럼 연결하여 사용할 수도 있다.
let numbers: [Int] = [1, 2, 3, 4, 5, 6]
let evenNumbers: [Int] = numbers.map { $0 * 2 }.filter { $0 % 2 == 0 }
print(evenNumbers) // [2, 4, 6, 8, 10, 12]
reduce
리듀스는 컨테이너 내부의 콘텐츠를 하나로 합하는 기능을 실행하는 고차함수이다.
배열이라면 배열의 모든 값을 전달 인자로 전달받은 클로저의 연산 결과로 합해준다.
스위프트의 리듀스는 두 가지 형태로 구현되어 있다.
첫 번째 리듀스는 클로저가 각 요소를 전달받아 연산한 후 값을 다음 클로저 실행을 위해 반환하며 컨테이너를 순환하는 형태이다.
func reduce<Result>(
_ initialResult: Result,
_ nextPartialResult: (Result, Self.Element) throws -> Result
) rethrows -> Result
initialResult이라는 이름의 매개변수로 전달되는 값을 통해 초깃값을 지정해줄 수 있으며, nextParticalResult라는 이름의 매개변수로 클로저를 전달받는다.
nextPartialResult 클로저의 첫 번째 매개변수는 리듀스 메서드의 initialResult 매개변수를 통해 전달받은 초깃값 또는 이전 클로저의 결괏값이다. 모든 순회가 끝나면 리듀스의 최종 결괏값이 된다. 두 번째 매개변수는 리듀스 메서드가 순환하는 컨테이너의 요소이다.
두 번째 리듀스 메서드는 컨테이너를 순환하며 클로저가 실행되지만 클로저가 따로 결괏값을 반환하지 않는 형태이다.
대신 inout 매개변수를 사용하여 초깃값에 직접 연산을 실행하게 된다.
func reduce<Result>(
into initialResult: Result,
_ updateAccumulatingResult: (inout Result, Self.Element) throws -> ()
) rethrows -> Result
updateAccumulatingResult 매개변수로 전달받는 클로저의 매개변수 중 첫 번째 매개변수를 inout 매개변수로 사용한다.
updateAccumulatingResult 클로저의 첫 번째 매개변수는 리듀스 메서드의 initialResult 매개변수를 이용해 전달받은 초깃값 또는 이전에 실행된 클로저 때문에 변경되어 있는 결괏값이다. 모든 순회가 끝나면 리듀스의 최종 결괏값이 된다.
두 번째 매개 변수는 리듀스 메서드가 순환하는 컨테이너의 요소이다. 상황에 따라서는 리듀스를 맵과 유사하게 사용할 수도 있다.
사용 예제
다음은 reduce 메서드의 사용 예제이다.
let numbers: [Int] = [2, 4, 6]
// 첫 번째 형태인 reduce(_:_:) 메서드 사용
// 초깃값이 0이고 정수 배열의 모든 값을 더한다.
var sum: Int = numbers.reduce(0, { (result: Int, next: Int) -> Int in
print("\(result) + \(next)")
// 0 + 2
// 2 + 4
// 6 + 6
return result + next
})
print(sum) // 12
// 두 번째 형태인 reduce(into:_:) 메서드 사용
// 초깃값이 0이고 정수 배열의 모든 값을 더한다.
// 첫 번째 리듀스 형태와 달리, 클로저의 값을 반환하지 않는다.
// 내부에서 직접 이전 값을 변경한다는 점에서 차이가 있음
sum = numbers.reduce(into: 0, { (result: inout Int, next: Int) in
print("\(result) + \(next)")
// 0 + 2
// 2 + 4
// 6 + 6
result += next
})
print(sum) // 12
참고
- 야곰의 스위프트 프로그래밍 3판
'Study > Swift' 카테고리의 다른 글
[Swift] 고차함수(1) - map, flatMap, compactMap (0) | 2022.09.18 |
---|---|
[Swift] RunLoop.Mode (0) | 2022.08.01 |
[Swift] Run Loop (0) | 2022.07.25 |
[Swift] Initializer 심화 (2) (0) | 2022.07.20 |
[Swift] Initializer 심화 (1) (0) | 2022.07.19 |