Список расщепление на подсписки по элементам

у меня есть этот список (List<String>):

["a", "b", null, "c", null, "d", "e"]

и я хотел бы что-то вроде этого:

[["a", "b"], ["c"], ["d", "e"]]

другими словами, Я хочу разделить свой список на подсписки, используя null значение в качестве разделителя, чтобы получить список списков (List<List<String>>). Я ищу решение Java 8. Я пробовал с Collectors.partitioningBy но я не уверен, что это то, что я ищу. Спасибо!

13 ответов


единственное решение, которое я придумал на данный момент, реализуя свой собственный коллектор.

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

Итак, вы должны знать, что это будет молча сломать если трубопровод работает в параллельно.

это не a желательно поведение и должно быть избежать. Вот почему я бросаю исключение в части объединителя (вместо (l1, l2) -> {l1.addAll(l2); return l1;}), поскольку он используется параллельно при сочетании этих двух списков, так что у вас есть исключение, а не плохого результата.

также это не очень эффективно из-за копирования списка (хотя он использует собственный метод для копирования базового массива).

Итак, вот реализация коллектора:

private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
    final List<String> current = new ArrayList<>();
    return Collector.of(() -> new ArrayList<List<String>>(),
        (l, elem) -> {
            if (sep.test(elem)) {
                l.add(new ArrayList<>(current));
                current.clear();
            }
            else {
                current.add(elem);
            }
        },
        (l1, l2) -> {
            throw new RuntimeException("Should not run this in parallel");
        },
        l -> {
            if (current.size() != 0) {
                l.add(current);
                return l;
            }
        );
}

и как использовать это:

List<List<String>> ll = list.stream().collect(splitBySeparator(Objects::isNull));

выход:

[[a, b], [c], [d, e]]


Как ответ Юпа Эггена вышел, похоже, что это можно сделать параллельно (отдайте ему должное за это!). При этом он уменьшает реализацию custom collector до:
private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
    return Collector.of(() -> new ArrayList<List<String>>(Arrays.asList(new ArrayList<>())),
                        (l, elem) -> {if(sep.test(elem)){l.add(new ArrayList<>());} else l.get(l.size()-1).add(elem);},
                        (l1, l2) -> {l1.get(l1.size() - 1).addAll(l2.remove(0)); l1.addAll(l2); return l1;});
}

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


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

private static <T> List<List<T>> splitBySeparator(List<T> list, Predicate<? super T> predicate) {
    final List<List<T>> finalList = new ArrayList<>();
    int fromIndex = 0;
    int toIndex = 0;
    for(T elem : list) {
        if(predicate.test(elem)) {
            finalList.add(list.subList(fromIndex, toIndex));
            fromIndex = toIndex + 1;
        }
        toIndex++;
    }
    if(fromIndex != toIndex) {
        finalList.add(list.subList(fromIndex, toIndex));
    }
    return finalList;
}

и назовите это как List<List<String>> list = splitBySeparator(originalList, Objects::isNull);.

его можно улучшить для проверять кра-случаи.


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

во-первых, вот обычный решение, для целей обсуждения и анализа:

static List<List<String>> splitConventional(List<String> input) {
    List<List<String>> result = new ArrayList<>();
    int prev = 0;

    for (int cur = 0; cur < input.size(); cur++) {
        if (input.get(cur) == null) {
            result.add(input.subList(prev, cur));
            prev = cur + 1;
        }
    }
    result.add(input.subList(prev, input.size()));

    return result;
}

это в основном просто, но есть немного тонкости. Один момент заключается в том, что ожидающий подлист из prev to cur всегда открыта. Когда мы сталкиваемся null мы закрываем его, добавляем его в список результатов и продвигаемся prev. После цикла мы закрываем подлист безоговорочно.

другое наблюдение заключается в том, что это цикл над индексами, а не над самими значениями, поэтому мы используем арифметику for-loop вместо расширенного цикла" для каждого". Но это предполагает, что мы можем передавать поток с помощью индексов для генерации поддиапазонов вместо потоковой передачи значений и ввода логики в коллектор (как это было сделано предлагаемое решение Юпа Эггена).

