2 + 3

2 * 3

5 - 11

5.0 - 11

5 / 11

6 / 3

5 // 11

6 // 3

"""
Explanation: Part 1.  Computing with Python 3.x
What is the difference between Python and a calculator?  We begin this first lesson by showing how Python can be used as a calculator, and we move into some of the basic programming language constructs:  data types, variables, lists, and loops.
This programming lesson complements Chapter 0 (Foundations) in An Illustrated Theory of Numbers.
Table of Contents

Python as a calculator
Calculating with booleans
Declaring variables
Ranges
Iterating over a range

<a id='calculator'></a>
Python as a calculator
Different kinds of data are stored as different types in Python.  For example, if you wish to work with integers, your data is typically stored as an int.  A real number might be stored as a float.  There are types for booleans (True/False data), strings (like "Hello World!"), and many more we will see.  
A more complete reference for Python's numerical types and arithmetic operations can be found in the official Python documentation.  The official Python tutorial is also a great place to start.
Python allows you to perform arithmetic operations:  addition, subtraction, multiplication, and division, on numerical types.  The operation symbols are +, -, *, and /.   Evaluate each of the following cells to see how Python performs operations on integers.  To evaluate the cell, click anywhere within the cell to select it (a selected cell will probably have a thick <span style="color:green">green</span> line on its left side) and use the keyboard shortcut Shift-Enter to evaluate.  As you go through this and later lessons, try to predict what will happen when you evaluate the cell before you hit Shift-Enter.
End of explanation
"""


-12 // 5

"""
Explanation: The results are probably not too surprising, though the last two require a bit of explanation.  Python interprets the input number 5 as an int (integer) and 5.0 as a float.  "Float" stands for "floating point number," which are decimal approximations to real numbers.  The word "float" refers to the fact that the decimal (or binary, for computers) point can float around (as in 1.2345 or 12.345 or 123.45 or 1234.5 or 0.00012345).  There are deep computational issues related to how computers handle decimal approximations, and you can read about the IEEE standards if you're interested.
Python enables different kinds of division.  The single-slash division in Python 3.x gives a floating point approximation of the quotient.  That's why 5 / 11 and 6 / 3 both output floats.  On the other hand, 5 // 11 and 6 // 3 yield integer outputs (rounding down) -- this is useful, but one has to be careful!
In fact the designers of Python changed their mind.  This tutorial assumes that you are using Python 3.x.  If you are using Python 2.x, the command 5 / 11 would output zero.
End of explanation
"""


(3 + 4) * 5

3 + (4 * 5)

3 + 4 * 5   #  What do you think will be the result?  Remember PEMDAS?

"""
Explanation: Why use integer division // and why use floating point division?  In practice, integer division is typically a faster operation.  So if you only need the rounded result (and that will often be the case), use integer division.  It will run much faster than carrying out floating point division then manually rounding down.
Observe that floating point operations involve approximation.  The result of 5.0/11.0 might not be what you expect in the last digit.  Over time, especially with repeated operations, floating point approximation errors can add up!
Python allows you to group expressions with parentheses, and follows the order of operations that you learn in school.
End of explanation
"""


#  An empty cell.  Have fun!


"""
Explanation: Now is a good time to try a few computations of your own, in the empty cell below.  You can type any Python commands you want in the empty cell.  If you want to insert a new cell into this notebook, it takes two steps:
1.  Click to the left of any existing cell.  This should make a <span style="color:blue">blue</span> bar appear to the left of the cell.
2.  Use the keyboard shortcut a to insert a new cell above the blue-selected cell or b to insert a new cell below the blue-selected cell.
You can also use the keyboard shortcut x do delete a blue-selected cell... be careful!
End of explanation
"""


23 // 5  # Integer division

23 % 5  # The remainder after division

"""
Explanation: For number theory, division with remainder is an operation of central importance.  Integer division provides the quotient, and the operation % provides the remainder.  It's a bit strange that the percent symbol is used for the remainder, but this dates at least to the early 1970s and has become standard across computer languages.
End of explanation
"""


divmod(23,5)

type(divmod(23,5))

"""
Explanation: Note in the code above, there are little "comments".  To place a short comment on a line of code, just put a hashtag # at the end of the line of code, followed by your comment.
Python gives a single command for division with remainder.  Its output is a tuple.
End of explanation
"""


type(3)

type(3.0)

type('Hello')

type([1,2,3])

"""
Explanation: All data in Python has a type, but a common complaint about Python is that types are a bit concealed "under the hood".  But they are not far under the hood.  Anyone can find out the type of some data with a single command.
End of explanation
"""


