3

I have a list of numbers like this:

[ 0, 1, 2, 3, 4, 5, 6, 7 ]

How to sum up every N (let's assume 2) elements in an elegant way and transform the list into:

[ 1, 5, 9, 13 ]

edit: I came up with the following solution:

    List<Double> input = Arrays.asList(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0);
    List<Double> output = new ArrayList<>();

    int N = 2;
    IntStream.range(0, (input.size() + N - 1) / N)
            .mapToObj(i -> input.subList(i * N, Math.min(N * (i + 1), input.size())))
            .mapToDouble(l -> l.stream().mapToDouble(Double::doubleValue).sum())
            .forEach(output::add);

    System.out.println(output);

It works, but I'm still looking for a more readable and simple one.

Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
Alex Romanov
  • 11,453
  • 6
  • 48
  • 51
  • 6
    Have you tried anything for this yet ? Its a good practice to post the code while asking question for your attempt. – PankajT Mar 29 '17 at 09:39
  • 1
    If your array is always an arithmetic sequence, then an elegant way is to use the formula for the sum of an arithmetic sequence. In your case you have to manipulate with the formula a little bit, since you have to sum numbers from `a` to `b` (which is a sub-sequence of an arithmetic sequence). – ahoxha Mar 29 '17 at 09:49
  • 2
    See [Is there a good way to extract chunks of data from a java 8 stream?](http://stackoverflow.com/q/25408350/5221149). You can then sum each chunk for the final result. – Andreas Mar 29 '17 at 09:59

5 Answers5

2

Given:

List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7);
int nth = 2;

How about:

IntStream.iterate(0, idx -> idx + nth)
         .limit(numbers.size() / nth)
         .map(idx -> IntStream.range(idx, idx + nth)
                              .reduce((sum, index) -> sum + numbers.get(index))
                              .orElse(0))
         .forEach(System.out::println);

Or alternatively:

IntStream.range(0, numbers.size() / nth)
         .map(idx -> IntStream.range(idx * nth, (idx + 1) * nth)
                              .map(index -> numbers.get(index))
                              .sum())
         .forEach(System.out::println);
Harmlezz
  • 7,972
  • 27
  • 35
2

Here's an alternative approach, using Collectors.groupingBy():

List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7);
int N = 2;
Collection<Integer> sums =  numbers.stream()
                                   .collect(Collectors.groupingBy(i-> i/N,
                                                                  Collectors.summingInt(Integer::intValue)))
                                   .values();
System.out.println (sums);

Output:

[1, 5, 9, 13]
Eran
  • 387,369
  • 54
  • 702
  • 768
2

Here's another approach which works despite your elements are sorted or not. Moreover it does not anticipate anything about your starting values and gives the correct result in a much broader spectrum. This approach still works even if the list.size() % n == 0 condition does not hold. So, literally, it does not need any preconditions to hold in order to get what is desired.

The intuition behind this approach is that your indices are naturally sorted, hence can be used to achieve what we need. We can group elements into one category as far as their current index i / n yields the same value. Then a downstream collector is used to calculate the sum of elements that fall in the same group. Here's how it looks.

Collection<Integer> sumsOfnElements = IntStream.range(0, numbers.size()).boxed()
    .collect(Collectors.groupingBy(i -> i / n, 
        Collectors.summingInt(numbers::get))).values(); 

Equivalent iterative solution following more or less same approach is given below. In a performance stringent setting, I would choose this imperative solution over the stream based one.

Assumption: input is an array of ints, Not a list.

final float inputLen = input.length;
final int resultLen = (int) Math.ceil(inputLen / n);
final int[] result = new int[resultLen];

for (int i = 0; i < inputLen; i++)
    result[i / n] += input[i];
Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
  • 1
    Very clean solution! Looks like it should be an accepted answer now. – Alex Romanov Sep 23 '19 at 09:37
  • 1
    Very nice use of lambdas. I like the "performance stringent setting" bit, too. Would parallelStream help if performance was an issue? Or is the overhead not worth it? – duffymo Oct 02 '19 at 12:40
  • Well, since the stream based implementation is parallel friendly, it should yield a noticeable parallel speedup. But we should not forget the startup and merge costs imposed by the parallel execution. The best thing is to measure it by doing a benchmark for different sizes of data sets. Sometimes the speedup gain depends on many factors including size, memory locality of the data set et.al. Anyway the stream based solution is definitely much more scalable than it's sequential counterpart. It's always interesting to see how raw performance and salability are often at odds with each other. – Ravindra Ranwala Oct 02 '19 at 16:26
0

How about this? (I've edited to take nTh)

 int nTh = 2;

    List<Integer> list = List.of(0, 1, 2, 3, 4, 5, 6, 7);

    int[] result = IntStream.iterate(0, i -> i + nTh)
            .limit(list.size() / nTh)
            .map(x -> {
                return IntStream.range(0, nTh).map(i -> list.get(i + x)).sum();
            })
            .toArray();

    System.out.println(Arrays.toString(result));
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • this will only work for nTh =2 and nTh = 4, for the given example. – Edd Mar 29 '17 at 10:28
  • 1
    @Edd I assume that `list.size` is indeed divisible by `nTh`; but that's the example provided anyway... – Eugene Mar 29 '17 at 10:52
  • I can understand that, but this will not run for any nTh. If you wanted to only cover the example provided, then I think it would be best to remove mentioning nTh as it will mislead people into believing that it will work for any N [0,l.size), which is not the case. Up to you. – Edd Mar 29 '17 at 10:58
0

I think most elegant way is using flatMap, so you avoid O(n²) complexity. Here's an example:

public static class Helper {

    private final int chunkSize;
    private int index;
    private int sum;

    public Helper(int chunkSize) {
        this.chunkSize = chunkSize;
    }

    public Stream<Integer> set(int v) {
        index += 1;
        sum += v;

        ArrayList<Integer> ret = new ArrayList<>();
        if (index == chunkSize) {
            ret.add(sum);
            sum = 0;
            index = 0;
        }
        return ret.stream();
    }
}

public static void main(String[] args) {
    List<Integer> input = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7);
    Helper helper = new Helper(2);
    List<Integer> output = input.stream().flatMap(helper::set).collect(Collectors.toList());

    System.out.println(input);
    System.out.println(output);
}

This will outputs:

[0, 1, 2, 3, 4, 5, 6, 7]
[1, 5, 9, 13]
Jack
  • 1,488
  • 11
  • 21