data structures

Table of Contents

there are many types of data structures in python. here are a few:

1. lists

  • ordered
  • items in a list are called elements, which are accessible via index.
  • supports slicing:
    • syntax: list_name[start:stop:step]
  • mutable, meaning that any changes to a list happen within the same list.
  • elements can be of different types.
  • use a list when:
    • you know you need to make changes to the data collection later on (adding, removing, etc.)
    • when the order of the list sequence matters
    • the size of the collection is not fixed.
  • example: a grocery list
    • you need to be able to add items when they come to mind or remove them when you buy something.
  • here are some common functions/methods used on lists:
print("indexing into a list:")

# list example
a_list = ["a", "b", "c"]

# use square brackets and indexing to retrieve an element from the list
print(a_list[1]) # prints b

print()
print("getting the length of a list:")

# get length of list
print(len(a_list)) # prints 3

print()
print("getting the indices of a list:")

# since we often know the length of a list using the len() function,
# we typically use a for loop to iterate through each index in the list.
for i in range(len(a_list)):
    print(i)

# prints out:
# 0
# 1
# 2

print()
print("getting the values of a list via looping by index:")

# we can also iterate through each element in the list using indexing.
for i in range(len(a_list)):
    print(a_list[i])

# prints out:
# a
# b
# c

print()
print("getting the values of a list via looping by value:")

# alternatively, we can iterate through each element in the list by value.
for element in a_list:
    print(element)

# prints out:
# a
# b
# c

print()
print("getting indices and corresponding value using enumerate():")

# you can simplify looping through iterables (lists, tuples, strings)
# when you need both the index and value using the enumerate() function.
# side note: the str() function converts an object
# (such as a float or integer) into a string.
for index, value in enumerate(a_list):
    print(str(index) + ", " + value)

# prints out:
# 0, a
# 1, b
# 2, c

print()
print("getting type list:")

# get the type "list"
print(type(a_list)) # prints <class 'list'>

print()
print("using list constructor to create a list:")

# list constructor creates a new list
b_list = list((1, 2, 3))
print(b_list) # prints [1, 2, 3]

print()
print("appending an element to a list:")

# adds a new element to the end of the list
b_list.append(4)
print(b_list) # prints [1, 2, 3, 4]

print()
print("inserting an element into a list:")

# inserts an element at a specific index in the list
a_list.insert(1, "d")
print(a_list) # prints ["a", "d", "b", "c"]

print()
print("removing an element from a list:")

# removes the first occurrence of a specific value from the list
a_list.remove("b")
print(a_list) # prints ["a", "d", "c"]

print()
print("popping an element from a list:")

# removes and returns the element at the specific index.
# if the index is not provided, then it removes and returns the last element.
b_list.pop(1)
print(b_list) # prints [1, 3, 4]

print()
print("popping the last element from a list:")

b_list.pop()
print(b_list) # prints [1, 3]

print()
print("sorting a list in-place:")

# sorts the elements in-place, modifying the original list.
a_list.sort()
print(a_list) # prints ["a", "c", "d"]

print()
print("reversing a list in-place:")

# reverses the order of elements in the list in-place.
b_list.reverse()
print(b_list) # prints [3, 1]

print()
print("returning the minimum item in a list:")

# returns the minimum item in the list
print(min(b_list)) # prints 1

print()
print("returning the maximum item in a list:")

# returns the maximum item in the list
print(max(b_list)) # prints 3

print()
print("returning the sum of the items in a list:")

# returns the sum of the items in the list (supports a mix of floats and integers)
print(sum(b_list)) # prints 4

print()
print("basic slicing:")

c_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# slicing from start to end with default step = 1:
print(c_list[2:7]) # prints [2, 3, 4, 5, 6]

print()
print("slicing with default start:")

# slicing from the beginning with default start = 0 and default step = 1:
print(c_list[:5]) # prints [0, 1, 2, 3, 4]

print()
print("slicing with default stop:")

# slicing to the end with default stop = (end of list) and default step = 1:
print(c_list[4:]) # prints [4, 5, 6, 7, 8, 9]

