본문 바로가기
🌱

Closure 3/3 - 고차함수

by 방우 2022. 10. 29.

고차함수(Higher-order Function)

고차함수란

  • 다른 함수를 전달인자로 받거나 함수실행의 결과를 함수로 반환하는 함수
  • 스위프트의 함수 및 클로저는 일급시민이기 때문에 함수의 전달인자로 받을 수 있으며, 함수의 결과값으로 반환할 수 있다.

일급시민이란?

스위프트 함수와 클로저는 일급시민(일급객체, 일급함수 ...)

다음 조건을 충족하는 객체를 일급시민이라고 한다

  • 변수에 저장할 수 있다.
  • 매개변수로 전달할 수 있다.
  • 리턴값으로 사용할 수 있다.

map

let winNumbers = makeLottoNumberCollection.intersection(Set(myLottoNumbers)).map{String($0)}

정의

Returns an array containing the results of mapping the given closure over the sequence’s elements.
기존의 컨테이너의 요소에 대해 정의한 클로저로 매핑한 결과를 새로운 컨테이너로 반환합니다.
map함수는 컨테이너 내부의 기존 데이터를 변형하여 새로운 컨테이너를 생성합니다.

선언

func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
  • throws -> T 클로저로 새로운 타입을 매핑한 결과인데 rethrows -> [T] 요롷게 타입변환된 컨데이너로 반환
  • T = Type Parameter

파라미터

  • 매핑 클로저로, 이 컨테이너의 요소를 매개변수로 받아들이고 정의한 클로저의 형태에 맞게 변환된 값을 반환합니다.
  • 전달인자를 매개변수로 표현한 것 같다. 이 둘은 문맥에 따라 혼용되기도한다. 정확하게 설명하면 매개변수는 함수의 정의부분에 나열되어 있는 변수들이고 전달인자는 함수를 호출할 때 전달되는 실제 값을 의미한다. 즉 위에서 _ transform 는 매개변수(*parameter)이고, : 이후에 인수를 받는 부분에 들어오는 값을 전달인자(argument)*라고한다.

매개변수(파라미터)

  • transform 은 컨테이너의 요소를 매개변수로 받아 선택적 값을 반환하는 클로저

리턴타입

let winNumbers = makeLottoNumberCollection.intersection(Set(myLottoNumbers)).map{String($0)}
    return winNumbers
  • $0은 Shorthand parameter names이라고 한다.
  • 클로저의 매개변수를 간결하게 표현한 것이다. 만약 매개 변수가 (a: Int, b: Int) 이렇게 돼있으면 순서대로 $0(a), $1(b) 이렇게 표현할 수 있고 그 자리에 들어오는 전달인자를 받아준다.

왜 사용하는데?

기존 데이터를 변형하여 새로운 컨테이너를 생성할 때 간결하고 효율적이니까

  1. 변형하고자 하는 numbers와 변형 결과를 받을 doubledNumbers, string
    1. 기존에 For-in문 이용
    2. map 사용
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int]
var strings: [String]

//MAKR: 1번

doubledNumbers = []
strings = []

for number in numbers {
    doubledNumbers.append(number * 2)
    strings.append("\\(number)")
}

print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]

//MARK: 2번

doubledNumbers = numbers.map({ (number: Int) -> Int in
    return number * 2
})

strings = numbers.map( { (number: Int) -> String in
    return "\\(number)"
})

print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]

// 매개변수, 반환타입, 반환 키워드(retrun) 생략, 후행클로저
doubledNumbers = numbers.map { $0 * 2 }
strings = numbers.map { "\\($0)" }

print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
// let strings는 타입 추론에 의해 문자 배열([String])타입을 갖습니다.
// 결과는 숫자가 문자로 바뀐 ["OneSix", "FiveEight", "FiveOneZero"]가 됩니다.

 

 

 


filter

왜 사용하는데?

  • 주어진 함수의 특정 조건을 만족하는 모든 요소를 모아, 새로운 배열로 반환하는 메서드. 예를 들어 짝수만 걸러내고 싶을 때, 특정 문자열만 걸러낼 때 등 사용가능하다.
Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
- 기존 컨테이너의 요소에 대해 조건에 만족하는 값을 새로운 컨테이너로 반환한다
- filter함수는 컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출

