Java 8/JDK8 Stream的函数Map/Reduce将List <String>分组为Map <String,List <String >>

Mar*_*les 10 java mapreduce anagram java-8 java-stream

Java 8即将发布......在学习Streams的过程中,我进入了一个关于使用一种新方法对字谜进行分组的场景.我面临的问题是我找不到使用map/reduce函数对Strings对象进行分组的方法.相反,我必须创建一个与http://docs.oracle.com/javase/tutorial/collections/streams/reduction.html中记录的类似的方式.

根据文档,我们可以简单地使用LIST.stream().collect(Collectors.groupingBy(POJO :: GET_METHOD)),这样Collectors.groupingBy()将根据使用的方法聚合地图的键.但是,这种方法似乎很难包装简单的String表示.

public class AnagramsGrouping {

  static class Word {

    public String original;

    public Word(String word) {
      original = word;
    }

    public String getKey() {
      char[] characters = input.toCharArray();
      Arrays.sort(characters);
      return new String(characters);
    }

    public String toString() {
      return original;
    };
  }

  public static void main(String[] args) {
    List<Word> words = Arrays.asList(new Word("pool"), new Word("loop"), 
         new Word("stream"), new Word("arc"), new Word("odor"),
         new Word("car"), new Word("rood"), new Word("meats"),
         new Word("fires"), new Word("fries"), new Word("night"),
         new Word("thing"), new Word("mates"), new Word("teams"));

    Map<String, List<Word>> anagrams = words.stream().collect(
           Collectors.groupingBy(Word::getKey));

    System.out.println(anagrams);

    // This prints the following:

    {door=[odor, rood], acr=[arc, car], ghint=[night, thing],
     aemrst=[stream], efirs=[fires, fries], loop=[pool, loop],
     aemst=[meats, mates, teams]}
Run Code Online (Sandbox Code Playgroud)

相反,我正在寻找一种更简单,更直接的解决方案,它使用新的map/reduce函数将结果累积到类似的接口Map中.根据/sf/answers/1462142321/,我有以下内容:

List<String> words2 = Arrays.asList("pool", "loop", "stream", "arc",
    "odor", "car", "rood", "meats", "fires", "fries",
    "night", "thing", "mates", "teams");

words2.stream().collect(Collectors.toMap(w -> sortChars(w), w -> w));
Run Code Online (Sandbox Code Playgroud)

但是这段代码生成了一个密钥冲突,因为它是一个1-1的Map."线程中的异常"主"java.lang.IllegalStateException:重复的密钥池",这是有意义的...有没有办法将它们分组到与groupingBy的第一个解决方案类似的输出中,但没有使用包装值的POJO ?

Stu*_*rks 19

单参数groupingBy收集器完全符合您的要求.它对您已经使用sortChars(或getKey在前面的示例中)完成的输入进行了分类.分类在相同密钥下的每个流值都被放入一个列表中,该列表是映射的值.因此我们有:

Map<String, List<String>> anagrams =
    words2.stream().collect(Collectors.groupingBy(w -> sortChars(w)));
Run Code Online (Sandbox Code Playgroud)

给出输出

{door=[odor, rood], acr=[arc, car], ghint=[night, thing], aemrst=[stream],
efirs=[fires, fries], loop=[pool, loop], aemst=[meats, mates, teams]}
Run Code Online (Sandbox Code Playgroud)

您还可以使用方法参考:

Map<String, List<String>> anagrams =
    words2.stream().collect(Collectors.groupingBy(GroupingAnagrams::sortChars));
Run Code Online (Sandbox Code Playgroud)

如果要对除构建列表之外的值执行某些操作,请使用多arg重载groupingBy和"下游"收集器.例如,要计算单词而不是构建列表,请执行以下操作:

Map<String, Long> anagrams =
    words2.stream().collect(
        Collectors.groupingBy(GroupingAnagrams::sortChars, Collectors.counting()));
Run Code Online (Sandbox Code Playgroud)

这导致:

{door=2, acr=2, ghint=2, aemrst=1, efirs=2, loop=2, aemst=3}
Run Code Online (Sandbox Code Playgroud)

编辑:

如果不清楚,sortChars只是一个静态函数,它执行与getKey第一个例子中的函数类似的函数,但是从字符串到字符串:

public static String sortChars(String input) {
    char[] characters = input.toCharArray();
    Arrays.sort(characters);
    return new String(characters);
}
Run Code Online (Sandbox Code Playgroud)