필수 과제 3번을 하는데
func a(_ input: [Int]) -> [Int] {
return input.enumerated()
.filter { $0.offset % 2 == 0 }
.map { $0.element }
}
func b(_ input: [String]) -> [String] {
return input.enumerated()
.filter { $0.offset % 2 == 0 }
.map { $0.element }
}
let intTest = [1, 2, 3, 4, 5]
let stringTest = ["가", "나", "다", "라", "마"]
print("a 함수 결과(Int) :", a(intTest)) // [1, 3, 5]
print("b 함수 결과(String) :", b(stringTest)) // ["가", "다", "마"]
a와 b는 각각 Int와 String형으로 짝수번째 요소 제거 후 반환하는 코드를 짰는데
c를 통해 하나의 함수로 대체할 수 있는 방법을 구현해야 하는 것에서 난관을 겪게 됨
같은 로직이지만 타입만 달라서 코드가 중복될 수밖에 없는데
이 때 제네릭 함수를 사용하면 해결할 수 있을 듯 함
모양은 똑같지만, 안에 들어가는 재료는 바뀌는 틀
도시락 통에 김밥도 넣을 수 있고 과일도 넣을 수 있고.. 또 뭐가 있지
암튼 되게 다양한 것들을 넣을 수 있을텐데
이 때 도시락 통이 제네릭 함수가 되는 것임
한 마디로 데이터 타입이 바뀌지만 구조는 그대로 쓸 수 있는 것.
func printIntArray(_ input: [Int]) {
for i in input {
print(i)
}
}
func printStringArray(_ input: [String]) {
for i in input {
print(i)
}
}
👉 요로코롬 숫자 배열, 문자 배열 따로따로 만들어야 하는데
func printArray<T>(_ input: [T]) {
for i in input {
print(i)
}
}
이렇게 만들기만 하면 됨
이때 T는 함수가 호출될 때 알아서 타입을 바꿔주는 친구임
이제 숫자, 글자 등등을 다 출력할 수 있음
| 제네릭 없음 | 제네릭 있음 |
|---|---|
| 같은 기능 함수 여러 개 | 하나만 만들면 됨 |
| 코드 중복 많음 | 코드 짧고 간단함 |
| 타입마다 따로 처리 | 한 번에 처리 가능 |
→ 코드가 깔끔해지고 일일히 넣어주는 것 보다 범용성이 넓어져서 다양한 타입을 넣을 수 있음
그러니까 c를 제네릭 함수를 통해 만들면
func c<T>(_ input: [T]) -> [T] {
return input.enumerated().filter { $0.offset % 2 == 0 }.map { $0.element }
}
print("c 함수 결과 (int) : ", c(intArray))
print("c 함수 결과 (string) : ", c(stringArray))
이런 식으로 구현할 수 있는 것.
코드를 자세하게 뜯어보면
func c<T>(_ input: [T]) -> [T]
| 부분 | 뜻 |
|---|---|
| func c | 함수 이름 c |
| < T > | 제네릭 타입 T 사용 → 어떤 타입의 배열이든 처리 가능 |
| (_ input: [T]) -> [T] | T 타입의 배열을 받아서, 같은 T 타입의 배열로 돌려줌 |
return input.enumerated()
.filter { $0.offset % 2 == 0 }
.map { $0.element }
.enumerated()
input.enumerated()
배열의 각 요소에 인덱스 번호를 붙여주는 함수
결과는 [(offset: Int, element: T)] 형태의 시퀀스 됨
["가", "나", "다"] → [(0, "가"), (1, "나"), (2, "다")]
.filter { $0.offset % 2 == 0 }
.filter { $0.offset % 2 == 0 }
조건에 맞는 요소만 남기는 함수.
offset이 짝수 인덱스(0, 2, 4)만 남김
[(0, "가"), (1, "나"), (2, "다"), (3, "라")]
→ 필터링 후 → [(0, "가"), (2, "다")]
.map { $0.element }
.map { $0.element }
요소를 변형해서 새 배열을 만드는 함수.
인덱스를 떼고 원래 요소만 남기게 됨
[(0, "가"), (2, "다")]
→ ["가", "다"]
func d<T: Numeric>(_ input: [T]) -> [T] {
return input.enumerated()
.filter { $0.offset % 2 == 0 }
.map { $0.element }
}
c 함수랑 비슷하지만 타입 제한 (T: Numeric) 이 추가됨
func d<T: Numeric>(_ input: [T]) -> [T]
| 부분 | 의미 |
|---|---|
| func d | 함수 이름은 d |
| <T: Numeric> | T는 제네릭 타입인데 Numeric 프로토콜을 따라야 함 |
| (_ input: [T]) | T 타입의 배열을 받아서 |
| -> [T] | 같은 타입의 배열을 반환 |
T: Numeric
숫자만 받아들일 수 있음
Int, Float, Double 같은 것만 가능하고
String, Bool, Date 같은 건 안됨
c랑 d의 다른 점
c는 문자든 숫자든 다 받아줘서 범용적이지만,
d는 숫자만 받아야 하는 상황에서 오류 방지에 유리함
그러니까 한 마디로!!! 이 함수는 숫자만 받아야 한다는 걸 컴파일 시점에서 강제화 햐서
오류를 미리 방지하려는 것임
물론 코드의 의도도 드러낼 수 있음 !
지금까지 짠 코드를 보면
main.swift
//
// main.swift
// ClosureBeyond
//
// Created by 노가현 on 6/2/25.
//
import Foundation
let num = 3
let intArray = [1, 2, 3, 4, 5]
let stringArray = ["가", "나", "다", "라", "마"]
print(sum(num, 7))
calculator(closure: sum)
convertIntArrayToStringArray()
filterEvenNumbersAndConvertToString()
exampleMyMap()
print("a 함수 :", a(intArray))
print("b 함수 :", b(stringArray))
print("c(Int) 함수 :", c(intArray))
print("c(String) 함수 :", c(stringArray))
print("d 함수 :", d(intArray))
highOrderFunction.swift
//
// HighOrderFunctions.swift
// ClosureBeyond
//
// Created by 노가현 on 6/2/25.
//
import Foundation
func convertIntArrayToStringArray() {
let numbers = [1, 2, 3, 4, 5]
let result = numbers.map(String.init)
print("숫자 -> 문자열 :", result)
}
func filterEvenNumbersAndConvertToString() {
let input = Array(1...10)
let output = input
.filter { $0.isMultiple(of: 2) }
.map(String.init)
print("짝수만 문자열로 :", output)
}
func myMap(_ array: [Int], _ transform: (Int) -> String) -> [String] {
var result: [String] = []
for item in array {
result.append(transform(item))
}
return result
}
func exampleMyMap() {
let result = myMap([1, 2, 3, 4, 5]) { String($0) }
print(result)
}
func a(_ input: [Int]) -> [Int] {
input.enumerated()
.filter { $0.offset.isMultiple(of: 2) }
.map(\.element)
}
func b(_ input: [String]) -> [String] {
input.enumerated()
.filter { $0.offset.isMultiple(of: 2) }
.map(\.element)
}
func c<T>(_ input: [T]) -> [T] {
input.enumerated()
.filter { $0.offset.isMultiple(of: 2) }
.map(\.element)
}
func d<T: Numeric>(_ input: [T]) -> [T] {
input.enumerated()
.filter { $0.offset.isMultiple(of: 2) }
.map(\.element)
}
closures.swift
//
// Closures.swift
// ClosureBeyond
//
// Created by 노가현 on 6/2/25.
//
import Foundation
let sum: (Int, Int) -> String = { a, b in
"두 수의 합은 \(a + b)입니다"
}
func calculator(closure: (Int, Int) -> String) {
let result = closure(4, 5)
print(result)
}
요로코롬 짰는데
가독성이 떨어진 거 같아서 코드를 수정하게 됨
원래 코드에서는 짝수 인덱스를 확인할 때 offset % 2 == 0을 사용하고, 요소를 꺼낼 때 map { $0.element }을 사용했는데 가독성이 떨어진 거 같아서 리팩터링할 때는
offset % 2 == 0 대신 offset.isMultiple(of: 2)를 사용함
이 값이 2의 배수인가?를 더 잘 나타내주는 방식이라 생각했음
KeyPath 문법으로 map { $0.element }도 map(.element)로 줄였음