Steve Chambers Steve Chambers - 1 month ago 8
Java Question

How to flatten all items from a nested Collection into a single List?

Given a complex nested collection of objects such as:

Set<List<Map<String, List<Object>>>> complexNestedCollection;


Does a generic method exist to flatten this out and get a single
List
of all
Object
s contained within?

A few details:


  1. The list shouldn't include collection objects themselves or map keys - only the values at the lowest level.

  2. It should follow the same ordering where possible - so in the example, items in the list would be in order, whereas ordering of maps/sets would depend on the implementation.

  3. It could optionally exclude duplicates

  4. UPDATE: It should ideally detect/handle circular references at any level, e.g. a
    List<List<Object>>
    where the outer List contains itself as a member. (Credit to Adrian JaƂoszewski for mentioning this in the comments below).



Note: The actual use case is to get all Strings from a
List<List<String>>
, which can be done easily enough with two loops but it made me wonder about the general case.

Answer

Here is a FlattenEverythingButTheKitchenSink class, a slightly modified version of a previous answer. It was tested with Java 7 and Java 8.

It works with Lists, Sets, Maps, Queues, and even Arrays of arbitrary depth. It compiles and runs without warning, and I couldn't find any counterexample. Hence the class name :)

If you want a List of objects with possible duplicates, use flatten, if you want a Set, use uniqFlatten.

EDITED: Refactoring to avoid code repetition.

package stackOverflow;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;


// Answer for
// http://stackoverflow.com/questions/20144826/how-to-flatten-all-items-from-a-nested-collection-into-a-single-list
public class FlattenEverythingButTheKitchenSink
{
    public static void main(String[] args) {
        int[][][] int3dArray = { { { 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 } } };
        String[][] string2dArray = { { "He, llo" }, { "Wo", "rld" } };
        String[] stringArray = { "Hello", "World" };
        Set<Integer> integersSet = new HashSet<Integer>();
        integersSet.add(1);
        integersSet.add(2);
        integersSet.add(3);

        Map<String, String> stringMap = new HashMap<>();
        stringMap.put("key1", "value1");
        stringMap.put("key2", "value2");
        stringMap.put("key3", "value3");

        Queue<String> qe = new LinkedList<String>();
        qe.add("x");
        qe.add("y");
        qe.add("z");

        Object[] objectArray = { "Hell", 0, "W", 0, "orld", integersSet, stringMap, qe };

        List<Object> mixList = new ArrayList<Object>();
        mixList.add("String");
        mixList.add(3);
        mixList.add(string2dArray);

        System.out.println(flatten(int3dArray));
        System.out.println(flatten(flatten(int3dArray)));
        System.out.println(flatten(3));
        System.out.println(flatten(stringArray));
        System.out.println(flatten(string2dArray));
        System.out.println(flatten(objectArray));
        System.out.println(flatten(mixList));

        mixList.add(int3dArray);

        System.out.println(uniqFlatten(mixList));
    }

    public static List<Object> flatten(Object object) {
        return (List<Object>) recursiveFlatten(object, true);
    }

    public static Set<Object> uniqFlatten(Object object) {
        return (Set<Object>) recursiveFlatten(object, false);
    }

    private static Collection<Object> recursiveFlatten(Object object, Boolean allowDuplicates) {
        Collection<Object> setOrList;
        if (allowDuplicates) {
            setOrList = new ArrayList<Object>();
        } else {
            setOrList = new LinkedHashSet<Object>();
        }
        if (object.getClass().isArray()) {
            for (int i = 0; i < Array.getLength(object); i++) {
                setOrList.addAll(recursiveFlatten(Array.get(object, i), allowDuplicates));
            }
        } else if (object instanceof Map) {
            for (Object element : ((Map<?, ?>) object).values()) {
                setOrList.addAll(recursiveFlatten(element, allowDuplicates));
            }
        } else if (object instanceof Iterable) {
            for (Object element : (Iterable<?>) object) {
                setOrList.addAll(recursiveFlatten(element, allowDuplicates));
            }
        } else {
            setOrList.add(object);
        }
        return setOrList;
    }
}

It outputs :

[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]
[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]
[3]
[Hello, World]
[He, llo, Wo, rld]
[Hell, 0, W, 0, orld, 1, 2, 3, value1, value2, value3, x, y, z]
[String, 3, He, llo, Wo, rld]
[String, 3, He, llo, Wo, rld, 1, 2, 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]

and shouldn't have any problem with a

Set<List<Map<String, List<Object>>>> complexNestedCollection;

It would also work with a

Set<List<Map<String, List<int[][][][]>>>>

The initialisation code wouldn't be pretty though :D