List<String> 获取以另一个列表中的一个字符串结尾的所有元素的计数
List<String> get count of all elements ending with one of strings from another list
假设我有一个包含以下元素的列表:
List<String> endings= Arrays.asList("AAA", "BBB", "CCC", "DDD");
我还有另一个大的字符串列表,我希望从中 select 所有以上述列表中的任何字符串结尾的元素。
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's Answer 确实有效,但它在包含数百万个字符串的列表上崩溃,因此在实践中效果不佳。
如果您创建一个辅助方法 getSuffix()
接受 String
和 return 它的后缀(例如 getSuffix("111.AAA")
将 return "AAA"
), 你可以筛选出另一个列表中包含后缀的String
s,然后将它们分组:
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
管道将 return Map
:
{AAA=[111.AAA, 222.AAA], CCC=[111.CCC, 222.CCC], BBB=[111.BBB, 222.BBB], DDD=[111.DDD, 222.DDD]}
P.S。请注意,如果将 endings
List
更改为 HashSet
.
,filter
步骤会更有效
使用groupingBy.
Map<String, List<String>> grouped = fullList
.stream()
.collect(Collectors.groupingBy(s -> s.split("\.")[1]));
s.split("\.")[1]
将采用 xxx.yyy.
的 yyy 部分
编辑:如果你想清空结尾不在列表中的值,你可以过滤掉它们:
grouped.keySet().forEach(key->{
if(!endings.contains(key)){
grouped.put(key, Collections.emptyList());
}
});
更新
这个和原来答案的做法类似,但是现在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
中元素的结构有所了解,那么您可以立即对其进行分组。
如果您的 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);
可以使用 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];
}
您可以将 groupingBy
与 endings
列表中的过滤器一起使用,
fullList.stream()
.collect(groupingBy(str -> endings.stream().filter(ele -> str.endsWith(ele)).findFirst().get()))
要对流进行分区,意味着将每个元素放入两个组中的一个。由于您有更多后缀,因此您需要 grouping 代替,即使用 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"; }
或类似函数。
假设我有一个包含以下元素的列表:
List<String> endings= Arrays.asList("AAA", "BBB", "CCC", "DDD");
我还有另一个大的字符串列表,我希望从中 select 所有以上述列表中的任何字符串结尾的元素。
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's Answer 确实有效,但它在包含数百万个字符串的列表上崩溃,因此在实践中效果不佳。
如果您创建一个辅助方法 getSuffix()
接受 String
和 return 它的后缀(例如 getSuffix("111.AAA")
将 return "AAA"
), 你可以筛选出另一个列表中包含后缀的String
s,然后将它们分组:
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
管道将 return Map
:
{AAA=[111.AAA, 222.AAA], CCC=[111.CCC, 222.CCC], BBB=[111.BBB, 222.BBB], DDD=[111.DDD, 222.DDD]}
P.S。请注意,如果将 endings
List
更改为 HashSet
.
filter
步骤会更有效
使用groupingBy.
Map<String, List<String>> grouped = fullList
.stream()
.collect(Collectors.groupingBy(s -> s.split("\.")[1]));
s.split("\.")[1]
将采用 xxx.yyy.
编辑:如果你想清空结尾不在列表中的值,你可以过滤掉它们:
grouped.keySet().forEach(key->{
if(!endings.contains(key)){
grouped.put(key, Collections.emptyList());
}
});
更新
这个和原来答案的做法类似,但是现在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
中元素的结构有所了解,那么您可以立即对其进行分组。
如果您的 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);
可以使用 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];
}
您可以将 groupingBy
与 endings
列表中的过滤器一起使用,
fullList.stream()
.collect(groupingBy(str -> endings.stream().filter(ele -> str.endsWith(ele)).findFirst().get()))
要对流进行分区,意味着将每个元素放入两个组中的一个。由于您有更多后缀,因此您需要 grouping 代替,即使用 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"; }
或类似函数。