3 + 3

3.0 + 3.0

'Hello' + 'World!'

[1,2,3] + [4,5,6]

3 + 3.0

3 + 'Hello!'

#  An empty cell.  Have fun!


"""
Explanation: The key to careful computation in Python is always being aware of the type of your data, and knowing how Python operates differently on data of different types.
End of explanation
"""


3 * 'Hello!'

0 * 'Hello!'

2 * [1,2,3]

"""
Explanation: As you can see, addition (the + operator) is interpreted differently in the contexts of numbers, strings, and lists.  The designers of Python allowed us to add numbers of different types:  if you try to operate on an int and a float, the int will typically be coerced into a float in order to perform the operation.  But the designers of Python did not give meaning to the addition of a number with a string, for example.  That's why you probably received a TypeError after trying the above line. 
On the other hand, Python does interpret multiplication of a natural number with a string or a list.
End of explanation
"""


#  Practice cell


"""
Explanation: Can you create a string with 100 A's (like AAA...)?  Use an appropriate operation in the cell below.
End of explanation
"""


2**1000

2.0**1000

"""
Explanation: Exponents in Python are given by the ** operator.  The following lines compute 2 to the 1000th power, in two different ways.
End of explanation
"""


type(2**1000)

type(2.0**1000)

#  An empty cell.  Have fun!


"""
Explanation: As before, Python interprets an operation (**) differently in different contexts.  When given integer input, Python evaluates 2**1000 exactly.  The result is a large integer.  A nice fact about Python, for number theorists, is that it handles exact integers of arbitrary length!  Many other programming languages (like C++) will give an error message if integers get too large in the midst of a computation.  
New in version 3.x, Python implements long integers without giving signals to the programmer or changing types.  In Python 2.x, there were two types: int for somewhat small integers (e.g., up to $2^{31}$) and long type for all larger integers.  Python 2.x would signal which type of integer was being used, by placing the letter "L" at the end of a long integer.  Now, in Python 3.x, the programmer doesn't really see the difference.  There is only the int type.  But Python still optimizes computations, using hardware functionality for arithmetic of small integers and custom routines for large integers.  The programmer doesn't have to worry about it most of the time.
For scientific applications, one often wants to keep track of only a certain number of significant digits.  If one computes the floating point exponent 2.0**1000, the result is a decimal approximation.  It is still a float.  The expression "e+301" stands for "multiplied by 10 to the 301st power", i.e., Python uses scientific notation for large floats.
End of explanation
"""


3 > 2

type(3 > 2)

10 < 3

2.4 < 2.4000001

32 >= 32

32 >= 31

2 + 2 == 4

"""
Explanation: Now is a good time for reflection.  Double-click in the cell below to answer the given questions.  Cells like this one are used for text rather than Python code.  Text is entered using markdown, but you can typically just enter text as you would in any text editor without problems.  Press shift-Enter after editing a markdown cell to complete the editing process.  
Note that a dropdown menu in the toolbar above the notebook allows you to choose whether a cell is Markdown or Code (or a few other things), if you want to add or remove markdown/code cells.
Exercises


What data types have you seen, and what kinds of data are they used for?  Can you remember them without looking back?


How is division / interpreted differently for different types of data?


How is multiplication * interpreted differently for different types of data?


What is the difference between 100 and 100.0, for Python?


Double-click this markdown cell to edit it, and answer the exercises.
<a id='booleans'></a>
Calculating with booleans
A boolean (type bool) is the smallest possible piece of data.  While an int can be any integer, positive or negative, a boolean can only be one of two things:  True or False.  In this way, booleans are useful for storing the answers to yes/no questions.  
Questions about (in)equality of numbers are answered in Python by operations with numerical input and boolean output.  Here are some examples.  A more complete reference is in the official Python documentation.
End of explanation
"""


#  Write your code here.


"""
Explanation: Which number is bigger:  $23^{32}$ or $32^{23}$?  Use the cell below to answer the question!
End of explanation
"""


63 % 7 == 0  # Is 63 divisible by 7?

101 % 2 == 0  # Is 101 even?

"""
Explanation: The expressions &lt;, &gt;, &lt;=, &gt;= are interpreted here as operations with numerical input and boolean output.  The symbol == (two equal symbols!) gives a True result if the numbers are equal, and False if the numbers are not equal.  An extremely common typo is to confuse = with ==.  But the single equality symbol = has an entirely different meaning, as we shall see.
Using the remainder operator % and equality, we obtain a divisibility test.
End of explanation
"""


