Looping over Map
objects¶
Imagine we have a Map<String, Double>
that maps city names to their populations (in millions, from 2017 via Google via this source). We could imagine the following values being added to our Map
Map<String, Double> populations = new TreeMap<String, Double>();
populations.put("Seattle", 0.724);
populations.put("Los Angeles", 4.0);
populations.put("New York", 8.623);
populations.put("Chicago", 2.716);
populations.put("Philadelphia", 1.581);
We could print out our Map
using a call to its toString()
method, which would give us the following result:
System.out.println(populations);
// Output: {Chicago=2.716, Los Angeles=4.0, New York=8.623, Philadelphia=1.581, Seattle=0.724}
But what if we wanted to do something with the values in our Map
such as printing them out in a different format, or finding the city with the highest population? For these tasks, we need to know how to loop over the items in our Map
.
Changing the printing format¶
Say I want to print out the items in my Map
in the following format
*city* has a population of *population* million
How would we go about doing this? Well we need a way to somehow get access to the keys and values of our Map
. Perhaps you recall seeing the methods values()
and keySet()
on the slides about the Map
interface. A call to values()
returns a collection of all the values in our current Map
. A call to keySet()
returns a collection of all of the keys in our current Map
. Since we know we want to print out the populations, why don’t we start by looping through all of the values in the Map
A non-ideal solution¶
for (double population : populations.values()) {
// Do something
}
Map
(the populations), but in order to print in the format mentioned above we also need access to the keys in our Map
(cities associated to those populations). We could try the following: for (double population : populations.values()) {
for (String city : populations.keySet()) {
if (populations.get(city) == population) {
System.out.println(city + " has a population of " + population + " million");
}
}
}
This could work, but it seems a bit clunky and very inefficient (it has a complexity of where is the number of key-value pairs in the Map
, since we loop over all the values and then for each value we loop over all the keys). In order to find the correct key/value pair we have to loop over the keys and get the values associated with them anyhow. Remember, in Map
objects we go from the key to the value. We don’t have an easy way to get from the value to the key.
An ideal solution¶
Rather than looping over the values in our Map
and then trying to somehow access the keys, it would be much easier to more appropriately use the design of Map
objects and initially loop over the keys. Let’s try solving the problem again, this time initially looping over the keySet()
for (String city : populations.keySet()) {
double population = populations.get(city);
System.out.println(city + " has a population of " + population + " million");
}
Wow that looks so much better! It’s also important to note that our second solution is much more efficient than our first solution was (our updated solution has complexity since it just loops over the keys). Use this exercise as a reminder that when we loop over Map
objects, we should always go from the key to the value. Below is a skeleton outline of how to loop over Map<KeyType, ValueType>
objects, though the details may change depending on the task at hand
for (KeyType key : mapName.keySet()) {
ValueType value = mapName.get(key);
// Do something with key and/or value
}
More Practice with Map
objects¶
Try this problem for a bit of practice with building Map
objects.
I’m a frayed you don’t remember Strings¶
The next take-home assessment deals with a lot of manipulations of Strings, so this example should help remind everyone how to work with Strings in their programs.
I want to write a method called dashes
that takes a String and puts dashes between all the characters. If the given String is null
, the method should throw an IllegalArgumentException
. For example, the call
System.out.println(dashes("hello")); // Output: h-e-l-l-o
First attempt¶
public static String dashes(String word) {
if (word.isEmpty()) {
throw new IllegalArgumentException();
}
// initialize to empty String
String result = "";
char[] chars = word.toCharArray();
// standard loop over characters in String
for (int i = 0; i < chars.length; i++) {
// build up dash and character
result += chars[i] + "-";
}
return result;
}
The above code has two correctness errors and one stylistic error, can you figure out what they are?
Try to guess the errors before expanding this box!
- Correctness: Does not correctly check for
null
for theIllegalArgumentException
- Students commonly mix up the “empty String” (
""
) and the “null String” (null
). An empty String""
is a String that has no characters while the null Stringnull
is the absence of the String at all. If you think of Strings like boxes that hold characters, the empty String is a box that is empty while the null String is a box that doesn’t exist. The proper check would beif (word == null)
- Students commonly mix up the “empty String” (
- Correctness: There are too many dashes in the result!
dashes("hello") => "h-e-l-l-o-"
. The fix is described below. - Code Quality: Unnecessarily creates an extra object when calling
toCharArray
. There is no need to create this array of characters since we have access to the characters in theString
using thecharAt
method.
This is a classic example of a fence-post problem. Where we have n characters and want to place n-1 characters we want to place between them. Many people call this a fence-post because we are trying to build something like a fence as can be seen in the figure below
|-|-|-|-|
Fencepost problem:
Posts = | (characters)
Links = - (dashes)
However our code places a “post” and a “link” on each iteration and doesn’t take into account that there are a different number of “posts” and “links”. One way to solve this is to pull part of an iteration outside the loop and then flip the order inside the loop.
public static String dashes(String word) {
if (word == null) {
throw new IllegalArgumentException();
}
String result = "" + word.charAt(0); // Note: Have to do `"" + ...` to turn the character into a String
for (int i = 1; i < word.length(); i++) { // Note: Starting at 1
result += "-" + word.charAt(i); // Note: Swapped order of fence and post
}
return result;
}
This solution almost works, except in one case when given the empty String ""
. To fix this we could add a special case at the beginning or add a pre-condition to the method saying we don’t handle those Strings (as long as that doesn’t break the spec we were asked to implement).
Below is an alternative way of solving the problem that some people prefer
public static String dashes(String word) {
if (word == null) {
throw new IllegalArgumentException();
}
String result = "";
for (int i = 0; i < word.length(); i++) {
result += "-" + word.charAt(i);
}
// Still doesn't work if word is the empty String!
return result.substring(1);
}
Collections¶
This assessment asks you to work with the Collection
interface. Many students haven’t seen this interface before, but it is nothing really new since it is the generalization of things like Set
and List
; in fact Set
and List
are both interfaces that satisfy the Collection
interface (more on this towards the end of the quarter). If you are working with a Collection
, you have access to the following methods; there are more methods in the actual Java specification, but you probably won’t use them.
Method | Description |
---|---|
add(value) | adds the given value to this collection |
addAll(other) | adds every value from the other collection to this one |
clear() | removes all the values from this collection |
contains(value) | returns true if the value is in this collection, false otherwise |
isEmpty() | returns true if and only if there are no values in this collection |
iterator() | returns an Iterator to access the elements in this collection |
remove(value) | removes the given value from this collection |
size() | returns the number of elements in this collection |