Programming 1, 2022-3
Lecture 3: Notes

String indexing and slicing

Let's consider more string operations in Python. First, the len() function will give us the length of a string:

>>> len('zmrzlina')
8

We can use the [] operator to extract individual characters of a string s. The first character is s[0], the second is s[1], and so on:

>>> s = 'zmrzlina'
>>> s[0]
'z'
>>> s[1]
'm'

A negative index retrieves a character from the end of the string. For example, s[-1] is the last character, and s[-2] is the second to last:

>>> s[-1]
'a'
>>> s[-2]
'n'

The syntax s[i : j] returns a slice (i.e. substring) of elements from s[i] up to (but not including) s[j]. Either i or j may be negative to index from the end of the sequence:

>>> s[0:4]
'zmrz'
>>> s[4:5]
'l'
>>> s[3:-1]
'zlin'

In s[i : j], if the start index i is omitted, it is 0, i.e. the beginning of the string. If the end index j is omitted, it is len(s), i.e. the end of the string:

>>> s[:4]
'zmrz'
>>> s[4:]
'lina'

The syntax s[i : j : k] will extract a slice of characters in which the index advances by k at each step. For example, if we use k = 2 then we will retrieve alternative characters:

>>> s[0:7:2]
'zrln'

Note that the step value can even be negative:

>>> s[6:2:-1]
'nilz'

If the step value is negative, then an empty start index refers to the end of the string, and an empty end index refers to the beginning. So you can reverse a string by specifying a step value of -1, and providing neither a start or end index:

>>> s[::-1]
'anilzrmz'

functions and methods

Python includes both functions and methods in its standard library.

A function takes one or more arguments and optionally returns a value. Some of Python's built-in functions that we've already seen in this course are len(), chr(), ord(), input() and print(), for example. To call a function, we simply write its name followed by the arguments:

n = input('Enter a number: ')

A method is like a function, but is invoked on a particular object. For example:

s = 'yoyo'
b = s.startswith('yo')  # method call

In the second line above, we are invoking (or calling) the startswith() method on the object s. (In Python a value and an object are the same thing.) This method call takes one additional argument, namely the string 'yo'. It returns a value, which is True in this case since the string 'yoyo' does start with 'yo'.

Both functions and methods are common in programming languages today. However, they are not both present in all languages: some languages have only functions, and others have only methods. Python is a bit of a hybrid since it has both functions and methods. This arguably makes the language more flexible and convenient (at the cost of some complexity).

In this course we will soon learn how to write our own functions, and before too long we'll learn how to write our own methods as well.

string methods

Our quick reference lists a number of built-in string methods, which can be quite useful. (As an exercise, you may wish to try writing programs that implement the functionality of some of these built-in methods.)

lists

Lists are a fundamental type in Python. We can make a list by specifying a series of values surrounded by square brackets:

l = [3, 5, 9, 11, 15]

A list may contain values of various types:

l = ['horse', 789, False, -22.3]

It may contain any number of values, or may even be empty:

l = []

The len function returns the number of elements in a list:

len(['potato', 'tomato', 'tornado'])    # returns 3

We can access elements of a list by index. The first element has index 0, and the last element has index len(l) – 1:

>>> l = [3, 5, 9, 11, 15]
>>> l[0]
3
>>> l[4]
15

Just like with strings, we can use negative indices to count from the end of the list:

>>> l = [3, 5, 9, 11, 15]
>>> l[-1]
15
>>> l[-2]
11

Slice syntax works with lists, just like with strings:

>>> l = [3, 5, 9, 11, 15]
>>> l[1:3]
[5, 9]
>>> l[3:]
[11, 15]

The 'in' operator tests whether a list contains a given value:

>>> 77 in [2, 8, 77, 3, 1]
True

Unlike strings, lists in Python are mutable. We can set values by index:

>>> l = [3, 5, 9, 11, 15]
>>> l[0] = 77
>>> l[3] = 99
>>> l
[77, 5, 9, 99, 15]

A list's length may change over time. The append() method adds a single element to a list, and the extend() method adds a series of elements. The += operator is a synonym for extend():

>>> l = [3, 5, 9, 11, 15]
>>> l.append(20)
>>> l.append(30)
>>> l
[3, 5, 9, 11, 15, 20, 30]
>>> l += [100, 200, 30]
>>> l
[3, 5, 9, 11, 15, 20, 30, 100, 200, 30]

The insert() method inserts an element into a list at a given position:

>>> l = [3, 5, 9, 11, 15]
>>> l.insert(2, 88)
>>> l
[3, 5, 88, 9, 11, 15]

The del operator can delete one or more elements of a list by index:

>>> l = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'grape']
>>> del l[1]
>>> l
['orange', 'pear', 'banana', 'kiwi', 'grape']
>>> del l[2:4]
>>> l
['orange', 'pear', 'grape']

Note that lists, ranges, and strings are all considered to be sequences in Python. The 'for' statement can loop over any sequence, and certain operations such as len() and slices also work on any sequence.

The list() function converts any sequence to a list. Note its effect on a string or a range:

>>> list('watermelon')
['w', 'a', 't', 'e', 'r', 'm', 'e', 'l', 'o', 'n']
>>> list(range(120, 130))
[120, 121, 122, 123, 124, 125, 126, 127, 128, 129]

See our quick reference guide to read about additional useful list methods.

structural and reference equality

Suppose that we write the following declarations:

>>> l = [3, 5, 7, 9]
>>> m = l

Now the variables l and m refer to the same list. If we change l[0], then the change will be visible in m:

>>> l[0] = 33
>>> m[0]
33

Alternatively, we may make a copy of the list l. There are several possible ways to do that:

>>> l = [3, 5, 7, 9]
>>> n = l.copy()      # technique 1: call the copy() method
>>> n = list(l)       # technique 2: call the list() function
>>> n = l[:]          # technique 3: use slice syntax

Now the list n has the same values as l, but it is a different list. Changes in one list will not be visible in the other:

>>> l[1] = 575
>>> l
[3, 575, 7, 9]
>>> n
[3, 5, 7, 9]

Python provides two different operators for testing equality. The first is the == operator:

>>> x == y
True
>>> x == z
True

This operator tests for structural equality. In other words, given two lists, it compares them element by element to see if they are equal. (It will even descend into sublists to compare elements there as well.)

The second equality operator is the is operator:

>>> x is y
True
>>> x is z
False

This operator tests for reference equality. In other words, it returns true only if its arguments actually refer to the same object. (Reference equality is also called physical equality).

You may want to use each of these operators in various situations. Note that is returns instantly, whereas == may traverse a list in its entirety, so it runs in O(N) in the worst case.