[iOS] Swift의 고차함수1 (map, filter, reduce)

이전글인 함수형 프로그래밍을 공부하면서 고차함수가 함수형 프로그래밍의 핵심 개념을 구현한다고 소개하였다. 오늘은 swift에서 고차함수가 무엇이 있는지 어떻게 작동하는지 알아보자!!

 

 

고차함수(High Order Functions)가 뭐야?

 

고차함수는 다른 함수를 인자로 받거나, 함수의 결과로 함수를 반환하는 함수를 말한다.

 

함수형 프로그래밍을 설명하면서 스위프트의 함수는 일급시민이라고 하였다. 

따라서 스위프트는 함수를 함수의 전달인자로 전달할 수 있고, 함수의 결과값으로 반환할 수 있다.

 

 

 

고차함수 종류

 

Swift에서 고차함수는 종류가 많다.

그 중에서 map, filter, reduce, compactMap, flatMap, forEach에 대해

알아보려고 한다.

 

 

 

Map

 

공식 도큐먼트의 정의대로라면 

map은 '기존의 컨테이너의 요소에 대해 정의한 클로저로 매핑한 결과새로운 컨테이너로 반환'하는 함수이다.

 

엥?? 이게 무슨소리냐고?

 

그럼 간단한 코드를 보면서 이해해 보자.

let string = ["1", "2", "3", "4", "5"] // 기존의 컨테이너
let numbers = string.map { Int($0)! } // 새로 반환받을 값을 정의하여 새로운 컨테이너에 반환

print(numbers)
// [1, 2, 3, 4, 5]

 

위 코드에서는 기존 String 배열을 Int 배열로 바꾸는 작업을 하였다.

 

이 과정에서 map을 사용하고 클로저로 string 배열 안에 있는 string들을 

Int로 바꿔주고 있다. 

 

string 배열 안에 있는 

"1" "2" "3" "4" "5"

1     2     3     4    5

바꾸면서 새로운 배열로 반환하고

numbers에 초기화해 준 것이다.

 

 

이제 좀 감이 잡히시는지 모르겠지만 다른 예를 봐보자.

// numbers의 각 요소에 9 곱하기
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] // 기존 컨테이너
let multiplyArray: [Int] = numbers.map { $0 * 9 } // 새로 반환받을 값을 정의하여 새로운 컨테이너에 반환

print(multiplyArray)
// [9, 18, 27, 36, 45, 54, 63, 72, 81]

 

이번엔 배열 안에 있는 각 요소에 9를 곱하게 하였다.

 

즉, map은 요소 하나하나주어진 함수(정의한 클로저)적용한 결과를 반환시키는 함수이다.

 

let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let lowercaseNames = cast.map { $0.lowercased() } //소문자로 바꿔주는 함수
// 'lowercaseNames' == ["vivien", "marlon", "kim", "karl"]
let letterCounts = cast.map { $0.count } 
// 'letterCounts' == [6, 6, 3, 4]

 

공식 도큐먼트에 있는 예시를 보면

case안에 있는 요소 하나하나를 lowercased() 처리한다든가

count()를 통해 몇 글자인지 반환하고 있다.

 

 

filter

 

filter는

'기존 컨테이너의 요소에 대해 조건에 만족하는 값에 대해서 새로운 컨테이너로 반환'하는 함수이다.

 

 

위에서 map을 공부했으니 filter는 좀 감이 올 수도 있겠다.

filter는 이름에서도 알 수 있듯

원하는 값만 걸러주는 역할을 한다.

 

코드로 예시를 봐보자.

// numbers에서 짝수만 추출하기
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] // 기존 컨테이너
let evenNumbers = numbers.filter { $0 % 2 == 0 } // 짝수인 값만 반환하게 설정

print(evenNumbers)
// [2, 4, 6, 8]

 

numbers라는 배열에서

각 요소들을 확인하고 짝수인 값들을 새로운 컨테이너로 반환하고 있다.

 

 

다른 예시를 봐보자.

let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let shortNames = cast.filter { $0.count < 5 } //글자수가 5개 미만인 요소만 필터링
print(shortNames)
// Prints "["Kim", "Karl"]"

 

위와 같이 글자수가 5개 미만인 요소만 추출할 수도 있다.

 

 

 

Reduce

 

reduce는

'정의한 클로저를 사용하여 기존 컨테이너의 요소를 결합한 결과를 반환'하는 함수이다.

 

특이하게도 다른 고차함수들과는 다르게 두 개의 인자를 받는다.

 

우선 코드부터 봐보자.

// 각 요소의 합 구하기
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let sumShortCut = numbers.reduce(0, +)
let sum = numbers.reduce(0) { $0 + $1 }

print(sumShortCut, sum)
// 55 55

 

우선 reduce는 두 가지 방식으로 표현할 수 있다.

기존처럼 클로저를 사용해 정의하는 방법과

sumShortCut처럼 인자를 입력하는 방법이다.

 

다만 인자를 입력하는 방법은 축약된 형태

일부 단순한 연산에만 사용이 가능하다고 한다.

 

 

let sum = numbers.reduce(0) { $0 + $1 }

 

우선 클로저를 사용하는 경우 

reduce()에 인자값으로 들어간 0초기값이다.

그리고 클로저 안에 어떻게 값을 통합할지 정의하게 된다.

여기서는 간단하게 덧셈을 하였다.

 

그럼 어떻게 55라는 결과가 나왔는지 알아보자.

 

우선 초기값(0)과 numbers의 첫 번째 값인 1을 가져와 덧셈을 하게 된다.

그럼 누적값1이 되고, 이제 두 번째 값인 2를 가져와 덧셈을 한다.

그럼 누적값3이 되고, 마찬가지로 세 번째 값인 3을 가져와 덧셈을 한다.

.....

이를 반복해 마지막 누적값을 반환하게 된다. (그래서 55)

 

위와 같은 방식으로 reduce는 작동을 한다.

 

 

 

이게 이해가 되었다면 

축약형도 왜 그렇게 적었는지 이해가 될 것이다.

let sumShortCut = numbers.reduce(0, +)

 

reduce 함수 안에 초기값과 

값을 어떻게 통합할 것인지를 정의연산 기호를 넣은 것이다.

 

 

 

 

 

그럼 곱셈은 어떻게 하면 될까??

여기 중요한 건 초기값이다!

 

 

// 각 요소의 곱셈 결과 구하기
let numbers = [1, 2, 3, 4, 5]
let sumShortCut = numbers.reduce(1, *)
let sum = numbers.reduce(1) { $0 * $1 }

print(sumShortCut, sum)
// 120 120

 

나머지 방식은 다 같으나

곱셈은 초기값0으로 주면

결과도 0이기 때문에 이런 부분을 주의하면 되겠다!!!