This site is from a past semester! The current version will be here when the new semester starts.

Week 4 [Mon, Jan 30th] - Programming Topics

Lists

The List Data Structure

A data structure contains data that are more complex than a single data value such as an integer. Lists are one such very useful data structure used in Python. A list contains an ordered sequence of items. Python uses square brackets to indicate lists.

Some list examples:

friends = [] # an empty list
fruits = ['apple', 'banana', 'orange'] # a list containing 3 string items
values = [0, 3.4, 'High', True] # a list containing items of different types
print(friends, fruits, values)

everything = [friends, fruits, values] # a list containing other lists
print(everything)

[] ['apple', 'banana', 'orange'] [0, 3.4, 'High', True]
[[], ['apple', 'banana', 'orange'], [0, 3.4, 'High', True]]

Lists


Exercise: Countries

Exercise : Countries

Complete the get_countries() function so that it return the list of countries shown on the right.

You can follow the example of the get_numbers() function given.

# sample function
def get_numbers():
  return [1, 2, 3]

def get_countries():
  return # ADD CODE HERE!

print(get_numbers())
print(get_countries())

[1, 2, 3]
['China', 'India', 'Malaysia', 'Singapore', 'Sri Lanka']

You can use the notation list_name[index_of_item] to access an item of a list. List indexes start from 0 i.e., the first item in a list has the index 0.

The code below shows how to use indexes to access items in a list.

fruits = ['Apple', 'Banana', 'Cherry', 'Dragon fruit']
print(fruits[0])
print(fruits[3])
print(fruits[-1])
print(fruits[-2])
 → 

Apple
Dragon Fruit
Dragon Fruit
Cherry

As you can see from the above example, list indexes can be negative; index -1 refers to the last item in the list, -2 refers to the second last item in the list, and so on.

The code below shows how to use indexes to update items in a list.

coins = [10, 30, 50, 100]
coins[1] = 20
print(coins)
 → 

[10, 20, 50, 100]

The example below shows how to access an item of a list that is inside another list; list_of_lists[2] accesses list_of_lists[2] which gives you the item at index 2 ([0.1, 0.2]) which is also a list, and then accesses the item at index 0 from that list, which is 0.1.

list_of_lists = [['a', 'b', 'c'], [1, 2, 3, 4], [0.1, 0.2]]
print(list_of_lists[2][0])
 → 

0.1

Accessing an item from a list


Exercise: Get Head, Get Tail

Exercise : Get Head, Get Tail

Complete the functions as described below:

  • get_head(item_list): returns the first item of the list received as the item_list parameter.
  • get_tail(item_list): returns the last item of the list received as the item_list parameter.
  • You may assume that the list has at least one item.
def get_head(item_list):
  return # ADD CODE HERE

def get_tail(item_list):
  return # ADD CODE HERE

print(get_head([1, 2, 3]))
print(get_head([5]))
print(get_tail(['a', 2, 3.0]))
print(get_tail(['cat']))
 → 

1
5
3.0
cat

Partial solution




Object References

Objects of some Python types are immutable. An immutable object, once created, cannot be modified.

  • Some immutable types you have seen already: int, float, decimal, bool, str
  • Other immutable types: complex, tuple, range, frozenset, bytes

Objects of some other Python types are mutable. A mutable object can be modified during its lifetime.

  • Some mutable types: list, dict, set, bytearray

In the example below, i = 5 assigns an integer to i. Because integers are immutable, i = i + 1 is not mutating the integer object 5; rather, it is simply creating a new integer object 6 and assigning that to i.
Similarly, s = s + ' World!' is not adding more letters to the existing string object Hello; rather, it is creating a new string object 'Hello World!' and assigning that to s.

i = 5
i = i + 1 # i is assigned a new integer object 6

s = 'Hello'
s = s + '  World!' # s is assigned a new string objet 'Hello World!'

numbers = [1, 2, 3]
numbers[0] = 4 # the existing list is being modified