как только мы поняли это, мы можем видеть, что каждая позиция null во входных данных есть разделитель для подсписка: это правый конец подсписка слева, и он (плюс один) является левым концом подсписка, чтобы право. Если мы можем обрабатывать крайние случаи, это приводит к подходу, где мы находим индексы, при которых null элементы возникают, сопоставляют их с подсписками и собирают подсписки.

результирующий код выглядит следующим образом:

static List<List<String>> splitStream(List<String> input) {
    int[] indexes = Stream.of(IntStream.of(-1),
                              IntStream.range(0, input.size())
                                       .filter(i -> input.get(i) == null),
                              IntStream.of(input.size()))
                          .flatMapToInt(s -> s)
                          .toArray();

    return IntStream.range(0, indexes.length-1)
                    .mapToObj(i -> input.subList(indexes[i]+1, indexes[i+1]))
                    .collect(toList());
}

получение индексов, при которых null происходит довольно легко. Камнем преткновения является добавление -1 и size в правом конце. Я решил использовать Stream.of чтобы сделать добавление, а затем flatMapToInt чтобы сгладить их. (Я пробовал несколько других подходов, но этот казался самым чистым.)

здесь немного удобнее использовать массивы для индексов. Во-первых, нотация для доступа к массиву лучше, чем для списка: indexes[i] и indexes.get(i). Во-вторых, использование массива позволяет избежать бокса.

на данный момент каждое значение индекса в массиве (за исключением последнего) на единицу меньше начальной позиции подсписка. Индекс справа от него-это конец подсписка. Мы просто поток по массиву и сопоставить каждую пару индексов в подлист и собирать выходные данные.

Обсуждение

подход потоков немного короче, чем версия for-loop, но он плотнее. Версия for-loop знакома, потому что мы все время делаем это на Java, но если вы еще не знаете, что должен делать этот цикл, это не очевидно. Возможно, вам придется смоделировать несколько циклов, прежде чем вы поймете, что prev делает и почему открытый подлист должен быть закрыт после окончания цикла. (Сначала я забыл его иметь, но я поймал это в тестировании.)

подход ручьи, мне кажется, легче осмыслить, что происходит: вам список (или массив), которая указывает границы между подсписками. Это легкий двухслойный поток. Трудность, как я уже упоминал выше, заключается в том, чтобы найти способ привязать значения ребер к концам. Если бы был лучший синтаксис для этого, например,

    // Java plus pidgin Scala
    int[] indexes =
        [-1] ++ IntStream.range(0, input.size())
                         .filter(i -> input.get(i) == null) ++ [input.size()];

это сделало бы вещи намного менее захламленной. (Что нам действительно нужно, так это понимание массива или списка.) После того, как у вас есть индексы, это простой вопрос, чтобы отобразить их в фактические подсписки и собрать их в список результатов.

и, конечно, это безопасно при параллельном запуске.

обновление 2016-02-06

вот лучший способ создать массив индексов подсписка. Он основан на тех же принципах, но он настраивает диапазон индексов и добавляет некоторые условия в фильтр, чтобы избежать необходимости конкатенации и плоского отображения индексов.

static List<List<String>> splitStream(List<String> input) {
    int sz = input.size();
    int[] indexes =
        IntStream.rangeClosed(-1, sz)
                 .filter(i -> i == -1 || i == sz || input.get(i) == null)
                 .toArray();

    return IntStream.range(0, indexes.length-1)
                    .mapToObj(i -> input.subList(indexes[i]+1, indexes[i+1]))
                    .collect(toList());
}

обновление 2016-11-23

Я совместно представил беседу с Брайаном Гетцем в Devoxx Antwerp 2016, "думая параллельно" (видео), который показал эту проблему и мои решения. Проблемы есть небольшие различия, которые разделяет на "#" Вместо null, но в остальном то же самое. В разговоре я отметил, что у меня была куча тестов на эту проблему. Я добавил их ниже, как автономную программу, вместе с моими реализациями цикла и потоков. Интересное упражнение для читателей-сопоставить решения, предложенные в других ответах, с тестовыми случаями, которые я привел здесь, и посмотреть, какие из них проваливаются и почему. (Другие решения должны быть адаптированы для разделения на основе предиката вместо деления на нуль.)

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import static java.util.Arrays.asList;

