{ "cells": [ { "cell_type": "markdown", "id": "1331faa1", "metadata": {}, "source": [ "You can order print and ebook versions of *Think Python 3e* from\n", "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." ] }, { "cell_type": "code", "execution_count": 1, "id": "260216be", "metadata": { "tags": [] }, "outputs": [], "source": [ "from os.path import basename, exists\n", "\n", "def download(url):\n", " \"\"\"Download a file if it isn't already here, and return its filename.\"\"\"\n", " filename = basename(url)\n", " if not exists(filename):\n", " from urllib.request import urlretrieve\n", "\n", " local, _ = urlretrieve(url, filename)\n", " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", "download('https://github.com/porttack/working-in-python/raw/v3/working_in_python.py');\n", "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "\n", "import working_in_python\n", "# apcsp:begin type=\"note\" chapter=\"18\"\n", "working_in_python.enable_docstring_reminders()\n", "# apcsp:end" ] }, { "cell_type": "markdown", "id": "f5cb253c", "metadata": { "tags": [] }, "source": [ "Here are versions of the `Card`, `Deck`, and `Hand` classes from Chapter 17, which we will use in some examples in this chapter." ] }, { "cell_type": "code", "execution_count": 2, "id": "ad0da137", "metadata": { "tags": [] }, "outputs": [], "source": [ "class Card:\n", " suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']\n", " rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7', \n", " '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']\n", " \n", " def __init__(self, suit, rank):\n", " self.suit = suit\n", " self.rank = rank\n", "\n", " def __str__(self):\n", " rank_name = Card.rank_names[self.rank]\n", " suit_name = Card.suit_names[self.suit]\n", " return f'{rank_name} of {suit_name}' " ] }, { "cell_type": "code", "execution_count": 3, "id": "8b00461b", "metadata": { "tags": [] }, "outputs": [], "source": [ "import random\n", "\n", "class Deck:\n", " def __init__(self, cards):\n", " self.cards = cards\n", " \n", " def __str__(self):\n", " res = []\n", " for card in self.cards:\n", " res.append(str(card))\n", " return '\\n'.join(res)\n", " \n", " def make_cards():\n", " cards = []\n", " for suit in range(4):\n", " for rank in range(2, 15):\n", " card = Card(suit, rank)\n", " cards.append(card)\n", " return cards\n", " \n", " def shuffle(self):\n", " random.shuffle(self.cards)\n", " \n", " def pop_card(self):\n", " return self.cards.pop()\n", " \n", " def add_card(self, card):\n", " self.cards.append(card)" ] }, { "cell_type": "code", "execution_count": 4, "id": "737b8979", "metadata": { "tags": [] }, "outputs": [], "source": [ "class Hand(Deck):\n", " def __init__(self, label=''):\n", " self.label = label\n", " self.cards = []" ] }, { "cell_type": "markdown", "id": "27e8d827", "metadata": {}, "source": [ "# Python Extras\n", "\n", "One of my goals for this book has been to teach you as little Python as possible. \n", "When there were two ways to do something, I picked one and avoided mentioning the other.\n", "Or sometimes I put the second one into an exercise.\n", "\n", "Now I want to go back for some of the good bits that got left behind.\n", "Python provides a number of features that are not really necessary -- you can write good code without them -- but with them you can write code that's more concise, readable, or efficient, and sometimes all three." ] }, { "cell_type": "markdown", "id": "7ddcece8", "metadata": {}, "source": [ "## Sets\n", "\n", "Python provides a class called `set` that represents a collection of unique elements.\n", "To create an empty set, we can use the class object like a function." ] }, { "cell_type": "code", "execution_count": 5, "id": "77f98242", "metadata": {}, "outputs": [], "source": [ "s1 = set()\n", "s1" ] }, { "cell_type": "markdown", "id": "904e2071", "metadata": {}, "source": [ "We can use the `add` method to add elements." ] }, { "cell_type": "code", "execution_count": 6, "id": "85157902", "metadata": {}, "outputs": [], "source": [ "s1.add('a')\n", "s1.add('b')\n", "s1" ] }, { "cell_type": "markdown", "id": "beee02fc", "metadata": {}, "source": [ "Or we can pass any kind of sequence to `set`." ] }, { "cell_type": "code", "execution_count": 7, "id": "b470e34a", "metadata": {}, "outputs": [], "source": [ "s2 = set('acd')\n", "s2" ] }, { "cell_type": "markdown", "id": "42f99153", "metadata": {}, "source": [ "An element can only appear once in a `set`.\n", "If you add an element that's already there, it has no effect." ] }, { "cell_type": "code", "execution_count": 8, "id": "e6a2d78b", "metadata": {}, "outputs": [], "source": [ "s1.add('a')\n", "s1" ] }, { "cell_type": "markdown", "id": "9b0a82ee", "metadata": {}, "source": [ "Or if you create a set with a sequence that contains duplicates, the result contains only unique elements." ] }, { "cell_type": "code", "execution_count": 9, "id": "a4f5ff4f", "metadata": {}, "outputs": [], "source": [ "set('banana')" ] }, { "cell_type": "markdown", "id": "328e2009", "metadata": {}, "source": [ "Some of the exercises in this book can be done concisely and efficiently with sets. \n", "For example, here is a solution to an exercise in Chapter 11 that uses a dictionary to check whether there are any duplicate elements in a sequence." ] }, { "cell_type": "code", "execution_count": 10, "id": "483046f2", "metadata": {}, "outputs": [], "source": [ "def has_duplicates(t):\n", " d = {}\n", " for x in t:\n", " d[x] = True\n", " return len(d) < len(t)" ] }, { "cell_type": "markdown", "id": "0b250e58", "metadata": {}, "source": [ "This version adds the element of `t` as keys in a dictionary, and then checks whether there are fewer keys than elements.\n", "Using sets, we can write the same function like this." ] }, { "cell_type": "code", "execution_count": 11, "id": "fe22c739", "metadata": {}, "outputs": [], "source": [ "def has_duplicates(t):\n", " s = set(t)\n", " return len(s) < len(t)" ] }, { "cell_type": "code", "execution_count": 12, "id": "3bbd3b72", "metadata": { "tags": [] }, "outputs": [], "source": [ "has_duplicates('abba')" ] }, { "cell_type": "markdown", "id": "30cf3158", "metadata": {}, "source": [ "An element can only appear in a set once, so if an element in `t` appears more than once, the set will be smaller than `t`.\n", "If there are no duplicates, the set will be the same size as `t`.\n", "\n", "`set` objects provide methods that perform set operations.\n", "For example, `union` computes the union of two sets, which is a new set that contains all elements that appear in either set." ] }, { "cell_type": "code", "execution_count": 13, "id": "0a8234b1", "metadata": {}, "outputs": [], "source": [ "s1.union(s2)" ] }, { "cell_type": "markdown", "id": "57b1d50c", "metadata": {}, "source": [ "Some arithmetic operators work with sets.\n", "For example, the `-` operator performs set subtraction -- the result is a new set that contains all elements from the first set that are _not_ in the second set." ] }, { "cell_type": "code", "execution_count": 14, "id": "5b81bb56", "metadata": {}, "outputs": [], "source": [ "s1 - s2" ] }, { "cell_type": "markdown", "id": "5099226c", "metadata": {}, "source": [ "In [Chapter 12](section_dictionary_subtraction) we used dictionaries to find the words that appear in a document but not in a word list.\n", "We used the following function, which takes two dictionaries and returns a new dictionary that contains only the keys from the first that don't appear in the second." ] }, { "cell_type": "code", "execution_count": 15, "id": "06a94266", "metadata": {}, "outputs": [], "source": [ "def subtract(d1, d2):\n", " res = {}\n", " for key in d1:\n", " if key not in d2:\n", " res[key] = d1[key]\n", " return res" ] }, { "cell_type": "markdown", "id": "455c3e34", "metadata": {}, "source": [ "With sets, we don't have to write this function ourselves.\n", "If `word_counter` is a dictionary that contains the unique words in the document and `word_list` is a list of valid words, we can compute the set difference like this." ] }, { "cell_type": "code", "execution_count": 16, "id": "3f303544", "metadata": { "tags": [] }, "outputs": [], "source": [ "# this cell creates a small example so we can run the following\n", "# cell without loading the actual data\n", "\n", "word_counter = {'word': 1}\n", "word_list = ['word']" ] }, { "cell_type": "code", "execution_count": 17, "id": "b200cbc2", "metadata": { "tags": [] }, "outputs": [], "source": [ "set(word_counter) - set(word_list)" ] }, { "cell_type": "markdown", "id": "91efe708", "metadata": {}, "source": [ "The result is a set that contains the words in the document that don't appear in the word list.\n", "\n", "The relational operators work with sets.\n", "For example, `<=` checks whether one set is a subset of another, including the possibility that they are equal." ] }, { "cell_type": "code", "execution_count": 18, "id": "15919aca", "metadata": {}, "outputs": [], "source": [ "set('ab') <= set('abc')" ] }, { "cell_type": "markdown", "id": "74d4d824", "metadata": {}, "source": [ "With these operators, we can use sets to do some of the exercises in Chapter 7.\n", "For example, here's a version of `uses_only` that uses a loop." ] }, { "cell_type": "code", "execution_count": 19, "id": "21940f25", "metadata": {}, "outputs": [], "source": [ "def uses_only(word, available):\n", " for letter in word: \n", " if letter not in available:\n", " return False\n", " return True" ] }, { "cell_type": "markdown", "id": "58c1da26", "metadata": {}, "source": [ "`uses_only` checks whether all letters in `word` are in `available`.\n", "With sets, we can rewrite it like this." ] }, { "cell_type": "code", "execution_count": 20, "id": "5769968e", "metadata": {}, "outputs": [], "source": [ "def uses_only(word, available):\n", " return set(word) <= set(available)" ] }, { "cell_type": "markdown", "id": "01ce8cff", "metadata": {}, "source": [ "If the letters in `word` are a subset of the letters in `available`, that means that `word` uses only letters in `available`." ] }, { "cell_type": "markdown", "id": "d7c22ef5", "metadata": {}, "source": [ "## Counters\n", "\n", "A `Counter` is like a set, except that if an element appears more than once, the `Counter` keeps track of how many times it appears.\n", "If you are familiar with the mathematical idea of a \"multiset\", a `Counter` is a\n", "natural way to represent a multiset.\n", "\n", "The `Counter` class is defined in a module called `collections`, so you have to import it.\n", "Then you can use the class object as a function and pass as an argument a string, list, or any other kind of sequence." ] }, { "cell_type": "code", "execution_count": 21, "id": "1baff39a", "metadata": {}, "outputs": [], "source": [ "from collections import Counter\n", "\n", "counter = Counter('banana')\n", "counter" ] }, { "cell_type": "code", "execution_count": 22, "id": "14d5aee5", "metadata": {}, "outputs": [], "source": [ "from collections import Counter\n", "\n", "t = (1, 1, 1, 2, 2, 3)\n", "counter = Counter(t)\n", "counter" ] }, { "cell_type": "markdown", "id": "8da28fe5", "metadata": {}, "source": [ "A `Counter` object is like a dictionary that maps from each key to the number of times it appears.\n", "As in dictionaries, the keys have to be hashable.\n", "\n", "Unlike dictionaries, `Counter` objects don't raise an exception if you access an\n", "element that doesn't appear.\n", "Instead, they return `0`." ] }, { "cell_type": "code", "execution_count": 23, "id": "28eb9235", "metadata": {}, "outputs": [], "source": [ "counter['d']" ] }, { "cell_type": "markdown", "id": "9bb2b650", "metadata": {}, "source": [ "We can use `Counter` objects to solve one of the exercises from Chapter 10, which asks for a function that takes two words and checks whether they are anagrams -- that is, whether the letters from one can be rearranged to spell the other.\n", "\n", "Here's a solution using `Counter` objects." ] }, { "cell_type": "code", "execution_count": 24, "id": "67aadb0b", "metadata": {}, "outputs": [], "source": [ "def is_anagram(word1, word2):\n", " return Counter(word1) == Counter(word2)" ] }, { "cell_type": "markdown", "id": "6907f368", "metadata": {}, "source": [ "If two words are anagrams, they contain the same letters with the same counts, so their `Counter` objects are equivalent.\n", "\n", "`Counter` provides a method called `most_common` that returns a list of value-frequency pairs, sorted from most common to least." ] }, { "cell_type": "code", "execution_count": 25, "id": "0c771fb9", "metadata": {}, "outputs": [], "source": [ "counter.most_common()" ] }, { "cell_type": "markdown", "id": "b02b7dff", "metadata": {}, "source": [ "They also provide methods and operators to perform set-like operations, including addition, subtraction, union and intersection.\n", "For example, the `+` operator combines two `Counter` objects and creates a new `Counter` that contains the keys from both and the sums of the counts.\n", "\n", "We can test it by making a `Counter` with the letters from `'bans'` and adding it to the letters from `'banana'`." ] }, { "cell_type": "code", "execution_count": 26, "id": "38a511cc", "metadata": {}, "outputs": [], "source": [ "counter2 = Counter('bans')\n", "counter + counter2" ] }, { "cell_type": "markdown", "id": "5461328e", "metadata": {}, "source": [ "You'll have a chance to explore other `Counter` operations in the exercises at the end of this chapter." ] }, { "cell_type": "markdown", "id": "7cf47e67", "metadata": {}, "source": [ "## defaultdict\n", "\n", "The `collections` module also provides `defaultdict`, which is like a dictionary except that if you access a key that doesn't exist, it generates a new value automatically.\n", "\n", "When you create a `defaultdict`, you provide a function that's used to create new values.\n", "A function that create objects is sometimes called a **factory**.\n", "The built-in functions that create lists, sets, and other types can be used as factories.\n", "\n", "For example, here's a `defaultdict` that creates a new `list` when needed. " ] }, { "cell_type": "code", "execution_count": 27, "id": "5303812d", "metadata": {}, "outputs": [], "source": [ "from collections import defaultdict\n", "\n", "d = defaultdict(list)\n", "d" ] }, { "cell_type": "markdown", "id": "9f43d537", "metadata": {}, "source": [ "Notice that the argument is `list`, which is a class object, not `list()`, which is a function call that creates a new list.\n", "The factory function doesn't get called unless we access a key that doesn't exist." ] }, { "cell_type": "code", "execution_count": 28, "id": "4955b14f", "metadata": {}, "outputs": [], "source": [ "t = d['new key']\n", "t" ] }, { "cell_type": "markdown", "id": "01f87415", "metadata": {}, "source": [ "The new list, which we're calling `t`, is also added to the dictionary.\n", "So if we modify `t`, the change appears in `d`:" ] }, { "cell_type": "code", "execution_count": 29, "id": "d6a771af", "metadata": {}, "outputs": [], "source": [ "t.append('new value')\n", "d['new key']" ] }, { "cell_type": "markdown", "id": "3e5d0151", "metadata": {}, "source": [ "If you are making a dictionary of lists, you can often write simpler\n", "code using `defaultdict`. \n", "\n", "In one of the exercises in [Chapter 11](chapter_tuples), I made a dictionary that maps from a sorted string of letters to the list of words that can be spelled with those letters.\n", "For example, the string `'opst'` maps to the list `['opts', 'post', 'pots', 'spot', 'stop', 'tops']`.\n", "Here's the original code." ] }, { "cell_type": "code", "execution_count": 30, "id": "9c4166e4", "metadata": {}, "outputs": [], "source": [ "def all_anagrams(filename):\n", " d = {}\n", " for line in open(filename):\n", " word = line.strip().lower()\n", " t = signature(word)\n", " if t not in d:\n", " d[t] = [word]\n", " else:\n", " d[t].append(word)\n", " return d" ] }, { "cell_type": "markdown", "id": "8e9a0a2b", "metadata": {}, "source": [ "And here's a simpler version using a `defaultdict`." ] }, { "cell_type": "code", "execution_count": 31, "id": "e908188e", "metadata": {}, "outputs": [], "source": [ "def all_anagrams(filename):\n", " d = defaultdict(list)\n", " for line in open(filename):\n", " word = line.strip().lower()\n", " t = signature(word)\n", " d[t].append(word)\n", " return d" ] }, { "cell_type": "markdown", "id": "cccdd46c", "metadata": {}, "source": [ "In the exercises at the end of the chapter, you'll have a chance to practice using `defaultdict` objects." ] }, { "cell_type": "code", "execution_count": 32, "id": "4e1e35f7", "metadata": {}, "outputs": [], "source": [ "from collections import defaultdict\n", "\n", "d = defaultdict(list)\n", "key = ('into', 'the')\n", "d[key].append('woods')\n", "d[key]" ] }, { "cell_type": "markdown", "id": "610359c1", "metadata": {}, "source": [ "## Conditional expressions\n", "\n", "Conditional statements are often used to choose one of two values, like this:" ] }, { "cell_type": "code", "execution_count": 33, "id": "25ff32b9", "metadata": { "tags": [] }, "outputs": [], "source": [ "import math\n", "x = 5" ] }, { "cell_type": "code", "execution_count": 34, "id": "11676b80", "metadata": {}, "outputs": [], "source": [ "if x > 0:\n", " y = math.log(x)\n", "else:\n", " y = float('nan')" ] }, { "cell_type": "code", "execution_count": 35, "id": "0249c4c8", "metadata": { "tags": [] }, "outputs": [], "source": [ "y" ] }, { "cell_type": "markdown", "id": "2c5fc3dd", "metadata": {}, "source": [ "This statement checks whether `x` is positive. If so, it computes its logarithm. \n", "If not, `math.log` would raise a ValueError.\n", "To avoid stopping the program, we generate a `NaN`, which is a special floating-point value that represents \"Not a Number\".\n", "\n", "We can write this statement more concisely using a **conditional expression**." ] }, { "cell_type": "code", "execution_count": 36, "id": "803a39c4", "metadata": {}, "outputs": [], "source": [ "y = math.log(x) if x > 0 else float('nan')" ] }, { "cell_type": "code", "execution_count": 37, "id": "03b1afa9", "metadata": { "tags": [] }, "outputs": [], "source": [ "y" ] }, { "cell_type": "markdown", "id": "6a7cf27b", "metadata": {}, "source": [ "You can almost read this line like English: \"`y` gets log-`x` if `x` is greater than 0; otherwise it gets `NaN`\".\n", "\n", "Recursive functions can sometimes be written concisely using conditional expressions. \n", "For example, here is a version of `factorial` with a conditional _statement_." ] }, { "cell_type": "code", "execution_count": 38, "id": "73d3e7e9", "metadata": {}, "outputs": [], "source": [ "def factorial(n):\n", " if n == 0:\n", " return 1\n", " else:\n", " return n * factorial(n-1)" ] }, { "cell_type": "markdown", "id": "56052b5c", "metadata": {}, "source": [ "And here's a version with a conditional _expression_." ] }, { "cell_type": "code", "execution_count": 39, "id": "d14e82df", "metadata": {}, "outputs": [], "source": [ "def factorial(n):\n", " return 1 if n == 0 else n * factorial(n-1)" ] }, { "cell_type": "markdown", "id": "d53fbc15", "metadata": {}, "source": [ "Another use of conditional expressions is handling optional arguments.\n", "For example, here is class definition with an `__init__` method that uses a conditional statement to check a parameter with a default value." ] }, { "cell_type": "code", "execution_count": 40, "id": "aa8fcfe5", "metadata": {}, "outputs": [], "source": [ "class Kangaroo:\n", " def __init__(self, name, contents=None):\n", " self.name = name\n", " if contents is None:\n", " contents = []\n", " self.contents = contents" ] }, { "cell_type": "markdown", "id": "655bfc46", "metadata": {}, "source": [ "Here's a version that uses a conditional expression." ] }, { "cell_type": "code", "execution_count": 41, "id": "fcb67453", "metadata": {}, "outputs": [], "source": [ "def __init__(self, name, contents=None):\n", " self.name = name\n", " self.contents = [] if contents is None else contents " ] }, { "cell_type": "markdown", "id": "fef85229", "metadata": {}, "source": [ "In general, you can replace a conditional statement with a conditional expression if both branches contain a single expression and no statements." ] }, { "cell_type": "markdown", "id": "45d3b306", "metadata": {}, "source": [ "## List comprehensions\n", "\n", "In previous chapters, we've seen a few examples where we start with an empty list and add elements, one at a time, using the `append` method.\n", "For example, suppose we have a string that contains the title of a movie, and we want to capitalize all of the words." ] }, { "cell_type": "code", "execution_count": 42, "id": "a1d31625", "metadata": {}, "outputs": [], "source": [ "title = 'monty python and the holy grail'" ] }, { "cell_type": "markdown", "id": "9eeb45a6", "metadata": {}, "source": [ "We can split it into a list of strings, loop through the strings, capitalize them, and append them to a list." ] }, { "cell_type": "code", "execution_count": 43, "id": "595cdbc8", "metadata": {}, "outputs": [], "source": [ "t = []\n", "for word in title.split():\n", " t.append(word.capitalize())\n", "\n", "' '.join(t)" ] }, { "cell_type": "markdown", "id": "b96197c2", "metadata": {}, "source": [ "We can do the same thing more concisely using a **list comprehension**:" ] }, { "cell_type": "code", "execution_count": 44, "id": "ac327d75", "metadata": {}, "outputs": [], "source": [ "t = [word.capitalize() for word in title.split()]\n", "\n", "' '.join(t)" ] }, { "cell_type": "markdown", "id": "e5b565ad", "metadata": {}, "source": [ "The bracket operators indicate that we are constructing a new list.\n", "The expression inside the brackets specifies the elements of the list, and the `for` clause indicates what sequence we are looping through.\n", "\n", "The syntax of a list comprehension might seem strange, because the loop variable -- `word` in this example -- appears in the expression before we get to its definition.\n", "But you get used to it.\n", "\n", "As another example, in [Chapter 9](section_word_list) we used this loop to read words from a file and append them to a list." ] }, { "cell_type": "code", "execution_count": 45, "id": "036ec803", "metadata": { "tags": [] }, "outputs": [], "source": [ "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython2/master/code/words.txt');" ] }, { "cell_type": "code", "execution_count": 46, "id": "1437f1f1", "metadata": {}, "outputs": [], "source": [ "word_list = []\n", "\n", "for line in open('words.txt'):\n", " word = line.strip()\n", " word_list.append(word)" ] }, { "cell_type": "code", "execution_count": 47, "id": "c9c2a932", "metadata": { "tags": [] }, "outputs": [], "source": [ "len(word_list)" ] }, { "cell_type": "markdown", "id": "2d1df49b", "metadata": {}, "source": [ "Here's how we can write that as a list comprehension." ] }, { "cell_type": "code", "execution_count": 48, "id": "d4f854c6", "metadata": {}, "outputs": [], "source": [ "word_list = [line.strip() for line in open('words.txt')]" ] }, { "cell_type": "code", "execution_count": 49, "id": "3d797346", "metadata": { "tags": [] }, "outputs": [], "source": [ "len(word_list)" ] }, { "cell_type": "markdown", "id": "92d856ba", "metadata": {}, "source": [ "A list comprehension can also have an `if` clause that determines which elements are included in the list.\n", "For example, here's a `for` loop we used in [Chapter 10](section_palindrome_list) to make a list of only the words in `word_list` that are palindromes." ] }, { "cell_type": "code", "execution_count": 50, "id": "d476e43c", "metadata": { "tags": [] }, "outputs": [], "source": [ "def is_palindrome(word):\n", " return list(reversed(word)) == list(word)" ] }, { "cell_type": "code", "execution_count": 51, "id": "6b295593", "metadata": {}, "outputs": [], "source": [ "palindromes = []\n", "\n", "for word in word_list:\n", " if is_palindrome(word):\n", " palindromes.append(word)" ] }, { "cell_type": "code", "execution_count": 52, "id": "f7b1cd91", "metadata": { "tags": [] }, "outputs": [], "source": [ "palindromes[:10]" ] }, { "cell_type": "markdown", "id": "151621d8", "metadata": {}, "source": [ "Here's how we can do the same thing with an list comprehension." ] }, { "cell_type": "code", "execution_count": 53, "id": "c356d9fb", "metadata": {}, "outputs": [], "source": [ "palindromes = [word for word in word_list if is_palindrome(word)]" ] }, { "cell_type": "code", "execution_count": 54, "id": "52e32b33", "metadata": { "tags": [] }, "outputs": [], "source": [ "palindromes[:10]" ] }, { "cell_type": "markdown", "id": "5fc4eab1", "metadata": {}, "source": [ "When a list comprehension is used as an argument to a function, we can often omit the brackets.\n", "For example, suppose we want to add up $1 / 2^n$ for values of $n$ from 0 to 9.\n", "We can use a list comprehension like this." ] }, { "cell_type": "code", "execution_count": 55, "id": "dcf239d7", "metadata": {}, "outputs": [], "source": [ "sum([1/2**n for n in range(10)])" ] }, { "cell_type": "markdown", "id": "2ee312e0", "metadata": {}, "source": [ "Or we can leave out the brackets like this." ] }, { "cell_type": "code", "execution_count": 56, "id": "b9bdec8d", "metadata": {}, "outputs": [], "source": [ "sum(1/2**n for n in range(10))" ] }, { "cell_type": "markdown", "id": "3d56d584", "metadata": {}, "source": [ "In this example, the argument is technically a **generator expression**, not a list comprehension, and it never actually makes a list.\n", "But other than that, the behavior is the same.\n", "\n", "List comprehensions and generator expressions are concise and easy to read, at least for simple expressions.\n", "And they are usually faster than the equivalent for loops, sometimes much faster.\n", "So if you are mad at me for not mentioning them earlier, I understand.\n", "\n", "But, in my defense, list comprehensions are harder to debug because you can't put a print statement inside the loop.\n", "I suggest you use them only if the computation is simple enough that you are likely to get it\n", "right the first time.\n", "Or consider writing and debugging a `for` loop and then converting it to a list comprehension." ] }, { "cell_type": "markdown", "id": "f9fac860", "metadata": {}, "source": [ "## `any` and `all`\n", "\n", "Python provides a built-in function, `any`, that takes a sequence of boolean values and returns `True` if any of the values are `True`." ] }, { "cell_type": "code", "execution_count": 57, "id": "81a15164", "metadata": {}, "outputs": [], "source": [ "any([False, False, True])" ] }, { "cell_type": "markdown", "id": "43217186", "metadata": {}, "source": [ "`any` is often used with generator expressions." ] }, { "cell_type": "code", "execution_count": 58, "id": "7756c9ea", "metadata": {}, "outputs": [], "source": [ "any(letter == 't' for letter in 'monty')" ] }, { "cell_type": "markdown", "id": "22395487", "metadata": {}, "source": [ "That example isn't very useful because it does the same thing as the `in` operator. \n", "But we could use `any` to write concise solutions to some of the exercises in [Chapter 7](chapter_search). For example, we can write `uses_none` like this." ] }, { "cell_type": "code", "execution_count": 59, "id": "70133073", "metadata": {}, "outputs": [], "source": [ "def uses_none(word, forbidden):\n", " \"\"\"Checks whether a word avoids forbidden letters.\"\"\"\n", " return not any(letter in forbidden for letter in word)" ] }, { "cell_type": "code", "execution_count": 60, "id": "aec0523b", "metadata": { "tags": [] }, "outputs": [], "source": [ "uses_none('banana', 'xyz')" ] }, { "cell_type": "code", "execution_count": 61, "id": "863252da", "metadata": { "tags": [] }, "outputs": [], "source": [ "uses_none('apple', 'efg')" ] }, { "cell_type": "markdown", "id": "fbefe3c1", "metadata": {}, "source": [ "This function loops through the letters in `word` and checks whether any of them are in `forbidden`.\n", "Using `any` with a generator expression is efficient because it stops immediately if it finds a `True` value, so it doesn't have to loop through the whole sequence.\n", "\n", "Python provides another built-in function, `all`, that returns `True` if every element of the sequence is `True`.\n", "We can use it to write a concise version of `uses_all`." ] }, { "cell_type": "code", "execution_count": 62, "id": "4b4046aa", "metadata": {}, "outputs": [], "source": [ "def uses_all(word, required):\n", " \"\"\"Check whether a word uses all required letters.\"\"\"\n", " return all(letter in word for letter in required)" ] }, { "cell_type": "code", "execution_count": 63, "id": "5c6addfb", "metadata": { "tags": [] }, "outputs": [], "source": [ "uses_all('banana', 'ban')" ] }, { "cell_type": "code", "execution_count": 64, "id": "54df18a1", "metadata": { "tags": [] }, "outputs": [], "source": [ "uses_all('apple', 'api')" ] }, { "cell_type": "markdown", "id": "8d9f7364", "metadata": {}, "source": [ "Expressions using `any` and `all` can be concise, efficient, and easy to read." ] }, { "cell_type": "markdown", "id": "911857a3", "metadata": {}, "source": [ "## Named tuples\n", "\n", "The `collections` module provides a function called `namedtuple` that can be used to create simple classes.\n", "For example, the `Point` object in [Chapter 16](section_create_point) has only two attributes, `x` and `y`.\n", "Here's how we defined it." ] }, { "cell_type": "code", "execution_count": 65, "id": "3550c5a4", "metadata": {}, "outputs": [], "source": [ "class Point:\n", " \"\"\"Represents a point in 2-D space.\"\"\"\n", " \n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", " \n", " def __str__(self):\n", " return f'({self.x}, {self.y})'" ] }, { "cell_type": "markdown", "id": "36f08927", "metadata": {}, "source": [ "That's a lot of code to convey a small amount of information.\n", "`namedtuple` provides a more concise way to define classes like this." ] }, { "cell_type": "code", "execution_count": 66, "id": "4852da0c", "metadata": {}, "outputs": [], "source": [ "from collections import namedtuple\n", "\n", "Point = namedtuple('Point', ['x', 'y'])" ] }, { "cell_type": "markdown", "id": "942a0877", "metadata": {}, "source": [ "The first argument is the name of the class you want to create. The\n", "second is a list of the attributes `Point` objects should have.\n", "The result is a class object, which is why it is assigned to a capitalized variable name.\n", "\n", "A class created with `namedtuple` provides an `__init__` method that assigns values to the attributes and a `__str__` that displays the object in a readable form.\n", "So we can create and display a `Point` object like this." ] }, { "cell_type": "code", "execution_count": 67, "id": "8acb172d", "metadata": {}, "outputs": [], "source": [ "p = Point(1, 2)\n", "p" ] }, { "cell_type": "markdown", "id": "b42ee9a2", "metadata": {}, "source": [ "`Point` also provides an `__eq__` method that checks whether two `Point` objects are equivalent -- that is, whether their attributes are the same." ] }, { "cell_type": "code", "execution_count": 68, "id": "494b78ac", "metadata": {}, "outputs": [], "source": [ "p == Point(1, 2)" ] }, { "cell_type": "markdown", "id": "9bcf275a", "metadata": {}, "source": [ "You can access the elements of a named tuple by name or by index." ] }, { "cell_type": "code", "execution_count": 69, "id": "f59e85f4", "metadata": {}, "outputs": [], "source": [ "p.x, p.y" ] }, { "cell_type": "code", "execution_count": 70, "id": "742795a9", "metadata": {}, "outputs": [], "source": [ "p[0], p[1]" ] }, { "cell_type": "markdown", "id": "0768ff41", "metadata": {}, "source": [ "You can also treat a named tuple as a tuple, as in this assignment." ] }, { "cell_type": "code", "execution_count": 71, "id": "df42d98a", "metadata": {}, "outputs": [], "source": [ "x, y = p\n", "x, y" ] }, { "cell_type": "markdown", "id": "964aa3bd", "metadata": {}, "source": [ "But `namedtuple` objects are immutable.\n", "After the attributes are initialized, they can't be changed." ] }, { "cell_type": "code", "execution_count": 72, "id": "e61693ba", "metadata": { "tags": [] }, "outputs": [], "source": [ "\n", "p[0] = 3" ] }, { "cell_type": "code", "execution_count": 73, "id": "871dafae", "metadata": { "tags": [] }, "outputs": [], "source": [ "\n", "p.x = 3" ] }, { "cell_type": "markdown", "id": "f2db7783", "metadata": {}, "source": [ "`namedtuple` provides a quick way to define simple classes.\n", "The drawback is that simple classes don't always stay simple.\n", "You might decide later that you want to add methods to a named tuple.\n", "In that case, you can define a new class that inherits from the named tuple." ] }, { "cell_type": "code", "execution_count": 74, "id": "586d8c51", "metadata": {}, "outputs": [], "source": [ "class Pointier(Point):\n", " \"\"\"This class inherits from Point\"\"\"" ] }, { "cell_type": "markdown", "id": "805475ce", "metadata": {}, "source": [ "Or at that point you could switch to a conventional class definition." ] }, { "cell_type": "markdown", "id": "4f3713a0", "metadata": {}, "source": [ "## Packing keyword arguments\n", "\n", "In [Chapter 11](section_argument_pack), we wrote a function that packs its arguments into a tuple." ] }, { "cell_type": "code", "execution_count": 75, "id": "6de26a32", "metadata": {}, "outputs": [], "source": [ "def mean(*args):\n", " return sum(args) / len(args)" ] }, { "cell_type": "markdown", "id": "71e3b049", "metadata": {}, "source": [ "You can call this function with any number of arguments." ] }, { "cell_type": "code", "execution_count": 76, "id": "ecb6a9fa", "metadata": {}, "outputs": [], "source": [ "mean(1, 2, 3)" ] }, { "cell_type": "markdown", "id": "486a690f", "metadata": {}, "source": [ "But the `*` operator doesn't pack keyword arguments.\n", "So calling this function with a keyword argument causes an error." ] }, { "cell_type": "code", "execution_count": 77, "id": "5871229d", "metadata": { "tags": [] }, "outputs": [], "source": [ "\n", "mean(1, 2, start=3)" ] }, { "cell_type": "markdown", "id": "eb7f9281", "metadata": {}, "source": [ "To pack keyword arguments, we can use the `**` operator:" ] }, { "cell_type": "code", "execution_count": 78, "id": "b274db6a", "metadata": {}, "outputs": [], "source": [ "def mean(*args, **kwargs):\n", " print(kwargs)\n", " return sum(args) / len(args)" ] }, { "cell_type": "markdown", "id": "067bf7c4", "metadata": {}, "source": [ "The keyword-packing parameter can have any name, but `kwargs` is a common choice.\n", "The result is a dictionary that maps from keywords to values." ] }, { "cell_type": "code", "execution_count": 79, "id": "3a1b131c", "metadata": {}, "outputs": [], "source": [ "mean(1, 2, start=3)" ] }, { "cell_type": "markdown", "id": "07be77f3", "metadata": {}, "source": [ "In this example, the value of `kwargs` is printed, but otherwise it has no effect.\n", "\n", "But the `**` operator can also be used in an argument list to unpack a dictionary.\n", "For example, here's a version of `mean` that packs any keyword arguments it gets and then unpacks them as keyword arguments for `sum`." ] }, { "cell_type": "code", "execution_count": 80, "id": "a0305500", "metadata": {}, "outputs": [], "source": [ "def mean(*args, **kwargs):\n", " return sum(args, **kwargs) / len(args)" ] }, { "cell_type": "markdown", "id": "ba00858c", "metadata": {}, "source": [ "Now if we call `mean` with `start` as a keyword argument, it gets passed along to sum, which uses it as the starting point of the summation.\n", "In the following example `start=3` adds `3` to the sum before computing the mean, so the sum is `6` and the result is `3`." ] }, { "cell_type": "code", "execution_count": 81, "id": "457d5610", "metadata": {}, "outputs": [], "source": [ "mean(1, 2, start=3)" ] }, { "cell_type": "markdown", "id": "949a2ca3", "metadata": {}, "source": [ "As another example, if we have a dictionary with keys `x` and `y`, we can use it with the unpack operator to create a `Point` object." ] }, { "cell_type": "code", "execution_count": 82, "id": "020c1243", "metadata": {}, "outputs": [], "source": [ "d = dict(x=1, y=2)\n", "Point(**d)" ] }, { "cell_type": "markdown", "id": "8aaf128a", "metadata": {}, "source": [ "Without the unpack operator, `d` is treated as a single positional argument, so it gets assigned to `x`, and we get a `TypeError` because there's no second argument to assign to `y`." ] }, { "cell_type": "code", "execution_count": 83, "id": "ba91298b", "metadata": { "tags": [] }, "outputs": [], "source": [ "\n", "d = dict(x=1, y=2)\n", "Point(d)" ] }, { "cell_type": "markdown", "id": "e8acb958", "metadata": {}, "source": [ "When you are working with functions that have a large number of keyword arguments, it is often useful to create and pass around dictionaries that specify frequently used options." ] }, { "cell_type": "code", "execution_count": 84, "id": "1b36b5ed", "metadata": {}, "outputs": [], "source": [ "def pack_and_print(**kwargs):\n", " print(kwargs)\n", " \n", "pack_and_print(a=1, b=2)" ] }, { "cell_type": "markdown", "id": "e046e382", "metadata": {}, "source": [ "## Debugging\n", "\n", "In previous chapters, we used `doctest` to test functions.\n", "For example, here's a function called `add` that takes two numbers and returns their sum.\n", "In includes a doctest that checks whether `2 + 2` is `4`." ] }, { "cell_type": "code", "execution_count": 85, "id": "e1366e43", "metadata": {}, "outputs": [], "source": [ "def add(a, b):\n", " '''Add two numbers.\n", " \n", " >>> add(2, 2)\n", " 4\n", " '''\n", " return a + b" ] }, { "cell_type": "markdown", "id": "a5e332d3", "metadata": {}, "source": [ "This function takes a function object and runs its doctests." ] }, { "cell_type": "code", "execution_count": 86, "id": "13802dc4", "metadata": {}, "outputs": [], "source": [ "from doctest import run_docstring_examples\n", "\n", "def run_doctests(func):\n", " run_docstring_examples(func, globals(), name=func.__name__)" ] }, { "cell_type": "markdown", "id": "2d752a40", "metadata": {}, "source": [ "So we can test `add` like this." ] }, { "cell_type": "code", "execution_count": 87, "id": "2136083a", "metadata": {}, "outputs": [], "source": [ "run_doctests(add)" ] }, { "cell_type": "markdown", "id": "77d36e9b", "metadata": {}, "source": [ "There's no output, which means all tests passed.\n", "\n", "Python provides another tool for running automated tests, called `unittest`.\n", "It is a little more complicated to use, but here's an example." ] }, { "cell_type": "code", "execution_count": 88, "id": "d6bc8bae", "metadata": {}, "outputs": [], "source": [ "from unittest import TestCase\n", "\n", "class TestExample(TestCase):\n", "\n", " def test_add(self):\n", " result = add(2, 2)\n", " self.assertEqual(result, 4)" ] }, { "cell_type": "markdown", "id": "59b4212a", "metadata": {}, "source": [ "First we import `TestCase`, which is a class in the `unittest` module.\n", "To use it, we have to define a new class that inherits from `TestCase` and provides at least one test method.\n", "The name of the test method must begin with `test` and should indicate which function it tests.\n", "\n", "In this example, `test_add` tests the `add` function by calling it, saving the result, and invoking `assertEqual`, which is inherited from `TestCase`.\n", "`assertEqual` takes two arguments and checks whether they are equal.\n", "\n", "In order to run this test method, we have to run a function in `unittest` called `main` and provide several keyword arguments.\n", "The following function shows the details -- if you are curious, you can ask a virtual assistant to explain how it works." ] }, { "cell_type": "code", "execution_count": 89, "id": "96587ab7", "metadata": {}, "outputs": [], "source": [ "import unittest\n", "\n", "def run_unittest():\n", " unittest.main(argv=[''], verbosity=0, exit=False)" ] }, { "cell_type": "markdown", "id": "5409ea0c", "metadata": {}, "source": [ "`run_unittest` does not take `TestExample` as an argument -- instead, it searches for classes that inherit from `TestCase`.\n", "Then it searches for methods that begin with `test` and runs them.\n", "This process is called **test discovery**.\n", "\n", "Here's what happens when we call `run_unittest`." ] }, { "cell_type": "code", "execution_count": 90, "id": "d3db5642", "metadata": {}, "outputs": [], "source": [ "run_unittest()" ] }, { "cell_type": "markdown", "id": "7775304a", "metadata": {}, "source": [ "`unittest.main` reports the number of tests it ran and the results.\n", "In this case `OK` indicates that the tests passed.\n", "\n", "To see what happens when a test fails, we'll add an incorrect test method to `TestExample`." ] }, { "cell_type": "code", "execution_count": 91, "id": "e5320931", "metadata": {}, "outputs": [], "source": [ "%%add_method_to TestExample\n", "\n", " def test_add_broken(self):\n", " result = add(2, 2)\n", " self.assertEqual(result, 100)" ] }, { "cell_type": "markdown", "id": "96810614", "metadata": {}, "source": [ "Here's what happens when we run the tests." ] }, { "cell_type": "code", "execution_count": 92, "id": "c8d4fa4a", "metadata": {}, "outputs": [], "source": [ "run_unittest()" ] }, { "cell_type": "markdown", "id": "64b743cb", "metadata": {}, "source": [ "The report includes the test method that failed and an error message showing where.\n", "The summary indicates that two tests ran and one failed.\n", "\n", "In the exercises below, I'll suggest some prompts you can use to ask a virtual assistant for more information about `unittest`." ] }, { "cell_type": "markdown", "id": "7d0fb256", "metadata": {}, "source": [ "## Glossary\n", "\n", "**factory:**\n", " A function used to create objects, often passed as a parameter to a function.\n", "\n", "**conditional expression:**\n", "An expression that uses a conditional to select one of two values.\n", "\n", "**list comprehension:**\n", "A concise way to loop through a sequence and create a list.\n", "\n", "**generator expression:**\n", "Similar to a list comprehension except that it does not create a list.\n", "\n", "**test discovery:**\n", "A process used to find and run tests." ] }, { "cell_type": "markdown", "id": "1b8180f7", "metadata": {}, "source": [ "\n", "**See also:** the [full vocabulary glossary](https://python.porttack.com/alignment/ap-vocabulary-glossary.html) collects every term in this book, alphabetized, alongside the complete AP CSP exam vocabulary list.\n", "" ] }, { "cell_type": "markdown", "id": "bc03f15d", "metadata": {}, "source": [ "## Exercises" ] }, { "cell_type": "code", "execution_count": null, "id": "5029c76d", "metadata": { "tags": [] }, "outputs": [], "source": [ "# This cell tells Jupyter to provide detailed debugging information\n", "# when a runtime error occurs. Run it before working on the exercises.\n", "\n", "%xmode Verbose" ] }, { "cell_type": "markdown", "id": "fe10415e", "metadata": {}, "source": [ "### Ask a virtual assistant\n", "\n", "There are a few topics in this chapter you might want to learn about.\n", "\n", "* \"What are the methods and operators of Python's set class?\"\n", "\n", "* \"What are the methods and operators of Python's Counter class?\"\n", "\n", "* \"What is the difference between a Python list comprehension and a generator expression?\"\n", "\n", "* \"When should I use Python's `namedtuple` rather than define a new class?\"\n", "\n", "* \"What are some uses of packing and unpacking keyword arguments?\"\n", "\n", "* \"How does `unittest` do test discovery?\"\n", "\n", "* \"Along with `assertEqual`, what are the most commonly used methods in `unittest.TestCase`?\"\n", "\n", "* \"What are the pros and cons of `doctest` and `unittest`?\"\n", "\n", "For the following exercises, consider asking a virtual assistant for help, but as always, remember to test the results." ] }, { "cell_type": "markdown", "id": "c61ecde2", "metadata": {}, "source": [ "### Exercise\n", "\n", "One of the exercises in Chapter 7 asks for a function called `uses_none` that takes a word and a string of forbidden letters, and returns `True` if the word does not use any of the letters. Here's a solution." ] }, { "cell_type": "code", "execution_count": 93, "id": "f75b48e0", "metadata": {}, "outputs": [], "source": [ "def uses_none(word, forbidden):\n", " for letter in word.lower():\n", " if letter in forbidden.lower():\n", " return False\n", " return True" ] }, { "cell_type": "markdown", "id": "b558b8b3", "metadata": {}, "source": [ "Write a version of this function that uses `set` operations instead of a `for` loop.\n", "Hint: ask a VA, \"How do I compute the intersection of Python sets?\"" ] }, { "cell_type": "markdown", "id": "e97dfead", "metadata": { "tags": [] }, "source": [ "You can use this outline to get started." ] }, { "cell_type": "code", "execution_count": 94, "id": "2024d4c6", "metadata": { "tags": [] }, "outputs": [], "source": [ "def uses_none(word, forbidden):\n", " \"\"\"Checks whether a word avoid forbidden letters.\n", " \n", " >>> uses_none('banana', 'xyz')\n", " True\n", " >>> uses_none('apple', 'efg')\n", " False\n", " >>> uses_none('', 'abc')\n", " True\n", " \"\"\"\n", " return False" ] }, { "cell_type": "code", "execution_count": 95, "id": "46a13618", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 96, "id": "160a29b3", "metadata": { "tags": [] }, "outputs": [], "source": [ "from doctest import run_docstring_examples\n", "\n", "def run_doctests(func):\n", " run_docstring_examples(func, globals(), name=func.__name__)" ] }, { "cell_type": "code", "execution_count": 97, "id": "8db988e6", "metadata": { "tags": [] }, "outputs": [], "source": [ "run_doctests(uses_none)" ] }, { "cell_type": "markdown", "id": "d2d670cf", "metadata": {}, "source": [ "### Exercise\n", "\n", "Scrabble is a board game where the objective is to use letter tiles to spell words.\n", "For example, if we have tiles with the letters `T`, `A`, `B`, `L`, `E`, we can spell `BELT` and `LATE` using a subset of the tiles -- but we can't spell `BEET` because we don't have two `E`s.\n", "\n", "Write a function that takes a string of letters and a word, and checks whether the letters can spell the word, taking into account how many times each letter appears." ] }, { "cell_type": "markdown", "id": "091919bd", "metadata": { "tags": [] }, "source": [ "You can use the following outline to get started." ] }, { "cell_type": "code", "execution_count": 98, "id": "b3c2c475", "metadata": { "tags": [] }, "outputs": [], "source": [ "def can_spell(letters, word):\n", " \"\"\"Check whether the letters can spell the word.\n", " \n", " >>> can_spell('table', 'belt')\n", " True\n", " >>> can_spell('table', 'late')\n", " True\n", " >>> can_spell('table', 'beet')\n", " False\n", " \"\"\"\n", " return False" ] }, { "cell_type": "code", "execution_count": 99, "id": "a67768c0", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 100, "id": "7900d2ed", "metadata": { "tags": [] }, "outputs": [], "source": [ "run_doctests(can_spell)" ] }, { "cell_type": "markdown", "id": "de2dc099", "metadata": {}, "source": [ "### Exercise\n", "\n", "In one of the exercises from [Chapter 17](chapter_inheritance), my solution to `has_straightflush` uses the following method, which partitions a `PokerHand` into a list of four hands, where each hand contains cards of the same suit." ] }, { "cell_type": "code", "execution_count": 101, "id": "661f1635", "metadata": {}, "outputs": [], "source": [ " def partition(self):\n", " \"\"\"Make a list of four hands, each containing only one suit.\"\"\"\n", " hands = []\n", " for i in range(4):\n", " hands.append(PokerHand())\n", " \n", " for card in self.cards:\n", " hands[card.suit].add_card(card)\n", " \n", " return hands" ] }, { "cell_type": "markdown", "id": "cd04a7a3", "metadata": {}, "source": [ "Write a simplified version of this function using a `defaultdict`." ] }, { "cell_type": "markdown", "id": "c236c5c7", "metadata": { "tags": [] }, "source": [ "Here's an outline of the `PokerHand` class and the `partition_suits` function you can use to get started." ] }, { "cell_type": "code", "execution_count": 102, "id": "b9cf2e47", "metadata": { "tags": [] }, "outputs": [], "source": [ "class PokerHand(Hand):\n", " \n", " def partition(self):\n", " return {}" ] }, { "cell_type": "code", "execution_count": 103, "id": "33f15964", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "9b176e67", "metadata": { "tags": [] }, "source": [ "To test your code, we'll make a deck and shuffle it." ] }, { "cell_type": "code", "execution_count": 104, "id": "339f912a", "metadata": { "tags": [] }, "outputs": [], "source": [ "cards = Deck.make_cards()\n", "deck = Deck(cards)\n", "deck.shuffle()" ] }, { "cell_type": "markdown", "id": "d253e3ad", "metadata": { "tags": [] }, "source": [ "Then create a `PokerHand` and add seven cards to it." ] }, { "cell_type": "code", "execution_count": 105, "id": "b4efde58", "metadata": { "tags": [] }, "outputs": [], "source": [ "random_hand = PokerHand('random')\n", "\n", "for i in range(7):\n", " card = deck.pop_card()\n", " random_hand.add_card(card)\n", " \n", "print(random_hand)" ] }, { "cell_type": "markdown", "id": "a714e145", "metadata": { "tags": [] }, "source": [ "If you invoke `partition` and print the results, each hand should contain cards of one suit only." ] }, { "cell_type": "code", "execution_count": 106, "id": "bfddf015", "metadata": { "tags": [] }, "outputs": [], "source": [ "hand_dict = random_hand.partition()\n", "\n", "for hand in hand_dict.values():\n", " print(hand)\n", " print()" ] }, { "cell_type": "markdown", "id": "218798e3", "metadata": {}, "source": [ "### Exercise\n", "\n", "Here's the function from Chapter 11 that computes Fibonacci numbers." ] }, { "cell_type": "code", "execution_count": 107, "id": "f854f6d5", "metadata": {}, "outputs": [], "source": [ "def fibonacci(n):\n", " if n == 0:\n", " return 0\n", " \n", " if n == 1:\n", " return 1\n", "\n", " return fibonacci(n-1) + fibonacci(n-2)" ] }, { "cell_type": "markdown", "id": "6acab624", "metadata": {}, "source": [ "Write a version of this function with a single return statement that use two conditional expressions, one nested inside the other." ] }, { "cell_type": "code", "execution_count": 108, "id": "4efa9382", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 109, "id": "6440d14b", "metadata": { "tags": [] }, "outputs": [], "source": [ "fibonacci(10) # should be 55" ] }, { "cell_type": "code", "execution_count": 110, "id": "3b9d5bac", "metadata": { "tags": [] }, "outputs": [], "source": [ "fibonacci(20) # should be 6765" ] }, { "cell_type": "markdown", "id": "2deb0e1f", "metadata": {}, "source": [ "### Exercise\n", "The following is a function that computes the binomial coefficient\n", "recursively." ] }, { "cell_type": "code", "execution_count": 111, "id": "9a9e43fa", "metadata": {}, "outputs": [], "source": [ "def binomial_coeff(n, k):\n", " \"\"\"Compute the binomial coefficient \"n choose k\".\n", "\n", " n: number of trials\n", " k: number of successes\n", "\n", " returns: int\n", " \"\"\"\n", " if k == 0:\n", " return 1\n", " \n", " if n == 0:\n", " return 0\n", "\n", " return binomial_coeff(n-1, k) + binomial_coeff(n-1, k-1)" ] }, { "cell_type": "markdown", "id": "656c61f6", "metadata": {}, "source": [ "Rewrite the body of the function using nested conditional expressions.\n", "\n", "This function is not very efficient because it ends up computing the same values over and over.\n", "Make it more efficient by memoizing it, as described in [Chapter 10](section_memos)." ] }, { "cell_type": "code", "execution_count": 112, "id": "07226bce", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 113, "id": "1e3b8009", "metadata": {}, "outputs": [], "source": [ "binomial_coeff(10, 4) # should be 210" ] }, { "cell_type": "markdown", "id": "921719dc", "metadata": {}, "source": [ "### Exercise\n", "\n", "Here's the `__str__` method from the `Deck` class in [Chapter 17](section_print_deck)." ] }, { "cell_type": "code", "execution_count": 114, "id": "4fd0793d", "metadata": {}, "outputs": [], "source": [ "%%add_method_to Deck\n", "\n", " def __str__(self):\n", " res = []\n", " for card in self.cards:\n", " res.append(str(card))\n", " return '\\n'.join(res)" ] }, { "cell_type": "markdown", "id": "27f189cf", "metadata": {}, "source": [ "Write a more concise version of this method with a list comprehension or generator expression." ] }, { "cell_type": "code", "execution_count": 115, "id": "2aa7da71", "metadata": { "tags": [] }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "25525582", "metadata": { "tags": [] }, "source": [ "You can use this example to test your solution." ] }, { "cell_type": "code", "execution_count": 116, "id": "cd8bb7d2", "metadata": { "tags": [] }, "outputs": [], "source": [ "cards = Deck.make_cards()\n", "deck = Deck(cards)\n", "print(deck)" ] }, { "cell_type": "code", "execution_count": null, "id": "16ce7d9f", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "a7f4edf8", "metadata": { "tags": [] }, "source": [ "\n", "**Working in Python** — modified by Eric Brown for a high school Computer Science Principles class. Source and modifications: [github.com/porttack/working-in-python](https://github.com/porttack/working-in-python)\n", "\n", "---\n", "\n", "\n", "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", "\n", "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", "\n", "Code license: [MIT License](https://mit-license.org/)\n", "\n", "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" ] } ], "metadata": { "celltoolbar": "Tags", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.11" } }, "nbformat": 4, "nbformat_minor": 5 }