2017-02-10 8 views
4

Я хочу, чтобы объединить внутренние списки с использованием java8 потоков для следующих например:Использование java8 Streams объединить внутренние списки в списке

Когда

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)); 

должны быть объединены в

[[0,1,2],[3]];  

И Когда

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)); 

должны быть объединены в

[[0,1,2,3,4]];     

Это до сих пор, что я сделал

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()); 
    } 
} 

, но я получаю это исключение:

Exception in thread "main" java.lang.NullPointerException 
at linqArraysOperations.LinqOperations.mergeCollections(LinqOperations.java:87) 

Обновлено с противоминной версии ответа

Вот чего я хочу достичь, но отличный ответ Тагира без рекурсии

меняю вещи немного в 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; 
    } 

ответ

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; 
} 

Хитрость в том, что в следующем 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; 
} 

Здесь map.get(true) содержит списки, которые имеют элементы из listInner и map.get(false) содержит другие списки, которые должны быть сохранены от предыдущий результат.

Порядок элементов, вероятно, не тот, который вы ожидаете, но вы можете легко сортировать вложенные списки или использовать List<TreeSet<Integer>> в качестве результирующей структуры данных, если хотите.

+0

Смотреть отлично !!!! !!! можете ли вы как-то удалить этот единственный цикл, если это возможно? –

+2

@Mohtisham, вы хотите переписать внешний цикл 'for' для Stream API? Ну, технически это операция «reduceLeft» или «foldLeft». У стандартного Stream API нет такой вещи, но если вы не заботитесь о параллелизме, вы можете использовать простой 'reduce', предоставляющий фиктивный комбайнер. Однако это не улучшит код. [Вот реализация] (https://gist.github.com/amaembo/bb65d43ac4202b584ce6c0a29db16aaf), используя мою бесплатную библиотеку [StreamEx] (https://github.com/amaembo/streamex/) (на самом деле имеющую foldLeft). Без StreamEx это выглядело бы еще хуже. –

+0

Я полностью потерян, чтобы понять, что происходит, можете ли вы прокомментировать, что делает ваш код для обоих случаев. Я знаю, что это большой вопрос и странный, но я чувствую себя совершенно беспомощным, чтобы зачать свое искусство. –

1

За исключением вы получаете, я бы предположил, что List<List<Integer>> что mergeCollections является в котором содержится null значений, и что эти броски NullPointerException на listInner::contains.

Во-вторых, если я правильно понимаю вашу проблему, вам нужен алгоритм, который может объединять списки, которые имеют общий элемент. Я придумал это, чтобы решить вашу проблему:

public class Combiner { 

    public static void main(String[] args) { 
     List<List<Integer>> mainList = new ArrayList<>(); 
     mainList.add(Arrays.asList(1, 2)); 
     mainList.add(Arrays.asList(4, 5)); 
     mainList.add(Arrays.asList(7, 8)); 
     mainList.add(Arrays.asList(6, 19)); 

     mainList.add(Arrays.asList(2, 3, 5, 7)); 

     System.out.println(combineList(new ArrayList<>(mainList))); 
     List<List<Integer>> result = mergeCollections(new ArrayList<>(mainList)); 
     System.out.println(result); 

    } 

    public static <T> List<List<T>> combineList(List<List<T>> argList) { 
     List<List<T>> result = new ArrayList<>(); 
     for (List<T> list : argList) { 
      //Copy the given list 
      List<T> addedList = new ArrayList<>(list); 
      result.add(addedList); 
      for (List<T> otherList : argList) { 
       if (list.equals(otherList)) continue; 
       //If at least one element is shared between the two lists 
       if (list.stream().anyMatch(otherList::contains)) { 
        //Add all elements that are exclusive to the second list 
        addedList.addAll(otherList.stream().map(t -> addedList.contains(t) ? null : t) 
          .filter(t -> t != null).collect(Collectors.toList())); 
       } 
      } 
     } 
     List<List<T>> del = new ArrayList<>(); 
     for (int i = 0; i < result.size(); i++) { 
      for (int j = i + 1; j < result.size(); j++) { 
       List<T> list = result.get(j); 
       if (listEqualsUnOrdered(list, result.get(i))) { 
        //Modified this 
        del.add(result.get(i)); 
       } 
      } 
      //Can't use listIterator here because of iterating starting at j + 1 
      result.removeAll(del); 
     } 
     //Recursion 
     if (!result.equals(argList)) { 
      result = combineList(result); 
     } 
     return result; 
    } 

    private static <T> boolean listEqualsUnOrdered(List<T> list1, List<T> list2) { 
     if (list1.size() != list2.size()) return false; 
     List<T> testOne = new ArrayList<>(list1); 
     testOne.removeAll(list2); 
     boolean testOnePassed = (testOne.size() == 0); 
     List<T> testTwo = new ArrayList<>(list2); 
     testTwo.removeAll(list1); 
     if (testTwo.size() == 0 && testOnePassed) return true; 
     return false; 
    } 
} 

Алгоритм достаточно прост, он использует простую рекурсию для запуска алгоритма, пока выход не является «чистым», то есть до тех пор списки не были полностью объединены. Я не делал никаких оптимизаций, но он делает то, что он должен делать.

Обратите внимание, что этот метод также объединит существующий список.

+0

Это не подходит для 1 тестового случая. Предоставление только 1 списка с элементом 3. –

+0

@Mohtisham Он отлично работает со мной. Вы отправили ему «Список <Список >'? Попробуйте 'System.out.println (combList (Collections.singletonList (Collections.singletonList (3))));', он печатает "[[3]]". – MikaelF

+0

, но он должен напечатать [[0,1,2] [3]] –

 Смежные вопросы

  • Нет связанных вопросов^_^