public class ListSplitting {
    static final Map<List<String>, List<List<String>>> TESTCASES = new LinkedHashMap<>();
    static {
        TESTCASES.put(asList(),
                  asList(asList()));
        TESTCASES.put(asList("a", "b", "c"),
                  asList(asList("a", "b", "c")));
        TESTCASES.put(asList("a", "b", "#", "c", "#", "d", "e"),
                  asList(asList("a", "b"), asList("c"), asList("d", "e")));
        TESTCASES.put(asList("#"),
                  asList(asList(), asList()));
        TESTCASES.put(asList("#", "a", "b"),
                  asList(asList(), asList("a", "b")));
        TESTCASES.put(asList("a", "b", "#"),
                  asList(asList("a", "b"), asList()));
        TESTCASES.put(asList("#"),
                  asList(asList(), asList()));
        TESTCASES.put(asList("a", "#", "b"),
                  asList(asList("a"), asList("b")));
        TESTCASES.put(asList("a", "#", "#", "b"),
                  asList(asList("a"), asList(), asList("b")));
        TESTCASES.put(asList("a", "#", "#", "#", "b"),
                  asList(asList("a"), asList(), asList(), asList("b")));
    }

    static final Predicate<String> TESTPRED = "#"::equals;

    static void testAll(BiFunction<List<String>, Predicate<String>, List<List<String>>> f) {
        TESTCASES.forEach((input, expected) -> {
            List<List<String>> actual = f.apply(input, TESTPRED);
            System.out.println(input + " => " + expected);
            if (!expected.equals(actual)) {
                System.out.println("  ERROR: actual was " + actual);
            }
        });
    }

    static <T> List<List<T>> splitStream(List<T> input, Predicate<? super T> pred) {
        int[] edges = IntStream.range(-1, input.size()+1)
                               .filter(i -> i == -1 || i == input.size() ||
                                       pred.test(input.get(i)))
                               .toArray();

        return IntStream.range(0, edges.length-1)
                        .mapToObj(k -> input.subList(edges[k]+1, edges[k+1]))
                        .collect(Collectors.toList());
    }

    static <T> List<List<T>> splitLoop(List<T> input, Predicate<? super T> pred) {
        List<List<T>> result = new ArrayList<>();
        int start = 0;

        for (int cur = 0; cur < input.size(); cur++) {
            if (pred.test(input.get(cur))) {
                result.add(input.subList(start, cur));
                start = cur + 1;
            }
        }
        result.add(input.subList(start, input.size()));

        return result;
    }

    public static void main(String[] args) {
        System.out.println("===== Loop =====");
        testAll(ListSplitting::splitLoop);
        System.out.println("===== Stream =====");
        testAll(ListSplitting::splitStream);
    }
}

решение заключается в использовании Stream.collect. Для создания коллектора с помощью его шаблона builder уже дано решение. Альтернативой является другой перегруженный collect будучи немного более примитивно.

    List<String> strings = Arrays.asList("a", "b", null, "c", null, "d", "e");
    List<List<String>> groups = strings.stream()
            .collect(() -> {
                List<List<String>> list = new ArrayList<>();
                list.add(new ArrayList<>());
                return list;
            },
            (list, s) -> {
                if (s == null) {
                    list.add(new ArrayList<>());
                } else {
                    list.get(list.size() - 1).add(s);
                }
            },
            (list1, list2) -> {
                // Simple merging of partial sublists would
                // introduce a false level-break at the beginning.
                list1.get(list1.size() - 1).addAll(list2.remove(0));
                list1.addAll(list2);
            });

как видно, я составляю список списков строк, где всегда есть хотя бы один последний (пустой) список строк.

  • первая функция создает начальный список списков строк. он указывает результат (типизированный) объект.
  • вторая функция вызывается для обработки каждого элемента. это действие на частичный результат и элемент.
  • третий на самом деле не используется, он вступает в игру при распараллеливании обработки, когда частичные результаты должны быть объединены.

