Python101/Session 2 - Data Structures/Session 2 - Data Structures...

310 KiB

<html> <head> </head>

Session 2

Use a locally installed editor like Visual Studio Code or this online editor: https://www.onlinegdb.com/online_python_compiler

Data Structures

Data structures are constructs that can contain one or more variables.

There are four basic data structures in Python:

  • Lists
  • Dictionaries
  • Tuples
  • Sets

Lists

Lists are defined by sqaure brackets [] with elements separated by commas. They can have elements of any data type.

Lists are probably the most used data structure.

Mutability

Lists are mutable. They can be changed after creation.

List examples

Some examples of lists:

In [1]:
a = [10, 20, 30, 40]
b = [1, True, 'Hi!', 4.3]                       # Multiple data types in the same list
c = [['Nested', 'lists'], ['are', 'possible']]  # List of lists

Dictionaries

Dictionaries have key/value pairs. This can for some problems be easier than having two lists that relate to eachother by index.

Syntax : {key1: value1, key2: value2, ..., key_n, value_n}

Note that values can be of any datatype like floats, strings etc., but they can also be lists or other data structures.

Keys must be unique within the dictionary. Otherwise it would be hard to extract the value by calling out a certain key, see the section about indexing and slicing below.

Keys also must be of an immutable type.

Mutability

Dictionaries are mutable. They can be changed after creation.

Dictionary examples

Some examples of dictionaries:

In [2]:
my_dict1 = {'axial_force': 319.2, 'moment': 74, 'shear': 23}         # Strings as keys and numbers as values
my_dict2 = {1: True, 'hej': 23}                                      # Keys are different types (ints and strings)
my_dict3 = {'Point1': [1.3, 51, 10.6], 'Point2': [7.1, 11, 6.7]}     # Strings as keys and lists as values

As with most stuff in Python the flexibility is very nice, but it can also be confusing to have many different types mixed in the same data structure. To make code more readable, it is often preferred to keep the same trend throughout the dictionary. I.e. all keys are of same type and all values are of the same type.

The keys and values can be extracted separately by the methods dict.keys() and dict.values():

In [3]:
my_dict1.keys()
Out[3]:
dict_keys(['axial_force', 'moment', 'shear'])
In [4]:
my_dict1.values()
Out[4]:
dict_values([319.2, 74, 23])

Tuples

Tuples are very comparable to lists, but they are defined by parantheses (). Most notable difference from lists is that tuples are immutable.

Mutability

Tuples are immutable. They cannot be changed after creation.

Tuple examples

In [5]:
t1 = (1, 24, 56)                      # Simple tuple of integers
t2 = (1, 1.62, '12', [1, 2 , 3])      # Multiple types as tuple elements
points = ((4, 5), (12, 6), (14, 9))   # Tuple of tuples

Sets

Sets are defined with curly brackets {}. They are unordered and don't have an index. See description of indexing further down. Sets also have unique items.

The primary idea about sets is the ability to perform set operations. These are known from mathematics and can determine the union, intersection, difference etc. of two given sets.

See for example these links for explanations on set operations: https://en.wikipedia.org/wiki/Set_(mathematics)#Basic_operations or https://snakify.org/en/lessons/sets/.

A list, string or tuple can be converted to a set by set(sequence_to_convert). Since sets only have unique items, the set resulting from the operation has same values as the input sequence, but with duplicates removed. This can be a way to find all unique elements.

For example:

list_uniques = list(set(list_duplicates))  # Convert list to set and back to list again with now only unique elements

Mutability

Sets are mutable. They can be changed after creation.

Set examples

In [6]:
s1 = {32, 3, 1, 86, 6, 8}
s2 = {8, 6, 21, 7, 26}
s1.union(s2)               # Find the union of the two sets
Out[6]:
{1, 3, 6, 7, 8, 21, 26, 32, 86}
In [7]:
s1.intersection(s2)       # Find the intersection of the two sets
Out[7]:
{6, 8}
In [8]:
list_with_duplicates = [1, 2, 3, 4, 5, 2, 2, 3, 1]
s3 = set(list_with_duplicates)   # Create a set of the list (which removed duplicates)
s3
Out[8]:
{1, 2, 3, 4, 5}