# Your code goes here.


"""
Explanation: Use the cell below to determine whether 1234567890 is divisible by 3.
End of explanation
"""


True and False

True or False

True or True

not True

"""
Explanation: Booleans can be operated on by the standard logical operations and, or, not.  In ordinary English usage, "and" and "or" are conjunctions, while here in Boolean algebra, "and" and "or" are operations with Boolean inputs and Boolean output.  The precise meanings of "and" and "or" are given by the following truth tables.
| and || True | False |
|-----||------|-------|
|||||
| True || True | False |
| False || False | False|
| or || True | False |
|-----||------|-------|
|||||
| True || True | True |
| False || True | False|
End of explanation
"""


(2 > 3) and (3 > 2)

(1 + 1 == 2) or (1 + 1 == 3)

not (-1 + 1 >= 0)

2 + 2 == 4

2 + 2 != 4  # For "not equal", Python uses the operation `!=`.

2 + 2 != 5  # Is 2+2 *not* equal to 5?

not (2 + 2 == 5)  # The same as above, but a bit longer to write.

"""
Explanation: Use the truth tables to predict the result (True or False) of each of the following, before evaluating the code.
End of explanation
"""


# Experiment here.


"""
Explanation: Experiment below to see how Python handles a double or triple negative, i.e., something with a not not.
End of explanation
"""


False * 100

True + 13

"""
Explanation: Python does give an interpretation to arithmetic operations with booleans and numbers.  Try to guess this interpretation with the following examples.  Change the examples to experiment!
End of explanation
"""


#  Use this space to work on the exercises


"""
Explanation: This ability of Python to interpret operations based on context is a mixed blessing.  On one hand, it leads to handy shortcuts -- quick ways of writing complicated programs.  On the other hand, it can lead to code that is harder to read, especially for a Python novice.  Good programmers aim for code that is easy to read, not just short!
The Zen of Python is a series of 20 aphorisms for Python programmers.  The first seven are below.

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.

Exercises


Did you look at the truth tables closely?  Can you remember, from memory, what True or False equals, or what True and False equals?  


How might you easily remember the truth tables?  How do they resemble the standard English usage of the words "and" and "or"?


If you wanted to know whether a number, like 2349872348723, is a multiple of 7 but not a multiple of 11, how might you write this in one line of Python code?


You can chain together and commands, e.g., with an expression like True and True and True (which would evaluate to True).  You can also group booleans, e.g., with True and (True or False).  Experiment to figure out the order of operations (and, or, not) for booleans.


The operation xor means "exclusive or".  Its truth table is: True xor True = False and False xor False = False and True xor False = True and False xor True = True.  How might you implement xor in terms of the usual and, or, and not?
End of explanation
"""


e = 2.71828

"""
Explanation: <a id='variables'></a>
Declaring variables
A central feature of programming is the declaration of variables.  When you declare a variable, you are storing data in the computer's memory and you are assigning a name to that data.  Both storage and name-assignment are carried out with the single equality symbol =.
End of explanation
"""


e * e

type(e)

"""
Explanation: With this command, the float 2.71828 is stored somewhere inside your computer, and Python can access this stored number by the name "e" thereafter.  So if you want to compute "e squared", a single command will do.
End of explanation
"""


my_number = 17

my_number < 23

"""
Explanation: You can use just about any name you want for a variable, but your name must start with a letter, must not contain spaces, and your name must not be an existing Python word.  Characters in a variable name can include letters (uppercase and lowercase) and numbers and underscores _.  
So e is a valid name for a variable, but type is a bad name.  It is very tempting for beginners to use very short abbreviation-style names for variables (like dx or vbn).  But resist that temptation and use more descriptive names for variables, like difference_x or very_big_number.  This will make your code readable by you and others!
There are different style conventions for variable names.  We use lowercase names, with underscores separating words,  roughly following Google's style conventions for Python code.
End of explanation
"""


my_number = 3.14

"""
Explanation: After you declare a variable, its value remains the same until it is changed.  You can change the value of a variable with a simple assignment.  After the above lines, the value of my_number is 17.
End of explanation
"""


S = 0
S = S + 1
S = S + 2
S = S + 3
print(S)

"""
Explanation: This command reassigns the value of my_number to 3.14.  Note that it changes the type too!  It effectively overrides the previous value and replaces it with the new value.
Often it is useful to change the value of a variable incrementally or recursively.  Python, like many programming languages, allows one to assign variables in a self-referential way.  What do you think the value of S will be after the following four lines?
End of explanation
"""


