# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + [markdown] slideshow={"slide_type": "slide"}
# # Lecture 5: Loops
#
# CSCI 1360: Foundations for Informatics and Analytics

# + [markdown] slideshow={"slide_type": "slide"}
# ## Overview and Objectives
#
# In this lecture, we'll go over the basics of looping in Python. By the end of this lecture, you should be able to

# + [markdown] slideshow={"slide_type": "fragment"}
#  - Perform basic arithmetic operations using arbitrary-length collections
#  - Use "unpacking" as a shortcut for iterating through dictionaries
#  - Describe the differences between the separate kinds of loops

# + [markdown] slideshow={"slide_type": "slide"}
# ## Part 1: `for` Loops

# + [markdown] slideshow={"slide_type": "fragment"}
# Looping, like lists, is a critical component in programming and data science. When we're training models on data, we'll need to loop over each data point, examining it in turn and adjusting our model accordingly *regardless of how many data points there are*. This kind of repetitive task is ideal for looping.

# + [markdown] slideshow={"slide_type": "slide"}
# Let's define for ourselves the following list:

# + slideshow={"slide_type": "fragment"}
ages = [21, 22, 19, 19, 22, 21, 22, 31]

# + [markdown] slideshow={"slide_type": "fragment"}
# This is a list containing the ages of some group of students, and we want to compute the average. How do we compute averages?

# + [markdown] slideshow={"slide_type": "slide"}
# We know an average is some *total quantity* divided by *number of elements*. Well, the latter is easy enough to compute:

# + slideshow={"slide_type": "fragment"}
number_of_elements = len(ages)
print(number_of_elements)

# + [markdown] slideshow={"slide_type": "fragment"}
# The total quantity is a bit trickier. You could certainly sum them all manually--

# + slideshow={"slide_type": "fragment"}
age_sum = ages[0] + ages[1] + ages[2] # + ... and so on

# + [markdown] slideshow={"slide_type": "fragment"}
# ...but that seems really, really tedious. Plus, how do you even know how many elements your list has?

# + [markdown] slideshow={"slide_type": "slide"}
# ### Loop structure

# + [markdown] slideshow={"slide_type": "fragment"}
# The structure itself is pretty simple:

# + [markdown] slideshow={"slide_type": "fragment"}
#  - some collection of "things" to iterate over

# + [markdown] slideshow={"slide_type": "fragment"}
#  - a placeholder for the current "thing"

# + [markdown] slideshow={"slide_type": "fragment"}
#  - a chunk of code describing what to do with the current "thing"

# + [markdown] slideshow={"slide_type": "slide"}
# Let's start simple: looping through a list, printing out each item one at a time.

# + slideshow={"slide_type": "fragment"}
for N in [2, 5, 7, 9]:  # Header
    print(N)            # Body

# + [markdown] slideshow={"slide_type": "fragment"}
# There are two main parts to the loop: the **header** and the **body**.

# + [markdown] slideshow={"slide_type": "fragment"}
#  - The **header** contains 1) the collection we're iterating over (in this example, the list), and 2) the "placeholder" we're using to hold the current value (in this example, `N`).

# + [markdown] slideshow={"slide_type": "fragment"}
#  - The **body** is the chunk of code under the header (indented!) that executes on each iteration.

# + [markdown] slideshow={"slide_type": "slide"}
# Back, then, to computing an average:

# + slideshow={"slide_type": "fragment"}
age_sum = 0
ages = [21, 22, 19, 19, 22, 21, 22, 31]

for age in ages:
    age_sum += age

avg = age_sum / number_of_elements  # Compute the average using the formula we know and love!
print("Average age: {:.2f}".format(avg))

# + [markdown] slideshow={"slide_type": "slide"}
# You can loop through sets and tuples the same way.

# + slideshow={"slide_type": "fragment"}
s = set([1, 1, 2, 3, 5])
for item in s:
    print(item)

# + slideshow={"slide_type": "fragment"}
t = tuple([1, 1, 2, 3, 5])
for item in t:
    print(item)

# + [markdown] slideshow={"slide_type": "slide"}
# ### Iterators

# + [markdown] slideshow={"slide_type": "fragment"}
# The unifying theme with all these collections you can loop through is that they're all examples of *iterators*.

