정보/레벨 1

Swift의 고차 함수(Higher-Order Functions)에 대해 설명해주세요.

밤새는 탐험가89 2024. 11. 12. 11:24

 고차 함수(Higher-Order Functions)는 다른 함수를 인자로 받거나, 함수 자체를 반환하는 함수를 의미합니다. Swift에서는 고차 함수를 통해 코드의 간결성과 가독성을 높이며, 컬렉션을 효과적으로 조작할 수 있습니다. Swift의 고차 함수는 주로 배열이나 딕셔너리 같은 컬렉션에서 자주 사용되며, 함수형 프로그래밍의 중요한 개념 중 하나입니다.

 

 

map

  • 설명: 컬렉션의 각 요소에 동일한 연산을 적용한 후, 그 결과를 새로운 배열로 반환하는 함수입니다. map은 기존 배열의 값을 변경하지 않고, 변환된 값을 기반으로 새로운 배열을 생성합니다.
let numbers = [1, 2, 3, 4, 5]
let squaredNumbers = numbers.map { $0 * $0 }
print(squaredNumbers)  // 출력: [1, 4, 9, 16, 25]

 

  • 활용: map은 데이터를 변환할 때 유용합니다. 예를 들어, 문자열 배열을 정수 배열로 변환하거나, 특정 값만을 추출할 때 사용합니다.

 

filter

  • 설명: 컬렉션의 각 요소를 조건에 따라 필터링하여, 조건을 충족하는 요소만을 포함하는 새로운 배열을 반환합니다.
let numbers = [1, 2, 3, 4, 5]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers)  // 출력: [2, 4]

 

  • 활용: filter는 특정 조건을 충족하는 요소만을 남기고 나머지를 제거할 때 사용됩니다. 예를 들어, 배열에서 짝수만 남기거나 특정 문자열이 포함된 요소를 필터링할 때 유용합니다.

 

 

reduce

  • 설명: 컬렉션의 모든 요소를 하나의 값으로 합치는 함수입니다. 초기값과 결합 클로저를 제공하여, 컬렉션의 각 요소를 순차적으로 결합한 결과를 반환합니다.
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { $0 + $1 }
print(sum)  // 출력: 15

 

  • 활용: reduce는 배열의 값을 합산하거나 곱한 결과를 얻을 때 유용합니다. 문자열을 이어붙이거나, 특정 조건에 따라 누적 결과를 계산할 때도 사용할 수 있습니다.

 

 

compactMap

  • 설명: map과 유사하지만, nil이 아닌 유효한 값만을 포함하는 배열을 반환합니다. 변환 과정에서 nil이 발생할 경우 이를 자동으로 제거합니다.
let values = ["1", "2", "three", "4", "five"]
let numbers = values.compactMap { Int($0) }
print(numbers)  // 출력: [1, 2, 4]

 

  • 활용: compactMap은 옵셔널 배열을 필터링할 때 유용하며, 변환 과정에서 발생하는 nil 값을 제거하여 유효한 값만을 처리할 수 있습니다.

 

flatMap

  • 설명: 2차원 배열(배열의 배열)을 1차원 배열로 펼쳐 주는 함수입니다. 각 요소를 변환한 후 nil 값을 제외하고 하나의 배열로 평평하게 만듭니다.
let nestedArray = [[1, 2, 3], [4, 5], [6, 7, 8]]
let flatArray = nestedArray.flatMap { $0 }
print(flatArray)  // 출력: [1, 2, 3, 4, 5, 6, 7, 8]

 

  • 활용: flatMap은 중첩된 배열을 단순화하여 1차원 배열로 처리할 때 유용합니다. 또한, 배열 내의 옵셔널 값을 제거하고 배열을 평탄화할 때도 사용됩니다.

 