my_number = 17
new_number = my_number + 1
my_number = 3.14

"""
Explanation: The first line S = 0 is the initial declaration:  the value 0 is stored in memory, and the name S is assigned to this value.
The next line S = S + 1 looks like nonsense, as an algebraic sentence.  But reading = as assignment rather than equality, you should read the line S = S + 1 as assigning the value S + 1 to the name S.  When Python interprets S = S + 1, it carries out the following steps.

Compute the value of the right side, S+1.  (The value is 1, since S was assigned the value 0 in the previous line.)
Assign this value to the left side, S.  (Now S has the value 1.)

Well, this is a slight lie.  Python probably does something more efficient, when given the command S = S + 1, since such operations are hard-wired in the computer and the Python interpreter is smart enough to take the most efficient route.  But at this level, it is most useful to think of a self-referential assignment of the form X = expression(X) as a two step process as above.

Compute the value of expression(X).
Assign this value to X.

Now consider the following three commands.
End of explanation
"""


print(my_number)
print(new_number)

"""
Explanation: What are the values of the variables my_number and new_number, after the execution of these three lines?
To access these values, you can use the print function.
End of explanation
"""


#  Use this space to work on the exercises.


"""
Explanation: Python is an interpreted language, which means (roughly) that Python carries out commands line-by-line from top to bottom.  So consider the three lines
python
my_number = 17
new_number = my_number + 1
my_number = 3.14
Line 1 sets the value of my_number to 17.  Line 2 sets the value of new_number to 18.  Line 3 sets the value of my_number to 3.14.  But Line 3 does not change the value of new_number at all.
(This will become confusing and complicated later, as we study mutable and immutable types.)
Exercises


What is the difference between = and == in the Python language?


If the variable x has value 3, and you then evaluate the Python command x = x * x, what will be the value of x after evaluation?


Imagine you have two variables a and b, and you want to switch their values.  How could you do this in Python?
End of explanation
"""


type([1,2,3])

type(['Hello',17])

"""
Explanation: <a id='ranges'></a>
Lists and ranges
Python stands out for the central role played by lists.  A list is what it sounds like -- a list of data.  Data within a list can be of any type.  Multiple types are possible within the same list!  The basic syntax for a list is to use brackets to enclose the list items and commas to separate the list items.
End of explanation
"""


type((1,2,3))

"""
Explanation: There is another type called a tuple that we will rarely use.  Tuples use parentheses for enclosure instead of brackets.
End of explanation
"""


type(range(10)) # Ranges are their own type, in Python 3.x.  Not in Python 2.x!

list(range(10)) # Let's see what's in the range.  Note it starts at zero!  Where does it stop?

"""
Explanation: There's another list-like type in Python 3, called the range type.  Ranges are kind of like lists, but instead of plunking every item into a slot of memory, ranges just have to remember three integers:  their start, their stop, and their step.    
The range command creates a range with a given start, stop, and step.  If you only input one number, the range will start at zero and use steps of one and will stop just before the given stop-number.
One can create a list from a range (plunking every term in the range into a slot of memory), by using the list command.  Here are a few examples.
End of explanation
"""


list(range(3,10))

list(range(-4,5))

"""
Explanation: A more complicated two-input form of the range command produces a range of integers starting at a given number, and terminating before another given number.
End of explanation
"""


len([2,4,6])

len(range(10))  # The len command can deal with lists and ranges.  No need to convert.

len(range(10,100)) # Can you figure out the length, before evaluating?

"""
Explanation: This is a common source of difficulty for Python beginners.  While the first parameter (-4) is the starting point of the list, the list ends just before the second parameter (5).  This takes some getting used to, but experienced Python programmers grow to like this convention.
The length of a list can be accessed by the len command.
End of explanation
"""


list(range(1,10,2))

list(range(11,30,2))

list(range(-4,5,3))

list(range(10,100,17))

"""
Explanation: The final variant of the range command (for now) is the three-parameter command of the form range(a,b,s).  This produces a list like range(a,b), but with a "step size" of s.  In other words, it produces a list of integers, beginning at a, increasing by s from one entry to the next, and going up to (but not including) b.  It is best to experiment a bit to get the feel for it!
End of explanation
"""


list(range(10,0,-1))

"""
Explanation: This can be used for descending ranges too, and observe that the final number b in range(a,b,s) is not included.
End of explanation
"""


list(range(10,100,7))  # What list will this create?  It won't answer the question...

