Welcome
Announcements
Do you have clicker A49D477E? You need to register it in Moodle.
Continuing on
Picking up from last class!
Sometimes it’s nice to have automated tests. But before we add a couple tests, let’s just check something:
In class exercise
Does x == y
? Does `x.equals(y)?
A Unit Test
OK, now we’ll write a test.
import static org.junit.Assert.*;
import org.junit.Test;
public class LocationTest {
@Test
public void testLocationEquals() {
Location x = new Location(0,0);
Location y = x;
Location z = new Location(0,0);
assertTrue(x == y);
assertFalse(x == z);
assertEquals(x, y);
assertEquals(x, z);
}
@Test
public void testLocationNotEquals() {
assertFalse(new Location(0,0).equals(new Location(0,1)));
}
}
A reminder about assertEquals
: it uses the objects’ built-in equals()
method. If you have tests failing (say, in a project?), and the expected and actual look the same, maybe you forgot to define .equals
on your objects?
Anyway, let’s go back to locationsVisited
. According to the problem statement, the house at (0, 0) always gets a present:
public static Set<Location> locationsVisited(String directions) {
Set<Location> visited = new HashSet<Location>();
visited.add(new Location(0,0));
return visited;
}
Does this work? Let’s add a test.
@Test
public void testEmptyDirections() {
assertEquals(new HashSet<Location>(Arrays.asList(new Location(0,0))), DeliverySimulator.locationsVisited(""));
}
Then we need to think about where the sleigh goes, and deliver a present at each stop:
public static Set<Location> locationsVisited(String directions) {
Set<Location> visited = new HashSet<Location>();
visited.add(new Location(0,0));
int x = 0;
int y = 0;
for (int i = 0; i < directions.length(); i++) {
if (directions.charAt(i) == '>') {
x += 1;
} else if (directions.charAt(i) == '<') {
x -= 1;
} else if (directions.charAt(i) == '^') {
y += 1;
} else if (directions.charAt(i) == 'v') {
y -= 1;
}
else {
throw new IllegalArgumentException();
}
visited.add(new Location(x, y));
}
return visited;
}
And now we add some tests:
@Test
public void testSimple() {
assertEquals(new HashSet<Location>(Arrays.asList(new Location(0, 0), new Location(1, 0))),
DeliverySimulator.locationsVisited(">"));
}
@Test
public void testFour() {
assertEquals(
new HashSet<Location>(
Arrays.asList(new Location(0, 0), new Location(0, 1), new Location(1, 1), new Location(1, 0))),
DeliverySimulator.locationsVisited("^>v<"));
}
You could also add checks for housesVisited
, either to their own test cases (which would be more true to the spirit of unit tests) or within the above.
A new problem, and a new data structure
What if, each time Santa visited a house, he delivered a present, and we wanted to know the total number of presents delivered? How might you go about tracking this?
You could, for example, keep a List
of visited locations (instead of a Set
). Then you could count the number of times each location appeared. But how would you tabulate it? You want to build some kind of table, a way to connect a set of things, and for each thing, some information about it.
(0,0) | (0,1) | (1,1) | (1,0)
------|-------|-------|------
2 1 1 1
You might create two List
s, one of Location
and one of Integer
, where the i
th element in each corresponded. But that’s clunky, hard to program, and has the poor performance of a list for lookups. You might edit the Location
to contain a counter
variable. That could work; but it might cause problems in other contexts, when the thing you’re storing is doesn’t have a clear “has-a” relationship with whatever you’re adding it to. Objects, like functions, should generally do a small set of things well.
What if I told you there was a data structure that solved this problem? What if I told you you’ve already (sorta) seen it? What if I told you it could be yours for no money down, and no monthly installments?
Introducing… the Map
.
Map
ADT
A Map
“maps” keys to values. In other words, it associates one kind of thing with another kind of thing.
The first “thing” is the key. Keys are lookup keys; you can think of them as kinda analogous to an index card, or a page number, or a URL; they are the thing we can do lookups on (though they can also be useful data in and of themselves).
The second “thing” is a value – each value is associated with a key.
So Map
s model, in essence, a table (on board). For our Santa problem, we might use Location
s as keys and Integer
s as values.
The keys in a map form a set – there can only be one of each. And each value is associated with exactly one key. Keys are unique (as they are a set), but the same value can be associated with more than one key (as shown in our table above).
And, the key values should be immutable, or at least, should not change with respect to equals
, for exactly the same reason as in Set
s – in fact, if you recall, Set
s are implemented using Map
s.
Like Set
s, Map
s come in two flavors you’ll likely use: HashMap
s and TreeMap
s, with the same constraints (about efficient hashCode
and compareTo
methods).
The type signature is slightly different though: Map<K, V>
; the key and value types are both parameterized. This should make sense: there’s no particular reason a map would store keys and values of the same type, only that all keys are of the same type, and all values are of the same type.
There are many useful Map
methods, but we’ll start with the basics that are unique to Map
:
containsKey
checks to see if a particular object is in the map’s set of keys, byequals
(this is fast, because it’s just like set access)containsValue
checks to see if a particular object is in the map (this is slow/linear, since generally it requires traversing each key’s value until the value is found or all values are checked)get
, which given a key returns the associated value (ornull
if not found!!!)getOrDefault
, which given a key and a default returns the associated value for the key (or the default if not found)put
which given a key and value, insert that key and value into the map (overwriting if previously in the map)keySet
returns theSet<K>
of keys in this map- and so on; read the Javadoc for details.
Let’s use these to come up with an answer to our previous question (“how many presents per house”).
How many?
Let’s say we only want to build the table once for a given set of directions. It makes sense, maybe, to think about encapsulating this in a class. The class is instantiated with a set of directions, then can answer questions about what results from that set of directions. Here we go:
import java.util.Map;
public class SleighTrack {
private Map<Location, Integer> locationCount;
public SleighTrack(String directions) {
locationCount = new HashMap<Location, Integer>();
locationCount.put(new Location(0, 0), 1);
}
}
And now let’s move our code over and adapt it to populate this map.
public SleighTrack(String directions) {
locationCount = new HashMap<Location, Integer>();
locationCount.put(new Location(0, 0), 1);
int x = 0;
int y = 0;
for (int i = 0; i < directions.length(); i++) {
if (directions.charAt(i) == '>') {
x += 1;
} else if (directions.charAt(i) == '<') {
x -= 1;
} else if (directions.charAt(i) == '^') {
y += 1;
} else if (directions.charAt(i) == 'v') {
y -= 1;
}
else {
throw new IllegalArgumentException();
}
Location loc = new Location(x, y);
int t = locationCount.getOrDefault(loc, 0);
locationCount.put(loc, t+1);
}
}
Let’s add a toString
method and see if it works:
public String toString() {
return locationCount.toString();
}
public static void main(String[] args) {
SleighTrack st = new SleighTrack("^>v<");
System.out.println(st);
}
What do we expect? Same as the table above. What do we get?
{(1, 0)=1, (0, 0)=2, (1, 1)=1, (0, 1)=1}
Bingo!
In class exercises
In our SleighTrack
, how might we compute the number of houses visited, given the code written so far?
public int housesVisited() {
// what goes here?
}
In our SleighTrack
, how might we find the set of houses visited, given the code written so far?
public Set<Location> locationsVisited() {
// what goes here?
}
In our SleighTrack
, how might we find the number of presents delivered to a house?
public int numberPresents(Location loc) {
return locationCount.getOrDefault(loc, 0);
}
On Map
generality
Map
s need not just be used to count things; they can be used to associate any (relatively immutable) set of keys with any values you like. For example, you could store:
- URLs and the results of retrieving those URLs
- filesystem paths and the sizes of those files
- student IDs and photos
- latin names and genomes
- basically, anything.