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

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

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

是否存在通用方法来展平它并获得包含在其中的所有Object的单个List

一些细节:

  1. 该列表不应包含集合对象本身或映射键 – 仅包含最低级别的值。
  2. 它应尽可能遵循相同的顺序 – 因此在示例中,列表中的项目将按顺序排列,而映射/集合的排序将取决于实现。
  3. 它可以选择性地排除重复
  4. 更新:理想情况下,它应检测/处理任何级别的循环引用,例如List<List> ,其中外部List将自身包含为成员。 (感谢AdrianJałoszewski在下面的评论中提到这一点)。

注意:实际的用例是从List<List>获取所有字符串,这可以通过两个循环轻松完成,但它让我对一般情况感到疑惑。

假设您使用Java 8 ,您可以使用Stream API执行此操作,这要归功于flatMap(Function> mapper)如下所示:

 // 1. Convert the Set as a Stream of List>> // 2. Extract the elements of the lists to get a Stream of Map> // 3. Extract values of the maps to get a Stream of List // 4. Extract the elements of the lists to get a Stream of Object // 5. Get rid of duplicates // 6. Collect the result as a List of Object List result = complexNestedCollection.stream() .flatMap(List::stream) .flatMap(m -> m.values().stream()) .flatMap(List::stream) .distinct() .collect(Collectors.toList()); 

Stream flatMap(Function> mapper)

返回一个流,该流包含将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果。 每个映射的流在其内容放入此流后关闭。 (如果映射的流为空,则使用空流,而不是。)


对于以前版本Java ,您仍然可以使用Google Guava中的 FluentIterable来替换Stream并使用transformAndConcat(Function> function)而不是flatMap来展平您的集合。

之前的代码片段将被重写为下一个:

 List result = new ArrayList<>( new LinkedHashSet<>( FluentIterable.from(complexNestedCollection) .transformAndConcat( new Function>>, Iterable>>> () { public Iterable>> apply(final List>> input) { return input; } } ).transformAndConcat( new Function>, Iterable>> () { public Iterable> apply(final Map> input) { return input.values(); } } ).transformAndConcat( new Function, Iterable> () { public Iterable apply(final List input) { return input; } } ).toList() ) ); 

我不确定这个确切的实现是否会起作用,因为它充满了未经检查的警告和其他危险的东西,但你应该得到一般的想法。

 public static Set recursiveExtract(Object stuff) { Set set = new HashSet(); if(stuff instanceof Iterable) { for(Object o : (Iterable)stuff) { set.addAll(recursiveExtract(o)); } } else if(stuff instanceof Map) { for(Object o : ((Map) stuff).values()) { set.addAll(recursiveExtract(o)); } } else { set.add(stuff); } return set; } 

如果你坚持列表,你也可以使用List ,但是如果你关心订单,你可以获得重复的结果,或者LinkedHashSet


请给我改进建议,而不是downvotes。 它更好。

这是FlattenEverythingButTheKitchenSink类,是之前答案的略微修改版本。 它使用Java 7和Java 8进行了测试。

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

如果您想要一个可能重复的对象列表,请使用展平,如果您想要一个Set,请使用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 integersSet = new HashSet(); integersSet.add(1); integersSet.add(2); integersSet.add(3); Map stringMap = new HashMap<>(); stringMap.put("key1", "value1"); stringMap.put("key2", "value2"); stringMap.put("key3", "value3"); Queue qe = new LinkedList(); qe.add("x"); qe.add("y"); qe.add("z"); Object[] objectArray = { "Hell", 0, "W", 0, "orld", integersSet, stringMap, qe }; List mixList = new ArrayList(); 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 flatten(Object object) { return (List) recursiveFlatten(object, true); } public static Set uniqFlatten(Object object) { return (Set) recursiveFlatten(object, false); } private static Collection recursiveFlatten(Object object, Boolean allowDuplicates) { Collection setOrList; if (allowDuplicates) { setOrList = new ArrayList(); } else { setOrList = new LinkedHashSet(); } 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; } } 

它输出:

 [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] 

并且应该没有任何问题

 Set>>> complexNestedCollection; 

它也适用于

 Set>>> 

初始化代码不会很漂亮:D

你可以使用LambdaJ的flatten函数。

 List simpleCollection = flatten(flatten(flatten(complexNestedCollection))); 

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

 public class CollectionFlattener { private List 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 getFlatCollection() { return new LinkedList<>(returnList); } } 

Visited类必须提供一种方法来检查我们遇到的对象是否是相同的(这就是我在这里使用==运算符而不是equals )。 这是我们可以减少循环依赖关系而不会丢失有关集合的信息的唯一方法,这些集合通过巧合包含相同的元素。

 public class Visited { private List 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; } } 

这里唯一需要的是null检查,但没有必要理解这个解决方案背后的逻辑。

我想知道这种情况可能是什么,以及定义一些特定的数据结构(如树)是否更好。 但不管怎么说:

我会避免使用generics,因为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; } 

这由两个私有函数支持,这些函数执行实际提取以填充提供的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); } }