решение с аккумулятором:

как указывает @StuartMarks, объединитель не заполняет договор о параллелизме.

из-за комментария @ArnaudDenoyelle версия с использованием reduce.

    List<List<String>> groups = strings.stream()
            .reduce(new ArrayList<List<String>>(),
                    (list, s) -> {
                        if (list.isEmpty()) {
                            list.add(new ArrayList<>());
                        }
                        if (s == null) {
                            list.add(new ArrayList<>());
                        } else {
                            list.get(list.size() - 1).add(s);
                        }
                        return list;
                    },
                    (list1, list2) -> {
                            list1.addAll(list2);
                            return list1;
                    });
  • первым параметром является накопленный объект.
  • вторая функция аккумулирует.
  • третий-вышеупомянутый комбайнера.

пожалуйста, не голосуйте. Мне не хватает места, чтобы объяснить это в комментариях.

это решение с Stream и foreach но это строго эквивалентно решению Алексиса или foreach цикл (и менее понятный, и я не мог избавиться от конструктора копирования):

List<List<String>> result = new ArrayList<>();
final List<String> current = new ArrayList<>();
list.stream().forEach(s -> {
      if (s == null) {
        result.add(new ArrayList<>(current));
        current.clear();
      } else {
        current.add(s);
      }
    }
);
result.add(current);

System.out.println(result);

Я понимаю, что вы хотите найти более элегантное решение с Java 8, но я действительно думаю, что он не был разработан для этого случая. И как сказал мистер Спун, весьма предпочитаю наивный способ в данном случае.


вот еще один подход, который использует функцию группировки, которая использует индексы списка для группировки.

здесь я группирую элемент по первому индексу после этого элемента со значением null. Итак, в вашем примере,"a" и "b" будет соответствовать 2. Кроме того, я mapping null значение -1 индекс, который должен быть удален позже.

List<String> list = Arrays.asList("a", "b", null, "c", null, "d", "e");

Function<String, Integer> indexGroupingFunc = (str) -> {
             if (str == null) {
                 return -1;
             }
             int index = list.indexOf(str) + 1;
             while (index < list.size() && list.get(index) != null) {
                 index++;
             }
             return index;
         };

Map<Integer, List<String>> grouped = list.stream()
               .collect(Collectors.groupingBy(indexGroupingFunc));

grouped.remove(-1);  // Remove null elements grouped under -1
System.out.println(grouped.values()); // [[a, b], [c], [d, e]]

вы также можете избежать получения первого индекса null элемент каждый раз, по кэширование текущего индекса min в AtomicInteger. Обновленный Function будет так:

AtomicInteger currentMinIndex = new AtomicInteger(-1);

Function<String, Integer> indexGroupingFunc = (str) -> {
        if (str == null) {
            return -1;
        }
        int index = names.indexOf(str) + 1;

        if (currentMinIndex.get() > index) {
            return currentMinIndex.get();
        } else {
            while (index < names.size() && names.get(index) != null) {
              index++;
            }
            currentMinIndex.set(index);
            return index;
        }
    };

хотя ответ Маркса Стюарта лаконичный, интуитивно понятный и параллельного просмотра (и лучшей), я хочу поделиться еще одним интересным решением, которое не нуждается в трюке с границами начала/конца.

если мы посмотрим на проблемную область и подумаем о параллелизме, мы можем легко решить эту проблему с помощью стратегии "разделяй и властвуй". Вместо того чтобы думать о проблеме как о последовательном списке, который мы должны пройти, мы можем рассматривать проблему как композицию та же основная проблема: разделение списка на null значение. Мы можем интуитивно легко увидеть, что мы можем рекурсивно разбить проблему со следующей рекурсивной стратегией:

split(L) :
  - if (no null value found) -> return just the simple list
  - else -> cut L around 'null' naming the resulting sublists L1 and L2
            return split(L1) + split(L2)

