Search code examples
listgroovyflattenpurely-functional

Flatten list of lists by one Level in Groovy without mutating variables


Imagine a list of lists, for example

List lists = [[[1]], [[2]], [[3]], [[4]], [[5]], [[6]], [[7]], [[8]], [[9]], [[10]]]

This is just an example - the inner lists may contain multiple elements. I want to flatten the list by one level without mutating variables. The result should be

[[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]]

Is there a good way in Groovy for that? It should be almost as fast as

lists.inject([]) { a, b -> a.addAll(b); a }
  • lists.flatten() flattens across all levels, resulting in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
  • lists.sum() is concise and does the job, but sadly in O(n2) for this case. It should be done in O(n).
  • The Java Streams way lists.stream().flatMap(List::stream).toList() seems to be O(n) in Groovy, but I was'nt so sure at first as it apparently comes with an extreme overhead compared to using it directly in Java. You can speed it up by a factor of 10 to 100 by just wrapping it in a method using @CompileStatic. Then it would be fast, but that is not really a convenient solution. There must be another way.

Solution

  • Ah, apparently that is what lists.collectMany { it } is for...

    collectMany() is a bit like flatMap in Scala or Java Streams. It does not mutate variables at top level and is reasonably fast: Not as fast as

    lists.inject([]) { a, b -> a.addAll(b); a }
    

    but much faster than

    lists.stream().flatMap(List::stream).toList() 
    

    I have played around with it and collectMany() is more powerful than the standard flatMap and pretty cool to use, also for concatenating Maps.