Получение набора с большинством элементов, вложенных в HashMap с использованием потоков Java - PullRequest
0 голосов
/ 14 декабря 2018

Итак, вот ситуация: мне нужно зарегистрировать голосование людей на определенные даты.Короче говоря, предлагается дата, и люди голосуют за желаемую дату.

Структура данных следующая:

private HashMap<LocalDateTime, Set<Vote>> votes;

Голосование:

public class Vote {
    private String name;
    private VoteType vote;

    public Vote(String name, VoteType vote) {
        super();
        this.name = name;
        this.vote = vote;
    }
}

Где VoteType - просто перечисление:

public enum VoteType {YES, NO, MAYBE}

Теперь я уже создал поток, который возвращает количество голосов за доступность (VoteType):

public Map<LocalDateTime, Integer> voteCount(VoteType targetVote) {
    return this.votes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new Integer(
            e.getValue().stream().filter(v -> v.getVote() == targetVote).collect(Collectors.toList()).size())));
}

Итак, мой вопрос:Как я могу получить, используя Java Streams, дату, которая получила наибольшее количество «ДА».

/* Returns the date that got the most 'YES' votes */
public LocalDateTime winningDate() {
    // TODO
}

Спасибо за помощь!

Ответы [ 6 ]

0 голосов
/ 14 декабря 2018

Был задан вопрос о том, как решить эту проблему "с помощью потоков Java" .Следующий - это с использованием потоков.И for - петля.

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;


public class VoteCountTest
{
    public static void main(String[] args)
    {
        Map<LocalDateTime, Set<Vote>> votes = 
            new LinkedHashMap<LocalDateTime, Set<Vote>>();

        Set<Vote> yes0 = votesWith(VoteType.NO, VoteType.NO);
        Set<Vote> yes1 = votesWith(VoteType.YES, VoteType.NO);
        Set<Vote> yes2 = votesWith(VoteType.YES, VoteType.YES);

        votes.put(LocalDateTime.of(2000, 1, 1, 1, 1), yes1);
        votes.put(LocalDateTime.of(2000, 1, 2, 1, 1), yes0);
        votes.put(LocalDateTime.of(2000, 1, 3, 1, 1), yes2);
        votes.put(LocalDateTime.of(2000, 1, 4, 1, 1), yes1);

        System.out.println(getWinningDateA(votes));
        System.out.println(getWinningDateB(votes));
    }

    public static Optional<LocalDateTime> getWinningDateA(
        Map<LocalDateTime, Set<Vote>> votes)
    {
        LocalDateTime bestDate = null;
        long maxCount = -1;
        Predicate<Vote> votedYes = v -> v.getVote() == VoteType.YES;
        for (Entry<LocalDateTime, Set<Vote>> entry : votes.entrySet())
        {
            long count = entry.getValue().stream().filter(votedYes).count(); 
            if (count > maxCount)
            {
                maxCount = count;
                bestDate = entry.getKey();
            }
        }
        return Optional.ofNullable(bestDate);
    }

    // As of https://stackoverflow.com/a/53771478/3182664
    public static Optional<LocalDateTime> getWinningDateB(Map<LocalDateTime, Set<Vote>> votes) 
    {
        return votes.entrySet() // Set<Entry<LocaleDateTime, Set<Vote>>
                .stream() // Stream<Entry<LocaleDateTime, Set<Vote>>
                .flatMap(e -> e.getValue().stream().filter(a -> a.getVote() == VoteType.YES)
                             .map(x -> e.getKey())) // Stream<LocalDateTime>
               .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) // Map<LocaleDateTime, Long>
               .entrySet() // Set<Entry<LocaleDateTime, Long>>
               .stream() // Stream<Entry<LocaleDateTime, Long>>
               .max(Comparator.comparingLong(Map.Entry::getValue)) // Optional<Entry<LocaleDateTime, Long>>
               .map(Map.Entry::getKey); // Optional<LocalDateTime>
    }    


    //=========================================================================
    enum VoteType {YES, NO, MAYBE}

    static class Vote {
        private String name;
        private VoteType vote;

        public Vote(String name, VoteType vote) {
            super();
            this.name = name;
            this.vote = vote;
        }
        public VoteType getVote()
        {
            return vote;
        }
    }

    private static Set<Vote> votesWith(VoteType... voteTypes)
    {
        Set<Vote> votes = new LinkedHashSet<Vote>();
        for (int i = 0; i < voteTypes.length; i++)
        {
            votes.add(new Vote("v" + i, voteTypes[i]));
        }
        return votes;
    }

}

Сравните это с «чистым потоком» и подумайте, какой код вы бы хотели читать, понимать и поддерживать в будущем.Тогда выбирайте мудро.

(Я знаю, что, строго говоря, это не может быть желаемым ответом на вопрос. Но некоторые люди, похоже, намеренно используют потоки с чрезмерным использованием и получают своего рода гикигордость от этого. Я также иногда наслаждаюсь этим как вызовом * 1015. * Но воображение, что I может быть тем, кто должен поддерживать эти мерзости функционального программирования в будущем, заставляет меня содрогаться ....)

0 голосов
/ 14 декабря 2018

Вы спросили, как это сделать с потоками, вот еще один способ:

class Max { long value = Long.MIN_VALUE; LocalDateTime date; }
Max max = new Max();
votes.forEach((d, vs) -> {
    long count = vs.stream().filter(v -> VoteType.YES == v.getVote()).count();
    if (count > max.value) {
        max.value = count;
        max.date = d;
    }
});

LocalDateTime maxDate = max.date;

И чтобы получить набор голосов:

Set<Vote> maxVotesForYes = votes.get(maxDate);

Это решение выполняет итерации записей на карте и считаетYES голосов за каждую дату.Если это число больше текущего максимального значения, максимальное значение (вместе с соответствующей датой) изменяется.

Чтобы иметь возможность изменить максимальное количество и соответствующую ему дату, нам нужен локальный классMax, который отслеживает эти значения (в противном случае мы не сможем изменять переменные из лямбды).

0 голосов
/ 14 декабря 2018

Используйте свой первый метод, который подсчитывает голоса «да», возвращает карту подсчета «да», которая передается в метод даты победы:

/* Returns the date that got the most 'YES' votes */
public LocalDateTime winningDate(Map<LocalDateTime, Integer> yesVotes) {
    return yesVotes.entrySet().stream().max(Map.Entry.comparingByValue()).get().getKey();
}

Я не могу не думать, что это было намерениездесь, но что я знаю.

0 голосов
/ 14 декабря 2018

Этот ответ показывает способ сделать это без вашего voteCount метода, но на случай, если может захочет написать некоторую логику в вашем winningDate методе, который бы интегрировался сваш уже созданный метод voteCount.

, в этом случае мы можем сделать:

/* Returns the date that got the most 'YES' votes */
public Optional<LocalDateTime> getWinningDate() {
    return voteCount(VoteType.YES).entrySet() // call voteCount and stream over the entries
            .stream()
            .max(Comparator.comparingLong(Map.Entry::getValue))
            .map(Map.Entry::getKey);
}
  • сначала мы вызываем метод voteCount(VoteType.YES), чтобы получить сопоставление дат и числаиз YES голосов на дату.
  • секунду мы находим максимальное значение LocalDateTime по подсчету голосов
  • обратите внимание, что я изменил тип возвращаемого метода на Optional<LocaleDateTime>, я мог бывернули .map(Map.Entry::getKey).orElse(null), таким образом, вы сможете сохранить текущий тип возврата метода LocalDateTime, но это просто плохо, и поэтому я решил отложить решение о том, что делать в «случае без значения», доклиент.
  • Я изменил имя метода на getWinningDate, чтобы улучшить читаемость.

Кроме того, метод voteCount можно улучшить до:

public Map<LocalDateTime, Long> voteCount(VoteType targetVote) {
        return this.votes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, 
                e -> e.getValue().stream().filter(v -> v.getVote() == targetVote).count()));
}

это позволяет избежать затрат на создание списка всех элементов, которые передают фильтр просто в rнаберите счет с помощью size(), вместо этого просто filter и вызовите count.

0 голосов
/ 14 декабря 2018

Итак, мой вопрос: как я могу получить, используя Java Streams, дату, которая получила наибольшее «ДА».

Это будет очень долго ...

  1. нам нужно попасть на позицию, где у нас есть Stream<LocalDateTime>, чтобы мы могли позже сгруппировать по дате, применяя нисходящий коллектор counting, чтобы получить количество голосов на эту конкретную дату, и мы можемвыполнить эту структуру через flatMap.
  2. нам нужно сохранить только те объекты, где тип голосования равен YES
  3. , мы сгруппировали результаты по дате и получили значения в виде числаYES голосует в эту конкретную дату.
  4. мы транслируем entrySet и находим max дату голосования

Код:

/* Returns the date that got the most 'YES' votes */
public Optional<LocalDateTime> getWinningDate() {
    return votes.entrySet() // Set<Entry<LocaleDateTime, Set<Vote>>
            .stream() // Stream<Entry<LocaleDateTime, Set<Vote>>
            .flatMap(e -> e.getValue().stream().filter(a -> a.getVote() == VoteType.YES)
                         .map(x -> e.getKey())) // Stream<LocalDateTime>
           .collect(groupingBy(Function.identity(), counting())) // Map<LocaleDateTime, Long>
           .entrySet() // Set<Entry<LocaleDateTime, Long>>
           .stream() // Stream<Entry<LocaleDateTime, Long>>
           .max(Comparator.comparingLong(Map.Entry::getValue)) // Optional<Entry<LocaleDateTime, Long>>
           .map(Map.Entry::getKey); // Optional<LocalDateTime>
}
  • обратите внимание, что я изменил тип возврата метода на Optional<LocaleDateTime>, я мог бы вернуть .map(Map.Entry::getKey).orElse(null), таким образом, вы сможете сохранить текущий тип возврата метода LocalDateTime, но это просто плохо и такЯ решил отложить решение о том, что делать в «бесполезном случае» для клиента.
  • Я изменил имя метода на getWinningDate, чтобы улучшить читаемость.

Что касается работы с Optional<T>, в вашем случае, если вы хотите иметьЗначение null в случае getWinningDate возврата пустого значения Необязательно, вы можете безопасно развернуть его как:

LocalDateTime winningDate = getWinningDate().orElse(null);

или если вы хотите указать дату по умолчанию:

LocalDateTime winningDate = getWinningDate().orElse(defaultDate);

или если вы уверены, что результат всегда будет, просто позвоните get().

LocalDateTime winningDate = getWinningDate().get();

и т.д ..

0 голосов
/ 14 декабря 2018

Вы можете сделать это следующим образом:

private LocalDateTime winningDate(Map<LocalDateTime, Integer> mapGroup) {
    Integer max = mapGroup
                    .values().stream()
                    .max(Comparator.naturalOrder())
                    .get();

    return mapGroup
                    .entrySet()
                    .stream()
                    .filter(e -> e.getValue().equals(max))
                    .map(Map.Entry::getKey)
                    .findFirst().orElse(null);
}
...