본문 바로가기

Swift

Swift - 고차 함수

고차함수: 함수의 인자로 함수를 받거나 실행결과를 함수로 반환하는 것.

 

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을 적용하고 그 결과값으로 구성된 배열을 반환하는 함수입니다.

배열요소들을 다른 값으로 맵핑하는 함수.

 

맵 메서드 동작 모식도

  1.  numbers array = [0, 1, 2, 3, 4] 

  2. numbers array 의 각 요소에 map 메서드의 매개변수로 전달한 클로저 적용. 

  3. 클로저의 연산 결과값을 담은 새 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