ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Reduce
    Swift 2020. 8. 4. 15:22

    Reduce에 대해 알아보겠습니다!

    Reduce

    정의부터 살펴볼까요??

     

    직역해보면 연속된 원소들을 클로저를 이용해 결합시키고 그 결과를 리턴한다. 정도로 이해할 수 있을 것 같아요.

    reduce는 2개의 파라미터 initialResult, nextPartialResult 와 1개의 Result 를 return 합니다. 
    공통점이 보이시나요? 네 파라미터와 결과가 모두 어떤 Result 들을 리턴하고 있습니다.

    reduce를 사용하는 목적을 생각해볼까요?
    위 정의의 Return Value 를 보면 the final accumulated value, 최종적으로 계산된 값을 리턴한다고 나와있습니다.
    단계 단계가 다 결과이니 만약 시퀀스의 원소가 없다면, 즉 nextPartialResult가 없으면, 초기값(초기결과)를 리턴 하는 것도 자연스럽네요.
    reduce는 여러 원소에 동일한 작업을 해서 하나의 값으로 만들어주고 싶을 때 사용합니다.

    "아하 연속된 원소들에 동일한 작업을 반복적으로 해서 하나의 결과를 만들고 싶을 때 사용하면 되겠구나!"

     

    사용법

    사용법에 대해 좀 더 자세히 알아볼게요.

    가장 기본적으로 활용할 수 있는 예가 배열의 합을 구할 수 있겠네요.
    1부터 100까지의 합을 구하려면 어떻게 할까요?
    for 문을 사용하면 이런식으로 할 수 있겠네요.

    var sum = 0
    for number in 0...100 {
      sum += number
    }

    하지만 합을 저장할 변수도 하나 만들어야 하고 간단한 동작인데 좀 비효율적으로 보입니다.
    이걸 reduce를 사용해서 개선할 수 있습니다.

    var arr = [Int](0...100)
    var sumArr = arr.reduce(0, { (n1, n2) -> Int in
        return n1 + n2
    })

    이걸 좀 더 파헤쳐봅시다!

    reduce가 실행되면 다음과 같은 과정을 거칩니다.

     

    1. initialResult첫번째 원소를 파라미터로 해서 nextPartialResult closure 가 실행된다. 
    위의 예에서는 initialResult(0) 과 첫번째 원소 arr[0] 이 파라미터로 넘어가서 {0, 0 in 0 + 0} 이 실행되겠죠.
    2.  이 결과를 앞서 실행된 결과를 initialResult로 해서 반복합니다.
    3. 원소를 다 탐색했다면 마지막 결과를 리턴합니다.

    말이 복잡하지만 그냥 클로저 연산을 반복해서 initialResult를 계속 만들어나간다고 생각할 수 있겠습니다.

     

    축약

    이렇게 되면 리듀스는 다 이해하신거에요!! 이제 클로저의 성질을 이용해서 reduce가 어떻게 축약될 수 있는지만 조금 더 살펴볼까요?

    파라미터가 모두 Int이니 리턴 값을 생략할 수 있겠죠.

    var sumArr = arr.reduce(0, { n1, n2 in
        n1 + n2
    })

    첫번째 파라미터가 이전 결과, 두번째 파라미터가 원소인 걸 알고 있으니 이렇게도 축약이 가능하겠죠?

    // $0: 이전 결과, $1: arr의 원소
    var sumArr = arr.reduce(0, { $0 + $1 })

    만약 현재 원소에 대해서만 같은 일을 계속하겠다고 하면 파라미터 자체를 생략할 수 있습니다. 이런 것도 가능합니다.

    var sumArr = arr.reduce(0, +)

     

    intialResult? into?

    reduce 의 기본 원리는 살펴봤는데요, xcode에서 reduce 를 사용하려고 보면 2가지 정의가 나오는 걸 확인할 수 있습니다.

    이 둘에는 같으면서 다른 차이가 있는데요!! 바로 초기 파라미터를 어떤 식으로 전달해줄 건지에 대한 차이입니다.

    정의를  잘 살펴보면 위의 초기 파라미터가 into 로 되어있는 녀석은
    다음 파라미터가 저희가 살펴봤던 nextPartialResult가 아니라 updateAccumulatingResult 로 되어 있습니다. 거기다 타입도 inout Result 입니다.
    이 말은 즉, 이녀석은 값을 return 하는 것이 아니라 초기값을 계속 업데이트 시켜나가는 방식이라는 말이 되죠!

    우리가 앞서 배운 녀석이 초기값을 계산해서 return 해주는 형태였다면 이녀석은 return 없이 초기값을 계속 inout으로 내부에서 사용하는 겁니다.

    예시를 보면 차이가 이해되실거에요. 앞의 예제를 조금 바꿔서 1부터 100까지의 합이 아니라 이녀석들을 출력해보려고 합니다. 모두 String으로 합쳐서 하나의 String으로 만들어 print 해줄거에요!

    앞에서 사용한 initialResult를 사용한다면? 이렇게 하면 됩니다 !! 참쉽죠?

    var str = arr.reduce("", { $0 + "\($1) "})

    그럼 얘는 어떨까요? 뭐가 다르냐구요? 네 초기 파라미터 앞에 into가 붙었죠. 
    initialResult 를 사용할 때는 파라미터명이 생략되고 into를 사용할 때는 명시해줘야 합니다.

    var str = arr.reduce(into: "", { $0 + "\($1) "})

    비슷해보이지만... 이렇게 하면 xcode는 친절히도 이렇게 알려줍니다. 느낌이 싸해집니다. 나는 더하고 있는데 왜
    +를 사용하지 않는다는거야?!!!

    into는 return 해주지 않고 초기 파라미터 값을 계속 inout으로 넘겨서 사용해주기 때문에 $0 값을 변경해줘야 합니다. 
    이렇게요.

    var str = arr.reduce(into: "", { $0 += "\($1) "})

     

    이상 리듀스에 대해서 알아봤습니다 :)

    참고로 결국은 n번을 다 돌아야하므로 시간 복잡도는 O(n)이 됩니다.

    감사합니다!

     

    'Swift' 카테고리의 다른 글

    순수함수 Pure function  (0) 2020.08.04
    람다 λ lambda  (0) 2020.08.04
    ~= 연산자 in Swift  (0) 2020.07.07
    Hashable in Swift 5  (0) 2020.06.25
    associatedType  (2) 2020.03.18

    댓글

Designed by Tistory.