요약

  • 고차 함수(Higher-Order Functions): Swift에서 다른 함수를 인자로 받거나 함수 자체를 반환하는 함수로, 코드의 간결성, 가독성, 유지보수성을 높이는 역할을 합니다.
  • 주요 고차 함수:
    • map: 각 요소에 연산을 적용한 새로운 배열을 반환. 데이터 변환에 유용.
    • filter: 조건을 만족하는 요소만을 포함하는 배열을 반환. 특정 조건으로 필터링할 때 사용.
    • reduce: 컬렉션의 요소들을 하나의 값으로 합침. 합산, 누적 계산에 유용.
    • compactMap: 변환 후 nil이 아닌 값들만 배열로 반환. 옵셔널 제거에 유용.
    • flatMap: 중첩 배열을 평평한 배열로 변환. 2차원 배열을 1차원으로 만들 때 사용.
  • 장점:
    • 코드 간결화: 복잡한 루프나 조건문을 줄여 직관적 코드 작성 가능.
    • 불변성 유지: 원본을 수정하지 않고 새로운 컬렉션 반환.
    • 함수형 프로그래밍 패러다임: 데이터 처리 로직을 간결하고 명확하게 작성.

Swift의 고차 함수는 컬렉션을 효율적으로 조작할 수 있게 하며, 코드의 품질을 높이는 중요한 기능입니다. iOS 개발에서는 이러한 고차 함수의 개념과 사용법을 이해하는 것이 매우 유용합니다.

 

 

map과 flatMap의 차이점은 무엇인가요?

**map**과 **flatMap**은 모두 컬렉션의 각 요소에 연산을 적용하는 고차 함수이지만, 변환 결과와 배열의 구조를 처리하는 방식에서 차이가 있습니다. map은 변환된 요소를 그대로 배열에 담아 반환하는 반면, flatMap은 **중첩된 배열을 평탄화(Flatten)**하여 1차원 배열로 반환합니다.

 

map과 flatMap의 차이점

  1. 변환 결과의 배열 구조
    • map: 각 요소를 변환한 결과를 중첩된 배열로 그대로 반환합니다. 변환 결과가 배열인 경우, 원래 배열 안에 배열이 중첩되어 있는 형태로 반환됩니다.
    • flatMap: 각 요소를 변환한 결과를 평탄화하여 1차원 배열로 반환합니다. 변환 결과가 배열인 경우, 중첩된 배열을 한 차원 낮춰서 단일 배열로 반환합니다.
  2. nil 값 처리
    • map: 옵셔널 값을 처리할 때 nil 값을 그대로 유지합니다. 변환 결과 배열에 nil 값이 포함될 수 있습니다.
    • flatMap: 옵셔널 값에서 nil을 자동으로 제거합니다. nil이 아닌 값들만 남겨 유효한 값들로만 배열을 구성합니다.

 

배열 내 배열을 다룰 때의 차이

let nestedArray = [[1, 2, 3], [4, 5], [6, 7, 8]]

// map 사용
let mappedArray = nestedArray.map { $0 }
print(mappedArray)  
// 출력: [[1, 2, 3], [4, 5], [6, 7, 8]] (중첩 배열)

// flatMap 사용
let flatMappedArray = nestedArray.flatMap { $0 }
print(flatMappedArray)  
// 출력: [1, 2, 3, 4, 5, 6, 7, 8] (1차원 배열)

 

위 예시에서 flatMap은 중첩된 배열을 평탄화하여 1차원 배열로 반환합니다. 반면 map은 중첩 배열을 유지한 채 반환합니다.

 

 

옵셔널을 포함한 배열에서의 차이

let values = ["1", "2", "three", "4"]

// map 사용
let mappedValues = values.map { Int($0) }
print(mappedValues)  
// 출력: [Optional(1), Optional(2), nil, Optional(4)] (옵셔널 포함)

// flatMap 사용
let flatMappedValues = values.compactMap { Int($0) }
print(flatMappedValues)  
// 출력: [1, 2, 4] (nil 제거)

 

위 예시에서 flatMap은 nil 값을 자동으로 제거하고 유효한 값만 반환하는 반면, map은 nil 값을 그대로 유지하여 옵셔널 배열을 반환합니다.

 

요약

  • map: 각 요소를 변환한 후 그대로 배열에 포함하여 반환. 옵셔널 값을 변환할 때는 nil 값을 유지.
  • flatMap: 각 요소를 변환한 후 중첩 배열을 평탄화하여 반환. 옵셔널 값을 변환할 때는 nil을 제거.

