고차함수: 함수의 인자로 함수를 받거나 실행결과를 함수로 반환하는 것.
Map:
자신을 호출할때 매개변수로 전달된 함수를 실행하여 그 결과를 다시 반환해주는 함수임. 배열, 딕셔너리, 셋에서 사용 가능.
맵을 사용하면 컨테이너가 담고 있던 각각의 값을 매개변수를 통해 받은 함수에 적용한 후 다시 컨테이너에 포장하여 반환됨.
-
기존 컨테이너의 값은 변경되지 않고 새로운 컨테이너가 생성되어 반환됨. 기존 데이터를 변형하는데 사용.
for - in 구문과 별반 차이 없음.
비교.
for-in 구문
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int] = [Int]()
var strings: [String] = [String]()
for number in numbers {
doubledNumbers.append(number * 2)
strings.append("\(number)")
}
print(doubledNumbers)
print(strings)
doubledNumbers = numbers.map({ (number: Int) -> Int in
return number * 2
})
strings = numbers.map({ (number: Int) -> String in
return "\(number)"
})
print(doubledNumbers)
print(strings)
func map<U>(transform: (T) -> U) -> Array<U>
Map은 배열 각 요소 x에 변환함수 transform을 적용하고 그 결과값으로 구성된 배열을 반환하는 함수입니다.
배열요소들을 다른 값으로 맵핑하는 함수.
맵 메서드 동작 모식도
-
numbers array = [0, 1, 2, 3, 4]
-
numbers array 의 각 요소에 map 메서드의 매개변수로 전달한 클로저 적용.
-
클로저의 연산 결과값을 담은 새 array doubledNumbers
아래와 같이 클로저 최적화 적용도 가능.
doubledNumbers = numbers.map { $0 * 2 }
클로저의 반복 사용
let evenNumbers = [0, 2, 4, 6, 8]
let oddNumbers = [0, 1, 3, 5, 7)
let multiplyTwo: (Int) -> Int = { $0 * 2 }
let doubledEvenNumbers = evenNumbers.map(multiplyTwo)
let doubledOddNumbers = oddNumbers.map(multiplyTwo)
딕셔너리 타입에서의 map 적용.
let alphabetDictionary: [String: String] = ["a":"A", "b":"B"]
var keys: [String] = alphabetDictionary.map { (tuple: (String, String)) -> String in
return tuple.0
}
keys = alphabetDictionary.map { $0.0 }
let values: [String] = alphabetDictionary.map { $0.1 }
print(keys)
print(values)
2. Filter
-
컨테이너 내부의 값을 걸러서 추출하는 역할을 고차함수.
-
맵과 마찬가지로 새로운 컨테이너에 값을 담아 반환.
-
맵처럼 기존 컨텐츠를 변형하는 것이 아니라, 특정 조건에 맞게 걸러내는 역할을 수행.
-
filter함수의 매개변수로 전달되는 함수의 반환 타입은 Bool임.
func filter(includeElement: (T) -> Bool) -> Array<T>
Filter는 조건식을 인자로 받아, 조건식이 true를 만족하는 요소들로만 구성된 배열을 반환하는 함수입니다.
쉽게 말하면, 배열요소들을 필터링하는 함수입니다.
let numbers = [0, 1, 2, 3, 4, 5]
let evenNumbers = numbers.filter { (number: Int) -> Bool in
return number % 2 == 0
}
print(evenNumbers)
let oddNumbers = numbers.filter { (number: Int) -> Bool in
return number % 2 == 1
}
print(oddNumbers)
맵과 필터 메서드의 연계 사용.
let numbers = [0, 1, 2, 3, 4, 5]
let mappedNumbers = numbers.map { $0 + 3 }
let evenNumbers = mappedNumbers.filter { (number: Int) -> Bool in
return number % 2 == 0
}
print(evenNumbers) // [4, 6, 8]
let oddNumbers = numbers.map { $0 + 3 }.filter{ $0 % 2 == 1 }
print(oddNumbers) // [3, 5, 7]
3. Reduce
-
결합(combine)이라고 불러야 마땅한 기능.
-
컨테이너 내부의 컨텐츠를 하나로 합하는 기능을 실행하는 고차함수.
func reduce<U>(initial: U, combine: @noescape (U, T) -> U) -> U
Reduce는 U를 초기값으로 하여,각 배열요소들과 순차적으로 결합연산을 하여 누적된 단일값을 반환하는 함수입니다.
실습때 했던 sum 함수 와 유사
var sum = 0
var nums = [1, 3, 5, 7]
for num in nums {
sum += num
}
print(sum)
let numbers = [1, 2, 3]
var sum = numbers.reduce(0, { (result: Int, next: Int) -> Int in
return result + next
})
0+1
1+2
3+3
print(result) // 6
// 초깃값이 0이고 정수배열의 모든 값을 뺸다.
let substract = numbers.reduce(0, { (result: Int, next: Int) -> Int in
return result - next
})
print(substract) // -6
let sumFromThree = numbers.reduce(3) {
return $0 + $1
}
var substractFromThree = numbers.reduce(3) {
return $0 - $1
}
두번째 형태
sum = numbers.reduce(into: 0, { (result: inout Int, next: Int) in
result += next
})
substractNum = numbers.reduce(into: 3, {
$0 -= $1
})
4. 사용예
이제 사용법에 대해서 알아볼까요? 다음과 같은 정수형 배열이 있습니다.
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1. 정수형 배열의 각 요소에 1을 더한 배열을 얻고 싶다.
2. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.
3. 정수형 배열에서 짝수로만 구성된 배열을 얻고 싶다.
4. 정수형 배열 값들의 총합을 얻고 싶다.
map 함수를 사용하여 1,2번 문제의 답을 쉽게 얻을 수 있습니다.
// 1. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.
arr.map { (x) -> Int in
return x+1
}
// 2. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.
arr.map { (x) -> Int in
return x*2
}
filter를 사용하여 3번을 해결해보겠습니다.
// 3. 정수형 배열에서 짝수로만 구성된 배열을 얻고 싶다.
arr.filter { (x) -> Bool in
return x%2 == 0
}
당연히 4번은 reduce의 몫입니다.
// 4. 정수형 배열 값들의 총합을 얻고 싶다.
arr.reduce(0, combine: { (result, x) -> Int in
return result + x
})
Trailing Closure 를 사용하여 다소 낯선 형태지만 코드가 너무 짧아 놀라셨죠?
(Trailing Closure는 함수의 마지막 인자가 클로져(함수)일 경우, 클로져를 함수 호출부의 꼬리(trailing)에 표기하는 방식입니다. )
이러한 고차원 함수들을 복합적으로 사용하여 프로그래밍하는 방식을 함수형 프로그래밍이라고 합니다.
함수형 프로그래밍은 짧고 명료한 코드를 작성할 수 있게하고, 버그의 가능성 또한 훨씬 줄인다고 합니다.
낯선 함수형 프로그래밍 방식에 익숙해지려면 다소 시간이 걸리겠지요? ^^
5. Map 함수 구현하기
학습한 내용을 좀더 잘 이해하기 위해 직접 map 함수를 만들어 보겠습니다.
일반적인 방법으로 1,2번 문제를 구현하는 코드를 작성했습니다.
var arr = [1, 2, 3, 4, 5]
func pluseOne(arr:[Int]) -> [Int] {
var result:[Int] = []
for x in arr {
result.append(x + 1)
}
return result
}
func multiplyTwo(arr:[Int]) -> [Int] {
var result:[Int] = []
for x in arr {
result.append(x * 2)
}
return result
}
두 함수는 연산하는 행위만 제외하고 거의 비슷합니다. 연산하는 행위를 함수로 묶어내면 일반화가 가능해보이네요.
func map(arr:[Int], transform:(Int->Int)) -> [Int] {
var result:[Int] = []
for x in arr {
result.append(transform(x))
}
return result
}
이제 제네릭을 사용하여 범용타입으로 대치하면
func map<T, U>(arr:[T], transform:(T->U)) -> [U] {
var result:[U] = []
for x in arr {
result.append(transform(x))
}
return result
}
map 함수가 완성되었습니다. 우리는 글로벌 함수로 구현했지만 표준라이브러리에서는 array의 메소드로 제공한다는 점이 유일한 차이입니다.
5. Filter 함수 구현하기
Map 함수를 구현해보아서 Filter 함수 구현은 좀더 간단합니다.
// 일반적인 방법으로 구현하고
func filter(arr:[Int]) -> [Int] {
var result:[Int] = []
for x in arr {
if x%2 == 0 {
result.append(x)
}
}
return result
}
// 함수로 공통 행위를 묶어낸 다음
func filter(arr:[Int], includeElement:(Int->Bool)) -> [Int] {
var result:[Int] = []
for x in arr {
if includeElement(x) {
result.append(x)
}
}
return result
}
// 제네릭 적용
func filter<T>(arr:[T], includeElement:(T->Bool)) -> [T] {
var result:[T] = []
for x in arr {
if includeElement(x) {
result.append(x)
}
}
return result
}
6. Reduce 함수 구현하기
단계적으로 구현해보니 고차원 함수 구현이 어렵지 않죠? 이제 reduce를 구현해 볼까요?
// 일반적으로 구현하고
func reduce(arr:[Int]) -> Int {
var result:Int = 0
for x in arr {
result = result + x
}
return result
}
// 초기값 할당과 누적하는 행위를 파라미터로 분리하고
func reduce(arr:[Int], initial:Int, combine:(Int,Int)->Int) -> Int {
var result:Int = initial
for x in arr {
result = combine(result, x)
}
return result
}
// 제네릭 적용
func reduce<T, U>(arr:[T], initial:U, combine:(U, T)->U) -> U {
var result:U = initial
for x in arr {
result = combine(result, x)
}
return result
}
'Swift' 카테고리의 다른 글
Swift - Generics(제네릭) (0) | 2020.03.22 |
---|---|
Swift - Self & Super (0) | 2020.03.22 |
Swift - Optional(옵셔널) (0) | 2020.03.20 |
Swift - First Class Citizen (0) | 2020.03.19 |
Swift - Closure(클로저) (0) | 2020.03.19 |