print()
print("slicing with step:")

# slicing with a step:
print("example 1:", c_list[1:9:2]) # prints [1, 3, 5, 7]
print("example 2:", c_list[::3]) # prints [0, 3, 6, 9]

print()
print("slicing with negative indices:")

# slicing with negative indices:
print("example 1:", c_list[-3:]) # prints [7, 8, 9]
print("example 2:", c_list[:-2]) # prints [0, 1, 2, 3, 4, 5, 6, 7]
print("example 3:", c_list[-5:-2]) # prints [5, 6, 7]

print()
print("using slicing to reverse a list:")
        
# using slicing to reverse a list:
print(c_list[::-1]) # prints [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

indexing into a list:
b

getting the length of a list:
3

getting the indices of a list:
0
1
2

getting the values of a list via looping by index:
a
b
c

getting the values of a list via looping by value:
a
b
c

getting indices and corresponding value using enumerate():
0, a
1, b
2, c

getting type list:
<class 'list'>

using list constructor to create a list:
[1, 2, 3]

appending an element to a list:
[1, 2, 3, 4]

inserting an element into a list:
['a', 'd', 'b', 'c']

removing an element from a list:
['a', 'd', 'c']

popping an element from a list:
[1, 3, 4]

popping the last element from a list:
[1, 3]

sorting a list in-place:
['a', 'c', 'd']

reversing a list in-place:
[3, 1]

returning the minimum item in a list:
1

returning the maximum item in a list:
3

returning the sum of the items in a list:
4

basic slicing:
[2, 3, 4, 5, 6]

slicing with default start:
[0, 1, 2, 3, 4]

slicing with default stop:
[4, 5, 6, 7, 8, 9]

slicing with step:
example 1: [1, 3, 5, 7]
example 2: [0, 3, 6, 9]

slicing with negative indices:
example 1: [7, 8, 9]
example 2: [0, 1, 2, 3, 4, 5, 6, 7]
example 3: [5, 6, 7]

using slicing to reverse a list:
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

2. tuples

  • ordered
  • unlike lists, they are immutable, meaning that once a tuple is created, it cannot be changed.
    • any changes made to it require creating a copy.
  • similar to lists, it can store elements of different data types.
  • can access values via indexing.
  • use a tuple when:
    • the collection of items should not be modified.
    • the size of the collection will not change.
    • when a function needs to return multiple values.
  • example: (x, y, z) coordinates and RGB color values.
    • based on what you’re using these for, you might not want to be able to easily change its data.
  • tuples tend to have some common or similar functions/methods that can also be used on lists.
  • here are some common functions/methods used on tuples:
# tuple example
a_tuple = (1, 2, 3, 2, 1)

print("indexing into a tuple:")

# like lists, tuples also support indexing.
print(a_tuple[2]) # prints 3

print()
print("counting the number of times a value appears in a tuple:")

# counts the number of times a specific value appears within the tuple
a_count = a_tuple.count(1)
print(a_count) # prints 2

print()
print("returns the index of the first occurrence of a specific value:")

# searches the tuple for a specific value and returns the index of the first occurrence of it.
a_index = a_tuple.index(2)
print(a_index) # prints 1

print()
print("converting a collection of items into a tuple:")
        
# converts a collection of items into a tuple
b_tuple = tuple([5, 4, 3, 2, 1])
print(b_tuple) # prints (5, 4, 3, 2, 1)

indexing into a tuple:
3

counting the number of times a value appears in a tuple:
2

returns the index of the first occurrence of a specific value:
1

converting a collection of items into a tuple:
(5, 4, 3, 2, 1)