map과 flatMap은 배열의 구조와 옵셔널 처리 방식에서 차이가 있으므로, 상황에 따라 적절한 함수를 선택해 사용해야 합니다.

 

 

filter, reduce 함수는 어떤 경우에 사용하나요?

**filter**와 **reduce**는 Swift에서 컬렉션을 조작하고 원하는 조건에 맞는 데이터를 추출하거나 합치는 데 유용한 고차 함수입니다. 이 두 함수는 각각 특정한 목적에 맞춰 데이터를 처리할 때 사용되며, 주로 다음과 같은 경우에 활용됩니다.

 

filter 함수 사용 경우

filter 함수는 컬렉션의 각 요소를 조건에 따라 필터링하여, 특정 조건을 만족하는 요소만을 포함하는 새로운 배열을 반환합니다.

  • 특정 조건을 만족하는 요소만 선택할 때
    • 예를 들어, 배열에서 짝수만 추출하고 싶을 때 filter를 사용해 간결하게 필터링할 수 있습니다.
let numbers = [1, 2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers)  // 출력: [2, 4, 6]

 

  • 텍스트 검색 기능에 활용
    • 검색어가 포함된 요소만 선택하는 경우, filter를 사용해 간편하게 조건을 적용할 수 있습니다. 예를 들어, 사용자 리스트에서 특정 이름이 포함된 사용자만 추출할 수 있습니다.
let names = ["Alice", "Bob", "Charlie", "Daniel"]
let searchResult = names.filter { $0.contains("a") || $0.contains("A") }
print(searchResult)  // 출력: ["Alice", "Charlie", "Daniel"]

 

 

  • 특정 조건에 맞는 객체 필터링
    • 예를 들어, 나이가 18세 이상인 사용자만 추출하고자 할 때 filter를 활용하여 객체 배열을 필터링할 수 있습니다.
struct User {
    let name: String
    let age: Int
}
let users = [User(name: "Alice", age: 22), User(name: "Bob", age: 17), User(name: "Charlie", age: 19)]
let adults = users.filter { $0.age >= 18 }
// 출력: [User(name: "Alice", age: 22), User(name: "Charlie", age: 19)]

 

 

 

reduce 함수 사용 경우

reduce 함수는 컬렉션의 모든 요소를 하나의 값으로 결합할 때 사용됩니다. 주로 합산, 곱셈, 문자열 연결, 특정 기준의 최댓값 및 최솟값 계산 등 누적 계산이 필요할 때 활용됩니다.

 

  • 배열의 합 또는 곱 계산
    • reduce를 사용해 배열의 모든 요소를 더하거나 곱하는 등의 누적 계산을 간단하게 처리할 수 있습니다.
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { $0 + $1 }
print(sum)  // 출력: 15

 

 

  • 최대값 또는 최솟값 찾기
    • reduce를 사용해 특정 기준의 최대값이나 최솟값을 찾는 데 유용합니다.
let numbers = [10, 20, 5, 40, 25]
let maxNumber = numbers.reduce(numbers[0]) { max($0, $1) }
print(maxNumber)  // 출력: 40

 

  • 배열의 문자열 결합
    • 문자열 배열의 모든 요소를 하나의 문자열로 연결할 때 reduce를 사용할 수 있습니다.
let words = ["Hello", "Swift", "World"]
let sentence = words.reduce("") { $0 + " " + $1 }
print(sentence)  // 출력: "Hello Swift World"

 

 

  • 복합 데이터 누적 계산
    • 객체 배열의 특정 필드 값을 누적하여 총합을 계산하는 데 활용할 수 있습니다. 예를 들어, 쇼핑카트의 총 금액 계산에 유용합니다.
struct Product {
    let name: String
    let price: Double
}
let cart = [Product(name: "Phone", price: 799.99), Product(name: "Laptop", price: 1200.00), Product(name: "Headphones", price: 199.99)]
let totalCost = cart.reduce(0) { $0 + $1.price }
print(totalCost)  // 출력: 2199.98

 

🔥 let totalCost = cart.reduce(0) { $0 + $1.price } 코드 설명

