Java HashMap Get Values (the most of which value) - java

Hi so a strange question...
Let's start with a simple description of the code:
I have a hashmap filled with <PlayerUUID, KingdomUUID>
Where the playerUUID is the key in the hashmap (obviously)
Now for my capture process system I need to determine how many of the diffrent values there is and thus which has the most.
For example:
3 players are attacking a point, 2 players are in ORION kingdom, 1 in Erion.
I need to check the values of the hashmap to see which of the kingdoms has the most attackers. (ORION is the answer)
Regards,
Thomas
I hope this description was good enough if not ask away!

You can do what f1sh did. However, Java 8 added streams and lambdas, which you can use as well. The resulting code is more compact, more readable, and less error prone. This code will be a bit slower, however you probably won't even feel it unless you deal with a lot of values.
public KingdomUUID getMax(HashMap<PlayerUUID, KingdomUUID> inputMap) {
return inputMap.entrySet()
.stream()
.max((entry1, entry2) -> entry1.getValue() > entry2.getValue() ? 1 : -1)
.get()
.getValue();
}
More about Java 8 streams.
More about Java 8 lambdas.

Use this method:
Entry<KingdomUUID, Integer> getMax(Map<PlayerUUID, KingdomUUID> input) {
Map<KingdomUUID, Integer> r = new HashMap<>();
for(KingdomUUID kingdom:input.values()){
final Integer old = r.get(kingdom);
r.put(kingdom, old==null?1:old+1);
}
Map.Entry<KingdomUUID, Integer> max = null;
for(Map.Entry<KingdomUUID, Integer> e:r.entrySet()){
if(max==null || e.getValue()>max.getValue()){
max = e;
}
}
return max;
}
You can use getKey() on the return object to see which KingdomUUID occurs most often and and getValue() how many times.

Related

Problem witn putIfAbsent() when modifying a stream

