it-swarm.com.ru

Java 8 Stream для определения максимального количества в текстовом файле

Для моего задания я должен заменить циклы на потоки, которые считают частоту слов в текстовом документе, и у меня возникают проблемы с вычислением части 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 в поток.

8
user9569944

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

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

Затем наиболее частое слово Word может быть вычислено с использованием приведенной выше 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());
2
Ravindra Ranwala

Хорошо, во-первых, ваша строка 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();

Тем не менее, ваши комментарии заставляют меня думать, что на самом деле вам нужна строковая строка для печати наиболее часто встречающихся слов (всех их) , т. Е. Слов, которые имеют 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());
            });

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

1
walen

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

Единственная проблема заключается в том, что это не очень оптимально, так как 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

1
Eugene

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

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

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

0
user9569944