The Sum of all Reductions
The reduction of all sums

During the Deep Freeze of 2018, everything in the world seemed to be reduced to ice and snow, including the sky. Even the sun seemed to be reduced as it ran away from my camera with a shiver.
This got me thinking about different kinds of reductions we have available in Java with Eclipse Collections and Java Streams. I wondered how many ways we could define sum
with the various methods available.
Summing an array of ints
Let’s consider several ways we can sum the values in an int array in Java.
Here is the data we will use.
int[] ints = {1, 2, 3, 4, 5};
// This supplier will generate an IntStream on demand
IntStream intStream = Arrays.stream(ints);
// This creates an IntList from Eclipse Collections
IntList intList = IntLists.immutable.with(ints);
For Loop
int sumForLoop = 0;
for (int i = 0; i < ints.length; i++)
{
sumForLoop += ints[i];
}
Assertions.assertEquals(15, sumForLoop);
forEach (IntStream
) / each (IntList
)
// sumForEach will be effectively final
int[] sumForEach = {0};
intStream.forEach(e -> sumForEach[0] += e);
Assertions.assertEquals(15, sumForEach[0]);
// sumEach will be effectively final
int[] sumEach = {0};
intList.each(e -> sumEach[0] += e);
Assertions.assertEquals(15, sumEach[0]);
injectInto (IntList
)
// injectInto boxes on IntList as there is no primitive version
int sumInject =
intList.injectInto(Integer.valueOf(0), Integer::sum).intValue();
Assert.assertEquals(15, sumInject);
reduce (IntStream
)
// reduce does not box on IntStream
int sumReduce =
intStream.reduce(Integer::sum).getAsInt();
Assertions.assertEquals(15, sumReduce);
sum (IntStream
/ IntList
)
int sum1 = intStream.sum();
Assertions .assertEquals(15, sum1);
long sum2 = intList.sum();
Assertions.assertEquals(15, sum2);
Clearly, the sum
methods available on IntStream
and IntList
are the simplest solutions. The minor difference with IntList
is that the result is widened to a long
which means you can add very large int
values without overflowing.
Summarizing an array of ints
When we summarize using the IntSummaryStatistics
class that was added in Java 8, we get the count
, sum
, min
, max
and average
calculated at the same time. This saves you from iterating multiple times. We will use the same data as before.
For Loop
IntSummaryStatistics statsForLoop = new IntSummaryStatistics();
for (int i = 0; i < ints.length; i++)
{
statsForLoop.accept(ints[i]);
}
Assertions.assertEquals(15, statsForLoop.getSum());
Assertions.assertEquals(1, statsForLoop.getMin());
Assertions.assertEquals(5, statsForLoop.getMax());
forEach (IntStream
) / each (IntList
)
IntSummaryStatistics statsForEach = new IntSummaryStatistics();
intStream.forEach(statsForEach::accept);
Assertions.assertEquals(15, statsForEach.getSum());
Assertions.assertEquals(1, statsForEach.getMin());
Assertions.assertEquals(5, statsForEach.getMax());
IntSummaryStatistics statsEach = new IntSummaryStatistics();
intList.each(statsEach::accept);
Assertions.assertEquals(15, statsEach.getSum());
Assertions.assertEquals(1, statsEach.getMin());
Assertions.assertEquals(5, statsEach.getMax());
injectInto (IntList
)
IntSummaryStatistics statsInject =
intList.injectInto(
new IntSummaryStatistics(),
(iss, each) -> {iss.accept(each); return iss;});
Assertions.assertEquals(15, statsInject.getSum());
Assertions.assertEquals(1, statsInject.getMin());
Assertions.assertEquals(5, statsInject.getMax());
collect (IntStream
)
IntSummaryStatistics statsCollect =
intStream.collect(
IntSummaryStatistics::new,
IntSummaryStatistics::accept,
IntSummaryStatistics::combine);
Assertions.assertEquals(15, statsCollect.getSum());
Assertions.assertEquals(1, statsCollect.getMin());
Assertions.assertEquals(5, statsCollect.getMax());
Note: I could not use reduce
because both parameters have to be the same type. I had to use collect
instead, which is a mutable reduction. The collect
method on primitive Streams does not take a Collector
, but instead takes a Supplier
, ObjectIntConsumer
(accumulator) and a BiConsumer
(combiner).
summaryStatistics (IntStream
/ IntList
)
IntSummaryStatistics stats1 = intStream.summaryStatistics();
Assertions.assertEquals(15, stats1.getSum());
Assertions.assertEquals(1, stats1.getMin());
Assertions.assertEquals(5, stats1.getMax());
IntSummaryStatistics stats2 = intList.summaryStatistics();
Assertions.assertEquals(15, stats2.getSum());
Assertions.assertEquals(1, stats2.getMin());
Assertions.assertEquals(5, stats2.getMax());
Again, the summaryStatistics
methods are the simplest solutions.
Summing the lengths of an array of Strings
Let’s say we want to sum the lengths of Strings in an array. This approach could be used for summing any int attribute of an object.
Here is the data we will use.
String[] words = {"The", "Quick", "Brown", "Fox", "jumps", "over", "the",
"lazy", "dog"};
Stream<String> stream = Stream.of(words);
ImmutableList<String> list = Lists.immutable.with(words);
For Loop
int sumForLoop = 0;
for (int i = 0; i < words.length; i++)
{
sumForLoop += words[i].length();
}
Assertions.assertEquals(35, sumForLoop);
For Each (Stream
) / each (ImmutableList
)
int[] sumForEach = {0};
stream.forEach(e -> sumForEach[0] += e.length());
Assertions.assertEquals(35, sumForEach[0]);
int[] sumEach = {0};
list.each(e -> sumEach[0] += e.length());
Assertions.assertEquals(35, sumEach[0]);
collectInt (ImmutableList
)+ injectInto (IntList
)
int sumInject = list
.collectInt(String::length)
.injectInto(Integer.valueOf(0), Integer::sum)
.intValue();
Assertions.assertEquals(35, sumInject);
collect (Stream
) + reducing (Collectors
)
int sumReducing =
stream.collect(Collectors.reducing(0,
String::length,
Integer::sum)).intValue();
Assertions.assertEquals(35, sumReduce);
mapToInt (Stream
) + Reduce (IntStream
)
int sumReduce = stream
.mapToInt(String::length)
.reduce(Integer::sum)
.getAsInt();
Assertions.assertEquals(35, sumReduce);
mapToInt (Stream
) + sum (IntStream
)
int sum1 = stream
.mapToInt(String::length)
.sum();
Assertions.assertEquals(35, sum1);
collectInt (ImmutableList
) + sum (IntList
)
long sum2 = list
.collectInt(String::length)
.sum();
Assertions.assertEquals(35, sum2);
collect (Stream
) + summingInt (Collectors
)
Integer summingInt = stream
.collect(Collectors.summingInt(String::length));
Assertions.assertEquals(35, summingInt.intValue());
sumOfInt (ImmutableList
)
long sumOfInt = list.sumOfInt(String::length);
Assertions.assertEquals(35, sumOfInt);
I think in these examples, sumOfInt
is the simplest solution.
Summing the lengths of Strings grouped by the first character
In this problem we will group Strings by their first character and sum
the length of the Strings for each character. I will prefer to use use primitive maps here for the grouping if possible.
Here is the data.
String[] words = {"The", "Quick", "Brown", "Fox", "jumps", "over", "the",
"lazy", "dog"};
Stream<String> stream = Stream.of(words).map(String::toLowerCase);
ImmutableList<String> list =
Lists.immutable.with(words).collect(String::toLowerCase);
The Stream
and ImmutableList
strings are converted to lowercase using map
and collect
, respectively. We will do this manually in the for loop example.
For Loop
MutableCharIntMap sumByForLoop = new CharIntHashMap();
for (int i = 0; i < words.length; i++)
{
String word = words[i].toLowerCase();
sumByForLoop.addToValue(word.charAt(0), word.length());
}
Assertions.assertEquals(35, sumByForLoop.values().sum());
Assertions.assertEquals(6, sumByForLoop.get('t'));
for Each (Stream
) / each (ImmutableList
)
MutableCharIntMap sumByForEach = new CharIntHashMap();
stream.forEach(e -> sumByForEach.addToValue(e.charAt(0), e.length()));
Assertions.assertEquals(35, sumByForEach.values().sum());
Assertions.assertEquals(6, sumByForEach.get('t'));
MutableCharIntMap sumByEach = new CharIntHashMap();
list.each(e -> sumByEach.addToValue(e.charAt(0), e.length()));
Assertions.assertEquals(35, sumByEach.values().sum());
Assertions.assertEquals(6, sumByEach.get('t'));
injectInto (ImmutableList
)
MutableCharIntMap sumByInject =
list.injectInto(
new CharIntHashMap(),
(map, each) -> {
map.addToValue(each.charAt(0), each.length());
return map;
});
Assertions.assertEquals(35, sumByInject.values().sum());
Assertions.assertEquals(6, sumByInject.get('t'));
reduce (Stream
)
MutableCharIntMap sumByReduce = stream
.reduce(
new CharIntHashMap(),
(map, e) -> {
map.addToValue(e.charAt(0), e.length());
return map;
},
(map1, map2) -> {
map1.putAll(map2);
return map1;
});
Assertions.assertEquals(35, sumByReduce.values().sum());
Assertions.assertEquals(6, sumByReduce.get('t'));
aggregateBy (ImmutableList
)
ImmutableMap<Character, Long> aggregateBy = list.aggregateBy(
word -> word.charAt(0),
() -> new Long(0),
(sum, each) -> sum + each.length());
Assertions.assertEquals(35,
aggregateBy.valuesView().sumOfLong(Long::longValue));
Assertions.assertEquals(6, aggregateBy.get('t').longValue());
aggregateInPlaceBy (ImmutableList
)
ImmutableMap<Character, LongAdder> aggregateInPlaceBy =
list.aggregateInPlaceBy(
word -> word.charAt(0),
LongAdder::new,
(adder, each) -> adder.add(each.length()));
Assertions.assertEquals(35,
aggregateInPlaceBy.valuesView().sumOfLong(LongAdder::longValue));
Assertions.assertEquals(6, aggregateInPlaceBy.get('t').longValue());
collect (Stream
)
MutableCharIntMap sumByCollect = stream.collect(
CharIntHashMap::new,
(map, e) -> map.addToValue(e.charAt(0), e.length()),
CharIntHashMap::putAll);
Assertions.assertEquals(35, sumByCollect.values().sum());
Assertions.assertEquals(6, sumByCollect.get('t'));
collect (Stream
) + groupingBy (Collectors
) + summingInt (Collectors
)
Map<Character, Integer> sumByCollectSummingInt =
stream.collect(Collectors.groupingBy(
word -> word.charAt(0),
Collectors.summingInt(String::length)));
Assertions.assertEquals(35,
sumByCollectSummingInt.values()
.stream().mapToInt(Integer::intValue).sum());
Assertions.assertEquals(Integer.valueO(6), sumByCollectSummingInt.get('t'));
collect (Stream
) + sumByInt (Collectors2
)
ObjectLongMap<Character> sumByCollectors2 =
stream.collect(
Collectors2.sumByInt(
word -> word.charAt(0), String::length));
Assertions.assertEquals(35, sumByCollectors2.values().sum());
Assertions.assertEquals(6, sumByCollectors2.get('t'));
reduceInPlace (ImmutableList
)+ sumByInt (Collectors2
)
ObjectLongMap<Character> reduceInPlaceCollectors2 =
list.reduceInPlace(
Collectors2.sumByInt(
e -> e.charAt(0), String::length));
Assertions.assertEquals(35, reduceInPlaceCollectors2.values().sum());
Assertions.assertEquals(6, reduceInPlaceCollectors2.get('t'));
sumByInt (ImmutableList
)
ObjectLongMap<Character> sumByInt =
list.sumByInt(e -> e.charAt(0), String::length);
Assertions.assertEquals(35, sumByInt.values().sum());
Assertions.assertEquals(6, sumByInt.get('t'));
The simplest solution here is sumByInt
.
Conclusion
We’ve covered a lot of different approaches you can use to sum
or summarize
values using Java and Eclipse Collections. In the case of summing, using a method with sum in the name will probably give you the simplest solution. You can solve almost any problem using methods like injectInto
and reduceInPlace
(Eclipse Collections) or collect (Java Stream). Methods like reduce
are less useful when your result needs to be different than your input. Methods like aggregateBy
and aggregateInPlaceBy
give you a more specific result than collect
because they always return a Map
. Using Collectors2
can be helpful if you want to iterate over a Stream
and get a primitive map result easily using collect
.
I am a Project Lead and Committer for the Eclipse Collections OSS project at the Eclipse Foundation. Eclipse Collections is open for contributions. If you like the library, you can let us know by starring it on GitHub.