One of Python's most useful built-in types. It may look like an array from other languages, but it is a whole lot more.
A string is a sequence of character values.
A list is a sequence of values of any type. The values in a list are called elements or items.
The easiest way to create a list is with the square brackets ([]
).
nums = [1, 2] # int items
names = ['Joe', 'Jane'] # str items
mixed = [1, 'one', [1, 2]] # int, str, list
empty = [] # empty list
print(nums, names, mixed, empty)
[1, 2] ['Joe', 'Jane'] [1, 'one', [1, 2]] []
Unlike a string, a list is mutable. Use the []
operator with an index to access an item.
# access the first element.
print(nums[0])
# update the first element.
nums[0] = 10
print(nums)
1 [10, 2]
It is helpful to visualize how objects relate to memory. Recall that in Python everything is an object that refers to a memory location.
A state diagram is useful in describing or visualizing this binding.
Take a look at the state diagram for our four lists, nums
, names
, mixed
and empty
. Notice the box representation of a list and the items within.
You could also use a negative index to access an item.
print(nums[1])
nums[-1] = 20
print(nums)
2 [10, 20]
The in
operator also works with lists.
print('Joe' in names)
print('Mary' in names)
True False
The most common way to traverse a list is with a for
loop.
for name in names:
print(name)
Joe Jane
Note that nested lists are still considered a single element.
for item in mixed:
print(item)
1 one [1, 2]
Updating the elements of a list requires the index, so one way to do this is using the range()
and len()
functions.
print(nums)
for i in range(len(nums)):
nums[i] *= 2
print(nums)
[10, 20] [20, 40]
The +
concatenates lists:
a = [1, 2, 3]
b = [4, 5]
c = a + b
print(c)
[1, 2, 3, 4, 5]
The *
operator repeats a list a given number of times:
a = [0]
b = a * 3
print(b)
[0, 0, 0]
The slice operator :
also works on lists:
a = ['a', 'b', 'c']
print(a[0:3])
print(a[:])
print(a[:2])
print(a[2:])
['a', 'b', 'c'] ['a', 'b', 'c'] ['a', 'b'] ['c']
The slice returns a copy of the list.
b = a[:]
b[0] = 'A'
print(a, b)
['a', 'b', 'c'] ['A', 'b', 'c']
A slice can appear on the left of an assignment operator, since lists are mutable:
a = ['old', 'old', 'old']
a[0:2] = ['new', 'new']
print(a)
['new', 'new', 'old']
a = [1, 2]
a.append(3)
print(a)
[1, 2, 3]
The .insert()
method allows you to insert an element into the list at a given index.
a = [1, 3]
a.insert(1, 2)
print(a)
[1, 2, 3]
The .extend()
method appends a source list to the end of a target list. The source list remains unaffected.
tgt = [1, 2]
src = [3, 4]
tgt.extend(src)
print('tgt:', tgt, 'src:', src)
tgt: [1, 2, 3, 4] src: [3, 4]
The .sort()
method orders the elements of a list from low to high.
chars = ['a', 'c', 'b']
chars.sort()
print(chars)
['c', 'b', 'a']
The .reverse()
method reverses the elements of a list.
a = [1 ,2, 3]
a.reverse()
print(a)
[3, 12, 1]
All the methods that update a list, return None
.
print([1,2].append(3))
print([1,2].extend([3]))
print([2,1].sort())
print([1,2].reverse())
None None None None
nums = [1, 2, 3, 4, 5]
total = 0
for num in nums:
total += num
print(total)
15
The sum of all the elements in the list has been reduced to a single value, total
. This is such a common operation that Python offers the sum()
function to sum the elements of a sequence.
print(sum(nums))
15
A map transforms one list into another by applying some change to the elements.
Suppose we want to create a new list by traversing a list of strings and changing each one to its uppercase equivalent.
lowercase = ['one', 'two', 'three']
uppercase = []
for item in lowercase:
uppercase.append(item.upper())
print(lowercase, uppercase)
['one', 'two', 'three'] ['ONE', 'TWO', 'THREE']
The selection of a sublist based on some criteria is called a filter operation.
Suppose you want to create a new list that consists of only the odd numbers given a list of numbers.
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
odd = []
for num in nums:
if num % 2 == 1:
odd.append(num)
print(odd)
[1, 3, 5, 7, 9]
a = [1, 2, 3]
# delete the second element.
r = a.pop(1)
print(r, a)
# delete the last element.
r = a.pop()
print(r, a)
2 [1, 3] 3 [1]
a = ['a', 'b', 'c']
del a[0]
print(a)
['b', 'c']
a = ['a', 'b', 'c']
# Use a slice to delete elements.
del a[0:2]
print(a)
['c']
a = ['a', 'b', 'c']
# Delete all the elements.
del a[:]
print(a)
[]
Careful with this since deleting the list itself is not the same as deleting all the elements of the list.
Deleting the list makes it inaccessible.
a = [1, 2, 3]
del a
print(a)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) /var/folders/_5/pbybv9c90j77vqh9wby2v8140000gq/T/ipykernel_32327/3599972270.py in <module> 1 a = [1, 2, 3] 2 del a ----> 3 print(a) NameError: name 'a' is not defined
If you know the element you want to remove rather than its index, use .remove()
.
a = ['a', 'b', 'c']
a.remove('c')
print(a)
['a', 'b']
s = 'Cat'
c = list(s)
print(c)
['C', 'a', 't']
If you want to break a string into words, use .split()
. Without an argument it separates each word by using a space as the delimiter.
s = 'How many days till Christmas?'
words = s.split()
print(words)
['How', 'many', 'days', 'till', 'Christmas?']
csv = 'one,two,three'
words = csv.split(',')
print(words)
['one', 'two', 'three']
If you have a list of words and would like to join them together into a string, use .join()
.
Since this is a string method, the string object to use is the delimiter that is used to stitch the words back together.
words = ['Hello','my','friends']
delimiter = ' '
s = delimiter.join(words)
print(s)
Hello my friends
A string object has a value namely the sequence of characters that make it up.
A list is also an object whose value is the sequence of elements it holds.
Thus, an object is thought of as having a value.
Variables are references to objects, so:
Two objects are said to be identical if they both refer to the same value in memory.
Use the is
operator to find out if the objects are identical.
s1 = 'Hello'
s2 = 'Hello'
l1 = [1,2]
l2 = [1,2]
print('s1 is s2:', s1 is s2)
print('l1 is l2:', l1 is l2)
s1 is s2: True l1 is l2: False
Why is s1
identical to s2
, but l1
is not identical to l2
?
The answer lies in the fact that strings are immutable and lists are not. Since a string cannot be mutated, an optimization is made by having both objects refer to the same memory.
Identity is a characteristic of the objects, but equivalence is a characteristic of their values.
Two objects are equivalent is they both hold the same value.
In our example above, the strings are equivalent and identical, while the lists are equivalent, but not identical.
Identical implies equivalent, but equivalent does not imply identical.
The state diagram for the string and list objects is:
If a
refers to an object and you assign a
to b
, b = a
, then both variables refer to the same object.
a = [1, 2]
b = a
print(b is a)
True
The state diagram looks like this:
The association of a variable with an object is called a reference. In this example, there are two references to the same object.
If the aliased object is mutable, changes made to one will affect the other.
b[0] = 10
print('a:', a)
print('b:', b)
a: [10, 2] b: [10, 2]
Aliasing can come in handy at times, but is very error-prone, so be careful using it with mutable objects.
When you pass a list to a function, the function receives a reference to the list, not a copy. If the function changes the list, the caller will see those changes.
Let's look at an example:
def deleteHead(lst):
del lst[0]
letters = ['a', 'b', 'c']
print('Before call:', letters)
deleteHead(letters)
print(' After call:', letters)
Before call: ['a', 'b', 'c'] After call: ['b', 'c']
Let's take a look at the stack diagram and see what actually takes place in memory.
In __main__
, i.e. the script module, the variable letters
refers to the list object with value ['a', 'b', 'c']
. This reference is then passed to the function deleteHead
and copied into the parameter lst
. Make sure you reread this carefully and understand that the reference is copied, not the object. This in effect creates an alias to the same object.
Be mindful of operations that update a list and ones that create a new list.
For example the .append()
updates while the +
creates a list.
# Update an existing list.
a = [1, 2]
b = a.append(3)
print(a, b)
[1, 2, 3] None
# Create a new list.
a = [1, 2]
b = a + [3]
print(a, b)
[1, 2] [1, 2, 3]
This distinction is rather important to remember, since unexpected (mostly bad) things can happen.
Remember that arguments are always passed by value, so if you assign a new object to a parameter, the argument object is not affected.
Look at this version of a bad delete head.
def badDeletHead(lst):
# A new list lst[1:] is assigned to lst, but this
# assignment creates a new list, it does not update
# the argument list.
lst = lst[1:]
print('In function:', lst)
letters = ['a', 'b', 'c']
print('Before call:', letters)
badDeletHead(letters)
print(' After call:', letters)
Before call: ['a', 'b', 'c'] In function: ['b', 'c'] After call: ['a', 'b', 'c']
An alternative is to return a new list with the desired modifications and assign it to the original list.
Here's an example:
def tail(lst):
return lst[1:]
letters = ['a', 'b', 'c']
letters = tail(letters)
print(letters)
['b', 'c']
When creating lists we can use a list comprehension to quickly populate the list.
A list comprehension consists of brackets ([]
) containing an expression followed by a for
clause, then zero or more for
or if
clauses.
[expression for element in list if conditional]
Let's see some examples.
The first creates a list of integers using the range()
function to generate the desired sequence of numbers. Each element e
is assigned to each generated number in turn and added to the list.
# Create the list [0, 1, 2, 3 , 4]
a = [e for e in range(5)]
print(a)
[0, 1, 2, 3, 4]
The next creates a list of squares.
squares = [e**2 for e in range(5)]
print(squares)
[0, 1, 4, 9, 16]
The next creates a list of just the odd squares.
oddSquares = [e for e in squares if e % 2 == 1]
print(oddSquares)
[1, 9]
Our last example creates a list of lists. Each nested list is made up of two elements ([1, 2]
) and the outer list is made up of three such lists to produce a two-dimesional list.
# Create the list [[1,2],[1,2],[1,2]]
a = [[e for e in range(1,3)] for l in range(3)]
print(a)
[[1, 2], [1, 2], [1, 2]]
Create a separate Python source file (.py) in VSC to complete each exercise.
Write a function called nestedSum()
that takes a list of integers and adds up the elements from all of the nested lists and returns their sum.
For example:
l = [[1, 2], [3], [4, 5, 6]]
print(nestedSum(l))
Output:
21
Write a function called cumulativeSum()
that takes a list of numbers and returns the cumulative sum; that is, a new list where the $i$th element is the sum of the first $i+1$ elements from the original list.
For example:
l = [1, 2, 3]
print(cumulativeSum(l))
Output:
[1, 3, 6]
Write a function called middle()
that takes a list and returns a new list that contains all but the first and last elements.
For example:
l = [1, 2, 3, 4]
print(middle(l))
Output:
[2, 3]
Write a function called chop()
that takes a list, modifies it by removing the first and last elements, and returns None
.
For example:
l = [1, 2, 3, 4]
chop(t)
print(l)
Output:
[2, 3]
Write a function called isSorted()
that takes a list as a parameter and returns True
if the list is sorted in ascending order and False
otherwise.
For example:
print(isSorted([1, 2, 2]))
print(isSorted(['b', 'a']))
Output:
True
False
Two words are anagrams if you can rearrange the letters from one to spell the other. Write a function called isAnagram()
that takes two strings and returns True
if they are anagrams.
Write a function called hasDuplicates()
that takes a list and returns True
if there is any element that appears more than once. It should not modify the original list.
Write a function buildWords()
that reads the file words.txt
and builds a list with one element per word. Write two versions of this function, one using the .append()
method and the other using the idiom t = t + [x]
.
Which one takes longer to run? Why?
Two words interlock if taking alternating letters from each forms a new word. For example, shoe
and cold
interlock to form schooled
.
Write a program that finds all pairs of words that interlock. Hint: don't enumerate all pairs!
To check whether a word is in the word list, you could use the in
operator, but it would be slow because it searches through the words in order.
Instead, since the word list is ordered, use a binary search. Alternatively you can search the Python docs for the bisect
module and use that.