diff --git a/ThinkPythonNotebooks.zip b/ThinkPythonNotebooks.zip index deb0379..f5c7866 100644 Binary files a/ThinkPythonNotebooks.zip and b/ThinkPythonNotebooks.zip differ diff --git a/ThinkPythonSolutions/.github/workflows/tests.yml b/ThinkPythonSolutions/.github/workflows/tests.yml new file mode 100644 index 0000000..a3f677c --- /dev/null +++ b/ThinkPythonSolutions/.github/workflows/tests.yml @@ -0,0 +1,37 @@ +name: tests + +on: + push: + branches: [v3] + pull_request: + schedule: + # Run on the first of every month + - cron: "0 0 1 * *" + workflow_dispatch: + +jobs: + tests: + name: Tests (${{ matrix.os }}, Python ${{ matrix.python-version }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + # don't test on windows + os: [ubuntu-latest, macos-latest] + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Run tests + run: | + make tests diff --git a/blank/chap00.ipynb b/blank/chap00.ipynb new file mode 100644 index 0000000..55b62c2 --- /dev/null +++ b/blank/chap00.ipynb @@ -0,0 +1,259 @@ +{ + "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": "markdown", + "id": "d9724920", + "metadata": {}, + "source": [ + "# Preface\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "b76f38c6", + "metadata": {}, + "source": [ + "## Who Is This Book For?\n", + "\n", + "If you want to learn to program, you have come to the right place.\n", + "Python is one of the best programming languages for beginners -- and it is also one of the most in-demand skills.\n", + "\n", + "You have also come at the right time, because learning to program now is probably easier than ever.\n", + "With virtual assistants like ChatGPT, you don't have to learn alone.\n", + "Throughout this book, I'll suggest ways you can use these tools to accelerate your learning.\n", + "\n", + "This book is primarily for people who have never programmed before and people who have some experience in another programming language.\n", + "If you have substantial experience in Python, you might find the first few chapters too slow.\n", + "\n", + "One of the challenges of learning to program is that you have to learn *two* languages: one is the programming language itself; the other is the vocabulary we use to talk about programs.\n", + "If you learn only the programming language, you are likely to have problems when you need to interpret an error message, read documentation, talk to another person, or use virtual assistants.\n", + "If you have done some programming, but you have not also learned this second language, I hope you find this book helpful." + ] + }, + { + "cell_type": "markdown", + "id": "b4dd57bc", + "metadata": {}, + "source": [ + "## Goals of the Book\n", + "\n", + "Writing this book, I tried to be careful with the vocabulary.\n", + "I define each term when it first appears.\n", + "And there is a glossary that the end of each chapter that reviews the terms that were introduced.\n", + "\n", + "I also tried to be concise.\n", + "The less mental effort it takes to read the book, the more capacity you will have for programming.\n", + "\n", + "But you can't learn to program just by reading a book -- you have to practice.\n", + "For that reason, this book includes exercises at the end of every chapter where you can practice what you have learned.\n", + "\n", + "If you read carefully and work on exercises consistently, you will make progress.\n", + "But I'll warn you now -- learning to program is not easy, and even for experienced programmers it can be frustrating.\n", + "As we go, I will suggest strategies to help you write correct programs and fix incorrect ones." + ] + }, + { + "cell_type": "markdown", + "id": "6516d914", + "metadata": {}, + "source": [ + "## Navigating the Book\n", + "\n", + "Each chapter in this book builds on the previous ones, so you should read them in order and take time to work on the exercises before you move on.\n", + "\n", + "The first six chapters introduce basic elements like arithmetic, conditionals, and loops.\n", + "They also introduce the most important concept in programming, functions, and a powerful way to use them, recursion.\n", + "\n", + "Chapters 7 and 8 introduce strings -- which can represent letter, words, and sentences -- and algorithms for working with them.\n", + "\n", + "Chapters 9 through 12 introduce Python's core data structures -- lists, dictionaries, and tuples -- which are powerful tools for writing efficient programs.\n", + "Chapter 12 presents algorithms for analyzing text and randomly generating new text.\n", + "Algorithms like these are at the core of large language models (LLMs), so this chapter will give you an idea of how tools like ChatGPT work.\n", + "\n", + "Chapter 13 is about ways to store data in long-term storage -- files and databases.\n", + "As an exercise, you can write a program that searches a file system and finds duplicate files.\n", + "\n", + "Chapters 14 through 17 introduce object-oriented programming (OOP), which is a way to organize programs and the data they work with.\n", + "Many Python libraries are written in object-oriented style, so these chapters will help you understand their design -- and define your own objects.\n", + "\n", + "The goal of this book is not to cover the entire Python language.\n", + "Rather, I focus on a subset of the language that provides the greatest capability with the fewest concepts.\n", + "Nevertheless, Python has a lot of features you can use to solve common problems efficiently.\n", + "Chapter 18 presents some of these features.\n", + "\n", + "Finally, Chapter 19 presents my parting thoughts and suggestions for continuing your programming journey." + ] + }, + { + "cell_type": "markdown", + "id": "23013838", + "metadata": {}, + "source": [ + "## What's new in the third edition?\n", + "\n", + "The biggest changes in this edition were driven by two new technologies -- Jupyter notebooks and virtual assistants.\n", + "\n", + "Each chapter of this book is a Jupyter notebook, which is a document that contains both ordinary text and code.\n", + "For me, that makes it easier to write the code, test it, and keep it consistent with the text.\n", + "For you, it means you can run the code, modify it, and work on the exercises, all in one place.\n", + "Instructions for working with the notebooks are in the first chapter.\n", + "\n", + "The other big change is that I've added advice for working with virtual assistants like ChatGPT and using them to accelerate your learning.\n", + "When the previous edition of this book was published in 2016, the predecessors of these tools were far less useful and most people were unaware of them. \n", + "Now they are a standard tool for software engineering, and I think they will be a transformational tool for learning to program -- and learning a lot of other things, too.\n", + "\n", + "The other changes in the book were motivated by my regrets about the second edition.\n", + "\n", + "The first is that I did not emphasize software testing.\n", + "That was already a regrettable omission in 2016, but with the advent of virtual assistants, automated testing has become even more important.\n", + "So this edition presents Python's most widely-used testing tools, `doctest` and `unittest`, and includes several exercises where you can practice working with them.\n", + "\n", + "My other regret is that the exercises in the second edition were uneven -- some were more interesting than others and some were too hard.\n", + "Moving to Jupyter notebooks helped me develop and test a more engaging and effective sequence of exercises.\n", + "\n", + "In this revision, the sequence of topics is almost the same, but I rearranged a few of the chapters and compressed two short chapters into one.\n", + "Also, I expanded the coverage of strings to include regular expressions.\n", + "\n", + "A few chapters use turtle graphics.\n", + "In previous editions, I used Python's `turtle` module, but unfortunately it doesn't work in Jupyter notebooks.\n", + "So I replaced it with a new turtle module that should be easier to use.\n", + "\n", + "Finally, I rewrote a substantial fraction of the text, clarifying places that needed it and cutting back in places where I was not as concise as I could be.\n", + "\n", + "I am very proud of this new edition -- I hope you like it!" + ] + }, + { + "cell_type": "markdown", + "id": "bfb779bb", + "metadata": {}, + "source": [ + "## Getting started\n", + "\n", + "For most programming languages, including Python, there are many tools you can use to write and run programs. \n", + "These tools are called integrated development environments (IDEs).\n", + "In general, there are two kinds of IDEs:\n", + "\n", + "* Some work with files that contain code, so they provide tools for editing and running these files.\n", + "\n", + "* Others work primarily with notebooks, which are documents that contain text and code.\n", + "\n", + "For beginners, I recommend starting with a notebook development environment like Jupyter.\n", + "\n", + "The notebooks for this book are available from an online repository at .\n", + "\n", + "There are two ways to use them:\n", + "\n", + "* You can download the notebooks and run them on your own computer. In that case, you have to install Python and Jupyter, which is not hard, but if you want to learn Python, it can be frustrating to spend a lot of time installing software.\n", + "\n", + "* An alternative is to run the notebooks on Colab, which is a Jupyter environment that runs in a web browser, so you don't have to install anything. Colab is operated by Google, and it is free to use.\n", + "\n", + "If you are just getting started, I strongly recommend you start with Colab." + ] + }, + { + "cell_type": "markdown", + "id": "2ebd2412", + "metadata": {}, + "source": [ + "## Resources for Teachers\n", + "\n", + "If you are teaching with this book, here are some resources you might find useful.\n", + "\n", + "* You can find notebooks with solutions to the exercises from , along with links to the additional resources below.\n", + "\n", + "* Quizzes for each chapter, and a summative quiz for the whole book, are available from [COMING SOON]\n", + "\n", + "* *Teaching and Learning with Jupyter* is an online book with suggestions for using Jupyter effectively in the classroom. You can read the book at \n", + "\n", + "* One of the best ways to use notebooks is live coding, where an instructor writes code and students follow along in their own notebooks. To learn about live coding -- and get other great advice about teaching programming -- I recommend the instructor training provided by The Carpentries, at " + ] + }, + { + "cell_type": "markdown", + "id": "28e7de55", + "metadata": {}, + "source": [ + "## Acknowledgments\n", + "\n", + "Many thanks to Jeff Elkner, who translated my Java book into Python,\n", + "which got this project started and introduced me to what has turned out\n", + "to be my favorite language.\n", + "Thanks also to Chris Meyers, who contributed several sections to *How to Think Like a Computer Scientist*.\n", + "\n", + "Thanks to the Free Software Foundation for developing the GNU Free Documentation License, which helped make my collaboration with Jeff and Chris possible, and thanks to the Creative Commons for the license I am using now.\n", + "\n", + "Thanks to the developers and maintainers of the Python language and the libraries I used, including the Turtle graphics module; the tools I used to develop the book, including Jupyter and JupyterBook; and the services I used, including ChatGPT, Copilot, Colab and GitHub.\n", + "\n", + "Thanks to the editors at Lulu who worked on *How to Think Like a Computer Scientist* and the editors at O'Reilly Media who worked on *Think Python*.\n", + "\n", + "Special thanks to the technical reviewers for the second edition, Melissa Lewis and Luciano Ramalho, and for the third edition, Sam Lau and Luciano Ramalho (again!).\n", + "I am also grateful to Luciano for developing the turtle graphics module I use in several chapters, called `jupyturtle`.\n", + "\n", + "Thanks to all the students who worked with earlier versions of this book and all the contributors who sent in corrections and suggestions.\n", + "More than 100 sharp-eyed and thoughtful readers have sent in suggestions and corrections over the past few years. Their contributions, and enthusiasm for this project, have been a huge help.\n", + "\n", + "If you have a suggestion or correction, please send email to `feedback@thinkpython.com`.\n", + "If you include at least part of the sentence the error appears in, that\n", + "makes it easy for me to search. Page and section numbers are fine, too,\n", + "but not quite as easy to work with. Thanks!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e31cebe", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { + "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 +} diff --git a/blank/chap01.ipynb b/blank/chap01.ipynb new file mode 100644 index 0000000..7b758f0 --- /dev/null +++ b/blank/chap01.ipynb @@ -0,0 +1,1205 @@ +{ + "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": "markdown", + "id": "a14edb7e", + "metadata": { + "tags": [] + }, + "source": [ + "# Welcome\n", + "\n", + "This is the Jupyter notebook for Chapter 1 of [*Think Python*, 3rd edition](https://greenteapress.com/wp/think-python-3rd-edition), by Allen B. Downey.\n", + "\n", + "If you are not familiar with Jupyter notebooks,\n", + "[click here for a short introduction](https://colab.research.google.com/github/AllenDowney/ThinkPython/blob/v3/chapters/jupyter_intro.ipynb).\n", + "\n", + "Then, if you are not already running this notebook on Colab, [click here to run this notebook on Colab](https://colab.research.google.com/github/AllenDowney/ThinkPython/blob/v3/chapters/chap01.ipynb)." + ] + }, + { + "cell_type": "markdown", + "id": "3b4a1f57", + "metadata": { + "tags": [] + }, + "source": [ + "The following cell downloads a file and runs some code that is used specifically for this book.\n", + "You don't have to understand this code yet, but you should run it before you do anything else in this notebook.\n", + "Remember that you can run the code by selecting the cell and pressing the play button (a triangle in a circle) or hold down `Shift` and press `Enter`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "213f9d96", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from os.path import basename, exists\n", + "\n", + "def download(url):\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/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "\n", + "import thinkpython" + ] + }, + { + "cell_type": "markdown", + "id": "333a6fc9", + "metadata": { + "tags": [] + }, + "source": [ + "# Programming as a way of thinking\n", + "\n", + "The first goal of this book is to teach you how to program in Python.\n", + "But learning to program means learning a new way to think, so the second goal of this book is to help you think like a computer scientist.\n", + "This way of thinking combines some of the best features of mathematics, engineering, and natural science.\n", + "Like mathematicians, computer scientists use formal languages to denote ideas -- specifically computations.\n", + "Like engineers, they design things, assembling components into systems and evaluating trade-offs among alternatives.\n", + "Like scientists, they observe the behavior of complex systems, form hypotheses, and test predictions.\n", + "\n", + "We will start with the most basic elements of programming and work our way up.\n", + "In this chapter, we'll see how Python represents numbers, letters, and words.\n", + "And you'll learn to perform arithmetic operations.\n", + "\n", + "You will also start to learn the vocabulary of programming, including terms like operator, expression, value, and type.\n", + "This vocabulary is important -- you will need it to understand the rest of the book, to communicate with other programmers, and to use and understand virtual assistants." + ] + }, + { + "cell_type": "markdown", + "id": "a371aea3", + "metadata": {}, + "source": [ + "## Arithmetic operators\n", + "\n", + "An **arithmetic operator** is a symbol that represents an arithmetic computation. For example, the plus sign, `+`, performs addition." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2568ec84", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fc0e7ce8", + "metadata": {}, + "source": [ + "The minus sign, `-`, is the operator that performs subtraction." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c4e75456", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "63e4e780", + "metadata": {}, + "source": [ + "The asterisk, `*`, performs multiplication." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "022a7b16", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a6192d13", + "metadata": {}, + "source": [ + "And the forward slash, `/`, performs division:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "05ae1098", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "641ad233", + "metadata": {}, + "source": [ + "Notice that the result of the division is `42.0` rather than `42`. That's because there are two types of numbers in Python: \n", + "\n", + "* **integers**, which represent numbers with no fractional or decimal part, and \n", + "\n", + "* **floating-point numbers**, which represent integers and numbers with a decimal point.\n", + "\n", + "If you add, subtract, or multiply two integers, the result is an integer.\n", + "But if you divide two integers, the result is a floating-point number.\n", + "Python provides another operator, `//`, that performs **integer division**.\n", + "The result of integer division is always an integer." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4df5bcaa", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b2a620ab", + "metadata": {}, + "source": [ + "Integer division is also called \"floor division\" because it always rounds down (toward the \"floor\"). " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ef08d549", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "41e2886a", + "metadata": {}, + "source": [ + "Finally, the operator `**` performs exponentiation; that is, it raises a\n", + "number to a power:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "df933e80", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b2502fb6", + "metadata": {}, + "source": [ + "In some other languages, the caret, `^`, is used for exponentiation, but in Python\n", + "it is a bitwise operator called XOR.\n", + "If you are not familiar with bitwise operators, the result might be unexpected:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "306b6b88", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "30078370", + "metadata": {}, + "source": [ + "I won't cover bitwise operators in this book, but you can read about\n", + "them at ." + ] + }, + { + "cell_type": "markdown", + "id": "0f5b7e97", + "metadata": {}, + "source": [ + "## Expressions\n", + "\n", + "A collection of operators and numbers is called an **expression**.\n", + "An expression can contain any number of operators and numbers.\n", + "For example, here's an expression that contains two operators." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6e68101d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8e95039c", + "metadata": {}, + "source": [ + "Notice that exponentiation happens before addition.\n", + "Python follows the order of operations you might have learned in a math class: exponentiation happens before multiplication and division, which happen before addition and subtraction.\n", + "\n", + "In the following example, multiplication happens before addition." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ffc25598", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "914a60d8", + "metadata": {}, + "source": [ + "If you want the addition to happen first, you can use parentheses." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "8dd1bd9a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "67ae0ae9", + "metadata": {}, + "source": [ + "Every expression has a **value**.\n", + "For example, the expression `6 * 7` has the value `42`." + ] + }, + { + "cell_type": "markdown", + "id": "caebaa51", + "metadata": {}, + "source": [ + "## Arithmetic functions\n", + "\n", + "In addition to the arithmetic operators, Python provides a few **functions** that work with numbers.\n", + "For example, the `round` function takes a floating-point number and rounds it off to the nearest integer." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1e3d5e01", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d1b220b9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f5738b4b", + "metadata": {}, + "source": [ + "The `abs` function computes the absolute value of a number.\n", + "For a positive number, the absolute value is the number itself." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "ff742476", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e518494a", + "metadata": {}, + "source": [ + "For a negative number, the absolute value is positive." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "9247c1a3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6969ce45", + "metadata": {}, + "source": [ + "When we use a function like this, we say we're **calling** the function.\n", + "An expression that calls a function is a **function call**.\n", + "\n", + "When you call a function, the parentheses are required.\n", + "If you leave them out, you get an error message." + ] + }, + { + "cell_type": "markdown", + "id": "5a73bfd5", + "metadata": { + "tags": [] + }, + "source": [ + "NOTE: The following cell uses `%%expect`, which is a Jupyter \"magic command\" that means we expect the code in this cell to produce an error. For more on this topic, see the\n", + "[Jupyter notebook introduction](https://colab.research.google.com/github/AllenDowney/ThinkPython/blob/v3/chapters/jupyter_intro.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "4674b7ca", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7d356f1b", + "metadata": {}, + "source": [ + "You can ignore the first line of this message; it doesn't contain any information we need to understand right now.\n", + "The second line is the code that contains the error, with a caret (`^`) beneath it to indicate where the error was discovered.\n", + "\n", + "The last line indicates that this is a **syntax error**, which means that there is something wrong with the structure of the expression.\n", + "In this example, the problem is that a function call requires parentheses.\n", + "\n", + "Let's see what happens if you leave out the parentheses *and* the value." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "7d3e8127", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "94478885", + "metadata": {}, + "source": [ + "A function name all by itself is a legal expression that has a value.\n", + "When it's displayed, the value indicates that `abs` is a function, and it includes some additional information I'll explain later." + ] + }, + { + "cell_type": "markdown", + "id": "31a85d17", + "metadata": {}, + "source": [ + "## Strings\n", + "\n", + "In addition to numbers, Python can also represent sequences of letters, which are called **strings** because the letters are strung together like beads on a necklace.\n", + "To write a string, we can put a sequence of letters inside straight quotation marks." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "bd8ae45f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d20050d8", + "metadata": {}, + "source": [ + "It is also legal to use double quotation marks." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "01d0055e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "76f5edb7", + "metadata": {}, + "source": [ + "Double quotes make it easy to write a string that contains an apostrophe, which is the same symbol as a straight quote." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0295acab", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d62d4b1c", + "metadata": {}, + "source": [ + "Strings can also contain spaces, punctuation, and digits." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "cf918917", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9ad47f7a", + "metadata": {}, + "source": [ + "The `+` operator works with strings; it joins two strings into a single string, which is called **concatenation**" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "aefe6af1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0ad969a3", + "metadata": {}, + "source": [ + "The `*` operator also works with strings; it makes multiple copies of a string and concatenates them." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "42e9e4e2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dfba16a5", + "metadata": {}, + "source": [ + "The other arithmetic operators don't work with strings.\n", + "\n", + "Python provides a function called `len` that computes the length of a string." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "a5e837db", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d91e00b3", + "metadata": {}, + "source": [ + "Notice that `len` counts the letters between the quotes, but not the quotes.\n", + "\n", + "When you create a string, be sure to use straight quotes.\n", + "The back quote, also known as a backtick, causes a syntax error." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "e3f65f19", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "40d893d1", + "metadata": {}, + "source": [ + "Smart quotes, also known as curly quotes, are also illegal." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "a705b980", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5471d4f8", + "metadata": {}, + "source": [ + "## Values and types\n", + "\n", + "So far we've seen three kinds of values:\n", + "\n", + "* `2` is an integer,\n", + "\n", + "* `42.0` is a floating-point number, and \n", + "\n", + "* `'Hello'` is a string.\n", + "\n", + "A kind of value is called a **type**.\n", + "Every value has a type -- or we sometimes say it \"belongs to\" a type.\n", + "\n", + "Python provides a function called `type` that tells you the type of any value.\n", + "The type of an integer is `int`." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "3df8e2c5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b137814c", + "metadata": {}, + "source": [ + "The type of a floating-point number is `float`." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "c4732c8d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "266dea4e", + "metadata": {}, + "source": [ + "And the type of a string is `str`." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "8f65ac45", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "76d216ed", + "metadata": {}, + "source": [ + "The types `int`, `float`, and `str` can be used as functions.\n", + "For example, `int` can take a floating-point number and convert it to an integer (always rounding down)." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "84b22f2f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dcd8d114", + "metadata": {}, + "source": [ + "And `float` can convert an integer to a floating-point value." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "9b66ee21", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "eda70b61", + "metadata": {}, + "source": [ + "Now, here's something that can be confusing.\n", + "What do you get if you put a sequence of digits in quotes?" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f64e107c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fdded653", + "metadata": {}, + "source": [ + "It looks like a number, but it is actually a string." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "609a8153", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2683ac35", + "metadata": {}, + "source": [ + "If you try to use it like a number, you might get an error." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "1cf21da4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "32c11cc4", + "metadata": {}, + "source": [ + "This example generates a `TypeError`, which means that the values in the expression, which are called **operands**, have the wrong type.\n", + "The error message indicates that the `/` operator does not support the types of these values, which are `str` and `int`.\n", + "\n", + "If you have a string that contains digits, you can use `int` to convert it to an integer." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "d45e6a60", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "86935d56", + "metadata": {}, + "source": [ + "If you have a string that contains digits and a decimal point, you can use `float` to convert it to a floating-point number." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "db30b719", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "03103ef4", + "metadata": {}, + "source": [ + "When you write a large integer, you might be tempted to use commas\n", + "between groups of digits, as in `1,000,000`.\n", + "This is a legal expression in Python, but the result is not an integer." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d72b6af1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3d24af71", + "metadata": {}, + "source": [ + "Python interprets `1,000,000` as a comma-separated sequence of integers.\n", + "We'll learn more about this kind of sequence later.\n", + "\n", + "You can use underscores to make large numbers easier to read." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "e19bf7e7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1761cbac", + "metadata": {}, + "source": [ + "## Formal and natural languages\n", + "\n", + "**Natural languages** are the languages people speak, like English, Spanish, and French. They were not designed by people; they evolved naturally.\n", + "\n", + "**Formal languages** are languages that are designed by people for specific applications. \n", + "For example, the notation that mathematicians use is a formal language that is particularly good at denoting relationships among numbers and symbols.\n", + "Similarly, programming languages are formal languages that have been designed to express computations." + ] + }, + { + "cell_type": "markdown", + "id": "1bf3d2dc", + "metadata": {}, + "source": [ + "Although formal and natural languages have some features in\n", + "common there are important differences:\n", + "\n", + "* Ambiguity: Natural languages are full of ambiguity, which people deal with by\n", + " using contextual clues and other information. Formal languages are\n", + " designed to be nearly or completely unambiguous, which means that\n", + " any program has exactly one meaning, regardless of context.\n", + "\n", + "* Redundancy: In order to make up for ambiguity and reduce misunderstandings,\n", + " natural languages use redundancy. As a result, they are\n", + " often verbose. Formal languages are less redundant and more concise.\n", + "\n", + "* Literalness: Natural languages are full of idiom and metaphor. Formal languages mean exactly what they say." + ] + }, + { + "cell_type": "markdown", + "id": "78a1cec8", + "metadata": {}, + "source": [ + "Because we all grow up speaking natural languages, it is sometimes hard to adjust to formal languages.\n", + "Formal languages are more dense than natural languages, so it takes longer to read them.\n", + "Also, the structure is important, so it is not always best to read from top to bottom, left to right.\n", + "Finally, the details matter. Small errors in spelling and\n", + "punctuation, which you can get away with in natural languages, can make\n", + "a big difference in a formal language." + ] + }, + { + "cell_type": "markdown", + "id": "4358fa9a", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "Programmers make mistakes. For whimsical reasons, programming errors are called **bugs** and the process of tracking them down is called **debugging**.\n", + "\n", + "Programming, and especially debugging, sometimes brings out strong emotions. If you are struggling with a difficult bug, you might feel angry, sad, or embarrassed.\n", + "\n", + "Preparing for these reactions might help you deal with them. One approach is to think of the computer as an employee with certain strengths, like speed and precision, and particular weaknesses, like lack of empathy and inability to grasp the big picture.\n", + "\n", + "Your job is to be a good manager: find ways to take advantage of the strengths and mitigate the weaknesses. And find ways to use your emotions to engage with the problem, without letting your reactions interfere with your ability to work effectively.\n", + "\n", + "Learning to debug can be frustrating, but it is a valuable skill that is useful for many activities beyond programming. At the end of each chapter there is a section, like this one, with my suggestions for debugging. I hope they help!" + ] + }, + { + "cell_type": "markdown", + "id": "33b8ad00", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**arithmetic operator:**\n", + "A symbol, like `+` and `*`, that denotes an arithmetic operation like addition or multiplication.\n", + "\n", + "**integer:**\n", + "A type that represents numbers with no fractional or decimal part.\n", + "\n", + "**floating-point:**\n", + "A type that represents integers and numbers with decimal parts.\n", + "\n", + "**integer division:**\n", + "An operator, `//`, that divides two numbers and rounds down to an integer.\n", + "\n", + "**expression:**\n", + "A combination of variables, values, and operators.\n", + "\n", + "**value:**\n", + "An integer, floating-point number, or string -- or one of other kinds of values we will see later.\n", + "\n", + "**function:**\n", + "A named sequence of statements that performs some useful operation.\n", + "Functions may or may not take arguments and may or may not produce a result.\n", + "\n", + "**function call:**\n", + "An expression -- or part of an expression -- that runs a function.\n", + "It consists of the function name followed by an argument list in parentheses.\n", + "\n", + "**syntax error:**\n", + "An error in a program that makes it impossible to parse -- and therefore impossible to run.\n", + "\n", + "**string:**\n", + " A type that represents sequences of characters.\n", + "\n", + "**concatenation:**\n", + "Joining two strings end-to-end.\n", + "\n", + "**type:**\n", + "A category of values.\n", + "The types we have seen so far are integers (type `int`), floating-point numbers (type ` float`), and strings (type `str`).\n", + "\n", + "**operand:**\n", + "One of the values on which an operator operates.\n", + "\n", + "**natural language:**\n", + "Any of the languages that people speak that evolved naturally.\n", + "\n", + "**formal language:**\n", + "Any of the languages that people have designed for specific purposes, such as representing mathematical ideas or computer programs.\n", + "All programming languages are formal languages.\n", + "\n", + "**bug:**\n", + "An error in a program.\n", + "\n", + "**debugging:**\n", + "The process of finding and correcting errors." + ] + }, + { + "cell_type": "markdown", + "id": "ed4ec01b", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "06d3e72c", + "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": "23adf208", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "As you work through this book, there are several ways you can use a virtual assistant or chatbot to help you learn.\n", + "\n", + "* If you want to learn more about a topic in the chapter, or anything is unclear, you can ask for an explanation.\n", + "\n", + "* If you are having a hard time with any of the exercises, you can ask for help.\n", + "\n", + "In each chapter, I'll suggest exercises you can do with a virtual assistant, but I encourage you to try things on your own and see what works for you." + ] + }, + { + "cell_type": "markdown", + "id": "ebf1a451", + "metadata": {}, + "source": [ + "Here are some topics you could ask a virtual assistant about:\n", + "\n", + "* Earlier I mentioned bitwise operators but I didn't explain why the value of `7 ^ 2` is 5. Try asking \"What are the bitwise operators in Python?\" or \"What is the value of `7 XOR 2`?\"\n", + "\n", + "* I also mentioned the order of operations. For more details, ask \"What is the order of operations in Python?\"\n", + "\n", + "* The `round` function, which we used to round a floating-point number to the nearest integer, can take a second argument. Try asking \"What are the arguments of the round function?\" or \"How do I round pi off to three decimal places?\"\n", + "\n", + "* There's one more arithmetic operator I didn't mention; try asking \"What is the modulus operator in Python?\"" + ] + }, + { + "cell_type": "markdown", + "id": "9be3e1c7", + "metadata": {}, + "source": [ + "Most virtual assistants know about Python, so they answer questions like this pretty reliably.\n", + "But remember that these tools make mistakes.\n", + "If you get code from a chatbot, test it!" + ] + }, + { + "cell_type": "markdown", + "id": "03c1ef93", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "You might wonder what `round` does if a number ends in `0.5`.\n", + "The answer is that it sometimes rounds up and sometimes rounds down.\n", + "Try these examples and see if you can figure out what rule it follows." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "5d358f37", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "12aa59a3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dd2f890e", + "metadata": {}, + "source": [ + "If you are curious, ask a virtual assistant, \"If a number ends in 0.5, does Python round up or down?\"" + ] + }, + { + "cell_type": "markdown", + "id": "2cd03bcb", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "When you learn about a new feature, you should try it out and make mistakes on purpose.\n", + "That way, you learn the error messages, and when you see them again, you will know what they mean.\n", + "It is better to make mistakes now and deliberately than later and accidentally.\n", + "\n", + "1. You can use a minus sign to make a negative number like `-2`. What happens if you put a plus sign before a number? What about `2++2`?\n", + "\n", + "2. What happens if you have two values with no operator between them, like `4 2`?\n", + "\n", + "3. If you call a function like `round(42.5)`, what happens if you leave out one or both parentheses?" + ] + }, + { + "cell_type": "markdown", + "id": "1fb0adfe", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Recall that every expression has a value, every value has a type, and we can use the `type` function to find the type of any value.\n", + "\n", + "What is the type of the value of the following expressions? Make your best guess for each one, and then use `type` to find out.\n", + "\n", + "* `765`\n", + "\n", + "* `2.718`\n", + "\n", + "* `'2 pi'`\n", + "\n", + "* `abs(-7)`\n", + "\n", + "* `abs(-7.0)`\n", + "\n", + "* `abs`\n", + "\n", + "* `int`\n", + "\n", + "* `type`" + ] + }, + { + "cell_type": "markdown", + "id": "23762eec", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "The following questions give you a chance to practice writing arithmetic expressions.\n", + "\n", + "1. How many seconds are there in 42 minutes 42 seconds?\n", + "\n", + "2. How many miles are there in 10 kilometers? Hint: there are 1.61 kilometers in a mile.\n", + "\n", + "3. If you run a 10 kilometer race in 42 minutes 42 seconds, what is your average pace in seconds per mile? \n", + " \n", + "4. What is your average pace in minutes and seconds per mile?\n", + "\n", + "5. What is your average speed in miles per hour?\n", + "\n", + "If you already know about variables, you can use them for this exercise.\n", + "If you don't, you can do the exercise without them -- and then we'll see them in the next chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "8fb50f30", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "5eceb4fb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "fee97d8d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "a998258c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "2e0fc7a9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "d25268d8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "523d9b0f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca083ccf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap02.ipynb b/blank/chap02.ipynb new file mode 100644 index 0000000..345e793 --- /dev/null +++ b/blank/chap02.ipynb @@ -0,0 +1,1144 @@ +{ + "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": "1a0a6ff4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from os.path import basename, exists\n", + "\n", + "def download(url):\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/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", + "\n", + "import thinkpython" + ] + }, + { + "cell_type": "markdown", + "id": "d0286422", + "metadata": {}, + "source": [ + "# Variables and Statements\n", + "\n", + "In the previous chapter, we used operators to write expressions that perform arithmetic computations.\n", + "\n", + "In this chapter, you'll learn about variables and statements, the `import` statement, and the `print` function.\n", + "And I'll introduce more of the vocabulary we use to talk about programs, including \"argument\" and \"module\".\n" + ] + }, + { + "cell_type": "markdown", + "id": "4ac44f0c", + "metadata": {}, + "source": [ + "## Variables\n", + "\n", + "A **variable** is a name that refers to a value.\n", + "To create a variable, we can write a **assignment statement** like this." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "59f6db42", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "52f187f1", + "metadata": {}, + "source": [ + "An assignment statement has three parts: the name of the variable on the left, the equals operator, `=`, and an expression on the right.\n", + "In this example, the expression is an integer.\n", + "In the following example, the expression is a floating-point number." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1301f6af", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3e27e65c", + "metadata": {}, + "source": [ + "And in the following example, the expression is a string." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f7adb732", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cb5916ea", + "metadata": {}, + "source": [ + "When you run an assignment statement, there is no output.\n", + "Python creates the variable and gives it a value, but the assignment statement has no visible effect.\n", + "However, after creating a variable, you can use it as an expression.\n", + "So we can display the value of `message` like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6bcc0a66", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e3fd81de", + "metadata": {}, + "source": [ + "You can also use a variable as part of an expression with arithmetic operators." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3f11f497", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6b2dafea", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "97396e7d", + "metadata": {}, + "source": [ + "And you can use a variable when you call a function." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "72c45ac5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "6bf81c52", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "397d9da3", + "metadata": {}, + "source": [ + "## State diagrams\n", + "\n", + "A common way to represent variables on paper is to write the name with\n", + "an arrow pointing to its value. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "2c25e84e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import math\n", + "\n", + "from diagram import make_binding, Frame\n", + "\n", + "binding = make_binding(\"message\", 'And now for something completely different')\n", + "binding2 = make_binding(\"n\", 17)\n", + "binding3 = make_binding(\"pi\", 3.141592653589793)\n", + "\n", + "frame = Frame([binding2, binding3, binding])" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "5b27a635", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from diagram import diagram, adjust\n", + "\n", + "\n", + "width, height, x, y = [3.62, 1.01, 0.6, 0.76]\n", + "ax = diagram(width, height)\n", + "bbox = frame.draw(ax, x, y, dy=-0.25)\n", + "# adjust(x, y, bbox)" + ] + }, + { + "cell_type": "markdown", + "id": "6f40da93", + "metadata": {}, + "source": [ + "This kind of figure is called a **state diagram** because it shows what state each of the variables is in (think of it as the variable's state of mind).\n", + "We'll use state diagrams throughout the book to represent a model of how Python stores variables and their values." + ] + }, + { + "cell_type": "markdown", + "id": "ba252c85", + "metadata": {}, + "source": [ + "## Variable names\n", + "\n", + "Variable names can be as long as you like. They can contain both letters and numbers, but they can't begin with a number. \n", + "It is legal to use uppercase letters, but it is conventional to use only lower case for\n", + "variable names.\n", + "\n", + "The only punctuation that can appear in a variable name is the underscore character, `_`. It is often used in names with multiple words, such as `your_name` or `airspeed_of_unladen_swallow`.\n", + "\n", + "If you give a variable an illegal name, you get a syntax error.\n", + "The name `million!` is illegal because it contains punctuation." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ac2620ef", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a1cefe3e", + "metadata": {}, + "source": [ + "`76trombones` is illegal because it starts with a number." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1a8b8382", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "94aa7e60", + "metadata": {}, + "source": [ + "`class` is also illegal, but it might not be obvious why." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b6938851", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "784cfb5c", + "metadata": {}, + "source": [ + "It turns out that `class` is a **keyword**, which is a special word used to specify the structure of a program.\n", + "Keywords can't be used as variable names.\n", + "\n", + "Here's a complete list of Python's keywords:" + ] + }, + { + "cell_type": "markdown", + "id": "127c07e8", + "metadata": {}, + "source": [ + "```\n", + "False await else import pass\n", + "None break except in raise\n", + "True class finally is return\n", + "and continue for lambda try\n", + "as def from nonlocal while\n", + "assert del global not with\n", + "async elif if or yield\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "4a8f4b3e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6f14d301", + "metadata": {}, + "source": [ + "You don't have to memorize this list. In most development environments,\n", + "keywords are displayed in a different color; if you try to use one as a\n", + "variable name, you'll know." + ] + }, + { + "cell_type": "markdown", + "id": "c954a3b0", + "metadata": {}, + "source": [ + "## The import statement\n", + "\n", + "In order to use some Python features, you have to **import** them.\n", + "For example, the following statement imports the `math` module." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "98c268e9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ea4f75ec", + "metadata": {}, + "source": [ + "A **module** is a collection of variables and functions.\n", + "The math module provides a variable called `pi` that contains the value of the mathematical constant denoted $\\pi$.\n", + "We can display its value like this." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "47bc17c9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c96106e4", + "metadata": {}, + "source": [ + "To use a variable in a module, you have to use the **dot operator** (`.`) between the name of the module and the name of the variable.\n", + "\n", + "The math module also contains functions.\n", + "For example, `sqrt` computes square roots." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "fd1cec63", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "185e94a3", + "metadata": {}, + "source": [ + "And `pow` raises one number to the power of a second number." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "87316ddd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5df25a9a", + "metadata": {}, + "source": [ + "At this point we've seen two ways to raise a number to a power: we can use the `math.pow` function or the exponentiation operator, `**`.\n", + "Either one is fine, but the operator is used more often than the function." + ] + }, + { + "cell_type": "markdown", + "id": "6538f22b", + "metadata": {}, + "source": [ + "## Expressions and statements\n", + "\n", + "So far, we've seen a few kinds of expressions.\n", + "An expression can be a single value, like an integer, floating-point number, or string.\n", + "It can also be a collection of values and operators.\n", + "And it can include variable names and function calls.\n", + "Here's an expression that includes several of these elements." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "7f0b92df", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "000dd2ba", + "metadata": {}, + "source": [ + "We have also seen a few kind of statements.\n", + "A **statement** is a unit of code that has an effect, but no value.\n", + "For example, an assignment statement creates a variable and gives it a value, but the statement itself has no value." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "b882c340", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cff0414b", + "metadata": {}, + "source": [ + "Similarly, an import statement has an effect -- it imports a module so we can use the variables and functions it contains -- but it has no visible effect." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "299817d8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2aeb1000", + "metadata": {}, + "source": [ + "Computing the value of an expression is called **evaluation**.\n", + "Running a statement is called **execution**." + ] + }, + { + "cell_type": "markdown", + "id": "f61601e4", + "metadata": {}, + "source": [ + "## The print function\n", + "\n", + "When you evaluate an expression, the result is displayed." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "805977c6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "efacf0fa", + "metadata": {}, + "source": [ + "But if you evaluate more than one expression, only the value of the last one is displayed." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "962e08ab", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cf2b991d", + "metadata": {}, + "source": [ + "To display more than one value, you can use the `print` function." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "a797e44d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "29af1f89", + "metadata": {}, + "source": [ + "It also works with floating-point numbers and strings." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "73428520", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8b4d7f4a", + "metadata": {}, + "source": [ + "You can also use a sequence of expressions separated by commas." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "9ad5bddd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "af447ec4", + "metadata": {}, + "source": [ + "Notice that the `print` function puts a space between the values." + ] + }, + { + "cell_type": "markdown", + "id": "7c73a2fa", + "metadata": {}, + "source": [ + "## Arguments\n", + "\n", + "When you call a function, the expression in parenthesis is called an **argument**.\n", + "Normally I would explain why, but in this case the technical meaning of a term has almost nothing to do with the common meaning of the word, so I won't even try.\n", + "\n", + "Some of the functions we've seen so far take only one argument, like `int`." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "060c60cf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c4ad4f2c", + "metadata": {}, + "source": [ + "Some take two, like `math.pow`." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "2875d9e0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "17293749", + "metadata": {}, + "source": [ + "Some can take additional arguments that are optional. \n", + "For example, `int` can take a second argument that specifies the base of the number." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "43b9cf38", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c95589a1", + "metadata": {}, + "source": [ + "The sequence of digits `101` in base 2 represents the number 5 in base 10.\n", + "\n", + "`round` also takes an optional second argument, which is the number of decimal places to round off to." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e8a21d05", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "21e4a448", + "metadata": {}, + "source": [ + "Some functions can take any number of arguments, like `print`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "724128f4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "667cff14", + "metadata": {}, + "source": [ + "If you call a function and provide too many arguments, that's a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "69295e52", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5103368e", + "metadata": {}, + "source": [ + "If you provide too few arguments, that's also a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "edec7064", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5333c416", + "metadata": {}, + "source": [ + "And if you provide an argument with a type the function can't handle, that's a `TypeError`, too." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "f86b2896", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "548828af", + "metadata": {}, + "source": [ + "This kind of checking can be annoying when you are getting started, but it helps you detect and correct errors." + ] + }, + { + "cell_type": "markdown", + "id": "be2b6a9b", + "metadata": {}, + "source": [ + "## Comments\n", + "\n", + "As programs get bigger and more complicated, they get more difficult to read.\n", + "Formal languages are dense, and it is often difficult to look at a piece of code and figure out what it is doing and why.\n", + "\n", + "For this reason, it is a good idea to add notes to your programs to explain in natural language what the program is doing. \n", + "These notes are called **comments**, and they start with the `#` symbol." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "607893a6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "519c83a9", + "metadata": {}, + "source": [ + "In this case, the comment appears on a line by itself. You can also put\n", + "comments at the end of a line:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "615a11e7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "87c8d10c", + "metadata": {}, + "source": [ + "Everything from the `#` to the end of the line is ignored---it has no\n", + "effect on the execution of the program.\n", + "\n", + "Comments are most useful when they document non-obvious features of the code.\n", + "It is reasonable to assume that the reader can figure out *what* the code does; it is more useful to explain *why*.\n", + "\n", + "This comment is redundant with the code and useless:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "cc7fe2e6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "eb83b14a", + "metadata": {}, + "source": [ + "This comment contains useful information that is not in the code:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "7c93a00d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6cd60d4f", + "metadata": {}, + "source": [ + "Good variable names can reduce the need for comments, but long names can\n", + "make complex expressions hard to read, so there is a tradeoff." + ] + }, + { + "cell_type": "markdown", + "id": "7d61e416", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "Three kinds of errors can occur in a program: syntax errors, runtime errors, and semantic errors.\n", + "It is useful to distinguish between them in order to track them down more quickly.\n", + "\n", + "* **Syntax error**: \"Syntax\" refers to the structure of a program and the rules about that structure. If there is a syntax error anywhere in your program, Python does not run the program. It displays an error message immediately.\n", + "\n", + "* **Runtime error**: If there are no syntax errors in your program, it can start running. But if something goes wrong, Python displays an error message and stops. This type of error is called a runtime error. It is also called an **exception** because it indicates that something exceptional has happened.\n", + "\n", + "* **Semantic error**: The third type of error is \"semantic\", which means related to meaning. If there is a semantic error in your program, it runs without generating error messages, but it does not do what you intended. Identifying semantic errors can be tricky because it requires you to work backward by looking at the output of the program and trying to figure out what it is doing." + ] + }, + { + "cell_type": "markdown", + "id": "6cd52721", + "metadata": {}, + "source": [ + "As we've seen, an illegal variable name is a syntax error." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "86f07f6e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b8971d33", + "metadata": {}, + "source": [ + "If you use an operator with a type it doesn't support, that's a runtime error. " + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "682395ea", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e51fa6e2", + "metadata": {}, + "source": [ + "Finally, here's an example of a semantic error.\n", + "Suppose we want to compute the average of `1` and `3`, but we forget about the order of operations and write this:" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "2ff25bda", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0828afc0", + "metadata": {}, + "source": [ + "When this expression is evaluated, it does not produce an error message, so there is no syntax error or runtime error.\n", + "But the result is not the average of `1` and `3`, so the program is not correct.\n", + "This is a semantic error because the program runs but it doesn't do what's intended." + ] + }, + { + "cell_type": "markdown", + "id": "07396f3d", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**variable:**\n", + "A name that refers to a value.\n", + "\n", + "**assignment statement:**\n", + "A statement that assigns a value to a variable.\n", + "\n", + "**state diagram:**\n", + "A graphical representation of a set of variables and the values they refer to.\n", + "\n", + "**keyword:**\n", + "A special word used to specify the structure of a program.\n", + "\n", + "**import statement:**\n", + "A statement that reads a module file so we can use the variables and functions it contains.\n", + "\n", + "**module:**\n", + "A file that contains Python code, including function definitions and sometimes other statements.\n", + "\n", + "**dot operator:**\n", + "The operator, `.`, used to access a function in another module by specifying the module name followed by a dot and the function name.\n", + "\n", + "**evaluate:**\n", + "Perform the operations in an expression in order to compute a value.\n", + "\n", + "**statement:**\n", + "One or more lines of code that represent a command or action.\n", + "\n", + "**execute:**\n", + "Run a statement and do what it says.\n", + "\n", + "**argument:**\n", + "A value provided to a function when the function is called.\n", + "\n", + "**comment:**\n", + "Text included in a program that provides information about the program but has no effect on its execution.\n", + "\n", + "**runtime error:**\n", + "An error that causes a program to display an error message and exit.\n", + "\n", + "**exception:**\n", + "An error that is detected while the program is running.\n", + "\n", + "**semantic error:**\n", + "An error that causes a program to do the wrong thing, but not to display an error message." + ] + }, + { + "cell_type": "markdown", + "id": "70ee273d", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "c9e6cab4", + "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": "7256a9b2", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "Again, I encourage you to use a virtual assistant to learn more about any of the topics in this chapter.\n", + "\n", + "If you are curious about any of keywords I listed, you could ask \"Why is class a keyword?\" or \"Why can't variable names be keywords?\"\n", + "\n", + "You might have noticed that `int`, `float`, and `str` are not Python keywords.\n", + "They are variables that represent types, and they can be used as functions.\n", + "So it is *legal* to have a variable or function with one of those names, but it is strongly discouraged. Ask an assistant \"Why is it bad to use int, float, and str as variable names?\"\n", + "\n", + "Also ask, \"What are the built-in functions in Python?\"\n", + "If you are curious about any of them, ask for more information.\n", + "\n", + "In this chapter we imported the `math` module and used some of the variable and functions it provides. Ask an assistant, \"What variables and functions are in the math module?\" and \"Other than math, what modules are considered core Python?\"" + ] + }, + { + "cell_type": "markdown", + "id": "f92afde0", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Repeating my advice from the previous chapter, whenever you learn a new feature, you should make errors on purpose to see what goes wrong.\n", + "\n", + "- We've seen that `n = 17` is legal. What about `17 = n`?\n", + "\n", + "- How about `x = y = 1`?\n", + "\n", + "- In some languages every statement ends with a semi-colon (`;`). What\n", + " happens if you put a semi-colon at the end of a Python statement?\n", + "\n", + "- What if you put a period at the end of a statement?\n", + "\n", + "- What happens if you spell the name of a module wrong and try to import `maath`?" + ] + }, + { + "cell_type": "markdown", + "id": "9d562609", + "metadata": {}, + "source": [ + "### Exercise\n", + "Practice using the Python interpreter as a calculator:\n", + "\n", + "**Part 1.** The volume of a sphere with radius $r$ is $\\frac{4}{3} \\pi r^3$.\n", + "What is the volume of a sphere with radius 5? Start with a variable named `radius` and then assign the result to a variable named `volume`. Display the result. Add comments to indicate that `radius` is in centimeters and `volume` in cubic centimeters." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "18de7d96", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6449b12b", + "metadata": {}, + "source": [ + "**Part 2.** A rule of trigonometry says that for any value of $x$, $(\\cos x)^2 + (\\sin x)^2 = 1$. Let's see if it's true for a specific value of $x$ like 42.\n", + "\n", + "Create a variable named `x` with this value.\n", + "Then use `math.cos` and `math.sin` to compute the sine and cosine of $x$, and the sum of their squared.\n", + "\n", + "The result should be close to 1. It might not be exactly 1 because floating-point arithmetic is not exact---it is only approximately correct." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "de812cff", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4986801f", + "metadata": {}, + "source": [ + "**Part 3.** In addition to `pi`, the other variable defined in the `math` module is `e`, which represents the base of the natural logarithm, written in math notation as $e$. If you are not familiar with this value, ask a virtual assistant \"What is `math.e`?\" Now let's compute $e^2$ three ways:\n", + "\n", + "* Use `math.e` and the exponentiation operator (`**`).\n", + "\n", + "* Use `math.pow` to raise `math.e` to the power `2`.\n", + "\n", + "* Use `math.exp`, which takes as an argument a value, $x$, and computes $e^x$.\n", + "\n", + "You might notice that the last result is slightly different from the other two.\n", + "See if you can find out which is correct." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "b4ada618", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "4424940f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "50e8393a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91e5a869", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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" + }, + "vscode": { + "interpreter": { + "hash": "357b915890fbc73e00b3ee3cc7035b34e6189554c2854644fe780ff20c2fdfc0" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/blank/chap03.ipynb b/blank/chap03.ipynb new file mode 100644 index 0000000..a2b6766 --- /dev/null +++ b/blank/chap03.ipynb @@ -0,0 +1,998 @@ +{ + "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": "103cbe3c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from os.path import basename, exists\n", + "\n", + "def download(url):\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/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", + "\n", + "import thinkpython" + ] + }, + { + "cell_type": "markdown", + "id": "6bd858a8", + "metadata": {}, + "source": [ + "# Functions\n", + "\n", + "In the previous chapter we used several functions provided by Python, like `int` and `float`, and a few provided by the `math` module, like `sqrt` and `pow`.\n", + "In this chapter, you will learn how to create your own functions and run them.\n", + "And we'll see how one function can call another.\n", + "As examples, we'll display lyrics from Monty Python songs.\n", + "These silly examples demonstrate an important feature -- the ability to write your own functions is the foundation of programming.\n", + "\n", + "This chapter also introduces a new statement, the `for` loop, which is used to repeat a computation." + ] + }, + { + "cell_type": "markdown", + "id": "b4ea99c5", + "metadata": {}, + "source": [ + "## Defining new functions\n", + "\n", + "A **function definition** specifies the name of a new function and the sequence of statements that run when the function is called. Here's an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d28f5c1a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0174fc41", + "metadata": {}, + "source": [ + "`def` is a keyword that indicates that this is a function definition.\n", + "The name of the function is `print_lyrics`.\n", + "Anything that's a legal variable name is also a legal function name.\n", + "\n", + "The empty parentheses after the name indicate that this function doesn't take any arguments.\n", + "\n", + "The first line of the function definition is called the **header** -- the rest is called the **body**.\n", + "The header has to end with a colon and the body has to be indented. By convention, indentation is always four spaces. \n", + "The body of this function is two print statements; in general, the body of a function can contain any number of statements of any kind.\n", + "\n", + "Defining a function creates a **function object**, which we can display like this." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2850a402", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "12bd0879", + "metadata": {}, + "source": [ + "The output indicates that `print_lyrics` is a function that takes no arguments.\n", + "`__main__` is the name of the module that contains `print_lyrics`.\n", + "\n", + "Now that we've defined a function, we can call it the same way we call built-in functions." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9a048657", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8f0fc45d", + "metadata": {}, + "source": [ + "When the function runs, it executes the statements in the body, which display the first two lines of \"The Lumberjack Song\"." + ] + }, + { + "cell_type": "markdown", + "id": "6d35193e", + "metadata": {}, + "source": [ + "## Parameters\n", + "\n", + "Some of the functions we have seen require arguments; for example, when you call `abs` you pass a number as an argument.\n", + "Some functions take more than one argument; for example, `math.pow` takes two, the base and the exponent.\n", + "\n", + "Here is a definition for a function that takes an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e5d00488", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1716e3dc", + "metadata": {}, + "source": [ + "The variable name in parentheses is a **parameter**.\n", + "When the function is called, the value of the argument is assigned to the parameter.\n", + "For example, we can call `print_twice` like this." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a3ad5f46", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f02be6d2", + "metadata": {}, + "source": [ + "Running this function has the same effect as assigning the argument to the parameter and then executing the body of the function, like this." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "042dfec1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ea8b8b6e", + "metadata": {}, + "source": [ + "You can also use a variable as an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8f078ad0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5c1884ad", + "metadata": {}, + "source": [ + "In this example, the value of `line` gets assigned to the parameter `string`." + ] + }, + { + "cell_type": "markdown", + "id": "a3e5a790", + "metadata": {}, + "source": [ + "## Calling functions\n", + "\n", + "Once you have defined a function, you can use it inside another function.\n", + "To demonstrate, we'll write functions that print the lyrics of \"The Spam Song\" ().\n", + "\n", + "> Spam, Spam, Spam, Spam, \n", + "> Spam, Spam, Spam, Spam, \n", + "> Spam, Spam, \n", + "> (Lovely Spam, Wonderful Spam!) \n", + "> Spam, Spam,\n", + "\n", + "We'll start with the following function, which takes two parameters.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e86bb32c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bdd4daa4", + "metadata": {}, + "source": [ + "We can use this function to print the first line of the song, like this." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "ec117999", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c6f81e09", + "metadata": {}, + "source": [ + "To display the first two lines, we can define a new function that uses `repeat`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3731ffd8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8058ffe4", + "metadata": {}, + "source": [ + "And then call it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "6792e63b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "07ca432a", + "metadata": {}, + "source": [ + "To display the last three lines, we can define another function, which also uses `repeat`." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "2dcb020a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9ff8c60e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d6456a19", + "metadata": {}, + "source": [ + "Finally, we can bring it all together with one function that prints the whole verse." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "78bf3a7b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "ba5da431", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d088fe68", + "metadata": {}, + "source": [ + "When we run `print_verse`, it calls `first_two_lines`, which calls `repeat`, which calls `print`.\n", + "That's a lot of functions.\n", + "\n", + "Of course, we could have done the same thing with fewer functions, but the point of this example is to show how functions can work together." + ] + }, + { + "cell_type": "markdown", + "id": "c3b16e3f", + "metadata": {}, + "source": [ + "## Repetition\n", + "\n", + "If we want to display more than one verse, we can use a `for` statement.\n", + "Here's a simple example." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "29b7eff3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bf320549", + "metadata": {}, + "source": [ + "The first line is a header that ends with a colon.\n", + "The second line is the body, which has to be indented.\n", + "\n", + "The header starts with the keyword `for`, a new variable named `i`, and another keyword, `in`. \n", + "It uses the `range` function to create a sequence of two values, which are `0` and `1`.\n", + "In Python, when we start counting, we usually start from `0`.\n", + "\n", + "When the `for` statement runs, it assigns the first value from `range` to `i` and then runs the `print` function in the body, which displays `0`.\n", + "\n", + "When it gets to the end of the body, it loops back around to the header, which is why this statement is called a **loop**.\n", + "The second time through the loop, it assigns the next value from `range` to `i`, and displays it.\n", + "Then, because that's the last value from `range`, the loop ends.\n", + "\n", + "Here's how we can use a `for` loop to print two verses of the song." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "038ad592", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "88a46733", + "metadata": {}, + "source": [ + "You can put a `for` loop inside a function.\n", + "For example, `print_n_verses` takes a parameter named `n`, which has to be an integer, and displays the given number of verses. " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8887637a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ad8060fe", + "metadata": {}, + "source": [ + "In this example, we don't use `i` in the body of the loop, but there has to be a variable name in the header anyway." + ] + }, + { + "cell_type": "markdown", + "id": "b320ec90", + "metadata": {}, + "source": [ + "## Variables and parameters are local\n", + "\n", + "When you create a variable inside a function, it is **local**, which\n", + "means that it only exists inside the function.\n", + "For example, the following function takes two arguments, concatenates them, and prints the result twice." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "0db8408e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3a35a6d0", + "metadata": {}, + "source": [ + "Here's an example that uses it:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1c556e48", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4ab4e008", + "metadata": {}, + "source": [ + "When `cat_twice` runs, it creates a local variable named `cat`, which is destroyed when the function ends.\n", + "If we try to display it, we get a `NameError`:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "73f03eea", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3ae36c29", + "metadata": {}, + "source": [ + "Outside of the function, `cat` is not defined. \n", + "\n", + "Parameters are also local.\n", + "For example, outside `cat_twice`, there is no such thing as `part1` or `part2`." + ] + }, + { + "cell_type": "markdown", + "id": "eabac8a6", + "metadata": {}, + "source": [ + "## Stack diagrams\n", + "\n", + "To keep track of which variables can be used where, it is sometimes useful to draw a **stack diagram**. \n", + "Like state diagrams, stack diagrams show the value of each variable, but they also show the function each variable belongs to.\n", + "\n", + "Each function is represented by a **frame**.\n", + "A frame is a box with the name of a function on the outside and the parameters and local variables of the function on the inside.\n", + "\n", + "Here's the stack diagram for the previous example." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "83df4e32", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from diagram import make_frame, Stack\n", + "\n", + "d1 = dict(line1=line1, line2=line2)\n", + "frame1 = make_frame(d1, name='__main__', dy=-0.3, loc='left')\n", + "\n", + "d2 = dict(part1=line1, part2=line2, cat=line1+line2)\n", + "frame2 = make_frame(d2, name='cat_twice', dy=-0.3, \n", + " offsetx=0.03, loc='left')\n", + "\n", + "d3 = dict(string=line1+line2)\n", + "frame3 = make_frame(d3, name='print_twice', \n", + " offsetx=0.04, offsety=-0.3, loc='left')\n", + "\n", + "d4 = {\"?\": line1+line2}\n", + "frame4 = make_frame(d4, name='print', \n", + " offsetx=-0.22, offsety=0, loc='left')\n", + "\n", + "stack = Stack([frame1, frame2, frame3, frame4], dy=-0.8)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "bcd5e1df", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from diagram import diagram, adjust\n", + "\n", + "\n", + "width, height, x, y = [3.77, 2.9, 1.1, 2.65]\n", + "ax = diagram(width, height)\n", + "bbox = stack.draw(ax, x, y)\n", + "# adjust(x, y, bbox)" + ] + }, + { + "cell_type": "markdown", + "id": "854fee12", + "metadata": {}, + "source": [ + "The frames are arranged in a stack that indicates which function called\n", + "which, and so on. Reading from the bottom, `print` was called by `print_twice`, which was called by `cat_twice`, which was called by `__main__` -- which is a special name for the topmost frame.\n", + "When you create a variable outside of any function, it belongs to `__main__`.\n", + "\n", + "In the frame for `print`, the question mark indicates that we don't know the name of the parameter.\n", + "If you are curious, ask a virtual assistant, \"What are the parameters of the Python print function?\"" + ] + }, + { + "cell_type": "markdown", + "id": "5690cfc0", + "metadata": {}, + "source": [ + "## Tracebacks\n", + "\n", + "When a runtime error occurs in a function, Python displays the name of the function that was running, the name of the function that called it, and so on, up the stack.\n", + "To see an example, I'll define a version of `print_twice` that contains an error -- it tries to print `cat`, which is a local variable in another function." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "886519cf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d7c0713b", + "metadata": {}, + "source": [ + "Now here's what happens when we run `cat_twice`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "1fe8ee82", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs, including a traceback.\n", + "\n", + "%xmode Verbose" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "d9082f88", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2f4defcf", + "metadata": {}, + "source": [ + "The error message includes a **traceback**, which shows the function that was running when the error occurred, the function that called it, and so on.\n", + "In this example, it shows that `cat_twice` called `print_twice`, and the error occurred in a `print_twice`.\n", + "\n", + "The order of the functions in the traceback is the same as the order of the frames in the stack diagram.\n", + "The function that was running is at the bottom." + ] + }, + { + "cell_type": "markdown", + "id": "374b4696", + "metadata": {}, + "source": [ + "## Why functions?\n", + "\n", + "It may not be clear yet why it is worth the trouble to divide a program into\n", + "functions.\n", + "There are several reasons:\n", + "\n", + "- Creating a new function gives you an opportunity to name a group of\n", + " statements, which makes your program easier to read and debug.\n", + "\n", + "- Functions can make a program smaller by eliminating repetitive code.\n", + " Later, if you make a change, you only have to make it in one place.\n", + "\n", + "- Dividing a long program into functions allows you to debug the parts\n", + " one at a time and then assemble them into a working whole.\n", + "\n", + "- Well-designed functions are often useful for many programs. Once you\n", + " write and debug one, you can reuse it." + ] + }, + { + "cell_type": "markdown", + "id": "c6dd486e", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "Debugging can be frustrating, but it is also challenging, interesting, and sometimes even fun.\n", + "And it is one of the most important skills you can learn.\n", + "\n", + "In some ways debugging is like detective work.\n", + "You are given clues and you have to infer the events that led to the\n", + "results you see.\n", + "\n", + "Debugging is also like experimental science.\n", + "Once you have an idea about what is going wrong, you modify your program and try again.\n", + "If your hypothesis was correct, you can predict the result of the modification, and you take a step closer to a working program.\n", + "If your hypothesis was wrong, you have to come up with a new one.\n", + "\n", + "For some people, programming and debugging are the same thing; that is, programming is the process of gradually debugging a program until it does what you want.\n", + "The idea is that you should start with a working program and make small modifications, debugging them as you go.\n", + "\n", + "If you find yourself spending a lot of time debugging, that is often a sign that you are writing too much code before you start tests.\n", + "If you take smaller steps, you might find that you can move faster." + ] + }, + { + "cell_type": "markdown", + "id": "d4e95e63", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**function definition:**\n", + "A statement that creates a function.\n", + "\n", + "**header:**\n", + " The first line of a function definition.\n", + "\n", + "**body:**\n", + " The sequence of statements inside a function definition.\n", + "\n", + "**function object:**\n", + "A value created by a function definition.\n", + "The name of the function is a variable that refers to a function object.\n", + "\n", + "**parameter:**\n", + " A name used inside a function to refer to the value passed as an argument.\n", + "\n", + "**loop:**\n", + " A statement that runs one or more statements, often repeatedly.\n", + "\n", + "**local variable:**\n", + "A variable defined inside a function, and which can only be accessed inside the function.\n", + "\n", + "**stack diagram:**\n", + "A graphical representation of a stack of functions, their variables, and the values they refer to.\n", + "\n", + "**frame:**\n", + " A box in a stack diagram that represents a function call.\n", + " It contains the local variables and parameters of the function.\n", + "\n", + "**traceback:**\n", + " A list of the functions that are executing, printed when an exception occurs." + ] + }, + { + "cell_type": "markdown", + "id": "eca485f2", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "3f77b428", + "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": "82951027", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "The statements in a function or a `for` loop are indented by four spaces, by convention.\n", + "But not everyone agrees with that convention.\n", + "If you are curious about the history of this great debate, ask a virtual assistant to \"tell me about spaces and tabs in Python\".\n", + "\n", + "Virtual assistant are pretty good at writing small functions.\n", + "\n", + "1. Ask your favorite VA to \"Write a function called repeat that takes a string and an integer and prints the string the given number of times.\" \n", + "\n", + "2. If the result uses a `for` loop, you could ask, \"Can you do it without a for loop?\"\n", + "\n", + "3. Pick any other function in this chapter and ask a VA to write it. The challenge is to describe the function precisely enough to get what you want. Use the vocabulary you have learned so far in this book.\n", + "\n", + "Virtual assistants are also pretty good at debugging functions.\n", + "\n", + "1. Ask a VA what's wrong with this version of `print_twice`.\n", + "\n", + " ```\n", + " def print_twice(string):\n", + " print(cat)\n", + " print(cat)\n", + " ```\n", + " \n", + "And if you get stuck on any of the exercises below, consider asking a VA for help." + ] + }, + { + "cell_type": "markdown", + "id": "b7157b09", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function named `print_right` that takes a string named `text` as a parameter and prints the string with enough leading spaces that the last letter of the string is in the 40th column of the display." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "a6004271", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "428fbee5", + "metadata": {}, + "source": [ + "Hint: Use the `len` function, the string concatenation operator (`+`) and the string repetition operator (`*`).\n", + "\n", + "Here's an example that shows how it should work." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "f142ce6a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "print_right(\"Monty\")\n", + "print_right(\"Python's\")\n", + "print_right(\"Flying Circus\")" + ] + }, + { + "cell_type": "markdown", + "id": "b47467fa", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `triangle` that takes a string and an integer and draws a pyramid with the given height, made up using copies of the string. Here's an example of a pyramid with `5` levels, using the string `'L'`." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "7aa95014", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "b8146a0d", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "triangle('L', 5)" + ] + }, + { + "cell_type": "markdown", + "id": "4a28f635", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `rectangle` that takes a string and two integers and draws a rectangle with the given width and height, made up using copies of the string. Here's an example of a rectangle with width `5` and height `4`, made up of the string `'H'`." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "bcedab79", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "73b0c0f6", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "rectangle('H', 5, 4)" + ] + }, + { + "cell_type": "markdown", + "id": "44a5de6f", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "The song \"99 Bottles of Beer\" starts with this verse:\n", + "\n", + "> 99 bottles of beer on the wall \n", + "> 99 bottles of beer \n", + "> Take one down, pass it around \n", + "> 98 bottles of beer on the wall \n", + "\n", + "Then the second verse is the same, except that it starts with 98 bottles and ends with 97. The song continues -- for a very long time -- until there are 0 bottles of beer.\n", + "\n", + "Write a function called `bottle_verse` that takes a number as a parameter and displays the verse that starts with the given number of bottles.\n", + "\n", + "Hint: Consider starting with a function that can print the first, second, or last line of the verse, and then use it to write `bottle_verse`." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "53424b43", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "61010ffb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ee0076dd", + "metadata": { + "tags": [] + }, + "source": [ + "Use this function call to display the first verse." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "47a91c7d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "bottle_verse(99)" + ] + }, + { + "cell_type": "markdown", + "id": "42c237c6", + "metadata": { + "tags": [] + }, + "source": [ + "If you want to print the whole song, you can use this `for` loop, which counts down from `99` to `1`.\n", + "You don't have to completely understand this example---we'll learn more about `for` loops and the `range` function later." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "336cdfa2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "for n in range(99, 0, -1):\n", + " bottle_verse(n)\n", + " print()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b02510c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap04.ipynb b/blank/chap04.ipynb new file mode 100644 index 0000000..23e5490 --- /dev/null +++ b/blank/chap04.ipynb @@ -0,0 +1,1109 @@ +{ + "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": 2, + "id": "df64b7da", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "320fc8bc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fbb4d5a2", + "metadata": {}, + "source": [ + "# Functions and Interfaces\n", + "\n", + "This chapter introduces a module called `jupyturtle`, which allows you to create simple drawings by giving instructions to an imaginary turtle.\n", + "We will use this module to write functions that draw squares, polygons, and circles -- and to demonstrate **interface design**, which is a way of designing functions that work together." + ] + }, + { + "cell_type": "markdown", + "id": "0b0efa00", + "metadata": { + "tags": [] + }, + "source": [ + "## The jupyturtle module\n", + "\n", + "To use the `jupyturtle` module, we can import it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8f5a8a45", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8c801121", + "metadata": {}, + "source": [ + "Now we can use the functions defined in the module, like `make_turtle` and `forward`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b3f255cd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "77a61cbb", + "metadata": {}, + "source": [ + "`make_turtle` creates a **canvas**, which is a space on the screen where we can draw, and a turtle, which is represented by a circular shell and a triangular head.\n", + "The circle shows the location of the turtle and the triangle indicates the direction it is facing.\n", + "\n", + "`forward` moves the turtle a given distance in the direction it's facing, drawing a line segment along the way.\n", + "The distance is in arbitrary units -- the actual size depends on your computer's screen.\n", + "\n", + "We will use functions defined in the `jupyturtle` module many times, so it would be nice if we did not have to write the name of the module every time.\n", + "That's possible if we import the module like this." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "234fde81", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c1322d31", + "metadata": {}, + "source": [ + "This version of the import statement imports `make_turtle` and `forward` from the `jupyturtle` module so we can call them like this." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1e768880", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bd319754", + "metadata": {}, + "source": [ + "`jupyturtle` provides two other functions we'll use, called `left` and `right`.\n", + "We'll import them like this." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6d874b03", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0da2a311", + "metadata": {}, + "source": [ + "`left` causes the turtle to turn left. It takes one argument, which is the angle of the turn in degrees.\n", + "For example, we can make a 90 degree left turn like this." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1bb57a0c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cea2940f", + "metadata": {}, + "source": [ + "This program moves the turtle east and then north, leaving two line segments behind.\n", + "Before you go on, see if you can modify the previous program to make a square." + ] + }, + { + "cell_type": "markdown", + "id": "e20ea96c", + "metadata": {}, + "source": [ + "## Making a square\n", + "\n", + "Here's one way to make a square." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "9a9e455f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7500957", + "metadata": {}, + "source": [ + "Because this program repeats the same pair of lines four times, we can do the same thing more concisely with a `for` loop." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "cc27ad66", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c072ea41", + "metadata": { + "tags": [] + }, + "source": [ + "## Encapsulation and generalization\n", + "\n", + "Let's take the square-drawing code from the previous section and put it in a function called `square`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ad5f1128", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0789b5d9", + "metadata": {}, + "source": [ + "Now we can call the function like this." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "193bbe5e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "da905fc6", + "metadata": {}, + "source": [ + "Wrapping a piece of code up in a function is called **encapsulation**.\n", + "One of the benefits of encapsulation is that it attaches a name to the code, which serves as a kind of documentation. Another advantage is that if you re-use the code, it is more concise to call a function twice than to copy and paste the body!\n", + "\n", + "In the current version, the size of the square is always `50`.\n", + "If we want to draw squares with different sizes, we can take the length of the sides as a parameter. " + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "def8a5f1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "397fda4b", + "metadata": {}, + "source": [ + "Now we can draw squares with different sizes." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "b283e795", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5a46bf64", + "metadata": {}, + "source": [ + "Adding a parameter to a function is called **generalization** because it makes the function more general: with the previous version, the square is always the same size; with this version it can be any size.\n", + "\n", + "If we add another parameter, we can make it even more general.\n", + "The following function draws regular polygons with a given number of sides." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "171974ed", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "286d3c77", + "metadata": {}, + "source": [ + "In a regular polygon with `n` sides, the angle between adjacent sides is `360 / n` degrees. \n", + "\n", + "The following example draws a `7`-sided polygon with side length `30`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "71f7d9d2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dc0226db", + "metadata": {}, + "source": [ + "When a function has more than a few numeric arguments, it is easy to forget what they are, or what order they should be in. \n", + "It can be a good idea to include the names of the parameters in the argument list." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8ff2a5f4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6aa28eba", + "metadata": {}, + "source": [ + "These are sometimes called \"named arguments\" because they include the parameter names.\n", + "But in Python they are more often called **keyword arguments** (not to be confused with Python keywords like `for` and `def`).\n", + "\n", + "This use of the assignment operator, `=`, is a reminder about how arguments and parameters work -- when you call a function, the arguments are assigned to the parameters." + ] + }, + { + "cell_type": "markdown", + "id": "b10184b4", + "metadata": {}, + "source": [ + "## Approximating a circle\n", + "\n", + "Now suppose we want to draw a circle.\n", + "We can do that, approximately, by drawing a polygon with a large number of sides, so each side is small enough that it's hard to see.\n", + "Here is a function that uses `polygon` to draw a `30`-sided polygon that approximates a circle." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "7f2a5f28", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "39023314", + "metadata": {}, + "source": [ + "`circle` takes the radius of the the circle as a parameter.\n", + "It computes `circumference`, which is the circumference of a circle with the given radius.\n", + "`n` is the number of sides, so `circumference / n` is the length of each side.\n", + "\n", + "This function might take a long time to run.\n", + "We can speed it up by calling `make_turtle` with a keyword argument called `delay` that sets the time, in seconds, the turtle waits after each step.\n", + "The default value is `0.2` seconds -- if we set it to `0.02` it runs about 10 times faster." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "75258056", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "701f9cf8", + "metadata": {}, + "source": [ + "A limitation of this solution is that `n` is a constant, which means\n", + "that for very big circles, the sides are too long, and for small\n", + "circles, we waste time drawing very short sides.\n", + "One option is to generalize the function by taking `n` as a parameter.\n", + "But let's keep it simple for now." + ] + }, + { + "cell_type": "markdown", + "id": "c48f262c", + "metadata": {}, + "source": [ + "## Refactoring\n", + "\n", + "Now let's write a more general version of `circle`, called `arc`, that takes a second parameter, `angle`, and draws an arc of a circle that spans the given angle.\n", + "For example, if `angle` is `360` degrees, it draws a complete circle. If `angle` is `180` degrees, it draws a half circle.\n", + "\n", + "To write `circle`, we were able to reuse `polygon`, because a many-sided polygon is a good approximation of a circle.\n", + "But we can't use `polygon` to write `arc`.\n", + "\n", + "Instead, we'll create the more general version of `polygon`, called `polyline`." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "381edd23", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c2b2503e", + "metadata": {}, + "source": [ + "`polyline` takes as parameters the number of line segments to draw, `n`, the length of the segments, `length`, and the angle between them, `angle`.\n", + "\n", + "Now we can rewrite `polygon` to use `polyline`." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "2f4eecc0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2714a59e", + "metadata": {}, + "source": [ + "And we can use `polyline` to write `arc`." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "539466f6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3c18773c", + "metadata": {}, + "source": [ + "`arc` is similar to `circle`, except that it computes `arc_length`, which is a fraction of the circumference of a circle.\n", + "\n", + "Finally, we can rewrite `circle` to use `arc`." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "8e09f456", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "313a357c", + "metadata": {}, + "source": [ + "To check that these functions work as expected, we'll use them to draw something like a snail.\n", + "With `delay=0`, the turtle runs as fast as possible." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "80d6eadd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a34da3d8", + "metadata": {}, + "source": [ + "In this example, we started with working code and reorganized it with different functions.\n", + "Changes like this, which improve the code without changing its behavior, are called **refactoring**.\n", + "\n", + "If we had planned ahead, we might have written `polyline` first and avoided refactoring, but often you don't know enough at the beginning of a project to design all the functions.\n", + "Once you start coding, you understand the problem better.\n", + "Sometimes refactoring is a sign that you have learned something." + ] + }, + { + "cell_type": "markdown", + "id": "d18c9d16", + "metadata": {}, + "source": [ + "## Stack diagram\n", + "\n", + "When we call `circle`, it calls `arc`, which calls `polyline`.\n", + "We can use a stack diagram to show this sequence of function calls and the parameters for each one." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "1571ee71", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "f4e37360", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3160bba1", + "metadata": {}, + "source": [ + "Notice that the value of `angle` in `polyline` is different from the value of `angle` in `arc`.\n", + "Parameters are local, which means you can use the same parameter name in different functions; it's a different variable in each function, and it can refer to a different value. " + ] + }, + { + "cell_type": "markdown", + "id": "c23552d3", + "metadata": {}, + "source": [ + "## A development plan\n", + "\n", + "A **development plan** is a process for writing programs.\n", + "The process we used in this chapter is \"encapsulation and generalization\".\n", + "The steps of this process are:\n", + "\n", + "1. Start by writing a small program with no function definitions.\n", + "\n", + "2. Once you get the program working, identify a coherent piece of it,\n", + " encapsulate the piece in a function and give it a name.\n", + "\n", + "3. Generalize the function by adding appropriate parameters.\n", + "\n", + "4. Repeat Steps 1 to 3 until you have a set of working functions.\n", + "\n", + "5. Look for opportunities to improve the program by refactoring. For\n", + " example, if you have similar code in several places, consider\n", + " factoring it into an appropriately general function.\n", + "\n", + "This process has some drawbacks -- we will see alternatives later -- but it can be useful if you don't know ahead of time how to divide the program into functions.\n", + "This approach lets you design as you go along." + ] + }, + { + "cell_type": "markdown", + "id": "a3b6b83d", + "metadata": {}, + "source": [ + "The design of a function has two parts:\n", + "\n", + "* The **interface** is how the function is used, including its name, the parameters it takes and what the function is supposed to do.\n", + "\n", + "* The **implementation** is how the function does what it's supposed to do.\n", + "\n", + "For example, here's the first version of `circle` we wrote, which uses `polygon`." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "baf964ba", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5d3d2e79", + "metadata": {}, + "source": [ + "And here's the refactored version that uses `arc`." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "e2e006d5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b726f72c", + "metadata": {}, + "source": [ + "These two functions have the same interface -- they take the same parameters and do the same thing -- but they have different implementations." + ] + }, + { + "cell_type": "markdown", + "id": "3e3bae20", + "metadata": { + "tags": [] + }, + "source": [ + "## Docstrings\n", + "\n", + "A **docstring** is a string at the beginning of a function that explains the interface (\"doc\" is short for \"documentation\").\n", + "Here is an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "b68f3682", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "55b60cbc", + "metadata": {}, + "source": [ + "By convention, docstrings are triple-quoted strings, also known as **multiline strings** because the triple quotes allow the string to span more than one line.\n", + "\n", + "A docstring should:\n", + "\n", + "* Explain concisely what the function does, without getting into the details of how it works,\n", + "\n", + "* Explain what effect each parameter has on the behavior of the function, and\n", + "\n", + "* Indicate what type each parameter should be, if it is not obvious.\n", + "\n", + "Writing this kind of documentation is an important part of interface design.\n", + "A well-designed interface should be simple to explain; if you have a hard time explaining one of your functions, maybe the interface could be improved." + ] + }, + { + "cell_type": "markdown", + "id": "f1115940", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "An interface is like a contract between a function and a caller. The\n", + "caller agrees to provide certain arguments and the function agrees to\n", + "do certain work.\n", + "\n", + "For example, `polyline` requires three arguments: `n` has to be an integer; `length` should be a positive number; and `angle` has to be a number, which is understood to be in degrees.\n", + "\n", + "These requirements are called **preconditions** because they are supposed to be true before the function starts executing. Conversely, conditions at the end of the function are **postconditions**.\n", + "Postconditions include the intended effect of the function (like drawing line segments) and any side effects (like moving the turtle or making other changes).\n", + "\n", + "Preconditions are the responsibility of the caller. If the caller violates a precondition and the function doesn't work correctly, the bug is in the caller, not the function.\n", + "\n", + "If the preconditions are satisfied and the postconditions are not, the bug is in the function. If your pre- and postconditions are clear, they can help with debugging." + ] + }, + { + "cell_type": "markdown", + "id": "a4d33a70", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**interface design:**\n", + "A process for designing the interface of a function, which includes the parameters it should take.\n", + "\n", + "**canvas:**\n", + "A window used to display graphical elements including lines, circles, rectangles, and other shapes.\n", + "\n", + "**encapsulation:**\n", + " The process of transforming a sequence of statements into a function definition.\n", + "\n", + "**generalization:**\n", + " The process of replacing something unnecessarily specific (like a number) with something appropriately general (like a variable or parameter).\n", + "\n", + "**keyword argument:**\n", + "An argument that includes the name of the parameter.\n", + "\n", + "**refactoring:**\n", + " The process of modifying a working program to improve function interfaces and other qualities of the code.\n", + "\n", + "**development plan:**\n", + "A process for writing programs.\n", + "\n", + "**docstring:**\n", + " A string that appears at the top of a function definition to document the function's interface.\n", + "\n", + "**multiline string:**\n", + "A string enclosed in triple quotes that can span more than one line of a program.\n", + "\n", + "**precondition:**\n", + " A requirement that should be satisfied by the caller before a function starts.\n", + "\n", + "**postcondition:**\n", + " A requirement that should be satisfied by the function before it ends." + ] + }, + { + "cell_type": "markdown", + "id": "0bfe2e19", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "9f94061e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "50ed5c38", + "metadata": {}, + "source": [ + "For the exercises below, there are a few more turtle functions you might want to use.\n", + "\n", + "* `penup` lifts the turtle's imaginary pen so it doesn't leave a trail when it moves.\n", + "\n", + "* `pendown` puts the pen back down.\n", + "\n", + "The following function uses `penup` and `pendown` to move the turtle without leaving a trail." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "6f9a0106", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c78c1e17", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `rectangle` that draws a rectangle with given side lengths.\n", + "For example, here's a rectangle that's `80` units wide and `40` units tall." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "c54ba660", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4b05078c", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following code to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "1311ee08", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8b8faaf6", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `rhombus` that draws a rhombus with a given side length and a given interior angle. For example, here's a rhombus with side length `50` and an interior angle of `60` degrees." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "3db6f106", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7917956b", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following code to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "1d845de9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a9175a90", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Now write a more general function called `parallelogram` that draws a quadrilateral with parallel sides. Then rewrite `rectangle` and `rhombus` to use `parallelogram`." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "895005cb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "7e7d34b0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "481396f9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c03bd4a2", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following code to test your functions." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "c8dfebc9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "991ab59d", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write an appropriately general set of functions that can draw shapes like this.\n", + "\n", + "![](https://github.com/AllenDowney/ThinkPython/raw/v3/jupyturtle_pie.png)\n", + "\n", + "Hint: Write a function called `triangle` that draws one triangular segment, and then a function called `draw_pie` that uses `triangle`." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "8be6442e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "be1b7ed8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8702c0ad", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following code to test your functions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c519ca39", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "89ce198a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9c78b76f", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write an appropriately general set of functions that can draw flowers like this.\n", + "\n", + "![](https://github.com/AllenDowney/ThinkPython/raw/v3/jupyturtle_flower.png)\n", + "\n", + "Hint: Use `arc` to write a function called `petal` that draws one flower petal." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "0f0e7498", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "6c0d0bff", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8fe06dea", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following code to test your functions.\n", + "\n", + "Because the solution draws a lot of small line segments, it tends to slow down as it runs.\n", + "To avoid that, you can add the keyword argument `auto_render=False` to avoid drawing after every step, and then call the `render` function at the end to show the result.\n", + "\n", + "While you are debugging, you might want to remove `auto_render=False`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04193da5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "4cfea3b0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9d9f35d1", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "There are several modules like `jupyturtle` in Python, and the one we used in this chapter has been customized for this book.\n", + "So if you ask a virtual assistant for help, it won't know which module to use.\n", + "But if you give it a few examples to work with, it can probably figure it out.\n", + "For example, try this prompt and see if it can write a function that draws a spiral:\n", + "\n", + "```\n", + "The following program uses a turtle graphics module to draw a circle:\n", + "\n", + "from jupyturtle import make_turtle, forward, left\n", + "import math\n", + "\n", + "def polygon(n, length):\n", + " angle = 360 / n\n", + " for i in range(n):\n", + " forward(length)\n", + " left(angle)\n", + " \n", + "def circle(radius):\n", + " circumference = 2 * math.pi * radius\n", + " n = 30\n", + " length = circumference / n\n", + " polygon(n, length)\n", + " \n", + "make_turtle(delay=0)\n", + "circle(30)\n", + "\n", + "Write a function that draws a spiral.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "7beb2afe", + "metadata": {}, + "source": [ + "Keep in mind that the result might use features we have not seen yet, and it might have errors.\n", + "Copy the code from the VA and see if you can get it working.\n", + "If you didn't get what you wanted, try modifying the prompt.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "46d3151c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "186c7fbc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap05.ipynb b/blank/chap05.ipynb new file mode 100644 index 0000000..9f9ad19 --- /dev/null +++ b/blank/chap05.ipynb @@ -0,0 +1,1549 @@ +{ + "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": "8119ba50", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "75b60d6c", + "metadata": {}, + "source": [ + "# Conditionals and Recursion\n", + "\n", + "The main topic of this chapter is the `if` statement, which executes different code depending on the state of the program.\n", + "And with the `if` statement we'll be able to explore one of the most powerful ideas in computing, **recursion**.\n", + "\n", + "But we'll start with three new features: the modulus operator, boolean expressions, and logical operators." + ] + }, + { + "cell_type": "markdown", + "id": "4ab7caf4", + "metadata": {}, + "source": [ + "## Integer division and modulus\n", + "\n", + "Recall that the integer division operator, `//`, divides two numbers and rounds\n", + "down to an integer.\n", + "For example, suppose the run time of a movie is 105 minutes. \n", + "You might want to know how long that is in hours.\n", + "Conventional division returns a floating-point number:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "30bd0ba7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3f224403", + "metadata": {}, + "source": [ + "But we don't normally write hours with decimal points.\n", + "Integer division returns the integer number of hours, rounding down:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "451e3198", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bfa9b0cf", + "metadata": {}, + "source": [ + "To get the remainder, you could subtract off one hour in minutes:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "64b92876", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "05caf27f", + "metadata": {}, + "source": [ + "Or you could use the **modulus operator**, `%`, which divides two numbers and returns the remainder." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0a593844", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "18c1e0d0", + "metadata": {}, + "source": [ + "The modulus operator is more useful than it might seem.\n", + "For example, it can check whether one number is divisible by another -- if `x % y` is zero, then `x` is divisible by `y`.\n", + "\n", + "Also, it can extract the right-most digit or digits from a number.\n", + "For example, `x % 10` yields the right-most digit of `x` (in base 10).\n", + "Similarly, `x % 100` yields the last two digits." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5bd341f7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "367fce0c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f2344fc0", + "metadata": {}, + "source": [ + "Finally, the modulus operator can do \"clock arithmetic\".\n", + "For example, if an event starts at 11 AM and lasts three hours, we can use the modulus operator to figure out what time it ends." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "db33a44d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "351c30df", + "metadata": {}, + "source": [ + "The event would end at 2 PM." + ] + }, + { + "cell_type": "markdown", + "id": "5ed1b58b", + "metadata": {}, + "source": [ + "## Boolean Expressions\n", + "\n", + "A **boolean expression** is an expression that is either true or false.\n", + "For example, the following expressions use the equals operator, `==`, which compares two values and produces `True` if they are equal and `False` otherwise:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "85589d38", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3c9c8f61", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "41fbc642", + "metadata": {}, + "source": [ + "A common error is to use a single equal sign (`=`) instead of a double equal sign (`==`).\n", + "Remember that `=` assigns a value to a variable and `==` compares two values. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c0e51bcc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "a6be44db", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d3ec6b48", + "metadata": {}, + "source": [ + "`True` and `False` are special values that belong to the type `bool`;\n", + "they are not strings:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "90fb1c9c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c1cae572", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "451b2e8d", + "metadata": {}, + "source": [ + "The `==` operator is one of the **relational operators**; the others are:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c901fe2b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "1457949f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "56bb7eed", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "1cdcc7ab", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "df1a1287", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "db5a9477", + "metadata": {}, + "source": [ + "## Logical operators\n", + "\n", + "To combine boolean values into expressions, we can use **logical operators**.\n", + "The most common are `and`, ` or`, and `not`.\n", + "The meaning of these operators is similar to their meaning in English.\n", + "For example, the value of the following expression is `True` only if `x` is greater than `0` *and* less than `10`." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "848c5f2c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e8c14026", + "metadata": {}, + "source": [ + "The following expression is `True` if *either or both* of the conditions is true, that is, if the number is divisible by 2 *or* 3:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "eb66ee6a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3bd0ef52", + "metadata": {}, + "source": [ + "Finally, the `not` operator negates a boolean expression, so the following expression is `True` if `x > y` is `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "6de8b97c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fc6098c2", + "metadata": {}, + "source": [ + "Strictly speaking, the operands of a logical operator should be boolean expressions, but Python is not very strict.\n", + "Any nonzero number is interpreted as `True`:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "add63275", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "102ceab9", + "metadata": {}, + "source": [ + "This flexibility can be useful, but there are some subtleties to it that can be confusing.\n", + "You might want to avoid it." + ] + }, + { + "cell_type": "markdown", + "id": "6b0f2dc1", + "metadata": {}, + "source": [ + "## if statements\n", + "\n", + "In order to write useful programs, we almost always need the ability to\n", + "check conditions and change the behavior of the program accordingly.\n", + "**Conditional statements** give us this ability. The simplest form is\n", + "the `if` statement:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "80937bef", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "973f705e", + "metadata": {}, + "source": [ + "`if` is a Python keyword.\n", + "`if` statements have the same structure as function definitions: a\n", + "header followed by an indented statement or sequence of statements called a **block**.\n", + "\n", + "The boolean expression after `if` is called the **condition**.\n", + "If it is true, the statements in the indented block run. If not, they don't.\n", + "\n", + "There is no limit to the number of statements that can appear in the block, but there has to be at least one.\n", + "Occasionally, it is useful to have a block that does nothing -- usually as a place keeper for code you haven't written yet.\n", + "In that case, you can use the `pass` statement, which does nothing." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "bc74a318", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "adf3f6c5", + "metadata": {}, + "source": [ + "The word `TODO` in a comment is a conventional reminder that there's something you need to do later." + ] + }, + { + "cell_type": "markdown", + "id": "eb39bcd9", + "metadata": {}, + "source": [ + "## The `else` clause\n", + "\n", + "An `if` statement can have a second part, called an `else` clause.\n", + "The syntax looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "d16f49f2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e7dc8943", + "metadata": {}, + "source": [ + "If the condition is true, the first indented statement runs; otherwise, the second indented statement runs.\n", + "\n", + "In this example, if `x` is even, the remainder when `x` is divided by `2` is `0`, so the condition is true and the program displays `x is even`.\n", + "If `x` is odd, the remainder is `1`, so the condition\n", + "is false, and the program displays `x is odd`.\n", + "\n", + "Since the condition must be true or false, exactly one of the alternatives will run. \n", + "The alternatives are called **branches**." + ] + }, + { + "cell_type": "markdown", + "id": "20c8adb6", + "metadata": {}, + "source": [ + "## Chained conditionals\n", + "\n", + "Sometimes there are more than two possibilities and we need more than two branches.\n", + "One way to express a computation like that is a **chained conditional**, which includes an `elif` clause." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "309fccb8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "46916379", + "metadata": {}, + "source": [ + "`elif` is an abbreviation of \"else if\".\n", + "There is no limit on the number of `elif` clauses.\n", + "If there is an `else` clause, it has to be at the end, but there doesn't have to be\n", + "one.\n", + "\n", + "Each condition is checked in order.\n", + "If the first is false, the next is checked, and so on.\n", + "If one of them is true, the corresponding branch runs and the `if` statement ends.\n", + "Even if more than one condition is true, only the first true branch runs." + ] + }, + { + "cell_type": "markdown", + "id": "e0c0b9dd", + "metadata": {}, + "source": [ + "## Nested Conditionals\n", + "\n", + "One conditional can also be nested within another.\n", + "We could have written the example in the previous section like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "d77539cf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "29f67a0a", + "metadata": {}, + "source": [ + "The outer `if` statement contains two branches. \n", + "The first branch contains a simple statement. The second branch contains another `if` statement, which has two branches of its own.\n", + "Those two branches are both simple statements, although they could have been conditional statements as well.\n", + "\n", + "Although the indentation of the statements makes the structure apparent, **nested conditionals** can be difficult to read.\n", + "I suggest you avoid them when you can.\n", + "\n", + "Logical operators often provide a way to simplify nested conditional statements.\n", + "Here's an example with a nested conditional." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "91cac1a0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5292eb11", + "metadata": {}, + "source": [ + "The `print` statement runs only if we make it past both conditionals, so we get the same effect with the `and` operator." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "f8ba1724", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dd8e808a", + "metadata": {}, + "source": [ + "For this kind of condition, Python provides a more concise option:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "014cd6f4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "db583cd9", + "metadata": {}, + "source": [ + "## Recursion\n", + "\n", + "It is legal for a function to call itself.\n", + "It may not be obvious why that is a good thing, but it turns out to be one of the most magical things a program can do.\n", + "Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "17904e98", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c88e0dc7", + "metadata": {}, + "source": [ + "If `n` is 0 or negative, `countdown` outputs the word, \"Blastoff!\" Otherwise, it\n", + "outputs `n` and then calls itself, passing `n-1` as an argument.\n", + "\n", + "Here's what happens when we call this function with the argument `3`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "6c1e32e2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3f3c87ec", + "metadata": {}, + "source": [ + "The execution of `countdown` begins with `n=3`, and since `n` is greater\n", + "than `0`, it displays `3`, and then calls itself\\...\n", + "\n", + "> The execution of `countdown` begins with `n=2`, and since `n` is\n", + "> greater than `0`, it displays `2`, and then calls itself\\...\n", + ">\n", + "> > The execution of `countdown` begins with `n=1`, and since `n` is\n", + "> > greater than `0`, it displays `1`, and then calls itself\\...\n", + "> >\n", + "> > > The execution of `countdown` begins with `n=0`, and since `n` is\n", + "> > > not greater than `0`, it displays \"Blastoff!\" and returns.\n", + "> >\n", + "> > The `countdown` that got `n=1` returns.\n", + ">\n", + "> The `countdown` that got `n=2` returns.\n", + "\n", + "The `countdown` that got `n=3` returns." + ] + }, + { + "cell_type": "markdown", + "id": "782e95bb", + "metadata": {}, + "source": [ + "A function that calls itself is **recursive**.\n", + "As another example, we can write a function that prints a string `n` times." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "1bb13f8e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "73d07c17", + "metadata": {}, + "source": [ + "If `n` is positive, `print_n_times` displays the value of `string` and then calls itself, passing along `string` and `n-1` as arguments.\n", + "\n", + "If `n` is `0` or negative, the condition is false and `print_n_times` does nothing.\n", + "\n", + "Here's how it works." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "e7b68c57", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1fb55a78", + "metadata": {}, + "source": [ + "For simple examples like this, it is probably easier to use a `for`\n", + "loop. But we will see examples later that are hard to write with a `for`\n", + "loop and easy to write with recursion, so it is good to start early." + ] + }, + { + "cell_type": "markdown", + "id": "c652c739", + "metadata": {}, + "source": [ + "## Stack diagrams for recursive functions\n", + "\n", + "Here's a stack diagram that shows the frames created when we called `countdown` with `n = 3`." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "643148da", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "a8510119", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9282331b", + "metadata": {}, + "source": [ + "The four `countdown` frames have different values for the parameter `n`.\n", + "The bottom of the stack, where `n=0`, is called the **base case**.\n", + "It does not make a recursive call, so there are no more frames." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "a2a376b3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "37bbc2b8", + "metadata": {}, + "source": [ + "## Infinite recursion\n", + "\n", + "If a recursion never reaches a base case, it goes on making recursive\n", + "calls forever, and the program never terminates. This is known as\n", + "**infinite recursion**, and it is generally not a good idea.\n", + "Here's a minimal function with an infinite recursion." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "af487feb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "450a20ac", + "metadata": {}, + "source": [ + "Every time `recurse` is called, it calls itself, which creates another frame.\n", + "In Python, there is a limit to the number of frames that can be on the stack at the same time.\n", + "If a program exceeds the limit, it causes a runtime error." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "e5d6c732", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "22454b51", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "39fc5c31", + "metadata": {}, + "source": [ + "The traceback indicates that there were almost 3000 frames on the stack when the error occurred.\n", + "\n", + "If you encounter an infinite recursion by accident, review your function to confirm that there is a base case that does not make a recursive call. And if there is a base case, check whether you are guaranteed to reach it." + ] + }, + { + "cell_type": "markdown", + "id": "45299414", + "metadata": {}, + "source": [ + "## Keyboard input\n", + "\n", + "The programs we have written so far accept no input from the user. They\n", + "just do the same thing every time.\n", + "\n", + "Python provides a built-in function called `input` that stops the\n", + "program and waits for the user to type something. When the user presses\n", + "*Return* or *Enter*, the program resumes and `input` returns what the user\n", + "typed as a string." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "ac0fb4a6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "f6a2e4d6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "acf9ec53", + "metadata": {}, + "source": [ + "Before getting input from the user, you might want to display a prompt\n", + "telling the user what to type. `input` can take a prompt as an argument:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "e0600e5e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "964346f0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1b754b39", + "metadata": {}, + "source": [ + "The sequence `\\n` at the end of the prompt represents a **newline**, which is a special character that causes a line break -- that way the user's input appears below the prompt.\n", + "\n", + "If you expect the user to type an integer, you can use the `int` function to convert the return value to `int`." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "590983cd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "60a484d7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0a65f2af", + "metadata": {}, + "source": [ + "But if they type something that's not an integer, you'll get a runtime error." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "8d3d6049", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "a04e3016", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a4ce3ed5", + "metadata": {}, + "source": [ + "We will see how to handle this kind of error later." + ] + }, + { + "cell_type": "markdown", + "id": "14c1d3dc", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "When a syntax or runtime error occurs, the error message contains a lot\n", + "of information, but it can be overwhelming. The most useful parts are\n", + "usually:\n", + "\n", + "- What kind of error it was, and\n", + "\n", + "- Where it occurred.\n", + "\n", + "Syntax errors are usually easy to find, but there are a few gotchas.\n", + "Errors related to spaces and tabs can be tricky because they are invisible\n", + "and we are used to ignoring them." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "b82642f6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d1d06263", + "metadata": {}, + "source": [ + "In this example, the problem is that the second line is indented by one space.\n", + "But the error message points to `y`, which is misleading.\n", + "Error messages indicate where the problem was discovered, but the actual error might be earlier in the code.\n", + "\n", + "The same is true of runtime errors. \n", + "For example, suppose you are trying to convert a ratio to decibels, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "583ef53c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "2f4b6082", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "55914374", + "metadata": {}, + "source": [ + "The error message indicates line 5, but there is nothing wrong with that line.\n", + "The problem is in line 4, which uses integer division instead of floating-point division -- as a result, the value of `ratio` is `0`.\n", + "When we call `math.log10`, we get a `ValueError` with the message `math domain error`, because `0` is not in the \"domain\" of valid arguments for `math.log10`, because the logarithm of `0` is undefined.\n", + "\n", + "In general, you should take the time to read error messages carefully, but don't assume that everything they say is correct." + ] + }, + { + "cell_type": "markdown", + "id": "8ffe690e", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**recursion:**\n", + "The process of calling the function that is currently executing.\n", + "\n", + "**modulus operator:**\n", + "An operator, `%`, that works on integers and returns the remainder when one number is divided by another.\n", + "\n", + "**boolean expression:**\n", + "An expression whose value is either `True` or `False`.\n", + "\n", + "**relational operator:**\n", + "One of the operators that compares its operands: `==`, `!=`, `>`, `<`, `>=`, and `<=`.\n", + "\n", + "**logical operator:**\n", + "One of the operators that combines boolean expressions, including `and`, `or`, and `not`.\n", + "\n", + "**conditional statement:**\n", + "A statement that controls the flow of execution depending on some condition.\n", + "\n", + "**condition:**\n", + "The boolean expression in a conditional statement that determines which branch runs.\n", + "\n", + "**block:**\n", + "One or more statements indented to indicate they are part of another statement.\n", + "\n", + "**branch:**\n", + "One of the alternative sequences of statements in a conditional statement.\n", + "\n", + "**chained conditional:**\n", + "A conditional statement with a series of alternative branches.\n", + "\n", + "**nested conditional:**\n", + "A conditional statement that appears in one of the branches of another conditional statement.\n", + "\n", + "**recursive:**\n", + "A function that calls itself is recursive.\n", + "\n", + "**base case:**\n", + "A conditional branch in a recursive function that does not make a recursive call.\n", + "\n", + "**infinite recursion:**\n", + "A recursion that doesn't have a base case, or never reaches it.\n", + "Eventually, an infinite recursion causes a runtime error.\n", + "\n", + "**newline:**\n", + "A character that creates a line break between two parts of a string." + ] + }, + { + "cell_type": "markdown", + "id": "8d783953", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "66aae3cb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "02f9f1d7", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "* Ask a virtual assistant, \"What are some uses of the modulus operator?\"\n", + "\n", + "* Python provides operators to compute the logical operations `and`, `or`, and `not`, but it doesn't have an operator that computes the exclusive `or` operation, usually written `xor`. Ask an assistant \"What is the logical xor operation and how do I compute it in Python?\"\n", + "\n", + "In this chapter, we saw two ways to write an `if` statement with three branches, using a chained conditional or a nested conditional.\n", + "You can use a virtual assistant to convert from one to the other.\n", + "For example, ask a VA, \"Convert this statement to a chained conditional.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "ade1ecb4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "dc7026c2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9c2a8466", + "metadata": {}, + "source": [ + "Ask a VA, \"Rewrite this statement with a single conditional.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "1fd919ea", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e0fbed08", + "metadata": {}, + "source": [ + "See if a VA can simplify this unnecessary complexity." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "1e71702e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "74ef776d", + "metadata": {}, + "source": [ + "Here's an attempt at a recursive function that counts down by two." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "84cbd5a4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "77178e79", + "metadata": {}, + "source": [ + "It seems to work." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "b0918789", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c9d3a8dc", + "metadata": {}, + "source": [ + "But it has an error. Ask a virtual assistant what's wrong and how to fix it.\n", + "Paste the solution it provides back here and test it." + ] + }, + { + "cell_type": "markdown", + "id": "240a3888", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "The `time` module provides a function, also called `time`, that returns\n", + "returns the number of seconds since the \"Unix epoch\", which is January 1, 1970, 00:00:00 UTC (Coordinated Universal Time)." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "1e7a2c07", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "054c3197", + "metadata": {}, + "source": [ + "Use integer division and the modulus operator to compute the number of days since January 1, 1970 and the current time of day in hours, minutes, and seconds." + ] + }, + { + "cell_type": "markdown", + "id": "310196ba", + "metadata": { + "tags": [] + }, + "source": [ + "You can read more about the `time` module at ." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "c5fa57b2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "322ddd0a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "fc43df7d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "0184d21a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6b1fd514", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "If you are given three sticks, you may or may not be able to arrange\n", + "them in a triangle. For example, if one of the sticks is 12 inches long\n", + "and the other two are one inch long, you will not be able to get the\n", + "short sticks to meet in the middle. For any three lengths, there is a\n", + "test to see if it is possible to form a triangle:\n", + "\n", + "> If any of the three lengths is greater than the sum of the other two,\n", + "> then you cannot form a triangle. Otherwise, you can. (If the sum of\n", + "> two lengths equals the third, they form what is called a \"degenerate\"\n", + "> triangle.)\n", + "\n", + "Write a function named `is_triangle` that takes three integers as\n", + "arguments, and that prints either \"Yes\" or \"No\", depending on\n", + "whether you can or cannot form a triangle from sticks with the given\n", + "lengths. Hint: Use a chained conditional.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "06381639", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2842401c", + "metadata": { + "tags": [] + }, + "source": [ + "Test your function with the following cases." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "156273af", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "e00793f4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "d2911c71", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "2b05586e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2ba42106", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "What is the output of the following program? Draw a stack diagram that\n", + "shows the state of the program when it prints the result." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "dac374ad", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "a438cec5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "2e3f56c7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bca9517d", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "The following exercises use the `jupyturtle` module, described in Chapter 4.\n", + "\n", + "Read the following function and see if you can figure out what it does.\n", + "Then run it and see if you got it right.\n", + "Adjust the values of `length`, `angle` and `factor` and see what effect they have on the result.\n", + "If you are not sure you understand how it works, try asking a virtual assistant." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "2b0d60a1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "ef0256ee", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e525ba59", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Ask a virtual assistant \"What is the Koch curve?\"\n", + "\n", + "To draw a Koch curve with length `x`, all you\n", + "have to do is\n", + "\n", + "1. Draw a Koch curve with length `x/3`.\n", + "\n", + "2. Turn left 60 degrees.\n", + "\n", + "3. Draw a Koch curve with length `x/3`.\n", + "\n", + "4. Turn right 120 degrees.\n", + "\n", + "5. Draw a Koch curve with length `x/3`.\n", + "\n", + "6. Turn left 60 degrees.\n", + "\n", + "7. Draw a Koch curve with length `x/3`.\n", + "\n", + "The exception is if `x` is less than `5` -- in that case, you can just draw a straight line with length `x`.\n", + "\n", + "Write a function called `koch` that takes `x` as an argument and draws a Koch curve with the given length.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "c1acc853", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2991143a", + "metadata": {}, + "source": [ + "The result should look like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "55507716", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b1c58420", + "metadata": { + "tags": [] + }, + "source": [ + "Once you have koch working, you can use this loop to draw three Koch curves in the shape of a snowflake." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "86d3123b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4c964239", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Virtual assistants know about the functions in the `jupyturtle` module, but there are many versions of these functions, with different names, so a VA might not know which one you are talking about.\n", + "\n", + "To solve this problem, you can provide additional information before you ask a question.\n", + "For example, you could start a prompt with \"Here's a program that uses the `jupyturtle` module,\" and then paste in one of the examples from this chapter.\n", + "After that, the VA should be able to generate code that uses this module.\n", + "\n", + "As an example, ask a VA for a program that draws a Sierpiński triangle.\n", + "The code you get should be a good starting place, but you might have to do some debugging.\n", + "If the first attempt doesn't work, you can tell the VA what happened and ask for help -- or you can debug it yourself." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "68439acf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6a95097a", + "metadata": {}, + "source": [ + "Here's what the result might look like, although the version you get might be different." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "43470b3d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d6969d4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap06.ipynb b/blank/chap06.ipynb new file mode 100644 index 0000000..7fadc4e --- /dev/null +++ b/blank/chap06.ipynb @@ -0,0 +1,1697 @@ +{ + "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": 87, + "id": "56b1c184", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "88ecc443", + "metadata": {}, + "source": [ + "# Return Values\n", + "\n", + "In previous chapters, we've used built-in functions -- like `abs` and `round` -- and functions in the math module -- like `sqrt` and `pow`.\n", + "When you call one of these functions, it returns a value you can assign to a variable or use as part of an expression.\n", + "\n", + "The functions we have written so far are different.\n", + "Some use the `print` function to display values, and some use turtle functions to draw figures.\n", + "But they don't return values we assign to variables or use in expressions.\n", + "\n", + "In this chapter, we'll see how to write functions that return values." + ] + }, + { + "cell_type": "markdown", + "id": "6cf2cf80", + "metadata": {}, + "source": [ + "## Some functions have return values\n", + "\n", + "When you call a function like `math.sqrt`, the result is called a **return value**.\n", + "If the function call appears at the end of a cell, Jupyter displays the return value immediately." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "e0e1dd91", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4b4885c2", + "metadata": {}, + "source": [ + "If you assign the return value to a variable, it doesn't get displayed." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "5aaf62d2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "196c692b", + "metadata": {}, + "source": [ + "But you can display it later." + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "741f7386", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "257b28d5", + "metadata": {}, + "source": [ + "Or you can use the return value as part of an expression." + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "e56d39c4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "23ed47ab", + "metadata": {}, + "source": [ + "Here's an example of a function that returns a value." + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "50a9a9be", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "273acabc", + "metadata": {}, + "source": [ + "`circle_area` takes `radius` as a parameter and computes the area of a circle with that radius.\n", + "\n", + "The last line is a `return` statement that returns the value of `area`.\n", + "\n", + "If we call the function like this, Jupyter displays the return value.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "d70fd9b5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4f28bfd6", + "metadata": {}, + "source": [ + "We can assign the return value to a variable." + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "ef20ba8c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3f82fe70", + "metadata": {}, + "source": [ + "Or use it as part of an expression." + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "0a4670f4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "15122fd2", + "metadata": {}, + "source": [ + "Later we can display the value of the variable we assigned the result to." + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "6e6460b9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a3f6dcae", + "metadata": {}, + "source": [ + "But we can't access `area`." + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "77613df9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f8ace9ce", + "metadata": {}, + "source": [ + "`area` is a local variable in a function, so we can't access it from outside the function." + ] + }, + { + "cell_type": "markdown", + "id": "41a4f03f", + "metadata": {}, + "source": [ + "## And some have None\n", + "\n", + "If a function doesn't have a `return` statement, it returns `None`, which is a special value like `True` and `False`.\n", + "For example, here's the `repeat` function from Chapter 3." + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "89c083f8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6ada19cf", + "metadata": {}, + "source": [ + "If we call it like this, it displays the first line of the Monty Python song \"Finland\"." + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "737b67ca", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fe49f5e5", + "metadata": {}, + "source": [ + "This function uses the `print` function to display a string, but it does not use a `return` statement to return a value.\n", + "If we assign the result to a variable, it displays the string anyway. " + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "9b4fa14f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4ecabbdb", + "metadata": {}, + "source": [ + "And if we display the value of the variable, we get nothing." + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "50f96bcb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "07033959", + "metadata": {}, + "source": [ + "`result` actually has a value, but Jupyter doesn't show it.\n", + "However, we can display it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "6712f2df", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "379b98c5", + "metadata": {}, + "source": [ + "The return value from `repeat` is `None`.\n", + "\n", + "Now here's a function similar to `repeat` except that has a return value." + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "0ec1afd3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "db6ad3d4", + "metadata": {}, + "source": [ + "Notice that we can use an expression in a `return` statement, not just a variable.\n", + "\n", + "With this version, we can assign the result to a variable.\n", + "When the function runs, it doesn't display anything." + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "c82334b6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1232cd8a", + "metadata": {}, + "source": [ + "But later we can display the value assigned to `line`." + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "595ec598", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ae02c7d2", + "metadata": {}, + "source": [ + "A function like this is called a **pure function** because it doesn't display anything or have any other effect -- other than returning a value." + ] + }, + { + "cell_type": "markdown", + "id": "567ae734", + "metadata": {}, + "source": [ + "## Return values and conditionals\n", + "\n", + "If Python did not provide `abs`, we could write it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "id": "236c59e6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ffd559b8", + "metadata": {}, + "source": [ + "If `x` is negative, the first `return` statement returns `-x` and the function ends immediately.\n", + "Otherwise, the second `return` statement returns `x` and the function ends.\n", + "So this function is correct.\n", + "\n", + "However, if you put `return` statements in a conditional, you have to make sure that every possible path through the program hits a `return` statement.\n", + "For example, here's an incorrect version of `absolute_value`." + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "id": "2f60639c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "da3280ae", + "metadata": {}, + "source": [ + "Here's what happens if we call this function with `0` as an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "c9dae6c8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5733f239", + "metadata": {}, + "source": [ + "We get nothing! Here's the problem: when `x` is `0`, neither condition is true, and the function ends without hitting a `return` statement, which means that the return value is `None`, so Jupyter displays nothing.\n", + "\n", + "As another example, here's a version of `absolute_value` with an extra `return` statement at the end." + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "id": "c8c4edee", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cf5486fd", + "metadata": {}, + "source": [ + "If `x` is negative, the first `return` statement runs and the function ends.\n", + "Otherwise the second `return` statement runs and the function ends.\n", + "Either way, we never get to the third `return` statement -- so it can never run.\n", + "\n", + "Code that can never run is called **dead code**.\n", + "In general, dead code doesn't do any harm, but it often indicates a misunderstanding, and it might be confusing to someone trying to understand the program." + ] + }, + { + "cell_type": "markdown", + "id": "68a6ae39", + "metadata": { + "tags": [] + }, + "source": [ + "## Incremental development\n", + "\n", + "As you write larger functions, you might find yourself spending more\n", + "time debugging.\n", + "To deal with increasingly complex programs, you might want to try **incremental development**, which is a way of adding and testing only a small amount of code at a time.\n", + "\n", + "As an example, suppose you want to find the distance between two points represented by the coordinates $(x_1, y_1)$ and $(x_2, y_2)$.\n", + "By the Pythagorean theorem, the distance is:\n", + "\n", + "$$\\mathrm{distance} = \\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$$ \n", + "\n", + "The first step is to consider what a `distance` function should look like in Python -- that is, what are the inputs (parameters) and what is the output (return value)?\n", + "\n", + "For this function, the inputs are the coordinates of the points.\n", + "The return value is the distance.\n", + "Immediately you can write an outline of the function:" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "id": "bbcab1ed", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7b384fcf", + "metadata": {}, + "source": [ + "This version doesn't compute distances yet -- it always returns zero.\n", + "But it is a complete function with a return value, which means that you can test it before you make it more complicated.\n", + "\n", + "To test the new function, we'll call it with sample arguments:" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "id": "923d96db", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "13a98096", + "metadata": {}, + "source": [ + "I chose these values so that the horizontal distance is `3` and the\n", + "vertical distance is `4`.\n", + "That way, the result is `5`, the hypotenuse of a `3-4-5` right triangle. When testing a function, it is useful to know the right answer.\n", + "\n", + "At this point we have confirmed that the function runs and returns a value, and we can start adding code to the body.\n", + "A good next step is to find the differences `x2 - x1` and `y2 - y1`. \n", + "Here's a version that stores those values in temporary variables and displays them." + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "id": "9374cfe3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c342a3bd", + "metadata": {}, + "source": [ + "If the function is working, it should display `dx is 3` and `dy is 4`.\n", + "If so, we know that the function is getting the right arguments and\n", + "performing the first computation correctly. If not, there are only a few\n", + "lines to check." + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "id": "405af839", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9424eca9", + "metadata": {}, + "source": [ + "Good so far. Next we compute the sum of squares of `dx` and `dy`:" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "id": "e52b3b04", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e28262f9", + "metadata": {}, + "source": [ + "Again, we can run the function and check the output, which should be `25`. " + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "id": "38eebbf3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c09f0ddc", + "metadata": {}, + "source": [ + "Finally, we can use `math.sqrt` to compute the distance:" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "id": "b4536ea0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f27902ac", + "metadata": {}, + "source": [ + "And test it." + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "id": "325efb93", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8ad2e626", + "metadata": {}, + "source": [ + "The result is correct, but this version of the function displays the result rather than returning it, so the return value is `None`.\n", + "\n", + "We can fix that by replacing the `print` function with a `return` statement." + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "id": "3cd982ce", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f3a13a14", + "metadata": {}, + "source": [ + "This version of `distance` is a pure function.\n", + "If we call it like this, only the result is displayed." + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "id": "c734f5b2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7db8cf86", + "metadata": {}, + "source": [ + "And if we assign the result to a variable, nothing is displayed." + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "id": "094a242f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0c3b8829", + "metadata": {}, + "source": [ + "The `print` statements we wrote are useful for debugging, but once the function is working, we can remove them. \n", + "Code like that is called **scaffolding** because it is helpful for building the program but is not part of the final product.\n", + "\n", + "This example demonstrates incremental development.\n", + "The key aspects of this process are:\n", + "\n", + "1. Start with a working program, make small changes, and test after every change.\n", + "\n", + "2. Use variables to hold intermediate values so you can display and check them.\n", + "\n", + "3. Once the program is working, remove the scaffolding.\n", + "\n", + "At any point, if there is an error, you should have a good idea where it is.\n", + "Incremental development can save you a lot of debugging time." + ] + }, + { + "cell_type": "markdown", + "id": "3dd7514f", + "metadata": {}, + "source": [ + "## Boolean functions\n", + "\n", + "Functions can return the boolean values `True` and `False`, which is often convenient for encapsulating a complex test in a function.\n", + "For example, `is_divisible` checks whether `x` is divisible by `y` with no remainder." + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "id": "64207948", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f3a58afb", + "metadata": {}, + "source": [ + "Here's how we use it." + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "id": "c367cdae", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 123, + "id": "837f4f95", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e9103ece", + "metadata": {}, + "source": [ + "Inside the function, the result of the `==` operator is a boolean, so we can write the\n", + "function more concisely by returning it directly." + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "id": "e411354f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4d82dae5", + "metadata": {}, + "source": [ + "Boolean functions are often used in conditional statements." + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "id": "925e7d4f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9e232afc", + "metadata": {}, + "source": [ + "It might be tempting to write something like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "id": "62178e75", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ff9e5160", + "metadata": {}, + "source": [ + "But the comparison is unnecessary." + ] + }, + { + "cell_type": "markdown", + "id": "a932a966", + "metadata": {}, + "source": [ + "## Recursion with return values\n", + "\n", + "Now that we can write functions with return values, we can write recursive functions with return values, and with that capability, we have passed an important threshold -- the subset of Python we have is now **Turing complete**, which means that we can perform any computation that can be described by an algorithm.\n", + "\n", + "To demonstrate recursion with return values, we'll evaluate a few recursively defined mathematical functions.\n", + "A recursive definition is similar to a circular definition, in the sense that the definition refers to the thing being defined. A truly circular definition is not very useful:\n", + "\n", + "> vorpal: An adjective used to describe something that is vorpal.\n", + "\n", + "If you saw that definition in the dictionary, you might be annoyed. \n", + "On the other hand, if you looked up the definition of the factorial function, denoted with the symbol $!$, you might get something like this: \n", + "\n", + "$$\\begin{aligned}\n", + "0! &= 1 \\\\\n", + "n! &= n~(n-1)!\n", + "\\end{aligned}$$ \n", + "\n", + "This definition says that the factorial of $0$ is $1$, and the factorial of any other value, $n$, is $n$ multiplied by the factorial of $n-1$.\n", + "\n", + "If you can write a recursive definition of something, you can write a Python program to evaluate it. \n", + "Following an incremental development process, we'll start with a function that take `n` as a parameter and always returns `0`." + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "id": "23e37c79", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ee1f63b8", + "metadata": {}, + "source": [ + "Now let's add the first part of the definition -- if the argument happens to be `0`, all we have to do is return `1`:" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "id": "5ea57d9f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4f2fd7c7", + "metadata": {}, + "source": [ + "Now let's fill in the second part -- if `n` is not `0`, we have to make a recursive\n", + "call to find the factorial of `n-1` and then multiply the result by `n`:" + ] + }, + { + "cell_type": "code", + "execution_count": 129, + "id": "b66e670b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "da3d1595", + "metadata": {}, + "source": [ + "The flow of execution for this program is similar to the flow of `countdown` in Chapter 5.\n", + "If we call `factorial` with the value `3`:\n", + "\n", + "Since `3` is not `0`, we take the second branch and calculate the factorial\n", + "of `n-1`\\...\n", + "\n", + "> Since `2` is not `0`, we take the second branch and calculate the\n", + "> factorial of `n-1`\\...\n", + ">\n", + "> > Since `1` is not `0`, we take the second branch and calculate the\n", + "> > factorial of `n-1`\\...\n", + "> >\n", + "> > > Since `0` equals `0`, we take the first branch and return `1` without\n", + "> > > making any more recursive calls.\n", + "> >\n", + "> > The return value, `1`, is multiplied by `n`, which is `1`, and the\n", + "> > result is returned.\n", + ">\n", + "> The return value, `1`, is multiplied by `n`, which is `2`, and the result\n", + "> is returned.\n", + "\n", + "The return value `2` is multiplied by `n`, which is `3`, and the result,\n", + "`6`, becomes the return value of the function call that started the whole\n", + "process.\n", + "\n", + "The following figure shows the stack diagram for this sequence of function calls." + ] + }, + { + "cell_type": "code", + "execution_count": 130, + "id": "455f0457", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 131, + "id": "a75ccd9b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f924c539", + "metadata": {}, + "source": [ + "The return values are shown being passed back up the stack.\n", + "In each frame, the return value is the product of `n` and `recurse`.\n", + "\n", + "In the last frame, the local variable `recurse` does not exist because the branch that creates it does not run." + ] + }, + { + "cell_type": "markdown", + "id": "acea9dc1", + "metadata": {}, + "source": [ + "## Leap of faith\n", + "\n", + "Following the flow of execution is one way to read programs, but it can quickly become overwhelming. An alternative is what I call the \"leap of faith\". When you come to a function call, instead of following the flow of execution, you *assume* that the function works correctly and returns the right result.\n", + "\n", + "In fact, you are already practicing this leap of faith when you use built-in functions.\n", + "When you call `abs` or `math.sqrt`, you don't examine the bodies of those functions -- you just assume that they work.\n", + "\n", + "The same is true when you call one of your own functions. For example, earlier we wrote a function called `is_divisible` that determines whether one number is divisible by another. Once we convince ourselves that this function is correct, we can use it without looking at the body again.\n", + "\n", + "The same is true of recursive programs.\n", + "When you get to the recursive call, instead of following the flow of execution, you should assume that the recursive call works and then ask yourself, \"Assuming that I can compute the factorial of $n-1$, can I compute the factorial of $n$?\"\n", + "The recursive definition of factorial implies that you can, by multiplying by $n$.\n", + "\n", + "Of course, it's a bit strange to assume that the function works correctly when you haven't finished writing it, but that's why it's called a leap of faith!" + ] + }, + { + "cell_type": "markdown", + "id": "ca2a2d76", + "metadata": { + "tags": [] + }, + "source": [ + "## Fibonacci\n", + "\n", + "After `factorial`, the most common example of a recursive function is `fibonacci`, which has the following definition: \n", + "\n", + "$$\\begin{aligned}\n", + "\\mathrm{fibonacci}(0) &= 0 \\\\\n", + "\\mathrm{fibonacci}(1) &= 1 \\\\\n", + "\\mathrm{fibonacci}(n) &= \\mathrm{fibonacci}(n-1) + \\mathrm{fibonacci}(n-2)\n", + "\\end{aligned}$$ \n", + "\n", + "Translated into Python, it looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 132, + "id": "cad75752", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "69d56a0b", + "metadata": {}, + "source": [ + "If you try to follow the flow of execution here, even for small values of $n$, your head explodes.\n", + "But according to the leap of faith, if you assume that the two recursive calls work correctly, you can be confident that the last `return` statement is correct.\n", + "\n", + "As an aside, this way of computing Fibonacci numbers is very inefficient.\n", + "In [Chapter 10](section_memos) I'll explain why and suggest a way to improve it." + ] + }, + { + "cell_type": "markdown", + "id": "26d9706b", + "metadata": {}, + "source": [ + "## Checking types\n", + "\n", + "What happens if we call `factorial` and give it `1.5` as an argument?" + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "id": "5e4b5f1d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0bec7ba4", + "metadata": {}, + "source": [ + "It looks like an infinite recursion. How can that be? The function has a\n", + "base case -- when `n == 0`.\n", + "But if `n` is not an integer, we can *miss* the base case and recurse forever.\n", + "\n", + "In this example, the initial value of `n` is `1.5`.\n", + "In the first recursive call, the value of `n` is `0.5`.\n", + "In the next, it is `-0.5`. \n", + "From there, it gets smaller (more negative), but it will never be `0`.\n", + "\n", + "To avoid infinite recursion we can use the built-in function `isinstance` to check the type of the argument.\n", + "Here's how we check whether a value is an integer." + ] + }, + { + "cell_type": "code", + "execution_count": 134, + "id": "3f607dff", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 135, + "id": "ab638bfe", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b0017b42", + "metadata": {}, + "source": [ + "Now here's a version of `factorial` with error-checking." + ] + }, + { + "cell_type": "code", + "execution_count": 136, + "id": "73aafac0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0561e3f5", + "metadata": {}, + "source": [ + "First it checks whether `n` is an integer.\n", + "If not, it displays an error message and returns `None`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "id": "be881cb7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "10b00a39", + "metadata": {}, + "source": [ + "Then it checks whether `n` is negative.\n", + "If so, it displays an error message and returns `None.`" + ] + }, + { + "cell_type": "code", + "execution_count": 138, + "id": "fa83014f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "96aa1403", + "metadata": {}, + "source": [ + "If we get past both checks, we know that `n` is a non-negative integer, so we can be confident the recursion will terminate.\n", + "Checking the parameters of a function to make sure they have the correct types and values is called **input validation**." + ] + }, + { + "cell_type": "markdown", + "id": "eb8a85a7", + "metadata": { + "tags": [] + }, + "source": [ + "## Debugging\n", + "\n", + "Breaking a large program into smaller functions creates natural checkpoints for debugging.\n", + "If a function is not working, there are three possibilities to consider:\n", + "\n", + "- There is something wrong with the arguments the function is getting -- that is, a precondition is violated.\n", + "\n", + "- There is something wrong with the function -- that is, a postcondition is violated.\n", + "\n", + "- The caller is doing something wrong with the return value.\n", + "\n", + "To rule out the first possibility, you can add a `print` statement at the beginning of the function that displays the values of the parameters (and maybe their types).\n", + "Or you can write code that checks the preconditions explicitly.\n", + "\n", + "If the parameters look good, you can add a `print` statement before each `return` statement and display the return value.\n", + "If possible, call the function with arguments that make it easy check the result. \n", + "\n", + "If the function seems to be working, look at the function call to make sure the return value is being used correctly -- or used at all!\n", + "\n", + "Adding `print` statements at the beginning and end of a function can help make the flow of execution more visible.\n", + "For example, here is a version of `factorial` with print statements:" + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "id": "1d50479e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0c044111", + "metadata": {}, + "source": [ + "`space` is a string of space characters that controls the indentation of\n", + "the output. Here is the result of `factorial(3)` :" + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "id": "798db5c4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "43b3e408", + "metadata": {}, + "source": [ + "If you are confused about the flow of execution, this kind of output can be helpful.\n", + "It takes some time to develop effective scaffolding, but a little bit of scaffolding can save a lot of debugging." + ] + }, + { + "cell_type": "markdown", + "id": "b7c3962f", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**return value:**\n", + "The result of a function. If a function call is used as an expression, the return value is the value of the expression.\n", + "\n", + "**pure function:**\n", + "A function that does not display anything or have any other effect, other than returning a return value.\n", + "\n", + "\n", + "**dead code:**\n", + "Part of a program that can never run, often because it appears after a `return` statement.\n", + "\n", + "**incremental development:**\n", + "A program development plan intended to avoid debugging by adding and testing only a small amount of code at a time.\n", + "\n", + "**scaffolding:**\n", + " Code that is used during program development but is not part of the final version.\n", + "\n", + "**Turing complete:**\n", + "A language, or subset of a language, is Turing complete if it can perform any computation that can be described by an algorithm.\n", + "\n", + "**input validation:**\n", + "Checking the parameters of a function to make sure they have the correct types and values" + ] + }, + { + "cell_type": "markdown", + "id": "ff7b1edf", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "id": "e0f15ca4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0da2daaf", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "In this chapter, we saw an incorrect function that can end without returning a value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90b4979f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "69563d4b", + "metadata": {}, + "source": [ + "And a version of the same function that has dead code at the end." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9217f038", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9fe8ae2e", + "metadata": {}, + "source": [ + "And we saw the following example, which is correct but not idiomatic." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3168489b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "14f52688", + "metadata": {}, + "source": [ + "Ask a virtual assistant what's wrong with each of these functions and see if it can spot the errors or improve the style.\n", + "\n", + "Then ask \"Write a function that takes coordinates of two points and computes the distance between them.\" See if the result resembles the version of `distance` we wrote in this chapter." + ] + }, + { + "cell_type": "markdown", + "id": "fd23bb60", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Use incremental development to write a function called `hypot` that returns the length of the hypotenuse of a right triangle given the lengths of the other two legs as arguments.\n", + "\n", + "Note: There's a function in the math module called `hypot` that does the same thing, but you should not use it for this exercise!\n", + "\n", + "Even if you can write the function correctly on the first try, start with a function that always returns `0` and practice making small changes, testing as you go.\n", + "When you are done, the function should only return a value -- it should not display anything." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62267fa3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f8fa829", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d129b03", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "030179b6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d737b468", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77a74879", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0521d267", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "468a31e9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abbe3ebf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "651295e4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0a66d82a", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a boolean function, `is_between(x, y, z)`, that returns `True` if $x < y < z$ or if \n", + "$z < y < x$, and`False` otherwise." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a4ee482", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c12f318d", + "metadata": { + "tags": [] + }, + "source": [ + "You can use these examples to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "956ed6d7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a994eaa6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4318028d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05208c8b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "57f06466", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "The Ackermann function, $A(m, n)$, is defined:\n", + "\n", + "$$\\begin{aligned}\n", + "A(m, n) = \\begin{cases} \n", + " n+1 & \\mbox{if } m = 0 \\\\ \n", + " A(m-1, 1) & \\mbox{if } m > 0 \\mbox{ and } n = 0 \\\\ \n", + "A(m-1, A(m, n-1)) & \\mbox{if } m > 0 \\mbox{ and } n > 0.\n", + "\\end{cases} \n", + "\\end{aligned}$$ \n", + "\n", + "Write a function named `ackermann` that evaluates the Ackermann function.\n", + "What happens if you call `ackermann(5, 5)`?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7eb85c5c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "85f7f614", + "metadata": { + "tags": [] + }, + "source": [ + "You can use these examples to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "687a3e5a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c49e9749", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8497dec4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4994ceae", + "metadata": { + "tags": [] + }, + "source": [ + "If you call this function with values bigger than 4, you get a `RecursionError`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76be4d15", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b8af1586", + "metadata": { + "tags": [] + }, + "source": [ + "To see why, add a print statement to the beginning of the function to display the values of the parameters, and then run the examples again." + ] + }, + { + "cell_type": "markdown", + "id": "7c2ac0a4", + "metadata": { + "tags": [] + }, + "source": [ + "### Exercise\n", + "\n", + "A number, $a$, is a power of $b$ if it is divisible by $b$ and $a/b$ is\n", + "a power of $b$. Write a function called `is_power` that takes parameters\n", + "`a` and `b` and returns `True` if `a` is a power of `b`. Note: you will\n", + "have to think about the base case." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bcba5fe", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e93a0715", + "metadata": { + "tags": [] + }, + "source": [ + "You can use these examples to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b6656e6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36d9e92a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d944b42", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63ec57c9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a33bbd07", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "The greatest common divisor (GCD) of $a$ and $b$ is the largest number\n", + "that divides both of them with no remainder.\n", + "\n", + "One way to find the GCD of two numbers is based on the observation that\n", + "if $r$ is the remainder when $a$ is divided by $b$, then $gcd(a,\n", + "b) = gcd(b, r)$. As a base case, we can use $gcd(a, 0) = a$.\n", + "\n", + "Write a function called `gcd` that takes parameters `a` and `b` and\n", + "returns their greatest common divisor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e067bfb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "efbebde9", + "metadata": { + "tags": [] + }, + "source": [ + "You can use these examples to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a7c1c21", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5df00229", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8bec38b3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap07.ipynb b/blank/chap07.ipynb new file mode 100644 index 0000000..22ea459 --- /dev/null +++ b/blank/chap07.ipynb @@ -0,0 +1,1349 @@ +{ + "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": "f0c8eb18", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a81d9ad2-9a47-4872-bf65-5a8dfb09c32f", + "metadata": { + "tags": [] + }, + "source": [ + "# Iteration and Search\n", + "\n", + "In 1939 Ernest Vincent Wright published a 50,000 word novel called *Gadsby* that does not contain the letter \"e\". Since \"e\" is the most common letter in English, writing even a few words without using it is difficult.\n", + "To get a sense of how difficult, in this chapter we'll compute the fraction of English words have at least one \"e\".\n", + "\n", + "For that, we'll use `for` statements to loop through the letters in a string and the words in a file, and we'll update variables in a loop to count the number of words that contain an \"e\".\n", + "We'll use the `in` operator to check whether a letter appears in a word, and you'll learn a programming pattern called a \"linear search\".\n", + "\n", + "As an exercise, you'll use these tools to solve a word puzzle called \"Spelling Bee\"." + ] + }, + { + "cell_type": "markdown", + "id": "389f5162-0a8c-4cc7-99f1-a261e0b39006", + "metadata": {}, + "source": [ + "## Loops and strings\n", + "\n", + "In Chapter 3 we saw a `for` loop that uses the `range` function to display a sequence of numbers." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6b8569b8-1576-45d2-99f6-c7a2c7e100c4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "937a0af9-5486-4195-8e59-6f819db1cf3f", + "metadata": {}, + "source": [ + "This version uses the keyword argument `end` so the `print` function puts a space after each number rather than a newline.\n", + "\n", + "We can also use a `for` loop to display the letters in a string." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6cb5b573-601c-42f5-a940-f1a4d244f990", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "72044436-ed85-407b-8821-9696648ec29c", + "metadata": {}, + "source": [ + "Notice that I changed the name of the variable from `i` to `letter`, which provides more information about the value it refers to.\n", + "The variable defined in a `for` loop is called the **loop variable**.\n", + "\n", + "Now that we can loop through the letters in a word, we can check whether it contains the letter \"e\"." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7040a890-6619-4ad2-b0bf-b094a5a0e43d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "644b7df2-4528-48d9-a003-2d21fbefbaab", + "metadata": {}, + "source": [ + "Before we go on, let's encapsulate that loop in a function." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "40fd553c-693c-4ffc-8e49-1d7de3c4d4a7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "31cfacd9-5721-457a-be40-80cf002723b2", + "metadata": {}, + "source": [ + "And let's make it a pure function that return `True` if the word contains an \"e\" and `False` otherwise." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8a34174b-5a77-482a-8480-14cfdff16339", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8f45502e-6db3-4fcc-802c-c97398787dbd", + "metadata": {}, + "source": [ + "We can generalize it to take the word as a parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0909121d-5218-49ca-b03e-258206f00e40", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "590f4399-16d3-4f1c-93bc-b1fe7ce46633", + "metadata": {}, + "source": [ + "Now we can test it like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3c4d8138-ddf6-46fe-a940-a7042617ceb1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "5c463051-b737-49ab-a1d7-4be66fd8331f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8adf1e92-435e-4b54-837a-bad603ebcafd", + "metadata": {}, + "source": [ + "## Reading the word list\n", + "\n", + "To see how many words contain an \"e\", we'll need a word list.\n", + "The one we'll use is a list of about 114,000 official crosswords; that is, words that are considered valid in crossword puzzles and other word games. " + ] + }, + { + "cell_type": "markdown", + "id": "0e3d1c79-5436-42ac-9e29-82fc59936263", + "metadata": { + "tags": [] + }, + "source": [ + "The following cell downloads the word list, which is a modified version of a list collected and contributed to the public domain by Grady Ward as part of the Moby lexicon project (see )." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "a7b7ac52-64a6-4dd9-98f5-b772a5e0f161", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5b383a3c-d173-4a66-bf86-23b329032004", + "metadata": {}, + "source": [ + "The word list is in a file called `words.txt`, which is downloaded in the notebook for this chapter.\n", + "To read it, we'll use the built-in function `open`, which takes the name of the file as a parameter and returns a **file object** we can use to read the file." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1ad13ce7-99be-4412-8e0b-978fe6de25f2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4414d9b1-cb8e-472e-818c-0555e29b1ad5", + "metadata": {}, + "source": [ + "The file object provides a function called `readline`, which reads characters from the file until it gets to a newline and returns the result as a string:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "dc2054e6-d8e8-4a06-a1ea-5cfcf4ccf1e0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d74e9fe9-117e-436a-ade3-3d08dfda2a00", + "metadata": {}, + "source": [ + "Notice that the syntax for calling `readline` is different from functions we've seen so far. That's because it is a **method**, which is a function associated with an object.\n", + "In this case `readline` is associated with the file object, so we call it using the name of the object, the dot operator, and the name of the method.\n", + "\n", + "The first word in the list is \"aa\", which is a kind of lava.\n", + "The sequence `\\n` represents the newline character that separates this word from the next.\n", + "\n", + "The file object keeps track of where it is in the file, so if you call\n", + "`readline` again, you get the next word:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "eaea1520-0fb3-4ef3-be6e-9e1cdcccf39f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cd466fdb-38ce-4e5b-92c7-05dc23922005", + "metadata": {}, + "source": [ + "To remove the newline from the end of the word, we can use `strip`, which is a method associated with strings, so we can call it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "f602bfb6-7a93-4fb8-ade6-6784155a6f1a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6dda6bac-e02e-47ee-9bab-70cef8c27d7a", + "metadata": {}, + "source": [ + "`strip` removes whitespace characters -- including spaces, tabs, and newlines -- from the beginning and end of the string.\n", + "\n", + "You can also use a file object as part of a `for` loop. \n", + "This program reads `words.txt` and prints each word, one per line:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "cf3b8b7e-5fc7-4bb1-b628-09277bdc5a0d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "93e320a5-4827-487e-a8ce-216392f398a8", + "metadata": {}, + "source": [ + "Now that we can read the word list, the next step is to count them.\n", + "For that, we will need the ability to update variables." + ] + }, + { + "cell_type": "markdown", + "id": "b63a6877", + "metadata": {}, + "source": [ + "## Updating variables\n", + "\n", + "As you may have discovered, it is legal to make more than one assignment\n", + "to the same variable.\n", + "A new assignment makes an existing variable refer to a new value (and stop referring to the old value).\n", + "\n", + "For example, here is an initial assignment that creates a variable." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6bf8a104", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c9735982", + "metadata": {}, + "source": [ + "And here is an assignment that changes the value of a variable." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "0fe7ae60", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fbcd1092-ce06-47fc-9204-fce1c9a100a5", + "metadata": {}, + "source": [ + "The following figure shows what these assignments looks like in a state diagram." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8a09bc24", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "36a45674-7f41-4850-98f1-2548574ce958", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "42b7b044-f83b-4483-9a24-64980c688c94", + "metadata": {}, + "source": [ + "The dotted arrow indicates that `x` no longer refers to `5`.\n", + "The solid arrow indicates that it now refers to `7`.\n", + "\n", + "A common kind of assignment is an **update**, where the new value of\n", + "the variable depends on the old." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "ba2ab90b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "88496dc4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d3025706", + "metadata": {}, + "source": [ + "This statement means \"get the current value of `x`, add one, and assign the result back to `x`.\"\n", + "\n", + "If you try to update a variable that doesn't exist, you get an error, because Python evaluates the expression on the right before it assigns a value to the variable on the left." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "4a0c46b9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "03d3959f", + "metadata": {}, + "source": [ + "Before you can update a variable, you have to **initialize** it, usually\n", + "with a simple assignment:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "2220d826", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "374fb3d5", + "metadata": {}, + "source": [ + "Increasing the value of a variable is called an **increment**; decreasing the value is called a **decrement**.\n", + "Because these operations are so common, Python provides **augmented assignment operators** that update a variable more concisely.\n", + "For example, the `+=` operator increments a variable by the given amount." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d8e1ac5a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3f4eedf1", + "metadata": {}, + "source": [ + "There are augmented assignment operators for the other arithmetic operators, including `-=` and `*=`." + ] + }, + { + "cell_type": "markdown", + "id": "70eeef60-6a34-403a-96bb-aa5c4574a5fe", + "metadata": {}, + "source": [ + "## Looping and counting\n", + "\n", + "The following program counts the number of words in the word list." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "0afd8f88", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9bd83ddd", + "metadata": {}, + "source": [ + "It starts by initializing `total` to `0`.\n", + "Each time through the loop, it increments `total` by `1`.\n", + "So when the loop exits, `total` refers to the total number of words." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "8686b2eb-c610-4d29-a942-2ef8f53e5e36", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "54904394", + "metadata": {}, + "source": [ + "A variable like this, used to count the number of times something happens, is called a **counter**.\n", + "\n", + "We can add a second counter to the program to keep track of the number of words that contain an \"e\"." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "89a05280", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ab73c1e3", + "metadata": {}, + "source": [ + "Let's see how many words contain an \"e\"." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "9d29b5e9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d2262e64", + "metadata": {}, + "source": [ + "As a percentage of `total`, about two-thirds of the words use the letter \"e\"." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "304dfd86", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fe002dde", + "metadata": {}, + "source": [ + "So you can understand why it's difficult to craft a book without using any such words." + ] + }, + { + "cell_type": "markdown", + "id": "632a992f", + "metadata": {}, + "source": [ + "## The in operator\n", + "\n", + "The version of `has_e` we wrote in this chapter is more complicated than it needs to be.\n", + "Python provides an operator, `in`, that checks whether a character appears in a string." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "fe6431b7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ede36fe9", + "metadata": {}, + "source": [ + "So we can rewrite `has_e` like this." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "85d3fba6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f86f6fc7", + "metadata": {}, + "source": [ + "And because the conditional of the `if` statement has a boolean value, we can eliminate the `if` statement and return the boolean directly." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "2d653847", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f2a05319", + "metadata": {}, + "source": [ + "We can simplify this function even more using the method `lower`, which converts the letters in a string to lowercase.\n", + "Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "a92a81bc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "57aa625a", + "metadata": {}, + "source": [ + "`lower` makes a new string -- it does not modify the existing string -- so the value of `word` is unchanged. " + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "d15f83a4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9f0bd075", + "metadata": {}, + "source": [ + "Here's how we can use `lower` in `has_e`." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "e7958af4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "020a57a7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "0b979b20", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1c39cb6b", + "metadata": {}, + "source": [ + "## Search\n", + "\n", + "Based on this simpler version of `has_e`, let's write a more general function called `uses_any` that takes a second parameter that is a string of letters.\n", + "If returns `True` if the word uses any of the letters and `False` otherwise." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "bd29ff63", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dc2d6290", + "metadata": {}, + "source": [ + "Here's an example where the result is `True`." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "9369fb05", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2c3c1553", + "metadata": {}, + "source": [ + "And another where it is `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "eb32713a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b2acc611", + "metadata": {}, + "source": [ + "`uses_any` converts `word` and `letters` to lowercase, so it works with any combination of cases. " + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "7e65a9fb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "673786a5", + "metadata": {}, + "source": [ + "The structure of `uses_any` is similar to `has_e`.\n", + "It loops through the letters in `word` and checks them one at a time.\n", + "If it finds one that appears in `letters`, it returns `True` immediately.\n", + "If it gets all the way through the loop without finding any, it returns `False`.\n", + "\n", + "This pattern is called a **linear search**.\n", + "In the exercises at the end of this chapter, you'll write more functions that use this pattern." + ] + }, + { + "cell_type": "markdown", + "id": "62cdb3fc", + "metadata": {}, + "source": [ + "## Doctest\n", + "\n", + "In [Chapter 4](section_docstring) we used a docstring to document a function -- that is, to explain what it does.\n", + "It is also possible to use a docstring to *test* a function.\n", + "Here's a version of `uses_any` with a docstring that includes tests." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "3982e7d3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2871d018", + "metadata": {}, + "source": [ + "Each test begins with `>>>`, which is used as a prompt in some Python environments to indicate where the user can type code.\n", + "In a doctest, the prompt is followed by an expression, usually a function call.\n", + "The following line indicates the value the expression should have if the function works correctly.\n", + "\n", + "In the first example, `'banana'` uses `'a'`, so the result should be `True`.\n", + "In the second example, `'apple'` does not use any of `'xyz'`, so the result should be `False`.\n", + "\n", + "To run these tests, we have to import the `doctest` module and run a function called `run_docstring_examples`.\n", + "To make this function easier to use, I wrote the following function, which takes a function object as an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "40ef00d3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "79e3de21", + "metadata": {}, + "source": [ + "We haven't learned about `globals` and `__name__` yet -- you can ignore them.\n", + "Now we can test `uses_any` like this." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "f37cfd36", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "432d8c31", + "metadata": {}, + "source": [ + "`run_doctests` finds the expressions in the docstring and evaluates them.\n", + "If the result is the expected value, the test **passes**.\n", + "Otherwise it **fails**.\n", + "\n", + "If all tests pass, `run_doctests` displays no output -- in that case, no news is good news.\n", + "To see what happens when a test fails, here's an incorrect version of `uses_any`." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "58c916cc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "34b78be4", + "metadata": {}, + "source": [ + "And here's what happens when we test it." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "7a325745", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "473aa6ec", + "metadata": {}, + "source": [ + "The output includes the example that failed, the value the function was expected to produce, and the value the function actually produced.\n", + "\n", + "If you are not sure why this test failed, you'll have a chance to debug it as an exercise." + ] + }, + { + "cell_type": "markdown", + "id": "382c134e", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**loop variable:**\n", + "A variable defined in the header of a `for` loop.\n", + "\n", + "**file object:**\n", + "An object that represents an open file and keeps track of which parts of the file have been read or written.\n", + "\n", + "**method:**\n", + " A function that is associated with an object and called using the dot operator.\n", + "\n", + "**update:**\n", + "An assignment statement that give a new value to a variable that already exists, rather than creating a new variables.\n", + "\n", + "**initialize:**\n", + "Create a new variable and give it a value.\n", + "\n", + "**increment:**\n", + "Increase the value of a variable.\n", + "\n", + "**decrement:**\n", + "Decrease the value of a variable.\n", + "\n", + "**counter:**\n", + " A variable used to count something, usually initialized to zero and then incremented.\n", + "\n", + "**linear search:**\n", + "A computational pattern that searches through a sequence of elements and stops what it finds what it is looking for.\n", + "\n", + "**pass:**\n", + "If a test runs and the result is as expected, the test passes.\n", + "\n", + "**fail:**\n", + "If a test runs and the result is not as expected, the test fails." + ] + }, + { + "cell_type": "markdown", + "id": "0a2b3510-e8d3-439b-a771-a4a58db6ac59", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "bc58db59", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8e8606b8-9a48-4cbd-a0b0-ea848666c77d", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "In `uses_any`, you might have noticed that the first `return` statement is inside the loop and the second is outside." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "6b3cdf6a-0e90-4a98-b0f1-ab95dd195ca7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e1920737-b485-4823-ac20-c1e35aa93e7f", + "metadata": {}, + "source": [ + "When people first write functions like this, it is a common error to put both `return` statements inside the loop, like this." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "7cbb72b1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d9b46591-6c80-4ff8-9378-e9318ce5e429", + "metadata": {}, + "source": [ + "Ask a virtual assistant what's wrong with this version." + ] + }, + { + "cell_type": "markdown", + "id": "99eff99e", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function named `uses_none` that takes a word and a string of forbidden letters, and returns `True` if the word does not use any of the forbidden letters.\n", + "\n", + "Here's an outline of the function that includes two doctests.\n", + "Fill in the function so it passes these tests, and add at least one more doctest." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "6c825b80", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "86a6c2c8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "2bed91e7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9465b09f-0c62-49f6-bbe2-365ecf1717ef", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `uses_only` that takes a word and a string of letters, and that returns `True` if the word contains only letters in the string.\n", + "\n", + "Here's an outline of the function that includes two doctests.\n", + "Fill in the function so it passes these tests, and add at least one more doctest." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "d0d8c6d6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "31de091e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "8c5133d4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "74259f36", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `uses_all` that takes a word and a string of letters, and that returns `True` if the word contains all of the letters in the string at least once.\n", + "\n", + "Here's an outline of the function that includes two doctests.\n", + "Fill in the function so it passes these tests, and add at least one more doctest." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "18b73bc0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "5c8be876", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "ad1fd6b9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7210adfa", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "*The New York Times* publishes a daily puzzle called \"Spelling Bee\" that challenges readers to spell as many words as possible using only seven letters, where one of the letters is required.\n", + "The words must have at least four letters.\n", + "\n", + "For example, on the day I wrote this, the letters were `ACDLORT`, with `R` as the required letter.\n", + "So \"color\" is an acceptable word, but \"told\" is not, because it does not use `R`, and \"rat\" is not because it has only three letters.\n", + "Letters can be repeated, so \"ratatat\" is acceptable.\n", + "\n", + "Write a function called `check_word` that checks whether a given word is acceptable.\n", + "It should take as parameters the word to check, a string of seven available letters, and a string containing the single required letter.\n", + "You can use the functions you wrote in previous exercises.\n", + "\n", + "Here's an outline of the function that includes doctests.\n", + "Fill in the function and then check that all tests pass." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "576ee509", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "a4d623b7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "23ed7f79", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0b9589fc", + "metadata": {}, + "source": [ + "According to the \"Spelling Bee\" rules,\n", + "\n", + "* Four-letter words are worth 1 point each.\n", + "\n", + "* Longer words earn 1 point per letter.\n", + "\n", + "* Each puzzle includes at least one \"pangram\" which uses every letter. These are worth 7 extra points!\n", + "\n", + "Write a function called `score_word` that takes a word and a string of available letters and returns its score.\n", + "You can assume that the word is acceptable.\n", + "\n", + "Again, here's an outline of the function with doctests." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "11b69de0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "eff4ac37", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "eb8e8745", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "82e5283b", + "metadata": { + "tags": [] + }, + "source": [ + "When all of your functions pass their tests, use the following loop to search the word list for acceptable words and add up their scores." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "6965f673", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dcc7d983", + "metadata": { + "tags": [] + }, + "source": [ + "Visit the \"Spelling Bee\" page at and type in the available letters for the day. The letter in the middle is required.\n", + "\n", + "I found a set of letters that spells words with a total score of 5820. Can you beat that? Finding the best set of letters might be too hard -- you have to be a realist." + ] + }, + { + "cell_type": "markdown", + "id": "9ae466ed", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "You might have noticed that the functions you wrote in the previous exercises had a lot in common.\n", + "In fact, they are so similar you can often use one function to write another.\n", + "\n", + "For example, if a word uses none of a set forbidden letters, that means it doesn't use any. So we can write a version of `uses_none` like this." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "d3aac2dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "307c07e6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "32aa2c09", + "metadata": {}, + "source": [ + "There is also a similarity between `uses_only` and `uses_all` that you can take advantage of.\n", + "If you have a working version of `uses_only`, see if you can write a version of `uses_all` that calls `uses_only`." + ] + }, + { + "cell_type": "markdown", + "id": "fa758462", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "If you got stuck on the previous question, try asking a virtual assistant, \"Given a function, `uses_only`, which takes two strings and checks that the first uses only the letters in the second, use it to write `uses_all`, which takes two strings and checks whether the first uses all the letters in the second, allowing repeats.\"\n", + "\n", + "Use `run_doctests` to check the answer." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "83c9d33c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "ab66c777", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "18f407b3", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Now let's see if we can write `uses_all` based on `uses_any`.\n", + "\n", + "Ask a virtual assistant, \"Given a function, `uses_any`, which takes two strings and checks whether the first uses any of the letters in the second, can you use it to write `uses_all`, which takes two strings and checks whether the first uses all the letters in the second, allowing repeats.\"\n", + "\n", + "If it says it can, be sure to test the result!" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "bfd6070c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "a3ea747d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "6980de57", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap08.ipynb b/blank/chap08.ipynb new file mode 100644 index 0000000..00a2a91 --- /dev/null +++ b/blank/chap08.ipynb @@ -0,0 +1,1655 @@ +{ + "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": "361d390a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9d97603b", + "metadata": {}, + "source": [ + "# Strings and Regular Expressions\n", + "\n", + "Strings are not like integers, floats, and booleans. A string is a **sequence**, which means it contains multiple values in a particular order.\n", + "In this chapter we'll see how to access the values that make up a string, and we'll use functions that process strings.\n", + "\n", + "We'll also use regular expressions, which are a powerful tool for finding patterns in a string and performing operations like search and replace.\n", + "\n", + "As an exercise, you'll have a chance to apply these tools to a word game called Wordle." + ] + }, + { + "cell_type": "markdown", + "id": "1280dd83", + "metadata": {}, + "source": [ + "## A string is a sequence\n", + "\n", + "A string is a sequence of characters. A **character** can be a letter (in almost any alphabet), a digit, a punctuation mark, or white space.\n", + "\n", + "You can select a character from a string with the bracket operator.\n", + "This example statement selects character number 1 from `fruit` and\n", + "assigns it to `letter`:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9b53c1fe", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a307e429", + "metadata": {}, + "source": [ + "The expression in brackets is an **index**, so called because it *indicates* which character in the sequence to select.\n", + "But the result might not be what you expect." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2cb1d58c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "57c13319", + "metadata": {}, + "source": [ + "The letter with index `1` is actually the second letter of the string.\n", + "An index is an offset from the beginning of the string, so the offset of the first letter is `0`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4ce1eb16", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "57d8e54c", + "metadata": {}, + "source": [ + "You can think of `'b'` as the 0th letter of `'banana'` -- pronounced \"zero-eth\".\n", + "\n", + "The index in brackets can be a variable." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "11201ba9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9630e2e7", + "metadata": {}, + "source": [ + "Or an expression that contains variables and operators." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fc4383d0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "939b602d", + "metadata": {}, + "source": [ + "But the value of the index has to be an integer -- otherwise you get a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "aec20975", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3f0f7e3a", + "metadata": {}, + "source": [ + "As we saw in Chapter 1, we can use the built-in function `len` to get the length of a string." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "796ce317", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "29013c47", + "metadata": {}, + "source": [ + "To get the last letter of a string, you might be tempted to write this:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3ccb4a64", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b87e09bd", + "metadata": {}, + "source": [ + "But that causes an `IndexError` because there is no letter in `'banana'` with the index 6. Because we started counting at `0`, the six letters are numbered `0` to `5`. To get the last character, you have to subtract `1` from `n`:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "2cf99de6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3c79dcec", + "metadata": {}, + "source": [ + "But there's an easier way.\n", + "To get the last letter in a string, you can use a negative index, which counts backward from the end. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3dedf6fa", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5677b727", + "metadata": {}, + "source": [ + "The index `-1` selects the last letter, `-2` selects the second to last, and so on." + ] + }, + { + "cell_type": "markdown", + "id": "8392a12a", + "metadata": {}, + "source": [ + "## String slices\n", + "\n", + "A segment of a string is called a **slice**.\n", + "Selecting a slice is similar to selecting a character." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "386b9df2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5cc12531", + "metadata": {}, + "source": [ + "The operator `[n:m]` returns the part of the string from the `n`th\n", + "character to the `m`th character, including the first but excluding the second.\n", + "This behavior is counterintuitive, but it might help to imagine the indices pointing *between* the characters, as in this figure:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "05f9743d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b09d8356", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c391abbf", + "metadata": {}, + "source": [ + "For example, the slice `[3:6]` selects the letters `ana`, which means that `6` is legal as part of a slice, but not legal as an index.\n", + "\n", + "\n", + "If you omit the first index, the slice starts at the beginning of the string." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "00592313", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1bd7dcb1", + "metadata": {}, + "source": [ + "If you omit the second index, the slice goes to the end of the string:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "01684797", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4701123b", + "metadata": {}, + "source": [ + "If the first index is greater than or equal to the second, the result is an **empty string**, represented by two quotation marks:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "c7551ded", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d12735ab", + "metadata": {}, + "source": [ + "An empty string contains no characters and has length 0.\n", + "\n", + "Continuing this example, what do you think `fruit[:]` means? Try it and\n", + "see." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "b5c5ce3e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "918d3dd0", + "metadata": {}, + "source": [ + "## Strings are immutable\n", + "\n", + "It is tempting to use the `[]` operator on the left side of an\n", + "assignment, with the intention of changing a character in a string, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "69ccd380", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "df3dd7d1", + "metadata": {}, + "source": [ + "The result is a `TypeError`.\n", + "In the error message, the \"object\" is the string and the \"item\" is the character\n", + "we tried to assign.\n", + "For now, an **object** is the same thing as a value, but we will refine that definition later.\n", + "\n", + "The reason for this error is that strings are **immutable**, which means you can't change an existing string.\n", + "The best you can do is create a new string that is a variation of the original." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "280d27a1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2848546f", + "metadata": {}, + "source": [ + "This example concatenates a new first letter onto a slice of `greeting`.\n", + "It has no effect on the original string." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "8fa4a4cf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "49e4da57", + "metadata": {}, + "source": [ + "## String comparison\n", + "\n", + "The relational operators work on strings. To see if two strings are\n", + "equal, we can use the `==` operator." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "b754d462", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e9be6097", + "metadata": {}, + "source": [ + "Other relational operations are useful for putting words in alphabetical\n", + "order:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "44374eb8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "a46f7035", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b66f449a", + "metadata": {}, + "source": [ + "Python does not handle uppercase and lowercase letters the same way\n", + "people do. All the uppercase letters come before all the lowercase\n", + "letters, so:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "a691f9e2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f9b916c9", + "metadata": {}, + "source": [ + "To solve this problem, we can convert strings to a standard format, such as all lowercase, before performing the comparison.\n", + "Keep that in mind if you have to defend yourself against a man armed with a Pineapple." + ] + }, + { + "cell_type": "markdown", + "id": "531069f1", + "metadata": {}, + "source": [ + "## String methods\n", + "\n", + "Strings provide methods that perform a variety of useful operations. \n", + "A method is similar to a function -- it takes arguments and returns a value -- but the syntax is different.\n", + "For example, the method `upper` takes a string and returns a new string with all uppercase letters.\n", + "\n", + "Instead of the function syntax `upper(word)`, it uses the method syntax `word.upper()`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "fa6140a6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1ac41744", + "metadata": {}, + "source": [ + "This use of the dot operator specifies the name of the method, `upper`, and the name of the string to apply the method to, `word`.\n", + "The empty parentheses indicate that this method takes no arguments.\n", + "\n", + "A method call is called an **invocation**; in this case, we would say that we are invoking `upper` on `word`." + ] + }, + { + "cell_type": "markdown", + "id": "2a13d4ef", + "metadata": { + "tags": [] + }, + "source": [ + "## Writing files\n", + "\n", + "String operators and methods are useful for reading and writing text files.\n", + "As an example, we'll work with the text of *Dracula*, a novel by Bram Stoker that is available from Project Gutenberg ()." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "e3f1dc18", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "963dda79", + "metadata": {}, + "source": [ + "I've downloaded the book in a plain text file called `pg345.txt`, which we can open for reading like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "bd2d5175", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b5d99e8c", + "metadata": {}, + "source": [ + "In addition to the text of the book, this file contains a section at the beginning with information about the book and a section at the end with information about the license.\n", + "Before we process the text, we can remove this extra material by finding the special lines at the beginning and end that begin with `'***'`.\n", + "\n", + "The following function takes a line and checks whether it is one of the special lines.\n", + "It uses the `startswith` method, which checks whether a string starts with a given sequence of characters." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "b9c9318c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2efdfe35", + "metadata": {}, + "source": [ + "We can use this function to loop through the lines in the file and print only the special lines." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "a9417d4c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "07fb5992", + "metadata": {}, + "source": [ + "Now let's create a new file, called `pg345_cleaned.txt`, that contains only the text of the book.\n", + "In order to loop through the book again, we have to open it again for reading.\n", + "And, to write a new file, we can open it for writing." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "f2336825", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "96d881aa", + "metadata": {}, + "source": [ + "`open` takes an optional parameters that specifies the \"mode\" -- in this example, `'w'` indicates that we're opening the file for writing.\n", + "If the file doesn't exist, it will be created; if it already exists, the contents will be replaced.\n", + "\n", + "As a first step, we'll loop through the file until we find the first special line." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "d1b286ee", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1989d5a1", + "metadata": {}, + "source": [ + "The `break` statement \"breaks\" out of the loop -- that is, it causes the loop to end immediately, before we get to the end of the file.\n", + "\n", + "When the loop exits, `line` contains the special line that made the conditional true." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "b4ecf365", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9f28c3b4", + "metadata": {}, + "source": [ + "Because `reader` keeps track of where it is in the file, we can use a second loop to pick up where we left off.\n", + "\n", + "The following loop reads the rest of the file, one line at a time.\n", + "When it finds the special line that indicates the end of the text, it breaks out of the loop.\n", + "Otherwise, it writes the line to the output file." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "a99dc11c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c07032a4", + "metadata": {}, + "source": [ + "When this loop exits, `line` contains the second special line." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "dfd6b264", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0c30b41c", + "metadata": {}, + "source": [ + "At this point `reader` and `writer` are still open, which means we could keep reading lines from `reader` or writing lines to `writer`.\n", + "To indicate that we're done, we can close both files by invoking the `close` method." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "4eda555c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d5084cdc", + "metadata": {}, + "source": [ + "To check whether this process was successful, we can read the first few lines from the new file we just created." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "5e1e8c74", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "34c93df3", + "metadata": {}, + "source": [ + "The `endswidth` method checks whether a string ends with a given sequence of characters." + ] + }, + { + "cell_type": "markdown", + "id": "fcdb4bbf", + "metadata": {}, + "source": [ + "## Find and replace\n", + "\n", + "In the Icelandic translation of *Dracula* from 1901, the name of one of the characters was changed from \"Jonathan\" to \"Thomas\".\n", + "To make this change in the English version, we can loop through the book, use the `replace` method to replace one name with another, and write the result to a new file.\n", + "\n", + "We'll start by counting the lines in the cleaned version of the file." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "63ebaafb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8ba9b9ca", + "metadata": {}, + "source": [ + "To see whether a line contains \"Jonathan\", we can use the `in` operator, which checks whether this sequence of characters appears anywhere in the line." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "9973e6e8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "27805245", + "metadata": {}, + "source": [ + "There are 199 lines that contain the name, but that's not quite the total number of times it appears, because it can appear more than once in a line.\n", + "To get the total, we can use the `count` method, which returns the number of times a sequence appears in a string." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "02e06ff1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "68026797", + "metadata": {}, + "source": [ + "Now we can replace `'Jonathan'` with `'Thomas'` like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "1450e82c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "57ba56f3", + "metadata": {}, + "source": [ + "The result is a new file called `pg345_replaced.txt` that contains a version of *Dracula* where Jonathan Harker is called Thomas." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "a57b64c6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cc9af187", + "metadata": {}, + "source": [ + "## Regular expressions\n", + "\n", + "If we know exactly what sequence of characters we're looking for, we can use the `in` operator to find it and the `replace` method to replace it.\n", + "But there is another tool, called a **regular expression** that can also perform these operations -- and a lot more.\n", + "\n", + "To demonstrate, I'll start with a simple example and we'll work our way up.\n", + "Suppose, again, that we want to find all lines that contain a particular word.\n", + "For a change, let's look for references to the titular character of the book, Count Dracula.\n", + "Here's a line that mentions him." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "a6069027", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d4fd6d11", + "metadata": {}, + "source": [ + "And here's the **pattern** we'll use to search." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "e3c19abe", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "268f647c", + "metadata": {}, + "source": [ + "A module called `re` provides functions related to regular expressions.\n", + "We can import it like this and use the `search` function to check whether the pattern appears in the text." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "db588abb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e17f6731", + "metadata": {}, + "source": [ + "If the pattern appears in the text, `search` returns a `Match` object that contains the results of the search.\n", + "Among other information, it has a variable named `string` that contains the text that was searched." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "924524a6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a8eab0f6", + "metadata": {}, + "source": [ + "It also provides a method called `group` that returns the part of the text that matched the pattern." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "c72b860c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b6962a7d", + "metadata": {}, + "source": [ + "And it provides a method called `span` that returns the index in the text where the pattern starts and ends." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "7c2f556c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8f1e5261", + "metadata": {}, + "source": [ + "If the pattern doesn't appear in the text, the return value from `search` is `None`." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "d5242ef6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d5ed33ff", + "metadata": {}, + "source": [ + "So we can check whether the search was successful by checking whether the result is `None`." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "18c09b63", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a08e38f6", + "metadata": {}, + "source": [ + "Putting all that together, here's a function that loops through the lines in the book until it finds one that matches the given pattern, and returns the `Match` object." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "fedb7d95", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "96570515", + "metadata": {}, + "source": [ + "We can use it to find the first mention of a character." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "d7cbe2c2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3f687fdc", + "metadata": {}, + "source": [ + "For this example, we didn't have to use regular expressions -- we could have done the same thing more easily with the `in` operator.\n", + "But regular expressions can do things the `in` operator cannot.\n", + "\n", + "For example, if the pattern includes the vertical bar character, `'|'`, it can match either the sequence on the left or the sequence on the right.\n", + "Suppose we want to find the first mention of Mina Murray in the book, but we are not sure whether she is referred to by first name or last.\n", + "We can use the following pattern, which matches either name." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "96c64f83", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8bea66a6", + "metadata": {}, + "source": [ + "We can use a pattern like this to see how many times a character is mentioned by either name.\n", + "Here's a function that loops through the book and counts the number of lines that match the given pattern." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "d0d2e926", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0e753a5b", + "metadata": {}, + "source": [ + "Now let's see how many times Mina is mentioned." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "d7e8c5b4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "780c9fab", + "metadata": {}, + "source": [ + "The special character `'^'` matches the beginning of a string, so we can find a line that starts with a given pattern." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "be63c5b0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "332bad2e", + "metadata": {}, + "source": [ + "And the special character `'$'` matches the end of a string, so we can find a line that ends with a given pattern (ignoring the newline at the end)." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "37595ac5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d4b22b6e", + "metadata": {}, + "source": [ + "## String substitution\n", + "\n", + "Bram Stoker was born in Ireland, and when *Dracula* was published in 1897, he was living in England.\n", + "So we would expect him to use the British spelling of words like \"centre\" and \"colour\".\n", + "To check, we can use the following pattern, which matches either \"centre\" or the American spelling \"center\"." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "18237bea", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "35abfd7d", + "metadata": {}, + "source": [ + "In this pattern, the parentheses enclose the part of the pattern the vertical bar applies to.\n", + "So this pattern matches a sequence that starts with `'cent'` and ends with either `'er'` or `'re'`." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "ce65805f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e5703c18", + "metadata": {}, + "source": [ + "As expected, he used the British spelling.\n", + "\n", + "We can also check whether he used the British spelling of \"colour\".\n", + "The following pattern uses the special character `'?'`, which means that the previous character is optional." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "af770664", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "beed9a7b", + "metadata": {}, + "source": [ + "This pattern matches either \"colour\" with the `'u'` or \"color\" without it." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "ed67bde7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1a31f179", + "metadata": {}, + "source": [ + "Again, as expected, he used the British spelling.\n", + "\n", + "Now suppose we want to produce an edition of the book with American spellings.\n", + "We can use the `sub` function in the `re` module, which does **string substitution**." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "52dd938c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "04a80fc6", + "metadata": {}, + "source": [ + "The first argument is the pattern we want to find and replace, the second is what we want to replace it with, and the third is the string we want to search.\n", + "In the result, you can see that \"colour\" has been replaced with \"color\"." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "d2e309a2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "3d8b2a9f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0318507d", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "When you are reading and writing files, debugging can be tricky.\n", + "If you are working in a Jupyter notebook, you can use **shell commands** to help.\n", + "For example, to display the first few lines of a file, you can use the command `!head`, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "cc39942c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1d480c02", + "metadata": {}, + "source": [ + "The initial exclamation point, `!`, indicates that this is a shell command, which is not part of Python.\n", + "To display the last few lines, you can use `!tail`." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "bc3741e1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6874023a", + "metadata": {}, + "source": [ + "When you are working with large files, debugging can be difficult because there might be too much output to check by hand.\n", + "A good debugging strategy is to start with just part of the file, get the program working, and then run it with the whole file.\n", + "\n", + "To make a small file that contains part of a larger file, we can use `!head` again with the redirect operator, `>`, which indicates that the results should be written to a file rather than displayed." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "d4c92501", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3fc851f8", + "metadata": {}, + "source": [ + "By default, `!head` reads the first 10 lines, but it takes an optional argument that indicates the number of lines to read." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "8f6606dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "24871c78", + "metadata": {}, + "source": [ + "This shell command reads the first 100 lines from `pg345_cleaned.txt` and writes them to a file called `pg345_cleaned_100_lines.txt`.\n", + "\n", + "Note: The shell commands `!head` and `!tail` are not available on all operating systems.\n", + "If they don't work for you, we can write similar functions in Python.\n", + "See the first exercise at the end of this chapter for suggestions." + ] + }, + { + "cell_type": "markdown", + "id": "c842524d", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**sequence:**\n", + " An ordered collection of values where each value is identified by an integer index.\n", + "\n", + "**character:**\n", + "An element of a string, including letters, numbers, and symbols.\n", + "\n", + "**index:**\n", + " An integer value used to select an item in a sequence, such as a character in a string. In Python indices start from `0`.\n", + "\n", + "**slice:**\n", + " A part of a string specified by a range of indices.\n", + "\n", + "**empty string:**\n", + "A string that contains no characters and has length `0`.\n", + "\n", + "**object:**\n", + " Something a variable can refer to. An object has a type and a value.\n", + "\n", + "**immutable:**\n", + "If the elements of an object cannot be changed, the object is immutable.\n", + "\n", + "**invocation:**\n", + " An expression -- or part of an expression -- that calls a method.\n", + "\n", + "**regular expression:**\n", + "A sequence of characters that defines a search pattern.\n", + "\n", + "**pattern:**\n", + "A rule that specifies the requirements a string has to meet to constitute a match.\n", + "\n", + "**string substitution:**\n", + "Replacement of a string, or part of a string, with another string.\n", + "\n", + "**shell command:**\n", + "A statement in a shell language, which is a language used to interact with an operating system." + ] + }, + { + "cell_type": "markdown", + "id": "4306e765", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "18bced21", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "772f5c14", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5be97ddc", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "In this chapter, we only scratched the surface of what regular expressions can do.\n", + "To get an idea of what's possible, ask a virtual assistant, \"What are the most common special characters used in Python regular expressions?\"\n", + "\n", + "You can also ask for a pattern that matches particular kinds of strings.\n", + "For example, try asking:\n", + "\n", + "* Write a Python regular expression that matches a 10-digit phone number with hyphens.\n", + "\n", + "* Write a Python regular expression that matches a street address with a number and a street name, followed by `ST` or `AVE`.\n", + "\n", + "* Write a Python regular expression that matches a full name with any common title like `Mr` or `Mrs` followed by any number of names beginning with capital letters, possibly with hyphens between some names.\n", + "\n", + "And if you want to see something more complicated, try asking for a regular expression that matches any legal URL.\n", + "\n", + "A regular expression often has the letter `r` before the quotation mark, which indicates that it is a \"raw string\".\n", + "For more information, ask a virtual assistant, \"What is a raw string in Python?\"" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "8650dec2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "20dcbbb3", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "See if you can write a function that does the same thing as the shell command `!head`.\n", + "It should take as arguments the name of a file to read, the number of lines to read, and the name of the file to write the lines into.\n", + "If the third parameter is `None`, it should display the lines rather than write them to a file.\n", + "\n", + "Consider asking a virtual assistant for help, but if you do, tell it not to use a `with` statement or a `try` statement." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "75b12538", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "24f6aac3", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following examples to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "9cbc19cd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "19de7df0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "242f7ba6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "adb78357", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "\"Wordle\" is an online word game where the objective is to guess a five-letter word in six or fewer attempts.\n", + "Each attempt has to be recognized as a word, not including proper nouns.\n", + "After each attempt, you get information about which of the letters you guessed appear in the target word, and which ones are in the correct position.\n", + "\n", + "For example, suppose the target word is `MOWER` and you guess `TRIED`.\n", + "You would learn that `E` is in the word and in the correct position, `R` is in the word but not in the correct position, and `T`, `I`, and `D` are not in the word.\n", + "\n", + "As a different example, suppose you have guessed the words `SPADE` and `CLERK`, and you've learned that `E` is in the word, but not in either of those positions, and none of the other letters appear in the word.\n", + "Of the words in the word list, how many could be the target word?\n", + "Write a function called `check_word` that takes a five-letter word and checks whether it could be the target word, given these guesses." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "2a37092e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "87fdf676", + "metadata": {}, + "source": [ + "You can use any of the functions from the previous chapter, like `uses_any`." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "8d19b6ce", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "63593f1b", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following loop to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "9bbf0b1c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d009cb52", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Continuing the previous exercise, suppose you guess the work `TOTEM` and learn that the `E` is *still* not in the right place, but the `M` is. How many words are left?" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "925c7aa9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "3f658f3a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c1d0f892", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "*The Count of Monte Cristo* is a novel by Alexandre Dumas that is considered a classic.\n", + "Nevertheless, in the introduction of an English translation of the book, the writer Umberto Eco confesses that he found the book to be \"one of the most badly written novels of all time\".\n", + "\n", + "In particular, he says it is \"shameless in its repetition of the same adjective,\" and mentions in particular the number of times \"its characters either shudder or turn pale.\"\n", + "\n", + "To see whether his objection is valid, let's count the number number of lines that contain the word `pale` in any form, including `pale`, `pales`, `paled`, and `paleness`, as well as the related word `pallor`. \n", + "Use a single regular expression that matches any of these words.\n", + "As an additional challenge, make sure that it doesn't match any other words, like `impale` -- you might want to ask a virtual assistant for help." + ] + }, + { + "cell_type": "markdown", + "id": "742efd98", + "metadata": { + "tags": [] + }, + "source": [ + "The following cell downloads the book from Project Gutenberg ." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "9a74be13", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "881c2f99", + "metadata": { + "tags": [] + }, + "source": [ + "The following cell runs a function that reads the file from Project Gutenberg and writes a file that contains only the text of the book, not the added information about the book." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "946c63d2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "08294921", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "3eb8f83f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "b6bffe8a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7db56337", + "metadata": { + "tags": [] + }, + "source": [ + "By this count, these words appear on `223` lines of the book, so Mr. Eco might have a point." + ] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap09.ipynb b/blank/chap09.ipynb new file mode 100644 index 0000000..c4cae84 --- /dev/null +++ b/blank/chap09.ipynb @@ -0,0 +1,1676 @@ +{ + "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": "bde1c3f9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3c25ca7e", + "metadata": {}, + "source": [ + "# Lists\n", + "\n", + "This chapter presents one of Python's most useful built-in types, lists.\n", + "You will also learn more about objects and what can happen when multiple variables refer to the same object.\n", + "\n", + "In the exercises at the end of the chapter, we'll make a word list and use it to search for special words like palindromes and anagrams." + ] + }, + { + "cell_type": "markdown", + "id": "4d32b3e2", + "metadata": {}, + "source": [ + "## A list is a sequence\n", + "\n", + "Like a string, a **list** is a sequence of values. In a string, the\n", + "values are characters; in a list, they can be any type.\n", + "The values in a list are called **elements**.\n", + "\n", + "There are several ways to create a new list; the simplest is to enclose the elements in square brackets (`[` and `]`).\n", + "For example, here is a list of two integers. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a16a119b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b5d6112c", + "metadata": {}, + "source": [ + "And here's a list of three strings." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ac7a4a0b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dda58c67", + "metadata": {}, + "source": [ + "The elements of a list don't have to be the same type.\n", + "The following list contains a string, a float, an integer, and even another list." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "18fb0e21", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "147fa217", + "metadata": {}, + "source": [ + "A list within another list is **nested**.\n", + "\n", + "A list that contains no elements is called an empty list; you can create\n", + "one with empty brackets, `[]`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0ff58916", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f95381bc", + "metadata": {}, + "source": [ + "The `len` function returns the length of a list." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f3153f36", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "371403a3", + "metadata": {}, + "source": [ + "The length of an empty list is `0`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "58727d35", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d3589a5d", + "metadata": {}, + "source": [ + "The following figure shows the state diagram for `cheeses`, `numbers` and `empty`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "25582cad", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "925c7d67", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "503f25d8", + "metadata": {}, + "source": [ + "Lists are represented by boxes with the word \"list\" outside and the numbered elements of the list inside." + ] + }, + { + "cell_type": "markdown", + "id": "e0b8ff01", + "metadata": {}, + "source": [ + "## Lists are mutable\n", + "\n", + "To read an element of a list, we can use the bracket operator.\n", + "The index of the first element is `0`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9deb85a3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9747e951", + "metadata": {}, + "source": [ + "Unlike strings, lists are mutable. When the bracket operator appears on\n", + "the left side of an assignment, it identifies the element of the list\n", + "that will be assigned." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "98ec5d9c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5097a517", + "metadata": {}, + "source": [ + "The second element of `numbers`, which used to be `123`, is now `17`.\n", + "\n", + "List indices work the same way as string indices:\n", + "\n", + "- Any integer expression can be used as an index.\n", + "\n", + "- If you try to read or write an element that does not exist, you get\n", + " an `IndexError`.\n", + "\n", + "- If an index has a negative value, it counts backward from the end of\n", + " the list.\n", + "\n", + "The `in` operator works on lists -- it checks whether a given element appears anywhere in the list." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "000aed26", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "bcb8929c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "89d01ebf", + "metadata": {}, + "source": [ + "Although a list can contain another list, the nested list still counts as a single element -- so in the following list, there are only four elements." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "5ad51a26", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4e0ea41d", + "metadata": {}, + "source": [ + "And `10` is not considered to be an element of `t` because it is an element of a nested list, not `t`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "156dbc10", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1ee7a4d9", + "metadata": {}, + "source": [ + "## List slices\n", + "\n", + "The slice operator works on lists the same way it works on strings.\n", + "The following example selects the second and third elements from a list of four letters." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "70b16371", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bc59d952", + "metadata": {}, + "source": [ + "If you omit the first index, the slice starts at the beginning. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "e67bab33", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1aaaae86", + "metadata": {}, + "source": [ + "If you omit the second, the slice goes to the end. " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "a310f506", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "67ad02e8", + "metadata": {}, + "source": [ + "So if you omit both, the slice is a copy of the whole list." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "1385a75e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9232c1ef", + "metadata": {}, + "source": [ + "Another way to copy a list is to use the `list` function." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "a0ca0135", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "50e4b182", + "metadata": {}, + "source": [ + "Because `list` is the name of a built-in function, you should avoid using it as a variable name.\n" + ] + }, + { + "cell_type": "markdown", + "id": "1b057c0c", + "metadata": {}, + "source": [ + "## List operations\n", + "\n", + "The `+` operator concatenates lists." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "66804de0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "474a5c40", + "metadata": {}, + "source": [ + "The `*` operator repeats a list a given number of times." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "96620f93", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5b33bc51", + "metadata": {}, + "source": [ + "No other mathematical operators work with lists, but the built-in function `sum` adds up the elements." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "0808ed08", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f216a14d", + "metadata": {}, + "source": [ + "And `min` and `max` find the smallest and largest elements." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7ed7e53d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "dda02e4e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "533a2009", + "metadata": {}, + "source": [ + "## List methods\n", + "\n", + "Python provides methods that operate on lists. For example, `append`\n", + "adds a new element to the end of a list:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "bcf04ef9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ccc57f77", + "metadata": {}, + "source": [ + "`extend` takes a list as an argument and appends all of the elements:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "be55916d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0f39d9f6", + "metadata": {}, + "source": [ + "There are two methods that remove elements from a list.\n", + "If you know the index of the element you want, you can use `pop`." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "b22da905", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6729415a", + "metadata": {}, + "source": [ + "The return value is the element that was removed.\n", + "And we can confirm that the list has been modified." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "01bdff91", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1e97ee7d", + "metadata": {}, + "source": [ + "If you know the element you want to remove (but not the index), you can use `remove`:" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "babe366e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "60e710fe", + "metadata": {}, + "source": [ + "The return value from `remove` is `None`.\n", + "But we can confirm that the list has been modified." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "f80f5b1d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2a9448a8", + "metadata": {}, + "source": [ + "If the element you ask for is not in the list, that's a ValueError." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "861f8e7e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "18305f96", + "metadata": {}, + "source": [ + "## Lists and strings\n", + "\n", + "A string is a sequence of characters and a list is a sequence of values,\n", + "but a list of characters is not the same as a string. \n", + "To convert from a string to a list of characters, you can use the `list` function." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "1b50bc13", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0291ef69", + "metadata": {}, + "source": [ + "The `list` function breaks a string into individual letters.\n", + "If you want to break a string into words, you can use the `split` method:" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "c28e5127", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0e16909d", + "metadata": {}, + "source": [ + "An optional argument called a **delimiter** specifies which characters to use as word boundaries. The following example uses a hyphen as a delimiter." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "ec6ea206", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7c61f916", + "metadata": {}, + "source": [ + "If you have a list of strings, you can concatenate them into a single string using `join`.\n", + "`join` is a string method, so you have to invoke it on the delimiter and pass the list as an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "75c74d3c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bedd842b", + "metadata": {}, + "source": [ + "In this case the delimiter is a space character, so `join` puts a space\n", + "between words.\n", + "To join strings without spaces, you can use the empty string, `''`, as a delimiter." + ] + }, + { + "cell_type": "markdown", + "id": "181215ce", + "metadata": {}, + "source": [ + "## Looping through a list\n", + "\n", + "You can use a `for` statement to loop through the elements of a list." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "a5df1e10", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c0e53a09", + "metadata": {}, + "source": [ + "For example, after using `split` to make a list of words, we can use `for` to loop through them." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "76b2c2e3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0857b55b", + "metadata": {}, + "source": [ + "A `for` loop over an empty list never runs the indented statements." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "7e844887", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6e5f55c9", + "metadata": {}, + "source": [ + "## Sorting lists\n", + "\n", + "Python provides a built-in function called `sorted` that sorts the elements of a list." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "9db54d53", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "44e028cf", + "metadata": {}, + "source": [ + "The original list is unchanged." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "33d11287", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "530146af", + "metadata": {}, + "source": [ + "`sorted` works with any kind of sequence, not just lists. So we can sort the letters in a string like this." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "38c7cb0c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f90bd9ea", + "metadata": {}, + "source": [ + "The result it a list.\n", + "To convert the list to a string, we can use `join`." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "2adb2fc3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a57084e2", + "metadata": {}, + "source": [ + "With an empty string as the delimiter, the elements of the list are joined with nothing between them." + ] + }, + { + "cell_type": "markdown", + "id": "ce98b3d5", + "metadata": {}, + "source": [ + "## Objects and values\n", + "\n", + "If we run these assignment statements:" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "aa547282", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "33d020aa", + "metadata": {}, + "source": [ + "We know that `a` and `b` both refer to a string, but we don't know whether they refer to the *same* string. \n", + "There are two possible states, shown in the following figure." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "95a2aded", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "3d75a28c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2f0b0431", + "metadata": {}, + "source": [ + "In the diagram on the left, `a` and `b` refer to two different objects that have the\n", + "same value. In the diagram on the right, they refer to the same object.\n", + "To check whether two variables refer to the same object, you can use the `is` operator." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "a37e37bf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d1eb0e36", + "metadata": {}, + "source": [ + "In this example, Python only created one string object, and both `a`\n", + "and `b` refer to it.\n", + "But when you create two lists, you get two objects." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "d6af7316", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a8d4c3d4", + "metadata": {}, + "source": [ + "So the state diagram looks like this." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "dea08b82", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "7e66ee69", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cc115a9f", + "metadata": {}, + "source": [ + "In this case we would say that the two lists are **equivalent**, because they have the same elements, but not **identical**, because they are not the same object. \n", + "If two objects are identical, they are also equivalent, but if they are equivalent, they are not necessarily identical." + ] + }, + { + "cell_type": "markdown", + "id": "a58db021", + "metadata": {}, + "source": [ + "## Aliasing\n", + "\n", + "If `a` refers to an object and you assign `b = a`, then both variables refer to the same object." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "d6a7eb5b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f6ab3262", + "metadata": {}, + "source": [ + "So the state diagram looks like this." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "dd406791", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "552e1e1e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c676fde9", + "metadata": {}, + "source": [ + "The association of a variable with an object is called a **reference**.\n", + "In this example, there are two references to the same object.\n", + "\n", + "An object with more than one reference has more than one name, so we say the object is **aliased**.\n", + "If the aliased object is mutable, changes made with one name affect the other.\n", + "In this example, if we change the object `b` refers to, we are also changing the object `a` refers to." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "6e3c1b24", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e3ef0537", + "metadata": {}, + "source": [ + "So we would say that `a` \"sees\" this change.\n", + "Although this behavior can be useful, it is error-prone.\n", + "In general, it is safer to avoid aliasing when you are working with mutable objects.\n", + "\n", + "For immutable objects like strings, aliasing is not as much of a problem.\n", + "In this example:" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "dad8a246", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "952bbf60", + "metadata": {}, + "source": [ + "It almost never makes a difference whether `a` and `b` refer to the same\n", + "string or not." + ] + }, + { + "cell_type": "markdown", + "id": "35045bef", + "metadata": {}, + "source": [ + "## List arguments\n", + "\n", + "When you pass a list to a function, the function gets a reference to the\n", + "list. If the function modifies the list, the caller sees the change. For\n", + "example, `pop_first` uses the list method `pop` to remove the first element from a list." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "613b1845", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4953b0f9", + "metadata": {}, + "source": [ + "We can use it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "3aff3598", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ef5d3c1e", + "metadata": {}, + "source": [ + "The return value is the first element, which has been removed from the list -- as we can see by displaying the modified list." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "c10e4dcc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e5288e08", + "metadata": {}, + "source": [ + "In this example, the parameter `lst` and the variable `letters` are aliases for the same object, so the state diagram looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "a13e72c7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "1a06dae9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c1a093d2", + "metadata": {}, + "source": [ + "Passing a reference to an object as an argument to a function creates a form of aliasing.\n", + "If the function modifies the object, those changes persist after the function is done." + ] + }, + { + "cell_type": "markdown", + "id": "88c07ec9", + "metadata": { + "tags": [] + }, + "source": [ + "## Making a word list\n", + "\n", + "In the previous chapter, we read the file `words.txt` and searched for words with certain properties, like using the letter `e`.\n", + "But we read the entire file many times, which is not efficient.\n", + "It is better to read the file once and put the words in a list.\n", + "The following loop shows how." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "6550f0b8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "e5a94833", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "44450ffa", + "metadata": {}, + "source": [ + "Before the loop, `word_list` is initialized with an empty list.\n", + "Each time through the loop, the `append` method adds a word to the end.\n", + "When the loop is done, there are more than 113,000 words in the list.\n", + "\n", + "Another way to do the same thing is to use `read` to read the entire file into a string." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "32e28204", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "65718c7f", + "metadata": {}, + "source": [ + "The result is a single string with more than a million characters.\n", + "We can use the `split` method to split it into a list of words." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "4e35f7ce", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1b5b25a3", + "metadata": {}, + "source": [ + "Now, to check whether a string appears in the list, we can use the `in` operator.\n", + "For example, `'demotic'` is in the list." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "a778a62a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9df6674d", + "metadata": {}, + "source": [ + "But `'contrafibularities'` is not." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "63341c0e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "243c25b6", + "metadata": {}, + "source": [ + "And I have to say, I'm anaspeptic about it." + ] + }, + { + "cell_type": "markdown", + "id": "ce9ffd79", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "Note that most list methods modify the argument and return `None`.\n", + "This is the opposite of the string methods, which return a new string and leave the original alone.\n", + "\n", + "If you are used to writing string code like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "88872f14", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d2117582", + "metadata": {}, + "source": [ + "It is tempting to write list code like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "e28e7135", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "991c439d", + "metadata": {}, + "source": [ + "`remove` modifies the list and returns `None`, so next operation you perform with `t` is likely to fail." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "97cf0c61", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c500e2d8", + "metadata": {}, + "source": [ + "This error message takes some explaining.\n", + "An **attribute** of an object is a variable or method associated with it.\n", + "In this case, the value of `t` is `None`, which is a `NoneType` object, which does not have a attribute named `remove`, so the result is an `AttributeError`.\n", + "\n", + "If you see an error message like this, you should look backward through the program and see if you might have called a list method incorrectly." + ] + }, + { + "cell_type": "markdown", + "id": "f90db780", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**list:**\n", + " An object that contains a sequence of values.\n", + "\n", + "**element:**\n", + " One of the values in a list or other sequence.\n", + "\n", + "**nested list:**\n", + "A list that is an element of another list.\n", + "\n", + "**delimiter:**\n", + " A character or string used to indicate where a string should be split.\n", + "\n", + "**equivalent:**\n", + " Having the same value.\n", + "\n", + "**identical:**\n", + " Being the same object (which implies equivalence).\n", + "\n", + "**reference:**\n", + " The association between a variable and its value.\n", + "\n", + "**aliased:**\n", + "If there is more than one variable that refers to an object, the object is aliased.\n", + "\n", + "**attribute:**\n", + " One of the named values associated with an object." + ] + }, + { + "cell_type": "markdown", + "id": "e67864e5", + "metadata": {}, + "source": [ + "## Exercises\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4e34564", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ae9c42da", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "In this chapter, I used the words \"contrafibularities\" and \"anaspeptic\", but they are not actually English words.\n", + "They were used in the British television show *Black Adder*, Season 3, Episode 2, \"Ink and Incapability\".\n", + "\n", + "However, when I asked ChatGPT 3.5 (August 3, 2023 version) where those words came from, it initially claimed they are from Monty Python, and later claimed they are from the Tom Stoppard play *Rosencrantz and Guildenstern Are Dead*.\n", + "\n", + "If you ask now, you might get different results.\n", + "But this example is a reminder that virtual assistants are not always accurate, so you should check whether the results are correct.\n", + "As you gain experience, you will get a sense of which questions virtual assistants can answer reliably.\n", + "In this example, a conventional web search can identify the source of these words quickly.\n", + "\n", + "If you get stuck on any of the exercises in this chapter, consider asking a virtual assistant for help.\n", + "If you get a result that uses features we haven't learned yet, you can assign the VA a \"role\".\n", + "\n", + "For example, before you ask a question try typing \"Role: Basic Python Programming Instructor\".\n", + "After that, the responses you get should use only basic features.\n", + "If you still see features we you haven't learned, you can follow up with \"Can you write that using only basic Python features?\"" + ] + }, + { + "cell_type": "markdown", + "id": "31d5b304", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Two words are anagrams if you can rearrange the letters from one to spell the other.\n", + "For example, `tops` is an anagram of `stop`.\n", + "\n", + "One way to check whether two words are anagrams is to sort the letters in both words.\n", + "If the lists of sorted letters are the same, the words are anagrams.\n", + "\n", + "Write a function called `is_anagram` that takes two strings and returns `True` if they are anagrams." + ] + }, + { + "cell_type": "markdown", + "id": "a882bfeb", + "metadata": { + "tags": [] + }, + "source": [ + "To get you started, here's an outline of the function with doctests." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "9c5916ed", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "5885cbd3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a86e7403", + "metadata": { + "tags": [] + }, + "source": [ + "You can use `doctest` to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "ce7a96ec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8501f3ba", + "metadata": {}, + "source": [ + "Using your function and the word list, find all the anagrams of `takes`." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "75e17c7b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7f279f2f", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Python provides a built-in function called `reversed` that takes as an argument a sequence of elements -- like a list or string -- and returns a `reversed` object that contains the elements in reverse order." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "aafa5db5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0f95c76f", + "metadata": {}, + "source": [ + "If you want the reversed elements in a list, you can use the `list` function." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "06cbb42a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8fc79a2f", + "metadata": {}, + "source": [ + "Of if you want them in a string, you can use the `join` method." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "18a73205", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ec4ce196", + "metadata": {}, + "source": [ + "So we can write a function that reverses a word like this." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "408932cb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "21550b5f", + "metadata": {}, + "source": [ + "A palindrome is a word that is spelled the same backward and forward, like \"noon\" and \"rotator\".\n", + "Write a function called `is_palindrome` that takes a string argument and returns `True` if it is a palindrome and `False` otherwise." + ] + }, + { + "cell_type": "markdown", + "id": "3748b4e0", + "metadata": { + "tags": [] + }, + "source": [ + "Here's an outline of the function with doctests you can use to check your function." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "9179d51c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "16d493ad", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "33c9b4ec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ad857abf", + "metadata": {}, + "source": [ + "You can use the following loop to find all of the palindromes in the word list with at least 7 letters." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "fea01394", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "11386f70", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `reverse_sentence` that takes as an argument a string that contains any number of words separated by spaces.\n", + "It should return a new string that contains the same words in reverse order.\n", + "For example, if the argument is \"Reverse this sentence\", the result should be \"Sentence this reverse\".\n", + "\n", + "Hint: You can use the `capitalize` methods to capitalize the first word and convert the other words to lowercase. " + ] + }, + { + "cell_type": "markdown", + "id": "13882893", + "metadata": { + "tags": [] + }, + "source": [ + "To get you started, here's an outline of the function with doctests." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "d9b5b362", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "a2cb1451", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "769d1c7a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fb5f24b1", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `total_length` that takes a list of strings and returns the total length of the strings.\n", + "The total length of the words in `word_list` should be $902{,}728$." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "1fba5377", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "21f4cf1c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3efb216", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap10.ipynb b/blank/chap10.ipynb new file mode 100644 index 0000000..56d77c9 --- /dev/null +++ b/blank/chap10.ipynb @@ -0,0 +1,1535 @@ +{ + "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": 35, + "id": "e55de5cd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "737e79eb", + "metadata": {}, + "source": [ + "# Dictionaries\n", + "\n", + "This chapter presents a built-in type called a dictionary.\n", + "It is one of Python's best features -- and the building block of many efficient and elegant algorithms.\n", + "\n", + "We'll use dictionaries to compute the number of unique words in a book and the number of times each one appears.\n", + "And in the exercises, we'll use dictionaries to solve word puzzles." + ] + }, + { + "cell_type": "markdown", + "id": "be7467bb", + "metadata": {}, + "source": [ + "## A dictionary is a mapping\n", + "\n", + "A **dictionary** is like a list, but more general.\n", + "In a list, the indices have to be integers; in a dictionary they can be (almost) any type.\n", + "For example, suppose we make a list of number words, like this." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "20dd9f32", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "aa626f88", + "metadata": {}, + "source": [ + "We can use an integer as an index to get the corresponding word." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "9b6625c0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c38e143b", + "metadata": {}, + "source": [ + "But suppose we want to go in the other direction, and look up a word to get the corresponding integer.\n", + "We can't do that with a list, but we can with a dictionary.\n", + "We'll start by creating an empty dictionary and assigning it to `numbers`." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "138952d9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3acce992", + "metadata": {}, + "source": [ + "The curly braces, `{}`, represent an empty dictionary.\n", + "To add items to the dictionary, we'll use square brackets." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "007ef505", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1dbe12c3", + "metadata": {}, + "source": [ + "This assignment adds to the dictionary an **item**, which represents the association of a **key** and a **value**.\n", + "In this example, the key is the string `'zero'` and the value is the integer `0`.\n", + "If we display the dictionary, we see that it contains one item, which contains a key and a value separated by a colon, `:`." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "753a8fbc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ad32c23d", + "metadata": {}, + "source": [ + "We can add more items like this." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "835aac1e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "278901e5", + "metadata": {}, + "source": [ + "Now the dictionary contains three items.\n", + "\n", + "To look up a key and get the corresponding value, we use the bracket operator." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "c0475cee", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "df5724e6", + "metadata": {}, + "source": [ + "If the key isn't in the dictionary, we get a `KeyError`." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "30c37eef", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2a027a6b", + "metadata": {}, + "source": [ + "The `len` function works on dictionaries; it returns the number of items." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "1b4ea0c2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "58221e96", + "metadata": {}, + "source": [ + "In mathematical language, a dictionary represents a **mapping** from keys to values, so you can also say that each key \"maps to\" a value.\n", + "In this example, each number word maps to the corresponding integer.\n", + "\n", + "The following figure shows the state diagram for `numbers`." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "eba36a24", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "9016bf4b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b092aa61", + "metadata": {}, + "source": [ + "A dictionary is represented by a box with the word \"dict\" outside and the items inside.\n", + "Each item is represented by a key and an arrow pointing to a value.\n", + "The quotation marks indicate that the keys here are strings, not variable names." + ] + }, + { + "cell_type": "markdown", + "id": "2a0a128a", + "metadata": {}, + "source": [ + "## Creating dictionaries\n", + "\n", + "In the previous section we created an empty dictionary and added items one at a time using the bracket operator.\n", + "Instead, we could have created the dictionary all at once like this." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "19dfeecb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "31ded5b2", + "metadata": {}, + "source": [ + "Each item consists of a key and a value separated by a colon.\n", + "The items are separated by commas and enclosed in curly braces.\n", + "\n", + "Another way to create a dictionary is to use the `dict` function.\n", + "We can make an empty dictionary like this." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "39b81034", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bfb215c9", + "metadata": {}, + "source": [ + "And we can make a copy of a dictionary like this." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "88fa12c5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "966c5539", + "metadata": {}, + "source": [ + "It is often useful to make a copy before performing operations that modify dictionaries." + ] + }, + { + "cell_type": "markdown", + "id": "2a948f62", + "metadata": { + "tags": [] + }, + "source": [ + "## The in operator\n", + "\n", + "The `in` operator works on dictionaries, too; it tells you whether something appears as a *key* in the dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "025cad92", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "80f6b264", + "metadata": {}, + "source": [ + "The `in` operator does *not* check whether something appears as a value." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "65de12ab", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "84856c8b", + "metadata": {}, + "source": [ + "To see whether something appears as a value in a dictionary, you can use the method `values`, which returns a sequence of values, and then use the `in` operator." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "87ddc1b2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "45dc3d16", + "metadata": {}, + "source": [ + "The items in a Python dictionary are stored in a **hash table**, which is a way of organizing data that has a remarkable property: the `in` operator takes about the same amount of time no matter how many items are in the dictionary.\n", + "That makes it possible to write some remarkably efficient algorithms." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "4849b563", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bba0522c", + "metadata": {}, + "source": [ + "To demonstrate, we'll compare two algorithms for finding pairs of words where one is the reverse of another -- like `stressed` and `desserts`.\n", + "We'll start by reading the word list." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "830b1208", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ab29fb8a", + "metadata": {}, + "source": [ + "And here's `reverse_word` from the previous chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "49231201", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "93f7ac1b", + "metadata": {}, + "source": [ + "The following function loops through the words in the list.\n", + "For each one, it reverses the letters and then checks whether the reversed word in the word list." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "a41759fb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d4ebb84d", + "metadata": {}, + "source": [ + "This function takes more than a minute to run.\n", + "The problem is that the `in` operator checks the words in the list one at a time, starting at the beginning.\n", + "If it doesn't find what it's looking for -- which happens most of the time -- it has to search all the way to the end." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "33bcddf8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2acb6c50", + "metadata": {}, + "source": [ + "And the `in` operator is inside the loop, so it runs once for each word.\n", + "Since there are more than 100,000 words in the list, and for each one we check more than 100,000 words, the total number of comparisons is the number of words squared -- roughly -- which is almost 13 billion. " + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "f2869dd0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5dbf01b7", + "metadata": {}, + "source": [ + "We can make this function much faster with a dictionary.\n", + "The following loop creates a dictionary that contains the words as keys." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "300416d9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b7f6a1b7", + "metadata": {}, + "source": [ + "The values in `word_dict` are all `1`, but they could be anything, because we won't ever look them up -- we will only use this dictionary to check whether a key exists.\n", + "\n", + "Now here's a version of the previous function that replaces `word_list` with `word_dict`." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "9d3dfd8d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5f41e54c", + "metadata": {}, + "source": [ + "This function takes less than one hundredth of a second, so it's about 10,000 times faster than the previous version." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "82b36568", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4cd91c99", + "metadata": {}, + "source": [ + "In general, the time it takes to find an element in a list is proportional to the length of the list.\n", + "The time it takes to find a key in a dictionary is almost constant -- regardless of the number of items." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "aa079ed3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b3bfa8a5", + "metadata": {}, + "source": [ + "## A collection of counters\n", + "\n", + "Suppose you are given a string and you want to count how many times each letter appears.\n", + "A dictionary is a good tool for this job.\n", + "We'll start with an empty dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "7c21ff00", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "34a9498a", + "metadata": {}, + "source": [ + "As we loop through the letters in the string, suppose we see the letter `'a'` for the first time.\n", + "We can add it to the dictionary like this." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "7d0afb00", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bca9fa11", + "metadata": {}, + "source": [ + "The value `1` indicates that we have seen the letter once.\n", + "Later, if we see the same letter again, we can increment the counter like this." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "ba97b5ea", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "274ea014", + "metadata": {}, + "source": [ + "Now the value associated with `'a'` is `2`, because we've seen the letter twice." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "30ffe9b4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2ca8f99d", + "metadata": {}, + "source": [ + "The following function uses these features to count the number of times each letter appears in a string." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "36f95332", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "735c758b", + "metadata": {}, + "source": [ + "Each time through the loop, if `letter` is not in the dictionary, we create a new item with key `letter` and value `1`.\n", + "If `letter` is already in the dictionary we increment the value associated with `letter`.\n", + "\n", + "Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "d6f1048e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8ac1fea4", + "metadata": {}, + "source": [ + "The items in `counter` show that the letter `'b'` appears once, `'r'` appears twice, and so on." + ] + }, + { + "cell_type": "markdown", + "id": "912bdf5d", + "metadata": {}, + "source": [ + "## Looping and dictionaries\n", + "\n", + "If you use a dictionary in a `for` statement, it traverses the keys of the dictionary.\n", + "To demonstrate, let's make a dictionary that counts the letters in `'banana'`." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "310e1489", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fe263f3d", + "metadata": {}, + "source": [ + "The following loop prints the keys, which are the letters." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "da4ec7fd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bf1b7824", + "metadata": {}, + "source": [ + "To print the values, we can use the `values` method." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "859fe1ad", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "721135be", + "metadata": {}, + "source": [ + "To print the keys and values, we can loop through the keys and look up the corresponding values." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "7242ab5b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "efa1bce5", + "metadata": {}, + "source": [ + "In the next chapter, we'll see a more concise way to do the same thing." + ] + }, + { + "cell_type": "markdown", + "id": "a160c0ef", + "metadata": {}, + "source": [ + "## Lists and dictionaries\n", + "\n", + "You can put a list in a dictionary as a value.\n", + "For example, here's a dictionary that maps from the number `4` to a list of four letters." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "29cd8207", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "815a829f", + "metadata": {}, + "source": [ + "But you can't put a list in a dictionary as a key.\n", + "Here's what happens if we try." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "ca9ff511", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2469b08a", + "metadata": {}, + "source": [ + "I mentioned earlier that dictionaries use hash tables, and that means that the keys have to be **hashable**.\n", + "\n", + "A **hash** is a function that takes a value (of any kind) and returns an integer.\n", + "Dictionaries use these integers, called hash values, to store and look up keys.\n", + "\n", + "This system only works if a key is immutable, so its hash value is always the same.\n", + "But if a key is mutable, its hash value could change, and the dictionary would not work.\n", + "That's why keys have to be hashable, and why mutable types like lists aren't.\n", + "\n", + "Since dictionaries are mutable, they can't be used as keys, either.\n", + "But they *can* be used as values." + ] + }, + { + "cell_type": "markdown", + "id": "acfd2720", + "metadata": { + "tags": [] + }, + "source": [ + "## Accumulating a list\n", + "\n", + "For many programming tasks, it is useful to loop through one list or dictionary while building another.\n", + "As an example, we'll loop through the words in `word_dict` and make a list of palindromes -- that is, words that are spelled the same backward and forward, like \"noon\" and \"rotator\".\n", + "\n", + "In the previous chapter, one of the exercises asked you to write a function that checks whether a word is a palindrome.\n", + "Here's a solution that uses `reverse_word`." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "0647278e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "af545fcd", + "metadata": {}, + "source": [ + "If we loop through the words in `word_dict`, we can count the number of palindromes like this." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "9eff9f2c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "73c1ce1e", + "metadata": {}, + "source": [ + "By now, this pattern is familiar.\n", + "\n", + "* Before the loop, `count` is initialized to `0`.\n", + "\n", + "* Inside the loop, if `word` is a palindrome, we increment `count`.\n", + "\n", + "* When the loop ends, `count` contains the total number of palindromes.\n", + "\n", + "We can use a similar pattern to make a list of palindromes." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "609bdd9a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "be909f3b", + "metadata": {}, + "source": [ + "Here's how it works:\n", + "\n", + "* Before the loop, `palindromes` is initialized with an empty list.\n", + "\n", + "* Inside the loop, if `word` is a palindrome, we append it to the end of `palindromes`.\n", + "\n", + "* When the loop ends, `palindromes` is a list of palindromes.\n", + "\n", + "In this loop, `palindromes` is used as an **accumulator**, which is a variable that collects or accumulates data during a computation.\n", + "\n", + "Now suppose we want to select only palindromes with seven or more letters.\n", + "We can loop through `palindromes` and make a new list that contains only long palindromes." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "c2db1187", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fa8ed275", + "metadata": {}, + "source": [ + "Looping through a list like this, selecting some elements and omitting others, is called **filtering**." + ] + }, + { + "cell_type": "markdown", + "id": "8ed50837", + "metadata": { + "tags": [] + }, + "source": [ + "## Memos\n", + "\n", + "If you ran the `fibonacci` function from [Chapter 6](section_fibonacci), maybe you noticed that the bigger the argument you provide, the longer the function takes to run." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "13a7ed35", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1b5203c2", + "metadata": {}, + "source": [ + "Furthermore, the run time increases quickly.\n", + "To understand why, consider the following figure, which shows the **call graph** for\n", + "`fibonacci` with `n=4`:" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "7ed6137a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "a9374c39", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "12098be7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4ee2a87c", + "metadata": {}, + "source": [ + "A call graph shows a set of function frames, with lines connecting each frame to the frames of the functions it calls.\n", + "At the top of the graph, `fibonacci` with `n=4` calls `fibonacci` with ` n=3` and `n=2`.\n", + "In turn, `fibonacci` with `n=3` calls `fibonacci` with `n=2` and `n=1`. And so on.\n", + "\n", + "Count how many times `fibonacci(0)` and `fibonacci(1)` are called. \n", + "This is an inefficient solution to the problem, and it gets worse as the argument gets bigger.\n", + "\n", + "One solution is to keep track of values that have already been computed by storing them in a dictionary.\n", + "A previously computed value that is stored for later use is called a **memo**.\n", + "Here is a \"memoized\" version of `fibonacci`:" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "28e443f5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d2ac4dd7", + "metadata": {}, + "source": [ + "`known` is a dictionary that keeps track of the Fibonacci numbers we already know\n", + "It starts with two items: `0` maps to `0` and `1` maps to `1`.\n", + "\n", + "Whenever `fibonacci_memo` is called, it checks `known`.\n", + "If the result is already there, it can return immediately.\n", + "Otherwise it has to compute the new value, add it to the dictionary, and return it.\n", + "\n", + "Comparing the two functions, `fibonacci(40)` takes about 30 seconds to run.\n", + "`fibonacci_memo(40)` takes about 30 microseconds, so it's a million times faster.\n", + "In the notebook for this chapter, you'll see where these measurements come from." + ] + }, + { + "cell_type": "markdown", + "id": "c6f39f84", + "metadata": { + "tags": [] + }, + "source": [ + "To measure how long a function takes, we can use `%time` which is one of Jupyter's \"built-in magic commands\".\n", + "These commands are not part of the Python language, so they might not work in other development environments." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "af818c11", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "7316d721", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ec969e51", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "As you work with bigger datasets it can become unwieldy to debug by printing and checking the output by hand. Here are some suggestions for debugging large datasets:\n", + "\n", + "1. Scale down the input: If possible, reduce the size of the dataset. For example if the\n", + " program reads a text file, start with just the first 10 lines, or\n", + " with the smallest example you can find. You can either edit the\n", + " files themselves, or (better) modify the program so it reads only\n", + " the first `n` lines.\n", + "\n", + " If there is an error, you can reduce `n` to the smallest value where the error occurs.\n", + " As you find and correct errors, you can increase `n` gradually." + ] + }, + { + "cell_type": "markdown", + "id": "1a62288b", + "metadata": {}, + "source": [ + "2. Check summaries and types: Instead of printing and checking the entire dataset, consider\n", + " printing summaries of the data -- for example, the number of items in\n", + " a dictionary or the total of a list of numbers.\n", + "\n", + " A common cause of runtime errors is a value that is not the right type. For debugging this kind of error, it is often enough to print the type of a value." + ] + }, + { + "cell_type": "markdown", + "id": "c749ea3c", + "metadata": {}, + "source": [ + "3. Write self-checks: Sometimes you can write code to check for errors automatically. For\n", + " example, if you are computing the average of a list of numbers, you\n", + " could check that the result is not greater than the largest element\n", + " in the list or less than the smallest. This is called a \"sanity\n", + " check\" because it detects results that are \"insane\".\n", + "\n", + " Another kind of check compares the results of two different computations to see if they are consistent. This is called a \"consistency check\"." + ] + }, + { + "cell_type": "markdown", + "id": "749b91e9", + "metadata": {}, + "source": [ + "4. Format the output: Formatting debugging output can make it easier to spot an error. We saw an example in [Chapter 6](section_debugging_factorial). Another tool you might find useful is the `pprint` module, which provides a `pprint` function that displays built-in types in a more human-readable format (`pprint` stands for \"pretty print\").\n", + "\n", + " Again, time you spend building scaffolding can reduce the time you spend debugging." + ] + }, + { + "cell_type": "markdown", + "id": "9820175f", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**dictionary:**\n", + " An object that contains key-value pairs, also called items.\n", + "\n", + "**item:**\n", + " In a dictionary, another name for a key-value pair.\n", + "\n", + "**key:**\n", + " An object that appears in a dictionary as the first part of a key-value pair.\n", + "\n", + "**value:**\n", + " An object that appears in a dictionary as the second part of a key-value pair. This is more specific than our previous use of the word \"value\".\n", + "\n", + "**mapping:**\n", + " A relationship in which each element of one set corresponds to an element of another set.\n", + "\n", + "**hash table:**\n", + "A collection of key-value pairs organized so that we can look up a key and find its value efficiently.\n", + "\n", + "**hashable:**\n", + " Immutable types like integers, floats and strings are hashable.\n", + " Mutable types like lists and dictionaries are not.\n", + "\n", + "**hash function:**\n", + "A function that takes an object and computes an integer that is used to locate a key in a hash table.\n", + "\n", + "**accumulator:**\n", + " A variable used in a loop to add up or accumulate a result.\n", + "\n", + "**filtering:**\n", + "Looping through a sequence and selecting or omitting elements.\n", + "\n", + "**call graph:**\n", + "A diagram that shows every frame created during the execution of a program, with an arrow from each caller to each callee.\n", + "\n", + "**memo:**\n", + " A computed value stored to avoid unnecessary future computation." + ] + }, + { + "cell_type": "markdown", + "id": "906c1236", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e3c12ec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "170f1deb", + "metadata": {}, + "source": [ + "### Ask an assistant\n", + "\n", + "In this chapter, I said the keys in a dictionary have to be hashable and I gave a short explanation. If you would like more details, ask a virtual assistant, \"Why do keys in Python dictionaries have to be hashable?\"\n", + "\n", + "In [a previous section](section_dictionary_in_operator), we stored a list of words as keys in a dictionary so that we could use an efficient version of the `in` operator.\n", + "We could have done the same thing using a `set`, which is another built-in data type.\n", + "Ask a virtual assistant, \"How do I make a Python set from a list of strings and check whether a string is an element of the set?\"" + ] + }, + { + "cell_type": "markdown", + "id": "badf7d65", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Dictionaries have a method called `get` that takes a key and a default value. \n", + "If the key appears in the dictionary, `get` returns the corresponding value; otherwise it returns the default value.\n", + "For example, here's a dictionary that maps from the letters in a string to the number of times they appear." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "06e437d9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c3f6458d", + "metadata": {}, + "source": [ + "If we look up a letter that appears in the word, `get` returns the number of times it appears." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "fc328161", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "49bbff3e", + "metadata": {}, + "source": [ + "If we look up a letter that doesn't appear, we get the default value, `0`." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "674b6663", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4ac3210f", + "metadata": {}, + "source": [ + "Use `get` to write a more concise version of `value_counts`.\n", + "You should be able to eliminate the `if` statement." + ] + }, + { + "cell_type": "markdown", + "id": "5413af6e", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "What is the longest word you can think of where each letter appears only once?\n", + "Let's see if we can find one longer than `unpredictably`.\n", + "\n", + "Write a function named `has_duplicates` that takes a sequence -- like a list or string -- as a parameter and returns `True` if there is any element that appears in the sequence more than once." + ] + }, + { + "cell_type": "markdown", + "id": "9879d9e7", + "metadata": { + "tags": [] + }, + "source": [ + "To get you started, here's an outline of the function with doctests." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "1744d3e9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "061e2903", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d8c85906", + "metadata": { + "tags": [] + }, + "source": [ + "You can use `doctest` to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "62dcdf4d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ce4df190", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this loop to find the longest words with no repeated letters." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "a1143193", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "afd5f3b6", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write function called `find_repeats` that takes a dictionary that maps from each key to a counter, like the result from `value_counts`.\n", + "It should loop through the dictionary and return a list of keys that have counts greater than `1`.\n", + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "9ea333ff", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "bbd6d241", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b9b0cbec", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following examples to test your code.\n", + "First, we'll make a dictionary that maps from letters to counts." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "ac983c6f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3aa62942", + "metadata": { + "tags": [] + }, + "source": [ + "The result from `find_repeats` should be `['a', 'n']`." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "214345d8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3eaf77f8", + "metadata": { + "tags": [] + }, + "source": [ + "Here's another example that starts with a list of numbers.\n", + "The result should be `[1, 2]`." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "10e9d54a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1c700d84", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Suppose you run `value_counts` with two different words and save the results in two dictionaries." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "c97e2419", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "deb14c7a", + "metadata": {}, + "source": [ + "Each dictionary maps from a set of letters to the number of times they appear.\n", + "Write a function called `add_counters` that takes two dictionaries like this and returns a new dictionary that contains all of the letters and the total number of times they appear in either word.\n", + "\n", + "There are many ways to solve this problem.\n", + "Once you have a working solution, consider asking a virtual assistant for different solutions." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "6cf70355", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "6a729a14", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f88110a9", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "A word is \"interlocking\" if we can split it into two words by taking alternating letters.\n", + "For example, \"schooled\" is an interlocking word because it can be split into \"shoe\" and \"cold\".\n", + "\n", + "To select alternating letters from a string, you can use a slice operator with three components that indicate where to start, where to stop, and the \"step size\" between the letters.\n", + "\n", + "In the following slice, the first component is `0`, so we start with the first letter.\n", + "The second component is `None`, which means we should go all the way to the end of the string.\n", + "And the third component is `2`, so there are two steps between the letters we select." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "56783358", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d432332d", + "metadata": {}, + "source": [ + "Instead of providing `None` as the second component, we can get the same effect by leaving it out altogether.\n", + "For example, the following slice selects alternating letters, starting with the second letter." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "77fa8483", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c8c4e3ba", + "metadata": {}, + "source": [ + "Write a function called `is_interlocking` that takes a word as an argument and returns `True` if it can be split into two interlocking words." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "e5e1030c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2787f786", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following loop to find the interlocking words in the word list." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "e04a5c73", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ced5aa90", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap11.ipynb b/blank/chap11.ipynb new file mode 100644 index 0000000..d80bc23 --- /dev/null +++ b/blank/chap11.ipynb @@ -0,0 +1,2039 @@ +{ + "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": 2, + "id": "295ac6d7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5705b2a6", + "metadata": { + "tags": [] + }, + "source": [ + "# Tuples\n", + "\n", + "This chapter introduces one more built-in type, the tuple, and then shows how lists, dictionaries, and tuples work together.\n", + "It also presents tuple assignment and a useful feature for functions with variable-length argument lists: the packing and unpacking operators.\n", + "\n", + "In the exercises, we'll use tuples, along with lists and dictionaries, to solve more word puzzles and implement efficient algorithms.\n", + "\n", + "One note: There are two ways to pronounce \"tuple\".\n", + "Some people say \"tuh-ple\", which rhymes with \"supple\".\n", + "But in the context of programming, most people say \"too-ple\", which rhymes with \"quadruple\"." + ] + }, + { + "cell_type": "markdown", + "id": "19474596", + "metadata": {}, + "source": [ + "## Tuples are like lists\n", + "\n", + "A tuple is a sequence of values. The values can be any type, and they are indexed by integers, so tuples are a lot like lists.\n", + "The important difference is that tuples are immutable.\n", + "\n", + "To create a tuple, you can write a comma-separated list of values." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "fb0bdca2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a2ec15d8", + "metadata": {}, + "source": [ + "Although it is not necessary, it is common to enclose tuples in parentheses." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5a6da881", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9194a159", + "metadata": {}, + "source": [ + "To create a tuple with a single element, you have to include a final comma." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e2596ca7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e39b95a5", + "metadata": {}, + "source": [ + "A single value in parentheses is not a tuple." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a0d350a6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a64bfb64", + "metadata": {}, + "source": [ + "Another way to create a tuple is the built-in function `tuple`. With no\n", + "argument, it creates an empty tuple." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c9100ee4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f3447831", + "metadata": {}, + "source": [ + "If the argument is a sequence (string, list or tuple), the result is a\n", + "tuple with the elements of the sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "44bd3d83", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2e48b980", + "metadata": {}, + "source": [ + "Because `tuple` is the name of a built-in function, you should avoid using it as a variable name.\n", + "\n", + "Most list operators also work with tuples.\n", + "For example, the bracket operator indexes an element." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "92e55b2c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2f702785", + "metadata": {}, + "source": [ + "And the slice operator selects a range of elements." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "38ee5c2a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c9ed9af2", + "metadata": {}, + "source": [ + "The `+` operator concatenates tuples." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "2e0e311a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1d7dcd6d", + "metadata": {}, + "source": [ + "And the `*` operator duplicates a tuple a given number of times." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "8bb7d715", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a53ce8bd", + "metadata": {}, + "source": [ + "The `sorted` function works with tuples -- but the result is a list, not a tuple." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "e653e00f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "50e5cadc", + "metadata": {}, + "source": [ + "The `reversed` function also works with tuples." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "8969188d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f6d973c5", + "metadata": {}, + "source": [ + "The result is a `reversed` object, which we can convert to a list or tuple." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "65d7ebaa", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7cb9ee6", + "metadata": {}, + "source": [ + "Based on the examples so far, it might seem like tuples are the same as lists." + ] + }, + { + "cell_type": "markdown", + "id": "8c3f381e", + "metadata": {}, + "source": [ + "## But tuples are immutable\n", + "\n", + "If you try to modify a tuple with the bracket operator, you get a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b4970fe0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "592ce99c", + "metadata": {}, + "source": [ + "And tuples don't have any of the methods that modify lists, like `append` and `remove`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "772738cc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "70772ba2", + "metadata": {}, + "source": [ + "Recall that an \"attribute\" is a variable or method associated with an object -- this error message means that tuples don't have a method named `remove`.\n", + "\n", + "Because tuples are immutable, they are hashable, which means they can be used as keys in a dictionary.\n", + "For example, the following dictionary contains two tuples as keys that map to integers." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "37e67042", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "47ba17ab", + "metadata": {}, + "source": [ + "We can look up a tuple in a dictionary like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d809a490", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f2c0a354", + "metadata": {}, + "source": [ + "Or if we have a variable that refers to a tuple, we can use it as a key." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "dfc42a8b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2ea8fc3c", + "metadata": {}, + "source": [ + "Tuples can also appear as values in a dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2debf30c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "25655ab3", + "metadata": {}, + "source": [ + "## Tuple assignment\n", + "\n", + "You can put a tuple of variables on the left side of an assignment, and a tuple of values on the right." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "1e94ea37", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "92c00ceb", + "metadata": {}, + "source": [ + "The values are assigned to the variables from left to right -- in this example, `a` gets the value `1` and `b` gets the value `2`.\n", + "We can display the results like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "99c96c7f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6362b36e", + "metadata": {}, + "source": [ + "More generally, if the left side of an assignment is a tuple, the right side can be any kind of sequence -- string, list or tuple. \n", + "For example, to split an email address into a user name and a domain, you could write:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "b67881ed", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d134a94c", + "metadata": {}, + "source": [ + "The return value from `split` is a list with two elements -- the first element is assigned to `username`, the second to `domain`." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "b4515e2b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5a7e3c62", + "metadata": {}, + "source": [ + "The number of variables on the left and the number of values on the\n", + "right have to be the same -- otherwise you get a `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "8e5b4a14", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "808c2928", + "metadata": {}, + "source": [ + "Tuple assignment is useful if you want to swap the values of two variables.\n", + "With conventional assignments, you have to use a temporary variable, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "2389d6de", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "98496d02", + "metadata": {}, + "source": [ + "That works, but with tuple assignment we can do the same thing without a temporary variable." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "5512edec", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a66a87bc", + "metadata": {}, + "source": [ + "This works because all of the expressions on the right side are evaluated before any of the assignments.\n", + "\n", + "We can also use tuple assignment in a `for` statement.\n", + "For example, to loop through the items in a dictionary, we can use the `items` method." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "651ab417", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dd0d4feb", + "metadata": {}, + "source": [ + "Each time through the loop, `item` is assigned a tuple that contains a key and the corresponding value.\n", + "\n", + "We can write this loop more concisely, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "2c0b7d47", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f0513578", + "metadata": {}, + "source": [ + "Each time through the loop, a key and the corresponding value are assigned directly to `key` and `value`." + ] + }, + { + "cell_type": "markdown", + "id": "efedeb37", + "metadata": {}, + "source": [ + "## Tuples as return values\n", + "\n", + "Strictly speaking, a function can only return one value, but if the\n", + "value is a tuple, the effect is the same as returning multiple values.\n", + "For example, if you want to divide two integers and compute the quotient\n", + "and remainder, it is inefficient to compute `x//y` and then `x%y`. It is\n", + "better to compute them both at the same time.\n", + "\n", + "The built-in function `divmod` takes two arguments and returns a tuple\n", + "of two values, the quotient and remainder." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "fff80eaa", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "33f3c57d", + "metadata": {}, + "source": [ + "We can use tuple assignment to store the elements of the tuple in two variables. " + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "4a0eb2a9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "d74ba1b6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "15079805", + "metadata": {}, + "source": [ + "Here is an example of a function that returns a tuple." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "dad3b3bb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "43c4e1e0", + "metadata": {}, + "source": [ + "`max` and `min` are built-in functions that find the largest and smallest elements of a sequence. \n", + "`min_max` computes both and returns a tuple of two values." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "fbd90b0e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "86b60e71", + "metadata": {}, + "source": [ + "We can assign the results to variables like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "5a101efb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "112b5aa2", + "metadata": { + "tags": [] + }, + "source": [ + "## Argument packing\n", + "\n", + "Functions can take a variable number of arguments. \n", + "A parameter name that begins with the `*` operator **packs** arguments into a tuple.\n", + "For example, the following function takes any number of arguments and computes their arithmetic mean -- that is, their sum divided by the number of arguments. " + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "0a33e2d0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6044fc1b", + "metadata": {}, + "source": [ + "The parameter can have any name you like, but `args` is conventional.\n", + "We can call the function like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "336a08ca", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a5e8b158", + "metadata": {}, + "source": [ + "If you have a sequence of values and you want to pass them to a function as multiple arguments, you can use the `*` operator to **unpack** the tuple.\n", + "For example, `divmod` takes exactly two arguments -- if you pass a tuple as a parameter, you get an error." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "991810bc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5a9110db", + "metadata": {}, + "source": [ + "Even though the tuple contains two elements, it counts as a single argument.\n", + "But if you unpack the tuple, it is treated as two arguments." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "f25ebee1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "da554863", + "metadata": {}, + "source": [ + "Packing and unpacking can be useful if you want to adapt the behavior of an existing function.\n", + "For example, this function takes any number of arguments, removes the lowest and highest, and computes the mean of the rest." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "7ad64412", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d1e05e49", + "metadata": {}, + "source": [ + "First it uses `min_max` to find the lowest and highest elements.\n", + "Then it converts `args` to a list so it can use the `remove` method.\n", + "Finally it unpacks the list so the elements are passed to `mean` as separate arguments, rather than as a single list.\n", + "\n", + "Here's an example that shows the effect." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "b2863701", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "cc1afa29", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "35e04996", + "metadata": {}, + "source": [ + "This kind of \"trimmed\" mean is used in some sports with subjective judging -- like diving and gymnastics -- to reduce the effect of a judge whose score deviates from the others. " + ] + }, + { + "cell_type": "markdown", + "id": "c4572cd2", + "metadata": {}, + "source": [ + "## Zip\n", + "\n", + "Tuples are useful for looping through the elements of two sequences and performing operations on corresponding elements.\n", + "For example, suppose two teams play a series of seven games, and we record their scores in two lists, one for each team." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "ad3e6f81", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b44f228b", + "metadata": {}, + "source": [ + "Let's see how many games each team won.\n", + "We'll use `zip`, which is a built-in function that takes two or more sequences and returns a **zip object**, so-called because it pairs up the elements of the sequences like the teeth of a zipper." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "9ce313ce", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9adcf8f9", + "metadata": {}, + "source": [ + "We can use the zip object to loop through the values in the sequences pairwise." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "321d9c30", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "51d1dabb", + "metadata": {}, + "source": [ + "Each time through the loop, `pair` gets assigned a tuple of scores.\n", + "So we can assign the scores to variables, and count the victories for the first team, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "7eb73d5d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ad740fcd", + "metadata": {}, + "source": [ + "Sadly, the first team won only three games and lost the series.\n", + "\n", + "If you have two lists and you want a list of pairs, you can use `zip` and `list`." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "9529baa8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ec4249fa", + "metadata": {}, + "source": [ + "The result is a list of tuples, so we can get the result of the last game like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "dbde77b8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "436486b9", + "metadata": {}, + "source": [ + "If you have a list of keys and a list of values, you can use `zip` and `dict` to make a dictionary.\n", + "For example, here's how we can make a dictionary that maps from each letter to its position in the alphabet." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "dbb7d0b3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b4de6974", + "metadata": {}, + "source": [ + "Now we can look up a letter and get its index in the alphabet." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "49e3fd8e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cc632542", + "metadata": {}, + "source": [ + "In this mapping, the index of `'a'` is `0` and the index of `'z'` is `25`.\n", + "\n", + "If you need to loop through the elements of a sequence and their indices, you can use the built-in function `enumerate`." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "9e4f3e51", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "92ad45bb", + "metadata": {}, + "source": [ + "The result is an **enumerate object** that loops through a sequence of pairs, where each pair contains an index (starting from 0) and an element from the given sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "c1dcb46d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cf0b55d7", + "metadata": {}, + "source": [ + "## Comparing and Sorting\n", + "\n", + "The relational operators work with tuples and other sequences.\n", + "For example, if you use the `<` operator with tuples, it starts by comparing the first element from each sequence.\n", + "If they are equal, it goes on to the next pair of elements, and so on, until it finds a pair that differ. " + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "aed20c28", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "65ceea86", + "metadata": {}, + "source": [ + "Subsequent elements are not considered -- even if they are really big." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "4d9e73b3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "55e4a35e", + "metadata": {}, + "source": [ + "This way of comparing tuples is useful for sorting a list of tuples, or finding the minimum or maximum.\n", + "As an example, let's find the most common letter in a word.\n", + "In the previous chapter, we wrote `value_counts`, which takes a string and returns a dictionary that maps from each letter to the number of times it appears." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "2077dfa9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a80012c1", + "metadata": {}, + "source": [ + "Here is the result for the string `'banana'`." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "b3d40516", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cc1ea4a7", + "metadata": {}, + "source": [ + "With only three items, we can easily see that the most frequent letter is `'a'`, which appears three times.\n", + "But if there were more items, it would be useful to sort them automatically.\n", + "\n", + "We can get the items from `counter` like this." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "8288c28f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ac8dea7a", + "metadata": {}, + "source": [ + "The result is a `dict_items` object that behaves like a list of tuples, so we can sort it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "bbbade35", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b690d17a", + "metadata": {}, + "source": [ + "The default behavior is to use the first element from each tuple to sort the list, and use the second element to break ties.\n", + "\n", + "However, to find the items with the highest counts, we want to use the second element to sort the list.\n", + "We can do that by writing a function that takes a tuple and returns the second element." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "a4c31795", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b964aa14", + "metadata": {}, + "source": [ + "Then we can pass that function to `sorted` as an optional argument called `key`, which indicates that this function should be used to compute the **sort key** for each item." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "f3d3619a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4dc96848", + "metadata": {}, + "source": [ + "The sort key determines the order of the items in the list.\n", + "The letter with the lowest count appears first, and the letter with the highest count appears last.\n", + "So we can find the most common letter like this." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "f078c8a6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d0d8b999", + "metadata": {}, + "source": [ + "If we only want the maximum, we don't have to sort the list.\n", + "We can use `max`, which also takes `key` as an optional argument." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "54030d8f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8a8327df", + "metadata": {}, + "source": [ + "To find the letter with the lowest count, we could use `min` the same way." + ] + }, + { + "cell_type": "markdown", + "id": "a62394a5", + "metadata": {}, + "source": [ + "## Inverting a dictionary\n", + "\n", + "Suppose you want to invert a dictionary so you can look up a value and get the corresponding key.\n", + "For example, if you have a word counter that maps from each word to the number of times it appears, you could make a dictionary that maps from integers to the words that appear that number of times.\n", + "\n", + "But there's a problem -- the keys in a dictionary have to be unique, but the values don't. For example, in a word counter, there could be many words with the same count.\n", + "\n", + "So one way to invert a dictionary is to create a new dictionary where the values are lists of keys from the original.\n", + "As an example, let's count the letters in `parrot`." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "ef158f81", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f4570eae", + "metadata": {}, + "source": [ + "If we invert this dictionary, the result should be `{1: ['p', 'a', 'o', 't'], 2: ['r']}`, which indicates that the letters that appear once are `'p'`, `'a'`, `'o'`, and `'t'`, and the letter than appears twice is `'r'`.\n", + "\n", + "The following function takes a dictionary and returns its inverse as a new dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "d3607b8d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ca5fa025", + "metadata": {}, + "source": [ + "The `for` statement loops through the keys and values in `d`.\n", + "If the value is not already in the new dictionary, it is added and associated with a list that contains a single element.\n", + "Otherwise it is appended to the existing list.\n", + "\n", + "We can test it like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "692d9cf8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4cfb1693", + "metadata": {}, + "source": [ + "And we get the result we expected.\n", + "\n", + "This is the first example we've seen where the values in the dictionary are lists.\n", + "We will see more!" + ] + }, + { + "cell_type": "markdown", + "id": "6d138cd7", + "metadata": { + "tags": [] + }, + "source": [ + "## Debugging\n", + "\n", + "Lists, dictionaries and tuples are **data structures**.\n", + "In this chapter we are starting to see compound data structures, like lists of tuples, or dictionaries that contain tuples as keys and lists as values.\n", + "Compound data structures are useful, but they are prone to errors caused when a data structure has the wrong type, size, or structure.\n", + "For example, if a function expects a list of integers and you give it a plain old integer\n", + "(not in a list), it probably won't work.\n", + "\n", + "To help debug these kinds of errors, I wrote a module called `structshape` that provides a function, also called `structshape`, that takes any kind of data structure as an argument and returns a string that summarizes its structure.\n", + "You can download it from\n", + "." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "e9f03e91", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "646f4d55", + "metadata": {}, + "source": [ + "We can import it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "90ab624a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "86cc6ccc", + "metadata": {}, + "source": [ + "Here's an example with a simple list." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "6794330f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9de4f6ec", + "metadata": {}, + "source": [ + "Here's a list of lists." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "54cd185b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "aced9984", + "metadata": {}, + "source": [ + "If the elements of the list are not the same type, `structshape` groups\n", + "them by type." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "04028afd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f63ff690", + "metadata": {}, + "source": [ + "Here's a list of tuples." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "b5d45c88", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c9ec67eb", + "metadata": {}, + "source": [ + "And here's a dictionary with three items that map integers to strings." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "15131907", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f29bb82b", + "metadata": {}, + "source": [ + "If you are having trouble keeping track of your data structures,\n", + "`structshape` can help." + ] + }, + { + "cell_type": "markdown", + "id": "79d93082", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**pack:**\n", + "Collect multiple arguments into a tuple.\n", + "\n", + "**unpack:**\n", + "Treat a tuple (or other sequence) as multiple arguments.\n", + "\n", + "**zip object:**\n", + "The result of calling the built-in function `zip`, can be used to loop through a sequence of tuples.\n", + "\n", + "**enumerate object:**\n", + "The result of calling the built-in function `enumerate`, can be used to loop through a sequence of tuples.\n", + "\n", + "**sort key:**\n", + "A value, or function that computes a value, used to sort the elements of a collection.\n", + "\n", + "**data structure:**\n", + "A collection of values, organized to perform certain operations efficiently." + ] + }, + { + "cell_type": "markdown", + "id": "1471b3c0", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "c65d68d2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "97a0352d", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "The exercises in this chapter might be more difficult than exercises in previous chapters, so I encourage you to get help from a virtual assistant.\n", + "When you pose more difficult questions, you might find that the answers are not correct on the first attempt, so this is a chance to practice crafting good prompts and following up with good refinements.\n", + "\n", + "One strategy you might consider is to break a big problems into pieces that can be solved with simple functions.\n", + "Ask the virtual assistant to write the functions and test them.\n", + "Then, once they are working, ask for a solution to the original problem.\n", + "\n", + "For some of the exercises below, I make suggestions about which data structures and algorithms to use.\n", + "You might find these suggestions useful when you work on the problems, but they are also good prompts to pass along to a virtual assistant." + ] + }, + { + "cell_type": "markdown", + "id": "f90e011f", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "In this chapter I said that tuples can be used as keys in dictionaries because they are hashable, and they are hashable because they are immutable.\n", + "But that is not always true.\n", + "\n", + "If a tuple contains a mutable value, like a list or a dictionary, the tuple is no longer hashable because it contains elements that are not hashable. As an example, here's a tuple that contains two lists of integers." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "4416fe4a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "02799077", + "metadata": {}, + "source": [ + "Write a line of code that appends the value `6` to the end of the second list in `t`. If you display `t`, the result should be `([1, 2, 3], [4, 5, 6])`." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "e6eda0e4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "644b1dfb", + "metadata": {}, + "source": [ + "Try to create a dictionary that maps from `t` to a string, and confirm that you get a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "4fae1acc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fb77a352", + "metadata": {}, + "source": [ + "For more on this topic, ask a virtual assistant, \"Are Python tuples always hashable?\"" + ] + }, + { + "cell_type": "markdown", + "id": "bdfc8c27", + "metadata": { + "tags": [] + }, + "source": [ + "### Exercise\n", + "\n", + "In this chapter we made a dictionary that maps from each letter to its index in the alphabet." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "855c7ed2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a8cd720b", + "metadata": {}, + "source": [ + "For example, the index of `'a'` is `0`." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "3c921f68", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a04c25db", + "metadata": {}, + "source": [ + "To go in the other direction, we can use list indexing.\n", + "For example, the letter at index `1` is `'b'`." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "b029b0da", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "165ab770", + "metadata": {}, + "source": [ + "We can use `letter_map` and `letters` to encode and decode words using a Caesar cipher.\n", + "\n", + "A Caesar cipher is a weak form of encryption that involves shifting each letter\n", + "by a fixed number of places in the alphabet, wrapping around to the beginning if necessary. For example, `'a'` shifted by 2 is `'c'` and `'z'` shifted by 1 is `'a'`.\n", + "\n", + "Write a function called `shift_word` that takes as parameters a string and an integer, and returns a new string that contains the letters from the string shifted by the given number of places.\n", + "\n", + "To test your function, confirm that \"cheer\" shifted by 7 is \"jolly\" and \"melon\" shifted by 16 is \"cubed\".\n", + "\n", + "Hints: Use the modulus operator to wrap around from `'z'` back to `'a'`. \n", + "Loop through the letters of the word, shift each one, and append the result to a list of letters.\n", + "Then use `join` to concatenate the letters into a string." + ] + }, + { + "cell_type": "markdown", + "id": "e7478b18", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "1cc07036", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "96560a0e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "c026c6d1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "5814999d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "39a67af9", + "metadata": { + "tags": [] + }, + "source": [ + "You can use `doctest` to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "9464d140", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "779f13af", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `most_frequent_letters` that takes a string and prints the letters in decreasing order of frequency.\n", + "\n", + "To get the items in decreasing order, you can use `reversed` along with `sorted` or you can pass `reverse=True` as a keyword parameter to `sorted`." + ] + }, + { + "cell_type": "markdown", + "id": "d71923e6", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this outline of the function to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "4309d0b5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "52228828", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c6354c44", + "metadata": { + "tags": [] + }, + "source": [ + "And this example to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "3bf2aa0d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2ca1e337", + "metadata": { + "tags": [] + }, + "source": [ + "Once your function is working, you can use the following code to print the most common letters in *Dracula*, which we can download from Project Gutenberg." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "e4fbf5d9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "817ec689", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "211c09c9", + "metadata": { + "tags": [] + }, + "source": [ + "According to Zim's \"Codes and Secret Writing\", the sequence of letters in decreasing order of frequency in English starts with \"ETAONRISH\".\n", + "How does this sequence compare with the results from *Dracula*?" + ] + }, + { + "cell_type": "markdown", + "id": "cbe9933e", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "In a previous exercise, we tested whether two strings are anagrams by sorting the letters in both words and checking whether the sorted letters are the same.\n", + "Now let's make the problem a little more challenging.\n", + "\n", + "We'll write a program that takes a list of words and prints all the sets of words that are anagrams.\n", + "Here is an example of what the output might look like:\n", + "\n", + " ['deltas', 'desalt', 'lasted', 'salted', 'slated', 'staled']\n", + " ['retainers', 'ternaries']\n", + " ['generating', 'greatening']\n", + " ['resmelts', 'smelters', 'termless']\n", + "\n", + "Hint: For each word in the word list, sort the letters and join them back into a string. Make a dictionary that maps from this sorted string to a list of words that are anagrams of it." + ] + }, + { + "cell_type": "markdown", + "id": "4b9ed2a8", + "metadata": { + "tags": [] + }, + "source": [ + "The following cells download `words.txt` and read the words into a list." + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "941719c1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "d2ec641b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e4cc2c8c", + "metadata": { + "tags": [] + }, + "source": [ + "Here's the `sort_word` function we've used before." + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "7ae29f73", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "013819a5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "70faa9f5", + "metadata": { + "tags": [] + }, + "source": [ + "To find the longest list of anagrams, you can use the following function, which takes a key-value pair where the key is a string and the value is a list of words.\n", + "It returns the length of the list." + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "fbf9ede3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dcda6e28", + "metadata": { + "tags": [] + }, + "source": [ + "We can use this function as a sort key to find the longest lists of anagrams." + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "55435050", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0b6d5add", + "metadata": { + "tags": [] + }, + "source": [ + "If you want to know the longest words that have anagrams, you can use the following loop to print some of them." + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "6a9320c2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4fbe939e", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `word_distance` that takes two words with the same length and returns the number of places where the two words differ.\n", + "\n", + "Hint: Use `zip` to loop through the corresponding letters of the words." + ] + }, + { + "cell_type": "markdown", + "id": "8b48dbdc", + "metadata": { + "tags": [] + }, + "source": [ + "Here's an outline of the function with doctests you can use to check your function." + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "3d5a75f8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "a9816dde", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "753a23c1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "066eec59", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "\"Metathesis\" is the transposition of letters in a word.\n", + "Two words form a \"metathesis pair\" if you can transform one into the other by swapping two letters, like `converse` and `conserve`.\n", + "Write a program that finds all of the metathesis pairs in the word list. \n", + "\n", + "Hint: The words in a metathesis pair must be anagrams of each other.\n", + "\n", + "Credit: This exercise is inspired by an example at ." + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "57649075", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6a028806", + "metadata": { + "tags": [] + }, + "source": [ + "### Exercise\n", + "\n", + "This is a bonus exercise that is not in the book.\n", + "It is more challenging than the other exercises in this chapter, so you might want to ask a virtual assistant for help, or come back to it after you've read a few more chapters.\n", + "\n", + "Here's another Car Talk Puzzler\n", + "():\n", + "\n", + "> What is the longest English word, that remains a valid English word,\n", + "> as you remove its letters one at a time?\n", + ">\n", + "> Now, letters can be removed from either end, or the middle, but you\n", + "> can't rearrange any of the letters. Every time you drop a letter, you\n", + "> wind up with another English word. If you do that, you're eventually\n", + "> going to wind up with one letter and that too is going to be an\n", + "> English word---one that's found in the dictionary. I want to know\n", + "> what's the longest word and how many letters does it have?\n", + ">\n", + "> I'm going to give you a little modest example: Sprite. Ok? You start\n", + "> off with sprite, you take a letter off, one from the interior of the\n", + "> word, take the r away, and we're left with the word spite, then we\n", + "> take the e off the end, we're left with spit, we take the s off, we're\n", + "> left with pit, it, and I.\n", + "\n", + "Write a program to find all words that can be reduced in this way, and\n", + "then find the longest one.\n", + "\n", + "This exercise is a little more challenging than most, so here are some\n", + "suggestions:\n", + "\n", + "1. You might want to write a function that takes a word and computes a\n", + " list of all the words that can be formed by removing one letter.\n", + " These are the \"children\" of the word.\n", + "\n", + "2. Recursively, a word is reducible if any of its children are\n", + " reducible. As a base case, you can consider the empty string\n", + " reducible.\n", + "\n", + "3. The word list we've been using doesn't contain single letter\n", + " words. So you might have to add \"I\" and \"a\".\n", + "\n", + "4. To improve the performance of your program, you might want to\n", + " memoize the words that are known to be reducible." + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "c19bf833", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "2d9764d6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "5e4f5d8e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "27d311dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 106, + "id": "68c27c7e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap12.ipynb b/blank/chap12.ipynb new file mode 100644 index 0000000..220b664 --- /dev/null +++ b/blank/chap12.ipynb @@ -0,0 +1,1814 @@ +{ + "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": "6c6265de", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "59a8621b", + "metadata": {}, + "source": [ + "# Text Analysis and Generation\n", + "\n", + "At this point we have covered Python's core data structures -- lists, dictionaries, and tuples -- and some algorithms that use them.\n", + "In this chapter, we'll use them to explore text analysis and Markov generation:\n", + "\n", + "* Text analysis is a way to describe the statistical relationships between the words in a document, like the probability that one word is followed by another, and\n", + "\n", + "* Markov generation is a way to generate new text with words and phrases similar to the original text.\n", + "\n", + "These algorithms are similar to parts of a Large Language Model (LLM), which is the key component of a chatbot.\n", + "\n", + "We'll start by counting the number of times each word appears in a book.\n", + "Then we'll look at pairs of words, and make a list of the words that can follow each word.\n", + "We'll make a simple version of a Markov generator, and as an exercise, you'll have a chance to make a more general version." + ] + }, + { + "cell_type": "markdown", + "id": "0e3811b8", + "metadata": {}, + "source": [ + "## Unique words\n", + "\n", + "As a first step toward text analysis, let's read a book -- *The Strange Case Of Dr. Jekyll And Mr. Hyde* by Robert Louis Stevenson -- and count the number of unique words.\n", + "Instructions for downloading the book are in the notebook for this chapter." + ] + }, + { + "cell_type": "markdown", + "id": "6567e1bf", + "metadata": { + "tags": [] + }, + "source": [ + "The following cell downloads the book from Project Gutenberg." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4cd1c980", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5465ab1d", + "metadata": { + "tags": [] + }, + "source": [ + "The version available from Project Gutenberg includes information about the book at the beginning and license information at the end.\n", + "We'll use `clean_file` from Chapter 8 to remove this material and write a \"clean\" file that contains only the text of the book." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "52ebfe94", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "49cfc352", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "44e53ce6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "50d1fafa", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bc66d7e2", + "metadata": {}, + "source": [ + "We'll use a `for` loop to read lines from the file and `split` to divide the lines into words.\n", + "Then, to keep track of unique words, we'll store each word as a key in a dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "16d24028", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "85171a3a", + "metadata": {}, + "source": [ + "The length of the dictionary is the number of unique words -- about `6000` by this way of counting.\n", + "But if we inspect them, we'll see that some are not valid words.\n", + "\n", + "For example, let's look at the longest words in `unique_words`.\n", + "We can use `sorted` to sort the words, passing the `len` function as a keyword argument so the words are sorted by length." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "1668e6bd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "795f5327", + "metadata": {}, + "source": [ + "The slice index, `[-5:]`, selects the last `5` elements of the sorted list, which are the longest words. \n", + "\n", + "The list includes some legitimately long words, like \"circumscription\", and some hyphenated words, like \"chocolate-coloured\".\n", + "But some of the longest \"words\" are actually two words separated by a dash.\n", + "And other words include punctuation like periods, exclamation points, and quotation marks.\n", + "\n", + "So, before we move on, let's deal with dashes and other punctuation." + ] + }, + { + "cell_type": "markdown", + "id": "bf89fafa", + "metadata": {}, + "source": [ + "## Punctuation\n", + "\n", + "To identify the words in the text, we need to deal with two issues:\n", + "\n", + "* When a dash appears in a line, we should replace it with a space -- then when we use `split`, the words will be separated.\n", + "\n", + "* After splitting the words, we can use `strip` to remove punctuation.\n", + "\n", + "To handle the first issue, we can use the following function, which takes a string, replaces dashes with spaces, splits the string, and returns the resulting list." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ed5f0a43", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d5decdec", + "metadata": {}, + "source": [ + "Notice that `split_line` only replaces dashes, not hyphens.\n", + "Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a9df2aeb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0d9eb318", + "metadata": {}, + "source": [ + "Now, to remove punctuation from the beginning and end of each word, we can use `strip`, but we need a list of characters that are considered punctuation.\n", + "\n", + "Characters in Python strings are in Unicode, which is an international standard used to represent letters in nearly every alphabet, numbers, symbols, punctuation marks, and more.\n", + "The `unicodedata` module provides a `category` function we can use to tell which characters are punctuation.\n", + "Given a letter, it returns a string with information about what category the letter is in." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "b138b123", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "994835ea", + "metadata": {}, + "source": [ + "The category string of `'A'` is `'Lu'` -- the `'L'` means it is a letter and the `'u'` means it is uppercase.\n", + "\n", + "The category string of `'.'` is `'Po'` -- the `'P'` means it is punctuation and the `'o'` means its subcategory is \"other\"." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "fe65df44", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "03773b9b", + "metadata": {}, + "source": [ + "We can find the punctuation marks in the book by checking for characters with categories that begin with `'P'`.\n", + "The following loop stores the unique punctuation marks in a dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "b47a87cf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e6741dfa", + "metadata": {}, + "source": [ + "To make a list of punctuation marks, we can join the keys of the dictionary into a string." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "348949be", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6af8d5a2", + "metadata": {}, + "source": [ + "Now that we know which characters in the book are punctuation, we can write a function that takes a word, strips punctuation from the beginning and end, and converts it to lower case." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "06121901", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "58a78cb1", + "metadata": {}, + "source": [ + "Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "881ed9f8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "314e4fbd", + "metadata": {}, + "source": [ + "Because `strip` removes characters from the beginning and end, it leaves hyphenated words alone." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ab5d2fed", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "99050f8a", + "metadata": {}, + "source": [ + "Now here's a loop that uses `split_line` and `clean_word` to identify the unique words in the book." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "2fdfb936", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "992e5466", + "metadata": {}, + "source": [ + "With this stricter definition of what a word is, there are about 4000 unique words.\n", + "And we can confirm that the list of longest words has been cleaned up." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "3104d191", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8014c330", + "metadata": {}, + "source": [ + "Now let's see how many times each word is used." + ] + }, + { + "cell_type": "markdown", + "id": "7ef40180", + "metadata": {}, + "source": [ + "## Word frequencies\n", + "\n", + "The following loop computes the frequency of each unique word." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "4fba7d1c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bd680b81", + "metadata": {}, + "source": [ + "The first time we see a word, we initialize its frequency to `1`. If we see the same word again later, we increment its frequency.\n", + "\n", + "To see which words appear most often, we can use `items` to get the key-value pairs from `word_counter`, and sort them by the second element of the pair, which is the frequency.\n", + "First we'll define a function that selects the second element." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "4be34c95", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b15a5bd6", + "metadata": {}, + "source": [ + "Now we can use `sorted` with two keyword arguments:\n", + "\n", + "* `key=second_element` means the items will be sorted according to the frequencies of the words.\n", + "\n", + "* `reverse=True` means they items will be sorted in reverse order, with the most frequent words first." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "8efe7c4c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "db6812e2", + "metadata": {}, + "source": [ + "Here are the five most frequent words." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "79c17341", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "551e81bb", + "metadata": {}, + "source": [ + "In the next section, we'll encapsulate this loop in a function.\n", + "And we'll use it to demonstrate a new feature -- optional parameters." + ] + }, + { + "cell_type": "markdown", + "id": "45243ccc", + "metadata": {}, + "source": [ + "## Optional parameters\n", + "\n", + "We've used built-in functions that take optional parameters.\n", + "For example, `round` takes an optional parameters called `ndigits` that indicates how many decimal places to keep." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "838bcb4f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6ae60945", + "metadata": {}, + "source": [ + "But it's not just built-in functions -- we can write functions with optional parameters, too.\n", + "For example, the following function takes two parameters, `word_counter` and `num`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "90c45e7e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "78cb1531", + "metadata": {}, + "source": [ + "The second parameter looks like an assignment statement, but it's not -- it's an optional parameter.\n", + "\n", + "If you call this function with one argument, `num` gets the **default value**, which is `5`." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "e106be95", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "29753ad6", + "metadata": {}, + "source": [ + "If you call this function with two arguments, the second argument gets assigned to `num` instead of the default value." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "8101a510", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e9bf907b", + "metadata": {}, + "source": [ + "In that case, we would say the optional argument **overrides** the default value.\n", + "\n", + "If a function has both required and optional parameters, all of the required parameters have to come first, followed by the optional ones." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "c046117b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3f450df2", + "metadata": { + "tags": [] + }, + "source": [ + "## Dictionary subtraction\n", + "\n", + "Suppose we want to spell-check a book -- that is, find a list of words that might be misspelled.\n", + "One way to do that is to find words in the book that don't appear in a list of valid words.\n", + "In previous chapters, we've used a list of words that are considered valid in word games like Scrabble.\n", + "Now we'll use this list to spell-check Robert Louis Stevenson.\n", + "\n", + "We can think of this problem as set subtraction -- that is, we want to find all the words from one set (the words in the book) that are not in the other (the words in the list)." + ] + }, + { + "cell_type": "markdown", + "id": "a3804d82", + "metadata": { + "tags": [] + }, + "source": [ + "The following cell downloads the word list." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "edd8ff1c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2a46556c", + "metadata": {}, + "source": [ + "As we've done before, we can read the contents of `words.txt` and split it into a list of strings." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "67ef3e08", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "22becbab", + "metadata": {}, + "source": [ + "The we'll store the words as keys in a dictionary so we can use the `in` operator to check quickly whether a word is valid." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "471d58e9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "94cc7c61", + "metadata": {}, + "source": [ + "Now, to identify words that appear in the book but not in the word list, we'll use `subtract`, which takes two dictionaries as parameters and returns a new dictionary that contains all the keys from one that are not in the other." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "4d4c3538", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e70c63b4", + "metadata": {}, + "source": [ + "Here's how we use it." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "8b42e014", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f8ada7bd", + "metadata": {}, + "source": [ + "To get a sample of words that might be misspelled, we can print the most common words in `diff`." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "f48be152", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "deeec418", + "metadata": {}, + "source": [ + "The most common \"misspelled\" words are mostly names and a few single-letter words (Mr. Utterson is Dr. Jekyll's friend and lawyer).\n", + "\n", + "If we select words that only appear once, they are more likely to be actual misspellings.\n", + "We can do that by looping through the items and making a list of words with frequency `1`." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "5716f967", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "98ae9281", + "metadata": {}, + "source": [ + "Here are the last few elements of the list." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "b37219f5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c5040834", + "metadata": {}, + "source": [ + "Most of them are valid words that are not in the word list.\n", + "But `'reindue'` appears to be a misspelling of `'reinduce'`, so at least we found one legitimate error." + ] + }, + { + "cell_type": "markdown", + "id": "afcbbe19", + "metadata": {}, + "source": [ + "## Random numbers\n", + "\n", + "As a step toward Markov text generation, next we'll choose a random sequence of words from `word_counter`.\n", + "But first let's talk about randomness.\n", + "\n", + "Given the same inputs, most computer programs are **deterministic**, which means they generate the same outputs every time.\n", + "Determinism is usually a good thing, since we expect the same calculation to yield the same result.\n", + "For some applications, though, we want the computer to be unpredictable.\n", + "Games are one example, but there are more.\n", + "\n", + "Making a program truly nondeterministic turns out to be difficult, but there are ways to fake it.\n", + "One is to use algorithms that generate **pseudorandom** numbers.\n", + "Pseudorandom numbers are not truly random because they are generated by a deterministic computation, but just by looking at the numbers it is all but impossible to distinguish them from random.\n", + "\n", + "The `random` module provides functions that generate pseudorandom numbers -- which I will simply call \"random\" from here on.\n", + "We can import it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "75b548a9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "2bfa31ae", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8cbbd7f8", + "metadata": {}, + "source": [ + "The `random` module provides a function called `choice` that chooses an element from a list at random, with every element having the same probability of being chosen." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "6f5d5c1c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "57c15af2", + "metadata": {}, + "source": [ + "If you call the function again, you might get the same element again, or a different one." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "1445068b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6f0c2572", + "metadata": {}, + "source": [ + "In the long run, we expect to get every element about the same number of times.\n", + "\n", + "If you use `choice` with a dictionary, you get a `KeyError`." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "4fc47ecd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "592722f3", + "metadata": {}, + "source": [ + "To choose a random key, you have to put the keys in a list and then call `choice`." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "91ae9d4c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "172d72f6", + "metadata": {}, + "source": [ + "If we generate a random sequence of words, it doesn't make much sense." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "8bf595c1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e0e2fbc4", + "metadata": {}, + "source": [ + "Part of the problem is that we are not taking into account that some words are more common than others.\n", + "The results will be better if we choose words with different \"weights\", so that some are chosen more often than others.\n", + "\n", + "If we use the values from `word_counter` as weights, each word is chosen with a probability that depends on its frequency." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "22953b65", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5098bf93", + "metadata": {}, + "source": [ + "The `random` module provides another function called `choices` that takes weights as an optional argument." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "1c7cdf4d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a3341e84", + "metadata": {}, + "source": [ + "And it takes another optional argument, `k`, that specifies the number of words to select." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "a7a3aa42", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e57e6f3d", + "metadata": {}, + "source": [ + "The result is a list of strings that we can join into something that's looks more like a sentence." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "c4286fb3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c7a35dff", + "metadata": {}, + "source": [ + "If you choose words from the book at random, you get a sense of the vocabulary, but a series of random words seldom makes sense because there is no relationship between successive words.\n", + "For example, in a real sentence you expect an article like \"the\" to be followed by an adjective or a noun, and probably not a verb or adverb.\n", + "So the next step is to look at these relationships between words." + ] + }, + { + "cell_type": "markdown", + "id": "0921dd53", + "metadata": {}, + "source": [ + "## Bigrams\n", + "\n", + "Instead of looking at one word at a time, now we'll look at sequences of two words, which are called **bigrams**.\n", + "A sequence of three words is called a **trigram**, and a sequence with some unspecified number of words is called an **n-gram**.\n", + "\n", + "Let's write a program that finds all of the bigrams in the book and the number of times each one appears.\n", + "To store the results, we'll use a dictionary where\n", + "\n", + "* The keys are tuples of strings that represent bigrams, and \n", + "\n", + "* The values are integers that represent frequencies.\n", + "\n", + "Let's call it `bigram_counter`." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "d8ee02f6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "33f97a2a", + "metadata": {}, + "source": [ + "The following function takes a list of two strings as a parameter.\n", + "First it makes a tuple of the two strings, which can be used as a key in a dictionary.\n", + "Then it adds the key to `bigram_counter`, if it doesn't exist, or increments the frequency if it does." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "bfdb1de1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5c30f429", + "metadata": {}, + "source": [ + "As we go through the book, we have to keep track of each pair of consecutive words.\n", + "So if we see the sequence \"man is not truly one\", we would add the bigrams \"man is\", \"is not\", \"not truly\", and so on.\n", + "\n", + "To keep track of these bigrams, we'll use a list called `window`, because it is like a window that slides over the pages of the book, showing only two words at a time.\n", + "Initially, `window` is empty." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "2e73df79", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9376558c", + "metadata": {}, + "source": [ + "We'll use the following function to process the words one at a time." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "495ad429", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "56895591", + "metadata": {}, + "source": [ + "The first time this function is called, it appends the given word to `window`.\n", + "Since there is only one word in the window, we don't have a bigram yet, so the function ends.\n", + "\n", + "The second time it's called -- and every time thereafter -- it appends a second word to `window`.\n", + "Since there are two words in the window, it calls `count_bigram` to keep track of how many times each bigram appears.\n", + "Then it uses `pop` to remove the first word from the window.\n", + "\n", + "The following program loops through the words in the book and processes them one at a time." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "c1224061", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "20c4627a", + "metadata": {}, + "source": [ + "The result is a dictionary that maps from each bigram to the number of times it appears.\n", + "We can use `print_most_common` to see the most common bigrams." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "4296485a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "757bd309", + "metadata": {}, + "source": [ + "Looking at these results, we can get a sense of which pairs of words are most likely to appear together.\n", + "We can also use the results to generate random text, like this." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "e03fd803", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "f6ee1840", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "eda80407", + "metadata": {}, + "source": [ + "`bigrams` is a list of the bigrams that appear in the books.\n", + "`weights` is a list of their frequencies, so `random_bigrams` is a sample where the probability a bigram is selected is proportional to its frequency. \n", + "\n", + "Here are the results." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "d6c65d79", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5f24c3b6", + "metadata": {}, + "source": [ + "This way of generating text is better than choosing random words, but still doesn't make a lot of sense." + ] + }, + { + "cell_type": "markdown", + "id": "a13d93b5", + "metadata": {}, + "source": [ + "## Markov analysis\n", + "\n", + "We can do better with Markov chain text analysis, which computes, for each word in a text, the list of words that come next.\n", + "As an example, we'll analyze these lyrics from the Monty Python song *Eric, the Half a Bee*:" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "3171d592", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "583ab9f0", + "metadata": {}, + "source": [ + "To store the results, we'll use a dictionary that maps from each word to the list of words that follow it." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "3321e6a4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d5d85b09", + "metadata": {}, + "source": [ + "As an example, let's start with the first two words of the song." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "e4e55c71", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0349fe78", + "metadata": {}, + "source": [ + "If the first word is not in `successor_map`, we have to add a new item that maps from the first word to a list containing the second word." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "f25dcb5e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "55bb8df9", + "metadata": {}, + "source": [ + "If the first word is already in the dictionary, we can look it up to get the list of successors we've seen so far, and append the new one." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "990354a0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6289cc32", + "metadata": {}, + "source": [ + "The following function encapsulates these steps." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "b9371452", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "74a51700", + "metadata": {}, + "source": [ + "If the same bigram appears more that once, the second word is added to the list more than once.\n", + "In this way, `successor_map` keeps track of how many times each successor appears.\n", + "\n", + "As we did in the previous section, we'll use a list called `window` to store pairs of consecutive words.\n", + "And we'll use the following function to process the words one at a time." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "8c3f45c2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "861a60d9", + "metadata": {}, + "source": [ + "Here's how we use it to process the words in the song." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "641990a3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bf490d67", + "metadata": {}, + "source": [ + "And here are the results." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "9322a49a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ff7bad74", + "metadata": {}, + "source": [ + "The word `'half'` can be followed by `'a'`, `'not'`, or `'the'`.\n", + "The word `'a'` can be followed by `'bee'` or `'vis'`.\n", + "Most of the other words appear only once, so they are followed by only a single word.\n", + "\n", + "Now let's analyze the book." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "45a60c52", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2676e2fb", + "metadata": {}, + "source": [ + "We can look up any word and find the words that can follow it." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "3e86102c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "e49d52f7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7b777a9c", + "metadata": {}, + "source": [ + "In this list of successors, notice that the word `'to'` appears three times -- the other successors only appear once." + ] + }, + { + "cell_type": "markdown", + "id": "e8bf85fc", + "metadata": {}, + "source": [ + "## Generating text\n", + "\n", + "We can use the results from the previous section to generate new text with the same relationships between consecutive words as in the original.\n", + "Here's how it works:\n", + "\n", + "* Starting with any word that appears in the text, we look up its possible successors and choose one at random.\n", + "\n", + "* Then, using the chosen word, we look up its possible successors, and choose one at random.\n", + "\n", + "We can repeat this process to generate as many words as we want.\n", + "As an example, let's start with the word `'although'`.\n", + "Here are the words that can follow it." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "15108884", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "747a41be", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b26a2ead", + "metadata": {}, + "source": [ + "We can use `choice` to choose from the list with equal probability." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "5a4682dc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9741beca", + "metadata": {}, + "source": [ + "If the same word appears more than once in the list, it is more likely to be selected.\n", + "\n", + "Repeating these steps, we can use the following loop to generate a longer series." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "36ee0f76", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "38a2d79a", + "metadata": {}, + "source": [ + "The result sounds more like a real sentence, but it still doesn't make much sense.\n", + "\n", + "We can do better using more than one word as a key in `successor_map`.\n", + "For example, we can make a dictionary that maps from each bigram -- or trigram -- to the list of words that come next.\n", + "As an exercise, you'll have a chance to implement this analysis and see what the results look like." + ] + }, + { + "cell_type": "markdown", + "id": "c59dff45", + "metadata": { + "tags": [] + }, + "source": [ + "## Debugging\n", + "\n", + "At this point we are writing more substantial programs, and you might find that you are spending more time debugging.\n", + "If you are stuck on a difficult bug, here are a few things to try:\n", + "\n", + "* Reading: Examine your code, read it back to yourself, and check that it says what you meant to say.\n", + "\n", + "* Running: Experiment by making changes and running different versions. Often if you display the right thing at the right place in the program, the problem becomes obvious, but sometimes you have to build scaffolding.\n", + "\n", + "* Ruminating: Take some time to think! What kind of error is it: syntax, runtime,\n", + " or semantic? What information can you get from the error messages,\n", + " or from the output of the program? What kind of error could cause\n", + " the problem you're seeing? What did you change last, before the\n", + " problem appeared?\n", + "\n", + "* Rubberducking: If you explain the problem to someone else, you sometimes find the\n", + " answer before you finish asking the question. Often you don't need\n", + " the other person; you could just talk to a rubber duck. And that's\n", + " the origin of the well-known strategy called **rubber duck\n", + " debugging**. I am not making this up -- see\n", + " .\n", + "\n", + "* Retreating: At some point, the best thing to do is back up -- undoing recent\n", + " changes -- until you get to a program that works. Then you can start rebuilding.\n", + " \n", + "* Resting: If you give your brain a break, sometime it will find the problem for you." + ] + }, + { + "cell_type": "markdown", + "id": "12c2cd32", + "metadata": {}, + "source": [ + "Beginning programmers sometimes get stuck on one of these activities and forget the others. Each activity comes with its own failure mode.\n", + "\n", + "For example, reading your code works if the problem is a typographical error, but not if the problem is a conceptual misunderstanding.\n", + "If you don't understand what your program does, you can read it 100 times and never see the error, because the error is in your head.\n", + "\n", + "Running experiments can work, especially if you run small, simple tests.\n", + "But if you run experiments without thinking or reading your code, it can take a long time to figure out what's happening.\n", + "\n", + "You have to take time to think. Debugging is like an experimental science. You should have at least one hypothesis about what the problem is. If there are two or more possibilities, try to think of a test that would eliminate one of them." + ] + }, + { + "cell_type": "markdown", + "id": "a55036e1", + "metadata": {}, + "source": [ + "But even the best debugging techniques will fail if there are too many\n", + "errors, or if the code you are trying to fix is too big and complicated.\n", + "Sometimes the best option is to retreat, simplifying the program until\n", + "you get back to something that works.\n", + "\n", + "Beginning programmers are often reluctant to retreat because they can't\n", + "stand to delete a line of code (even if it's wrong). If it makes you\n", + "feel better, copy your program into another file before you start\n", + "stripping it down. Then you can copy the pieces back one at a time.\n", + "\n", + "Finding a hard bug requires reading, running, ruminating, retreating, and sometimes resting.\n", + "If you get stuck on one of these activities, try the others." + ] + }, + { + "cell_type": "markdown", + "id": "25d091af", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**default value:**\n", + "The value assigned to a parameter if no argument is provided.\n", + "\n", + "**override:**\n", + " To replace a default value with an argument.\n", + "\n", + "**deterministic:**\n", + " A deterministic program does the same thing each time it runs, given the same inputs.\n", + "\n", + "**pseudorandom:**\n", + " A pseudorandom sequence of numbers appears to be random, but is generated by a deterministic program.\n", + "\n", + "**bigram:**\n", + "A sequence of two elements, often words.\n", + "\n", + "**trigram:**\n", + "A sequence of three elements.\n", + "\n", + "**n-gram:**\n", + "A sequence of an unspecified number of elements.\n", + "\n", + "**rubber duck debugging:**\n", + "A way of debugging by explaining a problem aloud to an inanimate object." + ] + }, + { + "cell_type": "markdown", + "id": "cde18229", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05752b6d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9b0efab8", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "In `add_bigram`, the `if` statement creates a new list or appends an element to an existing list, depending on whether the key is already in the dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "a4365ac0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "30d9e549", + "metadata": {}, + "source": [ + "Dictionaries provide a method called `setdefault` that we can use to do the same thing more concisely.\n", + "Ask a virtual assistant how it works, or copy `add_word` into a virtual assistant and ask \"Can you rewrite this using `setdefault`?\"\n", + "\n", + "In this chapter we implemented Markov chain text analysis and generation.\n", + "If you are curious, you can ask a virtual assistant for more information on the topic.\n", + "One of the things you might learn is that virtual assistants use algorithms that are similar in many ways -- but also different in important ways.\n", + "Ask a VA, \"What are the differences between large language models like GPT and Markov chain text analysis?\"" + ] + }, + { + "cell_type": "markdown", + "id": "060c9ef6", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function that counts the number of times each trigram (sequence of three words) appears. \n", + "If you test your function with the text of _Dr. Jekyll and Mr. Hyde_, you should find that the most common trigram is \"said the lawyer\".\n", + "\n", + "Hint: Write a function called `count_trigram` that is similar to `count_bigram`. Then write a function called `process_word_trigram` that is similar to `process_word_bigram`." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "f38a61ff", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "d047e546", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ca1f8a79", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following loop to read the book and process the words." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "6b8932ee", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "35d37fa1", + "metadata": { + "tags": [] + }, + "source": [ + "Then use `print_most_common` to find the most common trigrams in the book." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "44c3f0d8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4bd07bb7", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Now let's implement Markov chain text analysis with a mapping from each bigram to a list of possible successors.\n", + "\n", + "Starting with `add_bigram`, write a function called `add_trigram` that takes a list of three words and either adds or updates an item in `successor_map`, using the first two words as the key and the third word as a possible successor." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "3fcf85f4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "94d683fe", + "metadata": {}, + "source": [ + "Here's a version of `process_word_trigram` that calls `add_trigram`." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "d9e554e3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "82eeed41", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following loop to test your function with the lyrics of \"Eric, the Half a Bee\"." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "8c2ee21c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8829d4c2", + "metadata": { + "tags": [] + }, + "source": [ + "If your function works as intended, the predecessor `('half', 'a')` should map to a list with the single element `'bee'`.\n", + "In fact, as it happens, each bigram in this song appear only once, so all of the values in `successor_map` have a single element." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "b13384e3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "886212b5", + "metadata": {}, + "source": [ + "You can use the following loop to test your function with the words from the book." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "62c2177f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3e1d073e", + "metadata": {}, + "source": [ + "In the next exercise, you'll use the results to generate new random text." + ] + }, + { + "cell_type": "markdown", + "id": "04d7a6ee", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "For this exercise, we'll assume that `successor_map` is a dictionary that maps from each bigram to the list of words that follow it." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "64e11f26", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fb0f8f7d", + "metadata": {}, + "source": [ + "To generate random text, we'll start by choosing a random key from `successor_map`." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "fe2d93fa", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "83ed6c7e", + "metadata": {}, + "source": [ + "Now write a loop that generates 50 more words following these steps:\n", + "\n", + "1. In `successor_map`, look up the list of words that can follow `bigram`.\n", + "\n", + "2. Choose one of them at random and print it.\n", + "\n", + "3. For the next iteration, make a new bigram that contains the second word from `bigram` and the chosen successor.\n", + "\n", + "For example, if we start with the bigram `('doubted', 'if')` and choose `'from'` as its successor, the next bigram is `('if', 'from')`." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "22210a5c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c71d8a89", + "metadata": {}, + "source": [ + "If everything is working, you should find that the generated text is recognizably similar in style to the original, and some phrases make sense, but the text might wander from one topic to another.\n", + "\n", + "As a bonus exercise, modify your solution to the last two exercises to use trigrams as keys in `successor_map`, and see what effect it has on the results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d4efda7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap13.ipynb b/blank/chap13.ipynb new file mode 100644 index 0000000..69616a9 --- /dev/null +++ b/blank/chap13.ipynb @@ -0,0 +1,1677 @@ +{ + "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": "31b6f97a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c3ee5c27", + "metadata": { + "tags": [] + }, + "source": [ + "Credit: Photos downloaded from [Lorem Picsum](https://picsum.photos/), a service that provides placeholder images.\n", + "The name is a reference to \"lorem ipsum\", which is a name for placeholder text.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9b246dc6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "23792899", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "713dfeb0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "92934cb0", + "metadata": {}, + "source": [ + "# Files and Databases\n", + "\n", + "Most of the programs we have seen so far are **ephemeral** in the sense that they run for a short time and produce output, but when they end, their data disappears.\n", + "Each time you run an ephemeral program, it starts with a clean slate.\n", + "\n", + "Other programs are **persistent**: they run for a long time (or all the time); they keep at least some of their data in long-term storage; and if they shut down and restart, they pick up where they left off.\n", + "\n", + "A simple way for programs to maintain their data is by reading and writing text files.\n", + "A more versatile alternative is to store data in a database.\n", + "Databases are specialized files that can be read and written more efficiently than text files, and they provide additional capabilities.\n", + "\n", + "In this chapter, we'll write programs that read and write text files and databases, and as an exercise you'll write a program that searches a collection of photos for duplicates.\n", + "But before you can work with a file, you have to find it, so we'll start with file names, paths, and directories." + ] + }, + { + "cell_type": "markdown", + "id": "75cec7ca", + "metadata": {}, + "source": [ + "## Filenames and paths\n", + "\n", + "Files are organized into **directories**, also called \"folders\".\n", + "Every running program has a **current working directory**, which is the default directory for most operations.\n", + "For example, when you open a file, Python looks for it in the current working directory.\n", + "\n", + "The `os` module provides functions for working with files and directories (\"os\" stands for \"operating system\"). \n", + "It provides a function called `getcwd` that gets the name of the current working directory." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4fdb528a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c7b209f6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6c55575f", + "metadata": {}, + "source": [ + "The result in this example is the home directory of a user named `dinsdale`.\n", + "A string like `'/home/dinsdale'` that identifies a file or directory is called a **path**.\n", + "\n", + "A simple filename like `'memo.txt'` is also considered a path, but it is a **relative path** because it specifies a file name relative to the current directory.\n", + "In this example, the current directory is `/home/dinsdale`, so `'memo.txt'` is equivalent to the complete path `'/home/dinsdale/memo.txt'`.\n", + "\n", + "A path that begins with `/` does not depend on the current directory -- it is called an **absolute path**. \n", + "To find the absolute path to a file, you can use `abspath`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "66a15e44", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f1a0cf04", + "metadata": {}, + "source": [ + "The `os` module provides other functions for working with filenames and paths.\n", + "`listdir` returns a list of the contents of the given directory, including files and other directories.\n", + "Here's an example that lists the contents of a directory named `photos`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a22d2675", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ab160f29", + "metadata": {}, + "source": [ + "This directory contains a text file named `notes.txt` and three directories.\n", + "The directories contain image files in the JPEG format." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a217eac7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "06b27c85", + "metadata": {}, + "source": [ + "To check whether a file or directory exists, we can use `os.path.exists`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "aa56aeac", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9049009a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d41f235c", + "metadata": {}, + "source": [ + "To check whether a path refers to a file or directory, we can use `isdir`, which return `True` if a path refers to a directory." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0feddb06", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4bd70cc9", + "metadata": {}, + "source": [ + "And `isfile` which returns `True` if a path refers to a file." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "e9f6a762", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c6933c18", + "metadata": {}, + "source": [ + "One challenge of working with paths is that they look different on different operating systems.\n", + "On macOS and UNIX systems like Linux, the directory and file names in a path are separated by a forward slash, `/`.\n", + "Windows uses a backward slash, `\\`.\n", + "So, if you you run these examples on Windows, you will see backward slashes in the paths, and you'll have to replace the forward slashes in the examples.\n", + "\n", + "Or, to write code that works on both systems, you can use `os.path.join`, which joins directory and filenames into a path using a forward or backward slash, depending on which operating system you are using." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "eb738376", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0a665cf9", + "metadata": {}, + "source": [ + "Later in this chapter we'll use these functions to search a set of directories and find all of the image files." + ] + }, + { + "cell_type": "markdown", + "id": "31a96ba4", + "metadata": {}, + "source": [ + "## f-strings\n", + "\n", + "One way for programs to store data is to write it to a text file.\n", + "For example, suppose you are a camel spotter, and you want to record the number of camels you have seen during a period of observation.\n", + "And suppose that in one and a half years, you have spotted `23` camels.\n", + "The data in your camel-spotting book might look like this." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "23e7a6a4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f652aaac", + "metadata": {}, + "source": [ + "To write this data to a file, you can use the `write` method, which we saw in Chapter 8.\n", + "The argument of `write` has to be a string, so if we want to put other values in a file, we have to convert them to strings.\n", + "The easiest way to do that is with the built-in function `str`.\n", + "\n", + "Here's what that looks like:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "174d4f83", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "307c22d2", + "metadata": {}, + "source": [ + "That works, but `write` doesn't add a space or newline unless you include it explicitly.\n", + "If we read back the file, we see that the two numbers are run together." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "5209eda3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8008ecdc", + "metadata": {}, + "source": [ + "At the very least, we should add whitespace between the numbers.\n", + "And while we're at it, let's add some explanatory text.\n", + "\n", + "To write a combination of strings and other values, we can use an **f-string**, which is a string that has the letter `f` before the opening quotation mark, and contains one or more Python expressions in curly braces.\n", + "The following f-string contains one expression, which is a variable name." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "0f202d66", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a3fb0556", + "metadata": {}, + "source": [ + "The result is a string where the expression has been evaluated and replaced with the result.\n", + "There can be more than one expression." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "33c5f77f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bace1539", + "metadata": {}, + "source": [ + "And the expressions can contain operators and function calls." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "1fe7111c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bc6fa094", + "metadata": {}, + "source": [ + "So we could write the data to a text file like this." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "bc06e90a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f916d561", + "metadata": {}, + "source": [ + "Both f-strings end with the sequence `\\n`, which adds a newline character.\n", + "\n", + "We can read the file back like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "8d9eaf8d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c3bf6913", + "metadata": {}, + "source": [ + "In an f-string, an expression in curly brace is converted to a string, so you can include lists, dictionaries, and other types." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "ae3060c1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "838ef132", + "metadata": { + "tags": [] + }, + "source": [ + "If a f-string contains an invalid expression, the result is an error." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "26ca3d1b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cbbaaed3", + "metadata": {}, + "source": [ + "## YAML\n", + "\n", + "One of the reasons programs read and write files is to store **configuration data**, which is information that specifies what the program should do and how.\n", + "\n", + "For example, in a program that searches for duplicate photos, we might have a dictionary called `config` that contains the name of the directory to search, the name of another directory where it should store the results, and a list of file extensions it should use to identify image files.\n", + "\n", + "Here's what it might look like:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "5bb69e1a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1667bb96", + "metadata": {}, + "source": [ + "To write this data in a text file, we could use f-strings, as in the previous section. But it is easier to use a module called `yaml` that is designed for just this sort of thing.\n", + "\n", + "The `yaml` module provides functions to work with YAML files, which are text files formatted to be easy for humans *and* programs to read and write.\n", + "\n", + "Here's an example that uses the `dump` function to write the `config` dictionary to a YAML file. " + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "0090a29f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "97079479", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "92d1b7ff", + "metadata": {}, + "source": [ + "If we read back the contents of the file, we can see what the YAML format looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "8646dd2f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "33cdfd2c", + "metadata": {}, + "source": [ + "Now, we can use `safe_load` to read back the YAML file." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "e8ce2e4f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ca55764f", + "metadata": {}, + "source": [ + "The result is new dictionary that contains the same information as the original, but it is not the same dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "871d6ad5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "969ad306", + "metadata": {}, + "source": [ + "Converting an object like a dictionary to a string is called **serialization**.\n", + "Converting the string back to an object is called **deserialization**.\n", + "If you serialize and then deserialize an object, the result should be equivalent to the original." + ] + }, + { + "cell_type": "markdown", + "id": "5e130cf8", + "metadata": {}, + "source": [ + "## Shelve\n", + "\n", + "So far we've been reading and writing text files -- now let's consider databases.\n", + "A **database** is a file that is organized for storing data.\n", + "Some databases are organized like a table with rows and columns of information.\n", + "Others are organized like a dictionary that maps from keys to values; they are sometimes called **key-value stores**.\n", + "\n", + "The `shelve` module provides functions for creating and updating a key-value store called a \"shelf\".\n", + "As an example, we'll create a shelf to contain captions for the figures in the `photos` directory.\n", + "We'll use the `config` dictionary to get the name of the directory where we should put the shelf." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "70a6d625", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3e6cfb65", + "metadata": {}, + "source": [ + "We can use `os.makedirs` to create this directory, if it doesn't already exist." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "fd070fa7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6352f83f", + "metadata": {}, + "source": [ + "And `os.path.join` to make a path that includes the name of the directory and the name of the shelf file, `captions`." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f5aeb7ab", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cbb08679", + "metadata": {}, + "source": [ + "Now we can use `shelve.open` to open the shelf file.\n", + "The argument `c` indicates that the file should be created if necessary." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "291ea875", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0e4a2fb3", + "metadata": {}, + "source": [ + "The return value is officially a `DbfilenameShelf` object, more casually called a shelf object.\n", + "\n", + "The shelf object behaves in many ways like a dictionary.\n", + "For example, we can use the bracket operator to add an item, which is a mapping from a key to a value." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "b3f6d6ce", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "36fd5e3a", + "metadata": {}, + "source": [ + "In this example, the key is the path to an image file and the value is a string that describes the image.\n", + "\n", + "We also use the bracket operator to look up a key and get the corresponding value." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "d4e06b19", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e9b252a7", + "metadata": {}, + "source": [ + "If you make another assignment to an existing key, `shelve` replaces the old value." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "89a0936c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "003eacbc", + "metadata": {}, + "source": [ + "Some dictionary methods, like `keys`, `values` and `items`, also work with shelf objects." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "7d0c4cff", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "a9db327d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "056e0bd9", + "metadata": {}, + "source": [ + "We can use the `in` operator to check whether a key appears in the shelf." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "cc81ed95", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "982740b4", + "metadata": {}, + "source": [ + "And we can use a `for` statement to loop through the keys." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "68ff774e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b67a0ddc", + "metadata": {}, + "source": [ + "As with other files, you should close the database when you are done." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "2b411885", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a1e08b41", + "metadata": {}, + "source": [ + "Now if we list the contents of the data directory, we see two files." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "4806720c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "59eb2dde", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "97453006", + "metadata": {}, + "source": [ + "`captions.dat` contains the data we just stored.\n", + "`captions.dir` contains information about the organization of the database that makes it more efficient to access.\n", + "The suffix `dir` stands for \"directory\", but it has nothing to do with the directories we've been working with that contain files." + ] + }, + { + "cell_type": "markdown", + "id": "0033e4f5", + "metadata": { + "tags": [] + }, + "source": [ + "## Storing data structures\n", + "\n", + "In the previous example, the keys and values in the shelf are strings.\n", + "But we can also use a shelf to contain data structures like lists and dictionaries.\n", + "\n", + "As an example, let's revisit the anagram example from an exercise in [Chapter 11](section_exercise_11).\n", + "Recall that we made a dictionary that maps from a sorted string of letters to the\n", + "list of words that can be spelled with those letters.\n", + "For example, the key `'opst'` maps to the list `['opts', 'post', 'pots', 'spot', 'stop', 'tops']`.\n", + "\n", + "We'll use the following function to sort the letters in a word." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "8819b211", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8c24316c", + "metadata": {}, + "source": [ + "And here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "fc8f1434", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7c5d7555", + "metadata": {}, + "source": [ + "Now let's open a shelf called `anagram_map`.\n", + "The argument `'n'` means we should always create a new, empty shelf, even if one already exists." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "f2f17f05", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6b495e79", + "metadata": {}, + "source": [ + "Now we can add an item to the shelf like this." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "b918dcfe", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f058dff1", + "metadata": {}, + "source": [ + "In this item, the key is a string and the value is a list of strings.\n", + "\n", + "Now suppose we find another word that contains the same letters, like `tops`" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "e0d728e6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a215367d", + "metadata": {}, + "source": [ + "The key is the same as in the previous example, so we want to append a second word to the same list of strings.\n", + "Here's how we would do it if `db` were a dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "12e39086", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e1a83f39", + "metadata": {}, + "source": [ + "But if we run that and then look up the key in the shelf, it looks like it has not been updated." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "b0dc42e0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "32a56de9", + "metadata": {}, + "source": [ + "Here's the problem: when we look up the key, we get a list of strings, but if we modify the list of strings, it does not affect the shelf.\n", + "If we want to update the shelf, we have to read the old value, update it, and then write the new value back to the shelf." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "b4105610", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a054f537", + "metadata": {}, + "source": [ + "Now the value in the shelf is updated." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "4f737cd9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ca02c0d7", + "metadata": {}, + "source": [ + "As an exercise, you can finish this example by reading the word list and storing all of the anagrams in a shelf." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "5395780f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b1336e56", + "metadata": { + "tags": [] + }, + "source": [ + "## Checking for equivalent files\n", + "\n", + "Now let's get back to the goal of this chapter: searching for different files that contain the same data.\n", + "One way to check is to read the contents of both files and compare.\n", + "\n", + "If the files contain images, we have to open them with mode `'rb'`, where `'r'` means we want to read the contents and `'b'` indicates **binary mode**.\n", + "In binary mode, the contents are not interpreted as text -- they are treated as a sequence of bytes.\n", + "\n", + "Here's an example that opens and reads an image file." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "a3104fc7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "36687da2", + "metadata": {}, + "source": [ + "The result from `read` is a `bytes` object -- as the name suggests, it contains a sequence of bytes.\n", + "\n", + "In general the contents of an image file and not human-readable.\n", + "But if we read the contents from a second file, we can use the `==` operator to compare." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "fd6dd21d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "36006cd4", + "metadata": {}, + "source": [ + "These two files are not equivalent.\n", + "\n", + "Let's encapsulate what we have so far in a function." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "b1c8643c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b4e08850", + "metadata": {}, + "source": [ + "If we have only two files, this function is a good option.\n", + "But suppose we have a large number of files and we want to know whether any two of them contain the same data.\n", + "It would be inefficient to compare every pair of files.\n", + "\n", + "An alternative is to use a **hash function**, which takes the contents of a file and computes a **digest**, which is usually a large integer.\n", + "If two files contain the same data, they will have the same digest.\n", + "If two files differ, they will *almost always* have different digests.\n", + "\n", + "The `hashlib` module provides several hash functions -- the one we'll use is called `md5`.\n", + "We'll start by using `hashlib.md5` to create a `HASH` object." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "c03198fc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a8e480f0", + "metadata": {}, + "source": [ + "The `HASH` object provides an `update` method that takes the contents of the file as an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "1567ec9e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "86bd6bc0", + "metadata": {}, + "source": [ + "Now we can use `hexdigest` to get the digest as a string of hexadecimal digits that represent an integer in base 16." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "b2e61e76", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b9b4b347", + "metadata": {}, + "source": [ + "The following function encapsulates these steps." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "330def4e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "914b85b2", + "metadata": {}, + "source": [ + "If we hash the contents of a different file, we can confirm that we get a different digest." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "eae2d207", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "deeabdde", + "metadata": {}, + "source": [ + "Now we have almost everything we need to find equivalent files.\n", + "The last step is to search a directory and find all of the images files." + ] + }, + { + "cell_type": "markdown", + "id": "129475df", + "metadata": { + "tags": [] + }, + "source": [ + "## Walking directories\n", + "\n", + "The following function takes as an argument the directory we want to search.\n", + "It uses `listdir` to loop through the contents of the directory.\n", + "When it finds a file, it prints its complete path.\n", + "When it finds a directory, it calls itself recursively to search the subdirectory." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "c84a64db", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "faaf5b25", + "metadata": {}, + "source": [ + "We can use it like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "f1c60f6b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "897c66bf", + "metadata": {}, + "source": [ + "The order of the results depends on details of the operating system." + ] + }, + { + "cell_type": "markdown", + "id": "c7853f18", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "When you are reading and writing files, you might run into problems with whitespace.\n", + "These errors can be hard to debug because whitespace characters are normally invisible.\n", + "For example, here's a string that contains spaces, a tab represented by the sequence `\\t`, and a newline represented by the sequence `\\n`.\n", + "When we print it, we don't see the whitespace characters." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "15d7425a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "49bbebe6", + "metadata": {}, + "source": [ + "The built-in function `repr` can help. It takes any object as an argument and returns a string representation of the object.\n", + "For strings, it represents whitespace characters with backslash sequences." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "61feff85", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "790cf8dd", + "metadata": {}, + "source": [ + "This can be helpful for debugging.\n", + "\n", + "One other problem you might run into is that different systems use different characters to indicate the end of a line. Some systems use a newline, represented `\\n`. Others use a return character, represented `\\r`. \n", + "Some use both. If you move files between different systems, these\n", + "inconsistencies can cause problems.\n", + "\n", + "File name capitalization is another issue you might encounter if you work with different operating systems.\n", + "In macOS and UNIX, file names can contain lowercase and uppercase letters, digits, and most symbols.\n", + "But many Windows applications ignore the difference between lowercase and uppercase letters, and several symbols that are allowed in macOS and UNIX are not allowed in Windows." + ] + }, + { + "cell_type": "markdown", + "id": "cf063639", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**ephemeral:**\n", + "An ephemeral program typically runs for a short time and, when it ends, its data are lost.\n", + "\n", + "**persistent:**\n", + " A persistent program runs indefinitely and keeps at least some of its data in permanent storage.\n", + "\n", + "**directory:**\n", + "A collection of files and other directories.\n", + "\n", + "**current working directory:**\n", + "The default directory used by a program unless another directory is specified.\n", + "\n", + "**path:**\n", + " A string that specifies a sequence of directories, often leading to a file.\n", + "\n", + "**relative path:**\n", + "A path that starts from the current working directory, or some other specified directory.\n", + "\n", + "**absolute path:**\n", + "A path that does not depend on the current directory.\n", + "\n", + "**f-string:**\n", + "A string that has the letter `f` before the opening quotation mark, and contains one or more expressions in curly braces.\n", + "\n", + "**configuration data:**\n", + "Data, often stored in a file, that specifies what a program should do and how.\n", + "\n", + "**serialization:**\n", + "Converting an object to a string.\n", + "\n", + "**deserialization:**\n", + "Converting a string to an object.\n", + "\n", + "**database:**\n", + " A file whose contents are organized to perform certain operations efficiently.\n", + "\n", + "**key-value stores:**\n", + "A database whose contents are organized like a dictionary with keys that correspond to values.\n", + "\n", + "**binary mode:**\n", + "A way of opening a file so the contents are interpreted as sequence of bytes rather than a sequence of characters.\n", + "\n", + "**hash function:**\n", + "A function that takes and object and computes an integer, which is sometimes called a digest.\n", + "\n", + "**digest:**\n", + "The result of a hash function, especially when it is used to check whether two objects are the same." + ] + }, + { + "cell_type": "markdown", + "id": "67941fdd", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "bd885ba1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9a537173", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "There are several topics that came up in this chapter that I did not explain in detail.\n", + "Here are some questions you can ask a virtual assistant to get more information. \n", + "\n", + "* \"What are the differences between ephemeral and persistent programs?\"\n", + "\n", + "* \"What are some examples of persistent programs?\"\n", + "\n", + "* \"What's the difference between a relative path and an absolute path?\"\n", + "\n", + "* \"Why does the `yaml` module have functions called `load` and `safe_load`?\"\n", + "\n", + "* \"When I write a Python shelf, what are the files with suffixes `dat` and `dir`?\"\n", + "\n", + "* \"Other than key-values stores, what other kinds of databases are there?\"\n", + "\n", + "* \"When I read a file, what's the difference between binary mode and text mode?\"\n", + "\n", + "* \"What are the differences between a bytes object and a string?\"\n", + "\n", + "* \"What is a hash function?\"\n", + "\n", + "* \"What is an MD5 digest?\"\n", + "\n", + "As always, if you get stuck on any of the following exercises, consider asking a VA for help. Along with your question, you might want to paste in the relevant functions from this chapter." + ] + }, + { + "cell_type": "markdown", + "id": "7586e1e9", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `replace_all` that takes as arguments a pattern string, a replacement string, and two filenames.\n", + "It should read the first file and write the contents into the second file (creating it if necessary).\n", + "If the pattern string appears anywhere in the contents, it should be replaced with the replacement string." + ] + }, + { + "cell_type": "markdown", + "id": "85844afb", + "metadata": { + "tags": [] + }, + "source": [ + "Here's an outline of the function to get you started." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "1e598e70", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "d3774d1a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7f37245c", + "metadata": {}, + "source": [ + "To test your function, read the file `photos/notes.txt`, replace `'photos'` with `'images'`, and write the result to the file `photos/new_notes.txt`." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "3b80dfd8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "4b67579f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "1d990225", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7b2589a4", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "In [a previous section](section_storing_data_structure), we used the `shelve` module to make a key-value store that maps from a sorted string of letters to a list of anagrams.\n", + "To finish the example, write a function called `add_word` that takes as arguments a string and a shelf object.\n", + "\n", + "It should sort the letters of the word to make a key, then check whether the key is already in the shelf. If not, it should make a list that contains the new word and add it to the shelf. If so, it should append the new word to the existing value. " + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "1c2fff95", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "07cb454f", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this loop to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "8008cde6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "a6b5e51a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "40342366", + "metadata": { + "tags": [] + }, + "source": [ + "If everything is working, you should be able to look up a key like `'opst'` and get a list of words that can be spelled with those letters." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "ffefe13e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "7eb54fbc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "ac784df7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "228e977c", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "In a large collection of files, there may be more than one copy of the same file, stored in different directories or with different file names.\n", + "The goal of this exercise is to search for duplicates.\n", + "As an example, we'll work with image files in the `photos` directory.\n", + "\n", + "Here's how it will work:\n", + "\n", + "* We'll use the `walk` function from [](section_walking_directories) to search this directory for files that end with one of the extensions in `config['extensions']`.\n", + "\n", + "* For each file, we'll use `md5_digest` from [](section_md5_digest) to compute a digest of the contents.\n", + "\n", + "* Using a shelf, we'll make a mapping from each digest to a list of paths with that digest.\n", + "\n", + "* Finally, we'll search the shelf for any digests that map to multiple files.\n", + "\n", + "* If we find any, we'll use `same_contents` to confirm that the files contain the same data." + ] + }, + { + "cell_type": "markdown", + "id": "8f5365da", + "metadata": {}, + "source": [ + "I'll suggest some functions to write first, then we'll bring it all together.\n", + "\n", + "1. To identify image files, write a function called `is_image` that takes a path and a list of file extensions, and returns `True` if the path ends with one of the extensions in the list. Hint: Use `os.path.splitext` -- or ask a virtual assistant to write this function for you." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "03b6acf9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dde4b5c8", + "metadata": { + "tags": [] + }, + "source": [ + "You can use `doctest` to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "21c33092", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1a7c8f49", + "metadata": {}, + "source": [ + "2. Write a function called `add_path` that takes as arguments a path and a shelf. It should use `md5_digest` to compute a digest of the file contents. Then it should update the shelf, either creating a new item that maps from the digest to a list containing the path, or appending the path to the list if it exists." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "31c056a9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "08223a21", + "metadata": {}, + "source": [ + "3. Write a version of `walk` called `walk_images` that takes a directory and walks through the files in the directory and its subdirectories. For each file, it should use `is_image` to check whether it's an image file and `add_path` to add it to the shelf." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "e54d6993", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1ea76a52", + "metadata": {}, + "source": [ + "When everything is working, you can use the following program to create the shelf, search the `photos` directory and add paths to the shelf, and then check whether there are multiple files with the same digest." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "c31ba5b6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "102d4d10", + "metadata": {}, + "source": [ + "You should find one pair of files that have the same digest.\n", + "Use `same_contents` to check whether they contain the same data." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "d7c7e679", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6a6b5a7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap14.ipynb b/blank/chap14.ipynb new file mode 100644 index 0000000..38aaffc --- /dev/null +++ b/blank/chap14.ipynb @@ -0,0 +1,1458 @@ +{ + "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": "ae5a86f8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e826e661", + "metadata": {}, + "source": [ + "# Classes and Functions\n", + "\n", + "At this point you know how to use functions to organize code and how to use built-in types to organize data.\n", + "The next step is **object-oriented programming**, which uses programmer-defined types to organize both code and data.\n", + "\n", + "Object-oriented programming is a big topic, so we will proceed gradually.\n", + "In this chapter, we'll start with code that is not idiomatic -- that is, it is not the kind of code experienced programmers write -- but it is a good place to start.\n", + "In the next two chapters, we will use additional features to write more idiomatic code." + ] + }, + { + "cell_type": "markdown", + "id": "6b414d4a", + "metadata": {}, + "source": [ + "## Programmer-defined types\n", + "\n", + "We have used many of Python's built-in types -- now we will define a new type.\n", + "As a first example, we'll create a type called `Time` that represents a time of day.\n", + "A programmer-defined type is also called a **class**.\n", + "A class definition looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c9c99d2c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e2414cd2", + "metadata": {}, + "source": [ + "The header indicates that the new class is called `Time`.\n", + "The body is a docstring that explains what the class is for.\n", + "Defining a class creates a **class object**.\n", + "\n", + "The class object is like a factory for creating objects.\n", + "To create a `Time` object, you call `Time` as if it were a function." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d318001a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f63247d4", + "metadata": {}, + "source": [ + "The result is a new object whose type is `__main__.Time`, where `__main__` is the name of the module where `Time` is defined." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f37d67fd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "14d0c96a", + "metadata": {}, + "source": [ + "When you print an object, Python tells you what type it is and where it is stored in memory (the prefix `0x` means that the following number is in hexadecimal)." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e2bd114a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b6445414", + "metadata": {}, + "source": [ + "Creating a new object is called **instantiation**, and the object is an **instance** of the class." + ] + }, + { + "cell_type": "markdown", + "id": "4c3768ec", + "metadata": {}, + "source": [ + "## Attributes\n", + "\n", + "An object can contain variables, which are called **attributes** and pronounced with the emphasis on the first syllable, like \"AT-trib-ute\", rather than the second syllable, like \"a-TRIB-ute\".\n", + "We can create attributes using dot notation." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e166701a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b3fd8858", + "metadata": {}, + "source": [ + "This example creates attributes called `hour`, `minute`, and `second`, which contain the hours, minutes, and seconds of the time `11:59:01`, which is lunch time as far as I am concerned.\n", + "\n", + "The following diagram shows the state of `lunch` and its attributes after these assignments. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "3eb47826", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6702a353", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d9df5b48", + "metadata": {}, + "source": [ + "The variable `lunch` refers to a `Time` object, which contains three attributes. \n", + "Each attribute refers to an integer.\n", + "A state diagram like this -- which shows an object and its attributes -- is called an **object diagram**.\n", + "\n", + "You can read the value of an attribute using the dot operator." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4c4eff2b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5ccfaea0", + "metadata": {}, + "source": [ + "You can use an attribute as part of any expression." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7ac6db21", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c5e6725b", + "metadata": {}, + "source": [ + "And you can use the dot operator in an expression in an f-string." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1ecdc091", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e70671d2", + "metadata": {}, + "source": [ + "But notice that the previous example is not in the standard format.\n", + "To fix it, we have to print the `minute` and `second` attributes with a leading zero.\n", + "We can do that by extending the expressions in curly braces with a **format specifier**.\n", + "In the following example, the format specifiers indicate that `minute` and `second` should be displayed with at least two digits and a leading zero if needed." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a8a45573", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bcbea13a", + "metadata": {}, + "source": [ + "We'll use this f-string to write a function that displays the value of a `Time`object.\n", + "You can pass an object as an argument in the usual way.\n", + "For example, the following function takes a `Time` object as an argument. " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "fc77feb2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3b8ccbed", + "metadata": {}, + "source": [ + "When we call it, we can pass `lunch` as an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "59b7f4f4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "18826e53", + "metadata": {}, + "source": [ + "## Objects as return values\n", + "\n", + "Functions can return objects. For example, `make_time` takes parameters called `hour`, `minute`, and `second`, stores them as attributes in a `Time` object, and returns the new object." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "fde15b59", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d8a6acca", + "metadata": {}, + "source": [ + "It might be surprising that the parameters have the same names as the attributes, but that's a common way to write a function like this.\n", + "Here's how we use `make_time` to create a `Time` object.`" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f4199d7f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "05720bcb", + "metadata": {}, + "source": [ + "## Objects are mutable\n", + "\n", + "Suppose you are going to a screening of a movie, like *Monty Python and the Holy Grail*, which starts at `9:20 PM` and runs for `92` minutes, which is one hour `32` minutes.\n", + "What time will the movie end?\n", + "\n", + "First, we'll create a `Time` object that represents the start time." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "57847af3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "001bcda9", + "metadata": {}, + "source": [ + "To find the end time, we can modify the attributes of the `Time` object, adding the duration of the movie." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "f3637b10", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7007ab61", + "metadata": {}, + "source": [ + "The movie will be over at 10:52 PM.\n", + "\n", + "Let's encapsulate this computation in a function and generalize it to take the duration of the movie in three parameters: `hours`, `minutes`, and `seconds`." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "3468f4d0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a51913e2", + "metadata": {}, + "source": [ + "Here is an example that demonstrates the effect." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "ad8177ad", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "42d7de02", + "metadata": {}, + "source": [ + "The following stack diagram shows the state of the program just before `increment_time` modifies the object." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "6f90c060", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "93a1db71", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d1e27667", + "metadata": {}, + "source": [ + "Inside the function, `time` is an alias for `start`, so when `time` is modified, `start` changes.\n", + "\n", + "This function works, but after it runs, we're left with a variable named `start` that refers to an object that represents the *end* time, and we no longer have an object that represents the start time.\n", + "It would be better to leave `start` unchanged and make a new object to represent the end time.\n", + "We can do that by copying `start` and modifying the copy." + ] + }, + { + "cell_type": "markdown", + "id": "0128f850", + "metadata": {}, + "source": [ + "## Copying\n", + "\n", + "The `copy` module provides a function called `copy` that can duplicate any object.\n", + "We can import it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "9f74834d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "940adbeb", + "metadata": {}, + "source": [ + "To see how it works, let's start with a new `Time` object that represents the start time of the movie." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "770c077a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "514f05b9", + "metadata": {}, + "source": [ + "And make a copy." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "edced6e5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "87d8956b", + "metadata": {}, + "source": [ + "Now `start` and `end` contain the same data." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "509c3640", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e75c1e09", + "metadata": {}, + "source": [ + "But the `is` operator confirms that they are not the same object." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "60d812f7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "22b68a3f", + "metadata": {}, + "source": [ + "Let's see what the `==` operator does." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "4d504362", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "78ebf931", + "metadata": {}, + "source": [ + "You might expect `==` to yield `True` because the objects contain the same data.\n", + "But for programmer-defined classes, the default behavior of the `==` operator is the same as the `is` operator -- it checks identity, not equivalence." + ] + }, + { + "cell_type": "markdown", + "id": "a3934fdd-d4cd-41e0-86e6-5bb78d0886a7", + "metadata": {}, + "source": [ + "## Pure functions\n", + "\n", + "We can use `copy` to write pure functions that don't modify their parameters.\n", + "For example, here's a function that takes a `Time` object and a duration in hours, minutes and seconds.\n", + "It makes a copy of the original object, uses `increment_time` to modify the copy, and returns it." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "85090d3e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c181af12", + "metadata": {}, + "source": [ + "Here's how we use it." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "1d9cf4da", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "54b1ca4a", + "metadata": {}, + "source": [ + "The return value is a new object representing the end time of the movie.\n", + "And we can confirm that `start` is unchanged." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "9fe30d71", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1233b2db", + "metadata": {}, + "source": [ + "`add_time` is a **pure function** because it does not modify any of the objects passed to it as arguments and its only effect is to return a value.\n", + "\n", + "Anything that can be done with impure functions can also be done with pure functions.\n", + "In fact, some programming languages only allow pure functions.\n", + "Programs that use pure functions might be less error-prone, but impure functions are sometimes convenient and can be more efficient.\n", + "\n", + "In general, I suggest you write pure functions whenever it is reasonable and resort to impure functions only if there is a compelling advantage.\n", + "This approach might be called a **functional programming style**." + ] + }, + { + "cell_type": "markdown", + "id": "9d9fabbc", + "metadata": {}, + "source": [ + "## Prototype and patch\n", + "\n", + "In the previous example, `increment_time` and `add_time` seem to work, but if we try another example, we'll see that they are not quite correct.\n", + "\n", + "Suppose you arrive at the theater and discover that the movie starts at `9:40`, not `9:20`.\n", + "Here's what happens when we compute the updated end time." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "57a96bf9-7d7b-4715-a4b3-2dfad1beb670", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c712ebf7-7e52-490e-91d7-5f1c83334de0", + "metadata": {}, + "source": [ + "The result is not a valid time.\n", + "The problem is that `increment_time` does not deal with cases where the number of seconds or minutes adds up to more than `60`.\n", + "\n", + "Here's an improved version that checks whether `second` exceeds or equals `60` -- if so, it increments `minute` -- then checks whether `minute` exceeds or equals `60` -- if so, it increments `hour`." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "5bada1df-be0a-4f6a-8fe3-d92bd937dc70", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c133c5d8", + "metadata": {}, + "source": [ + "Fixing `increment_time` also fixes `add_time`, which uses it.\n", + "So now the previous example works correctly." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "a139b64b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a2f644a6-ca43-494e-af14-6e845b3d7973", + "metadata": {}, + "source": [ + "But this function is still not correct, because the arguments might be bigger than `60`.\n", + "For example, suppose we are given the run time as `92` minutes, rather than `1` hours and `32` minutes.\n", + "We might call `add_time` like this." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "8c9384cb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "72e0a08b", + "metadata": {}, + "source": [ + "The result is not a valid time.\n", + "So let's try a different approach, using the `divmod` function.\n", + "We'll make a copy of `start` and modify it by incrementing the `minute` attribute." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "47b04507", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c56355bc", + "metadata": {}, + "source": [ + "Now `minute` is `132`, which is `2` hours and `12` minutes.\n", + "We can use `divmod` to divide by `60` and return the number of whole hours and the number of minutes left over." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "8ce8f8bc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "43204703", + "metadata": {}, + "source": [ + "Now `minute` is correct, and we can add the hours to `hour`." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "90445645", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a68ae1cd", + "metadata": {}, + "source": [ + "The result is a valid time.\n", + "We can do the same thing with `hour` and `second`, and encapsulate the whole process in a function." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "0a9653a2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7437113a", + "metadata": {}, + "source": [ + "With this version of `increment_time`, `add_time` works correctly, even if the arguments exceed `60`." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "694cfdd1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7c6329b2", + "metadata": {}, + "source": [ + "This section demonstrates a program development plan I call **prototype and patch**.\n", + "We started with a simple prototype that worked correctly for the first example.\n", + "Then we tested it with more difficult examples -- when we found an error, we modified the program to fix it, like putting a patch on tire with a puncture.\n", + "\n", + "This approach can be effective, especially if you don't yet have a deep understanding of the problem.\n", + "But incremental corrections can generate code that is unnecessarily complicated -- since it deals with many special cases -- and unreliable -- since it is hard to know if you have\n", + "found all the errors." + ] + }, + { + "cell_type": "markdown", + "id": "39031461-49a9-4eba-a075-ef49a6f5552b", + "metadata": {}, + "source": [ + "## Design-first development\n", + "\n", + "An alternative plan is **design-first development**, which involves more planning before prototyping. In a design-first process, sometimes a high-level insight into the problem makes the programming much easier.\n", + "\n", + "In this case, the insight is that we can think of a `Time` object as a three-digit number in base 60 -- also known as sexagesimal.\n", + "The `second` attribute is the \"ones column\", the `minute` attribute is the \"sixties column\",\n", + "and the `hour` attribute is the \"thirty-six hundreds column\".\n", + "When we wrote `increment_time`, we were effectively doing addition in base 60, which is why we had to carry from one column to the next.\n", + "\n", + "This observation suggests another approach to the whole problem -- we can convert `Time` objects to integers and take advantage of the fact that Python knows how to do integer arithmetic.\n", + "\n", + "Here is a function that converts from a `Time` to an integer." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "a4427f06-10f3-478f-af4b-888297ee59ac", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c7e7789e", + "metadata": {}, + "source": [ + "The result is the number of seconds since the beginning of the day.\n", + "For example, `01:01:01` is `1` hour, `1` minute and `1` second from the beginning of the day, with is the sum of `3600` seconds, `60` seconds, and `1` second." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "e71f9661", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6ea525c8-4547-4bde-91c3-17f45add1bf8", + "metadata": {}, + "source": [ + "And here's a function that goes in the other direction -- converting an integer to a `Time` object -- using the `divmod` function." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "7a93edcc-de21-43b1-b0a9-1bcbc7f6125c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4706b5df", + "metadata": {}, + "source": [ + "We can test it by converting the previous example back to a `Time`." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "967fc3c2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0c2b8469-d4a7-46f9-a0a1-f2a6c1595183", + "metadata": {}, + "source": [ + "Using these functions, we can write a more concise version of `add_time`." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "0278a042-9e5b-460f-bd26-2fa319e7193a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cb560257", + "metadata": {}, + "source": [ + "The first line converts the arguments to a `Time` object called `duration`.\n", + "The second line converts `time` and `duration` to seconds and adds them.\n", + "The third line converts the sum to a `Time` object and returns it.\n", + "\n", + "Here's how it works." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "ee78ffbc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "db762aa8-4aab-4c17-a88d-72c5048f18c0", + "metadata": {}, + "source": [ + "In some ways, converting from base 60 to base 10 and back is harder than\n", + "just dealing with times. Base conversion is more abstract; our intuition\n", + "for dealing with time values is better.\n", + "\n", + "But if we have the insight to treat times as base 60 numbers -- and invest the effort to write the conversion functions `time_to_int` and `int_to_time` -- we get a program that is shorter, easier to read and debug, and more reliable.\n", + "\n", + "It is also easier to add features later. For example, imagine subtracting two `Time` objects to find the duration between them.\n", + "The naive approach is to implement subtraction with borrowing.\n", + "Using the conversion functions is easier and more likely to be correct.\n", + "\n", + "Ironically, sometimes making a problem harder -- or more general -- makes it easier, because there are fewer special cases and fewer opportunities for error." + ] + }, + { + "cell_type": "markdown", + "id": "a0d23d08", + "metadata": { + "tags": [] + }, + "source": [ + "## Debugging\n", + "\n", + "Python provides several built-in functions that are useful for testing and debugging programs that work with objects.\n", + "For example, if you are not sure what type an object is, you can ask." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "652bee8f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7ec0eabf", + "metadata": {}, + "source": [ + "You can also use `isinstance` to check whether an object is an instance of a particular class." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "3ab974e4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4f453fe9", + "metadata": {}, + "source": [ + "If you are not sure whether an object has a particular attribute, you\n", + "can use the built-in function `hasattr`." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "5f80e5ad", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a0131d84", + "metadata": {}, + "source": [ + "To get all of the attributes, and their values, in a dictionary, you can use `vars`." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "2a102f0f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f1a443c8", + "metadata": {}, + "source": [ + "The `structshape` module, which we saw in [Chapter 11](section_debugging_11), also works with programmer-defined types." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "b71f46d8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "1e6498a8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "501436c0-6634-415f-be84-2d130232b2b8", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**object-oriented programming:**\n", + "A style of programming that uses objects to organize code and data.\n", + "\n", + "**class:**\n", + " A programmer-defined type. A class definition creates a new class object.\n", + "\n", + "**class object:**\n", + "An object that represents a class -- it is the result of a class definition.\n", + "\n", + "**instantiation:**\n", + "The process of creating an object that belongs to a class.\n", + "\n", + "**instance:**\n", + " An object that belongs to a class.\n", + "\n", + "**attribute:**\n", + " A variable associated with an object, also called an instance variable.\n", + "\n", + "**object diagram:**\n", + "A graphical representation of an object, its attributes, and their values.\n", + "\n", + "**format specifier:**\n", + "In an f-string, a format specifier determines how a value is converted to a string.\n", + "\n", + "**pure function:**\n", + "A function that does not modify its parameters or have any effect other than returning a value.\n", + "\n", + "**functional programming style:**\n", + "A way of programming that uses pure functions whenever possible.\n", + "\n", + "**prototype and patch:**\n", + "A way of developing programs by starting with a rough draft and gradually adding features and fixing bugs.\n", + "\n", + "**design-first development:**\n", + "A way of developing programs with more careful planning that prototype and patch." + ] + }, + { + "cell_type": "markdown", + "id": "09dd41c1", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "ab3d0104", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "da0aea86", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "There is a lot of new vocabulary in this chapter.\n", + "A conversation with a virtual assistant can help solidify your understanding.\n", + "Consider asking:\n", + "\n", + "* \"What is the difference between a class and a type?\"\n", + "\n", + "* \"What is the difference between an object and an instance?\"\n", + "\n", + "* \"What is the difference between a variable and an attribute?\"\n", + "\n", + "* \"What are the pros and cons of pure functions compared to impure functions?\"\n", + "\n", + "Because we are just getting started with object oriented programming, the code in this chapter is not idiomatic -- it is not the kind of code experienced programmers write.\n", + "If you ask a virtual assistant for help with the exercises, you will probably see features we have not covered yet.\n", + "In particular, you are likely to see a method called `__init__` used to initialize the attributes of an instance.\n", + "\n", + "If these features make sense to you, go ahead and use them.\n", + "But if not, be patient -- we will get there soon.\n", + "In the meantime, see if you can solve the following exercises using only the features we have covered so far.\n", + "\n", + "Also, in this chapter we saw one example of a format specifier. For more information, ask \"What format specifiers can be used in a Python f-string?\"" + ] + }, + { + "cell_type": "markdown", + "id": "c85eab62", + "metadata": {}, + "source": [ + "## Exercises\n" + ] + }, + { + "cell_type": "markdown", + "id": "bcdab7d6", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `subtract_time` that takes two `Time` objects and returns the interval between them in seconds -- assuming that they are two times during the same day." + ] + }, + { + "cell_type": "markdown", + "id": "5033ee5f", + "metadata": { + "tags": [] + }, + "source": [ + "Here's an outline of the function to get you started." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "7d898f43", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "f1b54959", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "73334265", + "metadata": { + "tags": [] + }, + "source": [ + "You can use `doctest` to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "5a25a3de", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c3189549", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `is_after` that takes two `Time` objects and returns `True` if the first time is later in the day than the second, and `False` otherwise." + ] + }, + { + "cell_type": "markdown", + "id": "fd4ac340", + "metadata": { + "tags": [] + }, + "source": [ + "Here's an outline of the function to get you started." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "05499ffa", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "12b4ad17", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f9da8ede", + "metadata": { + "tags": [] + }, + "source": [ + "You can use `doctest` to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "4e580404", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "16dff862", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Here's a definition for a `Date` class that represents a date -- that is, a year, month, and day of the month." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "c5de60ed", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3311fa97", + "metadata": {}, + "source": [ + "1. Write a function called `make_date` that takes `year`, `month`, and `day` as parameters, makes a `Date` object, assigns the parameters to attributes, and returns the result the new object. Create an object that represents June 22, 1933.\n", + "\n", + "2. Write a function called `print_date` that takes a `Date` object, uses an f-string to format the attributes, and prints the result. If you test it with the `Date` you created, the result should be `1933-06-22`.\n", + "\n", + "3. Write a function called `is_after` that takes two `Date` objects as parameters and returns `True` if the first comes after the second. Create a second object that represents September 17, 1933, and check whether it comes after the first object.\n", + "\n", + "Hint: You might find it useful to write a function called `date_to_tuple` that takes a `Date` object and returns a tuple that contains its attributes in year, month, day order." + ] + }, + { + "cell_type": "markdown", + "id": "90b10ca5", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this function outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "9e16bcde", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "ff95300b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "20c5edf8", + "metadata": { + "tags": [] + }, + "source": [ + "You can use these examples to test `make_date`." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "62180007", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "2d70104e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "16ff5bef", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this function outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "2cc0653e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "0b8f63df", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c36c432e", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this example to test `print_date`." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "2d0a026d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "17565b1e", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this function outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "70413f48", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "b244a057", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "5fab04bd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d9b5dd67", + "metadata": { + "tags": [] + }, + "source": [ + "You can use these examples to test `is_after`." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "59166d30", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "c33706ee", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d6f1cc2f", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap15.ipynb b/blank/chap15.ipynb new file mode 100644 index 0000000..2ffde33 --- /dev/null +++ b/blank/chap15.ipynb @@ -0,0 +1,924 @@ +{ + "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": "3161b50b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fa22117f", + "metadata": {}, + "source": [ + "# Classes and Methods\n", + "\n", + "Python is an **object-oriented language** -- that is, it provides features that support object-oriented programming, which has these defining characteristics:\n", + "\n", + "- Most of the computation is expressed in terms of operations on objects.\n", + "\n", + "- Objects often represent things in the real world, and methods often correspond to the ways things in the real world interact.\n", + "\n", + "- Programs include class and method definitions.\n", + "\n", + "For example, in the previous chapter we defined a `Time` class that corresponds to the way people record the time of day, and we defined functions that correspond to the kinds of things people do with times.\n", + "But there was no explicit connection between the definition of the `Time` class and the function definitions that follow.\n", + "We can make the connection explicit by rewriting a function as a **method**, which is defined inside a class definition." + ] + }, + { + "cell_type": "markdown", + "id": "9857823a", + "metadata": {}, + "source": [ + "## Defining methods\n", + "\n", + "In the previous chapter we defined a class named `Time` and wrote a function named `print_time` that displays a time of day." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ee093ca4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a89ddf58", + "metadata": {}, + "source": [ + "To make `print_time` a method, all we have to do is move the function\n", + "definition inside the class definition. Notice the change in\n", + "indentation.\n", + "\n", + "At the same time, we'll change the name of the parameter from `time` to `self`.\n", + "This change is not necessary, but it is conventional for the first parameter of a method to be named `self`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "fd26a1bc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8da4079c", + "metadata": {}, + "source": [ + "To call this method, you have to pass a `Time` object as an argument.\n", + "Here's the function we'll use to make a `Time` object." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5fc157ea", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c6ad4e12", + "metadata": {}, + "source": [ + "And here's a `Time` instance." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "35acd8e6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bbbcd333", + "metadata": {}, + "source": [ + "Now there are two ways to call `print_time`. The first (and less common)\n", + "way is to use function syntax." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f755081c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2eb0847e", + "metadata": {}, + "source": [ + "In this version, `Time` is the name of the class, `print_time` is the name of the method, and `start` is passed as a parameter.\n", + "The second (and more idiomatic) way is to use method syntax:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d6f91aec", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c80c40f0", + "metadata": {}, + "source": [ + "In this version, `start` is the object the method is invoked on, which is called the **receiver**, based on the analogy that invoking a method is like sending a message to an object.\n", + "\n", + "Regardless of the syntax, the behavior of the method is the same.\n", + "The receiver is assigned to the first parameter, so inside the method, `self` refers to the same object as `start`." + ] + }, + { + "cell_type": "markdown", + "id": "8deb6c34", + "metadata": {}, + "source": [ + "## Another method\n", + "\n", + "Here's the `time_to_int` function from the previous chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "24c4c985", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "144e043f", + "metadata": {}, + "source": [ + "And here's a version rewritten as a method.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "dde6f15f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e3a721ab", + "metadata": {}, + "source": [ + "The first line uses the special command `add_method_to`, which adds a method to a previously-defined class.\n", + "This command works in a Jupyter notebook, but it is not part of Python, so it won't work in other environments.\n", + "Normally, all methods of a class are inside the class definition, so they get defined at the same time as the class.\n", + "But for this book, it is helpful to define one method at a time.\n", + "\n", + "As in the previous example, the method definition is indented and the name of the parameter is `self`.\n", + "Other than that, the method is identical to the function.\n", + "Here's how we invoke it." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8943fa0a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "14565505", + "metadata": {}, + "source": [ + "It is common to say that we \"call\" a function and \"invoke\" a method, but they mean the same thing." + ] + }, + { + "cell_type": "markdown", + "id": "7bc24683", + "metadata": {}, + "source": [ + "## Static methods\n", + "\n", + "As another example, let's consider the `int_to_time` function.\n", + "Here's the version from the previous chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8547b1c2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2b77c2a0", + "metadata": {}, + "source": [ + "This function takes `seconds` as a parameter and returns a new `Time` object.\n", + "If we transform it into a method of the `Time` class, we have to invoke it on a `Time` object.\n", + "But if we're trying to create a new `Time` object, what are we supposed to invoke it on?\n", + "\n", + "We can solve this chicken-and-egg problem using a **static method**, which is a method that does not require an instance of the class to be invoked.\n", + "Here's how we rewrite this function as a static method." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "b233669c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7e2e788", + "metadata": {}, + "source": [ + "Because it is a static method, it does not have `self` as a parameter.\n", + "To invoke it, we use `Time`, which is the class object." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7e88f06b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d2f4fd5a", + "metadata": {}, + "source": [ + "The result is a new object that represents 9:40." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "8c9f66b0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e6a18c76", + "metadata": {}, + "source": [ + "Now that we have `Time.from_seconds`, we can use it to write `add_time` as a method.\n", + "Here's the function from the previous chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "c600d536", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8e56da48", + "metadata": {}, + "source": [ + "And here's a version rewritten as a method." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "c6fa0176", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b784a4ea", + "metadata": {}, + "source": [ + "`add_time` has `self` as a parameter because it is not a static method.\n", + "It is an ordinary method -- also called an **instance method**.\n", + "To invoke it, we need a `Time` instance." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e17b2ad7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f1c806a9", + "metadata": {}, + "source": [ + "## Comparing Time objects\n", + "\n", + "As one more example, let's write `is_after` as a method.\n", + "Here's the `is_after` function, which is a solution to an exercise in the previous chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "971eebbb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8e7153e8", + "metadata": {}, + "source": [ + "And here it is as a method." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "90d7234d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "50815aec", + "metadata": {}, + "source": [ + "Because we're comparing two objects, and the first parameter is `self`, we'll call the second parameter `other`.\n", + "To use this method, we have to invoke it on one object and pass the\n", + "other as an argument." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "19e3d639", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "cf97e358", + "metadata": {}, + "source": [ + "One nice thing about this syntax is that it almost reads like a question,\n", + "\"`end` is after `start`?\"" + ] + }, + { + "cell_type": "markdown", + "id": "15a17fce", + "metadata": {}, + "source": [ + "## The `__str__` method\n", + "\n", + "When you write a method, you can choose almost any name you want.\n", + "However, some names have special meanings.\n", + "For example, if an object has a method named `__str__`, Python uses that method to convert the object to a string.\n", + "For example, here is a `__str__` method for a time object." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "f935a999", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b056b729", + "metadata": {}, + "source": [ + "This method is similar to `print_time`, from the previous chapter, except that it returns the string rather than printing it.\n", + "\n", + "You can invoke this method in the usual way." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "61d7275d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "76092a0c", + "metadata": {}, + "source": [ + "But Python can also invoke it for you.\n", + "If you use the built-in function `str` to convert a `Time` object to a string, Python uses the `__str__` method in the `Time` class." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "b6dcc0c2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8a26caa8", + "metadata": {}, + "source": [ + "And it does the same if you print a `Time` object." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "6e1e6fb3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "97eb30c2", + "metadata": {}, + "source": [ + "Methods like `__str__` are called **special methods**.\n", + "You can identify them because their names begin and end with two underscores." + ] + }, + { + "cell_type": "markdown", + "id": "e01e9673", + "metadata": {}, + "source": [ + "## The __init__ method\n", + "\n", + "The most special of the special methods is `__init__`, so-called because it initializes the attributes of a new object.\n", + "An `__init__` method for the `Time` class might look like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7ddcca8a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8ba624c3", + "metadata": {}, + "source": [ + "Now when we instantiate a `Time` object, Python invokes `__init__`, and passes along the arguments.\n", + "So we can create an object and initialize the attributes at the same time." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "afd652c6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "55e0e296", + "metadata": {}, + "source": [ + "In this example, the parameters are optional, so if you call `Time` with no arguments,\n", + "you get the default values." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "8a852588", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bacb036d", + "metadata": {}, + "source": [ + "If you provide one argument, it overrides `hour`:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "0ff75ace", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "37edb221", + "metadata": {}, + "source": [ + "If you provide two arguments, they override `hour` and `minute`." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "b8e948bc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "277de217", + "metadata": {}, + "source": [ + "And if you provide three arguments, they override all three default\n", + "values.\n", + "\n", + "When I write a new class, I almost always start by writing `__init__`, which makes it easier to create objects, and `__str__`, which is useful for debugging." + ] + }, + { + "cell_type": "markdown", + "id": "94bbbd7d", + "metadata": {}, + "source": [ + "## Operator overloading\n", + "\n", + "By defining other special methods, you can specify the behavior of\n", + "operators on programmer-defined types. For example, if you define a\n", + "method named `__add__` for the `Time` class, you can use the `+`\n", + "operator on Time objects.\n", + "\n", + "Here is an `__add__` method." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "0d140036", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0221c9ad", + "metadata": {}, + "source": [ + "We can use it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "280acfce", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7cc7866e", + "metadata": {}, + "source": [ + "There is a lot happening when we run these three lines of code:\n", + "\n", + "* When we instantiate a `Time` object, the `__init__` method is invoked.\n", + "\n", + "* When we use the `+` operator with a `Time` object, its `__add__` method is invoked.\n", + "\n", + "* And when we print a `Time` object, its `__str__` method is invoked.\n", + "\n", + "Changing the behavior of an operator so that it works with programmer-defined types is called **operator overloading**.\n", + "For every operator, like `+`, there is a corresponding special method, like `__add__`. " + ] + }, + { + "cell_type": "markdown", + "id": "b7299e62", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "A `Time` object is valid if the values of `minute` and `second` are between `0` and `60` -- including `0` but not `60` -- and if `hour` is positive.\n", + "Also, `hour` and `minute` should be integer values, but we might allow `second` to have a fraction part.\n", + "Requirements like these are called **invariants** because they should always be true.\n", + "To put it a different way, if they are not true, something has gone wrong.\n", + "\n", + "Writing code to check invariants can help detect errors and find their causes.\n", + "For example, you might have a method like `is_valid` that takes a Time object and returns `False` if it violates an invariant." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "6eb34442", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a10ad3db", + "metadata": {}, + "source": [ + "Then, at the beginning of each method you can check the arguments to make sure they are valid." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "57d86843", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e7c78e9a", + "metadata": {}, + "source": [ + "The `assert` statement evaluates the expression that follows. If the result is `True`, it does nothing; if the result is `False`, it causes an `AssertionError`.\n", + "Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "5452888b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "56680d97", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "18bd34ad", + "metadata": {}, + "source": [ + "`assert` statements are useful because they distinguish code that deals with normal conditions from code that checks for errors." + ] + }, + { + "cell_type": "markdown", + "id": "58b86fbe", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**object-oriented language:**\n", + "A language that provides features to support object-oriented programming, notably user-defined types.\n", + "\n", + "**method:**\n", + "A function that is defined inside a class definition and is invoked on instances of that class.\n", + "\n", + "**receiver:**\n", + "The object a method is invoked on.\n", + "\n", + "**static method:**\n", + "A method that can be invoked without an object as receiver.\n", + "\n", + "**instance method:**\n", + "A method that must be invoked with an object as receiver.\n", + "\n", + "**special method:**\n", + "A method that changes the way operators and some functions work with an object.\n", + "\n", + "**operator overloading:**\n", + "The process of using special methods to change the way operators with with user-defined types.\n", + "\n", + "**invariant:**\n", + " A condition that should always be true during the execution of a program." + ] + }, + { + "cell_type": "markdown", + "id": "796adf5c", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3115ea33", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "25cd6888", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "For more information about static methods, ask a virtual assistant:\n", + "\n", + "* \"What's the difference between an instance method and a static method?\"\n", + "\n", + "* \"Why are static methods called static?\"\n", + "\n", + "If you ask a virtual assistant to generate a static method, the result will probably begin with `@staticmethod`, which is a \"decorator\" that indicates that it is a static method.\n", + "Decorators are not covered in this book, but if you are curious, you can ask a VA for more information.\n", + "\n", + "In this chapter we rewrote several functions as methods.\n", + "Virtual assistants are generally good at this kind of code transformation.\n", + "As an example, paste the following function into a VA and ask it, \"Rewrite this function as a method of the `Time` class.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "133d7679", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fc9f135b-e242-4ef6-83eb-8e028235c07b", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "In the previous chapter, a series of exercises asked you to write a `Date` class and several functions that work with `Date` objects.\n", + "Now let's practice rewriting those functions as methods.\n", + "\n", + "1. Write a definition for a `Date` class that represents a date -- that is, a year, month, and day of the month.\n", + "\n", + "2. Write an `__init__` method that takes `year`, `month`, and `day` as parameters and assigns the parameters to attributes. Create an object that represents June 22, 1933.\n", + "\n", + "2. Write `__str__` method that uses an f-string to format the attributes and returns the result. If you test it with the `Date` you created, the result should be `1933-06-22`.\n", + "\n", + "3. Write a method called `is_after` that takes two `Date` objects and returns `True` if the first comes after the second. Create a second object that represents September 17, 1933, and check whether it comes after the first object.\n", + "\n", + "Hint: You might find it useful write a method called `to_tuple` that returns a tuple that contains the attributes of a `Date` object in year-month-day order." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "3c9f3777-4869-481e-9f4e-4223d6028913", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1122620d-f3f6-4746-8675-13ce0b7f3ee9", + "metadata": { + "tags": [] + }, + "source": [ + "You can use these examples to test your solution." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "fd4b2521-aa71-45da-97eb-ce62ce2714ad", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "ee3f1294-cad1-406b-a574-045ad2b84294", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "ac093f7b-83cf-4488-8842-5c71bcfa35ec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "7e7cb5e1-631f-4b1e-874f-eb16d4792625", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b92712d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap16.ipynb b/blank/chap16.ipynb new file mode 100644 index 0000000..4309183 --- /dev/null +++ b/blank/chap16.ipynb @@ -0,0 +1,1551 @@ +{ + "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": "ae5a86f8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e826e661", + "metadata": {}, + "source": [ + "# Classes and Objects\n", + "\n", + "At this point we have defined classes and created objects that represent the time of day and the day of the year.\n", + "And we've defined methods that create, modify, and perform computations with these objects.\n", + "\n", + "In this chapter we'll continue our tour of object-oriented programming (OOP) by defining classes that represent geometric objects, including points, lines, rectangles, and circles.\n", + "We'll write methods that create and modify these objects, and we'll use the `jupyturtle` module to draw them.\n", + "\n", + "I'll use these classes to demonstrate OOP topics including object identity and equivalence, shallow and deep copying, and polymorphism." + ] + }, + { + "cell_type": "markdown", + "id": "6b414d4a", + "metadata": { + "tags": [] + }, + "source": [ + "## Creating a Point\n", + "\n", + "In computer graphics a location on the screen is often represented using a pair of coordinates in an `x`-`y` plane.\n", + "By convention, the point `(0, 0)` usually represents the upper-left corner of the screen, and `(x, y)` represents the point `x` units to the right and `y` units down from the origin.\n", + "Compared to the Cartesian coordinate system you might have seen in a math class, the `y` axis is upside-down.\n", + "\n", + "There are several ways we might represent a point in Python:\n", + "\n", + "- We can store the coordinates separately in two variables, `x` and `y`.\n", + "\n", + "- We can store the coordinates as elements in a list or tuple.\n", + "\n", + "- We can create a new type to represent points as objects.\n", + "\n", + "In object-oriented programming, it would be most idiomatic to create a new type.\n", + "To do that, we'll start with a class definition for `Point`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c9c99d2c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3d35a095", + "metadata": {}, + "source": [ + "The `__init__` method takes the coordinates as parameters and assigns them to attributes `x` and `y`.\n", + "The `__str__` method returns a string representation of the `Point`.\n", + "\n", + "Now we can instantiate and display a `Point` object like this." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d318001a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b3fd8858", + "metadata": {}, + "source": [ + "The following diagram shows the state of the new object. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3eb47826", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6702a353", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "713b7410", + "metadata": {}, + "source": [ + "As usual, a programmer-defined type is represented by a box with the name of the type outside and the attributes inside.\n", + "\n", + "In general, programmer-defined types are mutable, so we can write a method like `translate` that takes two numbers, `dx` and `dy`, and adds them to the attributes `x` and `y`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0f710c4f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4d183292", + "metadata": {}, + "source": [ + "This function translates the `Point` from one location in the plane to another.\n", + "If we don't want to modify an existing `Point`, we can use `copy` to copy the original object and then modify the copy." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6e37126b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "562567c2", + "metadata": {}, + "source": [ + "We can encapsulate those steps in another method called `translated`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f38bfaaa", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7a635ee", + "metadata": {}, + "source": [ + "In the same way that the built in function `sort` modifies a list, and the `sorted` function creates a new list, now we have a `translate` method that modifies a `Point` and a `translated` method that creates a new one.\n", + "\n", + "Here's an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cef864a6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "923362d2", + "metadata": {}, + "source": [ + "In the next section, we'll use these points to define and draw a line." + ] + }, + { + "cell_type": "markdown", + "id": "837f98fd", + "metadata": {}, + "source": [ + "## Creating a Line\n", + "\n", + "Now let's define a class that represents the line segment between two points.\n", + "As usual, we'll start with an `__init__` method and a `__str__` method." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6ae012a1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d7dad30e", + "metadata": {}, + "source": [ + "With those two methods, we can instantiate and display a `Line` object we'll use to represent the `x` axis." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "39b2ae2a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e7b5fd9a", + "metadata": {}, + "source": [ + "When we call `print` and pass `line` as a parameter, `print` invokes `__str__` on `line`.\n", + "The `__str__` method uses an f-string to create a string representation of the `line`. \n", + "\n", + "The f-string contains two expressions in curly braces, `self.p1` and `self.p2`.\n", + "When those expressions are evaluated, the results are `Point` objects.\n", + "Then, when they are converted to strings, the `__str__` method from the `Point` class gets invoked.\n", + "\n", + "That's why, when we display a `Line`, the result contains the string representations of the `Point` objects.\n", + "\n", + "The following object diagram shows the state of this `Line` object." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "dee93202", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "43682c57", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "079859b5", + "metadata": {}, + "source": [ + "String representations and object diagrams are useful for debugging, but the point of this example is to generate graphics, not text!\n", + "So we'll use the `jupyturtle` module to draw lines on the screen.\n", + "\n", + "As we did in [Chapter 4](section_turtle_module), we'll use `make_turtle` to create a `Turtle` object and a small canvas where it can draw.\n", + "To draw lines, we'll use two new functions from the `jupyturtle` module:\n", + "\n", + "* `jumpto`, which takes two coordinates and moves the `Turtle` to the given location without drawing a line, and \n", + "\n", + "* `moveto`, which moves the `Turtle` from its current location to the given location, and draws a line segment between them.\n", + "\n", + "Here's how we import them." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "5225d870", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9d2dd88f", + "metadata": {}, + "source": [ + "And here's a method that draws a `Line`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "999843cb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2341f0e0", + "metadata": {}, + "source": [ + "To show how it's used, I'll create a second line that represents the `y` axis." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6ac10ec7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d7450736", + "metadata": {}, + "source": [ + "And then draw the axes." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b15b70dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "473c156f", + "metadata": {}, + "source": [ + "As we define and draw more objects, we'll use these lines again.\n", + "But first let's talk about object equivalence and identity." + ] + }, + { + "cell_type": "markdown", + "id": "950da673", + "metadata": {}, + "source": [ + "## Equivalence and identity\n", + "\n", + "Suppose we create two points with the same coordinates." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "62414805", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "82b14526", + "metadata": {}, + "source": [ + "If we use the `==` operator to compare them, we get the default behavior for programmer-defined types -- the result is `True` only if they are the same object, which they are not." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "7c611eb2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "96be0ff8", + "metadata": {}, + "source": [ + "If we want to change that behavior, we can provide a special method called `__eq__` that defines what it means for two `Point` objects to be equal." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "3a976072", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7f4409de", + "metadata": {}, + "source": [ + "This definition considers two `Points` to be equal if their attributes are equal.\n", + "Now when we use the `==` operator, it invokes the `__eq__` method, which indicates that `p1` and `p2` are considered equal." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "7660d756", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "52662e6a", + "metadata": {}, + "source": [ + "But the `is` operator still indicates that they are different objects." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e76ff9ef", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c008d3dd", + "metadata": {}, + "source": [ + "It's not possible to override the `is` operator -- it always checks whether the objects are identical.\n", + "But for programmer-defined types, you can override the `==` operator so it checks whether the objects are equivalent.\n", + "And you can define what equivalent means." + ] + }, + { + "cell_type": "markdown", + "id": "893a8cab", + "metadata": {}, + "source": [ + "## Creating a Rectangle\n", + "\n", + "Now let's define a class that represents and draws rectangles.\n", + "To keep things simple, we'll assume that the rectangles are either vertical or horizontal, not at an angle.\n", + "What attributes do you think we should use to specify the location and size of a rectangle?\n", + "\n", + "There are at least two possibilities:\n", + "\n", + "- You could specify the width and height of the rectangle and the location of one corner.\n", + "\n", + "- You could specify two opposing corners.\n", + "\n", + "At this point it's hard to say whether either is better than the other, so let's implement the first one.\n", + "Here is the class definition." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "c2a2aa29", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "df2852f3", + "metadata": {}, + "source": [ + "As usual, the `__init__` method assigns the parameters to attributes and the `__str__` returns a string representation of the object.\n", + "Now we can instantiate a `Rectangle` object, using a `Point` as the location of the upper-left corner." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "5d36d561", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a9e0b5ec", + "metadata": {}, + "source": [ + "The following diagram shows the state of this object." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "6e47d99a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "1e7e4a72", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bb54e6b5", + "metadata": {}, + "source": [ + "To draw a rectangle, we'll use the following method to make four `Point` objects to represent the corners." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "8fb74cd8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "20dbe0cb", + "metadata": {}, + "source": [ + "Then we'll make four `Line` objects to represent the sides." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "1c419c73", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "30fe41cc", + "metadata": {}, + "source": [ + "Then we'll draw the sides." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "5ceccb7d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "390ba3e7", + "metadata": {}, + "source": [ + "Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "2870c782", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "532a4f69", + "metadata": {}, + "source": [ + "The figure includes two lines to represent the axes." + ] + }, + { + "cell_type": "markdown", + "id": "0e713a90", + "metadata": {}, + "source": [ + "## Changing rectangles\n", + "\n", + "Now let's consider two methods that modify rectangles, `grow` and `translate`.\n", + "We'll see that `grow` works as expected, but `translate` has a subtle bug.\n", + "See if you can figure it out before I explain.\n", + "\n", + "`grow` takes two numbers, `dwidth` and `dheight`, and adds them to the `width` and `height` attributes of the rectangle." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "21537ed7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a51913e2", + "metadata": {}, + "source": [ + "Here's an example that demonstrates the effect by making a copy of `box1` and invoking `grow` on the copy." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "243ca804", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6d74da62", + "metadata": {}, + "source": [ + "If we draw `box1` and `box2`, we can confirm that `grow` works as expected." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "110f995f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0c940008", + "metadata": {}, + "source": [ + "Now let's see about `translate`.\n", + "It takes two numbers, `dx` and `dy`, and moves the rectangle the given distances in the `x` and `y` directions. " + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "8a5e7e73", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c27fe91d", + "metadata": {}, + "source": [ + "To demonstrate the effect, we'll translate `box2` to the right and down." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "78b9e344", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e01badbc", + "metadata": {}, + "source": [ + "Now let's see what happens if we draw `box1` and `box2` again." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "b70bcad9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5310bdd7", + "metadata": {}, + "source": [ + "It looks like both rectangles moved, which is not what we intended!\n", + "The next section explains what went wrong." + ] + }, + { + "cell_type": "markdown", + "id": "940adbeb", + "metadata": {}, + "source": [ + "## Deep copy\n", + "\n", + "When we use `copy` to duplicate `box1`, it copies the `Rectangle` object but not the `Point` object it contains.\n", + "So `box1` and `box2` are different objects, as intended." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "b67ce168", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "eac5309b", + "metadata": {}, + "source": [ + "But their `corner` attributes refer to the same object." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "1d9cf4da", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f0cc51b5", + "metadata": {}, + "source": [ + "The following diagram shows the state of these objects." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "6d883459", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "637042fb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "35f3e7e1", + "metadata": {}, + "source": [ + "What `copy` does is called a **shallow copy** because it copies the object but not the objects it contains.\n", + "As a result, changing the `width` or `height` of one `Rectangle` does not affect the other, but changing the attributes of the shared `Point` affects both!\n", + "This behavior is confusing and error-prone.\n", + "\n", + "Fortunately, the `copy` module provides another function, called `deepcopy`, that copies not only the object but also the objects it refers to, and the objects *they* refer to, and so on. \n", + "This operation is called a **deep copy**.\n", + "\n", + "To demonstrate, let's start with a new `Rectangle` that contains a new `Point`." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "168277a9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ff9ee872", + "metadata": {}, + "source": [ + "And we'll make a deep copy." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "75219ef6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7efd0e6a", + "metadata": {}, + "source": [ + "We can confirm that the two `Rectangle` objects refer to different `Point` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "fde2486c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ca925206", + "metadata": {}, + "source": [ + "Because `box3` and `box4` are completely separate objects, we can modify one without affecting the other.\n", + "To demonstrate, we'll move `box3` and grow `box4`." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "3f6d1d6b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3ff31c7c", + "metadata": {}, + "source": [ + "And we can confirm that the effect is as expected." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "092ded2c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "67051d62", + "metadata": {}, + "source": [ + "## Polymorphism\n", + "\n", + "In the previous example, we invoked the `draw` method on two `Line` objects and two `Rectangle` objects.\n", + "We can do the same thing more concisely by making a list of objects." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "c846343c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "773955dd", + "metadata": {}, + "source": [ + "The elements of this list are different types, but they all provide a `draw` method, so we can loop through the list and invoke `draw` on each one." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "10912b62", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a1ae190c", + "metadata": {}, + "source": [ + "The first and second time through the loop, `shape` refers to a `Line` object, so when `draw` is invoked, the method that runs is the one defined in the `Line` class.\n", + "\n", + "The third and fourth time through the loop, `shape` refers to a `Rectangle` object, so when `draw` is invoked, the method that runs is the one defined in the `Rectangle` class.\n", + "\n", + "In a sense, each object knows how to draw itself.\n", + "This feature is called **polymorphism**.\n", + "The word comes from Greek roots that mean \"many shaped\".\n", + "In object-oriented programming, polymorphism is the ability of different types to provide the same methods, which makes it possible to perform many computations -- like drawing shapes -- by invoking the same method on different types of objects.\n", + "\n", + "As an exercise at the end of this chapter, you'll define a new class that represents a circle and provides a `draw` method.\n", + "Then you can use polymorphism to draw lines, rectangles, and circles." + ] + }, + { + "cell_type": "markdown", + "id": "74d1b48f", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "In this chapter, we ran into a subtle bug that happened because we created a `Point` that was shared by two `Rectangle` objects, and then we modified the `Point`.\n", + "In general, there are two ways to avoid problems like this: you can avoid sharing objects or you can avoid modifying them.\n", + "\n", + "To avoid sharing objects, you can use deep copy, as we did in this chapter.\n", + "\n", + "To avoid modifying objects, consider replacing impure functions like `translate` with pure functions like `translated`.\n", + "For example, here's a version of `translated` that creates a new `Point` and never modifies its attributes." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "c803c91a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "76972167", + "metadata": {}, + "source": [ + "Python provides features that make it easier to avoid modifying objects.\n", + "They are beyond the scope of this book, but if you are curious, ask a virtual assistant, \"How do I make a Python object immutable?\"\n", + "\n", + "Creating a new object takes more time than modifying an existing one, but the difference seldom matters in practice.\n", + "Programs that avoid shared objects and impure functions are often easier to develop, test, and debug -- and the best kind of debugging is the kind you don't have to do." + ] + }, + { + "cell_type": "markdown", + "id": "02106995", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**shallow copy:**\n", + "A copy operation that does not copy nested objects.\n", + "\n", + "**deep copy:**\n", + "A copy operation that also copies nested objects.\n", + "\n", + "**polymorphism:**\n", + "The ability of a method or operator to work with multiple types of objects." + ] + }, + { + "cell_type": "markdown", + "id": "09dd41c1", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32b151a5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "da0aea86", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "For all of the following exercises, consider asking a virtual assistant for help.\n", + "If you do, you'll want include as part of the prompt the class definitions for `Point`, `Line`, and `Rectangle` -- otherwise the VA will make a guess about their attributes and functions, and the code it generates won't work." + ] + }, + { + "cell_type": "markdown", + "id": "7721e47b", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write an `__eq__` method for the `Line` class that returns `True` if the `Line` objects refer to `Point` objects that are equivalent, in either order." + ] + }, + { + "cell_type": "markdown", + "id": "2e488e0f", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "92c07380", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "a99446c4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3a44e45a", + "metadata": { + "tags": [] + }, + "source": [ + "You can use these examples to test your code." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "aa086dd1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e825f049", + "metadata": { + "tags": [] + }, + "source": [ + "This example should be `True` because the `Line` objects refer to `Point` objects that are equivalent, in the same order." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "857cba26", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c3d8fb2b", + "metadata": { + "tags": [] + }, + "source": [ + "This example should be `True` because the `Line` objects refer to `Point` objects that are equivalent, in reverse order." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "b45def0a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8c9c787b", + "metadata": { + "tags": [] + }, + "source": [ + "Equivalence should always be transitive -- that is, if `line_a` and `line_b` are equivalent, and `line_a` and `line_c` are equivalent, then `line_b` and `line_c` should also be equivalent." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "9784300c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d4f385fa", + "metadata": { + "tags": [] + }, + "source": [ + "This example should be `False` because the `Line` objects refer to `Point` objects that are not equivalent." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "5435c8e4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0e629491", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a `Line` method called `midpoint` that computes the midpoint of a line segment and returns the result as a `Point` object." + ] + }, + { + "cell_type": "markdown", + "id": "b8c52d19", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "f377afbb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "96d81b90", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4df69a9f", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following examples to test your code and draw the result." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "0d603aa3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "647d0982", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "e351bea3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "5ad5a076", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "8effaff0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0518c200", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a `Rectangle` method called `midpoint` that find the point in the center of a rectangle and returns the result as a `Point` object." + ] + }, + { + "cell_type": "markdown", + "id": "c586a3ed", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "d94a6350", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "de6756da", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d186c84b", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following example to test your code." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "4aec759c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "7ec3339d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "326dbf24", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "4da710d4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "00cbc4d9", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a `Rectangle` method called `make_cross` that:\n", + "\n", + "1. Uses `make_lines` to get a list of `Line` objects that represent the four sides of the rectangle.\n", + "\n", + "2. Computes the midpoints of the four lines.\n", + "\n", + "3. Makes and returns a list of two `Line` objects that represent lines connecting opposite midpoints, forming a cross through the middle of the rectangle." + ] + }, + { + "cell_type": "markdown", + "id": "29e994c6", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "30cc0726", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "6cdde16e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "970fcbca", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following example to test your code." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "2afd718c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "b7bdb467", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "9d09b2c3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0f707fe3", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a definition for a class named `Circle` with attributes `center` and `radius`, where `center` is a Point object and `radius` is a number.\n", + "Include special methods `__init__` and a `__str__`, and a method called `draw` that uses `jupyturtle` functions to draw the circle." + ] + }, + { + "cell_type": "markdown", + "id": "cb1b24a3", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following function, which is a version of the `circle` function we wrote in Chapter 4." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "b3d2328f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "189c30d4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b4325143", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following example to test your code.\n", + "We'll start with a square `Rectangle` with width and height `100`." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "49074ed5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2cdecfa9", + "metadata": { + "tags": [] + }, + "source": [ + "The following code should create a `Circle` that fits inside the square." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "d65a9163", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "37e94d98", + "metadata": { + "tags": [] + }, + "source": [ + "If everything worked correctly, the following code should draw the circle inside the square (touching on all four sides)." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "e3b23b4d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48abaaae", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap17.ipynb b/blank/chap17.ipynb new file mode 100644 index 0000000..bc6b43e --- /dev/null +++ b/blank/chap17.ipynb @@ -0,0 +1,2145 @@ +{ + "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": "217fc9bf", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ced31782", + "metadata": { + "tags": [] + }, + "source": [ + "# Inheritance\n", + "\n", + "The language feature most often associated with object-oriented programming is **inheritance**.\n", + "Inheritance is the ability to define a new class that is a modified version of an existing class.\n", + "In this chapter I demonstrate inheritance using classes that represent playing cards, decks of cards, and poker hands.\n", + "If you don't play poker, don't worry -- I'll tell you what you need to know." + ] + }, + { + "cell_type": "markdown", + "id": "b19c4dae", + "metadata": {}, + "source": [ + "## Representing cards\n", + "\n", + "There are 52 playing cards in a standard deck -- each of them belongs to one of four suits and one of thirteen ranks. \n", + "The suits are Spades, Hearts, Diamonds, and Clubs.\n", + "The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, and King.\n", + "Depending on which game you are playing, an Ace can be higher than King or lower than 2.\n", + "\n", + "If we want to define a new object to represent a playing card, it is obvious what the attributes should be: `rank` and `suit`.\n", + "It is less obvious what type the attributes should be.\n", + "One possibility is to use strings like `'Spade'` for suits and `'Queen'` for ranks.\n", + "A problem with this implementation is that it would not be easy to compare cards to see which had a higher rank or suit.\n", + "\n", + "An alternative is to use integers to **encode** the ranks and suits.\n", + "In this context, \"encode\" means that we are going to define a mapping between numbers and suits, or between numbers and ranks.\n", + "This kind of encoding is not meant to be a secret (that would be \"encryption\")." + ] + }, + { + "cell_type": "markdown", + "id": "a9bafecf", + "metadata": {}, + "source": [ + "For example, this table shows the suits and the corresponding integer codes:\n", + "\n", + "\n", + "| Suit | Code |\n", + "| --- | --- |\n", + "| Spades | 3 |\n", + "| Hearts | 2 |\n", + "| Diamonds | 1 |\n", + "| Clubs | 0 |\n", + "\n", + "With this encoding, we can compare suits by comparing their codes." + ] + }, + { + "cell_type": "markdown", + "id": "a1b46b1a", + "metadata": {}, + "source": [ + "To encode the ranks, we'll use the integer `2` to represent the rank `2`, `3` to represent `3`, and so on up to `10`.\n", + "The following table shows the codes for the face cards.\n", + "\n", + " \n", + "| Rank | Code |\n", + "| --- | --- |\n", + "| Jack | 11 |\n", + "| Queen | 12 |\n", + "| King | 13 |\n", + "\n", + "And we can use either `1` or `14` to represent an Ace, depending on whether we want it to be considered lower or higher than the other ranks.\n", + "\n", + "To represent these encodings, we will use two lists of strings, one with the names of the suits and the other with the names of the ranks.\n", + "\n", + "Here's a definition for a class that represents a playing card, with these lists of strings as **class variables**, which are variables defined inside a class definition, but not inside a method." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ef26adf0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d63f798a", + "metadata": {}, + "source": [ + "The first element of `rank_names` is `None` because there is no card with rank zero. By including `None` as a place-keeper, we get a list with the nice property that the index `2` maps to the string `'2'`, and so on.\n", + "\n", + "Class variables are associated with the class, rather than an instance of the class, so we can access them like this." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4e4bd268", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c837fff6", + "metadata": {}, + "source": [ + "We can use `suit_names` to look up a suit and get the corresponding string." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8aec2a6a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a59d905e", + "metadata": {}, + "source": [ + "And `rank_names` to look up a rank." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "baf029e9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "50dda19b", + "metadata": {}, + "source": [ + "## Card attributes\n", + "\n", + "Here's an `__init__` method for the `Card` class -- it takes `suit` and `rank` as parameters and assigns them to attributes with the same names." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "91320ea3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "31a2782d", + "metadata": {}, + "source": [ + "Now we can create a `Card` object like this." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c04bb77e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "85e5cf5d", + "metadata": {}, + "source": [ + "We can use the new instance to access the attributes." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b182e6fa", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "449225d3", + "metadata": {}, + "source": [ + "It is also legal to use the instance to access the class variables." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "17ce1a51", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "97232ffa", + "metadata": {}, + "source": [ + "But if you use the class, it is clearer that they are class variables, not attributes." + ] + }, + { + "cell_type": "markdown", + "id": "7a0a79ae", + "metadata": {}, + "source": [ + "## Printing cards\n", + "\n", + "Here's a `__str__` method for `Card` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6709b45a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d6c51352", + "metadata": {}, + "source": [ + "When we print a `Card`, Python calls the `__str__` method to get a human-readable representation of the card." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e7f9304d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "76044b9e", + "metadata": {}, + "source": [ + "The following is a diagram of the `Card` class object and the Card instance.\n", + "`Card` is a class object, so its type is `type`.\n", + "`queen` is an instance of `Card`, so its type is `Card`.\n", + "To save space, I didn't draw the contents of `suit_names` and `rank_names`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d589ed70", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "5518455f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ccb8e41d", + "metadata": {}, + "source": [ + "Every `Card` instance has its own `suit` and `rank` attributes, but there is only one `Card` class object, and only one copy of the class variables `suit_names` and `rank_names`." + ] + }, + { + "cell_type": "markdown", + "id": "98c6508d", + "metadata": {}, + "source": [ + "## Comparing cards\n", + "\n", + "Suppose we create a second `Card` object with the same suit and rank." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cadb115d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "3c92779c", + "metadata": {}, + "source": [ + "If we use the `==` operator to compare them, it checks whether `queen` and `queen2` refer to the same object." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "6a625fde", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "278d8abe", + "metadata": {}, + "source": [ + "They don't, so it returns `False`.\n", + "We can change this behavior by defining the special method `__eq__`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "4f394e57", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bd66a9d3", + "metadata": {}, + "source": [ + "`__eq__` takes two `Card` objects as parameters and returns `True` if they have the same suit and rank, even if they are not the same object.\n", + "In other words, it checks whether they are equivalent, even if they are not identical.\n", + "\n", + "When we use the `==` operator with `Card` objects, Python calls the `__eq__` method." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "2c10425b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "23d99d3e", + "metadata": {}, + "source": [ + "As a second test, let's create a card with the same suit and a different rank." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "c2a695b4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c5f66404", + "metadata": {}, + "source": [ + "We can confirm that `queen` and `six` are not equivalent." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "400c3340", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1dcb561f", + "metadata": {}, + "source": [ + "If we use the `!=` operator, Python invokes a special method called `__ne__`, if it exists.\n", + "Otherwise it invokes`__eq__` and inverts the result -- so if `__eq__` returns `True`, the result of the `!=` operator is `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c7d731b6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "d2be6c82", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "77c48464", + "metadata": {}, + "source": [ + "Now suppose we want to compare two cards to see which is bigger.\n", + "If we use one of the relational operators, we get a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "aa63fe2a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4db0ad52", + "metadata": {}, + "source": [ + "To change the behavior of the `<` operator, we can define a special method called `__lt__`, which is short for \"less than\".\n", + "For the sake of this example, let's assume that suit is more important than rank -- so all Spades outrank all Hearts, which outrank all Diamonds, and so on.\n", + "If two cards have the same suit, the one with the higher rank wins.\n", + "\n", + "To implement this logic, we'll use the following method, which returns a tuple containing a card's suit and rank, in that order." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "b2126f79", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d5062348", + "metadata": {}, + "source": [ + "We can use this method to write `__lt__`." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d4d0a652", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bd9ef8f5", + "metadata": {}, + "source": [ + "Tuple comparison compares the first elements from each tuple, which represent the suits.\n", + "If they are the same, it compares the second elements, which represent the ranks.\n", + "\n", + "Now if we use the `<` operator, it invokes the `__lt__` method." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "9d4ea1f8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "83289a77", + "metadata": {}, + "source": [ + "If we use the `>` operator, it invokes a special method called `__gt__`, if it exists.\n", + "Otherwise it invokes `__lt__` with the arguments in the opposite order." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "676ede7e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "3c4854fb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5d0a91de", + "metadata": {}, + "source": [ + "Finally, if we use the `<=` operator, it invokes a special method called `__le__`." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "27280fc2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6c85ac69", + "metadata": {}, + "source": [ + "So we can check whether one card is less than or equal to another." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "bea50d85", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "8d539454", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7af7b289", + "metadata": {}, + "source": [ + "If we use the `>=` operator, it uses `__ge__` if it exists. Otherwise, it invokes `__le__` with the arguments in the opposite order." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e7edb7cb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fe2a81cc", + "metadata": {}, + "source": [ + "As we have defined them, these methods are complete in the sense that we can compare any two `Card` objects, and consistent in the sense that results from different operators don't contradict each other.\n", + "With these two properties, we can say that `Card` objects are **totally ordered**.\n", + "And that means, as we'll see soon, that they can be sorted." + ] + }, + { + "cell_type": "markdown", + "id": "199f8bfc", + "metadata": {}, + "source": [ + "## Decks\n", + "\n", + "Now that we have objects that represent cards, let's define objects that represent decks.\n", + "The following is a class definition for `Deck` with\n", + "an `__init__` method takes a list of `Card` objects as a parameter and assigns it to an attribute called `cards`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "b55140e3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2d529789", + "metadata": {}, + "source": [ + "To create a list that contains the 52 cards in a standard deck, we'll use the following static method." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "836f1a32", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "47ae8f71", + "metadata": {}, + "source": [ + "In `make_cards`, the outer loop enumerates the suits from `0` to `3`.\n", + "The inner loop enumerates the ranks from `2` to `14` -- where `14` represents an Ace that outranks a King.\n", + "Each iteration creates a new `Card` with the current suit and rank, and appends it to `cards`.\n", + "\n", + "Here's how we make a list of cards and a `Deck` object that contains it." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "ca50c79b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "032ec302", + "metadata": {}, + "source": [ + "It contains 52 cards, as intended." + ] + }, + { + "cell_type": "markdown", + "id": "c2ec7f01", + "metadata": { + "tags": [] + }, + "source": [ + "## Printing the deck\n", + "\n", + "Here is a `__str__` method for `Deck`." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "1f1b923e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "660f18e6", + "metadata": {}, + "source": [ + "This method demonstrates an efficient way to accumulate a large string -- building a list of strings and then using the string method `join`. \n", + "\n", + "We'll test this method with a deck that only contains two cards." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "0c55a663", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "91c7145f", + "metadata": {}, + "source": [ + "If we call `str`, it invokes `__str__`." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "fb3350ef", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "00270656", + "metadata": {}, + "source": [ + "When Jupyter displays a string, it shows the \"representational\" form of the string, which represents a newline with the sequence `\\n`.\n", + "\n", + "However, if we print the result, Jupyter shows the \"printable\" form of the string, which prints the newline as whitespace." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d67f8fd5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "e97810c4", + "metadata": {}, + "source": [ + "So the cards appear on separate lines." + ] + }, + { + "cell_type": "markdown", + "id": "52d3d597", + "metadata": {}, + "source": [ + "## Add, remove, shuffle and sort\n", + "\n", + "To deal cards, we would like a method that removes a card from the deck\n", + "and returns it. The list method `pop` provides a convenient way to do\n", + "that." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "3836c48c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1fcef47b", + "metadata": {}, + "source": [ + "Here's how we use it." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "5afccad6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "65427954", + "metadata": {}, + "source": [ + "We can confirm that there are `51` cards left in the deck." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "58f9473a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7ca3614e", + "metadata": {}, + "source": [ + "To add a card, we can use the list method `append`." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "f3eac4b5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2ecd8703", + "metadata": {}, + "source": [ + "As an example, we can put back the card we just popped." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "f234eff4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8b5af8ce", + "metadata": {}, + "source": [ + "To shuffle the deck, we can use the `shuffle` function from the `random` module:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "81e60a08", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "b630cbb8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "bea615ea", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a8cb1a7f", + "metadata": {}, + "source": [ + "If we shuffle the deck and print the first few cards, we can see that they are in no apparent order." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "6b0f6b02", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a198dde3", + "metadata": {}, + "source": [ + "To sort the cards, we can use the list method `sort`, which sorts the elements \"in place\" -- that is, it modifies the list rather than creating a new list." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "6bff10b6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d4f017c7", + "metadata": {}, + "source": [ + "When we invoke `sort`, it uses the `__lt__` method to compare cards." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "568a6583", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2bb966fd", + "metadata": {}, + "source": [ + "If we print the first few cards, we can confirm that they are in increasing order." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "54e91c91", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5c41ce4d", + "metadata": {}, + "source": [ + "In this example, `Deck.sort` doesn't do anything other than invoke `list.sort`.\n", + "Passing along responsibility like this is called **delegation**." + ] + }, + { + "cell_type": "markdown", + "id": "0502961b", + "metadata": {}, + "source": [ + "## Parents and children\n", + "\n", + "Inheritance is the ability to define a new class that is a modified version of an existing class.\n", + "As an example, let's say we want a class to represent a \"hand\", that is, the cards held by one player.\n", + "\n", + "* A hand is similar to a deck -- both are made up of a collection of cards, and both require operations like adding and removing cards.\n", + "\n", + "* A hand is also different from a deck -- there are operations we want for hands that don't make sense for a deck. For example, in poker we might compare two hands to see which one wins. In bridge, we might compute a score for a hand in order to make a bid.\n", + "\n", + "This relationship between classes -- where one is a specialized version of another -- lends itself to inheritance. \n", + "\n", + "To define a new class that is based on an existing class, we put the name of the existing class in parentheses." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "f39fc598", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "339295cd", + "metadata": {}, + "source": [ + "This definition indicates that `Hand` inherits from `Deck`, which means that `Hand` objects can access methods defined in `Deck`, like `take_card` and `put_card`.\n", + "\n", + "`Hand` also inherits `__init__` from `Deck`, but if we define `__init__` in the `Hand` class, it overrides the one in the `Deck` class." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "9e7a1045", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9b6a763a", + "metadata": {}, + "source": [ + "This version of `__init__` takes an optional string as a parameter, and always starts with an empty list of cards.\n", + "When we create a `Hand`, Python invokes this method, not the one in `Deck` -- which we can confirm by checking that the result has a `label` attribute." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "8de8cff4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b1e2a67d", + "metadata": {}, + "source": [ + "To deal a card, we can use `take_card` to remove a card from a `Deck`, and `put_card` to add the card to a `Hand`." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "9f582ce0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dc2ce06b", + "metadata": {}, + "source": [ + "Let's encapsulate this code in a `Deck` method called `move_cards`." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "180069eb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "16e6c404", + "metadata": {}, + "source": [ + "This method is polymorphic -- that is, it works with more than one type: `self` and `other` can be either a `Hand` or a `Deck`.\n", + "So we can use this method to deal a card from `Deck` to a `Hand`, from one `Hand` to another, or from a `Hand` back to a `Deck`." + ] + }, + { + "cell_type": "markdown", + "id": "e648a722", + "metadata": {}, + "source": [ + "When a new class inherits from an existing one, the existing one is called the **parent** and the new class is called the **child**. In general:\n", + "\n", + "* Instances of the child class should have all of the attributes of the parent class, but they can have additional attributes.\n", + "\n", + "* The child class should have all of the methods of the parent class, but it can have additional methods.\n", + "\n", + "* If a child class overrides a method from the parent class, the new method should take the same parameters and return a compatible result.\n", + "\n", + "This set of rules is called the \"Liskov substitution principle\" after computer scientist Barbara Liskov.\n", + "\n", + "If you follow these rules, any function or method designed to work with an instance of a parent class, like a `Deck`, will also work with instances of a child class, like `Hand`.\n", + "If you violate these rules, your code will collapse like a house of cards (sorry)." + ] + }, + { + "cell_type": "markdown", + "id": "e80873dd", + "metadata": {}, + "source": [ + "## Specialization\n", + "\n", + "Let's make a class called `BridgeHand` that represents a hand in bridge -- a widely played card game.\n", + "We'll inherit from `Hand` and add a new method called `high_card_point_count` that evaluates a hand using a \"high card point\" method, which adds up points for the high cards in the hand.\n", + "\n", + "Here's a class definition that contains as a class variable a dictionary that maps from card names to their point values." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "b9e949cf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4c038717", + "metadata": {}, + "source": [ + "Given the rank of a card, like `12`, we can use `Card.rank_names` to get the string representation of the rank, and then use `hcp_dict` to get its score." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "0586b764", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c3a7820d", + "metadata": {}, + "source": [ + "The following method loops through the cards in a `BridgeHand` and adds up their scores." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "f01e8027", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "227bef61", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "94535d8e", + "metadata": {}, + "source": [ + "To test it, we'll deal a hand with five cards -- a bridge hand usually has thirteen, but it's easier to test code with small examples." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "f7d9275a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a1bd2521", + "metadata": {}, + "source": [ + "And here is the total score for the King and Queen." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "ceb63a74", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b4f5e107", + "metadata": {}, + "source": [ + "`BridgeHand` inherits the variables and methods of `Hand` and adds a class variable and a method that are specific to bridge.\n", + "This way of using inheritance is called **specialization** because it defines a new class that is specialized for a particular use, like playing bridge." + ] + }, + { + "cell_type": "markdown", + "id": "b493622d", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "Inheritance is a useful feature.\n", + "Some programs that would be repetitive without inheritance can be written more concisely with it.\n", + "Also, inheritance can facilitate code reuse, since you can customize the behavior of a parent class without having to modify it.\n", + "In some cases, the inheritance structure reflects the natural structure of the problem, which makes the design easier to understand.\n", + "\n", + "On the other hand, inheritance can make programs difficult to read.\n", + "When a method is invoked, it is sometimes not clear where to find its definition -- the relevant code may be spread across several modules.\n", + "\n", + "Any time you are unsure about the flow of execution through your program, the simplest solution is to add print statements at the beginning of the relevant methods.\n", + "If `Deck.shuffle` prints a message that says something like `Running Deck.shuffle`, then as the program runs it traces the flow of execution.\n", + "\n", + "As an alternative, you could use the following function, which takes an object and a method name (as a string) and returns the class that provides the definition of the method." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "20633d57", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1ee8f2da", + "metadata": {}, + "source": [ + "`find_defining_class` uses the `mro` method to get the list of class objects (types) that will be searched for methods.\n", + "\"MRO\" stands for \"method resolution order\", which is the sequence of classes Python searches to \"resolve\" a method name -- that is, to find the function object the name refers to.\n", + "\n", + "As an example, let's instantiate a `BridgeHand` and then find the defining class of `shuffle`." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "c5bec04f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "eeb70a14", + "metadata": {}, + "source": [ + "The `shuffle` method for the `BridgeHand` object is the one in `Deck`." + ] + }, + { + "cell_type": "markdown", + "id": "07f4c4bb", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**inheritance:**\n", + " The ability to define a new class that is a modified version of a previously defined class.\n", + "\n", + "**encode:**\n", + " To represent one set of values using another set of values by constructing a mapping between them.\n", + "\n", + "**class variable:**\n", + "A variable defined inside a class definition, but not inside any method.\n", + "\n", + "**totally ordered:**\n", + "A set of objects is totally ordered if we can compare any two elements and the results are consistent.\n", + "\n", + "**delegation:**\n", + "When one method passes responsibility to another method to do most or all of the work.\n", + "\n", + "**parent class:**\n", + "A class that is inherited from.\n", + "\n", + "**child class:**\n", + "A class that inherits from another class.\n", + "\n", + "**specialization:**\n", + "A way of using inheritance to create a new class that is a specialized version of an existing class." + ] + }, + { + "cell_type": "markdown", + "id": "1aea9b2b", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "e281457a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "7913e6b1", + "metadata": {}, + "source": [ + "### Ask a Virtual Assistant\n", + "\n", + "When it goes well, object-oriented programming can make programs more readable, testable, and reusable.\n", + "But it can also make programs complicated and hard to maintain.\n", + "As a result, OOP is a topic of controversy -- some people love it, and some people don't.\n", + "\n", + "To learn more about the topic, ask a virtual assistant:\n", + "\n", + "* What are some pros and cons of object-oriented programming?\n", + "\n", + "* What does it mean when people say \"favor composition over inheritance\"?\n", + "\n", + "* What is the Liskov substitution principle?\n", + "\n", + "* Is Python an object-oriented language?\n", + "\n", + "* What are the requirements for a set to be totally ordered?\n", + "\n", + "And as always, consider using a virtual assistant to help with the following exercises." + ] + }, + { + "cell_type": "markdown", + "id": "1af81269", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "In contract bridge, a \"trick\" is a round of play in which each of four players plays one card.\n", + "To represent those cards, we'll define a class that inherits from `Deck`." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "47d9ce31", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9916d562", + "metadata": {}, + "source": [ + "As an example, consider this trick, where the first player leads with the 3 of Diamonds, which means that Diamonds are the \"led suit\".\n", + "The second and third players \"follow suit\", which means they play a card with the led suit.\n", + "The fourth player plays a card of a different suit, which means they cannot win the trick.\n", + "So the winner of this trick is the third player, because they played the highest card in the led suit." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "7bb54059", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c94a1337", + "metadata": {}, + "source": [ + "Write a `Trick` method called `find_winner` that loops through the cards in the `Trick` and returns the index of the card that wins.\n", + "In the previous example, the index of the winning card is `2`." + ] + }, + { + "cell_type": "markdown", + "id": "60c43389", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "72a410d7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "b09cceb1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d7e3b94c", + "metadata": { + "tags": [] + }, + "source": [ + "If you test your method with the previous example, the index of the winning card should be `2`." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "e185c2f7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b5b9fb4b", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "The next few exercises ask to you write functions that classify poker hands.\n", + "If you are not familiar with poker, I'll explain what you need to know.\n", + "We'll use the following class to represent poker hands." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "2558ddd1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2daecced", + "metadata": {}, + "source": [ + "`PokerHand` provides two methods that will help with the exercises.\n", + "\n", + "* `get_suit_counts` loops through the cards in the `PokerHand`, counts the number of cards in each suit, and returns a dictionary that maps from each suit code to the number of times it appears.\n", + "\n", + "* `get_rank_counts` does the same thing with the ranks of the cards, returning a dictionary that maps from each rank code to the number of times it appears.\n", + "\n", + "All of the exercises that follow can be done using only the Python features we have learned so far, but some of them are more difficult than most of the previous exercises.\n", + "I encourage you to ask an AI for help.\n", + "\n", + "For problems like this, it often works well to ask for general advice about strategies and algorithms.\n", + "Then you can either write the code yourself or ask for code.\n", + "If you ask for code, you might want to provide the relevant class definitions as part of the prompt." + ] + }, + { + "cell_type": "markdown", + "id": "ccc2d8ca", + "metadata": {}, + "source": [ + "As a first exercise, we'll write a method called `has_flush` that checks whether a hand has a \"flush\" -- that is, whether it contains at least five cards of the same suit.\n", + "\n", + "In most varieties of poker, a hand contains either five or seven cards, but there are some exotic variations where a hand contains other numbers of cards.\n", + "But regardless of how many cards there are in a hand, the only ones that count are the five that make the best hand." + ] + }, + { + "cell_type": "markdown", + "id": "6911a0f6", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "7aa25f29", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "72c769d5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "07878cb3", + "metadata": { + "tags": [] + }, + "source": [ + "To test this method, we'll construct a hand with five cards that are all Clubs, so it contains a flush." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "e1c4f474", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "83698207", + "metadata": { + "tags": [] + }, + "source": [ + "If we invoke `get_suit_counts`, we can confirm that the rank code `0` appears `5` times." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "c91e3bdb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "fdec7276", + "metadata": { + "tags": [] + }, + "source": [ + "So `has_flush` should return `True`." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "a60e233d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ba10e1ba", + "metadata": { + "tags": [] + }, + "source": [ + "As a second test, we'll construct a hand with three Clubs and two other suits." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "ae5063e2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8611610f", + "metadata": { + "tags": [] + }, + "source": [ + "So `has_flush` should return `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "940928ba", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "ad716880", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a method called `has_straight` that checks whether a hand contains a straight, which is a set of five cards with consecutive ranks.\n", + "For example, if a hand contains ranks `5`, `6`, `7`, `8`, and `9`, it contains a straight.\n", + "\n", + "An Ace can come before a two or after a King, so `Ace`, `2`, `3`, `4`, `5` is a straight and so it `10`, `Jack`, `Queen`, `King`, `Ace`.\n", + "But a straight cannot \"wrap around\", so `King`, `Ace`, `2`, `3`, `4` is not a straight." + ] + }, + { + "cell_type": "markdown", + "id": "bbd04e50", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started.\n", + "It includes a few lines of code that count the number of Aces -- represented with the code `1` or `14` -- and store the total in both locations of the counter." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "6513ef14", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "8a220d67", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d2e576bf", + "metadata": { + "tags": [] + }, + "source": [ + "`good_hand`, which we created for the previous exercise, contains a straight.\n", + "If we use `get_rank_counts`, we can confirm that it has at least one card with each of five consecutive ranks." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "5a422cae", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "009a20dd", + "metadata": { + "tags": [] + }, + "source": [ + "So `has_straight` should return `True`." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "24483b99", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1d79b2b6", + "metadata": { + "tags": [] + }, + "source": [ + "`bad_hand` does not contain a straight, so `has_straight` should return `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "69f7cea9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c1ecebd3", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "A hand has a straight flush if it contains a set of five cards that are both a straight and a flush -- that is, five cards of the same suit with consecutive ranks.\n", + "Write a `PokerHand` method that checks whether a hand has a straight flush." + ] + }, + { + "cell_type": "markdown", + "id": "94570008", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "88bd35fb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "5e602773", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "997b120d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4a1e17b1", + "metadata": { + "tags": [] + }, + "source": [ + "Use the following examples to test your method." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "23704ab4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "7d8d2fdb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9127ceb1", + "metadata": { + "tags": [] + }, + "source": [ + "Note that it is not enough to check whether a hand has a straight and a flush.\n", + "To see why, consider the following hand." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "4c5727d9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c4ed18e4", + "metadata": { + "tags": [] + }, + "source": [ + "This hand contains a straight and a flush, but they are not the same five cards." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "7e65e951", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "5ff4e160", + "metadata": { + "tags": [] + }, + "source": [ + "So it does not contain a straight flush." + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "758fc922", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dd742401", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "A poker hand has a pair if it contains two or more cards with the same rank.\n", + "Write a `PokerHand` method that checks whether a hand contains a pair." + ] + }, + { + "cell_type": "markdown", + "id": "00464353", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "15a81a40", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "bc7ca211", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "20b65d89", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9f001207", + "metadata": {}, + "source": [ + "To test your method, here's a hand that has a pair." + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "57e616be", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "b8d87fd8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "3016e99a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "852e3b11", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c4180a64", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "A hand has a full house if it contains three cards of one rank and two cards of another rank.\n", + "Write a `PokerHand` method that checks whether a hand has a full house." + ] + }, + { + "cell_type": "markdown", + "id": "5aa80ee6", + "metadata": { + "tags": [] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "1f95601d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "420ca0fd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f020cf9e", + "metadata": { + "tags": [] + }, + "source": [ + "You can use this hand to test your method." + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "1e4957cb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "4dc8b899", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "86229763", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "7f1b3664", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "666340c1", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "This exercise is a cautionary tale about a common error that can be difficult to debug.\n", + "Consider the following class definition." + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "85b47651", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1e349832", + "metadata": {}, + "source": [ + "`__init__` takes two parameters: `name` is required, but `contents` is optional -- if it's not provided, the default value is an empty list.\n", + "\n", + "`__str__` returns a string representation of the object that includes the name and the contents of the pouch.\n", + "\n", + "`put_in_pouch` takes any object and appends it to `contents`.\n", + "\n", + "Now let's see how this class works.\n", + "We'll create two `Kangaroo` objects with the names `'Kanga'` and `'Roo'`." + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "13f1e1f3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "533982d1", + "metadata": {}, + "source": [ + "To Kanga's pouch we'll add two strings and Roo." + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "id": "e882e2d6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "41cd6d6e", + "metadata": {}, + "source": [ + "If we print `kanga`, it seems like everything worked." + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "id": "724805bb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "0ba26163", + "metadata": {}, + "source": [ + "But what happens if we print `roo`?" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "3f6cff16", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a2aef813", + "metadata": {}, + "source": [ + "Roo's pouch contains the same contents as Kanga's, including a reference to `roo`!\n", + "\n", + "See if you can figure out what went wrong.\n", + "Then ask a virtual assistant, \"What's wrong with the following program?\" and paste in the definition of `Kangaroo`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ef15c8c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap18.ipynb b/blank/chap18.ipynb new file mode 100644 index 0000000..6a069c1 --- /dev/null +++ b/blank/chap18.ipynb @@ -0,0 +1,2088 @@ +{ + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8b00461b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "737b8979", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3bbd3b72", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b200cbc2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "14d5aee5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "11676b80", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "0249c4c8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "03b1afa9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "1437f1f1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "c9c2a932", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "3d797346", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "6b295593", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "f7b1cd91", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "52e32b33", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "aec0523b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "863252da", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "5c6addfb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "54df18a1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "742795a9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "871dafae", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "markdown", + "id": "07be77f3", + "metadata": {}, + "source": [ + "In this example, the value of `kwargs` is printed, but otherwise is 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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": "bc03f15d", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5029c76d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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", + "Here are some question to ask an AI.\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 an AI 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": [] + }, + { + "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 an AI \"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": [] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "46a13618", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "160a29b3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "8db988e6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "a67768c0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "7900d2ed", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": 110, + "id": "3b9d5bac", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "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": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16ce7d9f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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 +} diff --git a/blank/chap19.ipynb b/blank/chap19.ipynb new file mode 100644 index 0000000..4d86fb8 --- /dev/null +++ b/blank/chap19.ipynb @@ -0,0 +1,188 @@ +{ + "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": "markdown", + "id": "171aca73", + "metadata": {}, + "source": [ + "# Final thoughts" + ] + }, + { + "cell_type": "markdown", + "id": "4d551c99", + "metadata": {}, + "source": [ + "Learning to program is not easy, but if you made it this far, you are off to a good start.\n", + "Now I have some suggestions for ways you can keep learning and apply what you have learned.\n", + "\n", + "This book is meant to be a general introduction to programming, so we have not focused on specific applications.\n", + "Depending on your interests, there are any number of areas where you can apply your new skills.\n", + "\n", + "If you are interested in Data Science, there are three books of mine you might like:\n", + "\n", + "* *Think Stats: Exploratory Data Analysis*, O'Reilly Media, 2014.\n", + "\n", + "* *Think Bayes: Bayesian Statistics in Python*, O'Reilly Media, 2021.\n", + "\n", + "* *Think DSP: Digital Signal Processing in Python*, O'Reilly Media, 2016." + ] + }, + { + "cell_type": "markdown", + "id": "cceabe36", + "metadata": {}, + "source": [ + "If you are interested in physical modeling and complex systems, you might like:\n", + "\n", + "* *Modeling and Simulation in Python: An Introduction for Scientists and Engineers*, No Starch Press, 2023.\n", + "\n", + "* *Think Complexity: Complexity Science and Computational Modeling*, O'Reilly Media, 2018.\n", + "\n", + "These use NumPy, SciPy, pandas, and other Python libraries for data science and scientific computing." + ] + }, + { + "cell_type": "markdown", + "id": "54a39121", + "metadata": {}, + "source": [ + "This book tries to find a balance between general principles of programming and details of Python.\n", + "As a result, it does not include every feature of the Python language.\n", + "For more about Python, and good advice about how to use it, I recommend *Fluent Python: Clear, Concise, and Effective Programming*, second edition by Luciano Ramalho, O'Reilly Media, 2022.\n", + "\n", + "After an introduction to programming, a common next step is to learn about data structures and algorithms.\n", + "I have a work in progress on this topic, called *Data Structures and Information Retrieval in Python*.\n", + "A free electronic version is available from Green Tea Press at ." + ] + }, + { + "cell_type": "markdown", + "id": "a1598510", + "metadata": {}, + "source": [ + "As you work on more complex programs, you will encounter new challenges.\n", + "You might find it helpful to review the sections in this book about debugging.\n", + "In particular, remember the Six R's of debugging from [Chapter 12](section_debugging_12): reading, running, ruminating, rubber-ducking, retreating, and resting.\n", + "\n", + "This book suggests tools to help with debugging, including the `print` and `repr` functions, the `structshape` function in [Chapter 11](section_debugging_11) -- and the built-in functions `isinstance`, `hasattr`, and `vars` in [Chapter 14](section_debugging_14)." + ] + }, + { + "cell_type": "markdown", + "id": "fb4dd345", + "metadata": {}, + "source": [ + "It also suggests tools for testing programs, including the `assert` statement, the `doctest` module, and the `unittest` module.\n", + "Including tests in your programs is one of the best ways to prevent and detect errors, and save time debugging.\n", + "\n", + "But the best kind of debugging is the kind you don't have to do.\n", + "If you use an incremental development process as described in [Chapter 6](section_incremental) -- and test as you go -- you will make fewer errors and find them more quickly when you do.\n", + "Also, remember encapsulation and generalization from [Chapter 4](section_encapsulation), which is particularly useful when you are developing code in Jupyter notebooks." + ] + }, + { + "cell_type": "markdown", + "id": "0d29933e", + "metadata": {}, + "source": [ + "Throughout this book, I've suggested ways to use virtual assistants to help you learn, program, and debug.\n", + "I hope you are finding these tools useful.\n", + "\n", + "In additional to virtual assistants like ChatGPT, you might also want to use a tool like Copilot that autocompletes code as you type.\n", + "I did not recommend using these tools, initially, because they can be overwhelming for beginners.\n", + "But you might want to explore them now.\n", + "\n", + "Using AI tools effectively requires some experimentation and reflection to find a flow that works for you.\n", + "If you think it's a nuisance to copy code from ChatGPT to Jupyter, you might prefer something like Copilot.\n", + "But the cognitive work you do to compose a prompt and interpret the response can be as valuable as the code the tool generates, in the same vein as rubber duck debugging." + ] + }, + { + "cell_type": "markdown", + "id": "c28d6815", + "metadata": {}, + "source": [ + "As you gain programming experience, you might want to explore other development environments.\n", + "I think Jupyter notebooks are a good place to start, but they are relatively new and not as widely-used as conventional integrated development environments (IDE).\n", + "For Python, the most popular IDEs include PyCharm and Spyder -- and Thonny, which is often recommended for beginners.\n", + "Other IDEs, like Visual Studio Code and Eclipse, work with other programming languages as well.\n", + "Or, as a simpler alternative, you can write Python programs using any text editor you like.\n", + "\n", + "As you continue your programming journey, you don't have to go alone!\n", + "If you live in or near a city, there's a good chance there is a Python user group you can join.\n", + "These groups are usually friendly to beginners, so don't be afraid.\n", + "If there is no group near you, you might be able to join events remotely.\n", + "Also, keep an eye out for regional Python conferences." + ] + }, + { + "cell_type": "markdown", + "id": "28cb22bf", + "metadata": {}, + "source": [ + "One of the best ways to improve your programming skills is to learn another language.\n", + "If you are interested in statistics and data science, you might want to learn R.\n", + "But I particularly recommend learning a functional language like Racket or Elixir.\n", + "Functional programming requires a different kind of thinking, which changes the way you think about programs.\n", + "\n", + "Good luck!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2783577", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { + "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.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/chap00.ipynb b/chapters/chap00.ipynb index ff1fcfa..e7c283c 100644 --- a/chapters/chap00.ipynb +++ b/chapters/chap00.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "d9724920", @@ -58,7 +50,7 @@ "\n", "Writing this book, I tried to be careful with the vocabulary.\n", "I define each term when it first appears.\n", - "And there is a glossary that the end of each chapter that reviews the terms that were introduced.\n", + "And there is a glossary at the end of each chapter that reviews the terms that were introduced.\n", "\n", "I also tried to be concise.\n", "The less mental effort it takes to read the book, the more capacity you will have for programming.\n", @@ -179,9 +171,9 @@ "\n", "If you are teaching with this book, here are some resources you might find useful.\n", "\n", - "* You can find notebooks with solutions to the exercises from , along with links to the additional resources below.\n", + "* You can find notebooks with solutions to the exercises at , along with links to the additional resources below.\n", "\n", - "* Quizzes for each chapter, and a summative quiz for the whole book, are available from [COMING SOON]\n", + "* Quizzes for each chapter, and a summative quiz for the whole book, are available on request.\n", "\n", "* *Teaching and Learning with Jupyter* is an online book with suggestions for using Jupyter effectively in the classroom. You can read the book at \n", "\n", @@ -225,6 +217,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -243,7 +251,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap01.ipynb b/chapters/chap01.ipynb index 12c68a7..bd3875d 100644 --- a/chapters/chap01.ipynb +++ b/chapters/chap01.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "a14edb7e", @@ -177,9 +169,9 @@ "source": [ "Notice that the result of the division is `42.0` rather than `42`. That's because there are two types of numbers in Python: \n", "\n", - "* **integers**, which represent whole numbers, and \n", + "* **integers**, which represent numbers with no fractional or decimal part, and \n", "\n", - "* **floating-point numbers**, which represent numbers with a decimal point.\n", + "* **floating-point numbers**, which represent integers and numbers with a decimal point.\n", "\n", "If you add, subtract, or multiply two integers, the result is an integer.\n", "But if you divide two integers, the result is a floating-point number.\n", @@ -341,7 +333,7 @@ "## Arithmetic functions\n", "\n", "In addition to the arithmetic operators, Python provides a few **functions** that work with numbers.\n", - "For example, the `round` function takes a floating-point number and rounds it off to the nearest whole number." + "For example, the `round` function takes a floating-point number and rounds it off to the nearest integer." ] }, { @@ -589,7 +581,7 @@ "source": [ "The other arithmetic operators don't work with strings.\n", "\n", - "Python provides a function called `len` that computes the length of a string.`" + "Python provides a function called `len` that computes the length of a string." ] }, { @@ -969,10 +961,10 @@ "A symbol, like `+` and `*`, that denotes an arithmetic operation like addition or multiplication.\n", "\n", "**integer:**\n", - "A type that represents whole numbers.\n", + "A type that represents numbers with no fractional or decimal part.\n", "\n", "**floating-point:**\n", - "A type that represents numbers with fractional parts.\n", + "A type that represents integers and numbers with decimal parts.\n", "\n", "**integer division:**\n", "An operator, `//`, that divides two numbers and rounds down to an integer.\n", @@ -1071,7 +1063,7 @@ "\n", "* I also mentioned the order of operations. For more details, ask \"What is the order of operations in Python?\"\n", "\n", - "* The `round` function, which we used to round a floating-point number to the nearest whole number, can take a second argument. Try asking \"What are the arguments of the round function?\" or \"How do I round pi off to three decimal places?\"\n", + "* The `round` function, which we used to round a floating-point number to the nearest integer, can take a second argument. Try asking \"What are the arguments of the round function?\" or \"How do I round pi off to three decimal places?\"\n", "\n", "* There's one more arithmetic operator I didn't mention; try asking \"What is the modulus operator in Python?\"" ] @@ -1272,6 +1264,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1291,7 +1299,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap02.ipynb b/chapters/chap02.ipynb index 2bf4929..fb2369f 100644 --- a/chapters/chap02.ipynb +++ b/chapters/chap02.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -515,7 +507,7 @@ "id": "cff0414b", "metadata": {}, "source": [ - "Similarly, an import statement has an effect -- it imports a module so we can use the values and functions it contains -- but it has no visible effect." + "Similarly, an import statement has an effect -- it imports a module so we can use the variables and functions it contains -- but it has no visible effect." ] }, { @@ -1017,7 +1009,7 @@ "A special word used to specify the structure of a program.\n", "\n", "**import statement:**\n", - "A statement that reads a module file and creates a module object.\n", + "A statement that reads a module file so we can use the variables and functions it contains.\n", "\n", "**module:**\n", "A file that contains Python code, including function definitions and sometimes other statements.\n", @@ -1036,7 +1028,6 @@ "\n", "**argument:**\n", "A value provided to a function when the function is called.\n", - "Each argument is assigned to the corresponding parameter in the function.\n", "\n", "**comment:**\n", "Text included in a program that provides information about the program but has no effect on its execution.\n", @@ -1061,7 +1052,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 43, "id": "c9e6cab4", "metadata": { "tags": [] @@ -1087,7 +1078,7 @@ "\n", "You might have noticed that `int`, `float`, and `str` are not Python keywords.\n", "They are variables that represent types, and they can be used as functions.\n", - "So it is *legal* to have a variable or function with one of those names, but it is strongly discouraged. Ask an assistant \"Why is it bad to use int, float, and string as variable names?\"\n", + "So it is *legal* to have a variable or function with one of those names, but it is strongly discouraged. Ask an assistant \"Why is it bad to use int, float, and str as variable names?\"\n", "\n", "Also ask, \"What are the built-in functions in Python?\"\n", "If you are curious about any of them, ask for more information.\n", @@ -1130,7 +1121,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 44, "id": "18de7d96", "metadata": {}, "outputs": [], @@ -1153,7 +1144,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 52, "id": "de812cff", "metadata": {}, "outputs": [], @@ -1180,7 +1171,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 46, "id": "b4ada618", "metadata": {}, "outputs": [], @@ -1190,7 +1181,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 47, "id": "4424940f", "metadata": {}, "outputs": [], @@ -1200,7 +1191,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 48, "id": "50e8393a", "metadata": {}, "outputs": [], @@ -1215,6 +1206,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1234,7 +1241,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" }, "vscode": { "interpreter": { diff --git a/chapters/chap03.ipynb b/chapters/chap03.ipynb index 7a5107a..5f2cb1d 100644 --- a/chapters/chap03.ipynb +++ b/chapters/chap03.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -420,7 +412,7 @@ "The first line is a header that ends with a colon.\n", "The second line is the body, which has to be indented.\n", "\n", - "The first line starts with the keyword `for`, a new variable named `i`, and another keyword, `in`. \n", + "The header starts with the keyword `for`, a new variable named `i`, and another keyword, `in`. \n", "It uses the `range` function to create a sequence of two values, which are `0` and `1`.\n", "In Python, when we start counting, we usually start from `0`.\n", "\n", @@ -588,13 +580,13 @@ "frame2 = make_frame(d2, name='cat_twice', dy=-0.3, \n", " offsetx=0.03, loc='left')\n", "\n", - "d3 = dict(s = line1+line2)\n", + "d3 = dict(string=line1+line2)\n", "frame3 = make_frame(d3, name='print_twice', \n", - " offsetx=-0.28, offsety=-0.3, loc='left')\n", + " offsetx=0.04, offsety=-0.3, loc='left')\n", "\n", "d4 = {\"?\": line1+line2}\n", "frame4 = make_frame(d4, name='print', \n", - " offsetx=-0.28, offsety=0, loc='left')\n", + " offsetx=-0.22, offsety=0, loc='left')\n", "\n", "stack = Stack([frame1, frame2, frame3, frame4], dy=-0.8)" ] @@ -611,10 +603,13 @@ "from diagram import diagram, adjust\n", "\n", "\n", - "width, height, x, y = [3.8, 2.91, 1.15, 2.66]\n", + "width, height, x, y = [3.77, 2.9, 1.1, 2.65]\n", "ax = diagram(width, height)\n", "bbox = stack.draw(ax, x, y)\n", - "#adjust(x, y, bbox)" + "# adjust(x, y, bbox)\n", + "\n", + "import matplotlib.pyplot as plt\n", + "plt.savefig('chap03_stack_diagram.png', dpi=300)" ] }, { @@ -881,7 +876,9 @@ "cell_type": "code", "execution_count": 30, "id": "f142ce6a", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "print_right(\"Monty\")\n", @@ -914,7 +911,8 @@ "execution_count": 32, "id": "b8146a0d", "metadata": { - "scrolled": true + "scrolled": true, + "tags": [] }, "outputs": [], "source": [ @@ -946,7 +944,8 @@ "execution_count": 34, "id": "73b0c0f6", "metadata": { - "scrolled": true + "scrolled": true, + "tags": [] }, "outputs": [], "source": [ @@ -997,7 +996,9 @@ { "cell_type": "markdown", "id": "ee0076dd", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "Use this function call to display the first verse." ] @@ -1006,7 +1007,9 @@ "cell_type": "code", "execution_count": 37, "id": "47a91c7d", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "bottle_verse(99)" @@ -1015,7 +1018,9 @@ { "cell_type": "markdown", "id": "42c237c6", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "If you want to print the whole song, you can use this `for` loop, which counts down from `99` to `1`.\n", "You don't have to completely understand this example---we'll learn more about `for` loops and the `range` function later." @@ -1042,6 +1047,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1061,7 +1082,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap04.ipynb b/chapters/chap04.ipynb index 7a09336..7117af6 100644 --- a/chapters/chap04.ipynb +++ b/chapters/chap04.ipynb @@ -2,25 +2,17 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "df64b7da", "metadata": { "tags": [] @@ -45,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "320fc8bc", "metadata": { "tags": [] @@ -83,7 +75,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "8f5a8a45", "metadata": {}, "outputs": [], @@ -101,7 +93,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "b3f255cd", "metadata": {}, "outputs": [], @@ -127,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "234fde81", "metadata": {}, "outputs": [], @@ -145,7 +137,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "1e768880", "metadata": {}, "outputs": [], @@ -165,7 +157,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "6d874b03", "metadata": {}, "outputs": [], @@ -184,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "1bb57a0c", "metadata": {}, "outputs": [], @@ -216,7 +208,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "9a9e455f", "metadata": {}, "outputs": [], @@ -246,7 +238,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "cc27ad66", "metadata": {}, "outputs": [], @@ -271,7 +263,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "ad5f1128", "metadata": {}, "outputs": [], @@ -292,7 +284,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "193bbe5e", "metadata": {}, "outputs": [], @@ -315,7 +307,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "def8a5f1", "metadata": {}, "outputs": [], @@ -336,7 +328,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "b283e795", "metadata": {}, "outputs": [], @@ -354,12 +346,12 @@ "Adding a parameter to a function is called **generalization** because it makes the function more general: with the previous version, the square is always the same size; with this version it can be any size.\n", "\n", "If we add another parameter, we can make it even more general.\n", - "The following function draws regular polygons with a given of sides." + "The following function draws regular polygons with a given number of sides." ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "171974ed", "metadata": {}, "outputs": [], @@ -383,7 +375,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "71f7d9d2", "metadata": {}, "outputs": [], @@ -403,7 +395,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "8ff2a5f4", "metadata": { "tags": [] @@ -439,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "7f2a5f28", "metadata": {}, "outputs": [], @@ -469,7 +461,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "75258056", "metadata": {}, "outputs": [], @@ -508,7 +500,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "381edd23", "metadata": {}, "outputs": [], @@ -531,7 +523,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "2f4eecc0", "metadata": {}, "outputs": [], @@ -551,7 +543,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "539466f6", "metadata": {}, "outputs": [], @@ -576,7 +568,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "8e09f456", "metadata": {}, "outputs": [], @@ -596,7 +588,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "80d6eadd", "metadata": {}, "outputs": [], @@ -633,7 +625,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "1571ee71", "metadata": { "tags": [] @@ -654,7 +646,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "f4e37360", "metadata": { "tags": [] @@ -693,7 +685,6 @@ "\n", "2. Once you get the program working, identify a coherent piece of it,\n", " encapsulate the piece in a function and give it a name.\n", - " Copy and paste working code to avoid retyping (and re-debugging).\n", "\n", "3. Generalize the function by adding appropriate parameters.\n", "\n", @@ -723,7 +714,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "baf964ba", "metadata": {}, "outputs": [], @@ -745,7 +736,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "e2e006d5", "metadata": {}, "outputs": [], @@ -777,7 +768,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "b68f3682", "metadata": {}, "outputs": [], @@ -885,7 +876,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "9f94061e", "metadata": { "tags": [] @@ -914,7 +905,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "id": "6f9a0106", "metadata": {}, "outputs": [], @@ -944,7 +935,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "c54ba660", "metadata": {}, "outputs": [], @@ -964,7 +955,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "1311ee08", "metadata": { "tags": [] @@ -980,14 +971,14 @@ "id": "8b8faaf6", "metadata": {}, "source": [ - "## Exercise\n", + "### Exercise\n", "\n", "Write a function called `rhombus` that draws a rhombus with a given side length and a given interior angle. For example, here's a rhombus with side length `50` and an interior angle of `60` degrees." ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "id": "3db6f106", "metadata": {}, "outputs": [], @@ -1007,7 +998,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "id": "1d845de9", "metadata": { "tags": [] @@ -1030,7 +1021,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "id": "895005cb", "metadata": {}, "outputs": [], @@ -1040,7 +1031,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 38, "id": "7e7d34b0", "metadata": {}, "outputs": [], @@ -1050,7 +1041,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "id": "481396f9", "metadata": {}, "outputs": [], @@ -1070,7 +1061,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "id": "c8dfebc9", "metadata": { "tags": [] @@ -1089,19 +1080,21 @@ }, { "cell_type": "markdown", - "id": "feae252c", + "id": "991ab59d", "metadata": {}, "source": [ "### Exercise\n", "\n", "Write an appropriately general set of functions that can draw shapes like this.\n", "\n", + "![](https://github.com/AllenDowney/ThinkPython/raw/v3/jupyturtle_pie.png)\n", + "\n", "Hint: Write a function called `triangle` that draws one triangular segment, and then a function called `draw_pie` that uses `triangle`." ] }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, "id": "8be6442e", "metadata": {}, "outputs": [], @@ -1111,7 +1104,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 42, "id": "be1b7ed8", "metadata": {}, "outputs": [], @@ -1131,14 +1124,14 @@ }, { "cell_type": "code", - "execution_count": 42, - "id": "89ce198a", + "execution_count": null, + "id": "c519ca39", "metadata": { "tags": [] }, "outputs": [], "source": [ - "make_turtle(delay=0)\n", + "turtle = make_turtle(delay=0)\n", "jump(-80)\n", "\n", "size = 40\n", @@ -1149,21 +1142,35 @@ "draw_pie(7, size)" ] }, + { + "cell_type": "code", + "execution_count": 51, + "id": "89ce198a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, { "cell_type": "markdown", - "id": "7c665dd1", + "id": "9c78b76f", "metadata": {}, "source": [ "### Exercise\n", "\n", "Write an appropriately general set of functions that can draw flowers like this.\n", "\n", + "![](https://github.com/AllenDowney/ThinkPython/raw/v3/jupyturtle_flower.png)\n", + "\n", "Hint: Use `arc` to write a function called `petal` that draws one flower petal." ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 44, "id": "0f0e7498", "metadata": {}, "outputs": [], @@ -1173,7 +1180,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 45, "id": "6c0d0bff", "metadata": {}, "outputs": [], @@ -1198,8 +1205,8 @@ }, { "cell_type": "code", - "execution_count": 45, - "id": "4cfea3b0", + "execution_count": null, + "id": "04193da5", "metadata": { "tags": [] }, @@ -1207,7 +1214,7 @@ "source": [ "from jupyturtle import render\n", "\n", - "make_turtle(auto_render=False)\n", + "turtle = make_turtle(auto_render=False)\n", "\n", "jump(-60)\n", "n = 7\n", @@ -1224,6 +1231,18 @@ "render()" ] }, + { + "cell_type": "code", + "execution_count": 53, + "id": "4cfea3b0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, { "cell_type": "markdown", "id": "9d9f35d1", @@ -1273,7 +1292,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 47, "id": "46d3151c", "metadata": {}, "outputs": [], @@ -1283,7 +1302,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 48, "id": "186c7fbc", "metadata": {}, "outputs": [], @@ -1292,12 +1311,20 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "979f5ad4", - "metadata": {}, - "outputs": [], - "source": [] + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1317,7 +1344,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap05.ipynb b/chapters/chap05.ipynb index e9b6f80..23e5094 100644 --- a/chapters/chap05.ipynb +++ b/chapters/chap05.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -88,8 +80,8 @@ "id": "3f224403", "metadata": {}, "source": [ - "But we don't normally write hours with decimal points. Floor division\n", - "returns the integer number of hours, rounding down:" + "But we don't normally write hours with decimal points.\n", + "Integer division returns the integer number of hours, rounding down:" ] }, { @@ -144,7 +136,7 @@ }, { "cell_type": "markdown", - "id": "f2344fc0", + "id": "18c1e0d0", "metadata": {}, "source": [ "The modulus operator is more useful than it might seem.\n", @@ -152,15 +144,42 @@ "\n", "Also, it can extract the right-most digit or digits from a number.\n", "For example, `x % 10` yields the right-most digit of `x` (in base 10).\n", - "Similarly, `x % 100` yields the last two digits.\n", - "\n", + "Similarly, `x % 100` yields the last two digits." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5bd341f7", + "metadata": {}, + "outputs": [], + "source": [ + "x = 123\n", + "x % 10" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "367fce0c", + "metadata": {}, + "outputs": [], + "source": [ + "x % 100" + ] + }, + { + "cell_type": "markdown", + "id": "f2344fc0", + "metadata": {}, + "source": [ "Finally, the modulus operator can do \"clock arithmetic\".\n", "For example, if an event starts at 11 AM and lasts three hours, we can use the modulus operator to figure out what time it ends." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 1, "id": "db33a44d", "metadata": {}, "outputs": [], @@ -179,18 +198,6 @@ "The event would end at 2 PM." ] }, - { - "cell_type": "code", - "execution_count": 7, - "id": "367fce0c", - "metadata": {}, - "outputs": [], - "source": [ - "a = 25 // 10\n", - "b = 25 % 10\n", - "a, b" - ] - }, { "cell_type": "markdown", "id": "5ed1b58b", @@ -1168,7 +1175,7 @@ "metadata": {}, "source": [ "The error message indicates line 5, but there is nothing wrong with that line.\n", - "The problem is in line 4, which uses floor division instead of floating-point division -- as a result, the value of `ratio` is `0`.\n", + "The problem is in line 4, which uses integer division instead of floating-point division -- as a result, the value of `ratio` is `0`.\n", "When we call `math.log10`, we get a `ValueError` with the message `math domain error`, because `0` is not in the \"domain\" of valid arguments for `math.log10`, because the logarithm of `0` is undefined.\n", "\n", "In general, you should take the time to read error messages carefully, but don't assume that everything they say is correct." @@ -1414,7 +1421,7 @@ "id": "054c3197", "metadata": {}, "source": [ - "Use floor division and the modulus operator to compute the number of days since January 1, 1970 and the current time of day in hours, minutes, and seconds." + "Use integer division and the modulus operator to compute the number of days since January 1, 1970 and the current time of day in hours, minutes, and seconds." ] }, { @@ -1687,7 +1694,7 @@ "\n", "The exception is if `x` is less than `5` -- in that case, you can just draw a straight line with length `x`.\n", "\n", - "Write a function called `koch` that takes `x` as a parameter and draws a Koch curve with the given length.\n" + "Write a function called `koch` that takes `x` as an argument and draws a Koch curve with the given length.\n" ] }, { @@ -1799,6 +1806,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1818,7 +1841,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap06.ipynb b/chapters/chap06.ipynb index 9763962..a8d03c3 100644 --- a/chapters/chap06.ipynb +++ b/chapters/chap06.ipynb @@ -2,25 +2,17 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 87, + "execution_count": 5, "id": "56b1c184", "metadata": { "tags": [] @@ -55,7 +47,7 @@ "When you call one of these functions, it returns a value you can assign to a variable or use as part of an expression.\n", "\n", "The functions we have written so far are different.\n", - "Some use the `print` function to display values, and some use `Turtle` functions to draw figures.\n", + "Some use the `print` function to display values, and some use turtle functions to draw figures.\n", "But they don't return values we assign to variables or use in expressions.\n", "\n", "In this chapter, we'll see how to write functions that return values." @@ -74,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 6, "id": "e0e1dd91", "metadata": {}, "outputs": [], @@ -94,7 +86,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 7, "id": "5aaf62d2", "metadata": {}, "outputs": [], @@ -112,7 +104,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 8, "id": "741f7386", "metadata": {}, "outputs": [], @@ -130,7 +122,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 9, "id": "e56d39c4", "metadata": {}, "outputs": [], @@ -148,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 10, "id": "50a9a9be", "metadata": {}, "outputs": [], @@ -172,7 +164,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 11, "id": "d70fd9b5", "metadata": {}, "outputs": [], @@ -190,7 +182,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 12, "id": "ef20ba8c", "metadata": {}, "outputs": [], @@ -208,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 13, "id": "0a4670f4", "metadata": {}, "outputs": [], @@ -226,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 14, "id": "6e6460b9", "metadata": {}, "outputs": [], @@ -244,7 +236,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 15, "id": "77613df9", "metadata": { "tags": [] @@ -277,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 16, "id": "89c083f8", "metadata": {}, "outputs": [], @@ -296,7 +288,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 17, "id": "737b67ca", "metadata": {}, "outputs": [], @@ -315,7 +307,7 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 18, "id": "9b4fa14f", "metadata": {}, "outputs": [], @@ -333,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 19, "id": "50f96bcb", "metadata": {}, "outputs": [], @@ -352,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 20, "id": "6712f2df", "metadata": {}, "outputs": [], @@ -372,7 +364,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 21, "id": "0ec1afd3", "metadata": {}, "outputs": [], @@ -394,7 +386,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 22, "id": "c82334b6", "metadata": {}, "outputs": [], @@ -412,7 +404,7 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 23, "id": "595ec598", "metadata": {}, "outputs": [], @@ -440,7 +432,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 24, "id": "236c59e6", "metadata": {}, "outputs": [], @@ -467,7 +459,7 @@ }, { "cell_type": "code", - "execution_count": 107, + "execution_count": 25, "id": "2f60639c", "metadata": {}, "outputs": [], @@ -489,7 +481,7 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 26, "id": "c9dae6c8", "metadata": {}, "outputs": [], @@ -509,7 +501,7 @@ }, { "cell_type": "code", - "execution_count": 109, + "execution_count": 27, "id": "c8c4edee", "metadata": {}, "outputs": [], @@ -563,7 +555,7 @@ }, { "cell_type": "code", - "execution_count": 110, + "execution_count": 28, "id": "bbcab1ed", "metadata": {}, "outputs": [], @@ -585,7 +577,7 @@ }, { "cell_type": "code", - "execution_count": 111, + "execution_count": 29, "id": "923d96db", "metadata": {}, "outputs": [], @@ -609,7 +601,7 @@ }, { "cell_type": "code", - "execution_count": 112, + "execution_count": 30, "id": "9374cfe3", "metadata": {}, "outputs": [], @@ -635,7 +627,7 @@ }, { "cell_type": "code", - "execution_count": 113, + "execution_count": 31, "id": "405af839", "metadata": {}, "outputs": [], @@ -653,7 +645,7 @@ }, { "cell_type": "code", - "execution_count": 114, + "execution_count": 32, "id": "e52b3b04", "metadata": {}, "outputs": [], @@ -676,7 +668,7 @@ }, { "cell_type": "code", - "execution_count": 115, + "execution_count": 33, "id": "38eebbf3", "metadata": {}, "outputs": [], @@ -694,7 +686,7 @@ }, { "cell_type": "code", - "execution_count": 116, + "execution_count": 34, "id": "b4536ea0", "metadata": {}, "outputs": [], @@ -717,7 +709,7 @@ }, { "cell_type": "code", - "execution_count": 117, + "execution_count": 35, "id": "325efb93", "metadata": {}, "outputs": [], @@ -737,7 +729,7 @@ }, { "cell_type": "code", - "execution_count": 118, + "execution_count": 36, "id": "3cd982ce", "metadata": {}, "outputs": [], @@ -761,7 +753,7 @@ }, { "cell_type": "code", - "execution_count": 119, + "execution_count": 37, "id": "c734f5b2", "metadata": {}, "outputs": [], @@ -779,7 +771,7 @@ }, { "cell_type": "code", - "execution_count": 120, + "execution_count": 38, "id": "094a242f", "metadata": {}, "outputs": [], @@ -821,7 +813,7 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 39, "id": "64207948", "metadata": {}, "outputs": [], @@ -843,7 +835,7 @@ }, { "cell_type": "code", - "execution_count": 122, + "execution_count": 40, "id": "c367cdae", "metadata": {}, "outputs": [], @@ -853,7 +845,7 @@ }, { "cell_type": "code", - "execution_count": 123, + "execution_count": 41, "id": "837f4f95", "metadata": {}, "outputs": [], @@ -872,7 +864,7 @@ }, { "cell_type": "code", - "execution_count": 124, + "execution_count": 42, "id": "e411354f", "metadata": {}, "outputs": [], @@ -891,7 +883,7 @@ }, { "cell_type": "code", - "execution_count": 125, + "execution_count": 43, "id": "925e7d4f", "metadata": {}, "outputs": [], @@ -910,7 +902,7 @@ }, { "cell_type": "code", - "execution_count": 126, + "execution_count": 44, "id": "62178e75", "metadata": {}, "outputs": [], @@ -949,7 +941,7 @@ "n! &= n~(n-1)!\n", "\\end{aligned}$$ \n", "\n", - "This definition says that the factorial of 0 is 1, and the factorial of any other value, $n$, is $n$ multiplied by the factorial of $n-1$.\n", + "This definition says that the factorial of $0$ is $1$, and the factorial of any other value, $n$, is $n$ multiplied by the factorial of $n-1$.\n", "\n", "If you can write a recursive definition of something, you can write a Python program to evaluate it. \n", "Following an incremental development process, we'll start with a function that take `n` as a parameter and always returns `0`." @@ -957,7 +949,7 @@ }, { "cell_type": "code", - "execution_count": 127, + "execution_count": 45, "id": "23e37c79", "metadata": {}, "outputs": [], @@ -971,12 +963,12 @@ "id": "ee1f63b8", "metadata": {}, "source": [ - "Now let's add the first part of the definition -- if the argument happens to be 0, all we have to do is return 1:" + "Now let's add the first part of the definition -- if the argument happens to be `0`, all we have to do is return `1`:" ] }, { "cell_type": "code", - "execution_count": 128, + "execution_count": 46, "id": "5ea57d9f", "metadata": {}, "outputs": [], @@ -994,12 +986,12 @@ "metadata": {}, "source": [ "Now let's fill in the second part -- if `n` is not `0`, we have to make a recursive\n", - "call to find the factorial of $n-1$ and then multiply the result by $n$:" + "call to find the factorial of `n-1` and then multiply the result by `n`:" ] }, { "cell_type": "code", - "execution_count": 129, + "execution_count": 47, "id": "b66e670b", "metadata": {}, "outputs": [], @@ -1047,7 +1039,7 @@ }, { "cell_type": "code", - "execution_count": 130, + "execution_count": 48, "id": "455f0457", "metadata": { "tags": [] @@ -1071,7 +1063,7 @@ " loc='left', dx=1.2)\n", " frames.append(frame)\n", " \n", - "binding1 = make_binding('n', n)\n", + "binding1 = make_binding('n', 0)\n", "frame = Frame([binding1], name='factorial', value=1, \n", " shim=1.2, loc='left', dx=1.4)\n", "frames.append(frame)\n", @@ -1081,7 +1073,7 @@ }, { "cell_type": "code", - "execution_count": 131, + "execution_count": 49, "id": "a75ccd9b", "metadata": { "tags": [] @@ -1150,7 +1142,7 @@ }, { "cell_type": "code", - "execution_count": 132, + "execution_count": 50, "id": "cad75752", "metadata": {}, "outputs": [], @@ -1170,7 +1162,7 @@ "metadata": {}, "source": [ "If you try to follow the flow of execution here, even for small values of $n$, your head explodes.\n", - "But according to the leap of faith, if you assume that the two recursive calls work correctly, you can be confident that the last return statement is correct.\n", + "But according to the leap of faith, if you assume that the two recursive calls work correctly, you can be confident that the last `return` statement is correct.\n", "\n", "As an aside, this way of computing Fibonacci numbers is very inefficient.\n", "In [Chapter 10](section_memos) I'll explain why and suggest a way to improve it." @@ -1188,7 +1180,7 @@ }, { "cell_type": "code", - "execution_count": 133, + "execution_count": 51, "id": "5e4b5f1d", "metadata": { "tags": [] @@ -1205,8 +1197,7 @@ "id": "0bec7ba4", "metadata": {}, "source": [ - "It looks like an infinite recursion. How can that be? The function has a\n", - "base case -- when `n == 0`.\n", + "It looks like an infinite recursion. How can that be? The function has base cases when `n == 1` or `n == 0`.\n", "But if `n` is not an integer, we can *miss* the base case and recurse forever.\n", "\n", "In this example, the initial value of `n` is `1.5`.\n", @@ -1220,7 +1211,7 @@ }, { "cell_type": "code", - "execution_count": 134, + "execution_count": 52, "id": "3f607dff", "metadata": {}, "outputs": [], @@ -1230,7 +1221,7 @@ }, { "cell_type": "code", - "execution_count": 135, + "execution_count": 53, "id": "ab638bfe", "metadata": {}, "outputs": [], @@ -1248,7 +1239,7 @@ }, { "cell_type": "code", - "execution_count": 136, + "execution_count": 54, "id": "73aafac0", "metadata": {}, "outputs": [], @@ -1278,7 +1269,7 @@ }, { "cell_type": "code", - "execution_count": 137, + "execution_count": 55, "id": "be881cb7", "metadata": {}, "outputs": [], @@ -1297,7 +1288,7 @@ }, { "cell_type": "code", - "execution_count": 138, + "execution_count": 56, "id": "fa83014f", "metadata": {}, "outputs": [], @@ -1346,7 +1337,7 @@ }, { "cell_type": "code", - "execution_count": 139, + "execution_count": 57, "id": "1d50479e", "metadata": {}, "outputs": [], @@ -1370,12 +1361,12 @@ "metadata": {}, "source": [ "`space` is a string of space characters that controls the indentation of\n", - "the output. Here is the result of `factorial(4)` :" + "the output. Here is the result of `factorial(3)` :" ] }, { "cell_type": "code", - "execution_count": 140, + "execution_count": 58, "id": "798db5c4", "metadata": {}, "outputs": [], @@ -1432,7 +1423,7 @@ }, { "cell_type": "code", - "execution_count": 142, + "execution_count": 59, "id": "e0f15ca4", "metadata": { "tags": [] @@ -1457,7 +1448,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 60, "id": "90b4979f", "metadata": {}, "outputs": [], @@ -1479,7 +1470,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 61, "id": "9217f038", "metadata": {}, "outputs": [], @@ -1503,7 +1494,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 62, "id": "3168489b", "metadata": {}, "outputs": [], @@ -1542,7 +1533,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 63, "id": "62267fa3", "metadata": {}, "outputs": [], @@ -1552,7 +1543,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 64, "id": "5f8fa829", "metadata": {}, "outputs": [], @@ -1562,7 +1553,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 65, "id": "3d129b03", "metadata": {}, "outputs": [], @@ -1572,7 +1563,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 66, "id": "030179b6", "metadata": {}, "outputs": [], @@ -1582,7 +1573,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 67, "id": "d737b468", "metadata": {}, "outputs": [], @@ -1592,7 +1583,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 68, "id": "77a74879", "metadata": {}, "outputs": [], @@ -1602,7 +1593,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 69, "id": "0521d267", "metadata": {}, "outputs": [], @@ -1612,7 +1603,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 70, "id": "468a31e9", "metadata": {}, "outputs": [], @@ -1622,7 +1613,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 71, "id": "abbe3ebf", "metadata": {}, "outputs": [], @@ -1632,7 +1623,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 72, "id": "651295e4", "metadata": {}, "outputs": [], @@ -1653,7 +1644,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 73, "id": "0a4ee482", "metadata": {}, "outputs": [], @@ -1673,7 +1664,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 74, "id": "956ed6d7", "metadata": { "tags": [] @@ -1685,7 +1676,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 75, "id": "a994eaa6", "metadata": { "tags": [] @@ -1697,7 +1688,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 76, "id": "4318028d", "metadata": { "tags": [] @@ -1709,7 +1700,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 77, "id": "05208c8b", "metadata": { "tags": [] @@ -1742,7 +1733,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 78, "id": "7eb85c5c", "metadata": {}, "outputs": [], @@ -1762,7 +1753,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 79, "id": "687a3e5a", "metadata": { "tags": [] @@ -1774,7 +1765,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 80, "id": "c49e9749", "metadata": { "tags": [] @@ -1786,7 +1777,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 81, "id": "8497dec4", "metadata": { "tags": [] @@ -1808,7 +1799,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 82, "id": "76be4d15", "metadata": { "tags": [] @@ -1847,7 +1838,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 83, "id": "0bcba5fe", "metadata": {}, "outputs": [], @@ -1867,7 +1858,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 84, "id": "4b6656e6", "metadata": { "tags": [] @@ -1879,7 +1870,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 85, "id": "36d9e92a", "metadata": { "tags": [] @@ -1891,7 +1882,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 86, "id": "1d944b42", "metadata": { "tags": [] @@ -1903,7 +1894,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 87, "id": "63ec57c9", "metadata": { "tags": [] @@ -1933,7 +1924,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 88, "id": "4e067bfb", "metadata": {}, "outputs": [], @@ -1953,7 +1944,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 89, "id": "2a7c1c21", "metadata": { "tags": [] @@ -1965,7 +1956,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 90, "id": "5df00229", "metadata": { "tags": [] @@ -1982,6 +1973,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -2001,7 +2008,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap07.ipynb b/chapters/chap07.ipynb index e026e3e..9d9c634 100644 --- a/chapters/chap07.ipynb +++ b/chapters/chap07.ipynb @@ -2,25 +2,17 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "f0c8eb18", "metadata": { "tags": [] @@ -74,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "id": "6b8569b8-1576-45d2-99f6-c7a2c7e100c4", "metadata": {}, "outputs": [], @@ -95,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "id": "6cb5b573-601c-42f5-a940-f1a4d244f990", "metadata": {}, "outputs": [], @@ -117,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "id": "7040a890-6619-4ad2-b0bf-b094a5a0e43d", "metadata": {}, "outputs": [], @@ -137,7 +129,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "40fd553c-693c-4ffc-8e49-1d7de3c4d4a7", "metadata": {}, "outputs": [], @@ -158,7 +150,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "8a34174b-5a77-482a-8480-14cfdff16339", "metadata": {}, "outputs": [], @@ -180,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "id": "0909121d-5218-49ca-b03e-258206f00e40", "metadata": {}, "outputs": [], @@ -202,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "id": "3c4d8138-ddf6-46fe-a940-a7042617ceb1", "metadata": {}, "outputs": [], @@ -212,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "id": "5c463051-b737-49ab-a1d7-4be66fd8331f", "metadata": {}, "outputs": [], @@ -243,7 +235,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "a7b7ac52-64a6-4dd9-98f5-b772a5e0f161", "metadata": { "tags": [] @@ -264,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "1ad13ce7-99be-4412-8e0b-978fe6de25f2", "metadata": {}, "outputs": [], @@ -282,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "id": "dc2054e6-d8e8-4a06-a1ea-5cfcf4ccf1e0", "metadata": {}, "outputs": [], @@ -307,7 +299,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "id": "eaea1520-0fb3-4ef3-be6e-9e1cdcccf39f", "metadata": {}, "outputs": [], @@ -326,7 +318,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "id": "f602bfb6-7a93-4fb8-ade6-6784155a6f1a", "metadata": {}, "outputs": [], @@ -348,7 +340,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "id": "cf3b8b7e-5fc7-4bb1-b628-09277bdc5a0d", "metadata": { "tags": [] @@ -385,7 +377,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "id": "6bf8a104", "metadata": {}, "outputs": [], @@ -404,7 +396,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 17, "id": "0fe7ae60", "metadata": {}, "outputs": [], @@ -423,7 +415,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "id": "8a09bc24", "metadata": { "tags": [] @@ -437,7 +429,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 19, "id": "36a45674-7f41-4850-98f1-2548574ce958", "metadata": { "tags": [] @@ -466,7 +458,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 20, "id": "ba2ab90b", "metadata": { "tags": [] @@ -478,7 +470,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 21, "id": "88496dc4", "metadata": {}, "outputs": [], @@ -499,14 +491,16 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 22, "id": "4a0c46b9", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "%%expect NameError\n", "\n", - "y = y + 1" + "z = z + 1" ] }, { @@ -520,14 +514,14 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 23, "id": "2220d826", "metadata": {}, "outputs": [], "source": [ - "y = 0\n", - "y = y + 1\n", - "y" + "z = 0\n", + "z = z + 1\n", + "z" ] }, { @@ -535,7 +529,28 @@ "id": "374fb3d5", "metadata": {}, "source": [ - "Increasing the value of a variable is called an **increment**; decreasing the value is called a **decrement**." + "Increasing the value of a variable is called an **increment**; decreasing the value is called a **decrement**.\n", + "Because these operations are so common, Python provides **augmented assignment operators** that update a variable more concisely.\n", + "For example, the `+=` operator increments a variable by the given amount." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d8e1ac5a", + "metadata": {}, + "outputs": [], + "source": [ + "z += 2\n", + "z" + ] + }, + { + "cell_type": "markdown", + "id": "3f4eedf1", + "metadata": {}, + "source": [ + "There are augmented assignment operators for the other arithmetic operators, including `-=` and `*=`." ] }, { @@ -550,7 +565,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "0afd8f88", "metadata": {}, "outputs": [], @@ -559,7 +574,7 @@ "\n", "for line in open('words.txt'):\n", " word = line.strip()\n", - " total = total + 1" + " total += 1" ] }, { @@ -574,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "id": "8686b2eb-c610-4d29-a942-2ef8f53e5e36", "metadata": {}, "outputs": [], @@ -594,7 +609,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "id": "89a05280", "metadata": {}, "outputs": [], @@ -606,7 +621,7 @@ " word = line.strip()\n", " total = total + 1\n", " if has_e(word):\n", - " count = count + 1" + " count += 1" ] }, { @@ -619,7 +634,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "id": "9d29b5e9", "metadata": {}, "outputs": [], @@ -637,7 +652,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "id": "304dfd86", "metadata": {}, "outputs": [], @@ -666,7 +681,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 30, "id": "fe6431b7", "metadata": {}, "outputs": [], @@ -685,7 +700,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "id": "85d3fba6", "metadata": {}, "outputs": [], @@ -707,7 +722,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 32, "id": "2d653847", "metadata": {}, "outputs": [], @@ -727,7 +742,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 33, "id": "a92a81bc", "metadata": {}, "outputs": [], @@ -745,7 +760,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 34, "id": "d15f83a4", "metadata": {}, "outputs": [], @@ -763,7 +778,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 35, "id": "e7958af4", "metadata": {}, "outputs": [], @@ -774,7 +789,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 36, "id": "020a57a7", "metadata": {}, "outputs": [], @@ -784,7 +799,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 37, "id": "0b979b20", "metadata": {}, "outputs": [], @@ -800,12 +815,12 @@ "## Search\n", "\n", "Based on this simpler version of `has_e`, let's write a more general function called `uses_any` that takes a second parameter that is a string of letters.\n", - "If returns `True` if the word uses any of the letters and `False` otherwise." + "It returns `True` if the word uses any of the letters and `False` otherwise." ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 38, "id": "bd29ff63", "metadata": {}, "outputs": [], @@ -827,7 +842,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 39, "id": "9369fb05", "metadata": {}, "outputs": [], @@ -845,7 +860,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 40, "id": "eb32713a", "metadata": {}, "outputs": [], @@ -858,12 +873,12 @@ "id": "b2acc611", "metadata": {}, "source": [ - "`uses_only` converts `word` and `letters` to lowercase, so it works with any combination of cases. " + "`uses_any` converts `word` and `letters` to lowercase, so it works with any combination of cases. " ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 41, "id": "7e65a9fb", "metadata": {}, "outputs": [], @@ -899,7 +914,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 42, "id": "3982e7d3", "metadata": {}, "outputs": [], @@ -936,7 +951,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 43, "id": "40ef00d3", "metadata": {}, "outputs": [], @@ -958,7 +973,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 44, "id": "f37cfd36", "metadata": {}, "outputs": [], @@ -981,7 +996,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 45, "id": "58c916cc", "metadata": {}, "outputs": [], @@ -1011,7 +1026,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 46, "id": "7a325745", "metadata": {}, "outputs": [], @@ -1061,7 +1076,7 @@ " A variable used to count something, usually initialized to zero and then incremented.\n", "\n", "**linear search:**\n", - "A computational pattern that searches through a sequence of elements and stops what it finds what it is looking for.\n", + "A computational pattern that searches through a sequence of elements and stops when it finds what it is looking for.\n", "\n", "**pass:**\n", "If a test runs and the result is as expected, the test passes.\n", @@ -1080,7 +1095,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 47, "id": "bc58db59", "metadata": { "tags": [] @@ -1388,7 +1403,7 @@ "\n", "* Each puzzle includes at least one \"pangram\" which uses every letter. These are worth 7 extra points!\n", "\n", - "Write a function called `score_word` that takes a word and a string of available lessons and returns its score.\n", + "Write a function called `score_word` that takes a word and a string of available letters and returns its score.\n", "You can assume that the word is acceptable.\n", "\n", "Again, here's an outline of the function with doctests." @@ -1457,6 +1472,7 @@ "source": [ "available = 'ACDLORT'\n", "required = 'R'\n", + "\n", "total = 0\n", "\n", "file_object = open('words.txt')\n", @@ -1597,20 +1613,46 @@ { "cell_type": "code", "execution_count": 71, - "id": "6980de57", + "id": "a3ea747d", "metadata": {}, "outputs": [], "source": [ - "# Solution goes here" + "# Here's what I got from ChatGPT 4o December 26, 2024\n", + "# It's correct, but it makes multiple calls to uses_any \n", + "\n", + "def uses_all(s1, s2):\n", + " \"\"\"Checks if all characters in s2 are in s1, allowing repeats.\"\"\"\n", + " for char in s2:\n", + " if not uses_any(s1, char):\n", + " return False\n", + " return True\n" ] }, { "cell_type": "code", - "execution_count": null, - "id": "102df097", + "execution_count": 72, + "id": "6980de57", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1630,7 +1672,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap08.ipynb b/chapters/chap08.ipynb index fa28c8b..0108312 100644 --- a/chapters/chap08.ipynb +++ b/chapters/chap08.ipynb @@ -2,25 +2,17 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "361d390a", "metadata": { "tags": [] @@ -75,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 2, "id": "9b53c1fe", "metadata": {}, "outputs": [], @@ -95,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 3, "id": "2cb1d58c", "metadata": {}, "outputs": [], @@ -114,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 4, "id": "4ce1eb16", "metadata": {}, "outputs": [], @@ -134,7 +126,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 5, "id": "11201ba9", "metadata": {}, "outputs": [], @@ -153,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 6, "id": "fc4383d0", "metadata": {}, "outputs": [], @@ -171,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 7, "id": "aec20975", "metadata": { "tags": [] @@ -193,7 +185,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 8, "id": "796ce317", "metadata": {}, "outputs": [], @@ -212,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 9, "id": "3ccb4a64", "metadata": { "tags": [] @@ -234,7 +226,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 10, "id": "2cf99de6", "metadata": {}, "outputs": [], @@ -253,7 +245,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 11, "id": "3dedf6fa", "metadata": {}, "outputs": [], @@ -282,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 12, "id": "386b9df2", "metadata": {}, "outputs": [], @@ -303,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 13, "id": "05f9743d", "metadata": { "tags": [] @@ -318,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 14, "id": "b09d8356", "metadata": { "tags": [] @@ -364,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 15, "id": "00592313", "metadata": {}, "outputs": [], @@ -382,7 +374,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 16, "id": "01684797", "metadata": {}, "outputs": [], @@ -400,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 17, "id": "c7551ded", "metadata": {}, "outputs": [], @@ -421,7 +413,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 18, "id": "b5c5ce3e", "metadata": { "tags": [] @@ -444,7 +436,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 19, "id": "69ccd380", "metadata": { "tags": [] @@ -473,7 +465,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 20, "id": "280d27a1", "metadata": {}, "outputs": [], @@ -493,7 +485,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 21, "id": "8fa4a4cf", "metadata": {}, "outputs": [], @@ -514,7 +506,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 22, "id": "b754d462", "metadata": {}, "outputs": [], @@ -536,7 +528,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 23, "id": "44374eb8", "metadata": {}, "outputs": [], @@ -552,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 24, "id": "a46f7035", "metadata": {}, "outputs": [], @@ -572,7 +564,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 25, "id": "a691f9e2", "metadata": {}, "outputs": [], @@ -605,7 +597,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 26, "id": "fa6140a6", "metadata": {}, "outputs": [], @@ -641,7 +633,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 27, "id": "e3f1dc18", "metadata": { "tags": [] @@ -664,7 +656,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 28, "id": "bd2d5175", "metadata": {}, "outputs": [], @@ -686,7 +678,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 29, "id": "b9c9318c", "metadata": {}, "outputs": [], @@ -705,7 +697,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 30, "id": "a9417d4c", "metadata": {}, "outputs": [], @@ -727,7 +719,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 31, "id": "f2336825", "metadata": {}, "outputs": [], @@ -749,7 +741,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 32, "id": "d1b286ee", "metadata": {}, "outputs": [], @@ -771,7 +763,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 33, "id": "b4ecf365", "metadata": {}, "outputs": [], @@ -793,7 +785,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 34, "id": "a99dc11c", "metadata": {}, "outputs": [], @@ -814,7 +806,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 35, "id": "dfd6b264", "metadata": {}, "outputs": [], @@ -833,7 +825,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 36, "id": "4eda555c", "metadata": {}, "outputs": [], @@ -852,7 +844,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 37, "id": "5e1e8c74", "metadata": {}, "outputs": [], @@ -870,7 +862,7 @@ "id": "34c93df3", "metadata": {}, "source": [ - "The `endswidth` method checks whether a string ends with a given sequence of characters." + "The `endswith` method checks whether a string ends with a given sequence of characters." ] }, { @@ -888,7 +880,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 38, "id": "63ebaafb", "metadata": {}, "outputs": [], @@ -910,7 +902,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 39, "id": "9973e6e8", "metadata": {}, "outputs": [], @@ -934,7 +926,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 40, "id": "02e06ff1", "metadata": {}, "outputs": [], @@ -956,7 +948,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 41, "id": "1450e82c", "metadata": {}, "outputs": [], @@ -978,7 +970,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 42, "id": "a57b64c6", "metadata": { "tags": [] @@ -1010,7 +1002,7 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 43, "id": "a6069027", "metadata": {}, "outputs": [], @@ -1028,7 +1020,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 44, "id": "e3c19abe", "metadata": {}, "outputs": [], @@ -1047,7 +1039,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 45, "id": "db588abb", "metadata": {}, "outputs": [], @@ -1069,7 +1061,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 46, "id": "924524a6", "metadata": {}, "outputs": [], @@ -1082,12 +1074,12 @@ "id": "a8eab0f6", "metadata": {}, "source": [ - "It also provides a function called `group` that returns the part of the text that matched the pattern." + "It also provides a method called `group` that returns the part of the text that matched the pattern." ] }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 47, "id": "c72b860c", "metadata": {}, "outputs": [], @@ -1100,12 +1092,12 @@ "id": "b6962a7d", "metadata": {}, "source": [ - "And it provides a function called `span` that returns the index in the text where the pattern starts and ends." + "And it provides a method called `span` that returns the index in the text where the pattern starts and ends." ] }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 48, "id": "7c2f556c", "metadata": {}, "outputs": [], @@ -1123,7 +1115,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 49, "id": "d5242ef6", "metadata": {}, "outputs": [], @@ -1142,7 +1134,7 @@ }, { "cell_type": "code", - "execution_count": 107, + "execution_count": 50, "id": "18c09b63", "metadata": {}, "outputs": [], @@ -1160,7 +1152,7 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 51, "id": "fedb7d95", "metadata": {}, "outputs": [], @@ -1182,7 +1174,7 @@ }, { "cell_type": "code", - "execution_count": 109, + "execution_count": 52, "id": "d7cbe2c2", "metadata": {}, "outputs": [], @@ -1206,12 +1198,12 @@ }, { "cell_type": "code", - "execution_count": 147, + "execution_count": 53, "id": "96c64f83", "metadata": {}, "outputs": [], "source": [ - "pattern = r'Mina|Murray'\n", + "pattern = 'Mina|Murray'\n", "result = find_first(pattern)\n", "result.string" ] @@ -1227,7 +1219,7 @@ }, { "cell_type": "code", - "execution_count": 111, + "execution_count": 54, "id": "d0d2e926", "metadata": {}, "outputs": [], @@ -1251,7 +1243,7 @@ }, { "cell_type": "code", - "execution_count": 112, + "execution_count": 55, "id": "d7e8c5b4", "metadata": {}, "outputs": [], @@ -1269,7 +1261,7 @@ }, { "cell_type": "code", - "execution_count": 113, + "execution_count": 56, "id": "be63c5b0", "metadata": {}, "outputs": [], @@ -1288,7 +1280,7 @@ }, { "cell_type": "code", - "execution_count": 114, + "execution_count": 57, "id": "37595ac5", "metadata": {}, "outputs": [], @@ -1311,7 +1303,7 @@ }, { "cell_type": "code", - "execution_count": 115, + "execution_count": 58, "id": "18237bea", "metadata": {}, "outputs": [], @@ -1330,7 +1322,7 @@ }, { "cell_type": "code", - "execution_count": 116, + "execution_count": 59, "id": "ce65805f", "metadata": {}, "outputs": [], @@ -1352,7 +1344,7 @@ }, { "cell_type": "code", - "execution_count": 117, + "execution_count": 60, "id": "af770664", "metadata": {}, "outputs": [], @@ -1370,7 +1362,7 @@ }, { "cell_type": "code", - "execution_count": 118, + "execution_count": 61, "id": "ed67bde7", "metadata": {}, "outputs": [], @@ -1393,7 +1385,7 @@ }, { "cell_type": "code", - "execution_count": 119, + "execution_count": 62, "id": "52dd938c", "metadata": {}, "outputs": [], @@ -1412,7 +1404,7 @@ }, { "cell_type": "code", - "execution_count": 120, + "execution_count": 63, "id": "d2e309a2", "metadata": { "tags": [] @@ -1430,13 +1422,15 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 64, "id": "3d8b2a9f", "metadata": { "tags": [] }, "outputs": [], "source": [ + "# Here's the pattern I used (which uses some features we haven't seen)\n", + "\n", "names = r'(?= 8 and is_interlocking(word):\n", + " first = word[0::2]\n", + " second = word[1::2]\n", + " print(word, first, second)" ] }, { @@ -1830,6 +1816,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1849,7 +1851,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap11.ipynb b/chapters/chap11.ipynb index 456393d..e10980c 100644 --- a/chapters/chap11.ipynb +++ b/chapters/chap11.ipynb @@ -2,25 +2,17 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "295ac6d7", "metadata": { "tags": [] @@ -78,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "fb0bdca2", "metadata": {}, "outputs": [], @@ -97,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "5a6da881", "metadata": {}, "outputs": [], @@ -116,7 +108,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "e2596ca7", "metadata": {}, "outputs": [], @@ -135,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "a0d350a6", "metadata": {}, "outputs": [], @@ -155,7 +147,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "c9100ee4", "metadata": {}, "outputs": [], @@ -175,7 +167,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "44bd3d83", "metadata": {}, "outputs": [], @@ -197,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "92e55b2c", "metadata": {}, "outputs": [], @@ -215,7 +207,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "38ee5c2a", "metadata": {}, "outputs": [], @@ -233,7 +225,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "2e0e311a", "metadata": {}, "outputs": [], @@ -251,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "8bb7d715", "metadata": {}, "outputs": [], @@ -269,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "e653e00f", "metadata": {}, "outputs": [], @@ -287,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "8969188d", "metadata": {}, "outputs": [], @@ -305,7 +297,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "65d7ebaa", "metadata": {}, "outputs": [], @@ -333,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "b4970fe0", "metadata": { "tags": [] @@ -354,7 +346,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "772738cc", "metadata": { "tags": [] @@ -379,7 +371,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "37e67042", "metadata": {}, "outputs": [], @@ -399,7 +391,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "d809a490", "metadata": {}, "outputs": [], @@ -417,7 +409,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "dfc42a8b", "metadata": {}, "outputs": [], @@ -436,14 +428,13 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "2debf30c", "metadata": {}, "outputs": [], "source": [ "t = tuple('abc')\n", - "s = [1, 2, 3]\n", - "d = {t: s}\n", + "d = {'key': t}\n", "d" ] }, @@ -459,7 +450,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "1e94ea37", "metadata": {}, "outputs": [], @@ -478,7 +469,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "99c96c7f", "metadata": {}, "outputs": [], @@ -497,7 +488,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "b67881ed", "metadata": {}, "outputs": [], @@ -516,7 +507,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "b4515e2b", "metadata": {}, "outputs": [], @@ -535,7 +526,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "8e5b4a14", "metadata": { "tags": [] @@ -557,7 +548,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "2389d6de", "metadata": {}, "outputs": [], @@ -577,7 +568,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "5512edec", "metadata": {}, "outputs": [], @@ -598,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "651ab417", "metadata": {}, "outputs": [], @@ -622,7 +613,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "2c0b7d47", "metadata": {}, "outputs": [], @@ -658,7 +649,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "fff80eaa", "metadata": {}, "outputs": [], @@ -676,7 +667,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "id": "4a0eb2a9", "metadata": {}, "outputs": [], @@ -687,7 +678,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "d74ba1b6", "metadata": {}, "outputs": [], @@ -705,7 +696,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "dad3b3bb", "metadata": {}, "outputs": [], @@ -725,7 +716,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "id": "fbd90b0e", "metadata": {}, "outputs": [], @@ -743,7 +734,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "id": "5a101efb", "metadata": {}, "outputs": [], @@ -768,7 +759,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "id": "0a33e2d0", "metadata": {}, "outputs": [], @@ -788,7 +779,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 38, "id": "336a08ca", "metadata": {}, "outputs": [], @@ -807,7 +798,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "id": "991810bc", "metadata": { "tags": [] @@ -830,7 +821,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "id": "f25ebee1", "metadata": {}, "outputs": [], @@ -849,7 +840,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, "id": "7ad64412", "metadata": {}, "outputs": [], @@ -876,7 +867,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 42, "id": "b2863701", "metadata": {}, "outputs": [], @@ -886,7 +877,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 43, "id": "cc1afa29", "metadata": {}, "outputs": [], @@ -915,7 +906,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 44, "id": "ad3e6f81", "metadata": {}, "outputs": [], @@ -935,7 +926,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 45, "id": "9ce313ce", "metadata": {}, "outputs": [], @@ -953,7 +944,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 46, "id": "321d9c30", "metadata": {}, "outputs": [], @@ -973,7 +964,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 47, "id": "7eb73d5d", "metadata": {}, "outputs": [], @@ -998,7 +989,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 48, "id": "9529baa8", "metadata": {}, "outputs": [], @@ -1017,7 +1008,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 49, "id": "dbde77b8", "metadata": {}, "outputs": [], @@ -1036,7 +1027,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 50, "id": "dbb7d0b3", "metadata": {}, "outputs": [], @@ -1056,7 +1047,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 51, "id": "49e3fd8e", "metadata": {}, "outputs": [], @@ -1076,7 +1067,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 52, "id": "9e4f3e51", "metadata": {}, "outputs": [], @@ -1094,7 +1085,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 53, "id": "c1dcb46d", "metadata": {}, "outputs": [], @@ -1117,7 +1108,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 54, "id": "aed20c28", "metadata": {}, "outputs": [], @@ -1135,7 +1126,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 55, "id": "4d9e73b3", "metadata": {}, "outputs": [], @@ -1155,7 +1146,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 56, "id": "2077dfa9", "metadata": {}, "outputs": [], @@ -1180,7 +1171,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 57, "id": "b3d40516", "metadata": {}, "outputs": [], @@ -1202,7 +1193,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 58, "id": "8288c28f", "metadata": {}, "outputs": [], @@ -1221,7 +1212,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 59, "id": "bbbade35", "metadata": {}, "outputs": [], @@ -1242,7 +1233,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 60, "id": "a4c31795", "metadata": {}, "outputs": [], @@ -1261,7 +1252,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 61, "id": "f3d3619a", "metadata": {}, "outputs": [], @@ -1282,7 +1273,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 62, "id": "f078c8a6", "metadata": {}, "outputs": [], @@ -1301,7 +1292,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 63, "id": "54030d8f", "metadata": {}, "outputs": [], @@ -1335,7 +1326,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 64, "id": "ef158f81", "metadata": {}, "outputs": [], @@ -1356,7 +1347,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 65, "id": "d3607b8d", "metadata": {}, "outputs": [], @@ -1385,7 +1376,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 66, "id": "692d9cf8", "metadata": {}, "outputs": [], @@ -1416,7 +1407,7 @@ "Lists, dictionaries and tuples are **data structures**.\n", "In this chapter we are starting to see compound data structures, like lists of tuples, or dictionaries that contain tuples as keys and lists as values.\n", "Compound data structures are useful, but they are prone to errors caused when a data structure has the wrong type, size, or structure.\n", - "For example, if a function expects a list if integers and you give it a plain old integer\n", + "For example, if a function expects a list of integers and you give it a plain old integer\n", "(not in a list), it probably won't work.\n", "\n", "To help debug these kinds of errors, I wrote a module called `structshape` that provides a function, also called `structshape`, that takes any kind of data structure as an argument and returns a string that summarizes its structure.\n", @@ -1426,7 +1417,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 67, "id": "e9f03e91", "metadata": { "tags": [] @@ -1446,7 +1437,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 68, "id": "90ab624a", "metadata": {}, "outputs": [], @@ -1464,7 +1455,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 69, "id": "6794330f", "metadata": {}, "outputs": [], @@ -1483,7 +1474,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 70, "id": "54cd185b", "metadata": {}, "outputs": [], @@ -1503,7 +1494,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 71, "id": "04028afd", "metadata": {}, "outputs": [], @@ -1522,7 +1513,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 72, "id": "b5d45c88", "metadata": {}, "outputs": [], @@ -1542,7 +1533,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 73, "id": "15131907", "metadata": {}, "outputs": [], @@ -1596,7 +1587,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 74, "id": "c65d68d2", "metadata": { "tags": [] @@ -1642,7 +1633,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 75, "id": "4416fe4a", "metadata": {}, "outputs": [], @@ -1664,7 +1655,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 76, "id": "e6eda0e4", "metadata": { "tags": [] @@ -1684,7 +1675,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 77, "id": "4fae1acc", "metadata": { "tags": [] @@ -1716,7 +1707,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 78, "id": "855c7ed2", "metadata": {}, "outputs": [], @@ -1736,7 +1727,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 79, "id": "3c921f68", "metadata": {}, "outputs": [], @@ -1755,7 +1746,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 80, "id": "b029b0da", "metadata": {}, "outputs": [], @@ -1794,7 +1785,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 81, "id": "1cc07036", "metadata": { "tags": [] @@ -1814,7 +1805,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 82, "id": "96560a0e", "metadata": {}, "outputs": [], @@ -1824,7 +1815,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 83, "id": "c026c6d1", "metadata": { "tags": [] @@ -1836,7 +1827,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 84, "id": "5814999d", "metadata": { "tags": [] @@ -1858,7 +1849,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 85, "id": "9464d140", "metadata": { "tags": [] @@ -1897,7 +1888,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 86, "id": "4309d0b5", "metadata": { "tags": [] @@ -1910,7 +1901,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 87, "id": "52228828", "metadata": {}, "outputs": [], @@ -1930,7 +1921,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 88, "id": "3bf2aa0d", "metadata": { "tags": [] @@ -1952,7 +1943,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 89, "id": "e4fbf5d9", "metadata": { "tags": [] @@ -1964,7 +1955,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 90, "id": "817ec689", "metadata": { "tags": [] @@ -2019,7 +2010,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 91, "id": "941719c1", "metadata": { "tags": [] @@ -2031,7 +2022,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 92, "id": "d2ec641b", "metadata": { "tags": [] @@ -2053,7 +2044,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 93, "id": "7ae29f73", "metadata": { "tags": [] @@ -2066,7 +2057,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 94, "id": "013819a5", "metadata": {}, "outputs": [], @@ -2087,7 +2078,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 95, "id": "fbf9ede3", "metadata": { "tags": [] @@ -2111,7 +2102,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 96, "id": "55435050", "metadata": { "tags": [] @@ -2135,7 +2126,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 97, "id": "6a9320c2", "metadata": { "tags": [] @@ -2176,7 +2167,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 98, "id": "3d5a75f8", "metadata": { "tags": [] @@ -2198,7 +2189,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 99, "id": "a9816dde", "metadata": {}, "outputs": [], @@ -2208,7 +2199,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 100, "id": "753a23c1", "metadata": { "tags": [] @@ -2241,7 +2232,7 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 101, "id": "57649075", "metadata": {}, "outputs": [], @@ -2303,7 +2294,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 102, "id": "c19bf833", "metadata": {}, "outputs": [], @@ -2313,7 +2304,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 103, "id": "2d9764d6", "metadata": {}, "outputs": [], @@ -2323,7 +2314,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 104, "id": "5e4f5d8e", "metadata": {}, "outputs": [], @@ -2333,7 +2324,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 105, "id": "27d311dd", "metadata": {}, "outputs": [], @@ -2343,7 +2334,7 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 106, "id": "68c27c7e", "metadata": {}, "outputs": [], @@ -2352,12 +2343,20 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "a34c2014", - "metadata": {}, - "outputs": [], - "source": [] + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -2377,7 +2376,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap12.ipynb b/chapters/chap12.ipynb index 071a104..f2d5d23 100644 --- a/chapters/chap12.ipynb +++ b/chapters/chap12.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -51,7 +43,7 @@ "source": [ "# Text Analysis and Generation\n", "\n", - "At this point we have covered Python's core data structures -- lists, dictionaries, and tuples -- and and some algorithms that use them.\n", + "At this point we have covered Python's core data structures -- lists, dictionaries, and tuples -- and some algorithms that use them.\n", "In this chapter, we'll use them to explore text analysis and Markov generation:\n", "\n", "* Text analysis is a way to describe the statistical relationships between the words in a document, like the probability that one word is followed by another, and\n", @@ -88,7 +80,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "4cd1c980", "metadata": { "tags": [] @@ -111,7 +103,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "52ebfe94", "metadata": { "tags": [] @@ -124,7 +116,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "49cfc352", "metadata": { "tags": [] @@ -150,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "44e53ce6", "metadata": {}, "outputs": [], @@ -160,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "50d1fafa", "metadata": { "tags": [] @@ -181,7 +173,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "id": "16d24028", "metadata": {}, "outputs": [], @@ -209,7 +201,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 8, "id": "1668e6bd", "metadata": {}, "outputs": [], @@ -249,7 +241,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "id": "ed5f0a43", "metadata": {}, "outputs": [], @@ -269,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "a9df2aeb", "metadata": {}, "outputs": [], @@ -291,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "b138b123", "metadata": {}, "outputs": [], @@ -313,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "id": "fe65df44", "metadata": {}, "outputs": [], @@ -332,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "id": "b47a87cf", "metadata": {}, "outputs": [], @@ -355,7 +347,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "id": "348949be", "metadata": {}, "outputs": [], @@ -374,7 +366,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "id": "06121901", "metadata": {}, "outputs": [], @@ -393,7 +385,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "id": "881ed9f8", "metadata": {}, "outputs": [], @@ -411,7 +403,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 17, "id": "ab5d2fed", "metadata": {}, "outputs": [], @@ -429,7 +421,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "id": "2fdfb936", "metadata": {}, "outputs": [], @@ -454,7 +446,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 19, "id": "3104d191", "metadata": {}, "outputs": [], @@ -482,7 +474,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 20, "id": "4fba7d1c", "metadata": {}, "outputs": [], @@ -510,7 +502,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 21, "id": "4be34c95", "metadata": {}, "outputs": [], @@ -528,12 +520,12 @@ "\n", "* `key=second_element` means the items will be sorted according to the frequencies of the words.\n", "\n", - "* `reverse=True` means they items will be sorted in reverse order, with the most frequent words first." + "* `reverse=True` means the items will be sorted in reverse order, with the most frequent words first." ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 22, "id": "8efe7c4c", "metadata": {}, "outputs": [], @@ -551,7 +543,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 23, "id": "79c17341", "metadata": {}, "outputs": [], @@ -582,7 +574,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 24, "id": "838bcb4f", "metadata": {}, "outputs": [], @@ -601,7 +593,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 25, "id": "90c45e7e", "metadata": {}, "outputs": [], @@ -625,7 +617,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 26, "id": "e106be95", "metadata": {}, "outputs": [], @@ -643,7 +635,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 27, "id": "8101a510", "metadata": {}, "outputs": [], @@ -663,7 +655,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 28, "id": "c046117b", "metadata": { "tags": [] @@ -705,7 +697,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 29, "id": "edd8ff1c", "metadata": { "tags": [] @@ -725,7 +717,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 30, "id": "67ef3e08", "metadata": {}, "outputs": [], @@ -738,12 +730,12 @@ "id": "22becbab", "metadata": {}, "source": [ - "The we'll store the words as keys in a dictionary so we can use the `in` operator to check quickly whether a word is valid." + "Then we'll store the words as keys in a dictionary so we can use the `in` operator to check quickly whether a word is valid." ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 31, "id": "471d58e9", "metadata": {}, "outputs": [], @@ -763,7 +755,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 32, "id": "4d4c3538", "metadata": {}, "outputs": [], @@ -786,7 +778,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 33, "id": "8b42e014", "metadata": {}, "outputs": [], @@ -804,7 +796,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 34, "id": "f48be152", "metadata": {}, "outputs": [], @@ -825,7 +817,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 35, "id": "5716f967", "metadata": {}, "outputs": [], @@ -846,7 +838,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 36, "id": "b37219f5", "metadata": {}, "outputs": [], @@ -888,7 +880,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 37, "id": "75b548a9", "metadata": {}, "outputs": [], @@ -898,7 +890,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 38, "id": "2bfa31ae", "metadata": { "tags": [] @@ -921,7 +913,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 39, "id": "6f5d5c1c", "metadata": {}, "outputs": [], @@ -940,7 +932,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 40, "id": "1445068b", "metadata": {}, "outputs": [], @@ -960,7 +952,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 41, "id": "4fc47ecd", "metadata": { "tags": [] @@ -982,7 +974,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 42, "id": "91ae9d4c", "metadata": {}, "outputs": [], @@ -1001,7 +993,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 43, "id": "8bf595c1", "metadata": {}, "outputs": [], @@ -1024,7 +1016,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 44, "id": "22953b65", "metadata": {}, "outputs": [], @@ -1042,7 +1034,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 45, "id": "1c7cdf4d", "metadata": {}, "outputs": [], @@ -1060,7 +1052,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 46, "id": "a7a3aa42", "metadata": {}, "outputs": [], @@ -1079,7 +1071,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 47, "id": "c4286fb3", "metadata": {}, "outputs": [], @@ -1119,7 +1111,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 48, "id": "d8ee02f6", "metadata": {}, "outputs": [], @@ -1139,7 +1131,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 49, "id": "bfdb1de1", "metadata": {}, "outputs": [], @@ -1166,7 +1158,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 50, "id": "2e73df79", "metadata": {}, "outputs": [], @@ -1184,7 +1176,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 51, "id": "495ad429", "metadata": {}, "outputs": [], @@ -1214,7 +1206,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 52, "id": "c1224061", "metadata": {}, "outputs": [], @@ -1236,7 +1228,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 53, "id": "4296485a", "metadata": {}, "outputs": [], @@ -1255,7 +1247,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 54, "id": "e03fd803", "metadata": { "tags": [] @@ -1267,7 +1259,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 55, "id": "f6ee1840", "metadata": {}, "outputs": [], @@ -1290,7 +1282,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 56, "id": "d6c65d79", "metadata": {}, "outputs": [], @@ -1320,7 +1312,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 57, "id": "3171d592", "metadata": {}, "outputs": [], @@ -1343,7 +1335,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 58, "id": "3321e6a4", "metadata": {}, "outputs": [], @@ -1361,7 +1353,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 59, "id": "e4e55c71", "metadata": {}, "outputs": [], @@ -1380,7 +1372,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 60, "id": "f25dcb5e", "metadata": {}, "outputs": [], @@ -1399,7 +1391,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 61, "id": "990354a0", "metadata": {}, "outputs": [], @@ -1421,7 +1413,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 62, "id": "b9371452", "metadata": {}, "outputs": [], @@ -1449,7 +1441,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 63, "id": "8c3f45c2", "metadata": {}, "outputs": [], @@ -1472,7 +1464,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 64, "id": "641990a3", "metadata": {}, "outputs": [], @@ -1495,7 +1487,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 65, "id": "9322a49a", "metadata": {}, "outputs": [], @@ -1517,7 +1509,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 66, "id": "45a60c52", "metadata": {}, "outputs": [], @@ -1541,7 +1533,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 67, "id": "3e86102c", "metadata": { "tags": [] @@ -1561,7 +1553,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 68, "id": "e49d52f7", "metadata": {}, "outputs": [], @@ -1598,7 +1590,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 69, "id": "15108884", "metadata": {}, "outputs": [], @@ -1610,7 +1602,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 70, "id": "747a41be", "metadata": { "tags": [] @@ -1634,7 +1626,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 71, "id": "5a4682dc", "metadata": {}, "outputs": [], @@ -1655,7 +1647,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 72, "id": "36ee0f76", "metadata": {}, "outputs": [], @@ -1790,7 +1782,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 73, "id": "05752b6d", "metadata": { "tags": [] @@ -1815,7 +1807,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 74, "id": "a4365ac0", "metadata": {}, "outputs": [], @@ -1835,7 +1827,7 @@ "metadata": {}, "source": [ "Dictionaries provide a method called `setdefault` that we can use to do the same thing more concisely.\n", - "Ask a virtual assistant how it works, or copy `add_word` into a virtual assistant and ask \"Can you rewrite this using `setdefault`?\"\n", + "Ask a virtual assistant how it works, or copy `add_bigram` into a virtual assistant and ask \"Can you rewrite this using `setdefault`?\"\n", "\n", "In this chapter we implemented Markov chain text analysis and generation.\n", "If you are curious, you can ask a virtual assistant for more information on the topic.\n", @@ -1858,7 +1850,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 75, "id": "f38a61ff", "metadata": {}, "outputs": [], @@ -1868,7 +1860,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 76, "id": "d047e546", "metadata": {}, "outputs": [], @@ -1888,7 +1880,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 77, "id": "6b8932ee", "metadata": { "tags": [] @@ -1916,7 +1908,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 78, "id": "44c3f0d8", "metadata": { "tags": [] @@ -1940,7 +1932,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 79, "id": "3fcf85f4", "metadata": {}, "outputs": [], @@ -1958,7 +1950,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 80, "id": "d9e554e3", "metadata": {}, "outputs": [], @@ -1983,7 +1975,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 81, "id": "8c2ee21c", "metadata": { "tags": [] @@ -2011,7 +2003,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 82, "id": "b13384e3", "metadata": { "tags": [] @@ -2031,7 +2023,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 83, "id": "62c2177f", "metadata": {}, "outputs": [], @@ -2065,7 +2057,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 84, "id": "64e11f26", "metadata": { "tags": [] @@ -2089,7 +2081,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 85, "id": "fe2d93fa", "metadata": {}, "outputs": [], @@ -2104,7 +2096,7 @@ "id": "83ed6c7e", "metadata": {}, "source": [ - "Now write a loop that generates `50` more words following these steps:\n", + "Now write a loop that generates 50 more words following these steps:\n", "\n", "1. In `successor_map`, look up the list of words that can follow `bigram`.\n", "\n", @@ -2117,7 +2109,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 86, "id": "22210a5c", "metadata": {}, "outputs": [], @@ -2142,6 +2134,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -2161,7 +2169,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap13.ipynb b/chapters/chap13.ipynb index 9e76060..ba90420 100644 --- a/chapters/chap13.ipynb +++ b/chapters/chap13.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -351,7 +343,7 @@ "id": "f652aaac", "metadata": {}, "source": [ - "To write this data to a file, you can use the `write` method, which we saw in [Chapter 8](section_writing_files).\n", + "To write this data to a file, you can use the `write` method, which we saw in Chapter 8.\n", "The argument of `write` has to be a string, so if we want to put other values in a file, we have to convert them to strings.\n", "The easiest way to do that is with the built-in function `str`.\n", "\n", @@ -809,7 +801,7 @@ "id": "e9b252a7", "metadata": {}, "source": [ - "If you make another assignment to an existing key, `dbm` replaces the old value." + "If you make another assignment to an existing key, `shelve` replaces the old value." ] }, { @@ -828,7 +820,7 @@ "id": "003eacbc", "metadata": {}, "source": [ - "Some dictionary methods, like `keys`, `values` and `items`, also work with database objects." + "Some dictionary methods, like `keys`, `values` and `items`, also work with shelf objects." ] }, { @@ -1268,7 +1260,7 @@ "id": "a8e480f0", "metadata": {}, "source": [ - "The `HASH` object provides an `update` function that takes the contents of the file as an argument." + "The `HASH` object provides an `update` method that takes the contents of the file as an argument." ] }, { @@ -1406,53 +1398,7 @@ "id": "897c66bf", "metadata": {}, "source": [ - "The order of the results depends on details of the operating system.\n", - "\n", - "Here is a more general version of `walk` that takes as a second parameter a function object.\n", - "Instead of printing the path of the files it discovers, it calls this function and passes the path as a parameter." - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "id": "49e52d49", - "metadata": {}, - "outputs": [], - "source": [ - "def walk(dirname, visit_func):\n", - " for name in os.listdir(dirname):\n", - " path = os.path.join(dirname, name)\n", - "\n", - " if os.path.isfile(path):\n", - " visit_func(path)\n", - " else:\n", - " walk(path, visit_func)" - ] - }, - { - "cell_type": "markdown", - "id": "7db28cf6", - "metadata": {}, - "source": [ - "Here's an example where we pass `print` as an argument, so when `walk` calls `visit_func`, it prints the paths of the files it discovers." - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "id": "6a86db4e", - "metadata": {}, - "outputs": [], - "source": [ - "walk('photos', print)" - ] - }, - { - "cell_type": "markdown", - "id": "1c6faa6d", - "metadata": {}, - "source": [ - "The parameter is called `visit_func` because it suggests that as we \"walk\" around the directory, we \"visit\" each file." + "The order of the results depends on details of the operating system." ] }, { @@ -1462,14 +1408,15 @@ "source": [ "## Debugging\n", "\n", - "When you are reading and writing files, you might run into problems with\n", - "whitespace. These errors can be hard to debug because spaces, tabs and\n", - "newlines are normally invisible:" + "When you are reading and writing files, you might run into problems with whitespace.\n", + "These errors can be hard to debug because whitespace characters are normally invisible.\n", + "For example, here's a string that contains spaces, a tab represented by the sequence `\\t`, and a newline represented by the sequence `\\n`.\n", + "When we print it, we don't see the whitespace characters." ] }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 65, "id": "15d7425a", "metadata": {}, "outputs": [], @@ -1483,14 +1430,13 @@ "id": "49bbebe6", "metadata": {}, "source": [ - "The built-in function `repr` can help. It takes any object as an\n", - "argument and returns a string representation of the object. For strings,\n", - "it represents whitespace characters with backslash sequences:" + "The built-in function `repr` can help. It takes any object as an argument and returns a string representation of the object.\n", + "For strings, it represents whitespace characters with backslash sequences." ] }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 66, "id": "61feff85", "metadata": {}, "outputs": [], @@ -1561,7 +1507,7 @@ "A database whose contents are organized like a dictionary with keys that correspond to values.\n", "\n", "**binary mode:**\n", - "A way of writing a file so the contents are interpreted as sequence of bytes rather than a sequence of characters.\n", + "A way of opening a file so the contents are interpreted as sequence of bytes rather than a sequence of characters.\n", "\n", "**hash function:**\n", "A function that takes and object and computes an integer, which is sometimes called a digest.\n", @@ -1580,7 +1526,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 67, "id": "bd885ba1", "metadata": { "tags": [] @@ -1641,26 +1587,31 @@ { "cell_type": "markdown", "id": "85844afb", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "Here's an outline of the function to get you started." ] }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 68, "id": "1e598e70", "metadata": {}, "outputs": [], "source": [ - "# Solution goes here" + "def replace_all(old, new, source_path, dest_path):\n", + " # read the contents of the source file\n", + " reader = open(source_path)\n", + "\n", + " # replace the old string with the new\n", + " \n", + " # write the result into the destination file\n", + " " ] }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 69, "id": "d3774d1a", "metadata": {}, "outputs": [], @@ -1678,7 +1629,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 70, "id": "3b80dfd8", "metadata": { "tags": [] @@ -1691,7 +1642,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 71, "id": "4b67579f", "metadata": { "tags": [] @@ -1706,7 +1657,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 72, "id": "1d990225", "metadata": { "tags": [] @@ -1731,7 +1682,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 73, "id": "1c2fff95", "metadata": {}, "outputs": [], @@ -1751,7 +1702,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 74, "id": "8008cde6", "metadata": { "tags": [] @@ -1763,7 +1714,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 75, "id": "a6b5e51a", "metadata": { "tags": [] @@ -1789,7 +1740,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 76, "id": "ffefe13e", "metadata": { "tags": [] @@ -1801,7 +1752,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 77, "id": "7eb54fbc", "metadata": { "tags": [] @@ -1815,7 +1766,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 78, "id": "ac784df7", "metadata": { "tags": [] @@ -1861,7 +1812,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 79, "id": "03b6acf9", "metadata": {}, "outputs": [], @@ -1881,7 +1832,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 80, "id": "21c33092", "metadata": { "tags": [] @@ -1906,7 +1857,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 81, "id": "31c056a9", "metadata": {}, "outputs": [], @@ -1919,12 +1870,12 @@ "id": "08223a21", "metadata": {}, "source": [ - "3. Write a function called `process_path` that takes a path, uses `is_image` to check whether it's an image file, and uses `add_path` to add it to the shelf." + "3. Write a version of `walk` called `walk_images` that takes a directory and walks through the files in the directory and its subdirectories. For each file, it should use `is_image` to check whether it's an image file and `add_path` to add it to the shelf." ] }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 87, "id": "e54d6993", "metadata": {}, "outputs": [], @@ -1942,7 +1893,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 88, "id": "c31ba5b6", "metadata": { "tags": [] @@ -1950,7 +1901,7 @@ "outputs": [], "source": [ "db = shelve.open('photos/digests', 'n')\n", - "walk('photos', process_path)\n", + "walk_images('photos')\n", "\n", "for digest, paths in db.items():\n", " if len(paths) > 1:\n", @@ -1968,7 +1919,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 89, "id": "d7c7e679", "metadata": {}, "outputs": [], @@ -1983,6 +1934,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -2002,7 +1969,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap14.ipynb b/chapters/chap14.ipynb index 603c237..f0f9998 100644 --- a/chapters/chap14.ipynb +++ b/chapters/chap14.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -301,7 +293,7 @@ "id": "bcbea13a", "metadata": {}, "source": [ - "We'll use this f-string to write a function that displays the value of time objects.\n", + "We'll use this f-string to write a function that displays the value of a `Time`object.\n", "You can pass an object as an argument in the usual way.\n", "For example, the following function takes a `Time` object as an argument. " ] @@ -367,7 +359,7 @@ "metadata": {}, "source": [ "It might be surprising that the parameters have the same names as the attributes, but that's a common way to write a function like this.\n", - "Here's how we use `make_time` to create a `Time` object.`" + "Here's how we use `make_time` to create a `Time` object." ] }, { @@ -725,12 +717,11 @@ "source": [ "`add_time` is a **pure function** because it does not modify any of the objects passed to it as arguments and its only effect is to return a value.\n", "\n", - "Anything that can be done with modifiers can also be done with pure functions.\n", + "Anything that can be done with impure functions can also be done with pure functions.\n", "In fact, some programming languages only allow pure functions.\n", - "Programs that use pure functions might be less error-prone than programs that use modifiers.\n", - "But modifiers are sometimes convenient and can be more efficient.\n", + "Programs that use pure functions might be less error-prone, but impure functions are sometimes convenient and can be more efficient.\n", "\n", - "In general, I suggest you write pure functions whenever it is reasonable and resort to modifiers only if there is a compelling advantage.\n", + "In general, I suggest you write pure functions whenever it is reasonable and resort to impure functions only if there is a compelling advantage.\n", "This approach might be called a **functional programming style**." ] }, @@ -767,7 +758,7 @@ "The result is not a valid time.\n", "The problem is that `increment_time` does not deal with cases where the number of seconds or minutes adds up to more than `60`.\n", "\n", - "Here's an improved version that checks whether `second` exceeds `60` -- if so, it increments `minute` -- then checks whether `minute` exceeds `60` -- if so, it increments `hour`." + "Here's an improved version that checks whether `second` exceeds or equals `60` -- if so, it increments `minute` -- then checks whether `minute` exceeds or equals `60` -- if so, it increments `hour`." ] }, { @@ -839,7 +830,7 @@ "source": [ "The result is not a valid time.\n", "So let's try a different approach, using the `divmod` function.\n", - "We'll make a copy of `start` and modify it by incrementing the `minutes` field." + "We'll make a copy of `start` and modify it by incrementing the `minute` attribute." ] }, { @@ -916,7 +907,7 @@ " \n", " carry, time.second = divmod(time.second, 60)\n", " carry, time.minute = divmod(time.minute + carry, 60)\n", - " carry, time.hour = divmod(time.hour + carry, 60)" + " carry, time.hour = divmod(time.hour + carry, 24)" ] }, { @@ -990,7 +981,7 @@ "metadata": {}, "source": [ "The result is the number of seconds since the beginning of the day.\n", - "For example, `01:01:01` is `1` hour, `1` minute and `1` second from the beginning of the day, with is the sum of `3600` seconds, `60` seconds, and `1` second." + "For example, `01:01:01` is `1` hour, `1` minute and `1` second from the beginning of the day, which is the sum of `3600` seconds, `60` seconds, and `1` second." ] }, { @@ -1303,7 +1294,7 @@ "\n", "* \"What is the difference between a variable and an attribute?\"\n", "\n", - "* \"What are the pros and cons of pure functions compared to modifiers?\"\n", + "* \"What are the pros and cons of pure functions compared to impure functions?\"\n", "\n", "Because we are just getting started with object oriented programming, the code in this chapter is not idiomatic -- it is not the kind of code experienced programmers write.\n", "If you ask a virtual assistant for help with the exercises, you will probably see features we have not covered yet.\n", @@ -1316,14 +1307,6 @@ "Also, in this chapter we saw one example of a format specifier. For more information, ask \"What format specifiers can be used in a Python f-string?\"" ] }, - { - "cell_type": "markdown", - "id": "c85eab62", - "metadata": {}, - "source": [ - "## Exercises\n" - ] - }, { "cell_type": "markdown", "id": "bcdab7d6", @@ -1714,6 +1697,22 @@ "id": "d6f1cc2f", "metadata": {}, "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1733,7 +1732,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap15.ipynb b/chapters/chap15.ipynb index 1d7a9e3..f7dfa60 100644 --- a/chapters/chap15.ipynb +++ b/chapters/chap15.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -122,7 +114,7 @@ "id": "8da4079c", "metadata": {}, "source": [ - "To call this function, you have to pass a `Time` object as an argument.\n", + "To call this method, you have to pass a `Time` object as an argument.\n", "Here's the function we'll use to make a `Time` object." ] }, @@ -363,7 +355,7 @@ "id": "d2f4fd5a", "metadata": {}, "source": [ - "The result is a new object that represents `9:40`." + "The result is a new object that represents 9:40." ] }, { @@ -381,7 +373,7 @@ "id": "e6a18c76", "metadata": {}, "source": [ - "Now that we have `Time.from_seconds`, we can use it to write `add_time` as a method.\n", + "Now that we have `Time.int_to_time`, we can use it to write `add_time` as a method.\n", "Here's the function from the previous chapter." ] }, @@ -417,7 +409,7 @@ "\n", " def add_time(self, hours, minutes, seconds):\n", " duration = make_time(hours, minutes, seconds)\n", - " seconds = time_to_int(self) + time_to_int(duration)\n", + " seconds = Time.time_to_int(self) + Time.time_to_int(duration)\n", " return Time.int_to_time(seconds)" ] }, @@ -439,7 +431,7 @@ "outputs": [], "source": [ "end = start.add_time(1, 32, 0)\n", - "print_time(end)" + "end.print_time()" ] }, { @@ -612,7 +604,7 @@ "id": "e01e9673", "metadata": {}, "source": [ - "## The __init__ method\n", + "## The init method\n", "\n", "The most special of the special methods is `__init__`, so-called because it initializes the attributes of a new object.\n", "An `__init__` method for the `Time` class might look like this:" @@ -844,7 +836,7 @@ "\n", " def is_after(self, other):\n", " assert self.is_valid(), 'self is not a valid Time'\n", - " assert other.is_valid(), 'self is not a valid Time'\n", + " assert other.is_valid(), 'other is not a valid Time'\n", " return self.time_to_int() > other.time_to_int()" ] }, @@ -898,7 +890,7 @@ "## Glossary\n", "\n", "**object-oriented language:**\n", - "A language that provides features to support object-oriented programming, notably user-defined types and inheritance.\n", + "A language that provides features to support object-oriented programming, notably user-defined types.\n", "\n", "**method:**\n", "A function that is defined inside a class definition and is invoked on instances of that class.\n", @@ -932,7 +924,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "id": "3115ea33", "metadata": { "tags": [] @@ -968,7 +960,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "id": "133d7679", "metadata": {}, "outputs": [], @@ -991,16 +983,16 @@ "\n", "2. Write an `__init__` method that takes `year`, `month`, and `day` as parameters and assigns the parameters to attributes. Create an object that represents June 22, 1933.\n", "\n", - "2. Write `__str__` method that uses a format string to format the attributes and returns the result. If you test it with the `Date` you created, the result should be `1933-06-22`.\n", + "3. Write `__str__` method that uses an f-string to format the attributes and returns the result. If you test it with the `Date` you created, the result should be `1933-06-22`.\n", "\n", - "3. Write a method called `is_after` that takes two `Date` objects and returns `True` if the first comes after the second. Create a second object that represents September 17, 1933, and check whether it comes after the first object.\n", + "4. Write a method called `is_after` that takes two `Date` objects and returns `True` if the first comes after the second. Create a second object that represents September 17, 1933, and check whether it comes after the first object.\n", "\n", "Hint: You might find it useful write a method called `to_tuple` that returns a tuple that contains the attributes of a `Date` object in year-month-day order." ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 38, "id": "3c9f3777-4869-481e-9f4e-4223d6028913", "metadata": {}, "outputs": [], @@ -1020,7 +1012,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "id": "fd4b2521-aa71-45da-97eb-ce62ce2714ad", "metadata": { "tags": [] @@ -1033,7 +1025,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "id": "ee3f1294-cad1-406b-a574-045ad2b84294", "metadata": { "tags": [] @@ -1046,7 +1038,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, "id": "ac093f7b-83cf-4488-8842-5c71bcfa35ec", "metadata": { "tags": [] @@ -1058,7 +1050,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 42, "id": "7e7cb5e1-631f-4b1e-874f-eb16d4792625", "metadata": { "tags": [] @@ -1075,6 +1067,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1094,7 +1102,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap16.ipynb b/chapters/chap16.ipynb index d1f0ddb..eb84e1c 100644 --- a/chapters/chap16.ipynb +++ b/chapters/chap16.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -242,7 +234,7 @@ "id": "a7a635ee", "metadata": {}, "source": [ - "In the same way that the built in function `sort` modifies a list, and the `sorted` function creates a new list, now we have a `translate` method that modifies a `Point` and a `translated` method that creates a new one.\n", + "In the same way that the `sort` method modifies a list and the `sorted` function creates a new list, now we have a `translate` method that modifies a `Point` and a `translated` method that creates a new one.\n", "\n", "Here's an example:" ] @@ -568,8 +560,8 @@ "id": "c008d3dd", "metadata": {}, "source": [ - "It's not possible to override the `is` operator -- it always checks whether the objects are **identical**.\n", - "But for programmer-defined types, you can override the `==` operator so it checks whether the objects are **equivalent**.\n", + "It's not possible to override the `is` operator -- it always checks whether the objects are identical.\n", + "But for programmer-defined types, you can override the `==` operator so it checks whether the objects are equivalent.\n", "And you can define what equivalent means." ] }, @@ -1194,7 +1186,7 @@ "\n", "To avoid sharing objects, you can use deep copy, as we did in this chapter.\n", "\n", - "To avoid modifying objects, consider replacing modifiers like `translate` with pure functions like `translated`.\n", + "To avoid modifying objects, consider replacing impure functions like `translate` with pure functions like `translated`.\n", "For example, here's a version of `translated` that creates a new `Point` and never modifies its attributes." ] }, @@ -1220,7 +1212,7 @@ "They are beyond the scope of this book, but if you are curious, ask a virtual assistant, \"How do I make a Python object immutable?\"\n", "\n", "Creating a new object takes more time than modifying an existing one, but the difference seldom matters in practice.\n", - "Programs that avoid shared objects and modifiers are often easier to develop, test, and debug -- and the best kind of debugging is the kind you don't have to do." + "Programs that avoid shared objects and impure functions are often easier to develop, test, and debug -- and the best kind of debugging is the kind you don't have to do." ] }, { @@ -1230,12 +1222,6 @@ "source": [ "## Glossary\n", "\n", - "**identical:**\n", - "Being the same object (which implies equivalence).\n", - "\n", - "**equivalent:**\n", - "Having the same value.\n", - "\n", "**shallow copy:**\n", "A copy operation that does not copy nested objects.\n", "\n", @@ -1293,9 +1279,7 @@ { "cell_type": "markdown", "id": "2e488e0f", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use the following outline to get started." ] @@ -1304,9 +1288,7 @@ "cell_type": "code", "execution_count": 49, "id": "92c07380", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "%%add_method_to Line\n", @@ -1324,22 +1306,13 @@ }, "outputs": [], "source": [ - "%%add_method_to Line\n", - "\n", - "def __eq__(self, other):\n", - " if (self.p1 == other.p1) and (self.p2 == other.p2):\n", - " return True\n", - " if (self.p1 == other.p2) and (self.p2 == other.p1):\n", - " return True\n", - " return False" + "# Solution goes here" ] }, { "cell_type": "markdown", "id": "3a44e45a", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use these examples to test your code." ] @@ -1348,9 +1321,7 @@ "cell_type": "code", "execution_count": 51, "id": "aa086dd1", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "start1 = Point(0, 0)\n", @@ -1361,9 +1332,7 @@ { "cell_type": "markdown", "id": "e825f049", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "This example should be `True` because the `Line` objects refer to `Point` objects that are equivalent, in the same order." ] @@ -1372,9 +1341,7 @@ "cell_type": "code", "execution_count": 52, "id": "857cba26", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "line_a = Line(start1, end)\n", @@ -1396,9 +1363,7 @@ "cell_type": "code", "execution_count": 53, "id": "b45def0a", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "line_c = Line(end, start1)\n", @@ -1408,9 +1373,7 @@ { "cell_type": "markdown", "id": "8c9c787b", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "Equivalence should always be transitive -- that is, if `line_a` and `line_b` are equivalent, and `line_a` and `line_c` are equivalent, then `line_b` and `line_c` should also be equivalent." ] @@ -1419,9 +1382,7 @@ "cell_type": "code", "execution_count": 54, "id": "9784300c", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "line_b == line_c # should be True" @@ -1430,9 +1391,7 @@ { "cell_type": "markdown", "id": "d4f385fa", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "This example should be `False` because the `Line` objects refer to `Point` objects that are not equivalent." ] @@ -1441,9 +1400,7 @@ "cell_type": "code", "execution_count": 55, "id": "5435c8e4", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "line_d = Line(start1, start2)\n", @@ -1463,9 +1420,7 @@ { "cell_type": "markdown", "id": "b8c52d19", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use the following outline to get started." ] @@ -1474,9 +1429,7 @@ "cell_type": "code", "execution_count": 56, "id": "f377afbb", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "%%add_method_to Line\n", @@ -1494,20 +1447,13 @@ }, "outputs": [], "source": [ - "%%add_method_to Line\n", - "\n", - " def midpoint(self):\n", - " mid_x = (self.p1.x + self.p2.x) / 2\n", - " mid_y = (self.p1.y + self.p2.y) / 2\n", - " return Point(mid_x, mid_y)" + "# Solution goes here" ] }, { "cell_type": "markdown", "id": "4df69a9f", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use the following examples to test your code and draw the result." ] @@ -1516,9 +1462,7 @@ "cell_type": "code", "execution_count": 58, "id": "0d603aa3", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "start = Point(0, 0)\n", @@ -1532,9 +1476,7 @@ "cell_type": "code", "execution_count": 59, "id": "647d0982", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "mid1 = line1.midpoint()\n", @@ -1545,9 +1487,7 @@ "cell_type": "code", "execution_count": 60, "id": "e351bea3", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "mid2 = line2.midpoint()\n", @@ -1558,9 +1498,7 @@ "cell_type": "code", "execution_count": 61, "id": "5ad5a076", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "line3 = Line(mid1, mid2)" @@ -1570,9 +1508,7 @@ "cell_type": "code", "execution_count": 62, "id": "8effaff0", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", @@ -1594,9 +1530,7 @@ { "cell_type": "markdown", "id": "c586a3ed", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use the following outline to get started." ] @@ -1605,9 +1539,7 @@ "cell_type": "code", "execution_count": 63, "id": "d94a6350", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "%%add_method_to Rectangle\n", @@ -1625,20 +1557,13 @@ }, "outputs": [], "source": [ - "%%add_method_to Rectangle\n", - "\n", - " def midpoint(self):\n", - " mid_x = self.corner.x + self.width / 2\n", - " mid_y = self.corner.y + self.height / 2\n", - " return Point(mid_x, mid_y)" + "# Solution goes here" ] }, { "cell_type": "markdown", "id": "d186c84b", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use the following example to test your code." ] @@ -1647,9 +1572,7 @@ "cell_type": "code", "execution_count": 65, "id": "4aec759c", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "corner = Point(30, 20)\n", @@ -1660,9 +1583,7 @@ "cell_type": "code", "execution_count": 66, "id": "7ec3339d", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "mid = rectangle.midpoint()\n", @@ -1673,9 +1594,7 @@ "cell_type": "code", "execution_count": 67, "id": "326dbf24", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "diagonal = Line(corner, mid)" @@ -1685,9 +1604,7 @@ "cell_type": "code", "execution_count": 68, "id": "4da710d4", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", @@ -1715,9 +1632,7 @@ { "cell_type": "markdown", "id": "29e994c6", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use this outline to get started." ] @@ -1726,9 +1641,7 @@ "cell_type": "code", "execution_count": 69, "id": "30cc0726", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "%%add_method_to Rectangle\n", @@ -1746,24 +1659,13 @@ }, "outputs": [], "source": [ - "%%add_method_to Rectangle\n", - "\n", - " def make_cross(self):\n", - " midpoints = []\n", - "\n", - " for line in self.make_lines():\n", - " midpoints.append(line.midpoint())\n", - "\n", - " p1, p2, p3, p4 = midpoints\n", - " return Line(p1, p3), Line(p2, p4)" + "# Solution goes here" ] }, { "cell_type": "markdown", "id": "970fcbca", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use the following example to test your code." ] @@ -1772,9 +1674,7 @@ "cell_type": "code", "execution_count": 71, "id": "2afd718c", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "corner = Point(30, 20)\n", @@ -1785,9 +1685,7 @@ "cell_type": "code", "execution_count": 72, "id": "b7bdb467", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "lines = rectangle.make_cross()" @@ -1797,9 +1695,7 @@ "cell_type": "code", "execution_count": 73, "id": "9d09b2c3", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", @@ -1823,9 +1719,7 @@ { "cell_type": "markdown", "id": "cb1b24a3", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use the following function, which is a version of the `circle` function we wrote in Chapter 4." ] @@ -1834,9 +1728,7 @@ "cell_type": "code", "execution_count": 74, "id": "b3d2328f", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "from jupyturtle import make_turtle, forward, left, right\n", @@ -1866,9 +1758,7 @@ { "cell_type": "markdown", "id": "b4325143", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "You can use the following example to test your code.\n", "We'll start with a square `Rectangle` with width and height `100`." @@ -1878,9 +1768,7 @@ "cell_type": "code", "execution_count": 76, "id": "49074ed5", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "corner = Point(20, 20)\n", @@ -1890,9 +1778,7 @@ { "cell_type": "markdown", "id": "2cdecfa9", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "The following code should create a `Circle` that fits inside the square." ] @@ -1901,9 +1787,7 @@ "cell_type": "code", "execution_count": 77, "id": "d65a9163", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "center = rectangle.midpoint()\n", @@ -1916,9 +1800,7 @@ { "cell_type": "markdown", "id": "37e94d98", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ "If everything worked correctly, the following code should draw the circle inside the square (touching on all four sides)." ] @@ -1927,9 +1809,7 @@ "cell_type": "code", "execution_count": 78, "id": "e3b23b4d", - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "make_turtle(delay=0.01)\n", @@ -1939,12 +1819,20 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "48abaaae", - "metadata": {}, - "outputs": [], - "source": [] + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -1964,7 +1852,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap17.ipynb b/chapters/chap17.ipynb index f62b69f..c26b8b7 100644 --- a/chapters/chap17.ipynb +++ b/chapters/chap17.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -141,7 +133,7 @@ "id": "d63f798a", "metadata": {}, "source": [ - "The first element of `rank_names` is `None` because there is no card with rank zero. By including `None` as a place-keeper, we get a list with the nice property that the index 2 maps to the string `'2'`, and so on.\n", + "The first element of `rank_names` is `None` because there is no card with rank zero. By including `None` as a place-keeper, we get a list with the nice property that the index `2` maps to the string `'2'`, and so on.\n", "\n", "Class variables are associated with the class, rather than an instance of the class, so we can access them like this." ] @@ -436,7 +428,7 @@ "metadata": {}, "source": [ "They don't, so it returns `False`.\n", - "We can change this behavior by defining a special method called `__eq__`." + "We can change this behavior by defining the special method `__eq__`." ] }, { @@ -516,7 +508,7 @@ "metadata": {}, "source": [ "If we use the `!=` operator, Python invokes a special method called `__ne__`, if it exists.\n", - "Otherwise it invokes`__eq__` and inverts the result -- so if `__eq__` returns `True`, the result of the `!=` operator is `False`. " + "Otherwise it invokes`__eq__` and inverts the result -- so if `__eq__` returns `True`, the result of the `!=` operator is `False`." ] }, { @@ -616,7 +608,7 @@ "Tuple comparison compares the first elements from each tuple, which represent the suits.\n", "If they are the same, it compares the second elements, which represent the ranks.\n", "\n", - "Now if we use the `<` operator, it invokes the `__lt__` operator." + "Now if we use the `<` operator, it invokes the `__lt__` method." ] }, { @@ -1465,8 +1457,7 @@ "outputs": [], "source": [ "def find_defining_class(obj, method_name):\n", - " \"\"\"\n", - " \"\"\"\n", + " \"\"\"Find the class where the given method is defined.\"\"\"\n", " for typ in type(obj).mro():\n", " if method_name in vars(typ):\n", " return typ\n", @@ -1545,7 +1536,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 64, "id": "e281457a", "metadata": { "tags": [] @@ -1597,7 +1588,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 65, "id": "47d9ce31", "metadata": {}, "outputs": [], @@ -1619,7 +1610,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 66, "id": "7bb54059", "metadata": {}, "outputs": [], @@ -1653,7 +1644,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 67, "id": "72a410d7", "metadata": { "tags": [] @@ -1668,7 +1659,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 68, "id": "b09cceb1", "metadata": { "tags": [] @@ -1690,7 +1681,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 69, "id": "e185c2f7", "metadata": { "tags": [] @@ -1714,7 +1705,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 70, "id": "2558ddd1", "metadata": {}, "outputs": [], @@ -1749,7 +1740,7 @@ "* `get_rank_counts` does the same thing with the ranks of the cards, returning a dictionary that maps from each rank code to the number of times it appears.\n", "\n", "All of the exercises that follow can be done using only the Python features we have learned so far, but some of them are more difficult than most of the previous exercises.\n", - "I encourage you to ask an AI for help.\n", + "I encourage you to ask a virtual assistant for help.\n", "\n", "For problems like this, it often works well to ask for general advice about strategies and algorithms.\n", "Then you can either write the code yourself or ask for code.\n", @@ -1779,7 +1770,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 71, "id": "7aa25f29", "metadata": { "tags": [] @@ -1795,7 +1786,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 72, "id": "72c769d5", "metadata": { "tags": [] @@ -1817,7 +1808,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 73, "id": "e1c4f474", "metadata": { "tags": [] @@ -1846,7 +1837,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 74, "id": "c91e3bdb", "metadata": { "tags": [] @@ -1868,7 +1859,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 75, "id": "a60e233d", "metadata": { "tags": [] @@ -1890,7 +1881,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 76, "id": "ae5063e2", "metadata": { "tags": [] @@ -1923,7 +1914,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 77, "id": "940928ba", "metadata": { "tags": [] @@ -1943,7 +1934,7 @@ "Write a method called `has_straight` that checks whether a hand contains a straight, which is a set of five cards with consecutive ranks.\n", "For example, if a hand contains ranks `5`, `6`, `7`, `8`, and `9`, it contains a straight.\n", "\n", - "An Ace can come before a two or after a King, so `Ace`, `2`, `3`, `4`, `5` is a straight and so it `10`, `Jack`, `Queen`, `King`, `Ace`.\n", + "An Ace can come before a two or after a King, so `Ace`, `2`, `3`, `4`, `5` is a straight and so is `10`, `Jack`, `Queen`, `King`, `Ace`.\n", "But a straight cannot \"wrap around\", so `King`, `Ace`, `2`, `3`, `4` is not a straight." ] }, @@ -1960,7 +1951,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 78, "id": "6513ef14", "metadata": { "tags": [] @@ -1981,7 +1972,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 79, "id": "8a220d67", "metadata": { "tags": [] @@ -2004,7 +1995,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 80, "id": "5a422cae", "metadata": { "tags": [] @@ -2026,7 +2017,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 81, "id": "24483b99", "metadata": { "tags": [] @@ -2048,7 +2039,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 82, "id": "69f7cea9", "metadata": { "tags": [] @@ -2081,7 +2072,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 83, "id": "88bd35fb", "metadata": { "tags": [] @@ -2097,7 +2088,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 84, "id": "5e602773", "metadata": { "tags": [] @@ -2109,7 +2100,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 85, "id": "997b120d", "metadata": { "tags": [] @@ -2131,7 +2122,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 86, "id": "23704ab4", "metadata": { "tags": [] @@ -2143,7 +2134,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 87, "id": "7d8d2fdb", "metadata": { "tags": [] @@ -2166,7 +2157,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 88, "id": "4c5727d9", "metadata": { "tags": [] @@ -2193,7 +2184,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 89, "id": "7e65e951", "metadata": { "tags": [] @@ -2215,7 +2206,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 90, "id": "758fc922", "metadata": { "tags": [] @@ -2239,14 +2230,16 @@ { "cell_type": "markdown", "id": "00464353", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "You can use the following outline to get started." ] }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 91, "id": "15a81a40", "metadata": { "tags": [] @@ -2261,7 +2254,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 92, "id": "bc7ca211", "metadata": { "tags": [] @@ -2273,7 +2266,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 93, "id": "20b65d89", "metadata": { "tags": [] @@ -2293,7 +2286,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 94, "id": "57e616be", "metadata": {}, "outputs": [], @@ -2305,7 +2298,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 95, "id": "b8d87fd8", "metadata": {}, "outputs": [], @@ -2315,7 +2308,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 96, "id": "3016e99a", "metadata": {}, "outputs": [], @@ -2325,7 +2318,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 97, "id": "852e3b11", "metadata": {}, "outputs": [], @@ -2356,7 +2349,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 98, "id": "1f95601d", "metadata": { "tags": [] @@ -2371,7 +2364,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 99, "id": "420ca0fd", "metadata": { "tags": [] @@ -2393,7 +2386,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 100, "id": "1e4957cb", "metadata": { "tags": [] @@ -2408,7 +2401,7 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 101, "id": "4dc8b899", "metadata": { "tags": [] @@ -2420,7 +2413,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 102, "id": "86229763", "metadata": { "tags": [] @@ -2432,7 +2425,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 103, "id": "7f1b3664", "metadata": { "tags": [] @@ -2455,7 +2448,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 104, "id": "85b47651", "metadata": {}, "outputs": [], @@ -2501,12 +2494,12 @@ "`put_in_pouch` takes any object and appends it to `contents`.\n", "\n", "Now let's see how this class works.\n", - "We'll create two `Kangaroo` objects with the names Kanga and Roo." + "We'll create two `Kangaroo` objects with the names `'Kanga'` and `'Roo'`." ] }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 105, "id": "13f1e1f3", "metadata": {}, "outputs": [], @@ -2525,7 +2518,7 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 106, "id": "e882e2d6", "metadata": {}, "outputs": [], @@ -2545,7 +2538,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 107, "id": "724805bb", "metadata": {}, "outputs": [], @@ -2563,7 +2556,7 @@ }, { "cell_type": "code", - "execution_count": 107, + "execution_count": 108, "id": "3f6cff16", "metadata": {}, "outputs": [], @@ -2589,6 +2582,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -2608,7 +2617,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap18.ipynb b/chapters/chap18.ipynb index e1760e2..3027822 100644 --- a/chapters/chap18.ipynb +++ b/chapters/chap18.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 1, @@ -411,7 +403,7 @@ "source": [ "The result is a set that contains the words in the document that don't appear in the word list.\n", "\n", - "The comparison operators work with sets.\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." ] }, @@ -487,7 +479,7 @@ "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 standard module called `collections`, so you have to import it.\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." ] }, @@ -1589,7 +1581,7 @@ "id": "71e3b049", "metadata": {}, "source": [ - "You can call this function with any number of positional arguments." + "You can call this function with any number of arguments." ] }, { @@ -1669,7 +1661,7 @@ "id": "07be77f3", "metadata": {}, "source": [ - "In this example, the value of `kwargs` is printed, but otherwise is has no effect.\n", + "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`." @@ -1897,7 +1889,7 @@ "metadata": {}, "source": [ "`run_unittest` does not take `TestExample` as an argument -- instead, it searches for classes that inherit from `TestCase`.\n", - "Then is searches for methods that begin with `test` and runs them.\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`." @@ -2021,7 +2013,6 @@ "### Ask a virtual assistant\n", "\n", "There are a few topics in this chapter you might want to learn about.\n", - "Here are some question to ask an AI.\n", "\n", "* \"What are the methods and operators of Python's set class?\"\n", "\n", @@ -2035,11 +2026,11 @@ "\n", "* \"How does `unittest` do test discovery?\"\n", "\n", - "\"Along with `assertequal`, what are the most commonly used methods in `unittest.TestCase`?\"\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", + "* \"What are the pros and cons of `doctest` and `unittest`?\"\n", "\n", - "For the following exercises, consider asking an AI for help, but as always, remember to test the results." + "For the following exercises, consider asking a virtual assistant for help, but as always, remember to test the results." ] }, { @@ -2072,7 +2063,7 @@ "metadata": {}, "source": [ "Write a version of this function that uses `set` operations instead of a `for` loop.\n", - "Hint: ask an AI \"How do I compute the intersection of Python sets?\"" + "Hint: ask a VA, \"How do I compute the intersection of Python sets?\"" ] }, { @@ -2572,6 +2563,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { @@ -2591,7 +2598,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/chapters/chap19.ipynb b/chapters/chap19.ipynb index 45f9084..4d86fb8 100644 --- a/chapters/chap19.ipynb +++ b/chapters/chap19.ipynb @@ -2,22 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "b112e25b", + "id": "1331faa1", "metadata": {}, "source": [ - "You can order print and electronic versions of *Think Python 3e* from\n", + "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": null, - "id": "3466e1f6", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "171aca73", @@ -154,6 +146,22 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [] + }, + "source": [ + "[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": { diff --git a/jb/_config.yml b/jb/_config.yml index 245575a..f0b261f 100644 --- a/jb/_config.yml +++ b/jb/_config.yml @@ -1,5 +1,5 @@ # Book settings -title: Think Python, 3rd edition +title: Think Python author: Allen B. Downey latex: @@ -14,6 +14,19 @@ repository: html: use_repository_button: true + extra_footer: | + + parse: # myst_extended_syntax: true # instead enable individual features below myst_enable_extensions: # https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html diff --git a/jb/_toc.yml b/jb/_toc.yml index e5e3fd4..6a5c362 100644 --- a/jb/_toc.yml +++ b/jb/_toc.yml @@ -1,23 +1,31 @@ format: jb-book root: index -chapters: -- file: chap00 -- file: chap01 -- file: chap02 -- file: chap03 -- file: chap04 -- file: chap05 -- file: chap06 -- file: chap07 -- file: chap08 -- file: chap09 -- file: chap10 -- file: chap11 -- file: chap12 -- file: chap13 -- file: chap14 -- file: chap15 -- file: chap16 -- file: chap17 -- file: chap18 -- file: chap19 +parts: +- caption: Front Matter + chapters: + - file: chap00 +- caption: Chapters + numbered: True + chapters: + - file: chap01 + - file: chap02 + - file: chap03 + - file: chap04 + - file: chap05 + - file: chap06 + - file: chap07 + - file: chap08 + - file: chap09 + - file: chap10 + - file: chap11 + - file: chap12 + - file: chap13 + - file: chap14 + - file: chap15 + - file: chap16 + - file: chap17 + - file: chap18 + - file: chap19 +- caption: End Matter + chapters: + - file: blank diff --git a/jupyturtle_flower.png b/jupyturtle_flower.png new file mode 100644 index 0000000..30a7ab2 Binary files /dev/null and b/jupyturtle_flower.png differ diff --git a/jupyturtle_flower.svg b/jupyturtle_flower.svg new file mode 100644 index 0000000..33236db --- /dev/null +++ b/jupyturtle_flower.svg @@ -0,0 +1,1930 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jupyturtle_pie.png b/jupyturtle_pie.png new file mode 100644 index 0000000..472eb5c Binary files /dev/null and b/jupyturtle_pie.png differ diff --git a/jupyturtle_pie.svg b/jupyturtle_pie.svg new file mode 100644 index 0000000..5c7c117 --- /dev/null +++ b/jupyturtle_pie.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/think_python_3e.jpg b/think_python_3e.jpg new file mode 100644 index 0000000..06b118d Binary files /dev/null and b/think_python_3e.jpg differ diff --git a/thinkpython.py b/thinkpython.py index 02ff181..3f08885 100644 --- a/thinkpython.py +++ b/thinkpython.py @@ -2,21 +2,6 @@ import io import re -from IPython.core.magic import register_cell_magic -from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring - - -def traceback(mode): - """Set the traceback mode. - - mode: string - """ - with contextlib.redirect_stdout(io.StringIO()): - get_ipython().run_cell(f'%xmode {mode}') - - -traceback('Minimal') - def extract_function_name(text): """Find a function definition and return its name. @@ -34,61 +19,75 @@ def extract_function_name(text): return None -@register_cell_magic -def add_method_to(args, cell): +# the functions that define cell magic commands are only defined +# if we're running in Jupyter. - # get the name of the function defined in this cell - func_name = extract_function_name(cell) - if func_name is None: - return f"This cell doesn't define any new functions." +try: + from IPython.core.magic import register_cell_magic + from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring - # get the class we're adding it to - namespace = get_ipython().user_ns - class_name = args - cls = namespace.get(class_name, None) - if cls is None: - return f"Class '{class_name}' not found." + @register_cell_magic + def add_method_to(args, cell): - # save the old version of the function if it was already defined - old_func = namespace.get(func_name, None) - if old_func is not None: - del namespace[func_name] + # get the name of the function defined in this cell + func_name = extract_function_name(cell) + if func_name is None: + return f"This cell doesn't define any new functions." - # Execute the cell to define the function - get_ipython().run_cell(cell) + # get the class we're adding it to + namespace = get_ipython().user_ns + class_name = args + cls = namespace.get(class_name, None) + if cls is None: + return f"Class '{class_name}' not found." - # get the newly defined function - new_func = namespace.get(func_name, None) - if new_func is None: - return f"This cell didn't define {func_name}." + # save the old version of the function if it was already defined + old_func = namespace.get(func_name, None) + if old_func is not None: + del namespace[func_name] - # add the function to the class and remove it from the namespace - setattr(cls, func_name, new_func) - del namespace[func_name] - - # restore the old function to the namespace - if old_func is not None: - namespace[func_name] = old_func - - -@register_cell_magic -def expect_error(line, cell): - try: + # Execute the cell to define the function get_ipython().run_cell(cell) - except Exception as e: - get_ipython().run_cell('%tb') - + # get the newly defined function + new_func = namespace.get(func_name, None) + if new_func is None: + return f"This cell didn't define {func_name}." -@magic_arguments() -@argument('exception', help='Type of exception to catch') -@register_cell_magic -def expect(line, cell): - args = parse_argstring(expect, line) - exception = eval(args.exception) - try: - get_ipython().run_cell(cell) - except exception as e: - get_ipython().run_cell("%tb") - + # add the function to the class and remove it from the namespace + setattr(cls, func_name, new_func) + del namespace[func_name] + # restore the old function to the namespace + if old_func is not None: + namespace[func_name] = old_func + + @register_cell_magic + def expect_error(line, cell): + try: + get_ipython().run_cell(cell) + except Exception as e: + get_ipython().run_cell("%tb") + + @magic_arguments() + @argument("exception", help="Type of exception to catch") + @register_cell_magic + def expect(line, cell): + args = parse_argstring(expect, line) + exception = eval(args.exception) + try: + get_ipython().run_cell(cell) + except exception as e: + get_ipython().run_cell("%tb") + + def traceback(mode): + """Set the traceback mode. + + mode: string + """ + with contextlib.redirect_stdout(io.StringIO()): + get_ipython().run_cell(f"%xmode {mode}") + + traceback("Minimal") +except (ImportError, NameError): + print("Warning: IPython is not available, cell magic not defined.")