list(range(14,100,7))  # Starting at 14 gives the multiples of 7.

len(range(14,100,7))  # Gives the length of the list, and answers the question!

"""
Explanation: How many multiples of 7 are between 10 and 100?  We can find out pretty quickly with the range command and the len command (to count).
End of explanation
"""


#  Use this space to work on the exercises.


"""
Explanation: Exercises


If a and b are integers, what is the length of range(a,b)?


Use a list and range command to produce the list [1,2,3,4,5,6,7,8,9,10].


Create the list [1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5] with a single list and range command and another operation.


How many multiples of 3 are there between 300 and 3000?
End of explanation
"""


for n in [1,2,3,4,5]:
    print(n*n)

for s in ['I','Am','Python']:
    print(s + "!")

"""
Explanation: <a id='iterating'></a>
Iterating over a range
Computers are excellent at repetitive reliable tasks.  If we wish to perform a similar computation, many times over, a computer a great tool.  Here we look at a common and simple way to carry out a repetetive computation:  the "for loop".  The "for loop" iterates through items in a list or range, carrying out some action for each item.  Two examples will illustrate.
End of explanation
"""


n = 1
print(n*n)
n = 2
print(n*n)
n = 3
print(n*n)
n = 4
print(n*n)
n = 5
print(n*n)

"""
Explanation: The first loop, unraveled, carries out the following sequence of commands.
End of explanation
"""


P = 1
for n in range(1,6):
    P = P * n
print(P)

"""
Explanation: But the "for loop" is more efficient and more readable to programmers.  Indeed, it saves the repetition of writing the same command print n*n over and over again.  It also makes transparent, from the beginning, the range of values that n is assigned to.  
When you read and write "for loops", you should consider how they look unravelled -- that is how Python will carry out the loop.  And when you find yourself faced with a repetetive task, you might consider whether it may be wrapped up in a for loop.
Try to unravel the loop below, and predict the result, before evaluating the code.
End of explanation
"""


P = 1
for n in range(1,6):
    P = P * n
    print("n is",n,"and P is",P)
print(P)

"""
Explanation: This might have been difficult!  So what if you want to trace through the loop, as it goes?  Sometimes, especially when debugging, it's useful to inspect every step of the loop to see what Python is doing.  We can inspect the loop above, by inserting a print command within the scope of the loop.
End of explanation
"""


print("My favorite number is",17)

"""
Explanation: Here we have used the print command with strings and numbers together.  In Python 3.x, you can print multiple things on the same line by separating them by commas.  The "things" can be strings (enclosed by single or double-quotes) and numbers (int, float, etc.).
End of explanation
"""


P = 1
n = 1
P = P * n
print("n is",n,"and P is",P)
n = 2
P = P * n
print("n is",n,"and P is",P)
n = 3
P = P * n
print("n is",n,"and P is",P)
n = 4
P = P * n
print("n is",n,"and P is",P)
n = 5
P = P * n
print("n is",n,"and P is",P)
print (P)

"""
Explanation: If we unravel the loop above, the linear sequence of commands interpreted by Python is the following.
End of explanation
"""


P = 1
for n in range(1,6):
    P = P * n  # this command is in the scope of the loop.
    print("n is",n,"and P is",P)  # this command is in the scope of the loop too!
print(P)

"""
Explanation: Let's analyze the loop syntax in more detail.
python
P = 1
for n in range(1,6):
    P = P * n  # this command is in the scope of the loop.
    print("n is",n,"and P is",P)  # this command is in the scope of the loop too!
print(P)
The "for" command ends with a colon :, and the next two lines are indented.  The colon and indentation are indicators of scope.  The scope of the for loop begins after the colon, and includes all indented lines.  The scope of the for loop is what is repeated in every step of the loop (in addition to the reassignment of n).
End of explanation
"""


P = 1
for n in range(1,6):
    P = P * n
print("n is",n,"and P is",P)
print(P)

"""
Explanation: If we change the indentation, it changes the scope of the for loop.  Predict what the following loop will do, by unraveling, before evaluating it.
End of explanation
"""


for x in [1,2,3]:
    for y in ['a', 'b']:
        print(x,y)

"""
Explanation: Scopes can be nested by nesting indentation.  What do you think the following loop will do?  Can you unravel it?
End of explanation
"""


# Insert your loop here.

"""
Explanation: How might you create a nested loop which prints 1 a then 2 a then 3 a then 1 b then 2 b then 3 b?  Try it below.
End of explanation
"""