Please help me to figure out what is wrong with the method bellow and how can I solve it. The method takes a stream of Person object and Map with String value (a task name) as a key and an int value (a mark). The purpose of the method is to check whether a stream contains the particular tasks from allHistoryTasks variable and if does apply to this Map .putIfAbsentmethod(taskName, 0) to ensure that all the tasks are present in the Map (the purpose is to calculate an average mark later).
When I run the test the UnsupportedOperationException error apears. When I comment the lines from the if statement and to forEach (lines 1, 2, 3, 4) test runs well. I'm new to Java and already spent several days on this issue but still can't solve it. Please tell me what is wrong here.
private Set<String> allHistoryTasks = Set.of("Phalanxing", "Shieldwalling", "Tercioing", "Wedging");
private String[] historyTasks = allHistoryTasks.toArray(new String[0]);
public Map<Person, Map<String, Integer>> addHistoryIfPresent(Stream<CourseResult> stream) {
return stream.collect(Collectors.toMap(
CourseResult::getPerson,
x -> {
if (allHistoryTasks.containsAll(x.getTaskResults().keySet())) //1
IntStream.range(0, allHistoryTasks.size()) //2
.parallel() //3
.forEach(i -> x.getTaskResults().putIfAbsent(historyTasks[i], 0)); //4
return x.getTaskResults();
}
));
}
custom classes & thread report
The x -> {} block is the 'value mapper'. It is supposed to turn an element of your stream into the value for a given map.
You have a stream of CourseResult objects, and want a Map<Person, Map<String, Integer>>, so this function turns a CourseResultobject into aMap<String, Integer>`.
You do this by mutating things and that is a biiig nono. Your stream functions should not have any side-effects. Fortunately, the author of CourseResult is way ahead of you and blocked you from making this error. You are calling .getTaskResults() on your course result object and then trying to modify it. You can't do that, as the getTaskResults() method returns a map that cannot be modified.
Presumably, you want to clone that map, and fix the clone. How do you do that? Well, you tell me, the API isn't clear. You could simply make a new ImmutableMap.builder(), loop through whatever you want to loop through, and so on. From your code it's not quite clear what end map you do want.
Note also that you're using powers without knowing what you're doing - you have a parallel stream and are then forEaching through it, mutating the same variable, which you absolutely cannot do: This results in bugs where the result of an operation depends on an evil coin flip, in the sense that it can work fine today even if you rerun the tests a million times, and fail tomorrow. Separately, using parallel() for such talks is borderline crazy - assuming the underlying stream impl actually parallelizes (.parallel() is a hint, not a demand), it would just slow everything waaay down. allHistoryTasks is tiny. This isn't what parallelism would be for.
This might be the answer to your question. Set.of method won't return the mutable set. So you need to declare a mutrable set like this to avoid this problem.
private Set<String> allHistoryTasks = new HashSet<>(Arrays.asList("Phalanxing", "Shieldwalling", "Tercioing", "Wedging"));
private String[] historyTasks = allHistoryTasks.toArray(new String[0]);
public Map<Person, Map<String, Integer>> addHistoryIfPresent(Stream<CourseResult> stream) {
return stream.collect(Collectors.toMap(
CourseResult::getPerson,
x -> {
if (allHistoryTasks.containsAll(x.getTaskResults().keySet())) //1
IntStream.range(0, allHistoryTasks.size()) //2
.parallel() //3
.forEach(i -> x.getTaskResults().putIfAbsent(historyTasks[i], 0)); //4
return x.getTaskResults();
}
));
}

Alternative to multiple nested for and if blocks

I'm trying to implement a complicated block that I've written using multiple for loops and if conditions to something much less convoluted. The initial code is
for(Coll_Monthly_Main monthlyAccount:monthlyList){
for(Coll_Daily_Main dailyAccount:dailyList){
if(monthlyAccount.getAccountId().trim().equals(dailyAccount.getAccountId().trim())){
for(Catg_Monthly monthlyCategory: monthlyAccount.getCatg()){
for(Catg_Daily dailyCategory: dailyAccount.getCatg()){
if(monthlyCategory.getPriCatId().trim().equals(dailyCategory.getPriCatId().trim())){
monthlyCategory.setMthTtl(dailyCategory.getMthTtl());
monthlyCategory.setMtd(dailyCategory.getMtd());
monthlyCategory.setYtd(dailyCategory.getYtd());
for(SecCatDtl_Mthly monthlySecCategory:monthlyCategory.getSecCatDtl()){
for(SecCatDtl_Daily dailySecCategory:dailyCategory.getSecCatDtl()){
if(monthlySecCategory.getCatId().trim().equals(dailySecCategory.getCatId().trim())){
monthlySecCategory.setMthTtl(dailySecCategory.getMthTtl());
monthlySecCategory.setMtd(dailySecCategory.getMtd());
monthlySecCategory.setYtd(dailySecCategory.getYtd());
}
}
}
}
}
}
}
}
}
return monthlyList;
I've followed this answer and have managed to implement the first level as below:-
monthlyList.forEach(coll_mthly->{
dailyList.stream().filter(coll_daily->coll_mthly.getAccountId().trim().equals(coll_daily.getAccountId().trim()))
.forEach(catg_mth->coll_mthly.getCatg())->{
};
});
For the next level of nesting, I need to loop over a nested list and I'm not sure how to proceed about it. I keep getting a syntax error as follows:-
Syntax error on tokens, TypeElidedFormalParameter expected instead
I'd appreciate any pointers in the right direction.
Update:-
This is how it looks like following Thomas' answer
Map<String, Coll_Daily_Main> dailies = dailyList.stream().collect(Collectors.toMap(cdm -> cdm.getAccountId(), cdm-> cdm) );
for(Coll_Monthly_Main monthlyAccount : monthlyList) {
Coll_Daily_Main dailiesForAccount = dailies.get( monthlyAccount.getAccountId().trim());
Map<String, Catg_Daily> dailyCatgories=dailiesForAccount.getCatg().stream().collect(Collectors.toMap(cv->cv.getPriCatId(), cv->cv));
for(Catg_Monthly monthlyCategory:monthlyAccount.getCatg()){
Catg_Daily dailyCategory = dailyCatgories.get(monthlyCategory.getPriCatId().trim());
if(dailyCategory!=null){
monthlyCategory.setMthTtl(dailyCategory.getMthTtl());
monthlyCategory.setMtd(dailyCategory.getMtd());
monthlyCategory.setYtd(dailyCategory.getYtd());
Map<String,SecCatDtl_Daily> dailySecCategories=dailyCategory.getSecCatDtl().stream().collect(Collectors.toMap(fg->fg.getCatId(), fg->fg));
for(SecCatDtl_Mthly monthlySecCategory:monthlyCategory.getSecCatDtl()){
SecCatDtl_Daily dailySecCategory =dailySecCategories.get(monthlySecCategory.getCatId().trim());
if(dailySecCategory!=null){
monthlySecCategory.setMthTtl(dailySecCategory.getMthTtl());
monthlySecCategory.setMtd(dailySecCategory.getMtd());
monthlySecCategory.setYtd(dailySecCategory.getYtd());
}
}
}
}
}
As the others already stated multiple times it's most likely better to rethink your approach and make it not even more readable but faster as well. One thing that comes to my mind: you have 3 levels that consist of 2 loops and an if to check whether the elements match (by id). Those levels will have O(n*m) complexity.
However, you could try to build a map or multimap (Guava has some) with the id as the key and get it down to O(n + m):
O(n) for building the map (ideally on on the larger set, i.e. daily)
O(m) for iterating over the second set (ideally the smaller set, i.e. monthly)
Lookups should be O(1) so can be ignored
I'm not sure what all those nested levels mean so I only can give an example of what you could do for one level (I'll take the first):
//I'm using Google Guava's classes here
SetMultimap<String, Coll_Daily_Main> dailies = ...;//use one that fits your needs
//Iterate over n daily entries and put them into the map which should be O(n)
dailyList.forEach( cdm -> dailies.put( cdm.getAccountId().trim(), cdm ) );
//Iterate over the (smaller) set of monthly entries and do a lookup for the dailies which should be O(m)
for(Coll_Monthly_Main monthlyAccount : monthlyList) {
Set<Coll_Daily_Main> dailiesForAccount = dailies.get( monthlyAccount.getAccountId().trim() );
//level done, either go down to the next or try to further straighten it out or optimize
}
Update:
I forgot to mention that you'd not have to use Guava with Java 8. Though the definition looks a little more awkward using a Map<String, Set<Coll_Daily_Main>> isn't that "hard" anymore:
Map<String, Set<String>> dailies = new HashMap<>();
dailyList.forEach( cdm -> dailies.computeIfAbsent( cdm.getAccountId().trim(), v -> new HashSet<>() ).add( cdm ) );
Note: you could also use collectors to make it a little shorter and in one line. Whether that's easier to read and use is up to debate.
Map<String, Set<Daily>> dailies =
dailyList.stream().collect( Collectors.groupingBy( cdm -> cdm.getAccountId().trim(),
Collectors.toSet() ) );
First you need to extract all your if statements in private methods.
Then you can start to refactor your for statements with lambdas.
You could even declare a static function (called loop in the example below) to export your nested loops logic:
public class Test {
public List<Coll_Monthly_Main> runThatThing(List<Coll_Monthly_Main> monthlyList, List<Coll_Daily_Main> dailyList) {
loop(monthlyList, dailyList, Test::updateMonthlyCategories);
return monthlyList;
}
private static void updateMonthlyCategories(Coll_Monthly_Main monthlyAccount, Coll_Daily_Main dailyAccount) {
if(monthlyAccount.getAccountId().trim().equals(dailyAccount.getAccountId().trim())){
loop(monthlyAccount.getCatg(), dailyAccount.getCatg(), Test::updateMonthlyCategory);
}
}
private static void updateMonthlyCategory(Catg_Monthly monthlyCategory, Catg_Daily dailyCategory) {
if(monthlyCategory.getPriCatId().trim().equals(dailyCategory.getPriCatId().trim())){
monthlyCategory.setMthTtl(dailyCategory.getMthTtl());
monthlyCategory.setMtd(dailyCategory.getMtd());
monthlyCategory.setYtd(dailyCategory.getYtd());
loop(monthlyCategory.getSecCatDtl(), dailyCategory.getSecCatDtl(), Test::updateMonthlySecondCategory);
}
}
private static void updateMonthlySecondCategory(SecCatDtl_Mthly monthlySecCategory, SecCatDtl_Daily dailySecCategory) {
if(monthlySecCategory.getCatId().trim().equals(dailySecCategory.getCatId().trim())){
monthlySecCategory.setMthTtl(dailySecCategory.getMthTtl());
monthlySecCategory.setMtd(dailySecCategory.getMtd());
monthlySecCategory.setYtd(dailySecCategory.getYtd());
}
}
// nested loops through list1 and list2 which apply the function `f` to all pairs.
//Using a BiConsumer because the f methods we use always return void
private static <T, U> void loop(List<T> list1, List<U> list2, BiConsumer<T, U> f) {
list1.forEach(
element1 -> list2.forEach(
element2 -> f.accept(element1, element2)
));
}
}

Convert OptionalDouble to Optional <java.lang.Double>

I have a method that builds a list and I want it to return the average of the list as an Optional value.
However, when I calculate the average value using Java 8, I always get the return value as an OptionalDouble.
How do I convert
OptionalDouble to Optional<Double>?
Below are my code for average calculation:
private static Optional<Double> averageListValue() {
// Build list
List<Double> testList = new ArrayList<>();
testList.add(...);
...
...
return testList.stream().mapToDouble(value -> value).average();
}
Thanks.
I'd go for this approach:
private static Optional<Double> convert(OptionalDouble od) {
return od.isPresent() ?
Optional.of(od.getAsDouble()) : Optional.empty();
}
A slight variation on #Andremoniy's answer is to skip the DoubleStream and use the averagingDouble() collector:
if (testList.isEmpty()) {
return Optional.empty();
}
return Optional.of(testList.stream().collect(Collector.averagingDouble()));
Or consider whether 0 is a valid return value for an empty list, and possibly skip the Optional entirely.
BTW, I found another solution, which has most simple form.
I've started thinking about: when result of average can be empty? Only when list it self is empty, right? So if we are sure that list is not empty, than we can safely do getAsDouble():
return Optional.ofNullable(testList.isEmpty() ? null :
testList.stream().mapToDouble(v -> v).average().getAsDouble())
(from performance point of view this could be more efficient than creating additional lambda wrappers, as was suggested in similar answers.)
This is quite an old question, but in newer versions of Java you can:
// Given an OptionalDouble
var myOptionalDouble = OptionalDouble.of(3.0d);
// Convert to Optional<Double>
var myOptional = myOptionalDouble.stream().boxed().findFirst();
The reverse is similarly easy:
var myOptional = Optional.of(Double.valueOf(3.0d));
var myOptionalDouble = myOptional.stream().mapToDouble(t -> t).findFirst();
I don't know if there exists a neat solution, but ths should work:
OptionalDouble optionalDouble = testList.stream().mapToDouble(value -> value).average();
return Optional.ofNullable(optionalDouble.isPresent() ? optionalDouble.getAsDouble() : null);
Just for fun, I wanted to see if it could be written in a single statement, without any need for an OptionalDouble temp variable. Here is the best I came up with:
return testList.stream().collect(Collectors.collectingAndThen(
Collectors.summarizingDouble(Double::doubleValue),
stat -> Optional.ofNullable(stat.getCount()>0 ? stat.getAverage() : null)));
I came to this "one line" (single statement) solution:
return ((Function<OptionalDouble, Optional<Double>>) od
-> od.isPresent() ? Optional.of(od.getAsDouble()) : Optional.empty())
.apply(testList.stream().mapToDouble(v -> v).average());
BTW, just for sake of minimalism, if you will do static import:
import static java.util.Optional.*;
you can omit Optional., what makes it a little bit less messy.

Not entirely clear on what this code does? (Includes Set, HashMap and .keySet())

So I've finished a program and have had help building it/worked with another person. I understand all of the program in terms of what each line of code does except one part. This is the code:
Set<String> set1 = firstWordGroup.getWordCountsMap().keySet();
Map<String, Integer> stringIntegerMap1 = set1.stream().collect(HashMap::new,
(hashMap, s) -> hashMap.put(s, s.length()), HashMap::putAll);
stringIntegerMap1.forEach((key,value) ->System.out.println(key + " : "+value));
Some background info:
getWordCut is a method that looks like this:
public HashMap getWordCountsMap() {
HashMap<String, Integer> myHashMap = new HashMap<String, Integer>();
for (String word : this.getWordArray()) {
if (myHashMap.keySet().contains(word)) {
myHashMap.put(word, myHashMap.get(word) + 1);
} else {
myHashMap.put(word, 1);
}
}
return myHashMap;
}
firstWordGroup is a constructor that stores a string of words.
If anybody could explain exactly what this block of code does and how it does it then that would be helpful, thanks.
P.S: I'm not sure if supplying the whole program to reproduce the code is relevant so if you think it is, just leave a comment saying so and I will edit the question so you can reproduce the program.
getWordsCountsMap() returns a map where the key is a word and the value is how many times the word occurred in the array
Set<String> set1 = firstWordGroup.getWordCountsMap().keySet();
The .keyset() method returns just the keys of the map, so now you have a set of the words, but have lost the occurrence counts.
Map<String, Integer> stringIntegerMap1 =
set1.stream()
.collect(HashMap::new,
(hashMap, s) -> hashMap.put(s, s.length()),
HashMap::putAll)
This is using Java8 streams to iterate through the set (words) originally put into a map and create a new hash map, where the key is the word (as it was before) and the value is the length of the word (whereas originally it was the word count). A new hash map is created and populated and returned.
What I'm not understanding is the final HashMap::putAll() which would seem to take the hashmap just populated and re-add all entries (which really would be a no-op because the keys would be replaced). Since I haven't whipped open my IDE to put in the code and test it (which, if you haven't yourself, would recommend, I'm just not interested enough to do so because it's not my problem!).
stringIntegerMap1.forEach((key,value) ->System.out.println(key + " : "+value));
In essence, this is a cleaner way to iterate through the entries in the map created, printing out the word and length for each.
After working through this and thinking about it, I have a feeling I'm doing your homework for you, the real way to figure this out is to break things down and debug through your IDE and seeing what each step of the way does.
Set<String> set1 = firstWordGroup.getWordCountsMap().keySet();
This line calles getWordCountsMap which returns a map from words to their count. It then ignores the count and just takes the words in a set. Note this could be achieved in a lot of much simpler ways.
Map<String, Integer> stringIntegerMap1 = set1.stream()
.collect(HashMap::new, (hashMap, s) -> hashMap.put(s, s.length()), HashMap::putAll);
This converts the set of words to a stream and then collects the stream. The collector starts by creating a hash map then, for each word, adding a map from the to its length. If multiple maps are created (as is allowed in streams) then they are combined using putAll. Again there are much simpler and clearer ways to achieve this.
stringIntegerMap1.forEach((key,value) ->System.out.println(key + " : "+value));
This line iterates through all entries in the map and prints out the key and value.
All this code could have been achieved with:
Arrays.stream(getWordArray())
.distinct().map(w -> w + ":" + w.length()).forEach(System.out::println);
This command converts the words to a stream, removes duplicates, maps the words to the output string then prints them.

Does java have a Frequency table?

Does Java have a built in Frequency Table? I remember using one in one of my classes and I know Python has one, but I do not remember if I built one on my own or if Java has one for use.
I assume by "frequency table" you mean a map of value-to-count.
The answer is no.
It's not much of a stretch to create a well-behaved one though:
Map<T, Integer> freq = new HashMap<T, Integer> () {
#Override
public Integer get(Object key) {
return containsKey(key) ? super.get(key) : 0;
}
}
Then when using you don't have to clutter your code with null checks when getting a frequency:
freq.get(value); // returns zero for not found
Or when incrementing:
freq.put(value, freq.get(value) + 1); // always works, won't throw NPE etc
Not built-in but you can check Apache Commons Statistics. It has an easy Frequency table builder that allows to find frequency, cumulative frequency, counts, etc.

Categories