Supplemental Resources

In addition to the core class resources which we expect every student to engage with, we have many additional resources to help you understand programming, which may be more or less useful for specific students. Reading, watching, or listening to these materials is optional, but we expect that if you are having trouble understanding things, you will make use of these resources to help review the class concepts from a different angle, and for many students, using some of these resources every week may be helpful. Use the links in the table of contents below to jump to specific resources.

Table of Contents

Extra Programs

Reference Pages

If you're looking for the details on how a specific function or programming construct works, we have detailed reference pages that should have short explanations for everything you'll use in the course. If something is missing, don't hesitate to ask a question about it. You can follow that link or use the "Quick Reference" link under the "Reference" section of the navigation bar for this site.

Short Explanations

These materials are like blog posts or tutorials for specific aspects of the course. If you're having trouble understanding the specific concepts they relate to, they may prove helpful to consult. They cover the following topics:

Course Software

Our software page covers how to install all of the required programs for this course on your own laptop. These will also be available on lab computers in the computer labs on campus.

Code Style Guide

If you are wondering how to use comments in your code or generally how to handle issues of code style, we have a code style guide which covers those issues in detail.

Problem-Solving Strategies

Note: you can listen to an audio recording of this material as part of our "The Path to Programming" podcast.

The title of this course is "Computer Programming and Problem Solving," and in large part we are teaching you to solve problems by experience. Practical experience writing code will sharpen your instincts and help you solve all kinds of problems, but even when solving problems from experience, there is a specific set of steps to follow, which we've outlined below. Also, there are a few other general problem-solving skills that we'll talk about, like incremental problem solving and the divide-solve-combine approach.

Problem Solving Steps

  1. The first step is to understand the problem. Read the problem statement carefully, whether you're working on a quiz question or a project task. Then, reformulate the problem in your own words. If you're working on a quiz, you can just imagine these words; when working on a project task you should write down your understanding of the problem in your code file, either as comments, or if you are writing a function, as the documentation string for that function. Research shows that skipping this step is one of the biggest causes of problems, but those problems don't show up until later in the problem-solving process, so it also can cost a huge amount of time. Note: your description of the problem should include at least one and ideally several concrete examples of what the program is supposed to do in a particular situation.

  2. The second step is to identify similar problems that you already know how to solve. These can be problems from lab, or lecture notebooks, where you've gone over the solution and you know how it works. These won't be exactly identical, and the more dissimilar they are, the more work you will have to do to adapt a solution in the later steps. However, this is the key step that allows you to solve the problem: by understanding how it's like another problem you've already solved, the main thrust of the answer will become clear to you.

  3. The third step is to plan out a solution, based on the similar problem you identified in step 2, but adapting that solution to meet the requirements of this problem that you understood in step 1. Whether you're working on a quiz or on a project, you should write down your plan as a series of comments in your answer. These comments (in English, not as code) will form the outline of your solution. It will take some practice to understand the right level of comments to write at this stage, but think of it like outlining an essay: you want to hit the big points, but leave the details for later. One way to think about this step: imagine you are asked to solve the problem as a human, rather than trying to program a computer to do it. What steps would you take? How could you write down those steps as instructions so that another person could do the job based on those instructions? By the end of step 3, you should have both a description of the problem and an outline of a solution written down, but you haven't written any code yet. Again, skipping this step may seem like it will save time, but on average it will cost you a huge amount of time. Also, on a quiz you can often get partial credit if you have included comments describing a correct strategy, even if your implementation of that strategy has errors in it.

  4. Step four is where we start to write code. Based on your outline, you are writing a first draft of your solution. It's just a draft, so it's okay if it's messy and incomplete. The point is to put down some code that follows your outline from step 3. During this step, you will often realize that you forgot something in step 3, in which case you should add a comment explaining the extra step that's necessary as you write the code. This step is really hard, because you're suddenly being asked to deploy all of your memorized knowledge of a programming language, including vocabulary (which functions and operators to use and what do they do) and grammar/syntax (where do you put the parentheses or colons, etc.)! This is something that you'll naturally get better at with practice.

  5. Step five is to test your program. You've finished a draft, but of course you still need to proofread it and polish it up! Your first draft probably has many errors in it, but one skill that you're building is how to quickly spot and fix those errors based on the feedback that your programming language gives you. Being a good programmer is not about never making mistakes, just like being a good writer is not about never making a tpyo! Instead, you want to become skilled at using the tools available to you to quickly find and fix the mistakes that you will inevitably make, and the first part of that process is to find your mistakes. At first, simply running the program will probably produce an error message, but as you weed out some of the earlier bugs, you'll run out of error messages. At that point, use the concrete examples that you developed in step 1 as points of comparison: run your program using the same inputs, and see if you get the result that you said you should expect. In some cases, you might have made a mistake in part 1, and you can revise your examples (it's hard to see exactly how the program will work before you've started writing it). In other cases, you'll find that your draft program doesn't do the right thing, even though there's no error messages any more. If you test your program and it works for all of the examples you can think of, then you're probably done (but try to think of some extra complicated examples first).

  6. Step six is to fix your program, now that you've tested it. You will first need to deploy debugging skills during this step to refine your understanding of why your program doesn't work, so that you can figure out how to modify it. If you're getting an error message, you may still need more information about what's going wrong. If you don't have an error message but your program's behavior isn't correct, it's even more important to understand why so that you can apply the correct fix, instead of making a guess and potentially making things worse! You should use at least the most basic technique during this step: add print function calls to your code so that it reports key extra information as it runs, and look at what it spits out to figure out what's going on. Add as many prints as you need to get a good picture of what's happening. You could also use the debugger to run your code step-by-step. In any case, when you've identified what's causing the problem, modify your code to fix it. Sometimes at this point, you'll have to go back a bit in the process, even as far back as step 1. Maybe it becomes clear to you that you misunderstood the problem description, for example, or maybe you realize that in step 2, you misidentified which other problems you had already solved were similar. Back up as far as you need to and proceed again, at the very least repeating step 5. But don't feel too bad about that: your final program will be better as a result of your revisions, and you're also learning from your mistakes (you'll learn more from making a mistake and fixing it than you will from getting something right the first try). Even if you have to throw away some work, the knowledge you gained from that attempt will help you in your next attempt and when solving future problems!

If you have tested your code in step five and found that it finally works, congratulations, you're done! One wrinkle is that on a quiz, you won't be able to do steps 5 or 6 for real, because you can't run your code. However, you should be learning how to simulate code and imagine what it will do over the course of this class, so you can do steps 5 and 6 that way. We also usually try to keep the quiz questions simpler so that there's not too much to manage.

Incremental Problem Solving

You might have noticed that the problem solving steps above are a bit unwieldy to apply to an entire project task! Although writing down an outline of the steps you want to take is helpful for keeping yourself on track, there's so much to do that when it comes to steps 5 and 6, things can get a little bit overwhelming. One problem solving approach to apply in these cases is called incremental problem solving, and it basically says that you should only solve a little bit of a problem at a time. You'll still start with steps 1–3 above, but in step 4, instead of trying to write code to complete your entire outline, just write code to do one or two of the steps from your outline.

At this point, you'll need to look at your examples from part 1, and come up with intermediate expected results. What should be true after the first few steps of your outline, for the specific inputs or examples that you had in mind? Write these things down as comments, and then proceed to steps 5 and 6 for your partial program, testing the code that you've got so far to make sure it works correctly, even though it's not really solving the whole problem. Once you've finished the revision process for the first few steps you took, return to step 4 for the next few steps from your outline, and repeat this process as many times as it takes to complete your program. At every step of the way, you should be able to test things and build confidence that the code you've written so far works correctly, so that when you're writing code for the next step, you don't have to second-guess yourself. Occasionally you'll find you have to revisit some of that earlier code and revise it further, but this shouldn't be that common.

It definitely takes skill and practice to use this incremental technique, but it can help to break a too-big problem down into more manageable chunks, although see the next section for another technique that can help in a similar way.

Divide-Solve-Combine

This approach is similar to the incremental approach, but a bit more deliberate, and it involves changing the nature of your solution instead of just the process taken to construct that solution. Of course, by doing so, it often results in more modular and reusable code, which is a good thing. The divide-solve-combine approach, like the incremental approach, is useful when faced with a problem that feels too big or complicated to solve all at once, and it can also be useful when you get stuck on a single step of a larger problem that might have seemed simple when you were building your outline, but that turns out to be tricky when it's time to write code.

The essence of this technique is to divide your problem into two or more separate parts, such that solving those parts individually will solve the whole problem with just a little bit of glue to combine the individual solutions. Like the incremental approach, it takes skill and practice to learn how to apply it well, but it's a quite useful and general technique. The steps are as follows:

  1. Identify that your problem is too big to solve all at once, or that you've reached a complex sub-problem of a larger problem you were trying to solve.

  2. Decide how to cut up the larger problem: which parts are mostly independent of each other, and if they were solved individually, how could their solutions be combined to solve the larger problem? In some cases, this step is quite hard. Write down your plan for dividing up the larger problem as comments in your code.

  3. Reformulate your smaller parts as individual problems, and write down how you plan to combine their solutions. Then, apply the full set of 6 problem-solving steps to each sub-problem one at a time, starting from describing what the problem is and providing examples of how it should work, and finishing up once you've gone through several rounds of testing and revision and you're confident your solution to the sub-problem works. Repeat this for each of your sub-problems.

  4. Based on your ideas from step 2 about how to combine the sub-problem solutions, write the code necessary to do that, and follow steps 5 and 6 of the general problem solving approach to test and debug your combined solution. It's possible that at this point you may have to revise one of your sub-problems a bit, but you can go back and forth between the larger problem and the sub-problems until things come together.

