List <String>получить количество всех элементов, заканчивающихся одной строкой из другого списка - PullRequest
4 голосов
/ 26 сентября 2019

Допустим, у меня есть один список с такими элементами, как:

List<String> endings= Arrays.asList("AAA", "BBB", "CCC", "DDD");

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

List<String> fullList= Arrays.asList("111.AAA", "222.AAA", "111.BBB", "222.BBB", "111.CCC", "222.CCC", "111.DDD", "222.DDD");

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

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

Map<Boolean, List<String>> grouped = fullList.stream().collect(Collectors.partitioningBy((String e) -> !e.endsWith("AAA")));

ОБНОВЛЕНИЕ: Ответ MC Emperor действительно работает, но происходит сбой в списках, содержащих миллионы строк, поэтому на практике не работает так хорошо.

Ответы [ 7 ]

5 голосов
/ 26 сентября 2019

Обновление

Этот метод аналогичен подходу из исходного ответа, но теперь fullList больше не пересекается много раз.Вместо этого он просматривается один раз, и для каждого элемента в списке окончаний выполняется поиск совпадения.Это сопоставляется с Entry(ending, fullListItem), а затем группируется по элементу списка.При группировании элементы значения развертываются в List.

Map<String, List<String>> obj = fullList.stream()
    .map(item -> endings.stream()
        .filter(item::endsWith)
        .findAny()
        .map(ending -> new AbstractMap.SimpleEntry<>(ending, item))
        .orElse(null))
    .filter(Objects::nonNull)
    .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));

Оригинальный ответ

Вы можете использовать это:

Map<String, List<String>> obj = endings.stream()
    .map(ending -> new AbstractMap.SimpleEntry<>(ending, fullList.stream()
        .filter(str -> str.endsWith(ending))
        .collect(Collectors.toList())))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Требуетсявсе окончания и обходы fullList для элементов, заканчивающихся значением.

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

5 голосов
/ 26 сентября 2019

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

Если вы хотите поддерживать произвольный список endings, вы можете предпочесть что-толучше, чем линейный поиск.

Один из подходов - использование отсортированной коллекции с использованием компаратора на основе суффикса.

Компаратор может быть реализован как

Comparator<String> backwards = (s1, s2) -> {
    for(int p1 = s1.length(), p2 = s2.length(); p1 > 0 && p2 > 0;) {
        int c = Integer.compare(s1.charAt(--p1), s2.charAt(--p2));
        if(c != 0) return c;
    }
    return Integer.compare(s1.length(), s2.length());
};

Логикапохож на естественный порядок строк, с той лишь разницей, что он проходит от конца к началу.Другими словами, это эквивалентно Comparator.comparing(s -> new StringBuilder(s).reverse().toString()), но более эффективно.

Затем, учитывая такой ввод, как

List<String> endings= Arrays.asList("AAA", "BBB", "CCC", "DDD");
List<String> fullList= Arrays.asList("111.AAA", "222.AAA",
        "111.BBB", "222.BBB", "111.CCC", "222.CCC", "111.DDD", "222.DDD");

, вы можете выполнить задачу как

// prepare collection with faster lookup
TreeSet<String> suffixes = new TreeSet<>(backwards);
suffixes.addAll(endings);

// use it for grouping
Map<String, List<String>> map = fullList.stream()
    .collect(Collectors.groupingBy(suffixes::floor));

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

Map<String, Long> map = fullList.stream()
    .collect(Collectors.groupingBy(suffixes::floor, Collectors.counting()));

Если список может содержать строки, которые не соответствуют суффиксу списка, вы должны заменить suffixes::floor на s -> { String g = suffixes.floor(s); return g!=null && s.endsWith(g)? g: "_None"; } или аналогичную функцию.

3 голосов
/ 26 сентября 2019

Использование groupingBy .

Map<String, List<String>> grouped = fullList
  .stream()
  .collect(Collectors.groupingBy(s -> s.split("\\.")[1]));

s.split("\\.")[1] займет ггг часть ххх.ггг .

РЕДАКТИРОВАТЬ: если вы хотите очистить значения, для которых окончание отсутствует в списке, вы можете отфильтровать их:

grouped.keySet().forEach(key->{
  if(!endings.contains(key)){
    grouped.put(key, Collections.emptyList());
  }
});
2 голосов
/ 26 сентября 2019

Если вы создадите вспомогательный метод getSuffix(), который принимает String и возвращает его суффикс (например, getSuffix("111.AAA") вернет "AAA"), вы можете отфильтровать String с суффиксом, содержащимся в другом списке.и затем сгруппируйте их:

Map<String,List<String>> grouped =
    fullList.stream()
            .filter(s -> endings.contains(getSuffix(s)))
            .collect(Collectors.groupingBy(s -> getSuffix(s)));

Например, если suffix всегда начинается с индекса 4, вы можете получить:

public static String getSuffix(String s) {
    return s.substring(4);
}

и указанный выше конвейер Stream вернетMap:

{AAA=[111.AAA, 222.AAA], CCC=[111.CCC, 222.CCC], BBB=[111.BBB, 222.BBB], DDD=[111.DDD, 222.DDD]}

PS обратите внимание, что шаг filter будет более эффективным, если вы измените endings List на HashSet.

1 голос
/ 26 сентября 2019

Вы можете использовать groupingBy с фильтром в списке endings как,

fullList.stream()
  .collect(groupingBy(str -> endings.stream().filter(ele -> str.endsWith(ele)).findFirst().get()))
1 голос
/ 26 сентября 2019

Если в вашем fullList есть некоторые элементы с суффиксами, которых нет в вашем endings, вы можете попробовать что-то вроде:

    List<String> endings= Arrays.asList("AAA", "BBB", "CCC", "DDD");
    List<String> fullList= Arrays.asList("111.AAA", "222.AAA", "111.BBB", "222.BBB", "111.CCC", "222.CCC", "111.DDD", "222.DDD", "111.EEE");
    Function<String,String> suffix = s -> endings.stream()
                                                 .filter(e -> s.endsWith(e))
                                                 .findFirst().orElse("UnknownSuffix");
    Map<String,List<String>> grouped = fullList.stream()
                                               .collect(Collectors.groupingBy(suffix));
    System.out.println(grouped);
0 голосов
/ 26 сентября 2019

Можно использовать groupingBy подстрок с filter, чтобы гарантировать, что конечный Map имеет только Collection соответствующих значений.Это может быть записано как:

Map<String, List<String>> grouped = fullList.stream()
        .collect(Collectors.groupingBy(a -> getSuffix(a)))
        .entrySet().stream()
        .filter(e -> endings.contains(e.getKey()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

private static String getSuffix(String a) {
    return a.split(".")[1];
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...