If a list is wanted again:

In [9]:
list(s3)
Out[9]:
[1, 2, 3, 4, 5]

The in operator

The in operator can be used to check whether a certain item is contained in a sequence. The result of the evaluation is a boolean (True or False):

In [10]:
2 in [1, 2, 3]
Out[10]:
True
In [11]:
'ma' in 'Denmark'
Out[11]:
True
In [12]:
'er' in 'Denmark'  
Out[12]:
False

Indexing

When elements are put into a sequences (strings, lists, tuples etc.), the individual elements gets an index assgined to them. This enables each element to be extracted.
Indexing in Python starts from 0, while the negative index starts from -1: image.png

Use square brackets []

To extract an element by indexing, use sqaure brackets []. This is the general way and works for all sequences. Strings, lists and tuples are all sequences.

In [13]:
example_list = ['a', 'b', 'c', 'd']
example_list[0]
Out[13]:
'a'
In [14]:
example_list[-2]
Out[14]:
'c'
In [15]:
example_tuple = (10, 20, 30, 40)
example_tuple[3]
Out[15]:
40

IndexError

When trying to refer to an index that is not in the data structure, an IndexError is raised:

In [16]:
example_tuple[10]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-16-e749b52b197d> in <module>()
----> 1 example_tuple[10]

IndexError: tuple index out of range

Extracting values from dictionaries

Dictionaries differ from data structures like strings, lists and tuples since they do not have an index. Instead, a value are extracted by indexing the corresponding key:

In [17]:
d = {'N': 83, 'My': 154, 'Mz': 317}
d['My']
Out[17]:
154

Note: This means that keys in a dictionary must be unique!

See demonstation below, where the key 'a' is defined twice. The second defintion overwrites the first one.

In [18]:
{'a': 1, 'b': 2, 'c':3, 'a': 4}
Out[18]:
{'a': 4, 'b': 2, 'c': 3}

Slicing

Slicing is the operation of extracting multiple elements at once by calling out the index range. An example for slicing of a list is shown below: image.png

Note that the stop point of the slice is not included.

These examples are shown for lists, but the same concept works for strings and tuples. Set objects do not support indexing/slicing since they are unordered, and dictionaries cannot be sliced either as they have the key functionality instead.

The most common slicing operations are:

n[start:stop]  # elements from start to stop-1
    n[start:]      # elements from start to the end of the list 
    n[:stop]       # elements from the beginning to stop-1
    n[:]           # a copy of the whole list (alternative: list.copy())

As seen, the last one creates a copy. This can be useful when working with mutable objects. For example for copying a list to mutate the copy while keeping the orginal list unchanged.

There is also setp mechanism that can be used:

a[start:stop:step] # start through not past stop, by step

Steps could be combined with the slicing operations shown above.

List methods

Lists have many methods for common manipulations. Methods are recognized by the 'dot'-notation, so if a method was called method_name(), it would be used like list.method_name().

Some of the most common list methods are:

list.append(val)  # Insert val as the last element of the list, and make list one element longer
list.pop([i])     # Remove i'th element from list and return it (if i is not provided, it defaults to last element)
list.reverse()    # Reverse all elements in list

Note that these all mutate list.

The len() function which was used for strings in Session 1 also works for lists, dictionaries, tuples and sets. This is a function since it does not use 'dot'-notation.

Many websites have explanations about string manipulations. This is form the Python documentation itself: https://docs.python.org/3/tutorial/datastructures.html#data-structures

Note: Methods for common operations on dictionaires, tuples and sets also exist, but are not shown here.

Copying mutable objects

When copying objects that are mutable like, lists or dictionaries, there are some things to be aware of. This is demonstrated by a list example below.