Although this approach might sound like extra work (I had one problem to solve and you're telling me to create two!?!), it comes with two important benefits. First, it reduces your cognitive load: by dividing the original problem into smaller, simpler problems, you'll reduce the amount of information you need to hold in your brain at once, and especially while you're still exercising to improve the memory structures you'll need when programming, this is a huge benefit. Second, the code you end up with will be easier to understand, easier to debug, and more modular, meaning that it's more likely to be re-usable in the future.

Tips for Debugging

This document lists a few different simple debugging techniques.

Debugging as a Murder Mystery

Are you an expert at solving murder mysteries? Thankfully, you can put that expertise to use in programming as well! Here's how to solve a problem when Python is giving you an error message but you're not sure why:

  1. What is the murder weapon? Read what the error message says, and try to figure out some hints about which operation actually caused the error, and which variables/values were involved. Try to identify the specific operation (e.g., function call, operator, variable assignment, etc.) that caused the problem.
  2. Who are the suspects? Identify every variable and value on the line where the error occurs. For each suspect (i.e., variable or value) follow these steps:
    1. Check if the suspect is really a person, and not an elaborate ruse. Did you misspell a variable name? Have you used the correct syntax for defining a value? Did you use leave off quotation marks when you meant to use a string?
    2. Check if the suspect is wearing a disguise or using an assumed name. First, come up with an expectation for the type of each value, and for both the type and the value of each variable. Then, add a print function call to your code to print out what the actual types/values are (add this print immediately before the line where the error occurs). If there's a value or variable whose type or value doesn't match your expectations, you may need to investigate further.
    3. For suspects of interest check how they entered the crime scene. For each mismatched variable from the previous step, identify where its value was most recently updated, and figure out how to get it a more appropriate value. You may also need to change its name to match its value if the value is something that you do in fact need to use.
  3. If at this point, the perpetrator is still unclear, you may need to fully reconstruct the crime scene step-by-step. Take apart the expression where the error occurs and turn it into a series of single-operation statements using intermediate variables. With only one thing happening at once, the error should be easier to pinpoint.

Rules of Python

To help you understand how Python works, this section explains the rules that Python follows when running your code. Note that as we cover more advanced topics, we will teach you about new rules, but the basic rules are quite simple.

Core rules

When you press the "run" button in Thonny to run a file, or the "run" button in a Jupyter notebook to run a cell, here are the rules that Python follows:

  1. Start at the very first line of code.
  2. When running a line of code, first simplify any expressions on that line. To simplify an expression, Python simplifies things one step at a time, and the possible simplification steps are as follows:
    • Replace a variable name with the current value of that variable.
    • Combine two values using an operator. The two values to be combined must already be fully simplified.
    • Replace a function call whose argument(s) have been completely simplified with the result of that function call. Note that during this step, the function being called may produce side effects. Note that each built-in function has its own rules for what the result will be (and for what side effect(s) will occur) based on the argument(s) you give it.
  3. Once all expressions have been fully simplified down to individual values, if the line of code is an assignment statement, the value of the variable named in the assignment statement will be updated. If that variable already existed, its old value will be forgotten, or if not, that variable will be created. Either way, the simplified value of the expression on the right-hand side of the equals sign becomes the new value of the variable named on the left-hand side of the equals sign.
  4. After all expressions have been simplified into values, and an assignment has been made (if there was one) Python moves on to the next line of code in the file or notebook cell and repeats this process. When the last line of code has finished executing, the program stops.

Additional notes:

Tips for programming

Based on these rules, here are some important ideas to keep in mind:

Rules for functions

The def statement adds to the core rules above in the following ways:

  1. In rule #2, for running a line of code, if that line of code is a function definition (i.e., if it starts with the keyword def), instead of applying the normal simplification/assignment process, Python does the following:

    1. It gathers all of the lines of code which are indented at the same level as the first line of code following the def line (including that first line of code).
    2. Without simplifying anything or running any of that code, Python stores that code into a box (just like variable assignment) and names that box according to the function name which appears after the def keyword. Python also remembers the parameter names and the order of parameters which are specified on the definition line.
    3. Python then proceeds to execute the next line of code that comes after all of the indented code that it collected; that line of code must match the indentation level of the def statement, or an IndentationError will occur.
  2. In addition to the above process for gathering and storing code, rule #2 part 3 now includes the idea of simplifying a function call to a custom function that's been defined using def. When simplifying a custom function call, we follow the following rules to figure out the result value which will replace the function call in the expression we're simplifying:
    1. First, Python creates a new function call frame for processing the custom function, which is a special zone that can store temporary variables which will only be available during the processing of that function.
    2. Second, Python takes each argument value from the function call that we want to simplify, and based on its order among the arguments, it figures out which parameter of the function it is calling that argument corresponds to. For each of these parameters, an assignment is made within the new function call frame that assigns that parameter to hold the associated argument value.
    3. Next, Python executes each line of code that was stored when the function was defined, according to all of the normal rules for running code. During this process, any assignments that are made create or modify variables that are in the current function call frame, although variables from the whole program context can still be used as part of an expression (they just can't be modified).
    4. If Python encounters a return statement (a line of code that starts with the keyword return) it first fully simplifies the expression that follows the return keyword (if there is one), and then the simplified value of that expression becomes the result of the function call (no further lines of code will be executed). If there is no expression after the return keyword, then the result value will be the special value None.
    5. If there is no return statement, when the last line of code in the stored function is completed, the special value None becomes the result value.

Notes:

Rules for conditionals

The if statement adds to the core rules above in the following ways:

  1. In rule #2, for running a line of code, if that line of code is a conditional (i.e., if it starts with the keyword if), instead of applying the normal simplification/assignment process, Python does the following:
    1. It gathers all of the lines of code which are indented at the same level as the first line of code following the if line (including that first line of code).
    2. If simplifies the condition part of the conditional (the expression between the if and the colon at the end of the line) and determines whether that simplified value is "truthy" or "falsey" (see below).
    3. If the condition's simplified value is truthy, the lines of code that were collected in step 1 are executed per all of the usual rules of Python (including these rules). However, if the simplified condition value is falsey, those lines of code are skipped.
    4. If the line of code following the indented block is just else:, then the indented block of code following that else will be skipped if the first block of code was executed, or executed if the first block of code was skipped (the two blocks are mutually exclusive). If there are multiple elif blocks above an else, the else is only executed if all of them are skipped.
    5. If the line of code following an if (or elif) block starts with elif, the condition on that line will be simplified only if all connected if and elif blocks above it were skipped, and that condition's truthy/falsey status will determine whether we execute or skip the block underneath it.

For the purposes of conditional choices, "truthy" and "falsey" values are defined as follows:

Rules for loops

The while and for statements add to the core rules above in the following ways:

  1. In rule #2, for running a line of code, if that line of code is a while loop (i.e., if it starts with the keyword while), instead of applying the normal simplification/assignment process, Python does the following:

    1. It gathers all of the lines of code which are indented at the same level as the first line of code following the while line (including that first line of code). This is the loop body.
    2. It simplifies the condition expression which follows the keyword while (this is the loop condition). If the simplified value of that expression is truthy, the loop body is executed using all of the normal rules for executing code, including these rules about loops. Otherwise, the loop body is skipped and execution continues after it.
    3. If the loop body was not skipped, when it is finished (assuming it was not interrupted by something like a return) the loop condition is simplified again, and if it still simplifies to a truthy value, the loop body is run again. This process continues until the loop condition simplifies to a falsey value, at which point the loop body is skipped and the loop ends. If the loop condition never becomes falsey, the loop will continue indefinitely.
  2. In rule #2, for running a line of code, if that line of code is a for loop (i.e., if it starts with the keyword for), instead of applying the normal simplification/assignment process, Python does the following:
    1. It gathers all of the lines of code which are indented at the same level as the first line of code following the for line (including that first line of code). This is the loop body.
    2. The for line must name a loop variable, followed by the in keyword, and then the loop expression. Python simplifies the loop expression once, and the result must be a sequence (which we call the loop sequence). Note that the loop expression is simplified only once, unlike the loop condition in a while loop.
    3. Once the loop sequence has been determined, Python assigns the first item in the loop sequence as the value of the loop variable and then executes the entire loop body following all of the normal rules of Python, including these ones. When the loop body is finished (as long as it was not interrupted, e.g. via return), Python will assign the second item from the loop sequence into the loop variable and execute the loop body again, repeating the entire process for each of the items in the loop sequence. (If the loop sequence is an empty sequence, then the loop body is never executed). Each execution of the loop body is called an iteration.
    4. Once all iterations of the loop have finished, execution continues with the code that comes after the indented loop body.

The Case-by-Case Method for Recursive Problem Solving

This case-by-case explanation page describes a method for solving recursive problems that breaks things down to make it easier to get started if you're stuck.

Weekly Materials

Think Python

Allen Downey's Think Python (second edition) is our textbook for this class. You can read ThinkPython on the web or download a free PDF version of the textbook.

There is also a free interactive version of the textbook available on Runestone Academy. You can access the textbook without creating an account there, or create a free account to track your progress on the assignments, but note that those assignments do not count for credit for this course!

Our lecture and project pages also include links to relevant chapters on the web.

While reading the textbook is not required, we recommend it if you want to build a deeper understanding of the material, or if you are feeling lost about some of the concepts in the class. It does a good job of describing things systematically.

The Path to Programming

As an additional resource for the class beyond the textbook, we have a podcast that covers course topics at a high level, titled "The Path to Programming." This series gives advice about how to approach the class and talks about the concepts we're covering, but it doesn't get too much into the details of how to write code: the textbook, lecture slides, notebooks, and labs handle that.

Listening to these recordings isn't mandatory, but they are designed to be helpful in terms of understanding the concepts and approaching the class, and they're not that long each week.

Note: Each entry includes controls to play directly in the browser, a download link for the audio file if you want to listen to it later, a download link to the script if you'd prefer a text format, and an expandable widget that shows the script in the page.

Unit 1: Getting Started

  1. "What is Programming?" (3:26)
    Explains what programming is and what programmers do.


    Click here to download 00_what_is_programming.mp3
    Click here to download 00_what_is_programming_script.txt
    Show the script
    - This first segment is devoted to an important question: "What is programming?"
    - Programming is craft, art, engineering, and maybe even science.
    - But one way we can approach this question is to ask: what does a
      programmer do?
    - Someone who is programming writes programs by putting together words in
      a programming language, which begs the questions: "What is a program,
      and what is a programming language?"
    - A program is a series of instructions for a computer to carry out.
      They're written down in a way that the computer can execute them
      automatically, using a programming language.
    - Depending on the language and the computer, the instructions could have
      all sorts of effects, like doing calculations, displaying patterns
      on a screen, searching for information on the web, moving a robot arm,
      or transferring money between bank accounts.
    - So a program specifies some behavior or a series of actions to carry
      out, and the computer follows a program (we call this 'executing' it).
    - Does that give us an answer to our question "What is programming?"
    - Here's one definition:
    - Programming is a process of crafting programs, which are lists of
      instructions for a computer to carry out.
    - So what do we need to do to learn how to program?
    - We'll need to learn the language of the computer to make sure we can
      write down our instructions in a way that the computer can understand,
      and then we'll need to learn about processes and procedures.
    - A process is just a sequence of events or actions, and a procedure is
      a more general word than "program" for a series of instructions to be
      carried out (a procedure might be carried out by a person or an
      institution, not just by a computer).
    - So a procedure is a recipe for a process: if you follow procedure X,
      that will create process Y. And processes are designed to result in
      certain outcomes.
    - For example, a recipe is a kind of procedure; if you follow it you will
      carry out a process that might result in something delicious you can
      eat.
    - Similarly, a program is a kind of procedure, if you tell a computer to
      follow it, you can get that computer to engage in a process, and that
      process can achieve all sorts of things.
    - As you get more and more comfortable with programming, you'll be
      learning the secrets of a programming language, but that's only half of
      the goal: once you learn how to communicate with the computer you'll
      also need to learn what to say, and in particular, how to create
      procedures that result in useful processes.
    - For this reason, learning to program has a lot in common with learning
      how to design a board game or learning how to manage a bureaucracy: in
      all three pursuits, a deep understanding of how procedures shape
      processes and how processes can be manipulated to achieve desired
      outcomes is critical.
    - For the same reason, programming has aspects of art, craft, design,
      engineering, and science: we can approach the job of creating
      procedures from many different angles, but it's fundamentally a
      creative process that's got some tricky constraints, like architecture.
    - So back to our original question: "What is programming?"
    - Programming is crafting procedures for computers, using a programming
      language, with the goal of creating processes that achieve particular
      results.
    - Now that you've thought a bit about procedures, processes, and
      programs, keep them in mind as you dive into your first steps of
      programming practice.
    - You're going to be learning a few things at once, and it helps to keep
      them straight!
    
  2. "Learning Strategies" (11:21)
    An overview of what you'll learn in this class, including strategies for problem solving and advice for approaching the class.


    Click here to download 01_what_to_learn.mp3
    Click here to download 01_what_to_learn_script.txt
    Show the script
    Hello!
    - Welcome to the  Path to Programming; the audio supplementary materials
      for introduction to computer science!
    - I am a former student that took this introductory course and today I
      want to talk to you about the skills you need to make the best of this
      class.
    - Learning computer science can be challenging for many reasons. It’s
      almost like transporting yourself back to elementary school to learn
      math or writing for the first time because the concepts are new and
      you’re going to be expressing them in a way that is probably not
      familiar to you. So you really want to familiarize yourself with the
      programming language.
    - In this class you will be using Python, but this goes for any
      programming language. An easy way to start familiarizing yourself with
      Python would be to memorize the available commands.
    - Every programming language has a vocabulary of built-in commands that
      you'll need to learn to use, because you will be using them very often.
      The built in functions that you  might end up needing are: max (finds
      the of the several items that has been passed into the parameter), min
      (finds the smallest number that has been passed into the parameter),
      int (takes any number, or a piece of text that spells out a number, and
      gives back an integer), float (takes an integer or string that spells
      out a decimal number and gives back a floating-point number), len, str,
      round and print, input (Entering a print statement and the user can
      enter an answer) and more! Make sure you understand what each of these
      built-in functions do and make sure you know when to use them.
    - For example you could use max along with len to find the length of the
      longer of two pieces of text.
    - We will get more into the specific uses of the functions but what’s
      important for now is that you know what these built in functions are
      called and what each function does briefly. Understanding the built in
      functions ties into understanding data types and what operations to use
      with them.
    - A data type is something every value in python has. It basically
      describes what kind of  Knowing the type of data can help you choose
      the right operator.
    - There are four basic operators in Python these are +(plus), -(minus),
      %(remainder), *(times) and /(divide).
    - They don’t just act like the mathematical operators we know, because
      they interact with different types of data differently.
    - For example the plus operator can be used to concatenate (combine) strings.
    - One thing that I didn't pay enough attention to in the first few weeks
      of learning how to code was the memory model. The memory model
      demonstrates how a program would run and how assignments happen when
      you declare a variable. The memory model explains how the computer
      works when your program runs.
    - The first few weeks it seems really trivial to use the memory model but
      this is actually the perfect time to get acquainted with it. It’s
      important to use the memory model because programming has a lot of
      variable assignments that occur.
    
    - Another skill that you'll need to build up is working memory to hold
      several variables in your mind at once and understand how a sequence of
      several instructions will operate on them. Understanding your variables
      and what values they hold can help you understand how your code runs as
      well.
    - But variables aren't the only thing we use in Python. We can also just
      write expressions to calculate values, and we don’t need to assign them
      to a variable. The reason we use variables is to make sure that they
      are stored in the memory, so that we can access them later on without
      having to write the whole statement over again.
    - And obviously understanding what you’re doing with each line of code is
      one of the most essential parts of programming. There are so many
      things you will learn throughout introduction to programming and
      understanding what each function does, whether you define it or it’s a
      built in function is essential for you to be able to write code that
      not only works but is also efficient.
    
    - Everything we talked about so far is more so on the side of
      understanding the basics of  programming.
    - We touched on understanding the programming language, important
      features of the language include built in functions and user defined
      functions.
    - If you really get to know the functions then you will know which
      operations to use with them.
    - Another thing we touched on was making sure that you understand each
      variable you declare, know how to follow the statements you are making
      and an easy way to do this is by using the memory model.
    - But coding is not just about the programming language you use, it’s
      also about you, the user, and how hard you work beyond the computer
      screen.
    
    - An essential part of learning how to code is having good teamwork
      skills, including the ability to ask questions about what you don't
      know, and the ability to answer questions gracefully and learn from the
      process.
    - The biggest advice I can give you as someone who previously took
      introduction to programming is to never be afraid to ask questions.
      It’s easy to think that the answer to your question is obvious, or that
      it is not the most complex question of all time. But those things do
      not matter, if you are curious about it and you have the opportunity to
      ask it, never pass up on it. That’s why your instructors are there for
      you, they want to answer all your questions no matter how big or how
      small.
    - Learning something from scratch is always hard, so never doubt the
      validity of your questions. The more questions you ask the easier it
      will become for you to answer the questions that you or others have.
    - Beyond asking questions a very important part of intro is team work.
      Working in a group is often a great opportunity to see the way other
      people view the process of coding. You can always learn something from
      your partner or teach something to your coding partner without even
      realizing it.
    - Maybe the way you think about the memory model is different then your
      coding partners and you can really help them understand things more
      clearly with how you think about it. Or maybe your partner is really
      efficient about when they use their variables after executing a bunch
      of statements.
    - An important point to remember is not everyone is at the same level
      even if it is an introductory class, so never feel discouraged if your
      coding partner has more experience, or has a different approach then
      you. At the same time, if you end up feeling like your partner doesn't
      understand things, instead of trying to go ahead and solve things on
      your own, take the time to explain things to your partner.
    - The best way to learn something is to teach it, and in the process,
      you'll find that you will be able to build more solid foundations for
      your own understanding, even if you are explaining a concept that you
      feel is pretty "basic." Finally what will help you the most during this
      class is to have strong planning habits (for example, writing
      documentation and comments before writing code).
    - Commenting is often a part of programming that the user can easily
      neglect because when you understand a problem and how to solve it you
      often don’t feel the need to explain how you've gotten there. But not
      everyone has the same thought process or problem solving process, which
      is why it is very important to describe exactly what you are doing, so
      that the purpose of your code is understandable in every way possible.
    - As a plus, explaining your process and plan using comments and
      documentation before you write your code will help you reach a correct
      solution more quickly and will help make sure you understand the
      problem that you're trying to solve.
    - Besides having generally good planning skills make sure that you have
      the most clear and effective approach, by applying the key case based
      problem solving steps. The problem solving steps are simple and they
      include:
    
    1. Before attempting to solve a problem you really have to understand
       what the problem is asking for.
    2. Before trying to code something from scratch, think of a way to solve
       the problem and how you would approach it yourself if you didn't have
       a computer. That's the hard part, figuring out how to solve a complex
       problem, the easier part is telling the computer what to do in order
       to solve that problem.
    3. If you’re struggling to solve the problem on your own, gather
       relevant examples that can help you. If there’s a similar problem that
       you solved in class go over what the idea behind it was and look at
       the code to see how those ideas were implemented in the code.
        - Don’t only look for the similarities of the current and previous
          problems but also look for differences and try to understand where
          these differences are coming from. This is why understanding the
          built in functions, and understanding variables is important
          because you as the programmer will determine what tools you need to
          solve the problem. The more you understand the functions the easier
          it will be for you to ‘diagnose’ what you need. This way it will
          become easier for you to propose a solution to the problem.
    3. Program your solution (Write down the instructions in computer code.
       You'll need to exercise your knowledge of a programming language to
       understand how to translate your steps into steps the computer can
       carry out.)
    4. Test your program to make sure it works (Use your examples from step 1
       and see if the program behaves correctly.)
    5. Fix your program (If your program doesn't work, or only work
       sometimes, first figure out exactly why, and then decide which step
       you need to revisit.
        - Sometimes you just need to tweak, other times you need to go back
          to step 1. But don't worry, your incorrect work isn't wasted: it
          has already helped you build a deeper understanding of the problem
          that you're working on.)
    
    - If you can follow these problem-solving steps in order, you should be
      able to solve a wide range of different problems, and as you learn more
      and more about specific functions and language constructs, you'll be
      able to expand your vocabulary and keep solving more and more difficult
      problems. But if you develop bad habits and try to jump straight to a
      solution, you may find yourself getting stuck a lot or spending a huge
      amount of time to solve problems as they get more complex.
    - Pay attention to all of the basics we've discussed here and the process
      of learning how to program should go smoothly!
    
  3. "Problem-Solving Strategies" (13:22)
    Talks about key problem-solving strategies for this course, covering the same material that's on our problem-solving strategies reference page.


    Click here to download 02_problem_solving_strategies.mp3
    Click here to download 02_problem_solving_strategies_script.txt
    Show the script
    - This segment talks about several key problem-solving strategies that
      you'll be learning about in this class, and it's useful for you to know
      what these problem-solving strategies are, before you begin.
    - The title of this course is "Computer Programming and Problem Solving,"
      and in large part we are teaching you to solve problems **by
      experience**. Practical experience writing code will sharpen your
      instincts and help you solve all kinds of problems, but even when
      solving problems from experience, there is a specific set of steps to
      follow, which we've outlined below.
    - Also, there are a few other general problem-solving skills that we'll
      talk about, like incremental problem solving and the
      divide-solve-combine approach.
    
    - Let me talk first about the problem-solving steps.
    
    1. The first step is to **understand the problem**.
        - Read the problem statement carefully, whether you're working on a
          quiz question or a problem set task. Then, reformulate the problem
          in your own words.
        - If you're working on a quiz, you can just imagine these words; when
          working on a problem set you should write down your understanding
          of the problem in your code file, either as comments, or if you are
          writing a function, as the documentation string for that function.
        - Research shows that skipping this step is one of the biggest causes
          of problems, but those problems don't show up until later in the
          problem-solving process, so it also can cost a huge amount of time.
        - Note: your description of the problem should include at least one
          and ideally several **concrete examples** of what the program is
          supposed to do in a particular situation.
    
    2. The second step is to **identify similar problems** that you already
       know how to solve.
        - These can be problems from lab, or lecture notebooks, where you've
          gone over the solution and you know how it works.
        - These won't be exactly identical to the one you're solving now, and
          the more dissimilar they are, the more work you will have to do to
          adapt a solution in the later steps.
        - This is the key step that allows you to solve the problem:
          by understanding how it's like another problem you've already
          solved, the main thrust of the answer will become clear to you.
    
    3. The third step is to **plan out a solution**, based on the similar
       problem you identified in step 2, but adapting that solution to meet
       the requirements of this problem that you understood in step 1.
        - Whether you're working on a quiz or on a problem set, you should
          write down your plan as a series of comments in your answer. These
          comments (in English, not as code) will form the outline of your
          solution.
        - It will take some practice to understand the right level of
          comments to write at this stage, but think of it like outlining an
          essay: you want to hit the big points, but leave the details for
          later.
        - One way to think about this step: imagine you are asked to solve
          the problem as a human, rather than trying to program a computer to
          do it. What steps would you take? How could you write down those
          steps as instructions so that another person could do the job based
          on those instructions?
        - By the end of step 3, you should have both a description of the
          problem and an outline of a solution written down, but you haven't
          written any code yet.
        - Again, skipping this step may seem like it will save time, but on
          average it will cost you a huge amount of time.
        - Also, on a quiz you can often get partial credit if you have
          included comments describing a correct strategy, even if your
          implementation of that strategy has errors in it.
    
    4. Step four is where we start to write code.
        - Based on your outline, you are writing a first draft of your
          solution. It's just a draft, so it's okay if it's messy and
          incomplete. The point is to put down some code that follows your
          outline from step 3.
        - During this step, you will often realize that you forgot something
          in step 3, in which case you should add a comment explaining the
          extra step that's necessary as you write the code.
        - This step is really hard, because you're suddenly being asked to
          deploy all of your memorized knowledge of a programming language,
          including vocabulary (which functions and operators to use and what
          do they do) and grammar/syntax (where do you put the parentheses or
          colons, etc.)!
        - This is something that you'll naturally get better at with
          practice.
    
    5. Step five is to test your program.
        - You've finished a draft, but of course you still need to proofread
          it and polish it up! Your first draft probably has many errors in
          it, but one skill that you're building is how to quickly spot and
          fix those errors based on the feedback that your programming
          language gives you.
        - Being a good programmer is not about never making mistakes, just
          like being a good writer is not about never making a tpyo! Instead,
          you want to become skilled at using the tools available to you to
          quickly find and fix the mistakes that you will inevitably make,
          and the first part of that process is to find your mistakes.
        - At first, simply running the program will probably produce an error
          message, but as you weed out some of the earlier bugs, you'll run
          out of error messages.
        - At that point, use the **concrete examples** that you developed in
          step 1 as points of comparison: run your program using the same
          inputs, and see if you get the result that you said you should
          expect.
        - In some cases, you might have made a mistake in part 1, and you can
          revise your examples (it's hard to see exactly how the program will
          work before you've started writing it).
        - In other cases, you'll find that your draft program doesn't do the
          right thing, even though there's no error messages any more.
        - If you test your program and it works for all of the examples you
          can think of, then you're probably done (but you should try to
          think of some extra complicated examples first).
    
    6. Step six is to fix your program, now that you've tested it.
        - You will first need to deploy [debugging skills](debugging) during
          this step to refine your understanding of why your program doesn't
          work, so that you can figure out how to modify it.
        - If you're getting an error message, you may still need more
          information about what's going wrong.
        - If you don't have an error message but your program's behavior
          isn't correct, it's even more important to understand why so that
          you can apply the correct fix, instead of making a guess and
          potentially making things worse!
        - You should use at least the most basic technique during this step:
          add [`print`](builtins#print) function calls to your code so that
          it reports key extra information as it runs, and look at what it
          spits out to figure out what's going on. Add as many `print`s as
          you need to get a good picture of what's happening.
        - You could also use the debugger to run your code step-by-step.
        - In any case, when you've identified what's causing the problem,
          modify your code to fix it.
        - Sometimes at this point, you'll have to go back a bit in the
          process, even as far back as step 1. Maybe it becomes clear to you
          that you misunderstood the problem description, for example, or
          maybe you realize that in step 2, you misidentified which other
          problems were similar.
        - Back up as far as you need to and proceed again, at the very least
          repeating step 5.
        - But don't feel too bad about that: your final program will be
          better as a result of your revisions, and you're also learning from
          your mistakes (you'll learn more from making a mistake and fixing
          it than you will from getting something right the first try).
        - Even if you have to throw away some work, the knowledge you gained
          from that attempt will help you in your next attempt and when
          solving future problems!
    
    - If you have tested your code in step five and found that it finally
      works, congratulations, you're done!
    - One wrinkle is that on a quiz, you won't be able to do steps 5 or 6 for
      real, because you can't run your code.
    - However, you should be learning how to simulate code and imagine what
      it will do over the course of this class, so you can do steps 5 and 6
      that way.
    - We also usually try to keep the quiz questions simpler so that there's
      not too much to manage.
    
    - Those are the six steps to solving a problem, and of course step 6
      involves repeating some of them, so it may take longer than just
      getting to step 6.
    - Now I'm going to talk about incremental problem solving, which is
      another approach that you can merge with that first approach, to make
      it easier.
    
    - You might have noticed that the problem solving steps above are a bit
      unwieldy to apply to an entire problem set task!
    - Although writing down an outline of the steps you want to take is
      helpful for keeping yourself on track, there's so much to do that when
      it comes to steps 5 and 6, things can get a little bit overwhelming.
    - One problem solving approach to apply in these cases is called
      **incremental problem solving**, and it basically says that you should
      only solve a little bit of a problem at a time.
    - You'll still start with steps 1–3 above, but in step 4, instead of
      trying to write code to complete your entire outline, just write code
      to do one or two of the steps from your outline.
    
    - At this point, you'll need to look at your examples from part 1, and
      come up with intermediate expected results.
        - What should be true after the first few steps of your outline, for
          the specific inputs or examples that you had in mind?
    - Write these things down as comments, and then proceed to steps 5 and 6
      for your partial program, testing the code that you've got so far to
      make sure it works correctly, even though it's not really solving the
      whole problem.
    - Once you've finished the revision process for the first few steps you
      took, return to step 4 for the next few steps from your outline, and
      repeat this process as many times as it takes to complete your program.
    - At every step of the way, you should be able to test things and build
      confidence that the code you've written so far works correctly, so that
      when you're writing code for the next step, you don't have to
      second-guess yourself.
    - Occasionally you'll find you have to revisit some of that earlier code
      and revise it further, but this shouldn't be that common.
    
    - It definitely takes skill and practice to use this incremental
      technique, but it can help to break a too-big problem down into more
      manageable chunks, although see the next section for another technique
      that can help in a similar way.
    
    - And that next section is: divide-solve-combine.
    - This approach is similar to the incremental approach, but a bit more
      deliberate, and it involves changing the nature of your solution
      instead of just the process taken to construct that solution.
    - Of course, by doing so, it often results in more modular and reusable
      code, which is a good thing.
    - The divide-solve-combine approach, like the incremental approach, is
      useful when faced with a problem that feels too big or complicated to
      solve all at once, and it can also be useful when you get stuck on a
      single step of a larger problem that might have seemed simple when you
      were building your outline, but that turns out to be tricky when it's
      time to write code.
    
    - The essence of this technique is to divide your problem into two or
      more separate parts, such that solving those parts individually will
      solve the whole problem with just a little bit of glue to combine the
      individual solutions.
    - Like the incremental approach, it takes skill and practice to learn how
      to apply this technique well, but it's a quite useful and general
      technique. The steps are as follows:
    
    1. Identify that your problem is too big to solve all at once, or that
       you've reached a complex sub-problem of a larger problem you were
       trying to solve.
    
    2. Decide how to cut up the larger problem: which parts are mostly
       independent of each other, and if they were solved individually, how
       could their solutions be combined to solve the larger problem?
        - In some cases, this step is quite hard.
        - You should also write down what you're thinking in this step as
          comments in your code.
    
    3. Reformulate your smaller parts as individual problems, and write down
       how you plan to combine their solutions.
        - Then, apply the full set of 6 problem-solving steps to each
          sub-problem one at a time, starting from describing what the
          problem is and providing examples of how it should work, and
          finishing up once you've gone through several rounds of testing and
          revision and you're confident your solution to the sub-problem
          works.
        - Repeat this for each of your sub-problems.
    
    4. Based on your ideas from step 2 about how to combine the sub-problem
       solutions, write the code necessary to do that, and follow steps 5 and
       6 of the general problem solving approach to test and debug your
       combined solution.
        - It's possible that at this point you may have to revise one of your
          sub-problems a bit, but you can go back and forth between the
          larger problem and the sub-problems until things come together.
    
    - Although this approach might sound like extra work (I had one problem
      to solve and you're telling me to create two!?!), it comes with two
      important benefits.
       - First, it reduces your cognitive load: by dividing the original
         problem into smaller, simpler problems, you'll reduce the amount of
         information you need to hold in your brain at once, and especially
         while you're still exercising to improve the memory structures
         you'll need when programming, this is a huge benefit.
       - Second, the code you end up with will be easier to understand,
         easier to debug, and more modular, meaning that it's more likely to
         be re-usable in the future.
    
    - That's all I have to say on problem-solving techniques; I hope as you
      move through this class you revisit these techniques and think about
      how you're applying them when you're working on the problem sets and
      quizzes.
    

Unit 2: Defining Functions

  1. "Functions" (12:23)
    Talks about functions, defining custom functions, and the function call model including function call frames, as well as the difference between print and return.


    Click here to download 03_functions.mp3
    Click here to download 03_functions_script.txt
    Show the script
    - Hello! Welcome to the  Path to Programming; the audio supplementary
      materials for introduction to computer science!
    - I am a former student that took this introductory course and today I
      want to talk to you about functions!
    - You might have heard of functions in a mathematical sense, and in a
      higher conceptual sense functions in programming work in the same way.
      Just like in math, functions have variables that go in, and a result
      that comes out.
    - So a function in computer science takes a certain input (or several
      inputs) and returns an output based on the input.
    - Last week we talked about built-in functions, like print, max, min, int
      and more. These are built-in functions that already exist in Python.
    
    - There can be different outcomes from a function: it can have a result,
      and it could also have one or more side effects. Functions that don't
      have a result value instead have side effects.
    - Some of the built in functions we have talked about so far, like print
      and input, have side effects, namely, displaying text to whoever is
      running the program, who we call "the user."
    - One rule of thumb is that if a function only has side effects and no
      result value, you won't store the result in a variable (because there
      is no result).
    - Functions like print fall into this category, and they generally make
      something apparent to the user, whether that's text, graphics, or
      audio.
    - On the other hand, when you use a function like  max, min, int, float
      or str (string) which has a result value, you do need to store the
      result in a variable. These functions don't have side effects, and
      their purpose is to generate their result values that some other part
      of the program will further modify or make apparent to the user.
    - Basically when you use a function that has a side effect there will be
      something displayed, but if you use a function that only produces a
      result, nothing  will be displayed unless you store that result in a
      variable and later use that variable with a side-effect function.
    - Of course, there are some complicated functions, like input, which have
      both side effects and results. Input displays text, but also asks the
      user to enter their own text, and it makes that text available for use
      in your program as its result value, which is why despite having a side
      effect, you can store the result of input in a variable.
    
    - Now that we know the difference between side effects and result values,
      let's talk more about functions, but instead of talking about the
      built-in functions let's talk about user defined functions!
    - Functions are ways that you can store instructions to be used later,
      and they're quite powerful. Once you can define your own functions you
      will be able to perform a variety of different operations that are not
      in the library of the programming language you are using.
    - By capturing common patterns you can write so many functions. So let's
      dive into the nuts and bolts of how you can come up with your own user
      defined function. 
    
    - The first thing you must do when writing your own function is to define
      it!
    - To do this in Python you would use the def keyword. When you define
      a function you can name the function whatever you want, but make sure
      to pick a name that  makes sense to everyone else that would use the
      code you wrote and not just you.
    - Once you have a name then you may specify one or more parameters. A
      parameter is basically a slot that you can use to give information to
      your function when you call it.
    - Each time anyone calls your custom function, they must supply an
      argument value for each parameter that you set up when you defined the
      function.
    - For example if you define a function with one parameter named 'x,'
      whoever calls that function will need to supply one argument, and
      whatever value they supply will be called 'x' within your code.
    - After defining your parameters, you have to define how your function
      will actually work, and this is done in the body of your function.
    - This is the meat of the function and tells the computer what exactly
      your function will do, and it's made up of Python code statements just
      like the code you've already been writing. For example if you are
      writing a square function the body is where you would tell it to
      multiply the number with itself.
    
    - When you call a function, Python executes each line of code that you
      wrote in the function body, until it reaches a return statement.
    - If that return statement has an expression after it, Python will
      simplify that expression and the resulting value will become the result
      value of the function.
    - If the return statement doesn't have an expression after it, or if
      Python reaches the end of your function body without seeing a return
      statement, the result value of your function will be the special value
      None, which is Python's way of saying "there's no actual value here."
    - Either way, whoever called the function will get back the result value,
      which they can store in a variable or use as part of an expression,
      just as you've already been doing with built-in functions.
    
    - An important thing to pay attention to when writing the body of your
      function is to indent.
    - Indentation is a part of Python and tells the computer how to read what
      you have written: code that shares the same indentation level will be
      treated as part of the same group, for example, as the body of a
      function.
    - If you don't use indentation correctly the computer will get confused,
      and won't know how to read what you have written. If you don't indent
      correctly you will get an indentation error so make sure that you are
      indenting it correctly and consistently!
    
    - Last week we also touched on the memory model and how useful it will
      become and functions are a place where you can use the memory model to
      really understand what is going on behind the scenes! The application
      of the memory model to a function is called the Function Call Model.
    - By following the function call model rules we can figure out the result
      value of a function.
    - We already talked about how you can write your own function by using
      the def keyword, using parameters to accept arguments and specifying
      what will happen using a function call body.
    - When you call a function, behind the scenes Python creates something
      that is called a function call frame.
    - You can think of the function call frame as a box or special zone that
      can store temporary variables which will only be available during the
      processing of that function call.
    - So this all happens behind the scenes and you don't really interact
      with the function call frame as a programmer, but Python or the
      programming language you are using uses the function call frame to be
      able to execute a custom function without interfering with any other
      variables that might exist.
    - The function call frame contains the name of the function, the
      parameters, each filled with their corresponding argument values; and
      the body of the function. The function call frame evaluates the body of
      the function using the arguments that were provided as the
      value of each parameter, and running every line of code in the function
      body from the top to the bottom.
    - The frame is important conceptually because it is not something that
      you can really see at work when you are coding because it is discarded
      after the result value is determined and returned.
    - This means that variables within a function, including its parameters,
      are thrown away when we get to the end of the function, and they are
      not accessible within other functions: only the result value is.
    - Just to go over what we talked about in even more detail, when
      you define a new function, which you do just once, you set up the
      parameters and specify what code should execute.
        - Then each time that function is called, a new function call frame
          is created so that Python can process that specific function call
          (which might have different argument values than another function
          call to the same function).
        - The function call frame is a special zone where the behind the
          scenes work takes place with temporary variable reassignments etc.
        - Eventually, when the function is finished, the function call frame
          is discarded, and only the result value is returned to the code
          where the function call was made (which can now continue). 
    
    - As a concrete example, imagine that you write a function named 'square'
      with one parameter named 'x,' and a single line of code in its body
      which read 'return x times x.'
        - If you call that function by writing 'square parenthesis five close
          parenthesis', you are providing 5 as the argument value for that
          function call, and so x will be 5 in the function call frame that
          is created.
        - With x assigned to 5, the return expression which was 'x times x'
          will evaluate to 25. So wherever you wrote your function call, that
          part of the code will simplify to the value 25.
    
    - Note that if there are multiple parameters, there must be the same
      number of arguments, and each argument is assigned to be the value of
      one parameter based on the ordering of the parameters and arguments:
      first with first, second with second, etc. 
    
    - Now that we know what goes on behind the scenes when you call your
      function with the function call frame, let's talk about more nitty
      gritty things.
    - First we already mentioned that there can be multiple parameters in a
      function. The function call frame is able to take care of this by
      matching up each argument value with a single parameter as
      we mentioned.
    - Besides having multiple parameters you can also call other functions
      within a function that you have defined. Calling a function basically
      means that you can use it in the body of the function that you are
      currently writing.
    - The only condition required to call a function is that it either has
      to be built-in or you must have previously defined it, essentially it
      must exist before you call a function that uses it (but not necessarily
      before that other function is defined).
    - Calling multiple functions can be useful when you are trying to make
      more complicated calculations.
    - Another concept about functions that we will touch on is the local
      variable. A local variable only exists in the body of the function,
      which is where you define how your function will work in case you
      forgot! You can not reference a local variable outside of the body of a
      function.
    - Parameters are also a kind of local variable. They are assigned a value
      when the function is invoked (which is another word for calling a
      function). They also cannot be referred outside the function.
    
    - Finally I want to touch in the difference between using a print
      statement or a return statement in the body of your function.
    - This concept will tie into fruitfulness which I will touch on more in a
      minute, but fruitfulness will become very important as you start
      writing more complicated functions.
    - A return statement in the body specifies the result of the function
      invocation whereas a print statement causes characters to be displayed
      on the screen while your function is running.
    - Think back to the beginning of this episode when we mentioned that the
      print statement has a side effect, which displays something on your
      screen.
    - Be careful to not confuse print and return statements, as they have
      different uses.
        - A return statement creates a result value calculated from the
          function call frame, and that result gets returned to whoever
          called the function, so it can be used elsewhere in your program.
        - On the other hand, a print statement displays text to the user, but
          this text does not become accessible for further operations in
          Python.
        - Typically you will assign the result of a function which has a
          return value to a variable so that you can use it later on, but if
          a function just uses print to display text and does not have a
          return value, you will not really do that.
    - As we mentioned before if your function does not have a return
      statement the function will return the special value 'None'.
    - If a function has a return value other than None than it is called
      fruitful.
    - If a function does not have a return value then it is non-fruitful.
    - For example the print function does not have a return value (as we
      mentioned it displays text as a side effect) and therefore it is a
      non-fruitful function. 
    
    - That was our introduction to functions!
    - Make sure from this episode you take away the basic ideas of what a
      function is, how you define a function using a name, parameters and a
      body and how when you call a function, Python executes it using the
      function call model.
    - Once you understand these basics you can think about more complicated
      scenarios such as a function with multiple parameters, or a function
      that calls another function.
    - Finally make sure you understand the difference between a fruitful and
      non fruitful function.
    - Thank you for joining me on your path to Programming! I hope your
      journey is going well.
    

Unit 3: Booleans and Conditionals

  1. "Booleans and Conditionals" (11:08)
    Talks about Booleans, comparators, and conditionals, including if, elif, and else statements.


    Click here to download 04_booleans_and_conditionals.mp3
    Click here to download 04_booleans_and_conditionals_script.txt
    Show the script
    - Hello! Welcome to the  Path to Programming; the audio supplementary
      materials for introduction to computer science!
    - I am a former student that took this introductory course and today I
      want to talk to you about conditionals!
    - Life is filled with making decisions, every moment you weigh the
      options at hand and make a decision based on what will be most useful
      for you at a given moment. If it is sunny outside you take your
      sunglasses, if it is raining outside you take an umbrella with you, if
      you are hungry you eat food etc. Well you’re not the only one that
      makes decisions, so can your computer!
    - How does your computer make decisions, and what goes on behind the
      scenes for your computer to make a decision?
    - Today we will talk about booleans which help your computer make
      decisions, and conditionals which will actually make them. 
    
    - We talked about different data types in the past weeks like int, float, str (string) and input.
    - Bool is another Python data-type!
    - A bool can have two values: it can either be True or False.
    - These are called Boolean values, named after George Boole, who invented algebraic logic.
    - The first letter of each Boolean values must be capitalized when you
      are writing code.
    - To be able to get Boolean values you need to be able to compare other
      values. Booleans naturally arise when you compare values, for example,
      if you assert that a variable x is greater than 5, that comparison will
      be either True or False.
    - In order to compare values you need to use relational operators.
    - There are many relational operators and you should go over each one to
      make sure that you understand the purpose and meaning of each one. Some
      of the relational operators are "greater than," "less than or equals,"
      and "not equals."
    - You can use relational operators to compare different types of values.
      For example, just like in math you can use it on floats or integers.
    - When you execute the line of code that states “Five is greater than
      six” your computer will give you the Boolean value of False as you
      would expect, just like a mathematical operation that you are used to.
    
    - But relational operators are not limited to numbers, you can also use
      them on strings! This can be done in Python because Python
      automatically assigns a value to each character in a string.
    - For example if you execute the line of code that states “bat is less
      than cat” your computer will give you the Boolean value of True. This
      is because b comes before c in the alphabet therefore b is assigned a
      lower number compared to c, and therefore is less than (just like when
      sorting a dictionary, only if the first letters of two strings are tied
      will the second letters factor into the comparison).
    - One thing to look out for when comparing string values in differences
      in upper and lower case letters.
    - In most programming languages uppercase letters come before lowercase
      letters, meaning lowercase letters have a higher value associated with
      them. So if you executed the line of code that states “uppercase C cat
      is less than lower case b bat” you would get the Boolean value of True
      since lower case b has a higher value (97) than the upper-case C (67). 
    
    - Some other logical operators that you will be using are not, and, or.
      These logical operators work just like they sound in English.
    - The not operator evaluates to the opposite of the truth value. For
      example if you executed a line of code that states “not parenthesis
      three is greater than five close-parenthesis“ you would get True
      because three is not greater than five.
    - The and, or operations are kind of easy to mix up but the most
      important difference between them is that the "and" operation is used
      when you want to check that ALL of the statements you are making are
      true. When you are using the "or" operation you are checking if AT
      LEAST one of the statements you are making is True.
    - For example if you executed a line of code that stated “(Three is less
      than five AND bat is less than cat)” you would get the boolean value of
      True since both statements are true. And if you executed a line of code
      that stated  “(Three is greater than five OR bat is less than cat)” you
      would still get True, even though one of those statements is false, as
      three is not greater than five. But that doesn't matter because the or
      operator checks to see if at least one of the statements made is True.
    - Something that really helped me distinguish between using "and" and
      "or" was making Truth Tables.
    
    - A truth table is when you take two expressions and evaluate what the
      resulting boolean value would be based on the different operators you
      are using and the different bools the expressions can have. Making your
      own truth table is a great exercise if you feel stuck trying to figure
      out the differences between "and" and "or."
    - Understanding the differences between them is really important because
      as you improve at coding you will start combining logical operators.
      Meaning you will execute lines of code that feature multiple logical
      operators. So you could be executing a line of code that has multiple
      and’s and or’s!
    - That’s why it’s really important to know how to keep track of the
      results. In those cases, you should always use parentheses to
      explicitly specify how the operators are grouped with their operands.
    
    - Now that you have the tools to evaluate boolean values by using logical
      operators, let’s get more  into conditionals!
    - A conditional is a block of code that starts with the word "if,"
      followed by a condition expression which will evaluate to a Boolean
      value. When you write if, your computer can’t just run the code like it
      normally does, it has to make a decision!
    - That’s why using conditionals is very powerful and now that you have
      all the tools to code a conditional let’s get into how you can actually
      code it.
    - The first thing your computer does when you execute an if statement is
      to gather all of the lines of code which are indented at the same level
      as the first line of code following the if line.
    - This is why indenting is very important, you want to tell your computer
      which lines of code to apply the if statement to, and you do this
      through indentation.
    - If you indented correctly the condition expression that you have
      written using relational operators will be simplified and you will get
      a boolean value of either True or False.
    - If the boolean value of your conditional expression is True, then the
      lines of code that you had written will be executed, but if it was
      False then that block of code will be skipped over.
    - As you can see, using conditionals is very powerful, because you are
      essentially telling your computer to only execute some code when you
      know a condition you want is satisfied. By using conditionals you can
      make your computer essentially make decisions!
    
    - Just to go over what we talked about:
        - There is a new type we learned about called Boolean. A boolean type
          has two possible values, either True or False.
        - These are called boolean values.
        - You can write a conditional by using the keyword "if" and use
          relational operators in your conditional statement, and essentially
          tell your computer which line of codes you want it to execute and
          which ones you want it to skip over.
    
    - The power of if statements is definitely undeniable! But you are not
      just limited to if statements because you can also have else and elif.
    - What do else and elif statements do? Let’s say you wrote a block of
      code (indenting multiple lines at the same level) after an if statement
      and then you wrote a new block of code (indented at the same level as
      the if block) putting an else before it, indented to match the if.
    - In this situation, if the first block of code with the if statement was
      skipped over because the condition of the if was False, then you are
      guaranteed to execute the else block of code (we call these branches).
    - Essentially using an else statement with an if statement guarantees
      that you always execute one branch or the other. Because if the if
      branch isn't executed the else branch always will be. And vice versa,
      if the if branch is executed the else branch will always be skipped
      over.
    - This means that the if and else branches are mutually exclusive. This
      quality is what ensures there is always some branch of code that is
      executed no matter whether the if condition evaluates to True or False!
    
    - Besides the else statement there is also an elif statement.
    - If/else allows you to create two mutually exclusive branches, but what
      if you need more?
    - For example, the outcome of a game might be a win, a loss, or a tie. In
      this case, we might want three different things to happen depending on
      these three possibilities, and we can add an elif branch between our if
      and else branches to achieve this.
    - If a block of code after an if statement block of code (indented at the
      same level) starts with elif, the condition on the if block is
      evaluated first, and if it's true, the elif will be skipped, along with
      the else.
        - But if the if condition is False, the elif condition will be
          evaluated, and if it's True, that block will be executed (and the
          else will be skipped).
        - If both the if and elif conditions are false, any further elif
          cases will be checked, and if their conditions are all False, a
          final else block will execute (if it's present).
    - Essentially the elif block of code allows you to check multiple
      expressions for True one-by-one, and execute a block of code as soon as
      one of the conditions evaluates to True.
    - Elif and else statements are not required when you are writing
      conditional statements, the only required statement is the if
      statement. But they can be very powerful tools, and you will mostly
      likely need to use them.
    - Another important note to add is that an elif statement can be used
      multiple times in your code following an if statement, to set up as
      many branches as you need, however there can only be one else statement
      following an if statement!
    - If this all seems hard to follow, don’t worry. When you are thinking
      about executing conditionals take each statement one step at a time and
      evaluate whether each statement will be a True or a False and based on
      that move on to the next statement.
    - A very helpful visual tool can be making flow charts. You can basically
      draw a diagram of what will happen when each line of code will be
      executed. 
    
    - At a high level, Booleans are Python's way of representing True or
      False, and conditionals are set up using if, elif, and/or else to allow
      Python to use Booleans to make decisions about whether or not to
      execute certain blocks of code.
    - To create conditions for conditional statements, we usually use
      comparison operators or custom functions that return Booleans to
      specify the conditions that we want to use.
    - Put all of these things together, and your code can be a lot more
      flexible than what's possible just using function definitions alone.
    - This was Path to Programming hope! It was great talking to you, see you
      next time. 
    
    

Unit 4: Loops and Lists

  1. "Sequences and Loops" (10:11)
    Talks about sequences, including indexing and slicing, and loops, including for and while loops.


    Click here to download 05_sequences_and_loops.mp3
    Click here to download 05_sequences_and_loops_script.txt
    Show the script
    - Hello! Welcome to the  Path to Programming; the audio supplementary
      materials for introduction to computer science!
    - I am a former student that took this introductory course and today I
      want to talk to you about Sequences and Loops!
    - Last week talked about conditionals and how our computer can make
      decisions for us based on a condition we gave to our computer.
    - This week we will talk about how your computer would behave if you
      wanted your computer to keep working until you gave it a condition to
      stop working.
    - A basic example we can consider is counting the number of vowels in a
      word. We already know how to write our own function, from the previous
      weeks, to determine if a letter is a vowel or not.
    - If we could apply this function to each letter in a word and count the
      results, then we could count vowels. But first, let's talk about
      sequences.
    
    - Strings, lists, and ranges are all sequences and we need to learn about
      the properties of sequences such as indexing in order to be able to
      understand how to use loops on sequences.
    - We can use a lot of built in functions on sequences to get information
      about them.
    - So far we talked about strings, but we haven’t really touched on what
      lists and ranges are. We will talk more about both of them in this
      episode.
    - You will also be learning more about lists in the future as their own
      data type.
    - But let's start with common properties of all sequences, using strings
      as an example...
    
    - To begin with, every sequence in python can be indexed: you can use an
      integer to pull out a single item.
    - Indices start at 0 and go to the length of the sequence minus one.
    - For example if you had the string “Path” and you accessed the third
      index you would get the letter h (as a single-letter string). P A T H
      zero one two three.
    - To index a sequence in Python, write the sequence (or the name of the
      variable you've stored it in) and then add square brackets afterwards,
      with the index integer inside the brackets. You could also use a
      complex expression or variable in the brackets.
    
    - In addition to indexing them, sequences can be sliced: use square
      brackets just like an index, but put a colon inside to separate two
      integers, and you can grab a sub-sequence between those two positions.
    - For example, if we sliced the word "path" from index zero to index two,
      we'd get the letters P and A (the slice goes up to but NOT including
      the end index).
    - A slice can even use two colons to provide three numbers, where the
      third number is a step value indicating how fast to go; a step of 2
      would skip every other letter, for example.
    
    - Since indices are used so often for dealing with sequences, Python
      actually has a special kind of sequence called a "range" that can be
      used to easily generate indices for any other sequence.
    - The built-in "range" function can be called with 1, 2, or 3 parameters,
      to specify a start, stop and step, and it gives you back a special
      range-type sequence of integers that starts at the start value, ends
      just before (but not including) the stop value, and changes by the step
      value each time.
    - If you just call range with 1 parameter, it uses a start value of 0 and
      a step value of 1, so it will give you a range containing the integers
      from 0 up to but not including the number you gave it, and if that
      number is the length of another sequence, that range will contain
      exactly the indices of that sequence.
    - This is why range(len()) is a kind of idiom in Python: throw any
      sequence inside of len, and call range on the result, and you'll get a
      special sequence containing the indices of the first sequence,
      including the part where it starts at zero and stops before it reaches
      the length of the sequence.
    - For example, the word "hello" as a string is a sequence with 5 items,
      which are the individual letters, and it has indices zero, one, two,
      three, and four.
    - If we call len(hello), we'll get the number 5, and if we call range(5),
      we'll get a range with the numbers zero through four.
    - So if we call range(len("hello")), we'll get the right indices to use
      with that string.
    
    - But why would we want the indices of a sequence as another sequence,
      how is that even useful?
    - It turns out that one of Pythons loops, the for loop, operates
      exclusively on sequences. With a for loop, we can take a block of code
      and cause it to repeat once for each item in a sequence.
    - And what's even better is that with a for loop we get to declare a loop
      variable, which automatically gets assigned to the current item in the
      sequence.
    - We can use a for loop directly with a sequence, and our loop variable
      will hold items from that sequence, like letters if it's a string. Or,
      we could use range and len like we just talked about so that the for
      loop will iterate over the indices of our original sequence, which is
      called an index loop.
    - If we just want to repeat some code a set number of times and we don't
      care about a sequence, we could also use the range function, without
      len, to construct a sequence of numbers that has the length we want.
    - In all three cases: iterating over values, iterating over indices, or
      iterating over a custom range, the for loop works the same way.
    - You write for "loop variable" in sequence, put a colon, and then add an
      indented block of code.
    - You get to pick the name of the loop variable of course, so you might
      write "for letter in word" or "for number in range(5)". Python will
      then repeat the code in the block once for each item in the sequence,
      and during each repetition (called an "iteration") the loop variable
      will take on the value of a different item in the sequence.
    - So if we need to do something like count vowels in a word, we could use
      a for loop to do that, getting letters into the loop variable and
      checking them one by one.
    
    - There are actually two kinds of loops in Python. In addition to or
      loops, there are also while loops.
    - Last week when we talked about conditionals we talked about the if
      statement. You can think of a while loop as an if statement that
      constantly repeats itself until its condition becomes False.
    - When you execute a while loop you write the word while and then your
      condition, which is a boolean expression just like for an if statement.
    - The difference is that once the following block of code is complete, an
      if statement will continue to the code afterwards, but a while loop
      will check the condition again, and keep repeating its block of code
      until the condition evaluates to False.
    - So what exactly happens in your computer when you write a while loop?
    - First of all a line of code that starts with the keyword "while" is a
      while loop, so when you computer sees this keyword it starts executing
      the while loop.
    - Then your computer gathers all of the lines of code which are indented
      at the same level as the first line of code following the while line
      (including that first line of code). This is the loop body.
    - Your computer has to determine whether or not to run the loop body and
      the way that is determined is by checking the continuation condition.
    - If the continuation condition evaluates to True (because remember the
      continuation condition is a boolean expression), the loop body is
      executed using all of the normal rules for executing code, including
      these rules about loops.
    - If the continuation condition is not met, the loop body is skipped and
      execution continues after it.
    - If the loop body is not skipped, when the body is finished executing
      the while statement is revisited and the steps start again.
    - So the loop condition is simplified again, and if it still simplifies
      to a true boolean, the loop body is run again.
    - This process continues until the loop condition simplifies to a False
      value, at which point the loop body is skipped and the loop ends.
    - If the loop condition never becomes False, the loop will continue
      indefinitely, which is called an "infinite loop."
    - That’s why it’s easy to think about while loops as if statements that
      repeat themselves.
    
    - A concrete example would be counting down, like for a rocket launch.
    - First, we could set up a variable called n, with the value 10.
    - Then, we could use a while loop with the condition n is greater than or
      equal to zero, and in the loop we could write print(n).
    - With just that code, Python would keep printing 10 forever.
    - But if we add another line of code inside the loop that says n gets n
      minus one, then it will count down, starting at 10 and counting until
      it reaches zero.
    - After it prints zero, n will be assigned to -1, which will cause the
      condition to be False, and the loop will stop.
    
    - One last thing I want to touch on in this episode is the use of
      accumulation variables.
    - Often when we use loops, we want to build something up across different
      iterations, perhaps by adding numbers together into a sum, or by
      concatenating strings together into a longer message.
    - To do these kinds of things, we can create a variable before the loop
      begins, and then update that variable in the loop by adding to
      it.
    - Since each iteration of the loop will add to the same variable, by the
      end of the loop it will have accumulated a result.
    - For more complex results, you can use a conditional inside the loop as
      well, so that the counter is only updated on some iterations, not all
      the time.
    - This kind of setup is how you would solve the counting vowels problem
      we talked about at the beginning of this episode.
    
    - This week we talked about sequences, and for loops which are used to
      iterate through sequences.
    - We also talked about while loops, which use a continuation condition
      instead of a sequence to control their iteration.
    - Make sure you understand the properties of sequences, such as indices
      and how these properties are used when iterating using
      loops.
    - This was Path to Programming hope! It was great talking to you, see you
      next time. 
    
  2. "Lists and Memory Diagrams" (11:35)
    Talks about lists and memory diagrams. (work in progress)


    Click here to download 06_lists_and_memory_diagrams.mp3
    Click here to download 06_lists_and_memory_diagrams_script.txt
    Show the script
    - Hello! Welcome to the  Path to Programming; the audio supplementary
      materials for introduction to computer science!
    - I am a former student that took this introductory course and today I
      want to talk to you about Lists and Memory Diagrams!
    - If you remember the very first episode we talked about the memory
      model, and how imperative it was when it came to understanding how
      programming works on a basic level. We talked about how the memory
      model helps you understand variable assignments and by extension how a
      program runs.
    - You can think of a Memory Diagram as an extension of this model, as
      they will better help you understand how lists work, and what goes on
      behind the scenes in your computer when you code a list. 
    
    - Last episode we mentioned how a list is a data type but we didn’t get
      into the specifics of what a list is or how it works. We touched on the
      fact that a list is also a type of sequence, which has indices.
    - What separates a list from other types of sequences like strings is the
      fact that they are mutable.
    - You might be wondering what exactly this means, and no it doesn’t mean
      you can silence a list!
    - Instead it means that you can change the contents of a list even after
      creating it.
    - Once you write a string, it’s final, if you made a typo there’s no
      going back, you would have to write a new string with the correct
      spelling, you can’t go back and individually edit the characters.
    - But with lists you can manipulate them after creating them, you can
      change individual items or the length of a list that you have created,
      and you can do this without any new variable assignments.
    
    - Lists can store elements of different types (e.g., integers, booleans,
      strings).
    - They are usually homogeneous, meaning that all of the elements in a
      list are of the same type, but Python allows heterogeneous lists too.
    - A list with no elements is called an empty list.
    - You initialize a list by using brackets!
    - Like we have always done, you need to assign a list that you create to
      a variable so you can access it later on.
    - There are a lot of keywords for operations in lists you will need to
      learn so that you can use the data type of lists in the most efficient
      way possible.
    
    - An important keyword you should know is “in”, this operation allows you
      to check whether an element is in your list.
    - If you had created a list of numbers from 1 to 10 and you wanted to see
      if it contained the number 15, you would use “in” and get a Boolean
      value, and in this case it would be false since the list you created
      only goes up to 10.
    
    - Another important keyword that you will be frequently using is
      “append”.
    - Append is a method that allows you to add an element to a list that you
      have created.
    - It has a single parameter, in which you enter the element you want to
      add to the list.
    - Append is very useful and powerful to use in the body of a loop so
      elements are added to a list on each iteration of a loop you can even
      append a list you've created to another list!
    - Append is like a command that states “add this to the end of the list”-
      make sure you memorize this!
    - The most used list method is append, because it is used to create new
      lists and add elements to them.
    
    - Another very important keyword is “pop.”
    - Pop allows you to remove an item from a list that you have created.
    - You enter an index as the parameter and the element at that index is
      then returned to you and it is also removed from the list.
    - If you don’t give pop a parameter, Python will assume the element at
      the last index should be removed.
    
    - The final method you should know and memorize is “insert”.
    - Insert has two parameters, the first is the index and the second is the
      element that you want to insert before that index.
    - Because it is more specific compared to append (remember append just
      adds to the end of the list that’s why it is super useful when
      iterating) we don’t use it as often but nonetheless it is very useful. 
    
    - Like we talked about last week, lists have indices as they are a type
      of sequence.
    - Therefore lists have the ability to do indexing and slicing. You can
      use the same operations we talked about last week, to for example get
      specific elements from a list using indexing, or getting only a subset
      of a list using slicing.
    
    - Now let's start talking about memory diagrams.
    - As we mentioned in the very first week, visual tools can be really
      helpful to use when you are trying to understand how a program works
      behind the scenes.
    - Memory diagrams are a visual tool as they represent lists and help us
      understand how each of the methods we described work on lists.
    - It wouldn't be much help to you if I just sat here and described what a
      memory diagram looks like, my tip would be for you to search it up and
      familiarize yourself with it.
    - Make sure that you draw a memory diagram the first few times you
      encounter a problem or code that uses lists. Every time you draw a
      memory diagram you will get one step closer to internalizing how lists
      and the operations work.
    - Memory diagrams will help you understand which elements are contained
      within each index. They will become increasingly useful as you start
      encountering lists that have lists within them and have to use multiple
      indices.
    - Please make sure to utilize this tool as much as you can, until it
      becomes second nature to you! As a former student it definitely helped
      me really understand how indices and specifically lists work and it
      made using lists very enjoyable to me! 
    
    - An important thing the memory diagrams can help you realize is the
      difference between aliases and twins. So what exactly are aliases and
      twins?
    - An alias implies that you have assigned the same list to two different
      variables.  So you could have a variable a and a variable b pointing to
      the same list. When you access the list using the name a and make a
      change, the list will also reflect that change when you access the list
      using variable b.
    - Basically when you have an alias when you make a change you make using
      one variable also appears as a change in another variable. In terms of
      a memory diagram it implies that the two arrows from the variable names
      are pointing to the same thing, so when you mutate either there’s only
      one operation going on behind the scenes.
    - To make things more clear let’s think of a real life example that
      reflects this situation. There is a special agent named Joe, for his
      work he uses the name Bond. When Joe goes to the tattoo store and gets
      a tattoo so does the alias Bond because they are actually the same
      person, even though they use different names when needed.
    
    - Another scenario that arises from naming variables is the twins/clones
      example.
    - You can think of this scenario as the opposite situation of aliases.
      Twins arise when you assign different lists to different variables but
      the lists are the same. When you mutate one list that is
      technically identical to the other, the other one doesn't change
      because they have different variables assigned to them. So even though
      the lists contain the exact same elements, because they were created
      separately and accessed using different variables, mutating one doesn't
      mutate the other.
    - Following the tattoo example we used if Joe had an identical twin named
      James (so they have the same DNA) if Joe got a tattoo it wouldn't mean
      that James got a tattoo. The fact that they’re identical to start with
      doesn't mean a change happens to both of them.
    - Memory diagrams help make this clear: in a twins situation, two
      separate variables point to two separate lists that have the same exact
      content, so you can see that if one were to change, the other wouldn't.
    
    - We mentioned that lists are special because they are mutable, you can
      alter a list even after creating it. As a counter-example we said that
      strings are immutable.
    - Another type of immutable objects is a tuple. What are tuples? Tuples
      are essentially like lists but they are immutable.
    - The name comes from "quadruple", "quintuple," etc.: those words end
      with 'tuple,' so the general form with an unspecified number of
      elements is called a "tuple."
    - Tuples are still a form of sequence that contain elements, which are
      separated by commas and contained in parentheses. So when initializing
      a list if you recall you would use square brackets, but when
      initializing a tuple you would use regular parentheses. Like lists the
      elements in a tuple are separated by commas.
    - You can use operations we’ve used on strings like indexing and slicing
      since those operations don’t change a sequence, they just access
      information. But you can’t use the new operations we learned in this
      episode like append and pop on tuples because tuples are immutable and
      you can’t change a tuple after you’ve created one. 
    
    - One last thing I want to quickly touch on in this episode is nested
      loops. This is a complicated topic that we will hopefully expand up on
      but I wanted to tie it into this episode because just like we mentioned
      that there can be a list inside a list, there can also be a loop inside
      a loop!
    - These types of loops are usually the for loop type we talked about and
      they’re called nested loops. The loop that is inside the body of the
      for loop is called…. As you might have guessed, the inner loop.
    - The entire inner loop is executed each time the other loop is iterated,
      just like the rest of the commands in the outer loop body.
    - I would suggest that you experiment with loops yourself as they can be
      complicated to understand without trying them on your own! They are a
      very useful tool and can be used in combination with lists inside of
      lists.
    - The outer for example may be used to access a single list from a list
      of big lists and the inner loop may be used to iterate through the
      elements within the list accessed. Obviously hearing that is very mind
      boggling so I would urge you to try it on your own! 
    
    - We've come to the end of another episode of path to Programming!
    - This week we talked about lists and memory diagrams and touched on
      nested loops as a concept.
    - Lists are very important and they are a type of sequence, what makes
      lists special is that they can be mutated, which means that they can be
      changed after they have been created. Some operations to change lists
      after creation that we touched on are append, pop and insert.
    - A way to understand how lists work and operate behind the scenes is to
      use memory diagrams. So as we said please make sure you use them to
      better understand lists and their operations! We gave an example to the
      complications that can be resolved by using memory diagrams with the
      alias and twin example.
    - And finally today we touched on the concept of for loops and how they
      can be useful.
    - I hope you enjoyed this week’s episode. Talk to you next time!
    

Unit 5: Dictionaries

  1. "Dictionaries" (10:40)
    Talks about dictionaries, including the difference between a collection and a sequence and accessing elements using keys.


    Click here to download 07_dictionaries.mp3
    Click here to download 07_dictionaries_script.txt
    Show the script
    - Hello! Welcome to the Path to Programming; the audio supplementary
      materials for introduction to computer science!
    - I am a former student that took this introductory course and today I
      want to talk to you about Dictionaries!
    - Last week we talked about Lists which is a type of sequence and this
      week we will be talking about dictionaries which is a type of
      collection!
    - All sequences are collections but not all collections are sequences.
    - Dictionaries, like lists are used to store data but unlike lists you do
      not need an index to access the elements, instead you can access the
      elements using a key.
    - Crucially, lists are ordered. It matters that one element comes before
      another element. Indices specify the position of each element in the
      list. The elements in a dictionary are stored in key and value pairs.
      While Python does store these key-value pairs in a particular order,
      conceptually we consider dictionaries to be unordered.
    - You cannot use an index to access a dictionary.
    - Like with sequences, you can use the 'in' operator to check for an
      element.
    - With a dictionary though, the 'in' keyword checks if a key is in a
      dictionary.
    - Now that we have talked about the differences between collections and
      sequences let’s get into the nuts and bolts of dictionaries.
    
    - A Python dictionary is a mutable collection that maps keys to values.
    - This means that each value has a corresponding key which can be used to
      access those values.
    - Because a dictionary is mutable, we can add, change and remove
      key-value pairs.  An important detail to note about the mutability of
      dictionaries is that only the values can be mutable (in case you don’t
      remember mutable means that you can change a value after creating it).
    - The keys are NOT mutable. Imagine if you had a key to get into your
      car. If the key was changeable, then the key might not work with the
      car anymore. This is why it is important that the keys
      are immutable. We do not want to lose access to our values.
    - To sum up, a dictionary consists of a collection of key-value pairs.
      Each key-value pair maps the key to its associated value.
    - To create a dictionary you will use curly brackets and you will use
      colons to separate the values from their keys. You will first name the
      key and then use the colon and then you write the value. 
    
    - There are actually multiple ways to create a dictionary.
    - The first is to name your dictionary and each key-value pair. In this
      case you name your dictionary, and use curly brackets and then give
      your first key its name, then use a colon and then write the
      corresponding value. You can keep adding comma-separated key-value
      pairs.
    - Another way to initialize dictionaries is by using the dict keyword.
      Dict is a built-in function that allows you to enter a list of key and
      value pairs as tuples.
    - Finally you can create an empty dictionary and then add pairs using an
      assignment statement.
    - Simply write the name of the dictionary with the key in square brackets
      on the left hand side. Write the value on the right hand side.
    - I would suggest that you try all 3 ways to initialize a dictionary.
      Depending on the type of problem and how many value key pairs you will
      need to have you will prefer different methods.
    - My favorite method was definitely creating an empty dictionary and then
      using square brackets to name a key and give its associated values.
    - Another important thing to note is that a dictionary value can be of
      any type. This means that a value can be a string, a number, a list or
      even another dictionary! Any of the data types we covered are fair game
      to use in your dictionary as a value type. Same goes for keys. They
      just can't be mutable.
    
    - So if we can’t use indexing how can we access the values? A value can
      be retrieved from a dictionary by specifying its corresponding key in
      square brackets.
    - Because dictionaries are unordered, it does not matter in which order
      you input your data in a dictionary! You just need the key to access
      it.
    - If you refer to a key that is not in the dictionary, Python will give
      you an error. This type of error is specifically called a key error,
      since the key you are searching for does not exist.
    - One easy way to avoid this error from occurring is to use the in
      operator like you did in lists. 
    
    - Also as we mentioned dictionaries are mutable. You can change the
      value associated with a key and you can also add or remove key-value
      pairs. But you cannot change the keys!
    - I know we are talking about a lot of concepts that apply to different
      parts of a dictionary, so an easy tool to help you are memory diagrams.
      As we mentioned in the previous weeks memory diagrams are helpful
      visual tools and if you are stuck on how the key value pair
      relationship works I would suggest drawing memory diagrams of
      dictionaries. I always mention how helpful memory diagrams were for me
      and this is just another instance of that. 
    
    - So how can you add and delete pairs from a dictionary? Or how can you
      change the value to an existing key?
    - In order to add elements you can just use the assignment statement we
      mentioned where you write the name of the dictionary and use square
      brackets to name the key and then assign the value.
    - To delete an element you can use the pop method, which is the same
      method you use with lists.
    - If you want to overwrite the value for a key, you can use an assignment
      statement just like you did to add a new key-value pair. Simply use
      the same key and place the new value on the right-hand side.
    - Python also has a method called update that allows you to update it.
      You can use the update method by putting in the name of the dictionary
      and then putting dot update and using normal brackets. This method is
      useful to know but you will not be using it that much. 
    
    - We have been talking about methods that mutate or change a dictionary
      after you have created it. I also want to mention some methods that do
      not mutate the dictionary but are very useful as they return elements
      in the dictionary.
    - If you want to get all the keys, use the dot keys method which returns
      all the keys in that dictionary.
    - Another method is dot values which returns all the values in the
      dictionary.
    - These methods are extremely useful and I would urge you to memorize
      them as you will be using them throughout.
    - Another useful method is the get method. The get method is another way
      to access the values in a dictionary but prevents raising a KeyError if
      the key does not exist. The get method is not too important for this
      class but can be handy.
    - Finally the dot items method returns the key value pairs as tuples.
    - The three methods keys, values and items are really powerful and you
      will be using the latter two when you are iterating. 
    
    - The most limiting part of dictionaries can be the keys.
    - If you enter a key that does not exist you will get a key error, so as
      we mentioned before, always check if a key exists before you
      extensively use that key in your code.
    - Another limiting part of keys is that you can only
      have one key with a given name. Meaning that you can not have 2
      different keys with the same name. Each key must have a unique name.
      Therefore duplicate keys are not allowed. This is because a dictionary
      maps each key to a corresponding value, so it doesn't make sense to map
      a particular key more than once.
    - Also, the keys of a dictionary must be of a type that is immutable,
      because as you recall keys are immutable. So you can’t have a key that
      is a list because they are mutable. This means a key has to be an
      immutable type like integer, float, string or boolean, most types we
      are used to using are immutable so this shouldn't be a problem when
      you’re coding but just a detail that I want you to keep in mind! 
    
    - Up to now we have learned what a dictionary is, different ways to
      create a dictionary, how to use the key and value pair and why they are
      so powerful.
    - The last thing you will be doing with dictionaries is to iterate over
      them, generally with a for loop. You can iterate through keys
      separately or values separately or the key value pairs together.
    - When you are iterating through the keys in the dictionary you can
      iterate directly over the dictionary, you do not need to use the .keys
      method. An example snippet of code would be for key in my_dictionary.
      Again you don't need to use the .keys method.
    - However if you are iterating through values then you will need to use
      the .values method. An example of snippet code for this would look like
      for value in my_dictionary.values().
    - The same is true when you are iterating through items (the key and
      value pairs), where you have to specify in your code that you are
      iterating through items by using the .items method. 
    
    - Here we are at the end of another episode!
    - Today we talked about dictionaries, which is another very important
      data type in Python, alongside lists.
    - Lists and dictionaries are quite similar as they store a lot of data,
      but you access the data in them in very different ways.
    - Lists elements are accessed by indexing which is based on the order in
      which you enter the data, whereas dictionary elements are accessed by
      key that you associate with them, so they do not depend on the order in
      which you enter the data.
    - This is one of the greatest advantages of using a dictionary. But
      because of these differences in some instances
      it might be better to use a dictionary or a list and you should
      familiarize yourself with both so you know which one to choose. 
    
    - We also talked about methods that are very important and useful for
      dictionaries such as pop, update, values, keys and items so make sure
      you also know all of these! Make sure you memorize their names and
      their purposes.
    - Also make sure that you are comfortable creating dictionaries with the
      3 different methods we mentioned.
    - Thank you so much for listening this week! I hope you learned something
      new and I look forward to talking to you soon!