我尝试做的一个简单示例:
假设我有一个字符串列表,如果包含或不包含特定的子字符串,则需要根据条件将其分为4组。如果字符串包含Foo,则它应位于组Foo中;如果字符串包含Bar,则它应位于组Bar中;如果字符串同时包含Bar,则它应出现在两个组中。
List<String> strings = List.of("Foo", "FooBar", "FooBarBaz", "XXX");
由于字符串被分组到第一个匹配组中,因此上述输入的简单方法无法按预期工作:
Map<String,List<String>> result1 =
strings.stream()
.collect(Collectors.groupingBy(
str -> str.contains("Foo") ? "FOO" :
str.contains("Bar") ? "BAR" :
str.contains("Baz") ? "BAZ" : "DEFAULT"));
结果1是
{FOO=[Foo, FooBar, FooBarBaz], DEFAULT=[XXX]}
其中,预期结果应为
{FOO=[Foo, FooBar, FooBarBaz], BAR=[FooBar, FooBarBaz], BAZ=[FooBarBaz], DEFAULT=[XXX]}
在搜索了一段时间后,我找到了另一种方法,接近我想要的结果,但并不完全
Map<String,List<String>> result2 =
List.of("Foo", "Bar", "Baz", "Default").stream()
.flatMap(str -> strings.stream().filter(s -> s.contains(str)).map(s -> new String[]{str.toUpperCase(), s}))
.collect(Collectors.groupingBy(arr -> arr[0], Collectors.mapping(arr -> arr[1], Collectors.toList())));
System.out.println(result2);
结果2为
{BAR=[FooBar, FooBarBaz], FOO=[Foo, FooBar, FooBarBaz], BAZ=[FooBarBaz]}
虽然这样可以将包含子字符串的字符串正确地分组到所需的组中,但忽略不包含子字符串的字符串,因此应该属于默认组。预期结果如上所述(顺序无关紧要)
{BAR=[FooBar, FooBarBaz], FOO=[Foo, FooBar, FooBarBaz], BAZ=[FooBarBaz], DEFAULT=[XXX]}
目前,我正在使用两个结果图,并做额外的工作:
result2.put("DEFAULT", result1.get("DEFAULT"));
以上可以一步完成吗?有没有比我上面提到的更好的方法?
与使用字符串“Foo”、“Bar”等及其相应的大写版本相比,定义枚举将更方便、更简洁。
让我们称之为Keys
:
public enum Keys {
FOO("Foo"), BAR("Bar"), BAZ("Baz"), DEFAULT("");
private static final Set<Keys> nonDefaultKeys = EnumSet.range(FOO, BAZ); // Set of enum constants (not includes DEFAULT), needed to avoid creating EnumSet or array of constants via `values()` at every invocation of getKeys()
private String keyName;
Keys(String keyName) {
this.keyName = keyName;
}
public static List<String> getKeys(String str) {
List<String> keys = nonDefaultKeys.stream()
.filter(key -> str.contains(key.keyName))
.map(Enum::name)
.toList();
// if non-default keys not found, i.e. keys.isEmpty() - return the DEFAULT
return keys.isEmpty() ? List.of(DEFAULT.name()) : keys;
}
}
它有一个方法getKeys(String)
,它需要一个字符串并返回给定字符串应映射到的键列表。
通过使用封装在键枚举中的功能,我们可以通过使用收集(供应商、累加器、合并器)创建一个字符串映射,这些字符串被分成与键的名称相对应的组。
main()
public static void main(String[] args) {
List<String> strings = List.of("Foo", "FooBar", "FooBarBaz", "XXX");
Map<String, List<String>> stringsByGroup = strings.stream()
.collect(
HashMap::new, // mutable container - which will contain results of mutable reduction
(Map<String, List<String>> map, String next) -> Keys.getKeys(next)
.forEach(key -> map.computeIfAbsent(key, k -> new ArrayList<>()).add(next)), // accumulator function - defines how to store stream elements into the container
(left, right) -> right.forEach((k, v) ->
left.merge(k, v, (oldV, newV) -> { oldV.addAll(newV); return oldV; }) // combiner function - defines how to merge container while executing the stream in parallel
));
stringsByGroup.forEach((k, v) -> System.out.println(k + " -> " + v));
}
输出:
BAR -> [FooBar, FooBarBaz]
FOO -> [Foo, FooBar, FooBarBaz]
BAZ -> [FooBarBaz]
DEFAULT -> [XXX]
指向在线演示的链接
这是使用mapMulti的理想选择。MapMulti接受流式传输值的Bi消费者和消费者。消费者用于简单地将某些东西放回流中。这被添加到Java,因为平面图
可能会产生不希望的开销。
这可以通过构建一个String数组来工作,就像您之前对Token和包含的String和收集所做的那样(也像您之前所做的那样)。如果在字符串中找到了键,请接受带有它和包含字符串的String数组。否则,接受具有默认键和字符串的String数组。
List<String> strings =
List.of("Foo", "FooBar", "FooBarBaz", "XXX", "YYY");
Map<String, List<String>> result = strings.stream()
.<String[]>mapMulti((str, consumer) -> {
boolean found = false;
for (String token : List.of("FOO", "BAR",
"BAZ")) {
if (str.toUpperCase().contains(token)) {
consumer.accept(
new String[] { token, str });
found = true;
}
}
if (!found) {
consumer.accept(
new String[] { "DEFAULT", str });
}
})
.collect(Collectors.groupingBy(arr -> arr[0],
Collectors.mapping(arr -> arr[1],
Collectors.toList())));
result.entrySet().forEach(System.out::println);
打印
BAR=[FooBar, FooBarBaz]
FOO=[Foo, FooBar, FooBarBaz]
BAZ=[FooBarBaz]
DEFAULT=[XXX, YYY]
问题内容: 我正在尝试找到一种将String拆分为String数组的方法,并且每当遇到白色香料时就需要对其进行拆分,例如 “嗨,我是保罗” 进入” “嗨”“我”“保罗” 如何使用RegularExpression在split()方法中表示空格? 问题答案: 您需要一个正则表达式,例如,这意味着: 每当遇到至少一个空格时就进行拆分 。完整的Java代码是:
如何将过滤器列表拆分为单个过滤器元件?split2String在线程“main”java.util.regex中导致:异常。PatternSyntaxException:索引10或(|和)附近的未闭合组(
问题内容: 我有一个值为的字符串。我想将字符串分成两个字符串,值为的字符串和的值为字符串。 正确的功能/语法是什么? 我已经看过了,但是找不到将数据返回到两个单独的字符串中的实际语法。 问题答案: 该功能适用于:
将多行字符串根据行拆分为数组。 使用 String.split() 和一个正则表达式来匹配换行符并创建一个数组。 const splitLines = str => str.split(/\r?\n/); splitLines('This\nis a\nmultiline\nstring.\n'); // ['This', 'is a', 'multiline', 'string.' , '']
问题内容: 我需要将一个String拆分为单个字符String的数组。 例如,拆分“ cat”将得到数组“ c”,“ a”,“ t” 问题答案: 这将产生
我有一个字符串和一个ArrayList。字符串中有几个单词,用空格隔开,例如“firstword second third”。我想将字符串拆分为几个部分,并将“piece”字符串添加到ArrayList中。