a = Stack() # []
a.push('one') # ['one']
a.push('two') # ['one', 'two']
a.top() # 'two'
a.push('three') # ['one', 'two', 'three']
a.top() # 'three'
a.size() # 3
a.pop() # 'three' ['one', 'two']
a.pop() # 'two' ['one']
This is the API I want to have for the ‘Stack’ data structure.
Let’s implement it in Python
step by step.
The official documentation recommends using ’list’ when you need ‘Stack’.
I prefer to have a dedicated class with all the treats under the hood instead of just list
and its append
instead of push
.
So let’s define the class
class Stack:
__list = []
An alternative way of defining and instantiating the instance variable would be with constructor like this
class Stack:
def __init__(self):
self.__list = []
But at this point, this looks like overkill, so let’s stick to the first variant.
Two _
in the __list
name are there to make it kind of “private” and prevent accessing it directly like this
a = Stack()
a.__list # AttributeError: 'Stack' object has no attribute '__list'
First things first, I want this class to be a bit more user-friendly. Currently, it looks like this
Stack()
# <__main__.Stack object at 0x10df87fd0>
which is not helpful of course.
In Python this is done with the help of __str__()
method that should return string.
It would be logical to show the string representation of the __list
content.
For this to happen there is str()
builtin function
which is just the instantiation of the str
class. Isn’t that clever?
Here is what it looks like
class Stack:
__list = []
def __str__(self):
return str(self.__list)
That’s it.
Now it behaves a bit more user friendly
a = Stack()
print(a)
# []
But without print
, it is still cryptic
Stack()
# <__main__.Stack object at 0x10df87fd0>
For this to “fix” there is __repr__()
# <skipped>
def __repr__(self):
return str(self)
Let’s check it out
Stack()
# []
Yay! 🎉
Next, let’s implement the stack’s push
method.
class Stack:
# <skipped>
def push(self, el):
return self.__list.append(el)
Yep. Our stack grows to the right and “pops” back to the left.
The appending and removing values from the end of the list
are O(1)
operations in Python.
Check
a = Stack() # []
a.push('one')
a # ['one']
Now to the pop
.
Luckily, the list
already has the pop method
that removes and returns the last item in the list
; exactly what we need.
class Stack:
# <skipped>
def pop(self):
return self.__list.pop()
Check
a = Stack() # []
a.push('one') # ['one']
a.push('two') # ['one', 'two']
a.pop() # 'two'
a # ['one']
a.pop() # 'one'
a # []
So far so good. There are two top
and size
methods left.
The size
is the same as len()
builtin function
and the top
is just a reading of the first element.
class Stack:
# <skipped>
def size(self):
return len(self.__list)
def top(self):
return self.__list[0]
But here is a catch. If the list is empty, the top
will raise the IndexError
error
a = Stack()
a.top() # IndexError: list index out of range
So we need some kind of a fallback
to None
value. This is the null
in Python.
Here is the adjusted version of the top
method
class Stack:
# <skipped>
def top(self):
return None if self.size() == 0 else self.__list[0]
It looks like we have everything in place. Here is the full code of the Stack
class
class Stack:
__list = []
def push(self, el):
self.__list.append(el)
return self
def pop(self):
return self.__list.pop()
def size(self):
return len(self.__list)
def top(self):
return None if self.size() == 0 else self.__list[0]
def __str__(self):
return str(self.__list)
def __repr__(self):
return str(self)
And it works as expected
a = Stack() # []
a.push('one') # ['one']
a.push('two') # ['one', 'two']
a.top() # 'two'
a.push('three') # ['one', 'two', 'three']
a.top() # 'three'
a.size() # 3
a.pop() # 'three' ['one', 'two']
a.pop() # 'two' ['one']
Let’s add the cherry 🍒 on the top 😊. Let’s allow for initial values.
To make it happen we need a constructor
method, which is __init__()
in Python.
class Stack:
# __list = [] <= delete this line
def __init__(self, initial=[]):
self.__list = list(initial)
The initial=[]
is the nice “trick” to provide a default value, so we still can initialize it empty a = Stack()
.
But when we need to provide an initial collection
of values, we could do it as Stack([1,2,3])
.
Also, the use of the list() class instantiator allows for any collection
-like type to be used. E.g.
a = Stack( [1,2,3] )
a = Stack( (1,2,3) )
At this point, we have a pretty “full-fledged” Stack
class at our disposal 🎉
I am using this not so complex example on purpose.
This way it is easier to learn bit by bit the different idioms along the way, and at the same time not being bored and overwhelmed by a complex problem at hand.