Lab 7
The Miles Davis Quintet

Objectives

Step 0: Think about where we are headed

The first step is to think about the overall algorithm that needs to be written. DO NOT write the code yet, but instead think about the steps your function will need to take.

First, here's the function you'll end up writing:

def get_combinations(choose_from, target_len):
    """
    Gets all combinations of a given length chosen from a given list,
    returns a list of those combinations.

    :param choose_from: A list of elements to choose from.
    :param target_len: The target length of the combinations to find.
    :return: A list of lists, where each list is a combination of 
             elements from choose_from of length target_len.
             Returns the empty list if choose_from is empty.
    """

Carefully read the docstring and think about what the function's behavior is (not how it does it, but what it's supposed to do). For each of the following, determine precisely what should be returned. The result is always a list. Pay special attention to how many elements are in the list (and what those elements are):
function call result len(result)
get_combinations(['a','b','c','d','e'], 1) [['a'], ['b'], ['c'], ['d'], ['e']] 5
get_combinations(['a','b','c','d','e'], 5) [['a', 'b', 'c', 'd', 'e']] 1
get_combinations(['a','b','c'], 2) [['a', 'b'], ['a', 'c'], ['b', 'c']] 3
get_combinations([], 2) [] (empty list) 0

Step I: Start with the easy parts

Setup

We are now ready to start writing some code, so start with the following:

  1. Create a PyCharm project. Make sure you use your name somewhere in the project name.
  2. Download some starter code here: combo_finder.py.

Think

Recall from above that if we ask for the combinations of length 5 from a list of length 5, we should get a result list with just one list (containing all the elements to choose from). Also, if the list to choose from is empty, the list of combinations is also empty.

To get started on this, we've already added the following to get_combinations:

if len(choose_from) == target_len:
    return __list_with_one_list_inside(choose_from)
elif len(choose_from) == 0:
    return []

Before you proceed, make sure you understand why this is the right code for these cases.

Write a helper

Notice that this uses a helper function __list_with_one_list_inside that isn't complete yet. So, your next job is to write this function:

  1. Carefully read the docstring,
  2. delete the return None line in __list_with_one_list_inside, and
  3. replace it with appropriate code to implement the behavior the docstring describes.

Test it

You can now test by calling get_combinations with inputs where choose_from has length target_len or 0. I've already provided __test_combos and __test_get_combinations with a single example to start with. Add to __test_get_combinations so that the new code you've written is well-tested.

Test and debug until it works.

Step II: Write a helper function

Think

Now, let's think about the recursive cases in get_combinations (i.e. the part that's currently just return None). Recall from class that the combinations of length 5 from a list of length 7 are of two different forms:

We are going to work on the "stitched together" part now, which I'm defining as the following helper function shown in combo_finder.py:

def __prepend_to_all_lists(prefix, list_of_lists_to_prepend):
    """
    Creates and returns a new list of lists, where each new list is a 
    list for the given list with a given prefix added.
    :param prefix: A single element to prepend onto each list.
    :param list_of_lists_to_prepend: The list of the lists to prepend.
    :return: A list of new lists, containing the contents of each of the
    lists from list_of_lists_to_prepend with the prefix prepended. 
    """

Before you proceed, make sure you understand what this is supposed to do.

Write a helper

Now, in __prepend_to_all_lists delete the return None line and replace with code that implements the desired behavior. Note that if a is an element and b is a list, then [a] + b is a list with the same contents as b but with a prepended onto the beginning.

An important note

Make sure as you write this function that you do not write it to modify the list_of_lists_to_prepend. In fact, get in this habit now: especially when you are writing a recursive function, try to write functions that are "purely functional". I.E. they are functions that take parameters and return values without any side effects (they don't change the parameters and don't modify global variables).

Test it

Test __prepend_to_all_lists by calling it directly with different inputs. For example, try:

__prepend_to_all_lists('a', [['b'], ['c'], ['d', 'e']])

Test and debug until it works.

Step III: Bring it all together

Think

OK, now we have a helper that should make writing the recursive case easier. Recall from above that the combinations of length 5 from a list of length 7 are of two different forms:

In general, combinations of length n from a list l are of two forms:

How do we get the first element? How do we get the rest of the elements? How are we going to stitch things together? Before you proceed, make sure you understand answers to all of these questions.

Finish the code

Now, you are ready to finish the recursive case of get_combinations. Remove the return None line and replace it with code that:

Test it

You can now test get_combinations by adding other cases to __test_get_combinations.

Test and debug until it works.

How to turn in this lab

Before turning in any program in this class, remember our mantra: just because it works doesn't mean it's good. Part of your grade will also come from things like how understandable and readable your code is. You will also be graded on the neatness, presentation, and style of your program code.

For all labs, turn in only an electronic version. Please compress the lab and email the zip file or tarball to me at cassa@union.edu.

Ask for help if you're having problems!