Alex Romanov
Alex Romanov

Reputation: 11963

Sum up every N elements of a list?

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.

Upvotes: 3

Views: 4096

Answers (5)

Ravindra Ranwala
Ravindra Ranwala

Reputation: 21124

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];

Upvotes: 2

Eran
Eran

Reputation: 393781

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]

Upvotes: 2

Harmlezz
Harmlezz

Reputation: 8068

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);

Upvotes: 2

Jack
Jack

Reputation: 1606

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]

Upvotes: 0

Eugene
Eugene

Reputation: 120848

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));

Upvotes: 0

Related Questions