Hash Maps in Python


Hash Tables, better known as Hash Maps, are just collections of key-value pairs. In other words, they are pieces of data (values) mapped to unique identifiers called properties (keys).

Hash Maps were designed to give us a way of, given a key, associating a value with it for very quick lookups.

In Python, the Hash Map is implemented as a dictionary. This is why adding and lookup in a dictionary is so fast.

If you've worked with dictionaries in the past, you were working with Hash Maps without knowing it.


Creation:

You create an empty Hash Map just by creating an empty dictionary, like this:

hashMap = {}

Creating an empty dictionary takes O(1) time.


Initialization:

You can also add some key-value pairs in your dictionary at creation:

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown'
}

Accessing values:

A value is retrieved from a dictionary by specifying its corresponding key in square brackets ([]):

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown'
}

# Accessing values:
print(person['name']) # "Andy"
print(person['age']) # 30

# Accessing non-existing key:
print(person['job']) # raises a KeyError exception

As you can see, if you refer to a key that is not in the dictionary, Python raises a KeyError exception.

Accessing a value from a dictionary takes O(1) time.


Adding an entry:

Adding an entry to an existing dictionary is simply a matter of assigning a new key and value, via the assignment operator:

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown'
}

# Adding new entries:
person['job'] = 'Teacher'

str = 'hobby'
person[str] = 'Fishing'

print(person)
# The new Hash Map:
# {
#	'name': 'Andy',
#	'age': 30,
#	'hair color': 'brown',
#	'job': 'Teacher',
#	'hobby': 'Fishing'
# }

Adding a new entry to a dictionary takes O(1) time.


Updating an entry:

If you want to update an entry, you can just assign a new value to an existing key:

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown'
}

# Updating entries:
person['name'] = 'Andrew'
person['age'] = 29

print(person)
# The new Hash Map:
# {
#	'name': 'Andrew',
#	'age': 29,
#	'hair color': 'brown'
# }

Updating an entry from a dictionary takes O(1) time.


Deleting entries:

The del operator allows you to remove a key from a dictionary:

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown',
	'hobby': 'Fishing'
}

# Deleting entries:
del person['name']

key = 'age'
del person[key]

# Deleting a non-existent entry:
# del person['job'] -> this would raise a KeyError exception

print(person)
# The new Hash Map:
# {
#	'hair color': 'brown',
#	'hobby': 'Fishing'
# }

If you try to delete a key that is not in the dictionary, Python raises a KeyError exception.

Deleting an entry from a dictionary takes O(1) time.


Checking if a key exists:

You can check if a key exists in a dictionary using the in operator:

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown',
	'hobby': 'Fishing'
}

key = 'age'
print(key in person) # True

print('job' in person) # False

Iterating over the dictionary keys:

If we want to iterate over keys of the dictionary, we can use the for loop along with the keys() method:

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown',
	'hobby': 'Fishing'
}

for key in person.keys():
    print(key + ' is ' + str(person[key]))

# This will print the following:
# hobby is Fishing
# age is 30
# hair color is brown
# name is Andy


Notice the order is not the same as initiated. Dictionary keeps the data in random order

Also Notice that we used str() to cast / convert the value into a string since we have the integer 30 which needs casting. If we don't use it, we will get a runtime error

Iterating over a dictionary takes O(n) time.


Iterating over the dictionary values:

If we want to iterate over values of the dictionary, we can use the for loop along with the values() method:

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown',
	'hobby': 'Fishing'
}

for val in person.values():
    print(val)

# This will print the following:
# Fishing
# 30
# brown
# Andy

Notice the order is not the same as initiated. Dictionary keeps the data in random order

Iterating over a dictionary takes O(n) time.


Iterating using both the key and the value for simplicity:

If we want to iterate over both keys and values of the dictionary, we can use the for loop along with the items() method:

person = {
	'name': 'Andy',
	'age': 30,
	'hair color': 'brown',
	'hobby': 'Fishing'
}

for key, val in person.items():
    print(key + ' is ' +  str(val))

# This will print the following:
# hobby is Fishing
# age is 30
# hair color is brown
# name is Andy


Notice the order is not the same as initiated. Dictionary keeps the data in random order

