Как считать слова на карте с помощью потока

#java #dictionary #lambda #collections #java-stream

Вопрос:

Я работаю с List<String> … это большой текст. Текст выглядит так:

 List<String> lines = Arrays.asList("The first line", "The second line", "Some words can repeat", "The first the second"); //etc
 

Мне нужно вычислить в нем слова с выводом:

 first - 2
line - 2
second - 2
can - 1
repeat - 1
some - 1
words - 1

 

Слова короче 4 символов следует пропускать, поэтому «the» и «can» не отображаются в выводе. Здесь я написал пример, но изначально, если слово редкое и запись Затем отсортируйте карту по ключам в алфавитном порядке.
Используя только потоки, без конструкций «если», «в то время как» и «для».

То, что я реализовал:

 Map<String, Integer> wordCount = Stream.of(list)
                .flatMap(Collection::stream)
                .flatMap(str -> Arrays.stream(str.split("\p{Punct}| |[0-9]|…|«|»|“|„")))
                .filter(str -> (str.length() >= 4))
                .collect(Collectors.toMap(
                        i -> i.toLowerCase(),
                        i -> 1,
                        (a, b) -> java.lang.Integer.sum(a, b))
                );
 

WordCount содержит карту со словами и ее записями. Но как я могу пропустить редкие слова? Должен ли я создать новый поток? Если да, то как я могу получить значение карты? Я пробовал это, но это неправильно:

  String result = Stream.of(wordCount)
         .filter(i -> (Map.Entry::getValue > 10));
 

Мои вычисления должны возвращать строку:

 "word" - number of entries
 

Спасибо!

Комментарии:

1. Было бы полезно, если бы вы могли предоставить пример ввода и ожидаемый результат. Ваш подход до сих пор вызывает у меня только вопросы. Это пахнет как ксипроблема

2. может быть, если бы я мог угадать правильно, вам нужно, чтобы map эти записи String join были объединены в одну для вывода.

3. я добавил некоторые вводные и исходные данные в текст проблемы

Ответ №1:

Учитывая поток, который уже сделан:

 List<String> lines = Arrays.asList(
        "For the rabbit, it was a bad day.",
        "An Antillean rabbit is very abundant.",
        "She put the rabbit back in the cage and closed the door securely, then ran away.",
        "The rabbit tired of her inquisition and hopped away a few steps.",
        "The Dean took the rabbit and went out of the house and away."
);

Map<String, Integer> wordCounts = Stream.of(lines)
        .flatMap(Collection::stream)
        .flatMap(str -> Arrays.stream(str.split("\p{Punct}| |[0-9]|…|«|»|“|„")))
        .filter(str -> (str.length() >= 4))
        .collect(Collectors.toMap(
                String::toLowerCase,
                i -> 1,
                Integer::sum)
        );

System.out.println("Original:"   wordCounts);
 

Исходный вывод:

 Original:{dean=1, took=1, door=1, very=1, went=1, away=3, antillean=1, abundant=1, tired=1, back=1, then=1, house=1, steps=1, hopped=1, inquisition=1, cage=1, securely=1, rabbit=5, closed=1}
 

Вы можете сделать:

 String results = wordCounts.entrySet()
        .stream()
        .filter(wordToCount -> wordToCount.getValue() > 2) // 2 is rare
        .sorted(Map.Entry.comparingByKey()).map(wordCount -> wordCount.getKey()   " - "   wordCount.getValue())
            .collect(Collectors.joining(", "));

System.out.println(results);
 

Отфильтрованный вывод:

 away - 3, rabbit - 5
 

Комментарии:

1. В выводе используется стандартный символ «=» между ключом и Значением. Как я могу сделать вывод в виде строки: away — 3, rabbit — 5 ?

2. @CatherinZetaJones Я изменил код, чтобы отразить это.

Ответ №2:

Вы не можете исключить любые значения, которые меньше, чем rare до тех пор, пока вы не вычислите количество частот.

Вот как я мог бы это сделать.

  • сделайте подсчет частоты (я решил сделать это немного иначе, чем вы).
  • затем передайте в потоковом режиме набор записей карты и отфильтруйте значения, меньшие определенной частоты.
  • затем восстановите карту, используя a TreeMap , чтобы отсортировать слова в лексическом порядке
 List<String> list = Arrays.asList(....);

int wordRarity = 10; // minimum frequency to accept
int wordLength = 4; // minimum word length to accept
        
Map<String, Long> map = list.stream()
        .flatMap(str -> Arrays.stream(
                str.split("\p{Punct}|\s |[0-9]|…|«|»|“|„")))
        .filter(str -> str.length() >= wordLength)
        .collect(Collectors.groupingBy(String::toLowerCase, 
                Collectors.counting()))
        // here is where the rare words are filtered out.
        .entrySet().stream().filter(e->e.getValue() > wordRarity)
        .collect(Collectors.toMap(Entry::getKey, Entry::getValue,
                (a,b)->a,TreeMap::new));
    }
 

Обратите внимание, что (a,b)->a лямбда-функция является функцией слияния для обработки дубликатов и не используется. К сожалению, невозможно указать поставщика, не указав функцию слияния.

Самый простой способ распечатать их заключается в следующем:

 map.entrySet().forEach(e -> System.out.printf("%s - %s%n",
                e.getKey(), e.getValue()));
 

Комментарии:

1. Хотя ваше регулярное выражение будет отлично работать при разделении, вы, возможно, захотите изменить его на \p{Punct}|\s |[0-9]|…|«|»|“|„ Это, измените значение | | на |\s | и, таким образом, разделитесь на один или несколько пробелов (включая вкладки). Это приводит к тому, что пустые значения не передаются потоком, вызванным соседними пробелами. Вы можете проверить это, просто разделив некоторые слова пробелами и увидев разницу.