в этом случае мы сначала ищем любой null значение и момент, мы немедленно сократить список и вызвать рекурсивный вызов на подсписки. Если мы не найдем null (базовый случай), мы закончили с этой веткой и просто возвращаем список. Конкатенация всех результатов будет верните список, который мы ищем.

картина стоит тысячи слов:

enter image description here

алгоритм прост и полон: нам не нужны специальные трюки для обработки крайних случаев начала/конца списка. Нам не нужны специальные трюки для обработки крайних случаев, таких как пустые списки или списки только с null значения. Или списки, заканчивающиеся на null и начиная с null.

простой наивный реализация данной стратегии выглядит следующим образом:

public List<List<String>> split(List<String> input) {

    OptionalInt index = IntStream.range(0, input.size())
                                 .filter(i -> input.get(i) == null)
                                 .findAny();

    if (!index.isPresent())
        return asList(input);

    List<String> firstHalf  = input.subList(0, index.getAsInt());
    List<String> secondHalf = input.subList(index.getAsInt()+1, input.size());

    return asList(firstHalf, secondHalf).stream()
                 .map(this::split)
                 .flatMap(List::stream)
                 .collect(toList());

}

мы сначала ищем индекс любого null значение в списке. Если не найдем, вернем список. Если мы ее найдем, мы разделим список на 2 подсписка, поток над ними и рекурсивного вызова split опять способ. Полученные списки подзадачи затем извлекаются и объединяются для возвращаемого значения.

обратите внимание, что 2 потока можно легко сделать параллельными () и алгоритм все равно будет работать из-за функциональной декомпозиции задачи.

хотя код уже довольно лаконичен, его всегда можно адаптировать различными способами. Например, вместо того, чтобы проверять необязательное значение в базовом случае, мы могли бы воспользоваться orElse метод OptionalInt чтобы вернуть конечный индекс списка, позволяя нам повторно использовать второй поток и дополнительно отфильтровать пустые списки:

public List<List<String>> split(List<String> input) {

    int index =  IntStream.range(0, input.size())
                          .filter(i -> input.get(i) == null)
                          .findAny().orElse(input.size());

    return asList(input.subList(0, index), input.subList(index+1, input.size())).stream()
                 .map(this::split)
                 .flatMap(List::stream)
                 .filter(list -> !list.isEmpty())
                 .collect(toList());
}

пример дается только для того, чтобы указать на простоту, адаптивность и элегантность рекурсивного подхода. Действительно, эта версия введет небольшой штраф за производительность и потерпит неудачу, если вход был пуст (и как таковой может потребоваться дополнительный пустой чек).

в этом случае рекурсия может быть, вероятно, не лучшим решением (Стюарт Марки алгоритм поиска индексов только O (N) и отображение / разделение списков имеет значительную стоимость), но это выражает решение простым, интуитивно понятным распараллеливаемым алгоритмом без каких-либо побочных эффектов.

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


Это очень интересная проблема. Я придумал решение в одну линию. Это может быть не очень эффективно, но это работает.

List<String> list = Arrays.asList("a", "b", null, "c", null, "d", "e");
Collection<List<String>> cl = IntStream.range(0, list.size())
    .filter(i -> list.get(i) != null).boxed()
    .collect(Collectors.groupingBy(
        i -> IntStream.range(0, i).filter(j -> list.get(j) == null).count(),
        Collectors.mapping(i -> list.get(i), Collectors.toList()))
    ).values();

это аналогичная идея, которую придумал @Rohit Jain. Я группирую пространство между значениями null. Если вы действительно хотите List<List<String>> вы можете добавлять:

List<List<String>> ll = cl.stream().collect(Collectors.toList());

Ну, после немного работы U придумали однострочное потоковое решение. Он в конечном счете использует reduce() сделать группировку, которая казалась естественным выбором, но было немного уродливо получать строки в List<List<String>> требует снижения:

List<List<String>> result = list.stream()
  .map(Arrays::asList)
  .map(x -> new LinkedList<String>(x))
  .map(Arrays::asList)
  .map(x -> new LinkedList<List<String>>(x))
  .reduce( (a, b) -> {
    if (b.getFirst().get(0) == null) 
      a.add(new LinkedList<String>());
    else
      a.getLast().addAll(b.getFirst());
    return a;}).get();

