List <String>获取以另一个列表中的一个字符串结尾的所有元素的计数

del*_*ica 6 java partitioning java-stream

假设我有一个包含以下元素的列表:

List<String> endings= Arrays.asList("AAA", "BBB", "CCC", "DDD");
Run Code Online (Sandbox Code Playgroud)

我还有一个很大的字符串列表,从中我想从上面的列表中选择所有以任何字符串结尾的元素。

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

理想情况下,我想对第二个列表进行分区,以使其包含四个组,每个组仅包含那些以第一个列表中的一个字符串结尾的元素。因此,在上述情况下,结果将是4组,每组2个元素。

我找到了这个示例,但是我仍然缺少可以过滤包含在不同列表中的所有结尾的部分。

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

更新:MC Emperor的答案确实有效,但是它在包含数百万个字符串的列表上崩溃,因此在实践中效果不佳。

MC *_*ror 5

更新资料

这与原始答案中的方法类似,但是现在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())));
Run Code Online (Sandbox Code Playgroud)

原始答案

您可以使用此:

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));
Run Code Online (Sandbox Code Playgroud)

它采用所有结尾,并遍历fullList以该值结尾的for元素。

请注意,使用这种方法,对于每个元素,它遍历整个列表。这是相当低效的,我认为您最好使用另一种方式映射元素。例如,如果您对中的元素结构有所了解fullList,则可以立即对其进行分组。


Hol*_*ger 5

要对流进行分区,意味着将每个元素放入两个组之一。由于后缀更多,因此您希望分组,即使用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());
};
Run Code Online (Sandbox Code Playgroud)

逻辑类似于字符串的自然顺序,唯一的区别是它从末尾开始。换句话说,它等效于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");
Run Code Online (Sandbox Code Playgroud)

您可以按照以下方式执行任务

// 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));
Run Code Online (Sandbox Code Playgroud)

但是,如果您只对每个组的计数感兴趣,则在分组时应该正确计数,避免存储元素列表:

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

如果列表可以包含与列表后缀不匹配的字符串,则必须替换suffixes::floors -> { String g = suffixes.floor(s); return g!=null && s.endsWith(g)? g: "_None"; }或类似的函数。


Eri*_*ean 2

如果您的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);
Run Code Online (Sandbox Code Playgroud)