However, lists are mutable. Therefore, numbers[0] = 4 is changing the existing list [1, 2, 3] rather than creating a new list object.

An object reference is the address of the memory location where an object is currently stored. A variable is a name that is bound to an object reference. We can think of it as the variable pointing to an object that is stored at the object reference.

The variable spam is bound to the object reference 57207444 which is the memory location of the list object [0, 1, 2, 3, 4, 5]. i.e., spam is pointing to the object [0, 1, 2, 3, 4, 5] using the object reference 57207444.

spam = [0, 1, 2, 3, 4, 5]


Image credit: AtBSwP

When you assign variables as v1 = v2, if v2 is pointing to a mutable object, its object reference is copied to v1 so that now both v1 and v2 are pointing to the same object. However, if v2 is pointing to an immutable object, v1 = v2 results in each variable pointing to its own copy of the object.

Continuing with the previous example, cheese = spam results in the object reference stored in spam being copied into the variable cheese. The end result is both variables are now pointing to the same list object.

cheese = spam


Image credit: AtBSwP

This means any change to the list pointed by cheese will be reflected as a change to the list pointed to by spam (because both are pointing to the same list object!).

cheese [1] = 'Hello!'
print('spam is:', spam)
print('cheese is:', cheese)
 → 

spam is: [0, 'Hello!', 2, 3, 4, 5]
cheese is: [0, 'Hello!', 2, 3, 4, 5]


Image credit: AtBSwP

However, the behavior is different when we do a similar variable assignment and update using integers instead of lists. Because integers are immutable, cheese points to a copy of the integer pointed to by spam, not the same object.

spam = 5
cheese = spam
print('spam is:', spam)
print('cheese is:', cheese)

cheese = cheese + 1
print('spam is:', spam)
print('cheese is:', cheese)
 → 

spam is: 5
cheese is: 5
spam is: 5
cheese is: 6

The behavior for passing arguments to a function is similar to that of assigning one variable to another. If the argument is mutable, its object reference is assigned to the parameter. That means the code inside the function is able to modify the object passed as the argument and such changes remain after the function execution is over.

In the foo function below, when foo(original) is executed, the object reference of the argument original is copied to the parameter items. Now the function is able to modify the list object (i.e., the list object being pointed to by both original and items), and the changes remain in the list object even after the function has finished.

def foo(items):
  items[0] = 'Hi'
  print('inside foo:', items)

original = [1, 2, 3]
foo(original)
print('after foo:', original)
 → 

[Click here to visualize the execution]


inside foo: ['Hi', 2, 3]
after foo: ['Hi', 2, 3]

Contrast the above example with the one below. The bar(items) function assigns a new list to items parameter. That means items is no longer pointing to the list object that was passed in as the argument. After the function is executed, the original list remains the same as before.

def bar(items):
  items = ['a', 'b', 'c']
  print('inside bar:', items)

original = [1, 2, 3]
bar(original)
print('after bar:', original)
 → 

[Click here to visualize the execution]


inside bar: ['a', 'b', 'c']
after bar: [1, 2, 3]

If the argument passed to a function is of an immutable type, the function receives the reference to a copy of the argument. That means the code inside the function is unable to modify the object passed as the argument. However, it can return a new object that is based on the given object copy.

In this example, the function increment(age) is given an immutable object 25 as the argument. Although the parameter v that received the argument is assigned a new object (i.e., v = v + 1) inside the function, that change is not reflected in the argument age. That is because v is given a reference to a copy of age, not a reference to the actual object in age.

def increment(v):
  v = v + 1
  return v

age = 25
new_age = increment(age)
print('age:', age)
print('new age:', new_age)
 → 

[Click here to visualize the execution]


age: 25
new age: 26

Exercise: Swap Ends

Exercise : Swap Ends

Complete the functions as described below:

  • swap_ends(item_list): swaps the first and the last items of the item_list. Note that the function should modify the given item_list list, not return a new list.

