【问题标题】:Java 8 Stream function to group a List of anagrams into a Map of ListsJava 8 Stream 函数将字谜列表分组为列表映射
【发布时间】:2014-03-25 13:52:18
【问题描述】:

Java 8 即将发布...在学习 Streams 时,我遇到了一个关于使用一种新方法对字谜进行分组的场景。我面临的问题是我找不到使用 map/reduce 函数对字符串对象进行分组的方法。相反,我必须创建与 Aggregate Operations - Reduction 中记录的类似方式。

根据文档,我们可以简单地使用:

LIST<T>.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);
    }
}

这将打印以下内容:

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

相反,我正在寻找一种更简单、更直接的解决方案,它使用新的 map/reduce 函数将结果累积到类似的界面 Map&lt;String, List&lt;String&gt; 中。基于How to convert List to Map,我有以下几点:

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。

Exception in thread "main" java.lang.IllegalStateException: Duplicate key pool

这是有道理的...有没有办法将它们分组到与groupingBy 的第一个解决方案类似的输出中,但不使用包装值的 POJO?

【问题讨论】:

    标签: java mapreduce java-8 anagram java-stream


    【解决方案1】:

    单参数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 的多参数重载和“下游”收集器。例如,要计算单词而不是建立列表,请执行以下操作:

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

    【讨论】:

      【解决方案2】:

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

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

      • key - int[] - 单词字符码点的排序数组;
      • 值 - List&lt;String&gt; - 字谜列表;
      • 合并功能 - 两个列表合而为一;
      • 映射 - 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?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-20
        • 2019-04-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多