Computer Science

# List Patterns

We are going to show you some common patterns when using lists in your programs. Recognizing these patterns will help you know how to solve a problem you are given. If a problem looks like one of these, then you can follow this pattern to write your code.

## Map

The map pattern occurs when you want to take a list of values and individually map each value in the list to a new value. Here is an example:

Original ItemsNew Items
ballballroom
bathbathroom
bedbedroom
familyfamilyroom
foodfoodroom
carcarroom

Notice how we can make a new list by taking each item from the original list and concatenating ‘room’ to create a new word.

The steps for this pattern are:

• create a new, empty list
• iterate through each item in the original list
• use the item in the original list to create a new item
• append the new item to the new list
• return the new list

We can see an example of this pattern in the file called make_rooms.py:

def make_rooms(words):
rooms = []
for word in words:
room = word + 'room'
rooms.append(room)
return rooms

if __name__ == '__main__':
some_words = ['ball', 'bath', 'bed', 'family', 'food', 'car']
rooms = make_rooms(some_words)
print(f'Original words: {some_words}')
print(f'Rooms: {rooms}')

Notice how we use the original list words to make a new list rooms. Every word in words maps to a room in rooms.

You can see another example of this pattern in smaller_numbers.py, which takes a list of numbers and makes numbers smaller by dividing them by two, keeping only the integer portion. To do this, you need to be sure to use the // operator, which does floor division, meaning it works like regular division but returns the largest possible integer that divides into numerator by the denominator.

def make_smaller(numbers):
smaller_numbers = []
for number in numbers:
smaller = number // 2
smaller_numbers.append(smaller)
return smaller_numbers

if __name__ == '__main__':
original = [1, 2, 3, 4, 5, 6, 7, 8]
divided_by_two = make_smaller(original)
print(original)
print(divided_by_two)

## Filter

This example illustrates the filter pattern. By filter we mean that we start with a list and use its values to calculate and return a new list that has some or all of the original values. For we could take a list of numbers and return a new list that has only the odd numbers from the original list:

Original ItemsNew Items
23
35
4
5

The steps for this pattern are:

• create a new, empty list
• iterate through each item in the original list
• append the item to the new list if it meets some criteria
• return the new list

The file only_odds.py has code for this example:

def only_odds(numbers: list[int]) -> list[int]:
odds = []
for number in numbers:
if (number % 2) == 1:
odds.append(number)
return odds

if __name__ == '__main__':
print(only_odds([1, 2, 3, 4, 5, 6]))

We start by initializing a variable odds to an empty list. We then iterate through the list and check if each number is odd. We can do this by using the mod operator, %, which calculates the remainder, and check if the remainder is zero. If it is not zero, then we have an odd number, so we can append it to the list we store in the odds variable.

The function finishes by returning odds, so eventually the print function will print [3, 5].

## Select

This example illustrates the select pattern, which chooses a single value from a list, such as a minimum or maximum value.

The steps for this pattern are:

• initialize a variable to store the selected value; this is set to None
• iterate through the list
• if the variable is still None or the current item is “better” (smaller or larger) than the value of the variable
• change the variable so it now has the current item
• return the variable

The file called find_min.py has an example:

def find_min(numbers: list[int]) -> int:
smallest = None
for number in numbers:
if smallest is None or number < smallest:
smallest = number
return smallest

if __name__ == '__main__':
print(find_min([3, 6, 2, 8, 1, 7]))

Here we are trying to calculate the minimum value of a list of numbers. We start by initializing smallest to None, because there is no smallest number yet. Then, when we iterate over the list, we do two checks with an if statement. First, we check if smallest is still None. If it is, that means we haven’t found a smallest number yet, so the one we are currently looking at must be the smallest. Second, we check if the current number is smaller than smallest. If either of these checks succeeds, we set smallest = number.

Here is a chart for the iterations of the loop:

Iterationnumbersmallest
0N/ANone
11010
288
3408
4228
533
653

At the end, smallest = 3.

Notice that if we ran this code:

result = find_min([])

then we would get result = None.

## Accumulate

This example demonstrates the accumulate pattern. By accumulate, we mean calculating a single value as we iterate, like taking a sum or an average.

The steps for this pattern are:

• initialize a variable to an appropriate value (for example, zero)
• iterate through the list
• use the new item to modify the variable (for example, add it to the variable or subtract it)
• return the variable

The file average.py contains an example:

def average(numbers: list[int]) -> float:
total = 0
for number in numbers:
total = total + number

if __name__ == '__main__':
print(average([1, 2, 3, 4]))

In the average() function, we initialize the total variable to zero. Then, each time we iterate through the numbers, we increase total by the current number. We finally return the total divided by the length of the list.

Here is a chart showing how this code updates the number and total variables each time through the loop. Iteration number 0 is where we start before the for loop.

Iterationnumbertotal
0N/A0
111
223
336
4410

At the end of the function, it returns total / 4, which is 2.5.

## Multiple patterns at once

Here is a problem that requires using multiple patterns: given a list of numbers, write a function that subtracts 7 and then removes any negative numbers and any numbers greater than 10.

There is starter code in all_together.py:

def make_it_happen(numbers: list[int]) -> list[int]:
# Write code here
pass

if __name__ == '__main__':
original = [0, 7, 2, 14, 20, 32, 5, 12]
changed = make_it_happen(original)
print(changed)

How would you solve this problem? Focus on the functions you would call in make_it_happen().

Here is one idea of how to do this:

def make_it_happen(numbers: list[int]) -> list[int]:
numbers = sub_7(numbers)
numbers = filter_numbers(numbers)
return numbers

We create two functions — one to do the subtraction (which should use the map pattern) and one to do the filtering.

We can implement sub_7() first:

def sub_7(numbers: list[int]) -> list[int]:
new = []
for number in numbers:
new.append(number - 7)
return new

This follows the map pattern. Notice that we do the mapping and appending all in one step with new.append(number - 7).

We can temporarily use an empty function for filter_numbers(). Since this function needs to return something, we can do this with:

def filter_numbers(numbers: list[int]) -> list[int]:
return numbers

That just returns the original list unchanged. Now we can run our program, which starts with:

nums = [0, 7, 2, 14, 20, 32, 5, 12]

We get the following printed out:

[-7, 0, -5, 7, 13, 25, -2, 5]

That looks good!

Now we can implement filtering:

def should_keep(number: int) -> bool:
return number >= 0 and number <= 10

def filter_numbers(numbers: list[int]) -> list[int]:
new = []
for number in numbers:
if should_keep(number):
new.append(number)
return new

Notice that we use a separate function should_keep() to implement the decision about whether to keep a number in the list. We can do this with number >= 0 and number <= 10. We phrased this problem as removing any negative numbers and any numbers greater than 10, but for filtering we need to turn that into a statement about which numbers to keep: keeping all numbers greater than or equal to zero and less than or equal to 10.

Now when we run our code we get:

[0, 7, 5]

This has kept all of the numbers between and including 0 and 10.