3. namedtuples

  • an extension of regular tuples.
  • unlike regular tuples, which use indexing, elements can be accessed via descriptive field names using dot notation.
    • helps with code readability.
  • similar to regular tuples, namedtuple objects are also immutable.
    • in order to make changes, a new copy of the namedtuple must be created.
  • there are two main parts of a namedtuple:
    • the name. (the very first item when defining a namedtuple)
    • the fields/attributes associated with it. (the remaining items when defining a namedtuple AKA the stuff in square brackets)
  • use a namedtuple when:
    • you want a simple, structured data container.
    • you want to group related data together.
  • example: a College namedtuple
    • may have fields for the college name, faculty population, student population, and mascot affiliated with the college.
    • typically, you only want to use namedtuples for objects that are nouns rather than verbs.
      • example: chess
        • it would be appropriate to have a Piece namedtuple that has the name of the piece tuple (king, queen, bishop, etc) and its coordinates on a 2-dimensional list.
        • it wouldn’t be appropriate to have a MovePiece namedtuple because moving a piece is an action rather than a noun. it would make more sense to use something like a function to execute this task.
  • here are some common functions/methods used on namedtuples:
# don't forget to import namedtuple from the collections library before implementing namedtuple!
from collections import namedtuple

print("original Piece namedtuple:")

# namedtuple definition example
Piece = namedtuple('Piece', ['piece_type', 'x', 'y'])

# creation of namedtuple instance example
queen = Piece(piece_type="queen", x=1, y=5)

# creates and returns a new namedtuple instance with
# the specified fields replaced by new values.
# make sure to assign it to a variable so that the modified namedtuple can be used later.
new_queen = queen._replace(x=2, y=4)
print(queen)

print()
print("modified Piece namedtuple:")

# should still print out the original namedtuple,
# Piece(piece_type="queen", x=1, y=5)

print(new_queen)

# prints out the new namedtuple with modified fields,
# Piece(piece_type="queen", x=2, y=4)

print()
print("prints the Piece namedtuple fields, piece type, x, and y, in that order:")

# use dot notation to access namedtuple fields
print(new_queen.piece_type) # prints queen
print(new_queen.x) # prints 2
print(new_queen.y) # prints 4

original Piece namedtuple:
Piece(piece_type='queen', x=1, y=5)

modified Piece namedtuple:
Piece(piece_type='queen', x=2, y=4)

prints the Piece namedtuple fields, piece type, x, and y, in that order:
queen
2
4

4. sets

  • unordered
  • sets only store unique elements.
    • if you try to add a duplicate element, it will be ignored and the set will remain unchanged.
  • mutable (can add or remove elements)
    • individual elements in a set must be immutable.
  • iterable
  • use a set when:
    • you need a collection of unique items.
    • order of your collection doesn’t matter.
    • you want to use mathematical set operations on your collection. (ics 6b type stuff)
  • example: a collection of letters.
    • useful when you want to have unique, specific alphabet/grammar for a grammar parser.
  • here are some common functions/methods used on sets:
print("adds an element to a set:")

# set example
a_set = {1, 2}

# add an element to a set.
a_set.add(3)
print(a_set) # prints {1, 2, 3} in no particular order.

print()
print("removes an element from a set:")

# removes a specific element from a set.
a_set.remove(2)
print(a_set) # prints {1, 3} in no particular order.

print()
print("pops an arbitrary element from a set:")

# removes and returns some arbitrary element from the set.
a_element = a_set.pop()
print(f"popped element: {a_element}") # will either print 1 or 3.
print(f"set after popped element: {a_set}") # will either print {1} or {3}.

print()
print("gets the union of two sets:")

# just some new sets
set1 = {1, 2}
set2 = {2, 3}

# returns a new set containing all unique elements from both sets.
union_set = set1.union(set2)
print(union_set) # prints {1, 2, 3} in no particular order.

print()
print("gets the intersection of two sets:")

# even more new sets
set1 = {1, 2, 3}
set2 = {2, 3, 4}

# returns a new set containing only elements that are in both sets.
intersection_set = set1.intersection(set2)
print(intersection_set) # prints {2, 3} in no particular order.

print()
print("gets the difference of two sets:")

# yep more new sets... again
set1 = {1, 2, 3}
set2 = {2, 4}

# returns a new set containing elements present in the first set but not in the second set.
difference_set = set1.difference(set2)
print(difference_set) # prints {1, 3} in no particular order.

print()
print("gets the symmetric difference of two sets:")
        
# finally... have some more new sets
set1 = {1, 2, 3}
set2 = {2, 3, 4}

