DEV Community

Cover image for # Learning Python Data Structures and Algorithms Through Practical Examples
Sylvia Ndili
Sylvia Ndili

Posted on

# Learning Python Data Structures and Algorithms Through Practical Examples

When learning Python, understanding the syntax is just part of the process. You also need to know how to organise data, retrieve it, update it and validate user input. These are some of the foundations of data structures and algorithms.

In this article, we are going to explore Python's built-in data structures, string manipulation and file-related concepts through practical examples. At the end, I also applied what I learned in a small capstone project: A bundle purchase simulator inspired by the *544# menu.

Here are the key concepts I learned.

1. Lists: Working with Collections of Data

Lists are useful when you need to store multiple items in one variable. They can hold strings, numbers, Boolean values or even other lists.

students = ["Bob", "Faith", "Brian", "Ken"]
scores = [78, 85, 67, 34]
Enter fullscreen mode Exit fullscreen mode

Python uses zero-based indexing, meaning the first item is at index 0. Negative indexing lets you access elements from the end of a list.

print(scores[0])   # 78
print(scores[-1])  # 34
Enter fullscreen mode Exit fullscreen mode

Slicing allows you to retrieve a section of a list. The stop index is excluded.

print(scores[1:3])  # [85, 67]
print(scores[::-1])  # Reverses the list
Enter fullscreen mode Exit fullscreen mode

We also explored methods for modifying lists, including append(), insert(), extend(), remove(), and pop(). Other useful operations include sorting, counting occurrences, finding an item's position and calculating the length of a list.

One important distinction is that sort() changes the original list, while sorted() returns a new sorted list.

Lists are useful whenever data needs to be stored in an ordered, changeable collection.

2. Tuples: Storing Data That Should Not Change

Tuples look similar to lists, but they are immutable. Once created, their elements cannot be reassigned.

coordinates = (1.2864, 36.8172)

latitude, longitude = coordinates

print(latitude)
print(longitude)
Enter fullscreen mode Exit fullscreen mode

The example also demonstrates tuple unpacking, which assigns tuple elements to separate variables in one statement. Tuples are useful for values that belong together and should remain unchanged, such as coordinates or fixed sets of related values.

A tuple containing just one item needs a trailing comma:

single_item = (20,)
Enter fullscreen mode Exit fullscreen mode

Without the comma, (20) is simply an integer expression in parentheses.

3. Dictionaries: Organising Data Using Keys and Values

Dictionaries store information as key-value pairs. They are particularly useful when each piece of information has a label.

student = {
    "name": "Amina Wanjiku",
    "age": 24,
    "track": "Data Science",
    "scores": [75, 85, 91]
}
Enter fullscreen mode Exit fullscreen mode

We can access information using a key or the get() method.

print(student["name"])
print(student.get("city", "Not found"))
Enter fullscreen mode Exit fullscreen mode

The main difference is that accessing a missing key with square brackets raises a KeyError, while get() returns None or a specified default value.

Dictionaries can also be updated, extended with new keys and have entries removed using del or pop().

To process dictionary contents, we can iterate over keys, values or both:

for key, value in student.items():
    print(f"{key}: {value}")
Enter fullscreen mode Exit fullscreen mode

This structure is useful for representing records such as students, employees, products and transactions.

4. Sets: Handling Unique Values

Sets store unique elements. If a value appears multiple times, only one instance remains.

cities = {"Nairobi", "Mombasa", "Nairobi", "Kisumu"}
print(cities)
Enter fullscreen mode Exit fullscreen mode

Sets are useful for removing duplicates and comparing collections of data.

The are four important set operations:

  • Union (|) combines elements from both sets.
  • Intersection (&) returns elements shared by both sets.
  • Difference (-) returns elements found in one set but not the other.
  • Symmetric difference (^) returns elements found in either set, but not both.

For example, if two datasets contain customer IDs, intersection can help identify IDs that appear in both datasets.

Unlike lists, sets do not guarantee a particular iteration order, so they are not suitable when positional order matters.

5. Strings: Cleaning, Searching and Validating Text

Strings are sequences of characters. We can access individual characters using indexing and extract sections using slicing, much like lists.

However, strings are immutable. Instead of changing a character directly, we create a new string.

city = "Nairobi"
updated_city = "K" + city[1:]
print(updated_city)  # Karobi
Enter fullscreen mode Exit fullscreen mode

There are several string methods that are especially useful when handling user input and real-world data.

Method Purpose
lower() and upper() Standardise letter case
strip() Remove surrounding whitespace
split() Break a string into a list
join() Combine strings using a separator
find() Locate a substring, returning -1 if absent
replace() Replace part of a string
count() Count occurrences
startswith() and endswith() Check prefixes and suffixes
isdigit() Check whether a string contains digits

For example, consider a record containing unnecessary spaces:

dirty = "  Moses, 23, Nairobi, Data Science  "
parts = dirty.split(",")
clean_parts = [item.strip() for item in parts]
print(clean_parts)
Enter fullscreen mode Exit fullscreen mode

Here, split() separates the fields and strip() cleans each field. This is a simple example of data cleaning before analysis.

You can also use validation methods with loops to keep requesting input until the user entered an acceptable value. For example, isdigit() can help validate a whole-number input before converting it to an integer. One limitation is that isdigit() does not accept every valid numeric format. It rejects values such as negative numbers and decimals, so validation should match the type of input expected.

6. Bringing the Concepts Together: Bundle Purchase Simulator

The capstone project was a command-line bundle purchase simulator inspired by the *544# menu. It allows a user to browse data, SMS and minutes bundles, confirm a purchase, check their airtime balance or exit the application.

Here is the complete implementation of the bundle purchase simulator.

# =========================================================
# CAPSTONE PROJECT: Bundle Purchase Simulator (*544#)
# =========================================================

bundles = {
    "Data": [
        {"name": "50MB - Daily",   "price": 5,   "validity": "24 hours"},
        {"name": "500MB - Weekly", "price": 50,  "validity": "7 days"},
        {"name": "2GB - Monthly",  "price": 200, "validity": "30 days"},
    ],
    "SMS": [
        {"name": "20 SMS - Daily",   "price": 5,  "validity": "24 hours"},
        {"name": "200 SMS - Weekly", "price": 20, "validity": "7 days"},
    ],
    "Minutes": [
        {"name": "10 Min - Daily",    "price": 10,  "validity": "24 hours"},
        {"name": "100 Min - Monthly", "price": 100, "validity": "30 days"},
    ],
}

balance = 200.00  # Starting airtime balance in Ksh


# Step 1: Main Menu
def main_menu():
    print("\n========================================")
    print("                *544#                   ")
    print("========================================")
    print("1. Buy Data Bundle")
    print("2. Buy SMS Bundle")
    print("3. Buy Minutes Bundle")
    print("4. Check Balance")
    print("5. Exit")
    print("========================================")
    choice = input("Enter choice (1-5): ").strip()
    return choice


# Step 2: Show Bundles Sub-menu
def show_bundles(category, bundle_list):
    print(f"\n--- {category.upper()} BUNDLES ---")
    for index, bundle in enumerate(bundle_list, start=1):
        name = bundle["name"]
        price = bundle["price"]
        validity = bundle["validity"]
        print(f"{index}. {name:<16} | Ksh {price:<3} | Valid: {validity}")
    print("0. Back to main menu")


# Step 3: Buy Bundle Handler
def buy_bundle(category, bundle_list, balance):
    while True:
        show_bundles(category, bundle_list)
        choice_input = input("\nEnter choice: ").strip()

        # Validate digits input
        if not choice_input.isdigit():
            print("Invalid input! Please enter a valid number.")
            continue

        choice = int(choice_input)

        # Go back to main menu
        if choice == 0:
            break

        # Check range validity
        if 1 <= choice <= len(bundle_list):
            selected_bundle = bundle_list[choice - 1]
            name = selected_bundle["name"]
            price = selected_bundle["price"]

            confirm = input(f"Confirm purchase of {name} for Ksh {price}? (y/n): ").strip().lower()

            if confirm == 'y':
                if balance >= price:
                    balance -= price
                    print(f"Purchase successful! New balance: Ksh {balance:.2f}")
                else:
                    print("Insufficient balance. Please top up.")
            else:
                print("Transaction cancelled.")
        else:
            print(f"Invalid option! Please enter a number between 0 and {len(bundle_list)}.")

    return balance


# Step 4: Check Balance
def check_balance(balance):
    print(f"\nYour current balance is Ksh {balance:.2f}")


# Step 5: Main Program Loop
def main():
    global balance

    while True:
        user_choice = main_menu()

        if user_choice == '1':
            balance = buy_bundle("Data", bundles["Data"], balance)
        elif user_choice == '2':
            balance = buy_bundle("SMS", bundles["SMS"], balance)
        elif user_choice == '3':
            balance = buy_bundle("Minutes", bundles["Minutes"], balance)
        elif user_choice == '4':
            check_balance(balance)
        elif user_choice == '5':
            print("\nThank you for using *544#. Goodbye!")
            break
        else:
            print("\nInvalid choice! Please select an option between 1 and 5.")

# Run the application
main()
Enter fullscreen mode Exit fullscreen mode

The project brought together several concepts;

Data organisation: A dictionary stores the bundle categories, while lists hold the available bundles. Each bundle is represented by another dictionary containing its name, price and validity period.

Functions: Separate functions handle the main menu, display available bundles, process purchases and check the balance. This keeps the code organised and makes each function responsible for a specific task.

Loops and conditional statements: The main loop keeps the application running until the user chooses to exit. Conditional statements determine which menu option to execute.

Input validation: The program checks whether the user's selection contains digits and whether it falls within the available options. Invalid input prompts the user to try again instead of immediately ending the program.

Indexing: Menu selections start at 1, but Python lists start at index 0. Therefore, the selected bundle is retrieved using choice - 1.

Functions and return values: The purchase function receives the current balance, checks whether the user can afford the selected bundle and returns the updated balance.

The purchase logic is the most important part of the simulator:

if balance >= price:
    balance -= price
    print(f"Purchase successful! New balance: Ksh {balance:.2f}")
else:
    print("Insufficient balance. Please top up.")
Enter fullscreen mode Exit fullscreen mode

The balance is deducted only when sufficient funds are available. The simulator also asks the user to confirm the purchase before processing it.

This is a simplified simulation rather than a real ussd integration, but it demonstrates how data structures, functions, loops and validation work together in a small application.

Final Thoughts

These exercises helped me understand that choosing the right data structure depends on what the program needs to do. Lists work well for ordered collections, tuples for immutable groups of values, dictionaries for labelled data and sets for unique elements and comparisons.

String manipulation and input validation are equally important because data is not always clean and users do not always enter what a program expects.

The bundle purchase simulator brought these ideas together in one practical project. It was a useful way to move beyond individual Python methods and see how different concepts can work together to solve a problem.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more