선언(Declaration)

func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]

매개변수(Parameter)

isIncluded

  • 컨테이너의 요소를 인수로 취하고, 요소가 반환된 배열에 포함되어야하는지 여부를 Bool타입으로 반환하는 클로저

리턴타입(Return Value)

isIncluded에 맞게 true로 반환되는 값만 리턴

예시

  • 기존의 Int 배열에서 짝수만 호출해보자
// numbers에서 짝수만 호출하기

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

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

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

// 고차함수 filter 이용

let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
let evenNumbers = numbers.filter { $0 % 2 == 0}
// 축약함수를 쓰지 않고 하면 
//let evenNumbers = numbers.filter { (number: Int) -> Bool in
//    return number % 2 == 0
//}

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

// numbers에서 홀수만 호출하기
var oddNumbers: [Int] = numbers.filter( { (number: Int) -> Bool in
    return number % 2 != 0
})

print(oddNumbers) // [1, 3, 5, 7]

oddNumbers = numbers.filter { $0 % 2 != 0 }

let arr = [12, 3, 4, 19, 33]

arr.filter { (number:Int) in
    return number >= 10
} //[12, 19, 33]

Reduse

왜 사용하는데?

  • 여러개의 데이터들이 속한 배열을 하나의 데이터로 응축(reduce)하는 것
Returns the result of combining the elements of the sequence using the given closure.
reduce 는 정의한 클로저를 사용하여 기존 컨테이너의 요소를 결합한 결과를 반환합니다.
reduce함수는 컨테이너 내부의 콘텐츠를 하나로 통합합니다.

선언

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

리턴타입

최종 누적 값이 반환되며, 컨테이너의 요소가 없다면 initialResult의 값이 반환됩니다.

예시

  • 왜 (0, +)인지? 0은 초기값이고 +는 요소들을 초기값에 더해준다는 뜻이다.
  • 초기 값이 0인 이유는 안에 있는 요소를 초기값으로 설정해주는 것이 아니라 내가 어떤 초기값에 배열안의 수를 더해줄 것 인지 지정해주는 것이므로 오로지 배열 안의 합을 구하기 위해서는 초기값 0으로 설정해야한다.
//for-in 사용
let numbers = [1, 2, 3, 4, 5]
**var sum = 0**

for number in numbers {
    sum += number
}

print(sum) // 15

//reduce사용
let numbers = [1, 2, 3, 4, 5]
var sum = numbers.reduce(0, +) //15
// _ nextPartialResult: (Result, Element)

// 축약 안했을 때 1
let numbers: [Int] = [1, 2, 3, 4, 5]
var sum = numbers.reduce(0) { (result: Int, element: Int) -> Int in
    return result + element // 15
}

// 축약 안했을 때 2
let numbers: [Int] = [1, 2, 3, 4, 5]
var sum = numbers.reduce(0, { (result: Int, currentItem: Int) -> Int in
    print("\\(result) + \\(currentItem)")
    return result + currentItem // 15
})
/*
 0 + 1
 1 + 2
 3 + 3
 6 + 4
 10 + 5
 */

// 축약 안했을 때 3
let numbers: [Int] = [1, 2, 3, 4, 5]
var sum = numbers.reduce(0, { (result: Int, currentItem: Int) -> Int in
    print("\\(result) - \\(currentItem)") // -15
    return result - currentItem // 15
})
/*
 0 - 1
 -1 - 2
 -3 - 3
 -6 - 4
 -10 - 5
 */

// 반환타입 생략
let numbers: [Int] = [1, 2, 3, 4, 5]
var sum = numbers.reduce(0) { (result, element) in
    return result + element // 15
}

// 이렇게도 작성할 수 있음
let numbers: [Int] = [1, 2, 3, 4, 5]
var sum = numbers.reduce(3) { $0 + $1 }

