Jupyo's Daily Story
고차 함수(Higher-Order Functions) 본문
728x90
반응형
고차 함수(Higher-Order Functions)는 함수형 프로그래밍 패러다임의 핵심 요소 중 하나입니다.
고차 함수는 다음 두 가지 특징 중 하나 이상을 만족하는 함수를 의미합니다.
- 하나 이상의 함수를 인자로 받는다.
- 함수를 결과(반환 값)로 반환한다.
Swift는 함수를 일급 객체(First-Class Citizen)로 취급하기 때문에 고차 함수를 자유롭게 사용할 수 있습니다. 일급 객체라는 것은 함수가 변수나 상수에 할당될 수 있고, 다른 함수의 인자로 전달될 수 있으며, 함수의 반환 값으로 사용될 수 있다는 것을 의미합니다. Swift의 클로저(Closures)는 이러한 함수형 프로그래밍의 기능을 제공하는 강력한 도구이며, 고차 함수는 주로 클로저와 함께 사용되는 컬렉션(배열, 딕셔너리, 세트 등)의 데이터를 효율적으로 처리하고 코드를 간결하게 만듭니다.
주요 고차 함수들은 Swift 표준 라이브러리의 Sequence 및 Collection 프로토콜에 정의되어 있어, 배열(Array), 세트(Set), 딕셔너리(Dictionary) 등 다양한 컬렉션 타입에서 사용할 수 있습니다.
Swift의 주요 고차 함수
가장 자주 사용되는 고차 함수는 map, filter, reduce이며, 이 외에도 forEach, compactMap, floatMap, sorted 등이 있습니다.
map
- 역할 : 컬렉션의 각 요소를 변형(transform)하여 새로운 컬렉션을 반환합니다.
- 특징 : 원본 컬렉션의 요소 개수는 유지되지만, 각 요소의 타입이나 값은 변형될 수 있습니다. 원본 컬렉션은 변경되지 않습니다.
let numbers = [1, 2, 3, 4, 5]
// 각 숫자를 2배로 변형
let doubledNumbers = numbers.map { $0 * 2 }
print(doubledNumbers) // [2, 4, 6, 8, 10]
// 숫자를 문자열로 변형
let stringNumbers = numbers.map { "Number: \($0)" }
print(stringNumbers) // ["Number: 1", "Number: 2", "Number: 3", "Number: 4", "Number: 5"]
// Dictionary에도 적용 가능 (키-값 쌍을 변형)
let sources = ["Alice": 90, "Bob": 85, "Charlie": 92]
let studentNames = sources.map { $0.key }
print(studentNames) // ["Alice", "Bob", "Charlie"] (순서는 보장되지 않음)
filter
- 역할 : 컬렉션의 각 요소를 특정 조건에 따라 걸러내어(filter) 조건을 만족하는 요소들로 구성된 새로운 컬렉션을 반환합니다.
- 특징 : 클로저가 Bool 값을 반환하며, true를 반환하는 요소만 새로운 컬렉션에 포함됩니다. 원본 컬렉션은 변경되지 않습니다.
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 짝수만 필터링
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // [2, 4, 6, 8, 10]
// 5보다 큰 숫자만 필터링
let greaterThanFive = numbers.filter { $0 > 5 }
print(greaterThanFive) // [6, 7, 8, 9, 10]
let names = ["Alice", "Bob", "Charlie", "David"]
// 'A'로 시작하는 이름만 필터링
let aNames = names.filter { $0.hasPrefix("A") }
print(aNames) // ["Alice"]
reduce
- 역할 : 컬렉션의 모든 요소를 하나의 단일 값으로 결합(combine)합니다.
- 특징 : 초기 값(initial result)과 클로저를 인자로 받습니다. 클로저는 두 개의 인자를 받는데, 하나는 현재까지의 누적 값(result)이고 다른 하나는 컬렉션의 다음 요소(element)입니다.
let numbers = [1, 2, 3, 4, 5]
// 모든 요소의 합계 계산 (초기값 0)
let sum = numbers.reduce(0) { (currentSum, number) in
currentSum + number
}
print(sum) // 15
// 축약형
let sumShorthand = numbers.reduce(0, +)
print(sumShorthand) // 15
// 모든 요소를 문자열로 결합 (초기값 빈 문자열)
let combinedString = numbers.reduce("") { (currentString, number) in
currentString + String(number)
}
print(combinedString) // "12345"
// 배열의 모든 요소를 곱하기 (초기값 1)
let product = numbers.reduce(1) { $0 * $1 }
print(product) // 120
forEach
- 역할 : 컬렉션의 각 요소에 대해 주어진 클로저를 실행합니다.
- 특징 : for-in 루프와 유사하지만, forEach는 클로저 내부에서 break나 continue를 사용할 수 없습니다. 주로 각 요소에 대해 부수 효과(side effect)를 발생시킬 때 사용됩니다. 새로운 컬렉션을 반환하지 않습니다.
let names = ["Alice", "Bob", "Charlie"]
names.forEach { name in
print("Hello, \(name)!")
}
// 출력:
// Hello, Alice!
// Hello, Bob!
// Hello, Charlie!
compactMap
- 역할 : map과 유사하게 컬렉션의 각 요소를 변형하지만, nil 값을 결과에서 자동으로 제거합니다.
- 특징 : 변형 클로저가 옵셔널 값을 반환할 때 유용하며, nil이 아닌 값들로 구성된 새로운 컬렉션을 반환합니다.
let stringNumbers = ["1", "2", "three", "4", "five"]
// 문자열을 정수로 변환 (변환 실패 시 nil 반환)
let numbers = stringNumbers.compactMap { Int($0) }
print(numbers) // [1, 2, 4] (nil인 "three"와 "five"는 제거됨)
flatMap
- 역할 : 중첩된 컬렉션을 평탄화(flatten)하면서 각 요소를 변형합니다.
- 특징 : 컬렉션 안에 컬렉션이 있을 때 이를 단일 레벨의 컬렉션으로 만들면서 변형을 적용합니다. map과 flatten이 합쳐진 형태라고 생각할 수 있습니다.
let nestedNumbers = [[1, 2], [3, 4, 5], [6]]
// 중첩 배열을 평탄화하여 하나의 배열로 만듦
let flatNumbers = nestedNumbers.flatMap { $0 }
print(flatNumbers) // [1, 2, 3, 4, 5, 6]
// 각 서브 배열의 요소를 2배로 변형하고 평탄화
let doubledAndFlat = nestedNumbers.flatMap { $0.map { $0 * 2 } }
print(doubleAndFlat) // [2, 4, 6, 8, 10, 12]
// 옵셔널 배열을 평탄화하면서 nil 제거
let optionalArrays: [[Int?]] = [[1, nil, 2], [3, 4, nil]]
let flatAndNonNil = optionalArrays.flatMap { $0.compactMap { $0 } }
print(flatAndNonNil) // [1, 2, 3, 4]
sorted
- 역할 : 컬렉션의 요소를 정렬하여 새로운 정렬된 컬렉션을 반환합니다.
- 특징 : 정렬 기준을 정의하는 클로저를 인자로 받습니다. 클로저는 두 요소를 비교하여 첫 번째 요소가 두 번째 요소보다 앞에 와야 할 때 true를 반환합니다.
let unsortedNumbers = [3, 1, 4, 1, 5, 9, 2]
// 오름차순 정렬 (기본)
let ascendingNumbers = unsortedNumbers.sorted()
print(ascendingNumbers) // [1, 1, 2, 3, 4, 5, 9]
// 내림차순 정렬
let descendingNumbers = unsortedNumbers.sorted { $0 > $1 }
print(descendingNumbers) // [9, 5, 4, 3, 2, 1, 1]
let words = ["apple", "banana", "kiwi", "grape"]
// 길이 순으로 정렬
let sortedByLength = words.sorted { $0.count < $1.count }
print(sortedByLength) // ["kiwi", "apple", "grape", "banana"]
고차 함수 사용의 이점
- 코드 간결성 및 가독성 : for-in 루프와 임시 변수 사용을 줄여 코드를 더 짧고 읽기 쉽게 만듭니다.
- 오류 감소 : 반복문에서 발생할 수 있는 인덱스 오류, 오프 바이 원(off-by-one) 오류 등을 줄여줍니다.
- 함수형 프로그래밍 스타일 : 데이터를 불변(immutable)하게 유지하고, 부수 효과를 최소화하는 함수형 프로그래밍 원칙을 따르기 용이하게 합니다.
- 재사용성 : 컬렉션 처리 로직을 추상화하여 다양한 상황에서 재사용할 수 있는 유용성을 제공합니다.
- 성능 최적화 : Swift 표준 라이브러리에서 제공하는 고차 함수는 내부적으로 성능 최적화가 잘 되어 있는 경우가 많습니다.
반응형
'Swift' 카테고리의 다른 글
mutating (0) | 2025.06.16 |
---|---|
@MainActor (2) | 2025.06.05 |
프로토콜 (Protocols) (0) | 2024.11.10 |
static, class, final class 프로퍼티/메서드 (2) | 2024.10.26 |
생명주기 (Lifecycle) (2) | 2024.10.14 |