05/02/2024 • 4 Minute Read

How to Build a Multi-Key Dictionary in Python

Enhancing Dictionaries in Python: A Guide to Implementing Multi-Key Dictionaries

python programming on laptop

Python

Dictionaries in Python

Data Structures

Dictionaries are an incredible data structure, that can assist in making your code more approachable, consistent and efficient.

Unfortunately, out of the box Python only provides a 1-dimensional dictionary. That being a dictionary with a single key. Technically, this can be extended to any number of keys using tuples as your key data type, but there is one potential flaw. That being, that the order of your tuple matters, meaning two keys existing in a dictionary may not be access the correct value if they are not in the correct order.

In your problem solving endeavours, you may encounter problems where you would like to access a particular value, using the speed of a dictionary, but with two or more keys. Where the order of those keys is not guaranteed to be consistent.

To do this I’ve provided code snippets for two custom dictionary classes:

  1. A two dimensional dictionary → TwoKeyDict
  2. A n (where n is any number of keys ≥ 1) dimensional dictionary → NKeyDict

Two Key Dictionary

The TwoKeyDict class has the following methods:

  1. get_value_by_key → returns the value stored for a given two key combination, but if there is no value for that combination it returns None.
  2. set_key_value_pair → stores a new value or updates an existing one, for a given two key combination.
  3. remove_key_value_pair → deletes the key-value pair using the given two key combination, if it exists in the dictionary.
class TwoKeyDict:
    def __init__(self):
        self.dictionary = {}

    def get_value_by_key(self, key1, key2):
        if (key1, key2) in self.dictionary:
            return self.dictionary[(key1, key2)]

        if (key2, key1) in self.dictionary:
            return self.dictionary[(key2, key1)]

        return None

    def set_key_value_pair(self, key1, key2, value):
        if (key2, key1) in self.dictionary:
            self.dictionary[(key2, key1)] = value
        else:
            self.dictionary[(key1, key2)] = value
            
    def remove_key_value_pair(self, key1, key2):
        if (key1, key2) in self.dictionary:
            del self.dictionary[(key1, key2)]
        elif (key2, key1) in self.dictionary:
            del self.dictionary[(key2, key1)]
        else:
            return None

Example Usage

example_dict = TwoKeyDict()

example_dict.set_key_value_pair("a", "b", 1)
print(example_dict.get_value_by_key("a", "b"))  # 1

example_dict.set_key_value_pair("b", "a", 2)
print(example_dict.get_value_by_key("a", "b"))  # 2

example_dict.set_key_value_pair("c", "d", 3)
print(
    example_dict.get_value_by_key("c", "d"), example_dict.get_value_by_key("d", "c")
)  # 3 3

print(
    example_dict.get_value_by_key("a", "c"), example_dict.get_value_by_key("q", "x")
)  # None None

example_dict.remove_key_value_pair("a", "b")
print(example_dict.get_value_by_key("a", "b"))  # None

N Dimensional Dictionary

The NKeyDict class has the following methods:

  1. __init__ → the constructor used to set the number of keys/dimensions (num_keys) you would like the dictionary to use for key-value storage.
  2. get_value_by_key → returns the value stored for a given n-key combination, but if there is no value for that combination it returns None.
  3. set_key_value_pair → stores a new value or updates an existing one, for a given n-key combination.
  4. remove_key_value_pair → deletes the key-value pair using the given n-key combination, if it exists in the dictionary.
class NKeyDict:
    def __init__(self, num_keys: int):
        self.dictionary = {}
        self.num_keys = num_keys

    def get_value_by_key(self, keys: list[any]):
        if len(keys) != self.num_keys:
            raise Exception(
                "Number of keys does not match the number of keys in the dictionary"
            )

        keys.sort()
        return self.dictionary.get(tuple(keys), None)

    def set_key_value_pair(self, keys: list[any], value):
        if len(keys) != self.num_keys:
            raise Exception(
                "Number of keys does not match the number of keys in the dictionary"
            )

        keys.sort()
        self.dictionary[tuple(keys)] = value

    def remove_key_value_pair(self, keys: list[any]):
        if len(keys) != self.num_keys:
            raise Exception(
                "Number of keys does not match the number of keys in the dictionary"
            )

        keys.sort()

        if tuple(keys) in self.dictionary:
            del self.dictionary[tuple(keys)]
        else:
            return None

A important difference in the implementation of TwoKeyDict and NKeyDict is that TwoKeyDict takes each key as seperate arguments and checks for each permutation (ordered combination) of tuples in the dictionary. While NKeyDict takes a list of keys and sorts them so that permutations of the collection of keys don’t need to be checked.

Example Usage

example_3d_dict = NKeyDict(3)

example_3d_dict.set_key_value_pair([1, 2, 3], "Hello World!")
print(
    example_3d_dict.get_value_by_key([1, 2, 3]),
    example_3d_dict.get_value_by_key([3, 2, 1]),
) # Hello World! Hello World!

example_3d_dict.set_key_value_pair([2, 3, 1], "Goodbye!")
print(example_3d_dict.get_value_by_key([1, 2, 3])) # Goodbye!

example_3d_dict.remove_key_value_pair([1, 2, 3])
print(example_3d_dict.get_value_by_key([1, 2, 3])) # None
example_3d_dict.set_key_value_pair([5, 6, 7, 8], "This will cause an error!")
# Exception: Number of keys does not match the number of keys in the dictionary

Conclusion

Using the above code snippets, you can implement easily implement and utilise a multi-key dictionary in your programming tasks.

If you found this article helpful, check out my profile for other articles and follow me for more articles like this.