reduce 함수는 두 개의 매개변수를 받습니다:

  1. 초기값: 누적 결과의 시작값입니다. 이 예제에서는 0이 초기값으로 설정되어 있습니다.
  2. 결합 클로저: 각 요소를 누적 결과와 결합하는 로직입니다. 여기서는 { $0 + $1.price }라는 클로저가 결합 로직으로 사용되었습니다.

즉, reduce는 초기값 0부터 시작해서 배열의 각 요소를 누적 결과에 더해가는 방식으로 동작합니다.

 

클로저 { $0 + $1.price }의 역할

  • $0: reduce 함수의 누적 결과를 나타냅니다. 초기에는 reduce(0)에서 지정한 값인 0으로 시작합니다.
  • $1: cart 배열의 각 요소를 나타냅니다. 각 요소는 Product 타입의 인스턴스이며, 그중에서도 price 속성을 통해 가격을 가져옵니다.

따라서 { $0 + $1.price }라는 클로저는 누적 결과에 현재 Product의 가격을 더해주는 역할을 합니다.

동작 과정

  1. 첫 번째 반복:
    • $0: 초기값 0 (누적 결과)
    • $1: Product(name: "Phone", price: 799.99)
    • 계산: 0 + 799.99 -> 결과 799.99
  2. 두 번째 반복:
    • $0: 이전 반복의 결과 799.99 (누적 결과)
    • $1: Product(name: "Laptop", price: 1200.00)
    • 계산: 799.99 + 1200.00 -> 결과 1999.99
  3. 세 번째 반복:
    • $0: 이전 반복의 결과 1999.99 (누적 결과)
    • $1: Product(name: "Headphones", price: 199.99)
    • 계산: 1999.99 + 199.99 -> 결과 2199.98
  4. 모든 요소를 순회했으므로 최종 결과는 2199.98입니다.

 

 

요약

  • filter 함수: 특정 조건을 만족하는 요소만 선택하여 새로운 배열을 반환할 때 사용합니다. 데이터 검색, 특정 조건에 맞는 객체 선택에 유용합니다.
  • reduce 함수: 배열의 모든 요소를 하나의 값으로 결합할 때 사용합니다. 합산, 곱셈, 문자열 연결, 최댓값 및 최솟값 계산 등 누적 작업에 적합합니다.

이 두 고차 함수는 데이터 필터링 및 누적 계산이 필요한 경우 코드의 간결성과 가독성을 높여주는 중요한 도구입니다.

 

 

compactMap은 어떤 역할을 하나요?

**compactMap**은 옵셔널(nil) 값을 제거하고 유효한 값만을 포함하는 배열을 반환하는 고차 함수입니다. 주로 옵셔널을 포함한 배열에서 nil 값을 걸러내고, 변환한 값들만 모아 새로운 배열을 생성할 때 사용됩니다. compactMap은 map과 유사하지만, 변환 과정에서 nil 값을 자동으로 제외하여 비어 있지 않은 값만 남깁니다.

 

  • 옵셔널 변환 및 nil 제거
    • compactMap은 각 요소를 변환한 후 결과가 nil이 아닌 경우에만 배열에 포함합니다. 예를 들어, 문자열을 정수로 변환하는 과정에서 변환에 실패한 경우 nil이 반환되는데, 이 nil을 자동으로 걸러내고 유효한 정수들만 포함한 배열을 반환합니다.
let values = ["1", "2", "three", "4"]
let numbers = values.compactMap { Int($0) }
print(numbers)  // 출력: [1, 2, 4]

 

 

위 예시에서는 "three"가 Int로 변환될 수 없으므로 nil이 되며, compactMap은 이를 제외하고 [1, 2, 4]만 반환합니다.

 

  • 옵셔널 중첩 배열에서 유효한 값 추출
    • 옵셔널 배열에서 nil 값을 제거하고 유효한 값만을 추출할 때 유용합니다. 예를 들어, nil이 포함된 배열을 compactMap을 사용해 nil을 제거하고 값이 있는 요소만 남길 수 있습니다.
let items: [String?] = ["Apple", nil, "Banana", nil, "Cherry"]
let validItems = items.compactMap { $0 }
print(validItems)  // 출력: ["Apple", "Banana", "Cherry"]

 