Notes:

  • You can follow the example of set_head(item_list, new_head) function which modifies the given item_list by setting its first element to new_head.
  • pass is a dummy statement that does nothing. We use it here because Python does not accept empty functions. You should replace it with your own implementation of the function.
  • You may assume the list has at least two elements.
def set_head(item_list, new_head):
  item_list[0] = new_head

def swap_ends(item_list):
  pass # REPLACE THIS WITH YOUR CODE

pets = ['cat', 'fish', 'dog', 'rock']
set_head(pets, 'parrot')
print(pets)
swap_ends(pets)
print(pets)
items = [1, 2]
swap_ends(items)
print(items)








 → 









['parrot', 'fish', 'dog', 'rock']
['rock', 'fish', 'dog', 'parrot']
[2, 1]


Working with Lists

You can use del to delete an item at a specific position of a list.

The code below shows how to use del to delete items in a list.

spam = ['foo', 45.0, True]
del spam[-1] # delete last item
print(spam)
 → 

['foo', 45.0]

Deleting items from a list


Exercise: Delete Head, Delete Tail

Exercise : Delete Head, Delete Tail

Complete the functions as described below:

  • delete_head(item_list): deletes the first item in the list passed as item_list parameter.
  • delete_tail(item_list): deletes the last item in the list passed as item_list parameter.
def delete_head(item_list):
  pass # REPLACE THIS WITH YOUR CODE

def delete_tail(item_list):
  pass # REPLACE THIS WITH YOUR CODE

letters = ['a', 'b', 'c']
print(letters)
delete_head(letters)
print(letters)
delete_tail(letters)
print(letters)
 → 

['a', 'b', 'c']
['b', 'c']
['b']

Partial solution



You can use the slice notation list_name[start_index:end_index] to copy a into a new list. If the start_index is omitted, it means 'from the beginning of the list'. If the end_index is omitted, it means 'till the end of the list'.

Some example of slicing to get a sub list:

letters = ['0a', '1b', '2c', '3d', '4e']
sublist = letters[0:3]
print(sublist) # print items 0, 1, 2
print(letters[:3]) # same as above (first 3 items)
print(letters[1:4]) # print items 1, 2, 3
print(letters[3:]) # print from item 3 till end
print(letters[-2:]) # print last two items
print(letters[1:-1]) # print from item 1 to 2nd last item
 → 

['0a', '1b', '2c']
['0a', '1b', '2c']
['1b', '2c', '3d']
['3d', '4e']
['3d', '4e']
['1b', '2c', '3d']

Note that slicing gives you a copy of a portion of the original list i.e., you get a new list.

In the example below, the first item 0a of the list letters is deleted after taking the first two elements as a sub list. Note how the item 0a still remains in the sub list after it has been deleted from the original list.

letters = ['0a', '1b', '2c', '3d', '4e']
first_two_letters = letters[:2]
print('original list:', letters)
print('sub list     :', first_two_letters)
del letters[0] # delete first element in original list
print('original list:', letters)
print('sub list     :', first_two_letters)

original list: ['0a', '1b', '2c', '3d', '4e']
sub list     : ['0a', '1b']
original list: ['1b', '2c', '3d', '4e']
sub list     : ['0a', '1b']

Accessing multiple items from a list


Exercise: Get Body

Exercise : Get Body

Complete the functions as described below:

  • get_body(item_list): returns a sublist of item_list parameter, without the first and the last items.
  • get_without_head(item_list): returns a sublist of item_list parameter, without the first item.
  • get_without_tail(item_list): returns a sublist of item_list parameter, without the last item.
def get_body(item_list):
  return # ADD CODE HERE

def get_without_head(item_list):
  return # ADD CODE HERE

def get_without_tail(item_list):
  return # ADD CODE HERE

print(get_body([0, 1, 2, 3, 4]))
print(get_without_head([0, 1, 2, 3, 4]))
print(get_without_tail([0, 1, 2, 3, 4]))
 → 