In [19]:
x = [1, 2, 3]     
y = x          # <-- This does not make y a copy of x 
y              # It make y a pointer to the same underlying object (or id) as x has inside the computer
Out[19]:
[1, 2, 3]
In [20]:
id(x)  # Behind the scenes, the variable x gets assigned a unique object id
Out[20]:
2235132766792
In [21]:
id(y)  # y is seen to have the same underlying object id
Out[21]:
2235132766792

This means that when we mutate (or modify) y, the orignal list x gets changed as well, which is often not desired. This is because it's a pointer to the same object as y.

In [22]:
y.append(89)
y
Out[22]:
[1, 2, 3, 89]
In [23]:
x  # x also got 89 appended to it
Out[23]:
[1, 2, 3, 89]

Instead, the list x should be copied either by a the list.copy() method or by the slicing operation shown earlier.

An example is shown below by using the list.copy()method:

In [24]:
x_new = [1, 2, 3]   # Redefining x since it was mutated above
y_new = x_new.copy()  # Copy by a method that is built-in for lists
y_new
Out[24]:
[1, 2, 3]
In [25]:
# Append a value to y_new 
y_new.append(327)
y_new
Out[25]:
[1, 2, 3, 327]
In [26]:
# x has not changed
x_new
Out[26]:
[1, 2, 3]
In [27]:
# Print object id's as f-string 
print(f'x_new has object id: {id(x_new)} \ny_new has object id: {id(y_new)}')
x_new has object id: 2235134073480 
y_new has object id: 2235134300424

for loops

The general syntax in a for loop is

for item in iterable:
    # code goes here

Recall that an iterable is a fancy word for something that can be iterated over like a string, a list, a tuple etc.

So, printing numbers from 0-5 can be done like this:

In [28]:
# Printing numbers from 0-5
for i in [0, 1, 2, 3, 4, 5]:
    print(i)
0
1
2
3
4
5

A common way of quickly generating the numbers from 0-5 instead of typing the list [0, 1, 2, 3, 4, 5] is by the range() function, which has two forms:

range(stop)                 # Generates numbers from 0 to stop-1
range(start, stop[, step])  # Generates numbers from start to stop-1 (step is optional)
In [29]:
# Printing numbers from 0-5
for i in range(6):
    print(i**2)
0
1
4
9
16
25

As an alternatibe to iterating over a number sequence with a counter, it is also possible to just access the each value in turn.

Here is an example where each element of a list of strings is accessed and names string.

In [30]:
g = ['batman', 'superman', 'spiderman', 'ironman', 'green lantern']
h = []
for string in g:          # This would be like saying: for each string in the list g
    if len(string) > 7:   # If the current string has more than seven characters 
        print(string)     # Print it
superman
spiderman
green lantern

while loops

A while loop is a loop that continues until some condition is no longer satisfied.

Generally, a while will look like this:

while condition: 
    # code goes here

Where evaluation of condition must return a boolean (True or False).

There must be some kind of change in condition while looping. Otherwise the loop becomes an infinite loop and runs forever (or until you stop it). An example of an infinite loop is

counter = 0
while counter < 3: 
    print(counter)     # The variable counter is never updated, so this prints forever

The counter should be updated wihtin the loop, e.g. like this:

In [31]:
counter = 1
while counter < 5: 
    print(f'The count is {counter}')
    counter += 1    # Update counter (this is equivalent to: counter = counter + 1)
The count is 1
The count is 2
The count is 3
The count is 4

A while loop can be good when the number of iterations are not known beforehand. This could be when serching for a root for an equation or finding the neutral axis of a reinforced concrete section.

When iterating for in such cases, convergence is not always ensured. A common way of exiting the while loop is to define a max number of iterations and then check in each loop whether this number has been reached. If it has, then the loop should break.

A similar logic to whileloops could be done with by for loops, but a while loop is cleaner for some purposes and can help to clarify the intetn of the code.

