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 |
We are now ready to start writing some code, so start with the following:
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.
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:
You can now test by calling get_combinations with inputs where choose_from has length equal to 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.
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:
In these cases the first element of the list is stitched together with the combinations of length 4 chosen from the remaining elements.
In these cases, the total combinations are those combinations of length 5 chosen from the remaining elements.
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.
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.
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.
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.
Now, you are ready to finish the recursive case of get_combinations. Remove the return None line and replace it with code that:
You can now test get_combinations by adding other cases to __test_get_combinations.
Test and debug until it works.
In your project, you will eventually need a list of PokerHand objects, not a list of lists of Cards. To give you a sense of how you might do that, let's make a new function that gets a list of Combo objects instead of a list of lists.
Add combo.py to your project and copy the following into your combo_finder.py:
def get_list_of_combos(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 Combo objects, where each Combo is a combination of elements from choose_from of length target_len. Returns the empty list if choose_from is empty. """ list_of_lists = get_combinations(choose_from, target_len) to_return = [] for l in list_of_lists: to_return.append(__convert_to_combo(l)) return to_return
Notice that it calls your get_combinations to do most of the interesting work. The only thing remaining is for you to write the __convert_to_combo function, which should take a list of elements and make a Combo object that contains those elements.
You can test get_list_of_combos by changing __test_combos to call it instead of calling get_combinations.
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!