List of List of Lists (Flatten a ArrayList with N-Depth)

Viewed 314

I am trying to Flatten a ArrayList with N-Depth. For this I tried using flapMap method of Stream API. I am able to get it. But I have to use flatMap() method repeatedly as per the number of list of lists. If I am using one more flatMap() method, it shows compile time error. Is there any way to dynamically get it done.

This is the code that I used:

List<Integer> list1 = Arrays.asList(4,5,6);
List<Integer> list2 = Arrays.asList(7,8,9);

List<List<Integer>> listOfLists = Arrays.asList(list1, list2);

List<List<List<Integer>>> listA = Arrays.asList(listOfLists);
List<List<List<List<Integer>>>> listB = Arrays.asList(listA);

List<Integer> listFinal = listB.stream()
    .flatMap(x -> x.stream())
    .flatMap(x -> x.stream())
    .flatMap(x -> x.stream())
    .collect(Collectors.toList());
//In the above line, If I use listA instead of listB, it is showing error.

listFinal.forEach(x-> System.out.println(x));
4 Answers

This seems to work for me, using recursion. When given a list

  • check if it is a list of Integer, in which case return it.
  • otherwise it is a list of lists. FlatMap it (to remove 1 level of nesting), and flatten the resulting list.
private static List<Integer> flatten(List<?> list) {
    if (list.get(0) instanceof Integer) {
        return (List<Integer>) list;
    }

    List<List<?>> listOfLists = (List<List<?>>) list;
    return flatten(listOfLists.stream()
            .flatMap(Collection::stream)
            .collect(Collectors.toList()));
}

then

public static void main(String[] args) {
    List<List<List<List<List<Integer>>>>> listC = Arrays.asList(
            Arrays.asList(
                    Arrays.asList(
                            Arrays.asList(
                                    Arrays.asList(0, 1),
                                    Arrays.asList(2, 3, 4)
                            ),
                            Arrays.asList(
                                    Arrays.asList(5),
                                    Arrays.asList(6, 7),
                                    Arrays.asList(8, 9)
                            )
                    ),
                    Arrays.asList(
                            Arrays.asList(
                                    Arrays.asList(10, 11),
                                    Arrays.asList(12, 13, 14)
                            ),
                            Arrays.asList(
                                    Arrays.asList(15),
                                    Arrays.asList(16, 17),
                                    Arrays.asList(18, 19)
                            )
                    )
            ),
            Arrays.asList(
                    Arrays.asList(
                            Arrays.asList(
                                    Arrays.asList(20, 21),
                                    Arrays.asList(22, 23, 24)
                            ),
                            Arrays.asList(
                                    Arrays.asList(25),
                                    Arrays.asList(26, 27),
                                    Arrays.asList(28, 29)
                            )
                    ),
                    Arrays.asList(
                            Arrays.asList(
                                    Arrays.asList(30, 31),
                                    Arrays.asList(32, 33, 34)
                            ),
                            Arrays.asList(
                                    Arrays.asList(35),
                                    Arrays.asList(36, 37),
                                    Arrays.asList(38, 39)
                            )
                    )
            )
    );

    List<Integer> result = flatten(listC);

    System.out.println(listC);
    System.out.println(result);

}

prints

[[[[[0, 1], [2, 3, 4]], [[5], [6, 7], [8, 9]]], [[[10, 11], [12, 13, 14]], [[15], [16, 17], [18, 19]]]], [[[[20, 21], [22, 23, 24]], [[25], [26, 27], [28, 29]]], [[[30, 31], [32, 33, 34]], [[35], [36, 37], [38, 39]]]]]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]

There is a bit of Unchecked casting going on, but not sure how to do without

For List<List<List<List<Integer>>>> listB, .stream().flatMap(.stream()).flatMap(.stream()).flatMap(.stream()).collect()

But for List<List<List<Integer>>> listA, .stream().flatMap(.stream()).flatMap(.stream()).collect().

See flatMap() count is just one less than the Generic depth.

I hope you know that deeply nested collections isn't the best way to represent the data and must be avoided (it's an almost certain indicator of faulty design). So I'll treat this question as a cryptic puzzle rather than a practical task.

You can achieve that without using recursion. But caution this approach is vicious as well recursion because as well recursive approach it requires to relinquish the type safety provided by generics (I've warned that you shouldn't do that in the first place).

To do that, you need to perform intanceof checks in a loop. And populate the resulting list of row type with elements of a nested list.

Note :

  • contrary to commonly used generic collections like List<Integer> which characterized as covariant (i.e. you can assign only collection of the same type to it) you're allowed and to assign anything to the list of row type and to modify it as well. Which is an unsafe combination, and hence usage of row type collections is highly discouraged.

The loop exits if the first element isn't a list.

public static void main(String[] args) {
    List<List<List<List<Integer>>>> source =
            List.of(List.of(List.of(List.of(4,5,6), List.of(7,8,9))));

    List<Integer> result = source.stream()
            .flatMap(list -> deepFlatten(list).stream())
            .collect(Collectors.toList());
    
    System.out.println(source);
    System.out.println(result);
}

public static List<Integer> deepFlatten(List<?> nestedList) {
    if (nestedList.isEmpty()) {
        return (List<Integer>) nestedList;
    }

    List result = new ArrayList<>();
    List current = nestedList;
    while (current.get(0) instanceof List<?>) {
        for (Object next: current) {
            result.addAll((List) next);
        }
        current = result;
        result = new ArrayList<>();
    }
    return (List<Integer>) current;
}

Output

[[[[4, 5, 6], [7, 8, 9]]]]
[4, 5, 6, 7, 8, 9]

The below snippet worked for me. Thanks for the response all.

List list1 = Arrays.asList(4,5,6); List list2 = Arrays.asList(7,8,9);

    List<List<Integer>> listOfLists = Arrays.asList(list1, list2);

    List< List< List<Integer> > > list = Arrays.asList(listOfLists);
    List< List< List<List<Integer>> > > listB = Arrays.asList(list);
    
    
    //List<Integer> listF = listB.stream().flatMap(x -> x.stream()).flatMap(x -> x.stream()).flatMap(x -> x.stream()).collect(Collectors.toList());
    
    List<Integer> flattenList = (List<Integer>) getList(listB);
    flattenList.forEach(x-> System.out.println(x));
    
    
}
public static List<?> getList(List<? > s) {
    
    if( s.isEmpty() || !( s.get(0) instanceof List) ) {
        return s;
    }
    
    return getList( (List<?>) s.stream().flatMap(x-> ((List)x).stream()).collect(Collectors.toList()));
}
Related