본문 바로가기

swift

[Swift] map, flatMap, compactMap, reduce

1.map

1. normal

func map<T>(_ transform: (Self.Element) -> T) -> [T]
  • Self.Element를 T로 변환하여 새로운 배열을 반환하는 역할을 한다.
  • 즉, 배열의 원소를 원하는 타입으로 변환하여 새로운 배열을 만드는 것
let numbers = [1, 2, 3, 4]
let strings = numbers.map { "\($0)" }
print(strings) // ["1", "2", "3", "4"]

 

2. error handling

func map<T, E>(_ transform: (Self.Element) throws(E) -> T) throws(E) -> [T] where E : Error
  • 일반적인 map과 달리 Error를 던질 수 있다.
  • transform 클로져에서 Error를 던지면 map함수 자체에서도 Error를 던진다.
let invalidStrings = ["1", "two", "3"]

do {
    let numbers = try invalidStrings.map { str -> Int in
        guard let num = Int(str) else {
            throw ConversionError.invalidNumber
        }
        return num
    }
    print(numbers)
} catch {
    print("Conversion failed: \(error)") // Conversion failed: invalidNumber
}

 

 

2. flatMap

signature

func flatMap<SegmentOfResult>(
    _ transform: (Self.Element) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

 

사용 예시

let numbers = [1, 2, 3, 4]

// 맵
let mapped = numbers.map { Array(repeating: $0, count: $0) }
print(mapped) 
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]

//flatMap의 평탄화
let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
print(flatMapped) 
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

 

Sequence Protocol

protocol Sequence {
    associatedtype Element
    associatedtype Iterator: IteratorProtocol //반복자
    func makeIterator() -> Iterator
}
  • 제네릭 타입
    • SegmentOfResult
      • Sequence 프로토콜을 준수해야 함
      • Sequence 프로토콜
        • Array, set, dictionary등의 컬렉션을 포함하고 반복가능(iterable) 할 수 있는 모든 타입을 정의하는 프로토콜이다. 
    •  SegmentOfResult.Element
      • Sequence의 요소 타입
      • flatMap의 결과 배열의 요소 타입
  • transform 클로져
    • Self.Element(현재 배열의 요소)를 받아서 Sequence(배열, 리스트 등)를 반환하는 클로
    • throws
      • 이 클로져는 오류를 던질 수 도 있음
  • rethrows
    • transform 클로져가 오류를 던지면 rethrows도 오류를 던질 수 있고 오류를 던지지 않으면 rethrows도 오류를 던지지 않음
    • throws는 항상 do-catch 필수
    • rethrows는 상황에 맞게 do-catch 쓰거나 안쓰거나 가능   
  • 반환 값
    • SegmentOfResult.Element 타입의 배열을 반환
    • 변환된 결과가 중첩 배열이면 이를 평탄화 시켜 한 단일 배열로 변환한다.
    • 이를 평탄화라고 한다.

 

3.CompactMap

func compactMap<T>(_ transform: (Element) throws -> T?) rethrows -> [T]

 

//맵을 사용했을 때
let strings = ["1", "two", "3", "four"]
let mapped = strings.map { Int($0) }
print(mapped)
// [Optional(1), nil, Optional(3), nil] → `[Int?]`

//compactMap을 사용했을 때
let compactMapped = strings.compactMap { Int($0) }
print(compactMapped)
// [1, 3] → `[Int]`

 

  • nil이 포함될 수 있는 배열을 변환할 때 사용되며, nil 값을 자동으로 제거하여 새로운 배열을 반환하는 함수이다.
  • map과 비슷하지만 변환된 결과가 nil일 경우 자동으로 제외하고 배열을 만들어준다.

 

4.reduce

1. reduce(_:_:)

func reduce<Result>(
    _ initialResult: Result,
    _ nextPartialResult: (Result, Self.Element) throws -> Result
) rethrows -> Result
  • initialResult : 초기값
  • nextPartialResult : 누적 값을 업데이트 하는 클로져
  • 최종 결과값을 반환
  • 새로운 값을 반환
    • 불변성
      • 기존의 값이 변경되지 않음
      • 불필요한 복사가 발생함
    • 새로운 값 할당할때 사용하면 좋다.
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { partialResult, element in
    partialResult + element
}
print(sum) // 15
//0 → (0 + 1) → (1 + 2) → (3 + 3) → (6 + 4) → (10 + 5) → 15
    • 각 단계마다 partialResult가 새로운 값으로 갱신되어 불변성을 유지한다.

 

2. reduce(into:_:)

func reduce<Result>(
    into initialResult: Result,
    _ updateAccumulatingResult: (inout Result, Self.Element) throws -> ()
) rethrows -> Result
  • initialResult : 초기값(inout으로 직접 변경)
  • updateAccumulatingResult : 누적 값을 직접 변경하는 클로져(inout)
  • 최종 결과값을 반환
  • inout을 사용하여 기존 값을 직접 변경하기 때문에 reduce(_:_:) 보다 성능이 좋음
  • 배열, 딕셔너리, 집합등 변경 가능한 데이터를 처리할 때 좋다.

 

let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(into: 0) { result, element in
    result += element
}
print(sum) // 15
  • reduce(into:_:)는 누적된 결과값을 변경 가능한 상태(inout)로 유지하므로, 불필요한 복사가 발생하지 않아 성능이 더 좋음