如何使用 Stream 从 Java 中的字符串中提取三元组

how to extract a trigrams from a string in Java with Stream

假设我有一个 String = "abcabc"。三字母组都是三个字母的唯一连续组,在这种情况下

abc
bca
cab

我想提取它们并将它们放入地图中,最好是对它们进行计数。 所以我会得到

abc, 2
bca, 1
cab, 1

我想用 Streams 来做,而不是用循环。

我的想法是

  1. 将字符串转换为 HashMap:(索引,3 个字符的字符串)
  2. 对地图值流进行收集、分组和计数

但我无法将其放入代码中...

我想到了这样的东西:

        String testString = "abcabc";
        //I can't get point 1...this doesn't compile
        Map trigrams = IntStream.range(0, testString.length()-2).collect(Collectors.toMap(i->i, testString.substring(i,i+3));

        //point 2 seems to work
        //Map<Integer,String> trigrams =  Map.of(0,"abc", 1, "bca",2,"cab",3,"abc");
        
        List<String> trigramsList = trigrams.values().stream().collect(Collectors.toList());
        Map<String, Long> result = trigramsList.stream()
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

另外我对IntStream不太信服,也许还有别的办法。

这可以稍微简化。

import java.util.Map;
import java.util.function.Function;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.*;

public class Main {

    public static void main(String[] args) {
        String s = "a";

        Map<String, Long> map = IntStream.range(0, s.length() - 2)
                .mapToObj(i -> s.substring(i, i + 3))
                .collect(groupingBy(Function.identity(), counting()));

        System.out.println(map);
    }
}

但是,请记住,如果第二个索引大于字符串的长度或第二个索引小于第一个,substring() 方法将抛出异常。这仅适用于 IntStream.range() 按升序生成数字。所以,IntStream.range(0, -2) returns 一个空流。

如果您明确检查字符串中至少有 3 个字符会更好。

一种可能是使用 Guava Mutliset:

Mutliset trigrams = IntStream.range(0, testString.length()-2)
    .mapToObj(i->testString.substring(i,i+3))
    .collect(Collectors.toCollection(Mutltiset::new));

如果您不想使用 Google 库,可以这样做:

Map trigrams = IntStream.range(0, testString.length()-2)
    .mapToObj(i->testString.substring(i,i+3))
    .collect(Collectors.groupingBy(x->x, Collectors.counting()));

流很可能:

    String testString = "abcabc";
    Map<String, Long> trigramCnt = 
            // range through index 0-3
            IntStream.rangeClosed(0, testString.length() - 3)
            // create substrings abc (idx 0-2), bca (idx 1-3), cab (idx 2-4), abc (idx 3-5)    
            .mapToObj(i -> testString.substring(i, i + 3))
            // count them
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
    System.out.println(trigramCnt);
    //-> {bca=1, abc=2, cab=1}