-
Swift - reduce(_:_:)와 reduce(into:_:)Programming/Swift 2022. 1. 29. 15:39
안녕하세요 BeePeach입니다 :>
오늘 공부해볼 내용은 reduce 메서드입니다.
reduce 메서드는 Container의 요소를 결합하는 작업을 수행할 때 사용합니다.
그럼 reduce 메서드를 사용하는 방법과 reduce(_:_:)와 reduce(into:_:)는 어떤 차이를 가지는지 같이 공부해보도록 하겠습니다.
reduce(_:_:)
선언 부분을 보면 Generic function인것을 확인할 수 있습니다.
타입 파라미터로 Result가 선언되어 있네요.
타입을 확인하기 어렵게 되어있는데 찬찬히 하나씩 본다면 어렵지 않습니다.
첫 번째 파라미터는 initialResult 즉, 초기값이 전달됩니다.
뜬금없이 초기값이 뭔데??라고 할 수 있는데 예제를 보시게 되면 바로 이해하실 수 있습니다.
두 번째 파라미터는 nextPartialResult 이름을 가진 (Result, Self.Element) -> Result 타입의 클로저가 전달됩니다.
이름으로 유추해보자면 부분적인 결과인 거 같네요.
이것도 일단 뭔 말인지는 모르지만 다음을 보겠습니다.
이번에는 두 번째 파라미터로 전달된 클로저를 좀 더 자세히 보겠습니다.
클로저의 첫번째 파라미터는 initialResult로 전달받은 초기값 또는 이전 클로저가 반환하는 return값이 전달됩니다.
클로저의 두 번째 파라미터는 reduce(_:_:)를 호출한 컨테이너의 요소가 전달됩니다.
클로저가 반환하는 값은 파라미터로 받은 두 값을 적절하게 처리하여 첫 번째 파라미터로 받은 값과 같은 타입의 값을 리턴해줍니다.
마지막으로 reduce(_:_:) 메서드가 자체가 Result타입을 리턴하고 있습니다.
이 결과값은 클로저에서 정의한 연산을 끝내고 return 한 값을 반환해줍니다.
풀어서 설명하자면 클로저의 첫번째 파라미터에 맨 처음 initialResult로 전달된 초기값이 전달되고 두 번째 파라미터에 컨테이너의 맨 첫 요소가 전달됩니다.
그리고 이 두 파라미터를 이용해서 클로저에서 정의한 코드를 실행한 후에 결괏값을 리턴해주면
이 리턴값을 다시 클로저의 첫 번째 파라미터로 전달하고 두 번째 파라미터에는 컨테이너의 두 번째 요소가 전달됩니다.
이렇게 컨테이너의 모든 요소가 전달될때까지 이 클로저가 반복됩니다.
쉽게 Recursive function을 생각하면 됩니다.
그래서 이름이 nextPartialResult입니다. 부분적인 결과들을 리턴하고 이 결과를 이용해서 다음 연산에 사용하는 것입니다.
처음에 설명만 들으면 이게 뭔 소리야??하기 쉽습니다.
예제를 보게되면 금방 이해할 수 있습니다.
예시를 보면 [1, 2, 3] 배열에서 reduce(_:_:) 메서드를 호출했습니다.
sum은 초기값으로 0을 전달하고 클로저에서 첫 번째 파라미터인 result와 두 번째 파라미터인 element를 더한 값을 리턴하고 있습니다.
전달된 클로저의 흐름을 살펴보겠습니다.
첫 번째 파라미터인 result에는 초기값인 0이 전달됩니다.
두 번째 파라미터인 element에는 첫 요소의 값인 1이 전달됩니다.
이 두 값을 더해서 1이라는 결과가 나오게 됩니다.
그럼 이 1이라는 결과는 다시 클로저의 첫 번째 파라미터인 result로 전달됩니다.
두 번째 파라미터에는 element의 두 번째 요소인 2가 전달됩니다.
이 두 값을 더해서 3이라는 결과가 나오게 됩니다.
이러한 과정을 reduce(_:_:)를 호출한 컨테이너의 요소를 모두 반복될 때까지 진행합니다.
예제를 한 가지 더 보도록 하겠습니다.
이번에는 각 요소를 뺀 결과를 얻을 수 있습니다.
차이점은 초기 값이 10입니다. 그래서 10에 각 요소인 1, 2, 3을 반복적으로 빼게 됩니다.
클로저를 생략한다면 더 쉽게 표현이 가능합니다.
reduce(into:_:)
그럼 이 메서드는 reduce(_:__:)와 무슨 차이일까요??
첫번째 파라미터는 initialResult로 reduce(_:_:) 메서드와 똑같은 의미를 가집니다.
초기값을 의미하죠!
두 번째 파라미터는 타입이 (inout Result, Element) -> Void)인 클로저를 받습니다.
이름은 updateAccumulatingResult입니다. 누적된 결과를 업데이트한다는 의미 같네요??
클로저의 첫 번째 파라미터는 initialResult로 전달받은 초기값 또는 이전 클로저 실행으로 변경된 누적 결괏값입니다.
클로저의 두 번째 파라미터는 reduce(into:_:) 메서드를 호출한 컨테이너의 요소가 전달됩니다.
여기서 주의 깊게 봐야 하는 부분은 첫 번째 파라미터가 inout파라미터인 점과 리턴 값이 없다는 점입니다.
reduce(into:_:)메서드 자체는 클로저의 연산 결과를 반복적으로 거친 Result를 리턴합니다.
reduce(into:_:)의 가장 큰 차이는 클로저의 첫 번째 파라미터가 inout 파라미터라는 점입니다.
즉, initialResult로 전달받은 값을 클로저안에서 직접 변경합니다.
reduce(_:_:)은 initialResult로 전달 받은 값을 클로저에서 변경할 수 없습니다.
먼저 reduce(_:_:)에서 사용한 것과 비슷한 예제를 보겠습니다.
똑같이 요소들을 더하고 빼는 작업들입니다.
클로저를 보면 return 대신 +=, -=를 이용하는 것을 볼 수 있습니다.
클로저가 값을 리턴하는 게 아니고 첫 번째 파라미터로 전달된 값을 직접 변경하고 사용하기 때문에 첫 번째 파라미터에 값을 누적시켜주면 됩니다.
흐름을 따라가 본다면 초기값으로 10이 전달되고 이 초기값은 클로저의 result로 전달됩니다.
초기값에 10이 전달되고 이 값과 컨테이너의 첫 요소인 1을 더해서 이 결과를 다시 result에 저장합니다.
클로저가 끝나면 다시 클로저가 호출됩니다. 이번에는 result에 이전에 저장한 11이 저장되어 있습니다.
11과 컨테이너의 두 번째 요소인 2를 더한 값을 다시 result에 저장합니다.
이러한 과정을 반복하면서 작업을 수행하게 됩니다.
reduce(_:_:), reduce(into:_:) 두 메서드의 차이가 크게 없어 보입니다.
reduce(_:_:), reduce(into:_:)의 차이
클로저의 파라미터가 inout이라는 것은 무엇을 다르게 만들어줄까요??
만약 initialResult로 Array, Dictionary를 받는다고 생각해봅시다.
그럼 reduce(_:_:)에서는 Array, Dictionary를 직접 수정할 수 없기 때문에 새로운 변수로 copy 한 다음에 그 값을 변경하고 사용해야 하는 번거로움이 있습니다.
이 예제는 초기값으로 [-2, -1, 0]인 배열을 전달해서 reduce(_:_:)를 호출한 컨테이너의 요소들을 초기값으로 전달된 배열에 넣어주는 작업을 하고 있습니다.
이때 초기값으로 전달된 배열을 직접 수정할 수 없기 때문에 바로 append를 호출할 수 없습니다.
그래서 새로운 변수를 생성하고 여기에 append를 한 뒤 그 결과를 리턴하고 있습니다.
이렇게 직접 수정하는 방법은 애초에 사용이 불가능합니다.
그럼 이제 reduce(into:_:)를 이용해볼까요??
reduce(into:_:)는 initialResult를 Array, Dictionary로 받아도 클로저에서 직접 변경이 가능하므로 데이터를 다루기 쉬워집니다.
위와 같은 코드이지만 이번에는 훨씬 간단한 것을 확인할 수 있습니다.
Array를 클로저 내부에서 직접 수정이 가능하니까요!
initialResult를 Array나 Dictionary로 받는 게 왜 중요하냐고요??
reduce() 메서드의 결괏값이 결국에는 initialResult의 타입과 같아야 하기 때문입니다.
reduce(into:_:) 메서드는 초기값을 Array, Dictionary으로 전달하고 결과도 이 형태로 받을 수 있습니다.
아주 유용한 예시를 보도록 하겠습니다.
이 예시를 보면 String에서 reduce(into:_:)를 호출하고 있습니다.
String에서도 reduce 메서드를 사용할 수 있습니다.
초기 값으로 [:] 빈 Dictionary를 전달했습니다.
그럼 이제 reduce(into:_:) 메서드의 결과는 Dictionary형태가 되어야 합니다.
클로저에서 counts에는 처음에 초기값인 [:]이 전달됩니다.
letter는 String의 각각 Character가 전달됩니다.
counts[letter]는 현재 없는 key이므로 원래는 nil이 리턴되어야 하죠??
하지만 Dictionary에는 subscript로 default값을 전달할 수 있었습니다.
letter key가 없었다면 기본값을 0으로 하고 1을 더합니다.
그럼 character를 key로 가지고 value로는 개수를 가지는 [Character: Int] Dictionary를 얻을 수 있습니다.
이렇게 "abracadabra"에 각 char들이 몇 번 들어가 있는지 알 수 있는 dictionary를 얻었습니다.
정리
reduce(_:_) 메서드는 초기값으로 Int, String과 같이 단일 값을 받은 뒤 요소들을 모두 합해서 단일 값을 구하는데 유용합니다.
맨 위에서 보았던 요소들의 전체 합을 얻을 때와 같은 경우입니다.
reduce(into:_) 메서드는 Array, Dictionary으로 된 결괏값을 얻고 싶을 때 사용합니다.
맨 마직으로 살펴본 예제와 같은 경우입니다.
참고 자료
https://developer.apple.com/documentation/swift/array/3126956-reduce
https://developer.apple.com/documentation/swift/array/2298686-reduce
http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9791162242223
728x90'Programming > Swift' 카테고리의 다른 글
Swift - Error Handling 기초 (0) 2022.03.24 Swift - UserDefaults에 customType 저장하기 (0) 2022.03.01 Swift - Generic Type Constraint (0) 2022.01.25 Swift - Generic Type 확장하기 (0) 2022.01.24 Swift - Associated Type (0) 2022.01.19