[1, 2, 3]
[1, 2, 3, 4]
[0, 1, 2, 3]

Partial solution



You can use the len function to find the number of items in a list.

spam = ['a','b']
print('Length is:', len(spam))
 → 

Length is: 2

Exercise: Get Mid

Exercise : Get Mid

Complete the functions as described below:

  • get_mid(item_list): returns the middle element of the list item_list

follow the example in the code below when defining which element is considered the middle element when the list has an even number of elements.

def get_mid(item_list):
  return  # REPLACE WITH YOUR CODE

print(get_mid(['a', 'b', 'c']))
print(get_mid([1, 2, 3, 4]))
 → 

b
3

You can use the + operator to combine multiple lists into a new list.

girls = ['Amy', 'Betsy', 'Clara']
boys = ['Adam', 'Ben']
friends = girls + boys
print(friends)
 → 

['Amy', 'Betsy', 'Clara', 'Adam', 'Ben']

You can use the * operator to create a new list by replicating an existing list multiple times.

steps = ['left', 'right']
walking = steps*3
print(walking)
 → 

['left', 'right', 'left', 'right', 'left', 'right']

Length of a list, list concatenation, list replication


Exercise: Expand to Fill

Exercise : Expand to Fill

Complete the functions as described below:

  • expand_to_fill(item_list, length): returns a list containing a total of length items, where the items are repetitions of the items in the item_list.

hints

  • full_repetitions_needed = length // length_of_item_list
  • return item_list * full_repetitions_needed + portion_of_item_list
def expand_to_fill(item_list, length):
  return  # REPLACE WITH YOUR CODE

meals = ['Rice', 'Bread', 'Noodles']
print(expand_to_fill(meals, 5))
print(expand_to_fill(meals, 7))

['Rice', 'Bread', 'Noodles', 'Rice', 'Bread']
['Rice', 'Bread', 'Noodles', 'Rice', 'Bread', 'Noodles', 'Rice']

Partial solution



You can use in or not in to check if an item is in a list.

valid_responses = ['Yes', 'No', 'Maybe']
response = ''
while response not in valid_responses:
  response = input('What is your response?')
print('Your response:', response)

current_drinks = ['Tea', 'Coffee']
suggested_drink = input('Suggest a new drink:')
if suggested_drink in current_drinks:
  print(suggested_drink, 'is already in the list')
 → 

What is your response? abc
What is your response? Yes
Your response: Yes
Suggest a new drink: Tea
Tea is already in the list

Checking if an item is in a list


Exercise: XOR

Exercise : XOR

In case you didn't know: XOR (short for eXclusive OR) is a logical operation, when applied to two booleans, that outputs True only when inputs differ (one is True, the other is False)

Complete the functions as described below:

  • xor(list1, list2, item): returns True if item is in only one of the two lists list1 and list2. i.e., returns False if the item is absent from both lists or present in both lists.
def xor(list1, list2, item):
  return

print(xor(['a','b'], ['a', 'c'], 'a'))
print(xor(['a','b'], ['a', 'c'], 'b'))
print(xor(['a','b'], ['a', 'c'], 'c'))
print(xor(['a','b'], ['a', 'c'], 'd'))
 → 

False
True
True
False

Partial solution



You can traverse through items in a list using the for item_name in list_name: notation.

pets = ['Cats', 'Dogs', 'Hamsters']
for animal in pets:
  print('Do you sell '+ animal +'?')
 → 

Do you sell Cats?
Do you sell Dogs?
Do you sell Hamsters?

Exercise: Print Card Deck

Exercise : Print Card Deck

Complete the functions as described below:

  • print_card_deck(): Prints all cards in a deck of regular playing cards, en example of the output (partial) is given below.
def print_card_deck():
  suits = ['♣', '♦', '♥', '♠']
  ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']

print_card_deck()

 → 

A of ♣
2 of ♣
3 of ♣
4 of ♣
5 of ♣
6 of ♣
...

💡 Hint


Partial solution



