So basically I have a map that looks like this
HashMap<Integer, HashMap<String, Integer>> girls =
new HashMap<Integer, HashMap<**String**, Integer>>();'
and I want to return the bolded string and this is my method so far
public String getGirlName(int year, int rank) { //year refers to Key
//rank refers to other integer in value map
if (girls.containsKey(year) && girls.get(year).containsKey(rank)){
//shouldn't this return the name
return girls.get(year).get(rank); //error here where it says I can't return an integer I have to return a string
else return null;
}
}
I'm not sure how to do the above title
Let's try to see the structure of HashMap<Integer, HashMap<String, Integer>>
visually, since it is quite a complex data structure
// A B
// |--------|--------|
// C D
// |------|---------------------------------------------------------------------|
{
Integer: {String: Integer, String: Integer, String: Integer, String: Integer},
Integer: {String: Integer, String: Integer, String: Integer, String: Integer},
Integer: {String: Integer, String: Integer, String: Integer, String: Integer},
Integer: {String: Integer, String: Integer, String: Integer, String: Integer}
}
Sections A and C are the keys, B and D are the values.
Now let's see what does .get(year).get(rank);
. First, get(year)
is called. year
is a value in section C, so get(year)
returns a value in section D. Next, get(rank)
is called. Since it is called on the return value of get(year)
, rank
here is a value in section A. get(rank)
will return a value in section D, which are all Integer
s. Therefore, the compiler error occurs.
To fix this, simply swap the two types in the inner map. You change from HashMap<Integer, HashMap<String, Integer>>
to HashMap<Integer, HashMap<Integer, String>>
.
If you can avoid it, don't use a map of maps. There are reasons and situations where you might want to but it's very confusing, as you've seen yourself.
This is not one of the cases where you want to. Just declare a new class to encapsulate this information and store it in a list:
class Girl
{
// Declare getters if you want
public final int year;
public final int rank;
public final String name;
Girl(int year, int rank, String name){
this.year = year;
this.rank = rank;
this.name = name;
}
}
Now your data structure isn't a complete mess:
List<Girl> girls = new ArrayList<>();
and your function is super easy:
public String getGirlName(int year, int rank) {
for (Girl girl : girls) {
if (girl.year == year && girl.rank == rank) {
return girl.name;
}
}
throw new RuntimeException("No such girl"); // or whatever
}
As stated, your code will not compile for more than the reason you mentioned.
I think you are misunderstanding how to use a Map. A Map is used like an glossary in a book. A glossary contains a page number for a topic. In a similar sense, a Map contains a value (v [value]) for a given (k [page number]).
But you get to tell the computer what you want to store for what. So, you need to understand your keys and values so that you can construct your map correctly.
Try
return girls.getOrDefault(year, Collections.emptyMap()).entrySet().stream()
.filter(e -> e.getValue() == rank)
.map(e -> e.getKey())
.findFirst()
.orElse(null);
You can stream the Map doing:
//get the map that match the year:
Map<String, Integer> mapForKeyYear = g.getOrDefault(year, null);
//if the map is not null then filter that map to the rank:
String r = mapForKeyYear
.entrySet().stream()
.filter(e -> e.getValue() == rank)
.map(Map.Entry::getKey)
.findFirst().orElse(null);