Count lists within lists using lambdas

Viewed 196

I've got a list of some objects which each further contains another list and I would like to count the number total number of all items in those nested lists. This is how I do it now:

int accu = 0;
for (SomeObject so : objects) {
    accu += so.getListWithinObject().size();
}

But I feel like this can be written as one line using some Java 8 magic. Probably not even difficult, I just don't know how.

5 Answers

I'll list four one-liners. Each one gives the same output:

  1. objects.stream().map(SomeObject::getListWithinObject).flatMap(List::stream).count();

  2. objects.stream().flatMap(e -> e.getListWithinObject().stream()).count();

  3. objects.stream().map(e -> e.getListWithinObject().size()).reduce(0, Integer::sum);

  4. objects.stream().mapToLong(e -> e.getListWithinObject().size()).sum();

I am sure there are other ways, too. It just shows how powerful the Stream API is; you can perform a single task in multiple ways.

Two more one-liners provided by Eritrean (in comments) without using mapping:

  1. objects.stream().collect(Collectors.summingInt(so -> so.getListWithinObject().size()));

  2. objects.stream().reduce(0, (subSum, so) -> subSum + so.getListWithinObject().size(), Integer::sum)

If it's just a two-leves structure (so, the inner objects do not contain other lists that you want to sum), you can do like this:

objects.stream().map(SomeObject ::getListWithinObject)
       .filter(Objects::nonNull)
       .mapToInt(List::size)
       .sum();

You could stream your collection and then aggregate the sizes into a variable:

AtomicInteger total = new AtomicInteger(0);
objects.stream().forEach(o -> total.addAndGet(o.getListWithinObject().size()));
System.out.println("total size is: " + total);

But just because a stream could be made to work here does not mean you need to use one. There is nothing wrong with using an enhanced for loop.

Let's say your data looks like

final List<Integer> integers1 = Arrays.asList(1, 2, 3, 4);
final List<Integer> integers2 = Arrays.asList(5, 6, 7, 8);
final List<Integer> integers3 = Arrays.asList(9, 10, 11, 12);
final List<Integer> integers4 = Arrays.asList(13, 14, 15, 16);

final List<List<Integer>> lists = Arrays.asList(integers1, integers2, integers3, integers4);

Now, there are two ways to get the count

lists.stream().flatMap(List::stream) // this flattens the lists to look like [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
.count(); // 16

and,

lists.stream().mapToLong(List::size) // creates a long stream which looks like [4, 4, 4, 4]
.sum(); // 16

This is also possible, but very near to the answer given by @Andrea:

Integer accu = objects.stream()
                    .filter(o -> o != null) // may be omitted
                    .map(SomeObject::getListWithinObject)
                    .collect(Collectors.summingInt(List::size));
Related