👀 고차 함수란?
함수를 파라미터로 사용한 함수 또는 함수를 반환하는 함수.
클로저를 통해 내가 원하는 함수로 만들어서 사용할 수 있다.
Collection에 구현되어 있다 → 배열, 딕셔너리, 세트에서 사용 가능!
사실, 언제 이런 걸 쓰나 싶을 수 있는데 현업을 하면서 꽤나 자주, 유용하게 사용하고 있다. response로 받은 데이터를 우리가 원하는 형태로 변환해서 화면에 표출해줘야 하는데 더 깔끔하고 한눈에 알아볼 수 있게 해주는 코드로 만들어주는 것 중 하나가 고차 함수다.
- map
- filter
- reduce
- compactMap
- flatMap
이론은 간단히, 여러 상황 및 타입을 가정하고 예시를 통해 익혀보자 (예시 중심 게시글입니다.)
📌 map
각 element들을 어떤 형태로 변형할 때!
예를 들어, Int들을 String으로 바꾸고 싶다면??
let strings = [1, 2, 3].map { String($0) }
// ["1", "2", "3"]
각 element를 돌면서 매핑 클로저에 적힌 대로 변형되는 것.
strings에는 [1, 2, 3]가 변형된 새로운 배열 [”1”, “2”, “3”] 이 들어간다.
타입 혹은 값을 변형시킬 수도 있지만 이외에도 클로저에서 매핑한 조건에 맞는 결과 배열을 반환하기도 한다.
let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let lowercaseNames = cast.map { $0.lowercased() }
// 'lowercaseNames' == ["vivien", "marlon", "kim", "karl"]
let letterCounts = cast.map { $0.count } // 🌟
// 'letterCounts' == [6, 6, 3, 4]
📌 filter
element를 조건에 따라 걸러낼 때 사용! (필터링)
예를 들어, 빈 문자열을 걸러내고 싶다면???
let characterList = ["Rachel", "Monica", "Chandler", "", "Joe", "Phoebe", "", "Ross", ""]
let friends = characterList.filter { $0.isEmpty == false }
// ["Rachel", "Monica", "Chandler", "Joe", "Phoebe", "Ross"]
여기서 더 나아가 하나의 문자열로 만들려면??
짜잔
let characterList = ["Rachel", "Monica", "Chandler", "", "Joe", "Phoebe", "", "Ross", ""]
let friends = characterList
.filter { $0.isEmpty == false }
.joined(separator: ", ")
// Rachel, Monica, Chandler, Joe, Phoebe, Ross
클로저에 걸러내고 싶은 필터를 구현해 주면 된다!
📔 joined(separator:)는 정말 자주 사용하는 함수… 꼭 기억하자!
배열의 값이 1개일 경우(즉, 분리할 원소가 없다면) separator 분리자는 사용되지 않는다.
📌 reduce
각 element를 결합시켜서 하나의 값으로 변형시킬 때 (즉, 합치는~)
예를 들어, 중간고사 평균 점수를 알고싶다면?
let scores = [100, 98, 50, 100, 80]
let average = scores.reduce(0) { $0 + $1 } / scores.count
print(average)
// 85
reduce(0) ← 0 은 초기값을 의미한다. 이 값을 넣어줘야 한다.
$0는 앞의 값을 의미하는데 scores를 앞부터 더하면
0(초기값, $0) + 100($1) = 100
100($0) + 98($1) = 198
198($0) + 50($1) = 248
…
이렇게 앞에서 결합된 부분결과값이 $0가 되는 것이다~
숫자뿐만 아니라 문자열도 가능!
이번에는 제가 좋아하는 Taylor Swift의 New Romantics의 가사를 한번 붙여보자.
대신 각 문자열 사이에 줄 바꿈을 해보겠다!
let lyrics = ["'Cause, baby, I could build a castle", "Out of all the bricks they threw at me", "And every day is like a battle", "But every night with us is like a dream"]
let newRomantics = lyrics.reduce("") { $0 + "\\n\\n" + $1 }
print(newRomantics)
얼추 그럴듯해 보인다.
결과는??
'Cause, baby, I could build a castle
Out of all the bricks they threw at me
And every day is like a battle
But every night with us is like a dream
띠용. 맨 앞에 공백 뭐야~
맨 처음 시작할 때, 빈문자열 “” 와 “\n\n” 의 조합으로 인해 맨 앞에 공백이 생긴 것…
그렇다면 맨 앞부분만 빼고 해 볼까?
let lyrics = ["'Cause, baby, I could build a castle", "Out of all the bricks they threw at me", "And every day is like a battle", "But every night with us is like a dream"]
let newRomantics = lyrics.reduce("") { partialResult, value in
guard partialResult.isEmpty == false else { return value }
return "\\(partialResult)\\n\\n\\(value)"
}
print(newRomantics)
두구두구두구 🥁
'Cause, baby, I could build a castle
Out of all the bricks they threw at me
And every day is like a battle
But every night with us is like a dream
짜잔~
partialResult($0)와 value($1)를 가지고 조건을 추가해 줬다
- guard문을 가지고 초기값이 빈 문자열인 경우를 걸러내 준다 → 다음 partialResult가 value가 되도록 함
- guard문을 잘 넘긴 친구는 → \n\n를 포함해서 결합!
사실 이걸 보면서 읭 왜 저렇게 하지? 생각할 수 있다
위에는 작은 힌트가 숨어있는데~~
바로바로~~
reduce를 사용하지 않고 joined(separator:)를 사용하면 되기 때문!
let easyNewRomantics = lyrics.joined(separator: "\\n\\n")
우하하! 하지만 reduce를 좀 더 써보기 위함이었으니 쓸모없는 예시는 아니었습니다..
📌 compactMap
map인데, 옵셔널은 걸러준다! ← 굉장히 유용
이름처럼 compact 하게 값 있는 애들만 쪽 뽑아서 변환 후 반환
일을 하다보면 옵셔널 타입이 많고 값이 nil인 경우가 허다하다.
(사담) 이 게시글을 쓰게 된 이유인 우리의 compactMap. 재미있는 짜식
모든 element가 옵셔널이라면…? → Optional([]) 반환
예를 들어, 빈 문자열이거나 값이 nil인 경우를 걸러내고 싶다면? 이번에는 함수로 만들어보자!
func makeFriendsList(list: [String?]) -> [String]? {
guard list.isEmpty == false else { return nil }
return list.compactMap {
guard $0?.isEmpty == false else { return nil }
return $0
}
}
let characterList: [String?] = ["Rachel", "Monica", "Chandler", nil, "", "Joe", "Phoebe", "", "Ross", "", nil]
if let friends = makeFriendsList(list: characterList) {
print(friends)
} else {
print("There is no friends list... TwT")
}
// ["Rachel", "Monica", "Chandler", "Joe", "Phoebe", "Ross"]
makeFriendsList 함수를 통해서 characterList에 있는 값이 없거나 빈 문자열이 아닌 친구들의 이름을 담은 배열을 만들어냈다. 반환 타입을 옵셔널로 만들어줬고, if let 옵셔널 바인딩을 통해 값을 출력하도록 해줬다.
compactMap을 통해 값이 없는 (nil) 애들을 걸러줬는데 추가적으로 빈 문자열($0.isEmpty)까지 걸러서 내가 원하는 배열을 만들 수 있었다! ^0^ 만약 characterList가 빈 배열이라면 makeFriendsList는 nil을 반환할 것이다!
func makeFriendsList(list: [String?]) -> [String]? {
guard list.isEmpty == false else { return nil } // 여기서 이른 탈출! 🏃🏻♀️
return list.compactMap {
guard $0?.isEmpty == false else { return nil }
return $0
}
}
let characterList: [String?] = []
if let friends = makeFriendsList(list: characterList) {
print(friends)
} else {
print("There is no friend list~ TwT")
}
// There is no friend list~ TwT
이 예시를 가지고 좀 더 놀아보자! filter를 사용할 수도 있는데~~
func makeFriendsList(list: [String?]) -> [String]? {
guard list.isEmpty == false else { return nil }
return list
.compactMap { $0 }
.filter { $0.isEmpty == false }
}
compactMap으로 nil은 싹 없애주고 filter로 빈 문자열 걸러주기! 이렇게 하면 한눈에 알아보기 쉽네요 오호…
func makeFriendsList(list: [String?]) -> String {
return list
.compactMap {
guard $0?.isEmpty == false else { return nil }
return $0
}
.joined(separator: " / ")
}
let characterList: [String?] = ["Rachel", "Monica", "Chandler", nil, "", "Joe", "Phoebe", "", "Ross", "", nil]
print(makeFriendsList(list: characterList))
// Rachel / Monica / Chandler / Joe / Phoebe / Ross
이렇게도 할 수 있다! joined로 문자열 만들어주기~
📌 flatMap
내부의 중첩을 제거하고 하나로 만들어주는 함수!
쉽게 말해서, 2차원 배열을 1차원 배열로 만들어준다
let twoDimension = [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let oneDimension = twoDimension.flatMap { $0 }
print(oneDimension)
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
흠… 위에서 배운 애들로 추가해서 만들 수 있을 거 같은데???? (+_+)
// flatMap + reduce
let two = [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let one = two.flatMap { $0 }.reduce(0, +)
print(one) // 30
// flatMap + compactMap
let two: [[Int?]] = [[1], [2, nil], [3, nil, 3], [4, 4, 4, 4]]
let one = two.flatMap { $0 }.compactMap { $0 }
print(one) // [1, 2, 3, 3, 4, 4, 4, 4]
추가적으로 아래의 사이트에서 발견한 멋들어진 예제를 가져와봤다.
고차함수의 정의는 함수를 파라미터로 사용한다고 했는데 클로저도 함수이므로 우리는 계속 함수를 사용해 왔다. 주로 클로저 형태로 사용해서 그런지 함수를 사용한다는 생각이 안 들었다. 그런데 함수를 사용한 위의 예제를 보고 우왕 쩐다 했고 가져와봤다.
위의 블로그를 조금 가져와보면, hashtags(in:) 어떤 문자열에서 # 해시태그가 달린 단어를 뽑아내는 함수다.
func hashtags(in string: String) -> [String] {
let words = string.components(
separatedBy: .whitespacesAndNewlines
)
return word.filter { $0.starts(with: "#") }
}
let strings = [
"I'm excited about #SwiftUI",
"#Combine looks cool too",
"This year's #WWDC was amazing"
]
let tags = strings.flatMap(hashtags)
print(tags) // ["#SwiftUI", "#Combine", "#WWDC"]
flatMap의 매핑 클로저에 hashtags 함수를 넣었는데 strings의 각 원소들이 하나씩 hashtags의 매개변수로 들어가서 그 조건에 해당하는 결과를 반환하는 것이다.
그런데 hashtags함수를 통해 값을 변환해 줬는데 (문자열 → #가 붙은 문자열(단어)) map으로 하지 않고 flatMap으로 한 이유는?? hashtags의 반환 타입을 보면 [String] 문자열 배열이다. 즉, 결과인 tags를 보면 [[String]] 값을 도출해 낸다. 하지만 우리가 원하는 건?? [String] 문자열 배열이므로 flatMap으로 값을 변환 + 중첩 제거해 줬다.내가 만든 예제는 한참 멀었다ㅋ…
let tags = strings.map(hashtags)
print(tags) // [["#SwiftUI"], ["#Combine"], ["#WWDC"]]
'Swift' 카테고리의 다른 글
[Swift] DateFormatter 털어보기 (1/2) (0) | 2024.06.17 |
---|---|
Value Type vs Reference Type (1) | 2022.12.30 |
[Swift] 10진수 ↔ 2진수 변환하기 (0) | 2022.12.27 |
[Swift] 문자열과 문자(Strings & Characters) (2/2) (0) | 2022.02.13 |
[Swift] 문자열과 문자(Strings & Characters) (1/2) (0) | 2022.02.13 |