Java 8 / JDK8 Stream的函数Map / Reduce将List <string>分组为Map <string,list <string =“”>&gt; </string,> </string>

时间:2014-02-24 04:01:59

标签: java mapreduce java-8 anagram 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]}

相反,我正在寻找一种更简单,更直接的解决方案,它使用新的map / reduce函数将结果累积到类似的接口Map中。基于https://stackoverflow.com/a/20887747/433814,我有以下内容:

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));

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

2 个答案:

答案 0 :(得分:19)

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

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

给出输出

{door=[odor, rood], acr=[arc, car], ghint=[night, thing], aemrst=[stream],
efirs=[fires, fries], loop=[pool, loop], aemst=[meats, mates, teams]}

您还可以使用方法参考:

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

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

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

这导致:

{door=2, acr=2, ghint=2, aemrst=1, efirs=2, loop=2, aemst=3}

修改

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

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

答案 1 :(得分:0)

您可以使用带有四个参数的 toMap 方法并分别指定:键类型、值类型、具有相同键的值的合并函数,以及 Map 的具体实现,其中结果将被插入。

在这种情况下,您可以选择:

  • key - int[] - 单词的字符代码点的排序数组;
  • value - List<String> - 字谜列表;
  • 合并功能——两个列表合二为一;
  • map - TreeMap 带有比较两个 int[] 数组的比较器。
List<String> words = List.of("pool", "loop", "stream", "arc", "odor", "car",
        "rood", "meats", "fires", "fries", "night", "thing", "mates", "teams");
Map<int[], List<String>> anagrams = words.stream()
        .collect(Collectors.toMap(
                // key - a sorted array of character code points
                word -> word.codePoints().sorted().toArray(),
                // value - a list of anagrams
                word -> new ArrayList<>(List.of(word)),
                // merge elements of two lists
                (list1, list2) -> {
                    list1.addAll(list2);
                    return list1;
                },
                // comparator that compares two int[] arrays
                () -> new TreeMap<>(Arrays::compare)));
// output
anagrams.forEach((k, v) -> System.out.println(v.get(0) + "=" + v));

输出:

arc=[arc, car]
stream=[stream]
meats=[meats, mates, teams]
odor=[odor, rood]
fires=[fires, fries]
night=[night, thing]
pool=[pool, loop]

另见:How do you check if a word has an anagram that is a palindrome?