Reputation: 135
So here is the situation: I need to register people's vote for certain dates. In short, a date is proposed and people vote for the date they want.
The data structure is the following:
private HashMap<LocalDateTime, Set<Vote>> votes;
A vote is:
public class Vote {
private String name;
private VoteType vote;
public Vote(String name, VoteType vote) {
super();
this.name = name;
this.vote = vote;
}
}
Where VoteType is just an enum:
public enum VoteType {YES, NO, MAYBE}
Now I already made a stream that returns the amount of votes for the availability (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())));
}
So my question is: How can I get, using Java Streams, the date that got the most 'YES'.
/* Returns the date that got the most 'YES' votes */
public LocalDateTime winningDate() {
// TODO
}
Thank you for the help!
Upvotes: 9
Views: 238
Reputation: 54639
The question asked about how to solve this "using Java Streams". The following is using streams. And a for
-loop.
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;
}
}
Compare this to the "pure-stream" solution, and think about which code you'd rather like to read, understand and maintain in the future. Then choose wisely.
(I know that this may, strictly speaking, not be the desired answer to the question. But some people seem to intentionally over-use streams and derive some sort of geeky pride from that. I also enjoy that as a challenge occasionally. But imagining that I might be the one who has to maintain these abominations of functional programming in the future makes me shudder...)
Upvotes: 2
Reputation: 34460
You asked how to do it with streams, here's another way:
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;
And to get the set of votes:
Set<Vote> maxVotesForYes = votes.get(maxDate);
This solution iterates the map entries and counts YES
votes for each date. If this count is greater than the current max count, the max count (along with its corresponding date) is changed.
In order to be able to modify the max count and its corresponding date, we need a local class Max
that keeps track of these values (otherwise, we wouldn't be able to change variables from within a lambda).
Upvotes: 3
Reputation: 56423
So my question is: How can I get, using Java Streams, the date that got the most 'YES'.
This is going to be a lengthy one...
Stream<LocalDateTime>
so we can later group by date applying a counting
downstream collector to get the number of votes on that specific date and we can accomplish this structure via flatMap
.YES
YES
votes on that specific date.entrySet
and find the max
date by voteCode:
/* 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>
, I could have returned
.map(Map.Entry::getKey).orElse(null)
thus you've be able to maintain your current method return type of LocalDateTime
but that just feels bad and so
I've decided to defer the decision upon what to do in the "no value
case" to the client.getWinningDate
to enhance readability.As for dealing with Optional<T>
, in your case, if you want to have a null
value in the case of getWinningDate
returning an empty Optional, you can unwrap it safely as:
LocalDateTime winningDate = getWinningDate().orElse(null);
or if you want to provide a default date:
LocalDateTime winningDate = getWinningDate().orElse(defaultDate);
or if you're sure there will always be a result then simply call get()
.
LocalDateTime winningDate = getWinningDate().get();
etc..
Upvotes: 6
Reputation: 316
Use your first method which counts up the YES votes, returns a map of the yes counts which is passed into the winning date method:
/* 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();
}
I can't help but thinking this was the intention here, but what do I know.
Upvotes: 4
Reputation: 56423
This answer shows a way to do it without your voteCount
method but just in case you may want to write some logic in your winningDate
method that would integrate with your already made voteCount
method.
in which case we can do:
/* 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)
method to get a mapping of dates and the number of YES
votes on the date.LocalDateTime
by the vote countOptional<LocaleDateTime>
, I could have returned
.map(Map.Entry::getKey).orElse(null)
thus you'll be able to maintain
your current method return type of LocalDateTime
but that just feels
bad and so I've decided to defer the decision upon what to do in the
"no value case" to the client.getWinningDate
to enhance
readability.Further, the voteCount
method can be improved to:
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()));
}
this avoids the overhead of constructing a list of all the elements that pass the filter simply to retrieve the count via size()
, instead just filter
and invoke count
.
Upvotes: 4
Reputation: 2149
You can do it this way:
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);
}
Upvotes: 4