Reduce 함수 구현해 보기

2018-10-05

개요

지난 시간에는 Map, Reduce, Filter 함수 중 Map함수와 Filter함수를 구현해 봤습니다. 이번 시간에는 Reduce 함수를 구현해 보도록 하겠습니다.

이 포스트를 제대로 이해하기 위해서는 Closure와 Generic에 대한 이해가 선행돼야 합니다. 만약 익숙하지 않으시다면 Swift 공식문서의 Closure(영어, 한국어)와 Generic(영어, 한국어) 섹션의 정독을 권해드립니다.

Reduce 함수의 동작

reduce함수는 이름에서 유추할 수 있듯이 무언가 줄여주는 함수입니다. 보다 자세히는 주어진 콜렉션의 원소를 특정하게 결합(combine)시켜 하나의 연결(concatenation)된 형태로 만들어 주는 역할을 합니다.

예를 들어, 입력 값이 각각 다음과 같다면

let intArray = [1,2,3,4,5]
let stringArray = ["Hello,","Nice", "to", "meet", "You"]

이 입력을 reduce함수를 이용해 특정 형태로 연결시킬 수 있습니다.

let intResult = intArray.reduce(0) { result, x in result + x }
let stringResult = stringArray.reduce("") { result, x in result + x + " "}

intResult는 콜렉션 각 원소를 더한 결과를 반환하고, stringResult는 콜렉션 각 원소를 더하고 빈 공백을 뒤에 추가해 결합합니다. (글을 읽는 지금 reduce 함수의 사용이 정확히 이해되지 않는다고 걱정하실 필요 없습니다. 아래에서 이 함수를 구현하는 과정 중에 이해하실 수 있을 것입니다.)

reduce함수를 실행한 결과는 각각 다음과 같습니다.

intResult : 15
stringResult : Hello, Nice to meet You 

Reduce 함수의 구현

그럼 위 예제와 똑같이 동작하는 reduce함수를 만들어 보도록 하겠습니다.

우선 Int 배열의 각 원소를 더해서 반환하는 함수를 만들어 보겠습니다.

func sum(xs: [Int]) -> Int {
    var result = 0
    
    for x in xs {
        result += x
    }
    
    return result
}

초기 결과값을 0으로 설정하고 이후 주어진 배열의 정수를 하나씩 더해 전부 더한 결과를 반환합니다.

이제 String 배열의 각 원소를 모두 이어서 반환하는 함수를 만들어 보겠습니다.

func combine(xs: [String]) -> String {
    var result = ""

    for x in xs {
        result += x + " "
    }

    return result
}

초기 결과값을 빈상태로 시작해 주어진 배열의 문자를 모두 더해 전부 더한 결과를 반환합니다. 각 원소를 띄어서 결합하기 위해 뒤에 원소를 결합한 후에 " "를 추가합니다.

이 두 함수의 공통점을 찾으셨나요?

공통점은 1. 초기값을 지정하고 2. 각 원소를 어떻게 결합시킬지 정의 했다는 것입니다. 이 공통점을 뽑아내 구현하면 reduce함수를 만들 수 있습니다.

이 조건을 만족하는 Generic 함수를 만들면 다음과 같습니다.

func customReduce<A, R>(xs: [A], initialResult: R, nextPartialResult: (R, A) -> R) -> R {
    var result = initialResult

    for x in xs {
        result = nextPartialResult(result, x)
    }

    return result
}

앞에서 만든 sum함수와 combine함수도 customReduce함수로 정의해 사용할 수 있습니다.

func sumUsingReduce(xs: [Int]) -> Int {
    return customReduce(xs: xs, initialResult: 0, nextPartialResult: { result, x in result + x })
}

func combineUsingReduce(xs: [String]) -> String {
    return customReduce(xs: xs, initialResult: "", nextPartialResult: { result, x in result + x + " " })
}

이 함수의 실행 결과는 앞의 결과와 동일합니다.

[실행결과]

sumResult : 15
combineResult : Hello, Nice to meet You 

마지막으로 배열을 입력받아서 실행하는 것이 아니라 Swift에서 제공해주는 reduce함수처럼 배열에서 직접 실행할 수 있도록 Array의 extension으로 customReduce함수를 추가 해보겠습니다.

extension Array {
    func customReduce<R>(_ initialResult: R, nextPartialResult: (R, Element) -> R) -> R {
        var result = initialResult
        
        for x in self {
            result = nextPartialResult(result, x)
        }
        
        return result
    }
}

Reduce 함수와 CustomReduce 함수의 동작

이제 Swift에서 기본적으로 제공해주는 reduce의 동작과 저희가 구현한 customReduce의 동작을 비교해 보도록 하겠습니다.

[소스코드]

let intArray = [1,2,3,4,5]
let stringArray = ["Hello,","Nice", "to", "meet", "You"]
    
let reduceResult1 = intArray.reduce(0) { result, x in result + x }
let customReduceResult1 = intArray.customReduce(0) { result, x in result + x }
    
let reduceResult2 = stringArray.reduce("") { result, x in result + x + " " }
let customReduceResult2 = stringArray.customReduce("") { result, x in result + x + " " }
    
print("Reduce             #1 : \(reduceResult1)")
print("CustomReduceResult #1 : \(customReduceResult1)")
print("======================")
print("Reduce             #2 : \(reduceResult2)")
print("CustomReduceResult #2 : \(customReduceResult2)")

[실행결과]

Reduce             #1 : 15
CustomReduceResult #1 : 15
======================
Reduce             #2 : Hello, Nice to meet You 
CustomReduceResult #2 : Hello, Nice to meet You 

Swift에서 제공하는 reduce함수와 저희가 만든 customReduce함수가 동일하게 동작하는 것을 확인할 수 있습니다.

결론

이상 reduce함수에 대해 알아보고 직접 구현해 보았습니다. 이로써 가장 자주 사용하는 map,reduce,filter함수를 모두 직접 구현해 보았습니다. 알고나니 별로 어렵지 않죠? 😁

그럼 오늘도 즐코~ 하시길! 다음 포스트에서 봬요~ 😉