//var sum = numbers.reduce(3, { (result: Int, currentItem: Int) -> Int in
//    print("\\(result) + \\(currentItem)")
//    return result + currentItem // 15
//})
//print(sum) // 18

// 스트링 타입 더하기
let numbers: [String] = ["가", "나", "다", "라", "마"]
var sum = numbers.reduce("", { (result: String, currentItem: String) -> String in
    print("\\(result) + \\(currentItem)")
    return result + currentItem // "가나다라마"
})
/*
 + 가
가 + 나
가나 + 다
가나다 + 라
가나다라 + 마
 */
  • 이렇게 순서를 괄호 안에 넣어줄 수가 없음
reduce 메소드에 전달하는 클로저의 매개변수이름은 result , currentItem 과 같은 이름으로 하는 것이 좋음
첫번째 매개변수는 초깃값으로부터 출발하여 마지막 요소까지 순회하는 내내의 결과값이다
currentItem은 현재 순회하고 있는 요소의 값을 뜻함
결국 return result + currenItem이라고 표현한다면 이제까지 더해진 결과값에 이번 요소의 값을 더한다는 뜻

flatMap

왜 사용하는데?

  • 2차원 이상의 배열을 1차원으로 만들고 싶을 때
Returns an array containing the concatenated results of calling the given transformation with each element of this sequence.
컨테이너의 각 요소를 사용하여 지정된 조건을 호출할 때, 순차적인 결과의 배열을 반환

선언

func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

매개변수(Parameters)

transform

  • transform 은 컨테이너의 요소를 매개변수로 받아 컨테이너로 반환하는 클로저

예시1 (2차원 배열)

// 축약버전

let numbers = [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
let faltMapped = numbers.flatMap { $0 }
print(faltMapped) //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  • 1차원도 2차원도 아닌 배열이면 Any로 바꾸라고 함(위)

compactMap과 flatMap

  • 기존 flatMap은 배열 평평하게(차원을 내려줌?)해주고 nil을 제거하며 옵셔널 바인딩을 해주는 역할

→ Swfit 4.1부터는 1차원 배열에서 nil을 제거하고 옵셔널 바인딩을 하고 싶을 때 compactMap사용

    이러한 경우 더이상 flatMap을 혼용해서 사용할 수 없음(좀 더 알아봐야함 혼용할 수 있는 걸로 알고 있느데).

  • 하지만 2차원 이상부터는 하는 일이 다름 flatMap은 2차원을 1차원으로 만들어주지만 compactMap은 그렇지 않음.

forEach

  • 이걸로 대괄호를 제거하고 배열안의 요소를 빼낼 수도 있음
Calls the given closure on each element in the sequence in the same order as a for-in loop.
for문과 동일한 순서로 컨테이너의 각 요소에 대해 주어진 클로저를 호출합니다.

선언 (Declaration)

func forEach(_ body: (Element) throws -> Void) rethrows

매개변수(Parameters)

body

  • body는 컨터에너의 요소를 매개변수로 사용하는 클로저

forin과의 차이점

  1. for문으로는 몇 번 출력할 지 제어할 수 있지만 forEach는 컨테이너의 각 요소를 클로저에 던져 이용하기 때문에 요소의 수 만큼만 가능
  2. for문으로 요소하나하나를 반환하듯 쓸 수 있는데 forEach는 함수형태를 반환하기 때문에 따로 외부에서 값을 담을 게 필요함
  3. break - continue은 반복문에서만 사용가능하기 때문에U(왜냐면 forin은 루프를 도는거니까) ForEach는 사용할 수 없음
    1. break와 continue는 반복문 혹은 switch에서만 사용가능
  4. return도 forEach에서 사용할 수 없음 (forEach는 Void라서 return을 쓸 수 없음)

예시(Discussion)

// 배열 안의 요소가 여러개 일때

let numberWords = ["one", "two", "three"]

for word in numberWords {
    print(word)
}

numbers.forEach { word in
    print(word)
}
/*
 one
 two
 three
 */

// 배열안의 요소가 하나일 때

let numbers = [123]

for _ in 0...2 {
    print(numbers)
}
/*
 [123]
 [123]
 [123]
 */

numbers.forEach { numbers in
    print(numbers)
} // 123

예시2 (break)

예시3 (return)

let numberWords = ["one", "two", "three"]

// forin
func forInLoop() {
    for word in numberWords {
        print(word)
        if word == "two" { return }
    }
}

forInLoop()
/*
one
two
*/

let numberWords = ["one", "two", "three"]

// forEach
func forEach() {
    numberWords.forEach { word in
        print(word)
        if word == "two" { return }
    }
}

forEach()
/*
one
two
three
*/

주요 참고자료

https://eastjohntech.blogspot.com/2019/12/closure-self.html

 

closure에서는 왜 self를 사용해야 할까?

closure에서는 왜 self를 사용해야 할까? class에서 closure를 사용할 때 closure 안에서 객체의 변수 또는 함수에 접근할 때 self를 붙여서 사용을 해야합니다. class안에서는 self...

eastjohntech.blogspot.com

https://docs.swift.org/swift-book/LanguageGuide/Closures.html

 

Closures — The Swift Programming Language (Swift 5.7)

Closures Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages. Closures can capture and store referen

docs.swift.org

https://babbab2.tistory.com/81

 

Swift) 클로저(Closure) 정복하기(1/3) - 클로저, 누구냐 넌

