当前位置: 首页 > 知识库问答 >
问题:

使用stream groupingBy时将字符串分组为多个组

曹理
2023-03-14

我尝试做的一个简单示例:

假设我有一个字符串列表,如果包含或不包含特定的子字符串,则需要根据条件将其分为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"));

以上可以一步完成吗?有没有比我上面提到的更好的方法?

共有2个答案

黄凌龙
2023-03-14

与使用字符串“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]

指向在线演示的链接

江丰羽
2023-03-14

这是使用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中。