# + [markdown] slideshow={"slide_type": "fragment"}
# Easily the most common iterator you'll use (aside from lists, sets, and tuples) is the `range` function:

# + slideshow={"slide_type": "fragment"}
for i in range(10):
    print(i, end = " ")  # Prints everything on 1 line.

# + [markdown] slideshow={"slide_type": "fragment"}
# Note, again, that the range of numbers goes from 0 (inclusive) to the specified end (exclusive)! The critical point is that the argument to `range` specifies the *length* of the returned iterator.

# + [markdown] slideshow={"slide_type": "slide"}
# A few more examples of `range` before we get back to loops:

# + slideshow={"slide_type": "fragment"}
for i in range(5):  # One argument: specifies the "end"
    print(i, end = " ")

# + slideshow={"slide_type": "fragment"}
for i in range(5, 10):  # Two arguments: first is "start" (inclusive), second is "end" (exclusive)
    print(i, end = " ")

# + slideshow={"slide_type": "fragment"}
for i in range(0, 10, 2):  # Three arguments: start, end, and increment
    print(i, end = " ")

# + [markdown] slideshow={"slide_type": "slide"}
# **IMPORTANT: INDENTATION MATTERS**

# + [markdown] slideshow={"slide_type": "fragment"}
# You'll notice in these loops that the *loop body* is distinctly indented relative to the *loop header*. This is intentional and is indeed how it works! If you fail to indent the body of the loop, Python will complain:

# + slideshow={"slide_type": "fragment"}
some_list = [3.14159, "random stuff", 4200]
for item in some_list:
print(item)

# + [markdown] slideshow={"slide_type": "fragment"}
# With loops, whitespace in Python *really* starts to matter. If you want many things to happen inside of a loop, you'll need to indent every line!

# + [markdown] slideshow={"slide_type": "slide"}
# Let's say in some future homework assignment, I ask you to write a loop computing the squares of the numbers 1-10. How would you do it?

# + [markdown] slideshow={"slide_type": "fragment"}
# Well, you could manually write it out, I suppose...

# + slideshow={"slide_type": "fragment"}
squares = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# + [markdown] slideshow={"slide_type": "fragment"}
# ...but that's awfully boring.

# + [markdown] slideshow={"slide_type": "slide"}
# Instead, let's use the `range` function we were just discussing:

# + slideshow={"slide_type": "fragment"}
squares = []  # Empty list for all our squares

for num in range(10):
    squared_number = num ** 2  # Exponent operation!
    squares.append(squared_number)  # Add to our list.

print(squares)

# + [markdown] slideshow={"slide_type": "slide"}
# ### Looping through dictionaries

# + [markdown] slideshow={"slide_type": "fragment"}
# This gets its own subsection because it pulls together pretty much all the concepts we've discussed so far: lists, tuples, dictionaries, and looping.

# + [markdown] slideshow={"slide_type": "slide"}
# Let's start by defining a dictionary. In this case, we'll set up a dictionary that maps people to their favorite programming language.

# + slideshow={"slide_type": "fragment"}
favorite_languages = {
    'jen': 'python',
    'sarah': 'c',
    'edward': 'ruby',
    'shannon': 'python'
}
# Notice the indentation, if you decide to define a dictionary this way!

# + [markdown] slideshow={"slide_type": "slide"}
# Remember the super-useful methods for iterating through dictionaries? `keys` gives you a list of all the keys, `values` a list of all the values, and `items` a list of *tuples* of the key-value pairs. Here's the loop:

# + slideshow={"slide_type": "fragment"}
for key, value in favorite_languages.items():  # 1
    print("{} prefers {}.".format(key, value))

# + [markdown] slideshow={"slide_type": "fragment"}
# 1: Notice how `key, value` are just out there floating! This is called *unpacking* and is a very useful technique in Python. If I have a list of a few items, and (critically) *I know how many items there are*, I can do this

# + slideshow={"slide_type": "fragment"}
some_list = ['a', 'b']
a, b = some_list

# + [markdown] slideshow={"slide_type": "fragment"}
# instead of this

# + slideshow={"slide_type": "fragment"}
some_list = ['a', 'b']
a = some_list[0]
b = some_list[1]