위 예시에서 compactMap은 nil 값을 제거하여, ["Apple", "Banana", "Cherry"]라는 유효한 값들만 반환합니다.

 

  • 중첩된 Optional에서 Unwrapping
    • compactMap은 중첩된 옵셔널을 한 번에 처리할 수 있습니다. 이로 인해 옵셔널 배열이 또 다른 옵셔널을 포함할 때 유효한 값만 꺼내는 데 유용합니다.
let nestedOptionals: [Int??] = [1, nil, 2, nil, 3]
let flattenedValues = nestedOptionals.compactMap { $0 }
print(flattenedValues)  // 출력: [1, 2, 3]

 

여기서 compactMap은 중첩된 옵셔널을 벗기면서 nil을 제거해 [1, 2, 3]만 반환합니다.

 

요약

  • 역할: compactMap은 nil을 자동으로 걸러내고 유효한 값만 포함한 배열을 반환합니다.
  • 사용 상황:
    • 변환 과정에서 실패할 수 있는 값들을 다룰 때.
    • 옵셔널 배열에서 nil을 제거하고 유효한 값들만 필요할 때.
    • 중첩된 옵셔널을 제거하여 값을 평탄화할 때.

compactMap은 유효한 데이터만 다루어야 할 때 매우 유용하며, 데이터 필터링과 변환 과정을 간소화할 수 있는 중요한 고차 함수입니다.

 

 

🔥 let nestedOptionals: [Int??] = [1, nil, 2, nil, 3] 여기서 타입에 ?? 두개는 무슨뜻이야?

 타입에 ?? 두 개가 붙은 Int??는 중첩된 옵셔널을 의미합니다. 즉, 이 타입은 Optional<Optional<Int>>와 같은 형태로, 옵셔널이 한 번 더 감싸진 상태를 나타냅니다.

 

 Swift에서 옵셔널이 한 번 더 중첩되면 Int??와 같이 표시됩니다. 이중 옵셔널을 사용하는 상황은 흔하지 않지만, 데이터 변환이나 API 응답을 다룰 때나 옵셔널의 옵셔널이 필요할 때 발생할 수 있습니다.

 

let nestedOptionals: [Int??] = [1, nil, 2, nil, 3]

 

위 예시에서 각 요소는 Int?? 타입이며, 값이 1, 2, 3 같은 Int일 수도 있고, Optional(nil)일 수도 있으며, 완전히 nil일 수도 있는 상황을 나타냅니다.

  • 1을 포함한 경우: Optional(Optional(1))
  • 값이 nil인 경우: Optional(nil)
  • 전체가 nil인 경우: nil

따라서 [Int??]는 다음과 같은 3가지 상태를 가질 수 있습니다.

  1. 숫자 값을 포함한 옵셔널 값: Optional(Optional(1)) (옵셔널을 한 번 감싼 후 다시 감싼 상태)
  2. nil을 감싼 옵셔널: Optional(nil)
  3. 완전히 nil인 값: nil

중첩된 옵셔널을 다룰 때는 flatMap이나 compactMap을 사용해 옵셔널을 풀고 nil을 제거할 수 있습니다.

let nestedOptionals: [Int??] = [1, nil, 2, nil, 3]
let flattenedValues = nestedOptionals.compactMap { $0 }  // 1단계 옵셔널 제거
print(flattenedValues)  // 출력: [Optional(1), Optional(2), Optional(3)]

let finalValues = flattenedValues.compactMap { $0 }  // 2단계 옵셔널 제거
print(finalValues)  // 출력: [1, 2, 3]

 

 

여기서 첫 번째 compactMap은 Int??를 Int?로 풀고, 두 번째 compactMap은 Int?를 Int로 풀어내며, nil 값은 제거합니다.

 

요약

  • Int??는 중첩된 옵셔널을 의미하며, 옵셔널이 한 번 더 감싸진 상태입니다.
  • Int??는 Optional<Optional<Int>> 형태로, 값이 Int일 수도 있고, Optional(nil)일 수도 있으며, 완전히 nil일 수도 있습니다.
  • 중첩 옵셔널은 compactMap 또는 flatMap을 사용하여 필요한 값을 추출하고 nil을 제거할 수 있습니다.