Java 8 Потоки: подсчитать вхождение элементов (List <String>list1) из списка текстовых данных (List <String>list2) - PullRequest
5 голосов
/ 28 февраля 2020

Входные данные:

List<String> elements= new ArrayList<>();
        elements.add("Oranges");
        elements.add("Figs");
        elements.add("Mangoes");
        elements.add("Apple");

List<String> listofComments = new ArrayList<>();
        listofComments.add("Apples are better than Oranges");
        listofComments.add("I love Mangoes and Oranges");
        listofComments.add("I don't know like Figs. Mangoes are my favorites");
        listofComments.add("I love Mangoes and Apples");

Выходные данные: [Манго, яблоки, апельсины, инжир] -> Выходные данные должны быть в порядке убывания числа вхождений элементов. Если элементы отображаются равными нет. раз они должны быть расположены в алфавитном порядке.

Я новичок в Java 8 и столкнулся с этой проблемой. Я попытался решить это частично; Я не мог разобрать это. Может кто-нибудь помочь мне с лучшим кодом?

Мой код:

Function<String, Map<String, Long>> function = f -> {
            Long count = listofComments.stream()
                    .filter(e -> e.toLowerCase().contains(f.toLowerCase())).count();
            Map<String, Long> map = new HashMap<>(); //creates map for every element. Is it right?
            map.put(f, count);
            return map;
        };

elements.stream().sorted().map(function).forEach(e-> System.out.print(e));

Вывод: {Apple = 2} {Figs = 1} {Mangoes = 3} {Апельсины = 2}

Ответы [ 3 ]

4 голосов
/ 28 февраля 2020

В реальной жизни ios вам следует учитывать, что применение произвольного числа операций сопоставления к произвольному количеству комментариев может стать довольно дорогостоящим при увеличении чисел, поэтому стоит провести некоторую подготовку:

Map<String,Predicate<String>> filters = elements.stream()
    .sorted(String.CASE_INSENSITIVE_ORDER)
    .map(s -> Pattern.compile(s, Pattern.LITERAL|Pattern.CASE_INSENSITIVE))
    .collect(Collectors.toMap(Pattern::pattern, Pattern::asPredicate,
        (a,b) -> { throw new AssertionError("duplicates"); }, LinkedHashMap::new));

Класс Predicate очень полезен, даже если не выполняется сравнение с регулярными выражениями. Комбинация флагов LITERAL и CASE_INSENSITIVE позволяет выполнять поиск по заданному семантику c без необходимости конвертировать целые строки в нижний регистр (что, кстати, недостаточно для всех возможных сценариев ios). Для этого типа соответствия подготовка будет включать построение необходимой структуры данных для алгоритма Бойера-Мура для более эффективного поиска внутри.

Эта карта может быть повторно использована.

Для заданной вами c задачи один из способов ее использования будет

filters.entrySet().stream()
    .map(e -> Map.entry(e.getKey(), listofComments.stream().filter(e.getValue()).count()))
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
    .forEachOrdered(e -> System.out.printf("%-7s%3d%n", e.getKey(), e.getValue()));

, который будет печатать для ваших примеров:

Mangoes  3
Apple    2
Oranges  2
Figs     1

Обратите внимание, что карта filters уже отсортировано в алфавитном порядке, и sorted второй операции потока имеет значение stable для потоков с определенным порядком встреч, поэтому ему нужно только отсортировать по вхождениям записи с равными элементами сохранит их относительный порядок, который является алфавитным порядком исходной карты.

Map.entry(…) требует Java 9 или новее. Для Java 8 вам придется использовать что-то вроде
new AbstractMap.SimpleEntry(…).

0 голосов
/ 28 февраля 2020

Вы все еще можете изменить свою функцию для сохранения Map.Entry вместо полного Map

Function<String, Map.Entry<String, Long>> function = f -> Map.entry(f, listOfComments.stream()
        .filter(e -> e.toLowerCase().contains(f.toLowerCase())).count());

и затем отсортировать эти записи перед выполнением операции терминала forEach в вашем случае для печати

elements.stream()
        .map(function)
        .sorted(Comparator.comparing(Map.Entry<String, Long>::getValue)
                .reversed().thenComparing(Map.Entry::getKey))
        .forEach(System.out::println);

В результате вы получите следующее:

Mangoes=3
Apples=2
Oranges=2
Figs=1
0 голосов
/ 28 февраля 2020

Прежде всего, нужно объявить дополнительный класс. Он будет содержать элемент и считать:

class ElementWithCount {
    private final String element;
    private final long count;

    ElementWithCount(String element, long count) {
        this.element = element;
        this.count = count;
    }

    String element() {
        return element;
    }

    long count() {
        return count;
    }
}

Для вычисления count давайте объявим дополнительную функцию:

static long getElementCount(List<String> listOfComments, String element) {
    return listOfComments.stream()
            .filter(comment -> comment.contains(element))
            .count();
}

Так что теперь, чтобы найти результат, нам нужно преобразовать поток элементов в поток ElementWithCount объектов, затем отсортировать этот поток по количеству, затем преобразовать его обратно в поток элементов и собрать его в список результатов.

Чтобы упростить эту задачу, давайте определим компаратор как отдельную переменную:

Comparator<ElementWithCount> comparator = Comparator
        .comparing(ElementWithCount::count).reversed()
        .thenComparing(ElementWithCount::element);

и теперь, когда все части готовы, окончательное вычисление легко:

List<String> result = elements.stream()
        .map(element -> new ElementWithCount(element, getElementCount(listOfComments, element)))
        .sorted(comparator)
        .map(ElementWithCount::element)
        .collect(Collectors.toList());

Вы можете использовать Map.Entry вместо отдельного класса и встроенного getElementCount, так что Это будет «однострочное» решение:

List<String> result = elements.stream()
        .map(element ->
                new AbstractMap.SimpleImmutableEntry<>(element,
                        listOfComments.stream()
                                .filter(comment -> comment.contains(element))
                                .count()))
        .sorted(Map.Entry.<String, Long>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey()))
        .map(Map.Entry::getKey)
        .collect(Collectors.toList());

Но в этой форме понять гораздо сложнее, поэтому я рекомендую разбить его на логические части.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...