# + [markdown] slideshow={"slide_type": "slide"}
# In the same vein, I could have just as easily written the loop like this:

# + slideshow={"slide_type": "fragment"}
for keyvalue in favorite_languages.items():  # 1
    key = keyvalue[0]
    value = keyvalue[1]
    print("{} prefers {}.".format(key, value)) # 2

# + [markdown] slideshow={"slide_type": "fragment"}
# and indeed, if that is easier for you to understand, by all means do it! This is to illustrate all the concepts at play at once:

# + [markdown] slideshow={"slide_type": "fragment"}
#  - the loop header iterates through a list provided by `favorite_languages.items()`
#  - each iteration, `items()` provides a tuple: a key-value pair from the dictionary
#  - we can "unpack" these variables using shorthand, but it's also perfectly valid to do it the "regular" way

# + [markdown] slideshow={"slide_type": "slide"}
# That's pretty much `for` loops!

# + [markdown] slideshow={"slide_type": "fragment"}
# What about the case where you don't know ahead of time how many iterations your loop will take?

# + [markdown] slideshow={"slide_type": "slide"}
# ## Part 2: `while` Loops

# + [markdown] slideshow={"slide_type": "fragment"}
# "While" loops go back yet again to the concept of boolean logic we introduced in an earlier lecture: loop until some condition is reached.

# + [markdown] slideshow={"slide_type": "fragment"}
# The structure here is a little different than `for` loops. Instead of explicitly looping over an iterator, you'll set some condition that evaluates to either `True` or `False`; as long as the condition is `True`, Python executes another loop.

# + slideshow={"slide_type": "fragment"}
x = 10

while x < 15:
    print(x, end = " ")
    x += 1

# + [markdown] slideshow={"slide_type": "fragment"}
# `x < 15` is a boolean statement: it is either `True` or `False`, depending on the value of `x`. Initially, this number is 10, which is certainly `< 15`, so the loop executes. 10 is printed, `x` is incremented, and the condition is checked again.

# + [markdown] slideshow={"slide_type": "slide"}
# A potential downside of `while` loops: **forgetting to update the condition inside the loop.**

# + [markdown] slideshow={"slide_type": "fragment"}
# It's easy to take for granted; `for` loops implicitly handle this for us!

# + slideshow={"slide_type": "fragment"}
for i in range(10, 15):
    print(i, end = " ")
    # No update needed!

# + [markdown] slideshow={"slide_type": "fragment"}
# Use `for` loops frequently enough, and when you occasionally use a `while` loop, you'll forget you need to update the loop condition.

# + [markdown] slideshow={"slide_type": "slide"}
# ## Review Questions

# + [markdown] slideshow={"slide_type": "fragment"}
# Some questions to discuss and consider:
#
# 1: Using the awful matrix construct of a "list of lists," show how you could write loops that double the value of each element of the matrix.
#
# 2: `for` and `while` loops may have different syntax and different use cases, but you can often translate the same task between the two types of loops. Show how you could use a `while` loop to iterate through a list of numbers from `range()`.
#
# 3: Let's say you have two lists, `K` and `V`, that are both the same length. Show how, using only 1 loop, you can loop through both of them simultaneously.
#
# 4: Now let's say your lists `K` and `V` are *not* the same length. Using 1 loop, iterate through them, stopping when you reach the end of the shorter list.

# + [markdown] slideshow={"slide_type": "slide"}
# ## Course Administrivia

# + [markdown] slideshow={"slide_type": "fragment"}
# **A1 is out!**

# + [markdown] slideshow={"slide_type": "fragment"}
# **Even though A0 isn't "graded", please submit by 11:59 tonight.**

# + [markdown] slideshow={"slide_type": "fragment"}
# **Next week: generators and comprehensions and `enumerate`, oh my!**

# + [markdown] slideshow={"slide_type": "slide"}
# ## Additional Resources
#
#  1. <NAME>. *Python Crash Course*. 2016. ISBN-13: 978-1593276034
#  2. <NAME>. *Data Science from Scratch*. 2015. ISBN-13: 978-1491901427

StarcoderdataJupyterScriptsDedupFiltered