Java 8 - 统计字数,然后按降序排列

Java 8 - Count of words and then arrange in desc order

我有一个单词列表,说

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

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

最难的部分是排序。由于您只想保留结果中的前 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 次,所以它应该在 [=16= 之前]

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

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;

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

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

有时候解决一个问题最好的不是算法,而是数据结构。我想你在这里需要的是一个包。由于您希望输出按出现次数排序,然后按键排序,因此您应该使用的特定数据结构是 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 的提交者。

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

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