안녕하세요 :) 소들입니다 으휴 저번 주도 쓸데없이 바빴어서 포스팅을 못했네용 나태한 저번주의 나를 반성하며.. 하암..🥱 음 전에 제가 Swift의 꽃이 Optional이라고 말한 적 있는데, Optional 만큼

babbab2.tistory.com

http://yoonbumtae.com/?p=3365 

 

Swift(스위프트): 클로저 (Closure) - 정의 및 문법 - BGSMM

클로저 (Closures) 클로저(closure)는 코드에서 전달 및 사용할 수 있는 독립된 기능 블록(blocks of functionality) 입니다. 다른 프로그램의 람다(lambda)와 유사합니다. 클로저는 정의된 컨텍스트에서 모든

yoonbumtae.com

https://seons-dev.tistory.com/247

 

Swift : 기초문법 [클로저 및 고차함수(map, filter, reduce)]

본 게시글은 yagom님의 Swift 프로그래밍 3판을 참고하여 작성되었습니다. 클로저 클로저에 대해서 간단하게 예제를 들어보면서 살펴보자. calculator 이라는 함수가 있고, 값을 더하거나 빼거나 곱하

seons-dev.tistory.com

https://docs.swift.org/swift-book/ReferenceManual/Expressions.html#ID544

 

Expressions — The Swift Programming Language (Swift 5.7)

Expressions In Swift, there are four kinds of expressions: prefix expressions, infix expressions, primary expressions, and postfix expressions. Evaluating an expression returns a value, causes a side effect, or both. Prefix and infix expressions let you ap

docs.swift.org

https://babbab2.tistory.com/164

 

Swift) closure와 @escaping 이해하기

안녕하세요? :> 오랜만입니다 오랜만인 이유는 5, 6월 현업이 매우매우x100 바쁘기도 했고, 그에 따른 번아웃이 온 건지,, 한 달정도 공부에 대한 반항을 좀 해서,, ㅎㅎ;; 쨌든 다시 정신 차리고 돌

babbab2.tistory.com

https://icksw.tistory.com/157

 

[Swift] AutoClosure, EscapingClosure 알아보기

안녕하세요 Pingu입니다. Swift 문법 중 Closure(클로저)에 대해서 다시 공부를 하는데 AutoClosure, EscapingClosure이 잘 이해가 가지 않아서 정리 겸 공부를 한 번 하기 위해 글을 쓰려고 합니다. 클로저에

icksw.tistory.com

 

'🌱' 카테고리의 다른 글

푸릇푸릇 새싹 3기가 와써여  (0) 2023.05.05
새싹프로그램 2기를 마무리하며  (0) 2022.12.11
Closure 2/3  (0) 2022.10.29
Closure 1/3  (0) 2022.10.28
값전달 - 클로저 트러블 슈팅  (0) 2022.08.19