Thursday, June 12, 2025

HashMaps

 A map is a very powerful data structure that lets us relate one thing to another in pairs. One of these things is a “key,” and the other is a “value,” so we call the pairs “key-value” pairs. For now, know that one of the benefits of maps is that they have very efficient lookup. Whereas arrays or lists might require a linear search, there is some magic going on (which we will eventually cover) that allows maps to find whatever you are looking for almost instantaneously, both in the human-time sense and the computer-time sense.

As I’m sure you’ve gathered by now, I’m a huge fan of putting links to official Java documentation in these articles. Look at it here.

If you open the documentation link, you’ll immediately see how HashMaps are created: with the declaration of two types in the angle brackets, just as we had one type in angle brackets for Lists, Queues, and Stacks. Recall that, since we need both a key and a value, we need the two types this time, not just one.

Look at this example code snippet:

import java.util.HashMap;

 

public class HashMapExample {

    public static void main(String[] args) {

        // Create a HashMap

        HashMap<String, Integer> map = new HashMap<>(); // notice the pair of types

 

        // Add key-value pairs

        map.put("Apple", 3);

        map.put("Banana", 5);

        map.put("Orange", 2);

 

        // Access a value by key

        System.out.println("Quantity of Apples: " + map.get("Apple"));

 

        // Check if a key exists

        if (map.containsKey("Banana")) {

            System.out.println("Banana is in the map.");

        }

 

        // Remove a key-value pair

        map.remove("Orange");

 

        // Iterate through the HashMap

        for (String key : map.keySet()) {

            System.out.println(key + ": " + map.get(key));

        }

    }

}

First, notice the import statement and how we declare the HashMap. (The declaration is legal because every HashMap is a Map.) Second, take note of the names of the operations: the “add” operation is called “put,” and the “retrieve” operation is called “get.” To check if a particular key exists, there is a containsKey() method; for values, there is a similar containsValue(). Removing a pair—not just a key, or not just a value—is done by remove(), passing in the key. get() takes in a key and returns the associated value.

In this case, the mapping is one-directional: “Delta 105” maps to “Atlanta to Guarulhos.” Naturally, “Atlanta to Guarulhos” should map to “Delta 105,” since it does in the real world: the delta flight from ATL to GRU is 105; and Delta 105 is the flight from ATL to GRU. These kinds of maps do exist, but the HashMap is not one by default. To get bidirectional mapping, either make two HashMaps with opposite keys and values (the key of one is the value of the other) or use a BiMap. Google has an implementation in their Guava library, but, whenever I’ve wanted to do something bidirectional, I have always found it easier to write (and to understand later) the code that just uses two opposite maps the regular way.

This is how bidirectional mapping would look:
import java.util.HashMap;

import java.util.Map;

public class SimpleBiMap<K, V> {

    private final Map<K, V> forwardMap = new HashMap<>();

    private final Map<V, K> reverseMap = new HashMap<>();

 

    public void put(K key, V value) {

        if (forwardMap.containsKey(key) || reverseMap.containsKey(value)) {

            throw new IllegalArgumentException("Key or value already exists in the bimap");

        }

        forwardMap.put(key, value);

        reverseMap.put(value, key);

    }

 

    public V getValue(K key) {

        return forwardMap.get(key);

    }

 

    public K getKey(V value) {

        return reverseMap.get(value);

    }

 

    public void removeByKey(K key) {

        V value = forwardMap.remove(key);

        if (value != null) {

            reverseMap.remove(value);

        }

    }

 

    public void removeByValue(V value) {

        K key = reverseMap.remove(value);

        if (key != null) {

            forwardMap.remove(key);

        }

    }

}

 

public class AirlineRouteMap {

    public static void main(String[] args) {

        SimpleBiMap<String, String> routeMap = new SimpleBiMap<>();

 

        // Assign routes

        routeMap.put("Delta 105", "Atlanta to Sao Paulo");

        routeMap.put("United 222", "New York to London");

        routeMap.put("American 333", "Los Angeles to Tokyo");

 

        // Look up by flight code

        System.out.println("Route for Delta 105: " + routeMap.getValue("Delta 105"));

        // Output: Route for Delta 105: Atlanta to Sao Paulo

 

        // Look up by route description

        System.out.println("Flight code for 'New York to London': " + routeMap.getKey("New York to London"));

        // Output: Flight code for 'New York to London': United 222

    }

}

Here, SimpleBiMap maintains the two opposite maps. AirlineRouteMap simply implements a specific case for airline routes: given a city pair, you get the number, and given the number, you get the city pair.

Keys must be unique in a HashMap; values need not be. Because of this, if you want to get all the keys, the data structure that is returned is a Set, and the data structure of all the values is a Collection. If what you want is a Set of key-value pairs, that can come out of a HashMap as the entrySet. Map has inside of itself the class Map.Entry<K,V>, so should you want a Set of key-value pairs rather than a Map, you can get it.  

No comments:

Post a Comment

Switch

 Other than if/if-else/if-else if-else and the ternary operator, there is yet another common and important conditional expression in Java th...