Java 8 Stream для определения максимального количества в текстовом файле - PullRequest
0 голосов
/ 16 октября 2018

Для моего задания я должен заменить циклы на потоки, которые считают частоту слов в текстовом документе, и у меня возникают проблемы с вычислением части TODO.

String filename = "SophieSallyJack.txt";
if (args.length == 1) {
    filename = args[0];
}
Map<String, Integer> wordFrequency = new TreeMap<>();

List<String> incoming = Utilities.readAFile(filename);

wordFrequency = incoming.stream()
    .map(String::toLowerCase)
    .filter(word -> !word.trim().isEmpty())
    .collect(Collectors.toMap(word -> word, word -> 1, (a, b) -> a + b, TreeMap::new));                

int maxCnt = 0;

// TODO add a single statement that uses streams to determine maxCnt
for (String word : incoming) {
    Integer cnt = wordFrequency.get(word);
    if (cnt != null) {
        if (cnt > maxCnt) {
            maxCnt = cnt;
        }
    }
}
System.out.print("Words that appear " + maxCnt + " times:");

Я пробовал это:

wordFrequency = incoming.parallelStream().
    collect(Collectors.toConcurrentMap(w -> w, w -> 1, Integer::sum));

Но это не так, и я не уверен, как включить maxCnt в поток.

Ответы [ 4 ]

0 голосов
/ 16 октября 2018

Собрав воедино информацию, я смог успешно заменить цикл for на

    int maxCnt = wordFrequency.values().stream().max(Comparator.naturalOrder()).get();
    System.out.print("Words that appear " + maxCnt + " times:");

Я ценю всю помощь.

0 голосов
/ 16 октября 2018

Хорошо, во-первых, ваша строка wordFrequency может использовать Collectors#groupingBy и Collectors#counting вместо написания вашего собственного аккумулятора:

    List<String> incoming = Arrays.asList("monkey", "dog", "MONKEY", "DOG", "giraffe", "giraffe", "giraffe", "Monkey");
    wordFrequency = incoming.stream()
            .filter(word -> !word.trim().isEmpty()) // filter first, so we don't lowercase empty strings
            .map(String::toLowerCase)
            .collect(Collectors.groupingBy(s -> s, Collectors.counting()));

Теперь, когда мы получили это с пути... Ваша строка TODO говорит use streams to determine maxCnt.Вы можете сделать это легко, используя max с naturalOrder:

    int maxCnt = wordFrequency.values()
            .stream()
            .max(Comparator.naturalOrder())
            .orElse(0L)
            .intValue();

Однако, ваши комментарии заставляют меня думать, что на самом деле вам нужен однострочный print наиболее часто встречающиеся слова (все) , то есть слова, которые имеют maxCnt в качестве значения в wordFrequency.Итак, нам нужно «перевернуть» карту, сгруппировав слова по количеству, а затем выбрать запись с наибольшим количеством:

    wordFrequency.entrySet().stream() // {monkey=3, dog=2, giraffe=3}
            .collect(groupingBy(Map.Entry::getValue, mapping(Map.Entry::getKey, toList()))).entrySet().stream() // reverse map: {3=[monkey, giraffe], 2=[dog]}
            .max(Comparator.comparingLong(Map.Entry::getKey)) // maxCnt and all words with it: 3=[monkey, giraffe]
            .ifPresent(e -> {
                System.out.println("Words that appear " + e.getKey() + " times: " + e.getValue());
            });

. Это решение печатает все слова с maxCnt, вместо одного:

Words that appear 3 times: [monkey, giraffe].

Конечно, вы можете объединить операторы, чтобы получить один большой универсальный оператор, например:

    incoming.stream() // [monkey, dog, MONKEY, DOG, giraffe, giraffe, giraffe, Monkey]
            .filter(word -> !word.trim().isEmpty()) // filter first, so we don't lowercase empty strings
            .map(String::toLowerCase)
            .collect(groupingBy(s -> s, counting())).entrySet().stream() // {monkey=3, dog=2, giraffe=3}
            .collect(groupingBy(Map.Entry::getValue, mapping(Map.Entry::getKey, toList()))).entrySet().stream() // reverse map: {3=[monkey, giraffe], 2=[dog]}
            .max(Comparator.comparingLong(Map.Entry::getKey)) // maxCnt and all words with it: 3=[monkey, giraffe]
            .ifPresent(e -> {
                System.out.println("Words that appear " + e.getKey() + " times: " + e.getValue());
            });

Но теперь мы расширили значение «одного утверждения»:)

0 голосов
/ 16 октября 2018

Что ж, с этим TreeMap вы сделали почти все, что вам нужно, но, похоже, вы не знаете, что у него есть метод с именем lastEntry, и это единственный метод, который вам нужно вызывать после вычисления wordFrequency чтобы получить слово с самой высокой частотой.

Единственная проблема заключается в том, что это не очень оптимально, поскольку TreeMap сортирует данные по каждой вставке, и вам не нужны отсортированные данные, вам нужен max.Сортировка в случае TreeMap - O(nlogn), а вставка в HashMap - O(n).

Поэтому вместо использования TreeMap все, что вам нужно изменить, - это HashMap:

wordFrequency = incoming.stream()
    .map(String::toLowerCase)
    .filter(word -> !word.trim().isEmpty())
    .collect(Collectors.toMap(
             Function.identity(), 
             word -> 1, 
             (a, b) -> a + b, 
             HashMap::new)); 

Когда у вас есть это Map, вам нужно найти max - эта операция в общем случае O(n) и может быть выполнена с stream или без такового:

 Collections.max(wordFrequency.entrySet(), Map.Entry.comparingByValue())

Этот подход дает вам O(n) для HashMap вставки и O(n) для нахождения максимума - таким образом O(n) в целом, так что это быстрее, чем TreeMap

0 голосов
/ 16 октября 2018

Если у вас есть все слова, извлеченные из файла в List<String>, то это число слов для каждого слова может быть вычислено с использованием этого подхода,

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

Затем можно вычислить наиболее частое слово, используявыше map вот так,

Entry<String, Long> mostFreequentWord = wordToCountMap.entrySet().stream()
    .max(Map.Entry.comparingByValue())
    .orElse(new AbstractMap.SimpleEntry<>("Invalid", 0l));

При желании вы можете изменить два вышеупомянутых конвейера вместе,

Entry<String, Long> mostFreequentWord = words.stream()
    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
    .entrySet().stream()
    .max(Map.Entry.comparingByValue())
    .orElse(new AbstractMap.SimpleEntry<>("Invalid", 0l));

Обновление

В соответствии со следующим обсуждением всегда полезно возвращать Optional из ваших вычислений, например,

Optional<Entry<String, Long>> mostFreequentWord = words.stream()
    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
    .entrySet().stream()
    .max(Map.Entry.comparingByValue());
...