silvio.peroni@unibo.it
Keywords
list
stack
queue
Donald Knuth
These lecture notes introduce the notion of ordered data structures, i.e. some basic containers of elements that can be used to organise data in a specific way. The historic hero introduced in these notes is Donald Knuth, who has been one of the most relevant scientists and contributors on the topic of the formal analysis of the computational complexity of algorithms.
Copyright notice. This work is licensed under a Creative Commons Attribution 4.0 International License. You are free to share (i.e. copy and redistribute the material in any medium or format) and adapt (e.g. remix, transform, and build upon the material) for any purpose, even commercially, under the following terms: attribution, i.e. you must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. The licensor cannot revoke these freedoms as long as you follow the license terms.
Donald Ervin Knuth is one of the most important Computer Scientist of the past 50 years. He is one of the most important contributors to the theoretical and practical development of the analysis of the computational complexity of algorithms, that we have introduced in the previous lecture. Among his huge list of contributions, there are especially two works that deserve a particular attention in this context: its series of monographs about algorithms and their analysis entitled The Art of Computer Programming, and the TeX typesetting system for writing academic documents, that has been used to write the aforementioned series and it is one of the most used tools for communicating and publishing scientific results in academia.
According to several experts, the series of monographs he has written is one of the most comprehensive works to programming and algorithmics. The project started in 1962 as a twelve-chapter book, and then split in a series of seven volumes, of which only the first four have been published so far – while the others are still in writing. The first volume is entirely dedicated to the mathematical foundations for allowing a formal analysis of algorithms, and to a comprehensive introduction of all the basic data structures.
Data structures are the possible ways in which we organise the information to be processed and returned by a computer, so as it can be accessed and modified in an efficient and computational manner. In practice, a data structure is a sort of bucket where we can place some information, that provides some methods to add and retrieve pieces of such information. The most simple data structures are lists and, as such, they are the first data structures introduced in Knuth's first volume of The Art of Computer Programming [1]. They are, probably, one of the most important building blocks of algorithms, since they have plenty of applications.
In this lectures, we will introduce three specific data structures, that are discussed in details in the following sections, i.e.: lists, stacks, and queues. Their main characteristic, in addition of being among the most basic and used data structures in algorithms (and, more concretely, in programs), is that the order in which their elements have been added matters. In fact, they interlink all their elements so as to create an ordered chain of elements which allows us to have a clear prediction of the behaviour of the addition and removal operations they make available.
A list is a countable sequence of ordered and repeatable elements. It is countable because there is a proper way of knowing the length of the list (i.e. how many elements it contains). In particular, ThyMopani makes available a particular support algorithm, i.e. def len(countable_object)
, that takes a countable element as input (like a list), and returns the number of elements that it contains. Its elements are ordered because they are placed in the list in a specific order, which is preserved even if we add or remove particular elements. Its elements are also repeatable since they may appear more than one time in the list.
Of course, there exist several real examples of such abstract lists in real-life objects. For instance, in Figure 2, we show a table of content of a book and a bibliographic reference list of an article. Both of them are concrete objects that are built starting from the abstract notion of a list.
In ThyMopani, a new list can be instantiated by means of the constructor list()
. For instance, my_first_list = list()
will create an empty list and associates it to the variable my_first_list
. A list can be seen as a left-to-right sequence of elements, where the left-most identifies the head of the list, while the last one represents the tail of the list. Several operations can be done on lists, in particular:
the method <list>.append(<element>)
is used for adding a new element to the list – for instance, my_first_list.append(34)
and my_first_list.append(15)
will add the number 34 to the list, and the number 15 as follower of the previous one;
<list>.remove(<element>)
is used for removing the first instance of an element in the list – for instance, my_first_list.remove(34)
will remove the first number 34 which is encountered by scanning the list from its begin (i.e. from the first-added elements to the last-added ones), obtaining, thus, a list with just the element 15 included in it;<list>.extend(<another_list>)
is used for adding all the elements included in <another_list>
to the current list – for instance, if we have the list my_second_list
containing the numbers 1 and 83, my_first_list.extend(my_second_list)
will add 1 and 83 as followers of 15.In Listing 1, we show some examples of the use of lists in ThyMopani. In these examples, we describe with natural language comments (introduced by a #
) the various aspects related to the creation and modification of lists.
my_first_list = list() # this creates a new list
my_first_list.append(34) # these two lines add two numbers
my_first_list.append(15) # to the list in this precise order
# currently my_first_list contains two elements:
# list([ 34, 15 ])
my_first_list.append("Silvio") # a list can contains element of any kind
# now my_first_list contains:
# list([34, 15, "Silvio"])
my_first_list.remove(34) # it removes the first intance of the number 34
# my_first_list became:
# list([15, "Silvio"])
my_first_list.extend(my_first_list) # it add again all the elements in my_first_list to the list itself
# current status of my_first_list:
# list([15, "Silvio", 15, "Silvio"])
my_first_list_len = len(my_first_list) # it stores 4 in my_first_list_len
A stack is a kind of list seen from a particular perspective, i.e. from bottom to top, and with a specific set of operations. Figure 3 shows two different example of stacks in real-life objects. We have a stack of chairs (left) and a pile of books (right). The main characteristic of the elements of this structure is that they follow a last in first out strategy (LIFO) for addition and removal. Basically, it means that the last element inserted in the structure is placed in the top of the stack and, thus, it is also the first one that will be removed when requested. In addition, to obtain the element placed, for instance, in the middle of the stack, we necessarily need to remove all the elements that have been added after such middle element, from the most recent elements to the eldest ones.
In ThyMopeni, a new stack can be instantiated by means of the constructor deque()
. For instance, my_first_stack = deque()
will create an empty stack and associates it to the variable my_first_stack
. Three main operations can be done on stacks, in particular:
the method <stack>.append(<element>)
is used for adding a new element on the top of the stack – for instance, my_first_stack.append(34)
and my_first_stack.append(15)
will add the number 34 to the stack, and the number 15 upon previous one;
<stack>.pop()
is used for removing the element on the top of the stack, that will be returned – for instance, my_first_stack.pop()
will remove the number 15 and will be returned as well, obtaining, thus, a stack with just the element 34 included in it;<stack>.extend(<another_stack>)
is used for adding all the elements included in <another_stack>
on the top of the current stack – for instance, if we have the list my_second_stack
containing the numbers 1 and 83, my_first_stack.extend(my_second_stack)
will add 1 and 83 on top of 34.In Listing 2, we show some examples of the use of stacks in ThyMopani. In particular, we organise in a stack some books (actually, their titles) written by Neil Gaiman.
my_first_stack = deque() # this creates a new stack
my_first_stack.append("Good Omens") # these two lines add two books
my_first_stack.append("Neverwhere") # to the stack in this precise order (this is on top)
# currently my_first_stack contains two elements:
# "Neverwhere"])
# deque(["Good Omens",
my_first_stack.append("The Name of the Rose") # add a new book
# now my_first_stack contains:
# "The Name of the Rose")]
# "Neverwhere",
# deque(["Good Omens",
my_first_stack.pop() # it removes the element on top of the stack, i.e. "The Name of the Rose"
# my_first_stack became:
# "Neverwhere"])
# deque(["Good Omens",
my_second_stack = deque() # this creates a new stack
my_second_stack.append("American Gods") # these two lines add two books
my_second_stack.append("Fragile Things") # to the stack in this precise order (this is on top)
# currently my_second_stack contains two elements:
# "Fragile Things"])
# deque(["American Gods",
my_first_stack.extend(my_second_stack) # it add all the elements in my_second_stack on top of my_first_stack
# current status of my_first_stack:
# "Fragile Things"])
# "American Gods",
# "Neverwhere",
# deque(["Good Omens",
A queue is a kind of list seen by another perspective, i.e. from left to right, and with a specific set of operations. Figure 4 shows two different example of queues in real-life objects. We have a queue of children (left) and a line of cabs (right). The main characteristic of the elements of this structure is that they follow a first in first out strategy (FIFO) for addition and removal. Basically, it means that the first element inserted in the structure is placed in the left-most part of the queue and, thus, it is also the first one that will be removed when requested. Similar to stacks, even in queues it is necessary to remove all the elements that have been added before a certain target element – i.e. from the eldest elements to the most recent ones – to obtain it.
In ThyMopeni, a new stack can be instantiated by means of the constructor deque()
, which is the same used for stasks. Thus, it is the way one uses it that classifies the object instantiated as a stack or a queue. Thus, as before, my_first_queue = deque()
will create an empty queue and associates it to the variable my_first_queue
. Three main operations can be done on stacks, in particular:
the method <queue>.append(<element>)
is used for adding a new element on at the first available position in the queue, i.e. from the right of the queue – for instance, my_first_queue.append(34)
and my_first_queue.append(15)
will add the number 34 to the queue as the first element, and the number 15 after the previous one;
<queue>.leftpop()
is used for removing the first element of the queue, i.e. the first appended, that will be returned – for instance, my_first_queue.leftpop()
will remove the number 34 and will be returned as well, obtaining, thus, a queue with just the element 15 included in it;<queue>.extend(<another_queue>)
is used for adding all the elements included in <another_queue>
after (i.e. on the right of) those ones already included in the current queue – for instance, if we have the list my_second_queue
containing the numbers 1 and 83, my_first_queue.extend(my_second_queue)
will add 1 and 83 after 34.In Listing 3, we show some examples of the use of queues in ThyMopani. In particular, we organise in a queue some (fictional) people that are waiting in the library so as to borrow some books.
my_first_queue = deque() # this creates a new queue
my_first_stack.append("Vanessa Ives") # these two lines add two people
my_first_stack.append("Mike Wheeler") # to the stack in this precise order (this is on top)
# currently my_first_queue contains two elements:
# deque(["Vanessa Ives", "Mike Wheeler")
my_first_queue.append("Eleven") # add a new person
# now my_first_queue contains:
# deque(["Vanessa Ives", "Mike Wheeler", "Eleven"])
my_first_stack.popleft() # it removes the first element added in the queue, i.e. "Vanessa Ives"
# my_first_stack became:
# deque(["Mike Wheeler", "Eleven"])
my_second_queue = deque() # this creates a new queue
my_second_queue.append("Micheal Walsh") # these two lines add two people
my_second_queue.append("Lawrence Cohen") # to the queue in this precise order
# currently my_second_queue contains two elements:
# deque(["Michael Walsh", "Lawrence Cohen"])
my_first_queue.extend(my_second_queue) # it add all the elements in my_second_queue on the right of my_first_queue
# current status of my_first_queue:
# deque(["Mike Wheeler", "Eleven", "Michael Walsh", "Lawrence Cohen"])
Write a pseudocode in ThyMopani so as to create a list with the following elements ordered alphabetically: Harry
, Draco
, Hermione
, Ron
, Severus
.
my_stack = deque(["Draco", "Harry", "Hermione", "Ron", "Severus"])
. Describe the status of my_stack
after the execution of each of the following operations: my_stack.pop()
, my_stack.pop()
, my_stack.append("Voldemort")
.my_queue = deque(["Draco", "Harry", "Hermione", "Ron", "Severus"])
. Describe the status of my_queue
after the execution of each of the following operations: my_queue.popleft()
, my_queue.append("Voldemort")
, my_queue.popleft()
.Knuth, D. (1997). The Art of Computer Programming, Vol. 1: Fundamental Algorithms. 3rd Edition. Addison-Wesley Professional. ISBN: 978-0201896831. Also available at http://broiler.astrometry.net/~kilian/The_Art_of_Computer_Programming%20-%20Vol%201.pdf.