为 IntStream 生成直方图 Map 引发编译时错误

Producing histogram Map for IntStream raises compile-time-error

我有兴趣构建霍夫曼编码原型。为此,我想首先生成构成输入 Java String 的字符的直方图。我在 SO 和其他地方看到过很多解决方案(例如: 依赖于对 Stream 使用 collect() 方法以及 Function.identity()Collectors.counting() 以一种非常具体和直观的方式。

但是,当使用一段与我在上面链接的代码非常相似的代码时:

private List<HuffmanTrieNode> getCharsAndFreqs(String s){
        Map<Character, Long> freqs = s.chars().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        return null;
}

我从 Intellij 收到一个编译时错误,它基本上告诉我 collect 没有符合 Supplier 类型的参数,正如其签名所要求的那样:

不幸的是,我是 Java 8 Stream 层次结构的新手,我不完全确定对我来说最好的行动方案应该是什么。事实上,Map 方式对于我想要做的事情来说可能是太多的样板;如果是这样请告知。

String.chars() 方法 returns 和 IntStream。您可能想通过以下方式将其转换为 Stream<Character>

s.chars().mapToObj(c -> (char)c)

如前所述,您可以将流转换为原始类型再转换为对象类型。

s.chars().boxed()
 .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

问题是 s.chars() returns 一个 IntStream - Stream 的一个特定专业,它没有一个 collect 需要一个争论;它的 collect 有 3 个参数。显然,您可以使用 boxed 并将 IntStream 转换为 Stream<Integer>

Map<Integer, Long> map = yourString.codePoints()
          .boxed()
          .collect(Collectors.groupingBy(
                      Function.identity(), 
                      Collectors.counting()));

但现在的问题是您计算的是 code-points 而不是字符。如果您绝对知道您的字符串是由 BMP 中的字符组成的,您可以安全地转换为 char,如其他答案所示。如果你不是 - 事情会变得更加棘手。

在那种情况下,您需要将单个 unicode 代码点作为字符获取 - 但它 可能 不适合 Java char - 有 2 个字节;一个 unicode 字符最多可以有 4 个字节。

在这种情况下,您的地图应该是 Map<String, Long> 而不是 Map<Character, Long>

在 java-9 中引入了受支持的 \X(和 Scanner#findAll),这很容易做到:

 String sample = "A" + "\uD835\uDD0A" + "B" + "C";
         Map<String, Long> map = scan.findAll("\X")
               .map(MatchResult::group)
               .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));


 System.out.println(map); // {A=1, B=1, C=1, =1}

在 java-8 中,这会有点冗长:

    String sample = "AA" + "\uD835\uDD0A" + "B" + "C";
    Map<String, Long> map = new HashMap<>();

    Pattern p = Pattern.compile("\P{M}\p{M}*+");
    Matcher m = p.matcher(sample);

    while (m.find()) {
        map.merge(m.group(), 1L, Long::sum);
    }
    System.out.println(map); // {A=2, B=1, C=1, =1}