ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 고차함수 reduce
    개발...................../TIL 2024. 6. 13. 12:20

    Swift 언어에서의 reduce 함수는 배열이나 다른 컬렉션의 모든 요소를 결합하여 단일 값으로 줄이는 고차 함수입니다.

     

    이 함수는 초기값과 클로저를 사용하여 작동하며, 각 요소를 순회하면서 지정된 클로저를 적용하여 최종 결과를 생성합니다.

     

    기본적인 사용법

    Swift에서의 reduce 메서드는 다음과 같은 형식을 가집니다:

    func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
    • initialResult: 초기값으로 사용될 값입니다. 연산의 시작점이며, 이 값은 함수 호출 시 제공해야 합니다.
    • nextPartialResult: 클로저로서, 현재까지의 누산된 결과(Result)와 배열의 각 요소(Element)를 받아서 새로운 결과(Result)를 반환합니다. 이 클로저는 배열의 모든 요소에 대해 순차적으로 호출됩니다.

    동작 원리

    1. 초기값(initialResult)을 설정합니다.
    2. 배열의 첫 번째 요소부터 마지막 요소까지 순회하면서 nextPartialResult 클로저를 호출합니다.
    3. 클로저는 현재까지의 결과(Result)와 배열의 각 요소(Element)를 받아서 새로운 결과(Result)를 반환합니다.
    4. 이 새로운 결과는 다음 요소와 함께 다시 nextPartialResult에 전달됩니다.
    5. 배열의 모든 요소를 처리한 후 최종 결과를 반환합니다.

     

     

    아래 표현식1과 표현식2는 모두 Swift의 reduce 메서드를 사용하여 배열의 요소들을 합산하는 예시입니다.

     

    같은 결과를 반환하지만 사용 방식에는 약간의 문법적 차이가 있습니다.

     

     

    표현식1: numbers3.reduce(0, +)

    let numbers3 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    let sum1 = numbers3.reduce(0, +)

     

    이 구문에서 reduce 메서드는 초기값(0)과 더하기 연산자(+)를 사용합니다. 이것은 Swift에서 제공하는 + 연산자가 두 개의 숫자를 더하는 연산을 수행하기 때문에 간단하게 배열의 모든 요소를 합산할 수 있습니다. 초기값은 0으로 설정되어 있으며, 배열의 각 요소가 이 초기값부터 시작하여 누적적으로 더해지게 됩니다.

    표현식2: numbers4.reduce(0) { $0 + $1 }

    let numbers4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    let sum2 = numbers4.reduce(0) { $0 + $1 }
     

    여기서 reduce 메서드는 이번에는 클로저를 사용하여 구현되었습니다. 클로저는 { $0 + $1 } 형태로 작성되었고, 이 클로저는 두 개의 인자($0과 $1)를 받아서 더한 후 결과를 반환합니다. 첫 번째 인자($0)는 누적값(accumulator)이고, 두 번째 인자($1)는 배열의 각 요소입니다. 초기값은 여전히 0으로 설정되어 있습니다.

     

    왜 클로저를 쓸 때는 초기값과 배열의 각 요소에 '$' 를  쓸까? 

    $를 사용하는 것은 Swift에서 클로저의 인자를 간단하게 참조하기 위한 문법적 관습입니다.
    함수형 프로그래밍에서 클로저는 각 인자를 명시적으로 선언하지 않고도 직관적이고 간결하게 코드를 작성할 수 있도록 도와줍니다.

     

     

    차이점

    1. 연산자 vs 클로저: 표현식1에서는 기본 연산자(+)를 사용하여 간단하게 합산을 수행하며, 표현식2에서는 클로저를 사용하여 좀 더 유연하게 누적 연산을 정의할 수 있습니다. 클로저를 사용하면 다양한 복잡한 누적 연산을 구현할 수 있습니다.
    2. 문법적 차이: 표현식1은 연산자를 직접 사용하여 간결하게 표현할 수 있지만, 표현식2는 클로저를 사용하므로 좀 더 명시적이고 복잡한 누적 로직을 표현할 수 있습니다.

     

     

    산술 연산만 가능한 걸까요? 

     

    각 요소를 사용하여 원하는 작업을 수행하고, 그 결과를 누적하여 하나의 최종값으로 만드는 과정을 reduce 함수가 제공하는데,

    이 컨테이너는 초기값을 기반으로 시작하여 클로저에 의해 계속 갱신되며 최종적으로 반환됩니다.

     

    예를 들어, 배열의 모든 요소를 더한 값을 하나의 정수로 저장할 수도 있고,

    배열의 요소들을 이용하여 다른 형태의 컬렉션을 생성할 수도 있습니다.

    또한, 복잡한 객체를 생성하여 배열의 요소를 누적할 수도 있습니다. 

     

    예시

     

    1. 총합 계산: 배열의 모든 요소를 더해서 총합을 구하는 것 외에도, 곱셈, 뺄셈 등 다양한 산술 연산이 가능합니다.

    let numbers = [1, 2, 3, 4, 5] 
    // 총합 구하기 
    let sum = numbers.reduce(0, +) // 15 
    
    // 곱셈 연산 
    let product = numbers.reduce(1, *) // 120 (1 * 2 * 3 * 4 * 5) 
    
    // 차이 구하기 
    let difference = numbers.reduce(0, { $0 - $1 }) // -15 (0 - 1 - 2 - 3 - 4 - 5)

     

     

     

    2. 최댓값, 최솟값 찾기: reduce 함수를 사용하여 배열의 최댓값과 최솟값을 찾을 수 있습니다.

    let numbers = [10, 5, 7, 2, 15] 
    
    // 최댓값 찾기 
    let maxNumber = numbers.reduce(numbers[0], { max($0, $1) }) // 15 
    
    // 최솟값 찾기 
    let minNumber = numbers.reduce(numbers[0], { min($0, $1) }) // 2

     

    3. 문자열 연결: 배열의 문자열 요소들을 하나의 문자열로 합치는 작업도 가능합니다.

    let words = ["Hello", "World", "Swift"] // 문자열 합치기 
    let combinedString = words.reduce("", { $0 + " " + $1 }) // " Hello World Swift"

     

    4. 컬렉션 변환: reduce를 이용하여 배열을 다른 형태의 컬렉션으로 변환할 수도 있습니다.

    let numbers = [1, 2, 3, 4, 5] // 배열을 Set으로 변환 
    let numberSet = numbers.reduce(into: Set<Int>(), { $0.insert($1) }) 
    // numberSet: Set([1, 2, 3, 4, 5])

     

    5. 누적된 복잡한 객체 생성: 복잡한 객체를 누적하여 생성할 수 있습니다. 예를 들어, 여러 속성을 가진 객체를 배열로부터 생성하는 경우가 있을 수 있습니다.

    struct Person {
        var name: String
        var age: Int
    }
    
    let peopleData = [("Alice", 25), ("Bob", 30), ("Charlie", 28)]
    
    // 배열에서 Person 객체 배열로 변환
    let people = peopleData.reduce(into: [Person](), { result, data in
        result.append(Person(name: data.0, age: data.1))
    })
    /* people: [Person(name: "Alice", age: 25), Person(name: "Bob", age: 30), 
    Person(name: "Charlie", age: 28)]*/