Это и однако 1 строка!

при запуске с вводом из вопроса,

System.out.println(result);

выдает:

[[a, b], [c], [d, e]]

здесь код AbacusUtil

List<String> list = N.asList(null, null, "a", "b", null, "c", null, null, "d", "e");
Stream.of(list).splitIntoList(null, (e, any) -> e == null, null).filter(e -> e.get(0) != null).forEach(N::println);

декларация: я разработчик AbacusUtil.


в своем StreamEx библиотека есть groupRuns метод, который может помочь вам решить эту проблему:

List<String> input = Arrays.asList("a", "b", null, "c", null, "d", "e");
List<List<String>> result = StreamEx.of(input)
        .groupRuns((a, b) -> a != null && b != null)
        .remove(list -> list.get(0) == null).toList();

на groupRuns метод принимает BiPredicate что для пары соседних элементов возвращает true, если они должны быть сгруппированы. После этого удаляем группы, содержащие нули, а остальные собираем в список.

это решение является параллельным: вы можете использовать его и для параллельного потока. Также он хорошо работает с любым источником потока (не только списки произвольного доступа, как и в некоторых других решениях), и это несколько лучше, чем решения на основе коллектора, поскольку здесь вы можете использовать любую терминальную операцию без промежуточных отходов памяти.


со строкой можно сделать:

String s = ....;
String[] parts = s.split("sth");

если все последовательные коллекции (поскольку строка является последовательностью символов) имели эту абстракцию, это может быть выполнимо и для них:

List<T> l = ...
List<List<T>> parts = l.split(condition) (possibly with several overloaded variants)

если мы ограничим исходную проблему списком строк (и наложим некоторые ограничения на ее содержимое элементов), мы можем взломать ее следующим образом:

String als = Arrays.toString(new String[]{"a", "b", null, "c", null, "d", "e"});
String[] sa = als.substring(1, als.length() - 1).split("null, ");
List<List<String>> res = Stream.of(sa).map(s -> Arrays.asList(s.split(", "))).collect(Collectors.toList());

(пожалуйста, не принимайте это всерьез, хотя :))

в противном случае, обычная старая рекурсию работает:

List<List<String>> part(List<String> input, List<List<String>> acc, List<String> cur, int i) {
    if (i == input.size()) return acc;
    if (input.get(i) != null) {
        cur.add(input.get(i));
    } else if (!cur.isEmpty()) {
        acc.add(cur);
        cur = new ArrayList<>();
    }
    return part(input, acc, cur, i + 1);
}

(примечание в этом случае null должен быть добавлен к входному списку)

part(input, new ArrayList<>(), new ArrayList<>(), 0)

группировать по другому токену всякий раз, когда вы находите null (или разделитель). Я использовал здесь другое целое число (используется атомарный как держатель)

затем переназначить сгенерированную карту, чтобы преобразовать ее в список списков.

AtomicInteger i = new AtomicInteger();
List<List<String>> x = Stream.of("A", "B", null, "C", "D", "E", null, "H", "K")
      .collect(Collectors.groupingBy(s -> s == null ? i.incrementAndGet() : i.get()))
      .entrySet().stream().map(e -> e.getValue().stream().filter(v -> v != null).collect(Collectors.toList()))
      .collect(Collectors.toList());

System.out.println(x);

Я смотрел видео о параллельном мышлении Стюарта. Поэтому решил решить его, прежде чем увидеть свой ответ на видео. Обновит решение со временем. пока

Arrays.asList(IntStream.range(0, abc.size()-1).
filter(index -> abc.get(index).equals("#") ).
map(index -> (index)).toArray()).
stream().forEach( index -> {for (int i = 0; i < index.length; i++) {
                    if(sublist.size()==0){
                        sublist.add(new ArrayList<String>(abc.subList(0, index[i])));
                    }else{

                    sublist.add(new ArrayList<String>(abc.subList(index[i]-1, index[i])));
                    }
                }
    sublist.add(new ArrayList<String>(abc.subList(index[index.length-1]+1, abc.size())));
});