개요
Collection에서 제공하는 메소드에서 reduce()와 fold()의 차이를 알아봅니다.
내용
reduce()와 fold() 모두 accumulation(누산) 작업이 요구될 때 사용하는 Collection 메소드입니다.
두 메소드 모두 Collection의 요소들을 차례로 누적하여 하나의 값으로 만드는데 사용됩니다.
하지만 초기값 지정 여부에 따라 중요한 차이가 있습니다.
- reduce() : 초기값 지정 불가 (첫 번째 요소를 초기값으로 사용)
- fold() : 초기값 지정 가능 (명시적으로 지정된 초기값 사용)
이로 인해 반환되는 결과가 다릅니다.
1. 기본 동작
val numbers = listOf(1, 2, 3)
val sumByReduce = numbers.reduce { acc, num -> acc + num }
println("sum with reduce() : ${sumByReduce}") // sum with reduce() : 6
val sumByFold = numbers.reduce(10) { acc, num -> acc + num }
println("sum with fold() : ${sumByFold}") // sum with reduce() : 16
✅reduce()
Collection의 첫 번째 요소를 초기값으로 사용하며 이후의 요소들을 순차적으로 처리합니다.
✅ fold()
명시적으로 초기값을 지정하며 이를 기준으로 연산을 시작합니다.
2. 빈 Collection에서 reduce()와 fold()
val numbers = emptyList<Int>()
val sumByFold = numbers.fold(10) { acc, num -> acc + num }
println("sum with fold() : ${sumByFold}")
val sumByReduce = numbers.reduce { acc, num -> acc + num }
println("sum with reduce() : ${sumByReduce}")
// result
folded: 10
Empty collection can't be reduced.
java.lang.UnsupportedOperationException: Empty collection can't be reduced.
at kr.leocat.test.FoldTest.test(FoldTest.kt:35)
...
✅ fold()
초기값만 반환하면 되기 때문에 안전하게 결과를 반환합니다.
❌reduce()
빈 Collection에서 초기값을 지정할 수 없으므로 예외가 발생합니다.
즉, 빈 Collection을 처리해야 하는 경우 fold()를 사용하는 것이 안전합니다.
3. 첫 번째 요소의 차이
reduce()와 fold()가 첫 번째 요소로 사용하는 데이터도 서로 차이가 있습니다.
메소드 내부에서 변수 선언을 (acc, num)으로 했다면
- reduce()는 Collection의 첫 번째를 acc로, 두 번째 요소를 num으로 사용합니다.
- fold()는 초기값을 acc로, Collection의 첫 번째 요소를 num으로 사용합니다.
따라서 다음과 같은 코드는 결과가 다릅니다.
아래의 코드는 리스트에 있는 데이터에 각각 2를 곱하고 모두 더하는 게 목표입니다.
이때 reduce()는 11을 반환하고 fold()는 12를 반환합니다.
val numbers = listOf(1, 2, 3)
val sumByReduce = numbers.reduce { acc, num -> acc + num * 2 }
println("sum with reduce() : ${sumByReduce}") // sum with reduce() : 11
val sumByFold = numbers.fold(0) { acc, num -> acc + num * 2 }
println("sum with fold() : ${sumByFold}") // sum with fold() : 12
reduce() 연산 과정
- acc = 1, num = 2 → 1 + (2 * 2) = 5
- acc = 5, num = 3 → 5 + (3 * 2) = 11
fold() 연산 과정
- acc = 0, num = 1 → 0 + (1 * 2) = 2
- acc = 2, num = 2 → 2 + (2 * 2) = 6
- acc = 6, num = 3 → 6 + (3 * 2) = 12
초기값의 존재 여부가 연산 순서와 결과에 영향을 미칩니다.
4. Java의 Stream API와의 차이
List<Integer> numbers = ImmutableList.of(1,2,3);
Optional<Integer> sum = numbers.stream()
.reduce((total, num) -> total + num); // Integer::sum
System.out.println("reduced: " + sum.get());
Integer sumFromTen = numbers.stream()
.reduce(10, (total, num) -> total + num);
System.out.println("folded: " + sumFromTen);
Kotlin에서는 reduce()와 fold()를 명확하게 분리했지만,
Java의 Stream API에서는 위와 같이 reduce()의 오버로딩을 통해 초기값을 지정할 수 있습니다.
Java에서는 초기값이 없는 reduce()를 사용하면 Optional<Integer>를 반환합니다.
초기값이 있는 reduce()를 사용하면 fold()와 같은 동작을 합니다.
정리
특징 | reduce() | fold() |
초기값 | 사용 불가 ❌ (첫 번째 요소가 초기값) | 사용 가능 ✅ (명시적으로 지정) |
빈 컬렉션 처리 | 예외 발생 ❌ (UnsupportedOperationException) | 초기값 그대로 반환 ✅ |
첫 번째 요소 | 첫 번째 요소가 acc, 두 번째 요소부터 연산 | 초기값이 acc, 첫 번째 요소부터 연산 |
Java Stream API 대응 | reduce() (초기값 없는 버전) | reduce() (초기값 있는 버전) |
일반적으로 fold()가 더 유연하고 안전한 선택지입니다.
빈 Collection을 처리해야 하거나 초기값이 필요한 경우 fold()를 사용하는 것이 좋습니다.
하지만, 컬렉션의 첫 번째 요소를 초기값으로 사용하고 싶다면 reduce()를 선택할 수 있습니다.
'Kotlin' 카테고리의 다른 글
[Kotlin] Kotlin 기본 문법 by "Code with Joyce" 2편 - (2) (0) | 2022.12.12 |
---|---|
[Kotlin] Kotlin 기본 문법 by "Code with Joyce" 2편 - (1) (0) | 2022.10.12 |
[Kotlin] Kotlin 기본 문법 by "Code with Joyce" 1편 - (2) (0) | 2022.10.04 |
[Kotlin] Kotlin 기본 문법 by "Code with Joyce" 1편 - (1) (0) | 2022.10.03 |