List comprehensions

List comprehensions are another way of writing a for loop. It can be done in one line and is often cleaner and more readable for simple iteration.

General form

The general form of the simplest list comprehension is

result_list = [expression for item in iterable]
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • item is the counter for the iterable, think of this as the i'th element
  • expression can be anything, but will often include the item
In [32]:
L1 = [12, 215, 31, 437, 51]
L2 = [2*i for i in L1]       # List comprehension to multiply each element of L1 by 2
L2
Out[32]:
[24, 430, 62, 874, 102]

Note that 2 * L1 will not create the same output, but instead repeat the list as seen below. To get a vectorized behavior like that, we could have use numpy, which is a third party library for numerical compuations similar to Matlab.

In [33]:
2 * L1
Out[33]:
[12, 215, 31, 437, 51, 12, 215, 31, 437, 51]

List comprehension with if statement

The general form of a list comprehension with a conditional is

result_list = [expression for item in iterable if condition]
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • condition is a logical condition, e.g. "item > 3", which returns a boolean (True/False). This can act as as filter.
  • result_list is a new list containing expression for each item that fulfilled the condition
In [34]:
x = [1, 2, 3, 4, 5]                      # Define a list (iterable)
result_list = [i for i in x if i > 3]    # Filter list with list comprehension by use of condition
result_list
Out[34]:
[4, 5]

Form with conditional if and else statement

The general form of a list comprehension with a conditional if and else is:

result_list = [expression1 if condition else expression2 for item in iterable]
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • condition is a logical condition, e.g. "item > 3", which returns a boolean (True/False). This can act as as filter.
  • result_list is a new list containing elements where each item depends on condition. If it is True, expression1 is put in. Else expression2 is put in.
In [35]:
V = [3, 62, 182, 26, 151, 174]

# Set all elements of V that are less than 100 equal to 0
W = [0 if i < 100 else i for i in V]
W
Out[35]:
[0, 0, 182, 0, 151, 174]

List comprehensions are generally computationally faster than regular for loops and also faster to type.

Exercise 1

Extract the last and first element from the list

L1 = [10, 20, 30, 40, 50, 60]

Exercise 2

Extract the following list from L1 defined above by use of slicing.

[20, 30, 40]

Exercise 3

Create a list of the unique values from the following list:

L2 = ['Hi', 'Hello', 'Hi!', 'Hey', 'Hi', 'hey', 'Hey']

Exercise 4

Given the dictionary

d = {2: 122, 3: 535, 't': 'T', 'rom': 'cola'}

Play around with extracting the values by calling out the keys for all key/value pairs.

Exercise 5

Given the list

n = [23, 73, 12, 84]

Create a for loop that prints:

'23 sqaured is 529'
'73 sqaured is 5329'
'12 sqaured is 144'
'84 sqaured is 7056'

Exercise 6

Use a list comprehension to create a new list with areas of the circles that have diameters defined by

diameters = [10, 12, 16, 20, 25, 32]

Exercise 7

From the following list, create a new list containing only the elements that have exactly five characters.

phonetic_alphabet = ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot']

Exercise 8

Find the intersection of the two sets (elements that occur in both sets)

s1 = {'HE170B', 'HE210B', 'HE190A', 'HE200A', 'HE210A', 'HE210A'}

s2 = {'HE200A', 'HE210A', 'HE240A', 'HE200A', 'HE210B', 'HE340A'}

Exercise 9

Create a variable fy and set it equal to 435.

Given the tuple below, create a list where each value is:

  • The value itself if the value is below fy
  • fy if the value is larger than fy
rebar_stresses = (125, 501, 362, 156, 80, 475, 489)

Exercise 10

Given the tuple below, create a list where each value is:

  • 0 if the value is positive
  • The value itself is the value is negative and larger than -25
  • -25 if the value is lower than -25
T1 = (-18, -27, 2, -21, -15, 5)
</html>