🎛 고차함수란?
고차함수는 다른 함수를 전달인자로 받거나 함수실행의 결과를 함수로 반환하는 함수 이다.
→ 클로저를 파라미터로 받거나
→ 클로저를 리턴하는 경우
Foundation 에서 기본으로 제공하는 고차함수에는
map, compactMap, flatMap, filter, reduce 등이 있다.
🗺 map
기존 데이터를 변형(transform) 해서 새로운 컨테이너에 담아주는 고차함수이다.
map 은 파라미터로 클로저를 받는다.
여기에서 받는 클로저는 시퀸스의 요소들을 파라미터로 가지고 있어야 하고, 요소를 변형해서 반환한 것을 리턴해야 한다.
예시를 보자.
[0, 1, 2, 3, 4] → [0, 2, 4, 6, 8]
배열의 요소를 2배 곱해주는 로직이다.
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int]
//기본
doubledNumbers = numbers.map({ (number: Int) -> Int in
return number * 2
})
//클로저 축약
doubledNumbers = numbers.map { $0 * 2 }
print(doubledNumbers) // [0, 2, 4, 6, 8]
반환타입을 클로저로 정해줄 수 있기 때문에 타입을 변환할 수도 있다.
var strings: [String]
[0, 1, 2, 3, 4] → ["0", "1", "2", "3", "4"]
strings = numbers.map({ (number: Int) -> String in
return "\(number)"
})
print(strings) // ["0", "1", "2", "3", "4"]
🗺 compactMap
map 과 같은 기능을 하지만 한가지 기능이 더 추가 되어 있다.
이름에 맞게 가벼운 맵을 만들어준다. 배열안에 nil 이 들어있을 경우 nil 을 자동으로 제거해 준다.
map 과 똑같이 사용하면 된다.
let numbers: [Int?] = [nil, 2, nil, 4, 6]
let nonOptionalNumber : [Int]
//기본
nonOptionalNumber = numbers.compactMap({ (number) -> Int? in
return number })
//축약
nonOptionalNumber = numbers.compactMap { $0 }
print(nonOptionalNumber)
//[2, 4, 6]
아래는 공식문서 있는 예제이다.
저렇게 숫자로 변환할 수 없는 값일 경우 map 을 사용하면 nil 을 할당하게 되는데 compactMap 을 사용할 경우 nil 을 제거한 배열을 만들어 줄 수 있다.
let possibleNumbers = ["1", "2", "three", "///4///", "5"]
let mapped: [Int?] = possibleNumbers.map { str in Int(str) }
// [1, 2, nil, nil, 5]
let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }
// [1, 2, 5]
🗺 flatMap
map 과 같은 기능을 하지만 한가지 기능이 더 추가 되어 있다.
이름에 맞게 평평한 배열을 만들어 준다. 배열의 차원을 한단계 낮춰주는 역할을 한다.
let wrappedNumber = [[1], [2], [3]]
let flatNumber : [Int]
//기본
flatNumber = wrappedNumber.flatMap { (value) in return
value
}
//축약
flatNumber = wrappedNumber.flatMap { $0 }
print(flatNumber)
//[1, 2, 3]
2차원(두겹) 이던 배열이 1차원 배열이 되었다.
이렇게 중첩 되어 있는 배열을 풀 때 유용하다.
flatMap 을 이용한 공식문서의 예제를 보자
let numbers = [1, 2, 3, 4]
let mapped = numbers.map { Array(repeating: $0, count: $0) }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
map 을 이용해서 배열 안의 배열을 만들었기 때문에 2차원 배열이 생긴다.
flatMap 을 이용하면 2중 배열이 제거 되면서 1차원 배열이 된다.
🔍 filter
기존의 데이터를 조건에 따라 걸러내 새로운 시퀸스를 만들어 주는 고차함수 이다.
filter 는 파라미터로 클로저를 받는다.
이 클로저는 시퀸스의 요소를 파라미터로 가지고 Bool 값을 리턴 해야 한다.
이 Bool 값이 true 인 요소만 새로운 시퀸스(배열)에 담는다.
아래는 배열에서 짝수값만 가지는 배열을 만드는 코드이다.
let numbers = [1, 2, 3, 4, 5, 6]
let filtered: [Int]
//기본
filtered = numbers.filter({ (number: Int) -> Bool in
return number % 2 == 0
})
//축약
filtered = numbers.filter { number % 2 == 0 }
print(filtered)
//[0, 2, 4]
파라미터로 넣어햐 하는 클로저 안에 Bool 값으로 판단 할 수 있는 조건을 작성한다고 생각하면 쉽게 할 수 있다.
if 문을 작성하 듯이 if 안에 와야 하는 조건문이 return 에 온다고 생각하면 편하다.
아래는 공식문서에 있는 filter 예시 코드이다.
이름이 들어가 있는 배열에서 이름이 5글자 미만인 목록만 뽑아낸 코드이다.
let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let shortNames = cast.filter { $0.count < 5 }
print(shortNames)
// Prints "["Kim", "Karl"]"
♻️ reduce
배열을 배열이 아닌 값으로 축약해서 나타내 주는 고차함수 이다.
reduce 를 모든 값을 더해주는 함수라고 설명하거나, 가산기라고 설명하는 사람들도 있다.
물론 reduce 가 그 역할을 주로 하는 것은 사실이지만, 코드를 어떻게 작성하냐에 따라서 그렇지 않을 수 도 있다고 생각한다.
reduce(줄이다) 라는 이름을 고려했을 때, 개인적으로는 시퀸스(주로 배열)를 하나의 값으로 줄이는데 초점이 맞춰져있다고 생각하고 있다.
reduce는 첫번째 파라미터로 initialResult 를 받는다. (제네릭 타입)
두번째 파라미터로 클로저를 받는다.
그 클로저는 R 제네릭 타입과, 배열의 요소를 파라미터로 가지고 R 타입을 return 한다.
우선 코드를 보자
let sample = [10, 20, 30 , 40 , 50]
//기본
let reducedSample = sample.reduce(1) { (r: Int, e: Int) -> Int in
print("r: \(r), e \(e)")
return r + e
}
//축약
let reducedSample = sample.reduce(1) { r + e }
//더 짧게
let reducedSample = sample.reduce(1, +)
print(reducedSample)
//r: 1, e 10
//r: 11, e 20
//r: 31, e 30
//r: 61, e 40
//r: 101, e 50
// 161
우선 이 코드에서는 첫번째 파라미터(초깃값)로 1을 넣었다.
클로저의 2개의 파라미터는 r, e 라고 이름을 지어 주었다. 초기값에 1을 넣었기 때문에 타입추론으로 Int 라고 인식을 했을 것이다. 그럼 r 의 타입도 Int 로 해주어야 한다. (같은 제네릭을 사용하고 있기 때문에) 그리고 e 부분에는 배열의 요소가 들어온다.
지금 출력된 프린트 문을 보면, 첫번째 r 에는 우리가 지정해준 1이라는 초기값이 들어와 있다. 그리고 e 에는 배열에 첫번째 요소인 10이 들어와 있다. 그리고 우리가 클로저의 리턴을 r + e 로 주었다. 1 + 10 이 된다.
이 11 이
라는 값은 다음 루프를 돌 때 r 이라는 값에 할당된다.
이렇게 마지막 요소까지를 반복해서 모든 요소가 더해진 161 이라는 값을 최종적으로 리턴 해준다.
보통은 초기값을 0으로 두면, 모든 배열을 더하는 코드를 짤 수 있다.
🟢 더 짧게의 축약이 가능한 이유
+
연산자를 우리는 그냥 사용하고 있지만, 실제로+
연산자는 클로저로 구현되어 있다.
그래서 클로저를 넣는 자리에+
연산자만 넣어도r + e
로 인식해서 잘 작동하는 것이다.
아래는 String 을 활용한 코드를 보자.
let favorite = [123, 456, 789]
//기본
let reducedFavorite = favorite.reduce("내가 좋아하는 숫자 :") {(r: String, e: Int) -> String in
return "\(r) \(e)"
}
//축약
let reducedFavorite = favorite.reduce("내가 좋아하는 숫자 :") { "\(r) \(e)" }
print(reducedFavorite)
//내가 좋아하는 숫자 : 123 456 789
초기값을 String 타입을 주었기 때문에 리턴 타입도 String 으로 주었다.
이런식으로 활용할 수 있다.
🧤 다양한 사용법
String에 filter 사용하기
filter를 String 에 사용하면 조금 특이하게 사용할 수 있다.
let alphabet = "abcdefghijklmnop"
let filteredAlphabet = alphabet.filter { $0 > "g" }
print(filteredAlphabet)
//hijklmnop
배열이 아니라 정재된 string 값을 리턴한다.
타입을 지정해주면 배열을 리턴하게 할 수도 있다.
let alphabet = "abcdefghijklmnop"
let filteredAlphabet: [Character] = alphabet.filter { $0 > "g" }
print(filteredAlphabet)
//["h", "i", "j", "k", "l", "m", "n", "o", "p"]
inout 이 있는 reduce
reduce 를 xcode 에서 사용하다 보면 자동완성에 뭔가 reduce 가 두개 있는 것을 발견할 수 있다.
같은 역할을 하는 reduce 인데 inout 키워드를 이용하기 때문에 사용법이 약간 다르다.
코드를 보자.
let sample = [10, 20, 30 , 40 , 50]
let a = sample.reduce(into: 1) { (r: inout Int, e: Int) in
r = r + e
}
// 161
위의 있는 reduce 예시와 완전 똑같이 작동하는 코드이다.
다른 점은 r 값이 inout 으로 사용되기 때문에 return 을 이용해서 값을 할당해 주는 것이 아니라, 직접적으로 할당을 해주어야 한다는 것이다.
사실 굳이 이렇게 써야 하나 싶긴 한데, 뭐 이렇게 작성할 수도 있다는 것을 알아 두자.
flatMap 으로 옵셔널 바인딩
이미 옵셔널인 값을 map을 돌리게 되면, 경우에 따라서 옵셔널이 하나더 생겨 이중 옵셔널이 되는 경우가 있다.
아래 코드의 Int($0) 부분을 보면, Int 를 String 값으로 변환하면서 옵셔널? 이 붙게 된다.
flatMap 을 사용하면, ?? 를 ? 로 바꿀수 있다.
let number: String? = "100"
let mapedNumber: Int?? = number.map { Int($0) }
let flatMapedNumber: Int? = number.flatMap { Int($0) }
print(mapedNumber)
//Optional(Optional(100))
print(flatMapedNumber)
//Optional(100)
공식문서
map 공식문서
compactMap 공식문서
flatMap 공식문서
filter 공식문서
reduce 공식문서
'🍎 iOS > 🕊️ swift' 카테고리의 다른 글
[swift] 의존성 주입으로 코드를 예쁘게 하자! (0) | 2023.01.06 |
---|---|
[swift] 부동 소수점 오류가 나는 이유 (0) | 2023.01.06 |
[iOS] Number Fomatter (0) | 2023.01.06 |
[swift] Subscript 구현하기 (0) | 2023.01.06 |
[swift] String.Index 는 무엇일까? (0) | 2023.01.06 |