I have to complete this trivial operation with streams: given a list, get the sum and the sum of the first 20 elements.
This is what I had in mind
IntStream stream = obj.stream().mapToInt(d->d.getInt());
stream.limit(20).sum() / stream.sum();
However I cant do this, since I am told I can't reuse a stream, so.. I tried the following:
List<Integer> counts = obj.stream()
.mapToInt(d -> d.getInt())
.boxed().collect(Collectors.toList());
counts.stream().limit(20).sum() / counts.stream().sum();
However I am told that I can't use sum on Stream, so I need to mapToInt again for left and right hand side of this trivial operation.
Is there a way to do this operation in a more elegant and concise way using streams?
There's a cool way to do this in a single parallel pass, but not using streams.
int[] nums = obj.stream().mapToInt(Integer::intValue).toArray();
Arrays.parallelPrefix(nums, Integer::sum);
int sumOfFirst20 = nums[19];
int sumOfAll = nums[nums.length-1];
The parallelPrefix method will compute the sequence of partial sums of the specified function (here, plus) on the array, in place. So the first element is a0; the second is a0+a1, the third is a0+a1+a2, etc.
Instead of collecting the numbers in a List<Integer>
, you can turn them into an int[]
with toArray()
. This way, the code is a bit more compact, you don't have to box and unbox all the time, and you can turn that int[]
directly into an IntStream
.
int[] nums = obj.stream().mapToInt(d -> d.getInt()).toArray();
IntStream.of(nums).limit(20).sum() / IntStream.of(nums).sum();
There's no need to overcomplicate things. Just get the stream twice from the list:
int totalSum = obj.stream().mapToInt(i -> i).sum();
int first20 = obj.stream().limit(20).mapToInt(i -> i).sum();
Yes it will do two passes in the list (and the second time just for the 20 first elements so not a big deal), so I expect this is what they want you to do. It's simple, efficient and readable.
To do it in one pass you can use a collector. As an example, you can do it like this:
Map<Boolean, Integer> map = IntStream.range(0, obj.size())
.boxed()
.collect(partitioningBy(i -> i < 20,
mapping(i -> obj.get(i),
summingInt(i -> i))));
int first20 = map.get(true);
int totalSum = map.get(true) + map.get(false);
Basically, you stream the indices first. Then for each indice; you'll split them in the map in two lists, one for the 20 first indexes, and the others in the other list; resulting in a Map<Boolean, List<Integer>>
.
Then each index is mapped to its value in the original List
(with the mapping
collector).
Finally you transform each List<Integer>
by summing their values into a single Integer
. Then you just get the total sum.
Another workaround with a custom collector:
public static Collector<Integer, Integer[], Integer[]> limitSum(int limit, List<Integer> list) {
return Collector.of(() -> new Integer[]{0, 0},
(a, t) -> { a[0] += list.get(t); if(t < limit) a[1] += list.get(t); },
(a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },
a -> a);
}
and an example of usage:
List<Integer> obj = Stream.iterate(0, x -> x + 1).limit(5).collect(toList()); //[0, 1, 2, 3, 4]
Integer[] result = IntStream.range(0, obj.size())
.boxed()
.collect(limitSum(4, obj));
System.out.println(Arrays.toString(result)); //[10, 6]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With