본문 바로가기
Learnings/Swift & iOS

고차함수 내용정리 (map, filter, reduce)

by abcdesong 2021. 1. 6.

노션에서 보기

 

고차함수 내용정리 (map, filter, reduce)

Swift 고차함수에 대하여

www.notion.so

 

0. 고차함수

고차함수란?

매개변수로 함수를 받는 함수를 말한다. Swift에서 함수는 일급시민이기 때문에 다른 함수를 전달인자로 받을 수 있다.

Swift 표준 라이브러리에서는 다음과 같은 고차 함수를 제공한다

  • map
  • filter
  • reduce

모두 Container *(Array, Set, Dictionary 등)와 *Optional 타입에서 사용할 수 있다.

 

for-in 구문과 기본적인 작동 원리가 같으나, 다음과 같은 이점이 있다.

  • 코드가 간결하다
  • 재사용이 쉽다
  • 컴파일러 최적화 성능이 좋다

 

1. Map

map이란?

제공된 클로저를 각 항목에 적용한 후, 원래의 순서와 같도록 배치한 뒤 반환하는 메소드. 기존 데이터를 변형하여 새로운 컨테이너를 생성하는 것

아래와 같은 for-in을,

let numberList: [Int] = [1, 2, 3, 4]
var returnArray: [Int] = []

for number in numberList {
  returnArray.append(number * 2)
}
print(returnArray) //[2, 4, 6, 8]

map으로 쓰면 다음과 같다.

let returnArray = numberList.map({ (number : Int) -> Int in
           return number * 2 }) //Int 값을 받아온 뒤 2를 곱하여 반환

비교 포인트

  • 초기에 빈 배열을 생성할 필요가 없다
  • append() 연산을 수행하지 않아도 되어 시간 절약이 된다

 

이것저것 생략할 수 있다.

map({ (number : Int) -> Int in number * 2 }) //return 생략. 단, 클로저 내부의 코드가 두 줄 이상이라면 return을 생략할 수 없다
map({ (number) -> Int in number * 2 }) //원 타입 생략
map({ (number) -> in number * 2 }) //반환 타입 생략
map({ $0 * 2 }) //매개변수 생략. 이때 $0은 1번째 매개변수라는 의미
map{ $0 * 2 } //후행 클로저. 괄호 생략 후 클로저만 뒤따르게 된다.

* 하지만 과한 생략 시 가독성이 떨어지므로 주의가 필요하다.


매개변수로 전달할 함수를 클로저 상수로 만들어 코드 재사용이 가능하다.

let firstNumberList: [Int] = [1, 2, 3, 4, 5]
let secondNumberList: [Int] = [5, 6, 7, 8, 9]
let multiplyHundred: (Int) -> Int  = { $0 * 100 } //클로저 상수 선언

let calcFirstNumberList = firstNumberList.map(multiplyHundred)
let calcSecondNumberList = secondNumberList.map(multiplyHundred)

 

2. Filter

filter란?

컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출하는 메소드. 반환 타입은 Bool이며, true일 때 값을 포함한다.

 

아래와 같은 for-in을,

let numberlist: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var evenNumbers: [Int] = []

for number in numberlist {
  if number % 2 == 0 {
    evenNumbers.append(number)
  }
}

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

filter로 쓰면 다음과 같다.

let evenNumbers: [Int] = numberlist.filter {(number: Int) -> Bool in
                                            return number % 2 == 0
                                           }

 

싸그리 생략하면 다음과 같다

let evenNumbers: [Int] = numberlist.filter{ $0 % 2 == 0 }

 

다음과 같이 map과 filter를 함께 쓸 수도 있다

let ageNow: [Int] = [10, 13, 16, 18, 19]
let adultInFourYears: [Int] = ageNow.map{ $0 + 4 }.filter{ $0 >= 20 }
//map으로 4를 더한 값을 반환한 배열을 만든 뒤, filter로 20 이상의 데이터를 추출하여 다시 배열로 넘김
print(adultInFourYears) //[20, 22, 23] 

 

이번엔 Dictionary를 설정하고 위와 같은 로직을 실행하였다

let ageNow: [String:Int] = ["a":10, "b":13, "c":16, "d":18, "e":19]
let adultInFourYears: [String: Int] = ageNow.mapValues{ $0 + 4 }.filter{ $0.value >= 20 }
print(adultInFourYears) // ["e": 23, "c": 20, "d": 22] (Dictionary이므로 출력 순서는 랜덤)

Dictionary에서 key는 immutable하므로 변형을 제공하는 map에서는 mapValues라는 이름의 메소드를 통해 value 값을 변경하는 것만 가능하다.

이에 반해, filter에서는 key와 value 모두 필터링에 따른 추출이 가능하여 $0.value나 $0.key와 같은 식으로 지칭이 가능하다.

 

3. Reduce

reduce란?

컨테이너 내부의 데이터를 하나로 통합하는 메소드

아래의 for-in이,

let numberlist: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var sum: Int = 0

for number in numberlist {
  sum += number
}

print(sum) //36

reduce를 사용하면 이렇게 된다.

let sum: Int = numberlist.reduce(0, {(result: Int, currentItem: Int) -> return result + currentItem})
//이때 0은 sum의 초깃값이다.
//result는 0에서 출발하여 1(0+1) -> 3(1+2) -> 6(3+3)로 나아가는, 모든 수의 합에 도달하기까지 단계별 결괏값을 의미한다.
//currentItem은 각 순환에서 배열로부터 배당되는 데이터 값이다


축약하면 다음과 같다

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

 

* Swift에서는 연산자도 함수인데, + 함수는 매개변수 두개를 받아서 합을 반환하는 클로저이다. 따라서 아래와 같은 표현도 가능하다.

let sum = numberlist.reduce(0,+)
// '+' = {(result: Int, currentItem: Int) -> return result + currentItem}

 


참고한 글 목록

댓글