Also Notice that we used str() to cast / convert the value into a string since we have the integer 30 which needs casting. If we don't use it, we will get a runtime error

Iterating over a dictionary takes O(n) time.


Assignment
Follow the Coding Tutorial and let's play with some dictionaries.


Hint
Look at the examples above if you get stuck.


Introduction

In this lesson, we will explore the concept of Hash Maps in Python, which are implemented as dictionaries. Hash Maps are essential data structures that allow for efficient storage and retrieval of data using key-value pairs. They are widely used in various programming scenarios, such as caching, indexing, and managing configurations.

Understanding the Basics

Hash Maps, or dictionaries in Python, are collections of key-value pairs. Each key is unique and maps to a specific value. This structure allows for quick lookups, additions, and deletions. Understanding the basics of Hash Maps is crucial before diving into more complex operations.

For example, consider a dictionary that stores information about a person:

person = {
    'name': 'Andy',
    'age': 30,
    'hair color': 'brown'
}

In this dictionary, 'name', 'age', and 'hair color' are keys, and 'Andy', 30, and 'brown' are their corresponding values.

Main Concepts

Let's delve into the key concepts and techniques involved in working with Hash Maps:

Examples and Use Cases

Let's explore some examples to demonstrate the use of Hash Maps in various contexts:

# Example 1: Creating and initializing a dictionary
person = {
    'name': 'Andy',
    'age': 30,
    'hair color': 'brown'
}

# Example 2: Accessing values
print(person['name'])  # Output: Andy
print(person['age'])   # Output: 30

# Example 3: Adding and updating entries
person['job'] = 'Teacher'
person['age'] = 31

# Example 4: Deleting entries
del person['hair color']

# Example 5: Checking key existence
print('name' in person)  # Output: True
print('hair color' in person)  # Output: False

# Example 6: Iterating over keys and values
for key, value in person.items():
    print(f"{key}: {value}")

Common Pitfalls and Best Practices

When working with Hash Maps, it's important to avoid common mistakes and follow best practices:

Advanced Techniques

Advanced techniques can enhance the functionality of Hash Maps:

# Example of using get() method
print(person.get('hobby', 'Not specified'))  # Output: Not specified

# Example of nested dictionaries
person['address'] = {'city': 'New York', 'zip': '10001'}

# Example of dictionary comprehension
squares = {x: x*x for x in range(6)}
print(squares)  # Output: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Code Implementation

Here is a comprehensive code implementation demonstrating the correct use of Hash Maps:

# Creating and initializing a dictionary
person = {
    'name': 'Andy',
    'age': 30,
    'hair color': 'brown'
}

# Accessing values
print(person['name'])  # Output: Andy
print(person['age'])   # Output: 30

# Adding and updating entries
person['job'] = 'Teacher'
person['age'] = 31

# Deleting entries
del person['hair color']

# Checking key existence
print('name' in person)  # Output: True
print('hair color' in person)  # Output: False

# Iterating over keys and values
for key, value in person.items():
    print(f"{key}: {value}")

# Using get() method
print(person.get('hobby', 'Not specified'))  # Output: Not specified

# Nested dictionaries
person['address'] = {'city': 'New York', 'zip': '10001'}

# Dictionary comprehension
squares = {x: x*x for x in range(6)}
print(squares)  # Output: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Debugging and Testing

Debugging and testing are crucial for ensuring the correctness of your code:

# Example test cases
def test_person_dict():
    person = {
        'name': 'Andy',
        'age': 30,
        'hair color': 'brown'
    }
    assert person['name'] == 'Andy'
    assert person['age'] == 30
    person['job'] = 'Teacher'
    assert person['job'] == 'Teacher'
    del person['hair color']
    assert 'hair color' not in person

test_person_dict()
print("All tests passed!")

Thinking and Problem-Solving Tips

When working with Hash Maps, consider the following tips:

Conclusion

In this lesson, we covered the fundamental concepts of Hash Maps in Python, including creation, initialization, accessing values, adding and updating entries, deleting entries, checking key existence, and iterating over dictionaries. We also discussed common pitfalls, best practices, advanced techniques, and provided a comprehensive code implementation. Mastering these concepts will enhance your ability to work with Hash Maps effectively in various programming scenarios.

Additional Resources

For further reading and practice, consider the following resources: