Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
397: Implementing a Doubly Linked List
We've already covered singly linked lists, but they have a glaring limitation: they're a one-way street. If you're at the end of the list and want to see the previous element, you're out of luck unless you start all the way back at the head. In the real world, we often need to traverse in both directions. Think of your browser's back and forward buttons—that's a classic Doubly Linked List (DLL) use case.
Let's build a simple BrowserHistory manager. We want to be able to visit new pages, go back a few steps, and then move forward again.
Defining the bidirectional node
First, we need a node that knows about both its successor and its predecessor. I usually keep the node class as slim as possible. It doesn't need any logic; it just needs to hold data and two pointers.
class PageNode:
def __init__(self, url):
self.url = url
self.next = None
self.prev = None
Now we can build the main manager. I'll track the current page specifically, because in a browser, the "active" page isn't always the most recent one you visited—it depends on how many times you've hit the back button.
Navigating the history chain
I'll start by implementing the visit and back methods. The idea is that every time we visit a new page, we attach it to the current node and shift our focus forward.
class BrowserHistory:
def __init__(self, homepage):
self.current = PageNode(homepage)
def visit(self, url):
new_page = PageNode(url)
self.current.next = new_page
new_page.prev = self.current
self.current = new_page
def back(self, steps):
while steps > 0 and self.current.prev:
self.current = self.current.prev
steps -= 1
return self.current.url
def forward(self, steps):
while steps > 0 and self.current.next:
self.current = self.current.next
steps -= 1
return self.current.url
Fixing the "Ghost History" bug
I just noticed a problem. If I visit Page A, then Page B, then go back() to Page A, and then visit Page C... my current code just attaches Page C to Page A. But Page B is still hanging out there in memory as Page A's next. In a real browser, if you go back and then visit a new site, you effectively "wipe" the forward history. My visit method is currently ignoring that.
I need to make sure that when we visit a new page, we explicitly break the link to any existing "forward" pages. Here is the correction:
def visit(self, url):
new_page = PageNode(url)
# The fix: we don't just link; we overwrite any existing 'next'
self.current.next = new_page
new_page.prev = self.current
# Now the old forward chain is orphaned and will be garbage collected
self.current = new_page
It looks like the same code, but the mental shift is important: self.current.next = new_page doesn't just add a link; it destroys the path to everything that used to come after the current node. That's exactly what we want for a browser history.
Testing the flow
If we run this, we can see the bidirectional movement in action. We can jump back and forth, and the prev and next pointers handle the heavy lifting without us needing to manage an array or a list of indices.
history = BrowserHistory("google.com")
history.visit("github.com")
history.visit("stackoverflow.com")
print(history.back(1)) # Output: github.com
print(history.back(1)) # Output: google.com
print(history.forward(1)) # Output: github.com
history.visit("python.org") # This wipes stackoverflow.com
print(history.forward(1)) # Nothing happens, we are at the end
print(history.current.url) # Output: python.org
The beauty of the DLL here is that back and forward are extremely efficient. We aren't shifting elements in an array; we're just sliding a pointer along a chain.
📋 Practical Task
Implementing a Music Playlist Navigator
You are building a music player. Unlike a browser history, a playlist is often a "Circular Doubly Linked List"—meaning the last song points back to the first, and the first song points back to the last.
Your Task:
Implement a Playlist class. It should have:
- A
add_song(song_name)method that adds a song to the end of the list. - A
next_song()method that moves to the next song (and wraps around to the start if at the end). - A
prev_song()method that moves to the previous song (and wraps around to the end if at the start).
add_song method correctly maintains the circular links between the head and the tail of the list. Test it by adding three songs and calling next_song() four times to see if it wraps around correctly.There are no comments for now.