使用java8 Streams合并列表中的内部列表

Moh*_*air 4 java-8 java-stream

我想使用java8流合并内部列表,如下所示:

什么时候

List<List<Integer>> mainList =  new ArrayList<List<Integer>>();
        mainList.add(Arrays.asList(0,1));
        mainList.add(Arrays.asList(0,1,2));
        mainList.add(Arrays.asList(1,2));
        mainList.add(Arrays.asList(3));
Run Code Online (Sandbox Code Playgroud)

应该合并成

  [[0,1,2],[3]];       
Run Code Online (Sandbox Code Playgroud)

什么时候

List<List<Integer>> mainList =  new ArrayList<List<Integer>>();
        mainList.add(Arrays.asList(0,2));
        mainList.add(Arrays.asList(1,4));
        mainList.add(Arrays.asList(0,2,4));
        mainList.add(Arrays.asList(3,4));      
        mainList.add(Arrays.asList(1,3,4));
Run Code Online (Sandbox Code Playgroud)

应该合并成

 [[0,1,2,3,4]];                
Run Code Online (Sandbox Code Playgroud)

到目前为止,这是我所做的

static void mergeCollections(List<List<Integer>> collectionTomerge) {
    boolean isMerge = false;
    List<List<Integer>> mergeCollection = new ArrayList<List<Integer>>();

    for (List<Integer> listInner : collectionTomerge) {
        List<Integer> mergeAny = mergeCollection.stream().map(
                lc -> lc.stream().filter(listInner::contains)
        ).findFirst()
                .orElse(null)
                .collect(Collectors.toList());
    }
}
Run Code Online (Sandbox Code Playgroud)

但我得到这个例外:

Exception in thread "main" java.lang.NullPointerException
at linqArraysOperations.LinqOperations.mergeCollections(LinqOperations.java:87)
Run Code Online (Sandbox Code Playgroud)

更新了我的答案版本

这就是我想要实现的目标,但Tagir的好回答是没有递归

我通过使用Tagir回答没有平面地图的逻辑,在Mikhaal的回答中做了一些改变

public static <T> List<List<T>> combineList(List<List<T>> argList) {
       boolean isMerge = false;
       List<List<T>> result = new ArrayList<>();

       for (List<T> list : argList) {
                                List<List<T>> mergedFound =
                                        result.stream()
                                        .filter(mt->list.stream().anyMatch(mt::contains))
                                        .map(
                                              t ->  Stream.concat(t.stream(),list.stream()).distinct()
                                              .collect(Collectors.toList())
                                             )
                                       .collect(Collectors.toList());

                //if(mergedFound !=null && ( mergedFound.size() > 0 &&  mergedFound.stream().findFirst().get().size() > 0 )){
        if(mergedFound !=null &&  mergedFound.size() > 0 && ){
                   result = Stream.concat(result.stream().filter(t->list.stream().noneMatch(t::contains)),mergedFound.stream()).distinct().collect(Collectors.toList());
                   isMerge = true;
                }
                else
                    result.add(list);

       }
       if(isMerge && result.size() > 1)
          return  combineList(result);
        return result;
    }
Run Code Online (Sandbox Code Playgroud)

Tag*_*eev 5

这是非常简单但不是非常有效的解决方案:

static List<List<Integer>> mergeCollections(List<List<Integer>> input) {
    List<List<Integer>> result = Collections.emptyList();

    for (List<Integer> listInner : input) {
        List<Integer> merged = Stream.concat(
                // read current results and select only those which contain
                // numbers from current list
                result.stream()
                      .filter(list -> list.stream().anyMatch(listInner::contains))
                      // flatten them into single stream
                      .flatMap(List::stream),
                // concatenate current list, remove repeating numbers and collect
                listInner.stream()).distinct().collect(Collectors.toList());

        // Now we need to remove used lists from the result and add the newly created 
        // merged list
        result = Stream.concat(
                result.stream()
                      // filter out used lists
                      .filter(list -> list.stream().noneMatch(merged::contains)),
                Stream.of(merged)).collect(Collectors.toList());
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

棘手的部分是,接下来listInner可能会合并已经添加的几个列表.例如,如果我们有部分结果[[1, 2], [4, 5], [7, 8]],并处理新的listInner内容[2, 3, 5, 7],那么部分结果应该变为[[1, 2, 3, 4, 5, 7, 8]](即所有列表合并在一起).因此,在每次迭代中,我们都在寻找现有的部分结果,这些结果具有与当前数字相同的数字listInner,将它们展平,与当前数据连接listInner并转储到新merged列表中.接下来,我们从当前使用的结果列表中过滤掉merged并添加merged.

您可以使用partitioningBy收集器立即执行两个过滤步骤,使解决方案更有效:

static List<List<Integer>> mergeCollections(List<List<Integer>> input) {
    List<List<Integer>> result = Collections.emptyList();

    for (List<Integer> listInner : input) {
        // partition current results by condition: whether they contain
        // numbers from listInner
        Map<Boolean, List<List<Integer>>> map = result.stream().collect(
                Collectors.partitioningBy(
                        list -> list.stream().anyMatch(listInner::contains)));

        // now map.get(true) contains lists which intersect with current
        //    and should be merged with current
        // and map.get(false) contains other lists which should be preserved 
        //    in result as is
        List<Integer> merged = Stream.concat(
                map.get(true).stream().flatMap(List::stream),
                listInner.stream()).distinct().collect(Collectors.toList());
        result = Stream.concat(map.get(false).stream(), Stream.of(merged))
                       .collect(Collectors.toList());
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

这里map.get(true)包含列表,其中包含元素,listInnermap.get(false)包含应从上一结果中保留的其他列表.

元素的顺序可能与您期望的不同,但您可以轻松地对嵌套列表进行排序,或者根据需要使用List<TreeSet<Integer>>结果数据结构.

  • @Mohtisham,您是否想要将外部`for`循环重写为Stream API?好吧,从技术上讲,它是"reduceLeft"或"foldLeft"操作.标准流API没有这样的东西,但如果你不关心并行性,你可以使用简单的`reduce`提供伪造组合器.但是,这不会使代码更好.[这是实现](https://gist.github.com/amaembo/bb65d43ac4202b584ce6c0a29db16aaf)使用我的免费[StreamEx](https://github.com/amaembo/streamex/)库(实际上有foldLeft).没有StreamEx,它看起来会更糟. (2认同)