symmetric_difference = set1.symmetric_difference(set2)
print(symmetric_difference) # prints {1, 4} in no particular order.

adds an element to a set:
{1, 2, 3}

removes an element from a set:
{1, 3}

pops an arbitrary element from a set:
popped element: 1
set after popped element: {3}

gets the union of two sets:
{1, 2, 3}

gets the intersection of two sets:
{2, 3}

gets the difference of two sets:
{1, 3}

gets the symmetric difference of two sets:
{1, 4}

5. dictionaries

  • store data as collection of key-value pairs.
    • keys must be immutable and unique, but values don’t have to be unique and can be either immutable or mutable.
    • therefore, dictionary values can be of any data type.
  • mutable
  • typically unordered
  • use a dictionary when:
    • you have two pieces of data associated with each other, which can be used to create key-value pairs.
    • order of elements doesn’t matter.
  • examples: student dictionary
    • where the keys are student IDs and the values are student names.
    • since student IDs are unique, they should be used as the key to access a student’s name.
    • two students can have the exact same name while still being distinguishable via their student IDs (keys).
  • here are some common functions/methods used on dictionaries:
print("retrieves value based on key using get() method:")

# dictionary example
a_dict = {"a": 1, "b": 2}

# returns the value associated with the key.
print(a_dict.get("b")) # prints 2

print()
print("updating a dictionary with multiple key-value pairs:")

# updates the dictionary with key-value pairs from another dictionary.
other_dict = {"c": 3, "d": 4}
a_dict.update(other_dict)
print(a_dict) # prints {"a": 1, "b": 2, "c": 3, "d": 4} in no particular order.

print()
print("popping a key-value pair:")

# removes the item with the specific key and returns its value.
d_value = other_dict.pop("d")
print(f"popped key: {d_value}") # prints 4
print(f"dictionary after popped key: {other_dict}") # prints {"c": 3}

print()
print("changing a key's value:")

# changes the value associated with a specific key
a_dict["a"] = 0
print(a_dict) # prints {"a": 0, "b": 2, "c": 3, "d": 4} in no particular order.

print()
print("changing multiple keys' values using update() method:")

# the update() method can be used to change the value of one or more key-value pairs at once.
other_other_dict = {"b": 1, "c": 2, "d": 3}
a_dict.update(other_other_dict)
print(a_dict) # prints {"a": 0, "b": 1, "c": 2, "d": 3} in no particular order.

print()
print("using a for loop to iterate through keys:")

# you can use for loops to iterate through the keys, values, or keys and values.
for key in a_dict:
    print(key)

# prints out:
# a
# b
# c
# d

print()
print("using a for loop and keys() method to iterate through keys:")

# you can use the keys() method to retrieve the keys from a dictionary.
for key in a_dict.keys():
    print(key)

# prints out:
# a
# b
# c
# d

print()
print("using values() method to iterate through values:")

# you can use the values() method to retrieve the values from a dictionary.
for value in a_dict.values():
    print(value)

# prints out:
# 0
# 1
# 2
# 3

print()
print("using items() method to iterate through keys and their respective values:")
        
# you can use the items() method to retrieve the keys and their respective values
# from a dictionary.
# side note: the str() function converts an object
# (such as a float or integer) into a string.
for key, value in a_dict.items():
    print(key + ", " + str(value))

# prints out:
# a, 0
# b, 1
# c, 2
# d, 3

retrieves value based on key using get() method:
2

updating a dictionary with multiple key-value pairs:
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

popping a key-value pair:
popped key: 4
dictionary after popped key: {'c': 3}

changing a key's value:
{'a': 0, 'b': 2, 'c': 3, 'd': 4}

changing multiple keys' values using update() method:
{'a': 0, 'b': 1, 'c': 2, 'd': 3}

using a for loop to iterate through keys:
a
b
c
d

using a for loop and keys() method to iterate through keys:
a
b
c
d

using values() method to iterate through values:
0
1
2
3

using items() method to iterate through keys and their respective values:
a, 0
b, 1
c, 2
d, 3

Author: Ashley Pock

Created: 11/10/2025 Mon