【问题标题】:Java 8 - Count of words and then arrange in desc orderJava 8 - 字数,然后按 desc 顺序排列
【发布时间】:2017-02-14 04:24:51
【问题描述】:

我有一个单词列表,比如说

List<String> words = Arrays.asList("Hello alan i am here where are you"+  
  "and what are you doing hello are you there");

如何按降序获取列表中重复多次的前七个单词?然后单词应按字母顺序排列。所以上面的输出应该是前七个单词

you (3)
are (2)
hello (2)
alan (1)
am (1)
and (1)
doing (1)

我希望在 Java 8 中使用流来执行此操作,lamda。

我正在尝试这种方式。 首先对列表进行排序 其次,获取单词表及其单词列表中的单词数。

List<String> sortedWords = Arrays.asList("Hello alan i am here where are you and what are you doing hello you there".split(" "))
            .stream().sorted().collect(toList());

Map<String, Long> collect = 
            sortedWords.stream().collect(groupingBy(Function.identity(), counting()));

【问题讨论】:

    标签: java java-8 java-stream collectors


    【解决方案1】:

    最困难的部分是排序。由于您只想保留结果中的前 7 个元素,并且希望按其值对 Map 进行排序,因此我们需要创建一个包含所有结果的 Map,对其进行排序,然后保留 7 个结果。

    在下面的代码中,每个单词都是小写的,并自行分组,计算出现次数。然后,我们需要对这个映射进行排序,以便在条目上创建一个 Stream,根据值(按降序)对它们进行排序,然后根据键对其进行排序。保留前 7 个元素,映射到它们的键(对应于单词)并收集到 List 中,从而保持相遇顺序。

    public static void main(String[] args) {
        String sentence = "Hello alan i am here where are you and what are you doing hello are you there";
        List<String> words = Arrays.asList(sentence.split(" "));
    
        List<String> result = 
                words.stream()
                     .map(String::toLowerCase)
                     .collect(groupingBy(identity(), counting()))
                     .entrySet().stream()
                     .sorted(Map.Entry.<String, Long> comparingByValue(reverseOrder()).thenComparing(Map.Entry.comparingByKey()))
                     .limit(7)
                     .map(Map.Entry::getKey)
                     .collect(toList());
    
        System.out.println(result);
    }
    

    输出:

    [are, you, hello, alan, am, and, doing]
    

    请注意,您在想要的输出中犯了一个错误:"are" 实际上像 "you" 一样出现了 3 次,所以它应该在之前

    注意:这段代码假设了很多静态导入,即:

    import static java.util.Comparator.reverseOrder;
    import static java.util.function.Function.identity;
    import static java.util.stream.Collectors.counting;
    import static java.util.stream.Collectors.groupingBy;
    import static java.util.stream.Collectors.toList;
    

    【讨论】:

    • 我的偏好是使这两个显式流管道,而不是在“中间”执行 .entrySet().stream(),因为它更清楚实际发生了什么——您正在执行两个不同的流操作。这增强了可读性而没有任何运行时成本。 (更一般地说,虽然方法链接很简单,但它很容易被过度使用——仅仅因为你可以链接,并不意味着你必须一直这样做。)
    • @Brain,我会接受这个,因为它会更易读
    【解决方案2】:

    虽然@Tunaki 解决方案很棒,但有趣的是,使用my StreamEx library,可以在单个 Stream 管道中解决问题(在调用单个终端操作之前不会执行实际操作):

    Map<String, Long> map = StreamEx.of(words)
        .map(String::toLowerCase)
        .sorted() // sort original words, so now repeating words are next to each other
        .runLengths() // StreamEx feature: squash repeating words into Entry<String, Long>
        .sorted(Entry.<String, Long> comparingByValue().reversed()
                     .thenComparing(Entry.comparingByKey()))
        .limit(7) // Sort and limit
        .toCustomMap(LinkedHashMap::new); // Single terminal operation: store to LinkedHashMap
    

    或者如果只需要单词:

    List<String> list =StreamEx.of(words)
        .map(String::toLowerCase)
        .sorted() // sort original words, so now repeating words are next to each other
        .runLengths() // StreamEx feature: squash repeating words into Entry<String, Long>
        .sorted(Entry.<String, Long> comparingByValue().reversed()
                     .thenComparing(Entry.comparingByKey()))
        .limit(7) // Sort and limit
        .keys() // Drop counts leaving only words
        .toList(); // Single terminal operation: store to List
    

    【讨论】:

    • 我阅读您的答案越多,我就越相信您的 StreamEx 库首先应该在 API 中!
    • @Tagir,太好了,我会检查你的 StreamEx。
    【解决方案3】:

    有时问题的最佳解决方案不是算法,而是数据结构。我想你需要的是一个包。由于您希望输出按出现次数然后按键排序,因此您应该使用的特定数据结构是TreeBag。以下代码将使用 Eclipse Collections 和 Java 8 流:

    String string =
        "Hello alan i am here where are you and what are you doing hello are you there";
    List<ObjectIntPair<String>> pairs =
        Stream.of(string.toLowerCase().split(" "))
            .collect(Collectors.toCollection(TreeBag::new))
            .topOccurrences(7);
    System.out.println(pairs);
    

    这段代码将输出:

    // Strings with occurrences
    [are:3, you:3, hello:2, alan:1, am:1, and:1, doing:1, here:1, i:1, there:1, what:1, where:1]
    

    topOccurrences() 方法具有处理关系的逻辑,这基本上让开发人员决定他们希望如何处理关系情况。如果您想要此列表中的前七个项目,则可以将调用链接到 .take(7);

    代码还可以进一步简化为:

    List<ObjectIntPair<String>> pairs =
        TreeBag.newBagWith(string.split(" ")).topOccurrences(7);
    System.out.println(pairs);
    

    静态工厂方法TreeBag.newBagWith() 接受可变参数,因此您可以直接将String.split() 的结果传递给它。

    注意:我是 Eclipse Collections 的提交者。

    【讨论】:

      【解决方案4】:

      我是一个简单的人,所以我会先使用Map&lt;String, Integer&gt; 来计算每个单词。 然后为每个计数创建一个TreeSet,并将它们存储在TreeMap&lt;Integer, TreeSet&gt; 中。从那里应该相当简单。

      【讨论】:

        【解决方案5】:

        两步解决方案:分组/计数,然后按计数降序处理

        List<String> words = Arrays.asList("Hello alan i am here where are you and what are you doing hello you there".split(" "));
        
        Map<String, Long> collect = words.stream()
                .map(String::toLowerCase) // convert to lower case
                .collect( // group and count by name
                        Collectors.groupingBy(Function.identity(), Collectors.counting()));
        
        collect.keySet().stream()
                .sorted( // order by count descending, then by name
                        Comparator
                                .comparing(collect::get)
                                .reversed()
                                .thenComparing(Collator.getInstance()))
                .map(k -> k + " (" + collect.get(k) + ")") // map to name and count string
                .limit(7) // only first 7 entries
                .forEach(System.out::println); // output
        

        【讨论】:

          猜你喜欢
          • 2020-09-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-09-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多