Stacks and Applications in Music
Photo by Monika Borys on Unsplash
A stack is a data structure where elements are only added and removed from the same end. This is described as LIFO, or Last In First Out. I like to think about it like a stack of plates, where the last plate in the stack is the first to be picked up.
The three main operations of a stack are:
Push (or insert): add an element to the top of the stack
Pop (or remove): remove an element from the top of the stack
Peek (or take a look): returns the element from the top of the stack without removing it
Thanks for reading Learning Data Structures and Algorithms with Music! Subscribe for free to receive new posts and support my work.
The best way to implement a stack in Python is by using a class because it makes it easier to use the data structure for more complex operations. In addition, we can utilize Python’s built-in list class to implement the stack. Based on the definition of a stack, the methods that we need from Python’s list class are pop() and append(). Note that pop() will raise an IndexError if the list is empty, so we have to check for this in the code. Additionally, we can’t peek at an item if the stack is empty, so we need to check for that too, or we will get an error.
When call the class object. we initialize with an empty list to hold the stack items.
class Stack:
def __init__(self):
self.items = []
Note that __init__ is Python’s constructor method that is called when you create a new object. With __init__, we set up the initial state, which is an empty list. Items here represents the ENTIRE stack. Self is just a parameter that receives the specific object. If we call different stacks, each self will reference a different stack.
Next, we will create the methods for the 3 operations. Let’s start with push(). Note that item __ here represents a single element for the stack.
def push(self, item):
self.items.append(item)
Now, we will create the pop() and peek() methods. Before we implement them, we need to create a helper function that checks to see if the stack is empty by checking if it is the same as an empty list.
def is_empty(self):
return self.items == []
This method returns True if the stack is empty and False if the stack is not empty. An alternative approach is to check to see if the length of the stack is 0, i.e. length(self.items) == 0.
To implement the pop() method, we will first check to see if the stack is empty. If not, we will remove the last element. Python’s pop() method in the list class already removes the last element from the list if you don’t give it an index.
def pop(self):
if self.is_empty(items):
raise IndexError(”Sorry, your stack is empty”)
else:
return self.items.pop()
Similarly, to implement the peek() method, we need to check if the stack is empty. After all, you can’t peek at something that has nothing to peek at! Then, we take a look at the last element, which is the topmost element of the stack and we return that element. Note that Python uses zero to index the first element of a list, so we have to subtract the length of the list by 1 to get the last element of the list.
def peek(self, item):
if self.is_empty(items):
raise IndexError(”Sorry, your stack is empty”)
else:
return self.items[len(self.items) - 1]
An alternative way of returning the last item is self.items[-1] because in Python, negative indices count from the end, so -1 gives you the last item of the list.
Wonderful! So here is our full Stack implementation.
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
if self.is_empty():
raise IndexError(”Sorry, your stack is empty”)
else:
return self.items.pop()
def peek(self):
if self.is_empty():
raise IndexError(”Sorry, your stack is empty”)
else:
return self.items[-1]
def is_empty(self):
return self.items == []
Note that when Python reads the class definition, it reads all of the methods and stores them. So order of the methods doesn’t matter here. Only when we call a method outside of the class does order matter.
It’s nice to see how simple data structure implementation really is, especially if you utilize existing data structures.
Stack data structures have several interesting applications in music and music technology. In particular, stacks can be used for:
Undo/redo operations that are based on the most recent operation, which are utilized in Digital Audio Workstations (DAW).
Parsing musical notation if there are matching elements, like phrase marks in music, which are curved lines connecting notes.
Building chords.
I will explore some of these musical applications in the next posts!
Thanks for reading Learning Data Structures and Algorithms with Music! Subscribe for free to receive new posts and support my work.