如何将嵌套Java集合中的所有项展平到单个列表中?

如何将嵌套Java集合中的所有项展平到单个列表中?,java,list,loops,recursion,collections,Java,List,Loops,Recursion,Collections,给定一个复杂的嵌套对象集合,例如: Set<List<Map<String, List<Object>>>> complexNestedCollection; Set complexNestedCollection; 是否存在一种通用方法来将其展平并获得包含在其中的所有对象的单个列表 一些细节: 该列表不应包括集合对象本身或映射键-仅包括最低级别的值 在可能的情况下,它应该遵循相同的顺序-因此在本例中,列表中的项目将按顺序排列,而映射/集合的顺

给定一个复杂的嵌套对象集合,例如:

Set<List<Map<String, List<Object>>>> complexNestedCollection;
Set complexNestedCollection;
是否存在一种通用方法来将其展平并获得包含在其中的所有
对象的单个
列表

一些细节:

  • 该列表不应包括集合对象本身或映射键-仅包括最低级别的值
  • 在可能的情况下,它应该遵循相同的顺序-因此在本例中,列表中的项目将按顺序排列,而映射/集合的顺序将取决于实现
  • 它可以选择性地排除重复项
  • 更新:理想情况下,它应该检测/处理任何级别的循环引用,例如外部列表包含自身作为成员的
    列表。(感谢阿德里安·贾奥斯基在下面的评论中提到这一点)

  • 注意:实际的用例是从一个
    列表中获取所有字符串,这可以通过两个循环轻松完成,但它让我对一般情况感到好奇。

    我不确定这个具体的实现是否有效,因为它充满了未检查的警告和其他危险的东西,但您应该了解一般的想法

    public static Set<Object> recursiveExtract(Object stuff) {
    
        Set<Object> set = new HashSet<Object>();
    
        if(stuff instanceof Iterable) {
            for(Object o : (Iterable<?>)stuff) {
                set.addAll(recursiveExtract(o));
            }
        } else if(stuff instanceof Map) {
            for(Object o : ((Map<?, ? extends Object>) stuff).values()) {
                set.addAll(recursiveExtract(o));
            }
        } else {
            set.add(stuff);
        }
    
        return set;
    }
    
    公共静态集递归提取(对象填充){
    Set=newhashset();
    if(Iterable的stuff instanceof){
    对于(对象o:(Iterable)stuff){
    set.addAll(递归抽取(o));
    }
    }else if(映射的填充实例){
    对于(对象o:((Map您可以使用的展平功能)

    List<Object> simpleCollection = flatten(flatten(flatten(complexNestedCollection)));
    
    List simpleCollection=flant(展平(展平(complexNestedCollection));
    
    我想知道这个场景可能是什么,如果定义一些特定的数据结构(例如树)不是更好。但是无论如何:

    我会避免泛型,因为java的类型系统过于简单,无法处理递归类型:

    public static Collection flatten(Iterable collection, boolean duplicatesAllowed) {
        // create the result collection it just once and 
        ///pass it around as an accumulator
        // it gives you better time/space complexity
        Collection result = duplicatesAllowed ? new ArrayList() : new LinkedHashSet();
        flattenImpl(collection, result);
        return result;
    }
    
    这由两个私有函数支持,它们执行实际提取,填充提供的
    结果
    集合:

    private static void flattenImpl(Object obj, Collection result) {
        if (obj instanceof Iterable) {
            flattenImpl((Iterable)obj, result);
        } 
        else if (obj instanceof Map) {
            flattenImpl( ((Map)obj).values(), result);
        } 
        else {
            result.add(obj);
        }
    }
    
    private static void flattenImpl(Iterable collection, Collection result) {
        for(Object o : collection) {
            flattenImpl(o, result);
        }
    }
    

    假设您使用的是Java8,您可以使用下面的方法:


    这是一个FlatteEverythingButtheKitchensink类,是一个稍微修改过的版本,它是用Java7和Java8测试的

    它适用于列表、集合、映射、队列,甚至任意深度的数组。 它在没有警告的情况下编译和运行,我找不到任何反例。因此类名:)

    如果您想要一个包含可能重复对象的对象列表,请使用展平;如果您想要一个集合,请使用uniqFlatten

    编辑:重构以避免代码重复

    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
    // https://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;
        }
    }
    
    而且应该不会有任何问题的

    Set<List<Map<String, List<Object>>>> complexNestedCollection;
    
    Set complexNestedCollection;
    
    它还可以与

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

    但是初始化代码并不漂亮:D

    我建议解决这个问题的方法是创建一个类,该类递归地展平集合和映射,存储已经访问过的集合和映射,以处理循环依赖关系。这是DFS算法的一个直截了当的实现

    public class CollectionFlattener {
        private List<Object> returnList = new LinkedList<>();
        private Visited visited = new Visited();
    
        public CollectionFlattener(Object o) {
            handle(o);
        }
    
        private void handle(Object o) {
            if (o instanceof Map) {
                handleMap((Map) o);
            } else if (o instanceof Collection) {
                handleCollection((Collection) o);
            } else {
                returnList.add(o);
            }
        }
    
        private void handleCollection(Collection<?> collection) {
            if (!visited.isVisited(collection)) {
                visited.visit(collection);
                collection.forEach(this::handle);
            }
        }
    
        private void handleMap(Map<?, ?> map) {
            if (!visited.isVisited(map)) {
                visited.visit(map);
                handleCollection(map.values());
            }
        }
    
        public Collection<Object> getFlatCollection() {
            return new LinkedList<>(returnList);
        }
    }
    

    这里唯一需要做的是检查
    null
    ,但不需要了解此解决方案背后的逻辑。

    请看:这也可以通过循环完成:迭代
    集合
    列表
    值()
    地图的
    ,最后是lists@Marco谢谢,但我正在为一般情况寻找一个解决方案。它允许循环吗?例如
    List
    ,它包含自身。@AdrianJałoszewski很好的观点-我想这会在下面的大多数递归实现中导致
    StackOverflowException
    。(需要调查在建议的API方法中是否会发生这种情况。)现在添加了第(4)点对于这个问题。我正在做一些类似的工作。我试图使用泛型类型,但没有弄明白。我猜使用“Object”是唯一的选择。无论如何,你都必须进行类型检查。@MightyPork很好的开始,谢谢-当然不会被否决!但让我怀疑-肯定有人在某地做过这件事…你是吗e放弃原来的顺序。你可以使用LinkedHashSet代替。是的,如果顺序很重要的话。但是,列表也会起作用。从简单的代码回顾来看,这看起来很棒。我唯一的初步评论是
    flatten
    uniqFlatten
    之间存在重复-我想这里一定有一个相当简单的重构转换为采用“唯一”的单个方法布尔参数,然后使用
    ArrayList
    LinkedHashSet
    适当地分配一个集合。同样出于兴趣,这其中的哪些部分需要Java 8?感谢反馈。我的第一个实现使用了Lambdas,这就是注释的来源。我刚刚用Java 7测试了这段代码,效果很好。我将尝试重构代码以避免重复的方法。uniqFlatten是事后才想到的。谢谢你的回答!Java 8方法看起来很有希望,但不幸的是,我正在进行的项目在Java 7上卡住了-所以我使用了一个在线工具来尝试它(使用另一个答案中提供的数据)-看这是目前给出的一个错误-也许您可以建议或帮助它工作?(感谢提供Java 7替代方案,但我不太喜欢这个,因为从您的示例来看,它似乎无法转换为单一的“一刀切”通用方法?)查看我对您的问题的评论,泛型方法不是正确的方法,除非您想冒险获得不正确的结果,因为列表中的对象中间可能有一个集合。我的解决方案不是泛型的,它依赖于集合的类型,以确保在运行时得到您想要的结果。在这种情况下,t他认为定制解决方案可能更适合于回答这个特定的问题——它总是要求一个通用的解决方案(这并不是说这个答案不适用于任何情况)
    Set<List<Map<String, List<Object>>>> complexNestedCollection;
    
    Set<List<Map<String, List<int[][][][]>>>>
    
    public class CollectionFlattener {
        private List<Object> returnList = new LinkedList<>();
        private Visited visited = new Visited();
    
        public CollectionFlattener(Object o) {
            handle(o);
        }
    
        private void handle(Object o) {
            if (o instanceof Map) {
                handleMap((Map) o);
            } else if (o instanceof Collection) {
                handleCollection((Collection) o);
            } else {
                returnList.add(o);
            }
        }
    
        private void handleCollection(Collection<?> collection) {
            if (!visited.isVisited(collection)) {
                visited.visit(collection);
                collection.forEach(this::handle);
            }
        }
    
        private void handleMap(Map<?, ?> map) {
            if (!visited.isVisited(map)) {
                visited.visit(map);
                handleCollection(map.values());
            }
        }
    
        public Collection<Object> getFlatCollection() {
            return new LinkedList<>(returnList);
        }
    }
    
    public class Visited {
        private List<Object> visited = new LinkedList<>();
    
        public void visit(Object o) {
            if (!isVisited(o)) {
                visited.add(o);
            }
        }
        public boolean isVisited(Object o) {
            long count = visited.stream().filter(object -> object == o).count();
            return count != 0;
        }
    }