The enumerate function can help you easily maintain an iteration counter while traversing a list.

pets = ['Cats', 'Dogs', 'Hamsters']
for i, animal in enumerate(pets):
  print(i+1, 'Can I buy '+ animal +'?')
 → 

1 Can I buy Cats?
2 Can I buy Dogs?
3 Can I buy Hamsters?

Exercise: First Quarter

Exercise : First Quarter

Complete the functions as described below:

  • print_dates(): prints the dates of the first four months of the year, in the format given in the sample output.

  • You must use the given lists months and lengths in your solution.

  • You can use the functions given below:

    • generate_number_string(start, end): Returns a string containing integers from start to end (not inclusive), separated by space.
      e.g., generate_number_string(5, 10)'5 6 7 8 9'
def generate_number_string(start, end):
  number_string = ''
  for i in range(start, end):
    number_string = number_string + ' ' + str(i)
  return number_string
  
def print_dates():
  months = ['Jan', 'Feb', 'Mar', 'Apr'] # months of the first quarter
  lengths = [31, 28, 31, 30] # number of days in each month of the first quarter
  # ADD CODE HERE

print_dates()

Jan: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Feb: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Mar: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Apr: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

Partial solution




Methods

In Python, everything is an object. For example, 'Hello' is a string object, 14 is an integer object, [2.0, 3.5] is a list object that contains two float objects, and so on.

Accordingly, most Python objects have methods. Methods represent what the object can do. They are functions attached to an object and are executed in relation to the attached object. Methods are executed using object.method_name notation. The methods an object will have depend on the type of object  e.g., methods a string object will have are different from the methods a list object will have.

The first line below uses the strip() method of the string object to get rid of leading and trailing spaces. The second statement uses the replace() method to replace spaces with dashes.

print('[' + '  some text  '.strip() + ']')
print('some simple words'.replace(' ', '-'))
 → 

[some text]
some-simple-words

Some methods return an object. In such cases another method can be called on the returned object immediately. Such calling of methods on the returned object is called method chaining.

In the example of method chaining given below, 'aa bb cc '.strip() returns a string object 'aa bb cc' on which the replace(' ', '_') method is called. That returns a string object 'aa_bb_cc' on which the upper() method is called. That method converts the string into its upper case, giving us the final output of 'AA_BB_CC'

print('aa bb cc  '.strip().replace(' ', '_').upper())
 → 

AA_BB_CC

Exercise: Remove Extra Spaces

Exercise : Remove Extra Spaces

Complete the functions as described below:

  • remove_extra_spaces(text): returns a string that is similar to the given string text but with only one space between words if there were multiple spaces before. Also removes leading and trailing spaces.

You may find these built-in string functions useful:

  • split(): splits a string into a list of words.
    e.g., ' quick brown fox'.split()['quick', 'brown', 'fox']
    [more about the split() function]

  • join(list_of_items): joins the items in the list_of_items using the given string.
    e.g., '-'.join(['a', 'b', 'c'])'a-b-c'

def remove_extra_spaces(text):
  return

print('[' + remove_extra_spaces('  aaa bbb   ccc  ') + ']')
 → 

[aaa bbb ccc]

💡 Hint




List Methods

Here are some useful list methods:

Method Description
index() finds the index of an item in a list
append() adds an item to the end of the list
insert() inserts an item to a specific position in the list
remove() removes the specified item from the list
sort() sorts items in the list

Here are some examples of how those list methods can be used:

pets = ['Cats', 'Dogs', 'Hamsters']
print('Hamster is at', pets.index('Hamsters'))
print('Dog is at', pets.index('Dogs'))

pets.append('Parrots')
print('After appending Parrots', pets)

pets.insert(1, 'Rabbits')
print('After inserting Rabbits', pets)

pets.remove('Cats')
print('After removing Cats', pets)

pets.sort()
print('After sorting', pets)

pets.sort(reverse=True)
print('After reverse-sorting', pets)

