Advanced TypeScript Programming Projects
上QQ阅读APP看书,第一时间看更新

Mapping values using maps

A situation that often comes up is needing to store a number of items with an easily looked up key. For instance, suppose we had a music collection broken down into a number of genres:

enum Genre {
Rock,
CountryAndWestern,
Classical,
Pop,
HeavyMetal
}

Against each one of these genres, we are going to store the details of a number of artists or composers. One approach we could take would be to create a class that represents each genre. While we could do that, it would be a waste of our coding time. The way we are going to solve this problem is by using something called a map. A map is a generic class that takes in two types: the type of key to use for the map and the type of objects to store in it.

The key is a unique value that is used to allow us to store values or to quickly look things up—this makes maps a good choice for rapidly looking values up. We can have any type as a key and the value can be absolutely anything. For our music collection, we are going to create a class that uses a map with the genre as the key and a string array to represent the composer or artists:

class MusicCollection {
private readonly collection : Map<Genre, string[]>;
constructor() {
this.collection = new Map<Genre, string[]>();
}
}

In order to populate a map, we call the set method, as follows:

public Add(genre : Genre, artist : string[]) : void {
this.collection.set(genre, artist);
}

Retrieving the values from the map is as simple as calling Get with the relevant key:

public Get(genre : Genre) : string[] | undefined {
return this.collection.get(genre);
}
We have to add the undefined keyword to the return value here because there is a possibility that the map entry does not exist. If we forgot to take the possibility of undefined into account, TypeScript helpfully warns us of this. Yet again, TypeScript works hard to provide that robust safety net for our code.

We can now populate our collection, as follows:

let collection = new MusicCollection();
collection.Add(Genre.Classical, [`Debussy`, `Bach`, `Elgar`, `Beethoven`]);
collection.Add(Genre.CountryAndWestern, [`Dolly Parton`, `Toby Keith`, `Willie Nelson`]);
collection.Add(Genre.HeavyMetal, [`Tygers of Pan Tang`, `Saxon`, `Doro`]);
collection.Add(Genre.Pop, [`Michael Jackson`, `Abba`, `The Spice Girls`]);
collection.Add(Genre.Rock, [`Deep Purple`, `Led Zeppelin`, `The Dixie Dregs`]);

If we want to add a single artist, our code becomes slightly more complex. Using set, we either add a new entry into our map or we replace the previous entry with our new one. As this is the case, we really need to check to see whether we have already added that particular key. To do this, we call the has method. If we have not added the genre, we are going to call set with an empty array. Finally, we are going to get the array out of our map using get so that we can push our values in:

public AddArtist(genre: Genre, artist : string) : void {
if (!this.collection.has(genre)) {
this.collection.set(genre, []);
}
let artists = this.collection.get(genre);
if (artists) {
artists.push(artist);
}
}

One more thing we are going to do to our code is change the Add method. Right now, that implementation overwrites previous calls to Add for a particular genre, which means that calling AddArtist and then Add would end up overwriting the artist we added individually with the ones from the Add call:

collection.AddArtist(Genre.HeavyMetal, `Iron Maiden`);
// At this point, HeavyMetal just contains Iron Maiden
collection.Add(Genre.HeavyMetal, [`Tygers of Pan Tang`, `Saxon`, `Doro`]);
// Now HeavyMetal just contains Tygers of Pan Tang, Saxon and Doro

In order to fix the Add method, it is a simple change to iterate over our artists and call the AddArtist method, as follows:

public Add(genre : Genre, artist : string[]) : void {
for (let individual of artist) {
this.AddArtist(genre, individual);
}
}

Now, when we finish populating the HeavyMetal genre, our artists consist of Iron Maiden, Tygers of Pan Tang, Saxon, and Doro.