Hamster is at 2
Dog is at 1
After appending Parrots ['Cats', 'Dogs', 'Hamsters', 'Parrots']
After inserting Rabbits ['Cats', 'Rabbits', 'Dogs', 'Hamsters', 'Parrots']
After removing Cats ['Rabbits', 'Dogs', 'Hamsters', 'Parrots']
After sorting ['Dogs', 'Hamsters', 'Parrots', 'Rabbits']
After reverse-sorting ['Rabbits', 'Parrots', 'Hamsters', 'Dogs']

Note how in the example above sort(reverse=True) sorts the list in the reverse order. This notation of method(parameter_name=argument) is known as passing a keyword argument. Explanation: Some methods/functions has optional parameters. To specify an argument for such an optional parameter, we have to specify which parameter we are supplying. In the case of sort(reverse=True), we are supplying the boolean argument True to the optional parameter named reverse.

The print function takes an optional parameter end. If you provide it with an empty string i.e., end='', the function will not print a line break at the end. The example below illustrates the difference it makes.

pets = ['Rabbits', 'Parrots', 'Hamsters']

for animal in pets:
  print('{' + animal + '}')

for animal in pets:
  print('{' + animal + '}', end='')
 → 

{Rabbits}
{Parrots}
{Hamsters}
{Rabbits}{Parrots}{Hamsters}

List methods


Note that some methods the object while other methods don't.

In the example below, lower() (a non-mutating method) returns a new string that is in lower case while the original string remains unchanged. However, sort() (a mutating method) changes the list object it is attached to (and does not return anything).

name = 'JOHN DOE'
print(name.lower()) # lower() is a non-mutating method
print(name)
 → 

john doe
JOHN DOE
names = ['Zoe', 'Adam', 'Norm']
names.sort() # sort() is a mutating method
print(names)
 → 

['Adam', 'Norm', 'Zoe']

Exercise: Anagram Checker

Exercise : Anagram Checker

Complete the functions as described below:

  • is_anagram(word1, word2): returns True if word1 is an anagram of word2(i.e., letters of word1 can be rearranged to get word2 e.g., ton and not are anagrams).

You may find these built-in functions useful to this exercise:

  • list(string): splits a string into a list of letters.
    e.g., list('quick')['q', 'u', 'i', 'c', 'k']
def is_anagram(word1, word2):
  return  # REPLACE WITH YOUR CODE

print(is_anagram('santa', 'damith'))
print(is_anagram('santa', 'satan'))
 → 

False
True

💡 Hint


Partial solution



Exercise: Word Game

Exercise : Word Game

Implement a word game with the following rules:

  • The player inputs four-letter words. Words that are not four letters are rejected.
  • If the player inputs the same word multiple times, that word is banned.
  • When the player cannot think of any more suitable words, he/she can end the game by entering the word end
  • The score is the number of words entered by the player and accepted by the game (i.e., banned words are not counted)

Given below is a sample session. Try to follow the exact output format in your implementation:

========================================================
Welcome to the WORD GAME
Give all four-letter words you know, one word at a time
Enter the word 'end' to exit
========================================================
What's the next word?  park
What's the next word?  puck
What's the next word?  cut
Not a four-letter word
What's the next word?  people
Not a four-letter word
What's the next word?  team
What's the next word?  puck
Repeated word! puck is number 2 in the accepted words list.
puck is no longer an accepted word and is banned
What's the next word?  bust
What's the next word?  puck
puck is banned!
What's the next word?  meat
What's the next word?  team
Repeated word! team is number 2 in the accepted words list.
team is no longer an accepted word and is banned
What's the next word?  end
========================================================
Your score: 3
Accepted words (in order of entry): park bust meat
Accepted words (in sorted order): bust meat park
Banned words (in sorted order): puck team
Thank you for playing the WORD GAME
========================================================

Implementation suggestions:

  • Maintain two lists: one to keep accepted words, one to keep banned words
  • When a word is entered for the second time, move it from the accepted words list to the banned words list.