I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
This data looks more Gaussian than exponential. If we wanted to, we could use polyfit for this as well, but let's use the curve_fit function from Scipy, which can fit to arbitrary functions. You can learn more using help(curve_fit).
First define a general Gaussian function to fit to.
Can you write Python code for it?

def gauss(x,A,a): return A*np.exp(a*x**2)


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
This is a simple formatting structure that will satisfy most of your string formatting needs. More information on different format symbols is available in the string formatting part of the standard docs.
It's worth noting that more complicated string formatting methods are in development, but I prefer this system due to its simplicity and its similarity to C formatting strings.
Recall we discussed multiline strings. We can put format characters in these as well, and fill them with the percent sign as before.
Can you write Python code for it?

form_letter = """\

          %s

Dear %s,

We regret to inform you that your product did not
ship today due to %s.

We hope to remedy this as soon as possible.

          From,
          Your Supplier
"""

print(form_letter % ("July 1, 2016","Valued Customer Bob","alien attack"))


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
This is a very bad geometry for a water molecule, and it would be silly to run so many geometry optimizations of structures that are guaranteed to converge to the same single geometry, but you get the idea of how you can run vast numbers of simulations with a technique like this.
We used the enumerate function to loop over both the indices and the items of a sequence, which is valuable when you want a clean way of getting both. enumerate is roughly equivalent to:
Can you write Python code for it?

def my_enumerate(seq):
    l = []
    for i in range(len(seq)):
        l.append((i,seq[i]))
    return l
my_enumerate(oxygen_xy_coords)


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
This is clearly what we want.
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
This is only occasionally useful. Slightly more useful is the slicing operation, which you can also use on any sequence. We already know that we can use indexing to get the first element of a list:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
This is something of an advanced topic, but ordinary data types have boolean values associated with them, and, indeed, in early versions of Python there was not a separate boolean object. Essentially, anything that was a 0 value (the integer or floating point 0, an empty string "", or an empty list []) was False, and everything else was true. You can see the boolean value of any data object using the bool() function.
Can you write Python code for it?

bool(1)

bool(0)

bool(["This "," is "," a "," list"])


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
This looks okay, 0.05 seconds isn't a huge amount of time, but looking at the profiling shows that the append function is taking almost 20% of the time. Can we do better? Let's try a list comprehension.
Can you write Python code for it?

def evens2(n):
    "Return a list of even numbers below n"
    return [x for x in range(n) if x % 2 == 0]

import cProfile
cProfile.run('evens2(100000)')


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
To build matrices, you can either use the array command with lists of lists:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
To get matrix multiplication, you need the dot command:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
Tuples are like lists, in that you can access the elements using indices:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
Tuples are useful anytime you want to group different pieces of data together in an object, but don't want to create a full-fledged class (see below) for them. For example, let's say you want the Cartesian coordinates of some objects in your program. Tuples are a good way to do this:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
We can add an optional argument by specifying a default value in the argument list:
Can you write Python code for it?

def my_linspace(start,end,npoints = 50):
    v = []
    d = (end-start)/float(npoints-1)
    for i in range(npoints):
        v.append(start + i*d)
    return v


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
We can check the distribution by using the histogram feature, as shown on the help page for numpy.random.normal:
Can you write Python code for it?

count, bins, ignored = plt.hist(s, 30, normed=True)

plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
             np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
             linewidth=2, color='r')


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
We can do boolean tests on lists as well:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
We can now call fibonacci() for different sequence_lengths:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
We could also write this as:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
We need to add these results at each step to a list:
Can you write Python code for it?

data = []
for line in csv.splitlines()[2:]:
    words = line.split(',')
    data.append(list(map(float,words)))
data = np.array(data)
data


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
We'll find this useful later on.
2.4 Matrix Solvers
You can solve systems of linear equations using the solve command in the linear algebra toolbox of the numpy library:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
We've introduced a several new features here. First, note that the function itself is defined as a code block (a colon followed by an indented block). This is the standard way that Python delimits things. Next, note that the first line of the function is a single string. This is called a docstring, and is a special kind of comment that is often available to people using the function through the python command line:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
What does split() do?
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
What the keyword argument construction does is to take any additional keyword arguments (i.e. arguments specified by name, like "endpoint=False"), and stick them into a dictionary called "kwargs" (you can call it anything you like, but it has to be preceded by two stars). You can then grab items out of the dictionary using the get command, which also lets you specify a default value. I realize it takes a little getting used to, but it is a common construction in Python code, and you should be able to recognize it.
There's an analogous *args that dumps any additional arguments into a list called "args". Think about the range function: it can take one (the endpoint), two (starting and ending points), or three (starting, ending, and step) arguments. How would we define this?
Can you write Python code for it?

def my_range(*args):
    start = 0
    step = 1
    if len(args) == 1:
        end = args[0]
    elif len(args) == 2:
        start,end = args
    elif len(args) == 3:
        start,end,step = args
    else:
        raise Exception("Unable to parse arguments")
    v = []
    value = start
    while True:
        v.append(value)
        value += step
        if value > end: break
    return v


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
Why is 1 not the same as 1.0? Different data type. You can check the data type:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
You can access members of the list using the index of that item:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
You can add additional items to the list using the .append() command:
Can you write Python code for it?

languages = ["Fortran","C","C++"]
languages.append("Python")
print(languages)


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
You can also form empty (zero) matrices of arbitrary shape (including vectors, which Numpy treats as vectors with one row), using the zeros command:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
You can also pass in keywords to exclude the endpoint:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
You can name a variable almost anything you want. It needs to start with an alphabetical character or "_", can contain alphanumeric charcters plus underscores ("_"). Certain words, however, are reserved for the language:
and, as, assert, break, class, continue, def, del, elif, else, except,
exec, finally, for, from, global, if, import, in, is, lambda, not, or,
pass, print, raise, return, try, while, with, yield

Trying to define a variable using one of these will result in a syntax error:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
You can use + to concatenate multiple strings in a single statement:
Can you write Python code for it?

print( "This " + "is " + "a " + "longer " + "statement.")


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
and we have a bunch of methods we can use on the object:
Can you write Python code for it?

mystring.split()

mystring.startswith('Hi')

len(mystring)


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
as well as a ones command.
2.2 Linspace, matrix functions, and plotting
The linspace command makes a linear array of points from a starting to an ending value.
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
as well as matrix-vector products.
There are determinant, inverse, and transpose functions that act as you would suppose. Transpose can be abbreviated with ".T" at the end of a matrix object:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
as well as when you add two matrices together. (However, the matrices have to be the same shape.)
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
dot can also do dot products (duh!):
Can you write Python code for it?

v = np.array([3,4],'d')
np.sqrt(np.dot(v,v))


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
lambda is particularly convenient (as we'll see below) in passing simple functions as arguments to other functions.
map is a way to repeatedly apply a function to a list:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
linspace is an easy way to make coordinates for plotting. Functions in the numpy library (all of which are imported into IPython notebook) can act on an entire vector (or even a matrix) of points at once. Thus,
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
or double quotes
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
or simply
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
print even converts some arguments to strings for us:
Can you write Python code for it?

a,b,c = 1,2,3
print("The variables are ",1,2,3)


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
reduce is a way to repeatedly apply a function to the first two items of the list. There already is a sum function in Python that is a reduction:
Can you write Python code for it?


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
the factorial function we wrote is about a factor of 10 slower. This is because the built-in factorial function is written in C code and called from Python, and the version we wrote is written in plain old Python. A Python program has a lot of stuff in it that make it nice to interact with, but all that friendliness slows down the code. In contrast, the C code is less friendly but more efficient. If you want speed with as little effort as possible, write your code in an easy to program language like Python, but dump the slow parts into a faster language like C, and call it from Python. We'll go through some tricks to do this in this section.
4.2 Profiling
Profiling complements what timeit does by splitting the overall timing into the time spent in each function. It can give us a better understanding of what our program is really spending its time on.
Suppose we want to create a list of even numbers. Our first effort yields this:
Can you write Python code for it?

def evens(n):
    "Return a list of even numbers below n"
    l = []
    for x in range(n):
        if x % 2 == 0:
            l.append(x)
    return l


I am working on the file "@Crash+Course+v0.63.ipynb".
The first task is:
which is somewhat logically consistent with negative indices accessing the last elements of the list.
You can do:
Can you write Python code for it?

workdays = days_of_the_week[1:5]
print(workdays)


I am working on the file "A Beginners Guide to Python/04. Comments.ipynb".
The first task is:
In later lectures I'll explain how multiplying strings work. But for now just notice that in the first case you didn't know what was going on (because there were no helpful comments), but you understand what is happening in the second case because the comment explains the code.
Also as a quick heads up, generally speaking, if you feel you need to write comments explaining the code then this often a sign that your code isn't actually good code. Good code usually has the property that you can just tell what it is doing just by looking at it. For example:
Can you write Python code for it?

print( 4 * 2 ) # Very simply and clear code, you can tell what it does just by looking at it.
print( int(chr(52)).__mul__(int(chr(50))) ) # A TERRIBLE and confusing way to calculate "4 * 2"


I am working on the file "A Beginners Guide to Python/05. Variables & Assignment.ipynb".
The first task is:
'a += b' functions EXACTLY the same as 'a = a + b', the former is simply a short-cut that means you can do less typing, which can be useful if something has a long_variable_name.
Can you write Python code for it?

a = 5
a2 = 5

a += 5
a2 = a2 + 5

print(a, a2) # <--- They are both the same, see!?

# "updating" works for strings too:
s = "hello"
s += " "
s += "world"
print(s)


I am working on the file "A Beginners Guide to Python/05. Variables & Assignment.ipynb".
The first task is:
Don't confuse assignment with 'Equals'
In Python, assignment works left-to-right, unlike the equals symbol in mathematics assignment doesn't work both ways.
In other words:
a = b is not the same as b = a
Basically, the left-hand-side of the "=" symbol is the variable name, on the right-hand-side is the value.
Let me show you quickly...
Can you write Python code for it?

a = 4
a = b # throws a NameError, b is not defined!

# So what should we do if we want b to equal a?  Well, we would have to write:
a = 4
b = a

# and now both a and b should equal 4
print(a)
print(b)

# and lastly, a and b are both numbers, so  a + b is that same as 4 + 4 (more on numbers later).
print(4 + 4)
print(a + b)


I am working on the file "A Beginners Guide to Python/05. Variables & Assignment.ipynb".
The first task is:
In the example above we set 'a' to the value 5. In the next line we say assign 'a' to the value ('a' + 5). And since 'a' is 5 Python interprets this as 5 + 5, which is of course 10. Here is another example:
Can you write Python code for it?

a = 5
b = 5
a = a + b  #  5 + 5 = 10  ('a' therefore now equals 10, not 5)
a = a + b  # 10 + 5 = 15

print(a)


I am working on the file "A Beginners Guide to Python/05. Variables & Assignment.ipynb".
The first task is:
Incrementing Variables
Before we move onto the homework I wanted to make a quick little note about "updating" variables.
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/05. Variables & Assignment.ipynb".
The first task is:
QUESTION: Do you have any guesses as to why a name needs to be unique?

Well, if we have two variables called "x" and they have different values how is Python supposed to know what "x" you actually want? Remember the computer is stupid, it cannot figure out what you 'obviously' meant.
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/05. Variables & Assignment.ipynb".
The first task is:
So what is going on here? Well, Python first defines ‘x’ as equal to 4. In the very next line Python says "oh, x is equal to 5 now", and it "forgets" the old value of 4. In short, there never two versions of  ‘x’ here. Rather, there is one ‘x’ and it used to equal 4 and now it equals 5.
So basically, if you reuse names you will lose your data. And thats not a weird bug or quirk; Python was designed to be like this.

QUESTION: What happens if we use quotation marks, around the "the_number_four"?
Can you write Python code for it?

the_number_four = 4
print("theNumberFour")


I am working on the file "A Beginners Guide to Python/05. Variables & Assignment.ipynb".
The first task is:
The final point I'd like on this is that since this concept is super-useful the designers of Python thought it would be a good idea to be able to do this sort of calculation by using just two characters:
Can you write Python code for it?

long_variable_name = 10

long_variable_name = long_variable_name + 10
# is the same as:
long_variable_name += 10


I am working on the file "A Beginners Guide to Python/05. Variables & Assignment.ipynb".
The first task is:
You may recall in the very first lecture we got a name error when we tried to all Print() when we actually meant print(). We got a name error in this case too, but the cause it is a bit different.  What happened here is that Python executes code sequentially line by line, thus line 1 is executed before line 2 is and so on.
So let’s say on line 10 of some peice of code we have a variable called “Y”. Python will then check lines 1, 2, 3, .., 8, 9, 10 to see if “Y” is defined. If it isn’t Python throws a 'NameError'.
How do we fix this error? Well, in this case we just need to define ‘the number 5’ before we try to print it, like so
Can you write Python code for it?

the_number_five = 5
print(the_number_five)


I am working on the file "A Beginners Guide to Python/06. Variable Names.ipynb".
The first task is:
All we did here is change our variable name to be a bit more descriptive. And now since our variable name states that this figure includes tax we don't need the comment!

Show, don't tell.

The point of today was to make you aware that programming is not just about writing code that machines can understand; Good code is understood by both man AND machine.
Homework
Below is three lines of python code, the only thing you have seen yet is the '*' symbol which means multiplication. The Syntax:
{number}*{number}

Your task comes in three parts:

Figure out what the code is supposed to do. (this might take some googling. Hint: think circles!)
Add a comment or two, explaining what it does.
change the name "belt_size" and "cake" to something more meaningful.
After you have renamed the variables, do you still need the comments in order to understand the code?
Can you write Python code for it?

# WTF does this do? Place your bets now...
cake = 3.14
diameter = 2

belt_size = cake * diameter


I am working on the file "A Beginners Guide to Python/06. Variable Names.ipynb".
The first task is:
As for using underscores and captial letters in names, well, thats to do with classes, which I do not cover in this lecture series. All you need to know right now is that underscores and capital letters are best avoided.
Anyway, here are a few more examples:
Can you write Python code for it?

# superbad (nonsense name, mixed case, starts with underscore):
_QWeRTy = 10.20
# bad (still nonsense):
qwerty = 10.20
# poor (descriptive, but mixed case is very annoying):
SaLestAX = 10.20
# better (most boxes ticked!):
salestax = 10.20
# even better:
sales_tax = 10.20
# WAAAYYYY over the top (descriptive doesn't mean "write a novel!")
this_is_the_sales_tax_for_the_beer_I_bought_on_a_summers_day_in_1965_or_was_it_1967_I_cant_remember = 10.20


I am working on the file "A Beginners Guide to Python/06. Variable Names.ipynb".
The first task is:
So this bit of code isn't actually that bad; the variable name "price" is a reasonably good one (short, readable, descriptive) and the comment is helpful. All in all, this code is decent, but that doesn’t mean we cannot do even better:
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/07.2. Philosophy, Part Two.ipynb".
The first task is:
Literally this function takes two objects and returns mass * acceleration; But what does that actually mean? And why should we care?
Well, the meaning of this code is implicit, the author just assumes you are going to understand what it does and how to use it. The premise of today's lecture however is that this code can be rewritten to be more explicit, and being explicit is generally to be prefered.
To be more precise, the code make two implicit assumptions:

The end user has a basic understanding of Physics.
The end user knows to pass it numbers.

Regarding the second point above I'm not going to explain why this is a problem today (for that explanation, see lecture on Operator Overloading), but I will propose a few possible fixes. But first things first, lets talk physics!
To People with a modest background in Physics it is pretty obvious all we are doing is taking the formula for force, f=ma,
and putting it in code form.
But to understand the problem with the code, we need to imagine talking to someone that NEVER took a physics lesson at school before. For this person, they understand that we are multiplying two numbers (i.e. Mass, Acceleration) but they have not concept of what the result actually means. In short, the code only returns force if you know the physics, for everyone else the function return simply returns two numbers multiplied together.
In short, the code is being implicit but the poem tells us to be explicit. Alright, let's try and fix that now...
Can you write Python code for it?

def net_force(mass, acceleration):
    force = mass * acceleration
    return force

print(net_force(10,10))


I am working on the file "A Beginners Guide to Python/07.2. Philosophy, Part Two.ipynb".
The first task is:
This output might surprise you (and I'll explain why this happens in later lectures). Notice that the function net_force says it expects numbers. We passed in a string and an integer. And so, if we are using the function in an unexpected/unintended way should we surprised when things do not work as we expect?
So docstrings/comments can avoid bugs because they increase the chances that somebody will use some piece of code as it was designed to be used. If only I read the documentation, I would have known to not pass in a string!
Okay, so that explains why we should leave text for other developers to read, but why are Docstrings better than comments?
Well, the main reason is that docstrings make for better documentation. For example:
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/07.2. Philosophy, Part Two.ipynb".
The first task is:
we can use the "help" command to get information about a python objects. As a implementation detail, "help" will return the docstring of the function. Thus, if we use docstrings then other developers can call 'help' on our functions to find out what they do. Cool right?
Can you write Python code for it?

def add_eight_1(x):
    """
    Takes a integer x, and returns x + 8
    """
    return x + 8

help(add_eight_1)

def add_eight_2(x):
    # Takes a integer x, and returns x + 8
    return x + 8

help(add_eight_2)


I am working on the file "A Beginners Guide to Python/08. Calling Functions.ipynb".
The first task is:
4. Functions with optional arguments...
If a particular function argument is optional is will have two parts, a 'keyword' and a 'default value'. The syntax:
{keyword} = {value}

The 'default value' is what happens when we don't change anything, and obviously that is the bit we may want to change.
If you do give a function an optional argument make sure it is the last argument you pass the function; if you don't Python with throw and error. Anyway, here's the syntax:
{function name} ({argument}, {argument2}, {keyword} = {value} )

As a matter of fact, the print function we have been calling has an optional argument, this argument is called "sep" and its default value is a single 'space' character.
Let's look at the print calls above a bit more closely, did you notice that when we gave print several arguments it separated everything with a space? Well, we can change that behaviour by changing 'sep'.
For example...
Can you write Python code for it?

print("a", "b", "c", "d", "e", "f", "g")            # Notice 'sep' is not defined here. Thus the default value " " is used.
print("a", "b", "c", "d", "e", "f", "g", sep="")    # no spaces 'abc'
print("a", "b", "c", "d", "e", "f", "g", sep="__")  # double underscore, 'a__b__c'
print("a", "b", "c", "d", "e", "f", "g", sep=",")   # comma, 'a,b,c'
print("a", "b", "c", "d", "e", "f", "g", sep="\n")  # \n = New-line, please see string lecture!

print(sep="", "a", "b", "c") # Keywords must be at the end!


I am working on the file "A Beginners Guide to Python/08. Calling Functions.ipynb".
The first task is:
What happens when you give such a function too few/too many arguments? Well, you get an error:
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/09.1 Numbers, Part One.ipynb".
The first task is:
Dividing by Zero
Dividing by zero is something you are not allowed to do in mathmatics, Python is no different. If you try and divide by zero Python will simply throw and error. If you try to modulo by zero or use integer divsion you will get the same error.
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/09.1 Numbers, Part One.ipynb".
The first task is:
Floats
What are floating-point numbers?
Well, they are a bit like decimal numbers in Math (Warning! this is an over-simplification, in numbers part 2 I will go into more depth).
Just like Integers there are two main ways to create them:

Just type a floating point number, e.g 3.14
Or we can use the 'float' built-in method

Floats have more or less the same methods as integers do (e.g +-/*%//). Similarly the float() function works remarkably similar to the int function.
Can you write Python code for it?

string_number = "10.66"
print(float(string_number))

print(float(-3.2e20)) # < -- Python supports scientific notation...
print(float('inf'))   # < -- Python also has a way of representing infinity
print(float(0), float(10), float(5), sep="\n") # notice that when 'float' gets an integer as input '.0' is added to the number


I am working on the file "A Beginners Guide to Python/09.1 Numbers, Part One.ipynb".
The first task is:
Integer Divsion
Integer division returns the whole number part of  A / B.  for example, 10 // 6 is 1 because 6 fits into 10 exactly once (12 // 6 is of course 2).
So modulo in conjunction with integer division can get you both the whole part of A/B and the remainder of A/B
10 // 6  = 1
10 % 6   = 4
10 / 6   = 1.6666666666666667

Combine these two operations and you can make a nice clock:
Can you write Python code for it?

def time(time):
    """time measured in total elapsed minutes, function converts to days, hours, mins"""
    t = time // 60
    days  = t // 24 # 24 hours in a day
    hours = t %  24 # after working out number of days, get leftover hours
    mins =  time % 60
    return "Input in mins = '{}' which is: {} days {} hours and {} mins".format(time, days, hours, str(mins).zfill(2))

print(time(1))
print(time(60))
print(time(61))
print(time(960))
print(time(1440))
print(time(1447))
print(time(33456456))


I am working on the file "A Beginners Guide to Python/09.1 Numbers, Part One.ipynb".
The first task is:
Integer Methods
If we have two integer numbers (lets say A and B), then you probably won't be suprised to hear that we can add, subtract, and multiply those numbers together. The symbols for those commands are as follows:
Can you write Python code for it?

a = 10
b = 5

print(a +  b)  # addition
print(a -  b)  # subtraction
print(a *  b)  # multiplication
print(a ** b)  # exponentation
print(a %  b)  # modular arithmetic
print(a /  b)  # divsion (note, returns a float! Also, Python2 and Python3 act differently here, so beware!)
print(a // b)  # integer division

# For negative numbers, just add a "-" symbol before the number, for example:
print(-a, a)


I am working on the file "A Beginners Guide to Python/09.1 Numbers, Part One.ipynb".
The first task is:
Modular Arithmetic
Modular Arithmetic is something you can learn more about here. I’m going to skip most of the math and just show you a few common uses:

Work out if B is a divisor of A  (If not, we get the remainder)
Creating numerical cycles (eg, 121212121...)
Grabbing the last n digits of a number.

divisors...
A % B gives the remainder of A / B. Thus if A % B is 0 then that means the B must be a divisor of A. Ergo if x % 2 equals zero then that means x is an even number.
'cycles'...
As for cycles, an obvious use for this is stuff like measuring time, a clocks hand resets after hitting 60:
58 % 60 = 58
59 % 60 = 59
60 % 60 = 0    ←- count ‘resets’
61 % 60 = 1
62 % 60 = 2

Once we hit 61 secs then we would prefer to set the secs timer back to zero and update minutes. thus 61 secs becomes 1min 1sec
getting n last digits...
And finally, you can get last digits of a number by using x % (power of 10). A load of examples are printed in the console below.
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/09.1 Numbers, Part One.ipynb".
The first task is:
Order of Operations
The order of operations (sorted by highest-to-lowerest precidence) in Python is the following:

Parentheses
Exponentiation
Multiplication
Division
Addition
Subtraction

A few examples...
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/09.1 Numbers, Part One.ipynb".
The first task is:
So thats some basic mathmatics for you. Python does have support for fractions, complex numbers and a bunch of other stuff as well. But if you want that functionality you have to go hunting for it.
For example, suppose I want the log(x) or the sqrt(x) what can I do?
Can you write Python code for it?

from math import log2, sqrt

print(sqrt(100))
## Alternatively, you can also get the square root of a number by raising it to the 0.5 power
print(100**0.5)

print(log2(100))


I am working on the file "A Beginners Guide to Python/10. Strings.ipynb".
The first task is:
A Few More Operations...
Strings are a huge topic in Python, and we are going to have come back to them latter. But for now, let me leave you with a few basic operations you can perform on strings...
Can you write Python code for it?

# Repeating strings

#     {string} * {integer}

# Examples:

print("a" * 10)
print("abc" * 3)

# Concatenation

#    {string} + {string}

# Examples:

print("ab" + "c")
print("a" + "b" + "c")

# Membership

#   {string} in {string}

# Examples:

print("a" in "ab")
print("a" in "cb")
print("abc" in "aabbcc")  # must be an exact match.


I am working on the file "A Beginners Guide to Python/10. Strings.ipynb".
The first task is:
In short, \n is a newline, and \t is tab. Thus, if we are trying to save/open files/folders on windows systems that start with t or n we can end up having some difficulties.
There are a few solutions to this problem. If you are dealing with directories specificially then the best choice is to you the os module. This module will fix a number of these issues for you (the os module works on linux and windows machines).
for example:
Can you write Python code for it?

import os

directory = "C:\Documents\pictures\selfies"
photo_name = "santa_hat2.jpeg"

## the bad way
path_to_photo_1 = directory + "\\" + photo_name

## the good way
path_to_photo_2 = os.path.join(directory, photo_name)

print(path_to_photo_1)
print(path_to_photo_2)


I am working on the file "A Beginners Guide to Python/10. Strings.ipynb".
The first task is:
It is important to be aware of these Python features because If you don't know this stuff it you can be easily 'caught-out' the moment you start trying to parse complex strings. In what follows I have a (hopefully humorous) example of why you should care about this stuff. Let’s talk pathing.
Can you write Python code for it?

directory = "C:\Documents\pictures\selfies"
print(directory)


I am working on the file "A Beginners Guide to Python/10. Strings.ipynb".
The first task is:
So let's imagine we are building some sort of code that saves a directory as a string for use later on. If we print this particular directory we get no surprises, it just works as we would expect.
But hold-up, what if I wanted to send my girlfriend a naughty photo! inside of my 'selfies' folder I have a 'nudes' folder. And inside the 'nudes' folder I have a plethora of Jpegs; my little sausage pictured from a variety of different angles wearing an assortment of novelty hats.
<img src="http://i.imgur.com/wVrnjgc.png" style="width:300px;height:170px;" ALIGN="right">

“Wait, did he just say little?”

On this occasion however, let's pretend I'm not a total weirdo (debatable), I want to sent her something arty, something classy.
[scurries through folder...]
[finds ... 'tasteful.jpeg' ]

Alright, lets code that up and see what happens...
Can you write Python code for it?

directory2 = "C:\Documents\pictures\selfies\nudes\tasteful.jpeg"
print(directory2)


I am working on the file "A Beginners Guide to Python/10. Strings.ipynb".
The first task is:
So what was my genius solution?  Well obviously we cheat and change the text!
“don’t worry” --> “do not worry”

Problem...err….solved?
Anyway,  Python does have ways of handling such inputs, your homework for this week to figure out how to make my intended string work – if it takes you less than 10 minutes then congratulations, you figured it out faster than I did. :)
The str function
Just like the int() and float() functions, the str function is a good way to convert one data-type to another. If I have the an integer and I want to store it as a string I can simply call the str() function, and Python will do the rest. The code snippet below will take any float/integer and return a string representation of that number.
Can you write Python code for it?

def num_to_string(number):
    """takes a number of type float/int, returns string of that number"""
    return str(number)

# For an explanation of the next three lines of code, please see the 'calling functions' lecture.
a = num_to_string(4555549099511) # large integer
b = num_to_string(-0.0044352334) # negative float
c = num_to_string(4.3e10) # scientific notation

print(a, type(a))
print(b, type(b))
print(c, type(c))

# and notice that we can use the float/int methods to convert the strings back to numbers just as easily...
print( float(c), type(float(c)) )


I am working on the file "A Beginners Guide to Python/10. Strings.ipynb".
The first task is:
Why might you want to do this?
One reason you might want to store a number as a string is because if you convert a number to a string you have access to more 'methods' which may make some processes easier.
for example, lets suppose I want to find out what the first two digits of the number are. Converting a number to a string makes this process easy since strings are iterable and can be indexed into, whereas numbers cannot. Thats a lot of techinical jargon right now, but don't worry we shall cover indexing later.
Can you write Python code for it?

def first_two_digits(number):
    n = str(number) # < -- convert number to string
    n = n[:2]       # < -- get the first two characters via slicing (more on slicing later).
    n = int(n)      # < -- converting n back to a number.

    return n

print(first_two_digits(100000))
print(first_two_digits(933323))
print(first_two_digits(11))


I am working on the file "A Beginners Guide to Python/11. On the Importance of Errors (oh, and Operator Overloading).ipynb".
The first task is:
'add' and 'add2' are very similar functions, the main difference is that 'add' has a docstring that explains what the function does and also explains what the expected input is. 'add2' doesn't have documentation and it 'forces' the user to pass in integers. We haven't seen the assert statement yet, but basically what it does is it tells Python to throw an error whenever the condition is not met. In this case, if we give this function a string it will not work.
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/11. On the Importance of Errors (oh, and Operator Overloading).ipynb".
The first task is:
I argued that one of the main drawbacks of this code is that "[it] implicitly assumes the user knows to pass in numbers". In this lecture I aim to do two things. Firstly, I want you to understand what the problem is from a technical point of view. Secondly, I want you to grasp the importance of what is potentially at stake.
Can you write Python code for it?

# Function as above, but with DOCUMENTATION
def net_force(mass, acceleration):
    """
    Calculates f=ma, returns force.
    We assume mass, acceleration are of type int/float.
    """
    return mass * acceleration


I am working on the file "A Beginners Guide to Python/11. On the Importance of Errors (oh, and Operator Overloading).ipynb".
The first task is:
Of these two functions 'add' is more Pythonic, the code is more flexible and it trusts the user to be responsible. Meanwhile the function 'add2' is much more rigid and demands to be used in a certain way. Overall you should try to write code more like the 'add' function as opposed to 'add2'.
Moreover since the 'add' function doesn't hard-code requirements this code can be used in unexpected ways...
Operator Overloading...
Can you write Python code for it?

print( "✓"  +  "✓")    # strings (rememeber strings support *any* unicode character)
print(  1   +   2)      # integers
print( [1]  +  [2])     # lists
print( (1,) +  (2,))    # tuples


I am working on the file "A Beginners Guide to Python/11. On the Importance of Errors (oh, and Operator Overloading).ipynb".
The first task is:
Okay, we have called the chemo function twice and we get the number 130 and the string 6565 returned to us. We shall use those values in a jiffy. Okay, now lets suppose that part of our program has a 'parse results' function. The task of this function is to get the results ready for printing.
Can you write Python code for it?

def parse_results(number):
    return "The correct dose for the patient is " + number

parse_results(130)


I am working on the file "A Beginners Guide to Python/11. On the Importance of Errors (oh, and Operator Overloading).ipynb".
The first task is:
So in the above code I called parse_results with the number 130 and the function returns an error. Now, since are expecting to be given numbers as input and we want to return a string it makes sense to use str() to convert the number. Afterall, a program that spins errors when we give it correct input is of no use at all.
Can you write Python code for it?

def parse_results(number):
    return "The correct dose for the patient is " + str(number)
#                                                   ^ The change is right here

print(parse_results(130))
print(parse_results("6565"))


I am working on the file "A Beginners Guide to Python/11. On the Importance of Errors (oh, and Operator Overloading).ipynb".
The first task is:
The salient point is to notice is that the ‘+’ operator works not just on integers, but also tuples, lists, strings, class objects and so on. This is what we call 'operator overloading'.
Operator overloading occurs when a single operator has several different meaning/uses, depending on context. For numbers, ‘+’ means addition, for strings it means concatenation, and for other objects (i.e. custom classes) it could mean literally whatever we want it to mean.
In many ways, this is a great feature since it means the language ends up being a bit more concise and we have less symbols to remember. But you must be careful; There are bugs ahead!
Lets return to our function "force". Let’s call it with a bunch of objects (of different types) and see what happens!
Can you write Python code for it?

def net_force(mass, acceleration):
    """
    Returns mass * acceleration
    I trust you guys will use this function responsibly.
    """
    return mass * acceleration

print(net_force(10, 10))
print(net_force("2", 10))
print(net_force([1], 10 ))


I am working on the file "A Beginners Guide to Python/12. An Intuition for OOP.ipynb".
The first task is:
As a minor implementation detail my code adds Time to other Time objects, if we try to add an integer (e.g 4) to the time 10:30 we get an error. The reason for this is if we get handed an integer it is not entirely clear what should to happen; should we assume the integer is hours? But couldn’t that integer just as easily be minutes or total elapsed time (e.g 65 means 1:05) ? Since it's not clear, we don't guess. Instead we just yield an error.

“In the face of ambiguity, refuse the temptation to guess.” ~ Zen of Python

I could expand my Time object by adding more methods; maybe I could define subtraction, or a method for converting time-zones. But I wouldn’t add a ‘kick’ or a ‘bounce’ method because we can’t make sense of kicking or bouncing “12:30”.
Conclusion
It is important to remember that more or less everything in Python is an object. Strings are an object, Integers are an object and so on. Objects have methods (which are usually, although not always, functions) that can be called on them. Moreover, as we have seen above we can use the "class" keyword in Python to build our own objects with a corresponding set of methods.
Hopefully I have also demonstrated to you the power of OOP; by using objects we can think about code at a 'higher-level' of abstraction. And that's powerful stuff.
In the rest of this lecture series I do not mention classes and that's because in my opinion classes are a better topic for an intermediate guide; Classes can be tricky to get working but there are an undeniably powerful tool you should endeavour to learn more about.
Can you write Python code for it?

# Notice that, just like my "Time" object, integers, strings, floats, etc are all implemented in Python as classes.
print(type(Time(10,0)),type("hello"),type(10), type(5.67), sep="\n")


I am working on the file "A Beginners Guide to Python/12. An Intuition for OOP.ipynb".
The first task is:
So what does the above code do? Well, even though we haven't covered indentation and if/else statements (yet) my well-chosen variables names and comments were probably sufficient for you to figure out what is going on.
Basically, this code creates two objects (a ball and a football player called "Messi"), we then check if Messi is close to the ball. If he is, he kicks it up the pitch. If he isn't near the ball Messi starts running towards it.
In short, objects and object methods in Python allow us to write code at a 'high-level', we can leave all the 'low-level' stuff for another developer (or Python itself) to handle. And that leaves us all the time in the world to do the fun stuff!
Building A Time Object..
I am going to finish today's lecture with an actual example of Object Orientated Programming in practice; I'm going to quickly build a 'Time class'. It is going to be a class that allows us to add times together and print them. For example: 6:00 + 30 minutes should equal 6:30 and 10:59 + 1:01 should equal 12:00.
Please be aware that I DO NOT expect you to understand the code below nor am I going to explain in detail how it works either. I just wanted to end this lecture on a real example, simple as that.
Can you write Python code for it?

class Time(object):

    # Do you remember in the variable names lecture I said that
    # you shouldn't name a variable '__something' unless you know what you are doing?
    # Well, thats because double underscore is usually reserved for "hidden" class properties/functions.
    # In this particular case, __init__, __repr__, __add__, all have very special meanings in Python.

    def __init__(self, hours, mins=0):
        assert isinstance(hours, int) and 0<= hours <=24  # Hours should be between 0 and 24 for a 24-hour clock.
        assert isinstance(mins, int) and  0<= mins  <=60

        self.hours = hours
        self.mins = mins

    def __repr__(self):
        return format_time(self.hours, self.mins)

    def __add__(self, other):
        """
        This function takes itself and another Time object and adds them together.
        """
        a = self.mins
        b = other.mins

        x = self.hours
        y = other.hours

        c = str( (a + b) % 60 ).zfill(2)   # Integer division and Modulo Arithmetic. You have seen this before!
        z = str((((a+b) // 60) + x + y) % 25).zfill(2)

        return format_time(z, c)

def format_time(h, m):
    """
    This function takes hours (int) and mins(int) and returns them in a pretty format. 2, 59 => 02:59
    """
    return "{}:{}".format(str(h).zfill(2), str(m).zfill(2))


I am working on the file "A Beginners Guide to Python/12. An Intuition for OOP.ipynb".
The first task is:
When we print time objects they are represented as a string that looks just like an alarm clock; Time(12, 30) returns "12:30"
Now, the code above also defines a method "add". This method adds Time Y to Time X which is effectively asking:
“If the time now is X what time is in Y hours and Z minutes from now?”

Let's try adding some times together now:
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
From the above three lines you can probably get a good idea what the "isdigit" method is doing; This method checks checks to see if each character in the string is a digit (i.e 1,2,3,4,5,6,7,8,9,0) or not. If every single character is a digit the method returns True, False otherwise.
Just because a string methods take strings as input DOES NOT mean they must output strings as well.
Count Method
Can you write Python code for it?

print("nananananananaBATMAN".count("A")) # Note count is case sensitive.
print("nananananananaBATMAN".count("BATMAN"))
print("nananananananaBATMAN".count("ROBIN"))


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
Homework Assignment #1
Your first homework assignment for this week is to take the variable named "text" (this has been defined for you)  and count the number of times "z" occurs AND the letter "k" occurs. Add those numbers up and print the result.
As a further complication,  we DO NOT care about case (e.g. ‘z’ and ‘Z’ should both be included in the count).
Don’t feel bad if you struggle, this homework is a step up in difficulty compared to normal. Oh and I have also included a few test cases below that should help you figure out what to do (just in case my instructions were not clear enough).
For bonus difficulty, make your code work for any letter (e.g. "a", "b" returns the count of 'a' + 'A' + 'b' + 'B').
Can you write Python code for it?

text = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzZZZZeeeewwwwwwwwkKewewe2324____23!!!!!fsdffskdsdzzzzZZZZZZZZZZZZZZZZroiooioi"

# Simple Examples (string --> total you should return)
# "zZ"       --> 2
# "kK"       --> 2
# "KZ"       --> 2
# "1aZZZabc" --> 3
# "hello"    --> 0
# "ZzKk"     --> 4

# Your code goes here...


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
Homework Assignment #2
You are working on a program that has a greeting message and a goodbye message for several languages. Your job is to simply make the code more elegant and readable. Do whatever you think needs doing (note, there isn't really a right/wrong answer here, the aim of this homework is just to make you think about style and readability).
Can you write Python code for it?

# Make this more readable...

bye_spain = "buenas noches"
english_greeting = "hello"
english_goodbye = "sod off, lad"
greeting_japanese = "konichiwa"
spanish_greeting = "hola"
hello_in_french = "bonjour"
japanese_bye = "sayonara"


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
However, concatenation can become a little cumbersome when we start trying to create strings with several moving parts and/or with different data-types:
Can you write Python code for it?

name = "chris"
age = 29
no_of_pets = 203
pet = "wombat"

s = "Hi, " + name + " your age is " + str(age) + " and you have " + str(no_of_pets) + " " + pet + "'s. Wow, thats a lot of " + pet + "'s"
print(s)


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
I think you will all agree that the string 's' is getting clunky right now. Its so large that we have to scroll sideways just to see the end of it.
Format to the rescue!
“I have {x} cats and {y}{z}”.format (x, y, z)

Normally when I write syntax I use {} for my own commentary, but on this occasion you need to be aware '{}' is literally what you type in.
Python will then replace the {} with a value, which happens to be the value you give as an argument to format(). So format(x) will insert 'X' into the string. Maybe this is easier understood with actual examples:
Can you write Python code for it?

s = "I have {} cats and {} {}.".format(2, 3, "dogs")
print(s)


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
In the code cell above you may notice a new concept, "input". Input basically asks the user (yes thats you) to type in a message. In this particular case we call input three times and store the result in three seperate variables.  Once thats done we take the text and replace character X with character Y.
For example:
starting text = “BATMAN”
replace_this = “A”
Replace_with = “Z”
Returns: “BZTMZN”

Go ahead, why not play with this code for a bit.
Getting side-tracked with Style
Now these variables names are pretty good overall, but readability isn't just about having good function names, it is also about creating names that ‘fit’ together, that is, a naming structure consistent throughout the code.
After a little bit of thought I came up with a much better name than "old_sequence", I swapped it to "replace_this". This new name is not any better in and of itself, but when we juxtapose it alongside "replace_with" it is obviously a more elegant name.

replace_this
replace_with

‘old_sequence’ although a perfectly reasonable variable name by itself doesn’t show the reader that these two variables are related to one another. Changing ‘old_sequence’ to ‘replace_with’ makes the relationship clearer and as an extra bonus it means our code could almost pass for normal English.

text.replace(replace_this, replace_with)
text.replace(old_sequence, replace_with)

In the grand scheme of things we are making tiny little changes here, but I’d argue the first example is better. This tiny little change makes my code more beautiful, more elegant, and above all, more readable.
Hopefully this week's homework (#2) will make these concepts more clear to you.
Format
If you read my code snippets you will see that I use format a lot. At heart, format is a way to create strings with 'moving parts' inside them.
For example, if I want to greet the user I probably want some code that returns “hello, {user’s name)”.
We can do this with concatenation like so:
Can you write Python code for it?

name = "chris"
greeting = "hi, " + name

print(greeting)


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
Let's quickly go back to my 203 pet wombat example. But this time instead of using concatination we shall use the format method.
Can you write Python code for it?

name = "chris"
age = 29
no_of_pets = 203
pet = "wombat"

s = "Hi, {0} your age is {1} and you have {2} {3}'s. Wow, thats a lot of {3}s".format(name, age, no_of_pets, pet)
print(s)


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
Notice that this time instead of empty brackets '{}' we have numbers inside them (e.g '{3}'). This relates to something called indexing (more on this later), but for now, let's just say {3} means Python replaces {3} with the third argument parsed to the format method, in this case it is the variable called 'pet'.
As a minor technical detail, Python counts from zero so the 'third item' is actually the fourth item, if that makes sense.
Here's another example:
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
Talking of syntax, addition, multiplication and so on are also methods and they can be called using two different bits of syntax:
 "Hello" + "World"
 "Hello".__add__("World")

Just as before these two ways of doing things produce the same result. Anyway, in the rest of this lecture we will be going over some of the various string method in more detail...
Isdigit method
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/13. Strings & Methods.ipynb".
The first task is:
The count method shown above can be pretty useful, it takes two arguments and returns the total number of times the second string appears in the first. Do note that it is case-sensitive and it looks for an EXACT match. For example:
"abc".count("ab") --> 1
"acb".count("ab") --> 0

In short, count is looking for instances of the whole pattern and NOT how many times a and b appear individually.
Replace method
Can you write Python code for it?

text = input("Feed me characters...FEED ME NOW GRRRR!!!  ")
replace_this = input("Now give me a single character to change in the text ")
replace_with = input("What should we change that character to ? ")

print("")
print("============ RESULT =================")
print(text.replace(replace_this, replace_with))


I am working on the file "A Beginners Guide to Python/14. Lists.ipynb".
The first task is:
Alright, so that should just about cover everything you need concerning adding stuff to lists. Next up; removing stuff.
Removing elements
Compared to adding stuff to lists, removing things is a bit simpler, there are two basic ways to do it...
remove method
The remove method does exactly what is says, you give this function something to look such for and then it removes the first instance it finds. If that value isn't found, an error is returned. Allow me to demonstrate:
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/14. Lists.ipynb".
The first task is:
And since lists are also iterables, append/extend works on them as well:
Can you write Python code for it?

a = ["x", "y"]
b = a[:] # <== I will explain what this does in the index/slice lecture!
a.append(["a", "b", "c"])
b.extend(["a", "b", "c"])
print("Append 'abc':", a, "\nExtend 'abc':", b)


I am working on the file "A Beginners Guide to Python/14. Lists.ipynb".
The first task is:
I'd like to make two brief notes here; firstly, adding lists to lists (unlike numbers to numbers) does not have the commutative property. This means a + b is not the same as b + a. And this is why you need to be careful with ‘operator overloading’; just because you see the same symbol being used does not mean we are simply doing addition!
Secondly, I'd like to point out that we are creating a new list here, the original lists have not changed. If we want to add list b and list c to list a then we have to do things a little differently...
Can you write Python code for it?

a = [1,2]
b = [3,4]
c = [5]

print(a)                 # <= before we change it.
a += b + c               # += is shorthand for writing; a = a + b + c

print(a, b, c, sep="\n")


I am working on the file "A Beginners Guide to Python/14. Lists.ipynb".
The first task is:
In the case of iterables, however you are going to what to think about the behaviour you want; If I have the string "abc" do I want to add the whole thing or do I want to add all the individual elements (i.e. 'a', 'b', 'c') to the list.
If you want a single value to added to the list use append just as before. If you want each element in the list we need a new method, extend. For example:
Can you write Python code for it?

a = ["x", "y"]
b = a[:] # [:] creates a 'shallow copy' of a
a.append("abc")
b.extend("abc")
print("Append 'abc':", a, "\nExtend 'abc':", b)


I am working on the file "A Beginners Guide to Python/14. Lists.ipynb".
The first task is:
Notice the difference in the last two cases. If we say a = [a,b,c] then we set a to equal a list of three lists. Whereas in the second case list a has the numbers 1, 2 and then two lists at the end.
You may have noticed that everything we have discussed thus far has us adding lists to other lists, what if we want to add other data-types, such as numbers or strings? In this case we need append.
Append can be used to add also sorts of values to lists, boolean, integers, floats, strings, to name a few. Please note that append always adds things to the end of the list (the reason Python adds values to the end and not at the beginning is a bit technical, but basically, adding to the end is more efficient).
Here are a few examples...
Can you write Python code for it?

lst = [1, 2, 3]
lst.append(4)               # Integer
lst.append(63.979696)       # Float
lst.append(True)            # Boolean
print(lst)


I am working on the file "A Beginners Guide to Python/14. Lists.ipynb".
The first task is:
Okay, so thats several ways we can make lists covered, now lets look at a few ways we can play with lists once they are built!
Adding to lists...
So you have a list and you want to add values to it, how does one do that? The answer to that questions depends on:
1. What you want to add?
1. How you want that value stored?
1. Do you want to make a new list, or change the current list?
If we have two lists, we can just use the "+" command. like so:
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/14. Lists.ipynb".
The first task is:
Pop method
The pop method does two things, first it removes the item from the list, and second it returns that item. By default, pop will remove the last item in this list, but if you give it an index (more on indexing later), it can 'pop' something from any position. For example:
Can you write Python code for it?

lst = ["first", "middle", "last"]
item = lst.pop() # item now is assigned to "last". Last is removed from list.
print("item is '{}'".format(item), "lst is {}".format(lst), sep="\n")

item_2 = lst.pop(0) # index 0 is the first item
print("\nitem_2 is '{}'".format(item_2), "lst is {}".format(lst), sep="\n")


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
Anyway, thats enough about indexing for now, let's move onto the topic of slicing...
Slicing
What is slicing? Well it is a bit like indexing, only instead of returning point 'X' we return all the values between the points (x, y). Just as with indexing, you can slice strings as well as lists.
Note: start points are inclusive and endpoints are exclusive.
{variable} [{start} : {end} : {step}]
* Where start, end and step are all integer values.

It is also worth noting that each of start, end and step are optional arguments, when nothing is given they default to the start of the list, end of the list and the default step is 1.
If you give start/step an integer Python will treat that number as an index. Thus, a_list[2:10] says "Hey Python, go fetch me all the values in 'a_list' starting at index 2 up-to (but not including) index 10.
Unlike indexing however, if you try to slice outside of range you won't get an error message. If you have a list of length five and try to slice with values 0 and 100 Python will just return the whole list. If you try to slice the list at 100 and 200 an empty list '[]' will be the result. Lets see a few examples:
Can you write Python code for it?

lst = list(range(1,21)) # list(range) just makes a list of numbers 1 to 20

# The below function just makes it faster for me to type out the test cases below.
def printer(start, end, lst):
    """ Helper function, takes two integers (start, end) and a list/string.
    Function returns a formated string that contains: start, end and lst[start:end]"""
    if start:
        if end:
            sliced = lst[start:end]
        else:
            sliced = lst[start:]
    elif end:
        sliced = lst[:end]
    else:
        sliced = lst[:]

    return "slice is '[{}:{}]', which returns: {}".format(start, end, sliced)

print("STARTING LIST IS:", lst)
print("")

# Test cases
print("SLICING LISTS...")
print(printer("","", lst))    # [:] is sometimes called a 'shallow copy' of a list.
print(printer("", 5, lst ))   # first 5 items.
print(printer(14,"", lst))    # starting at index 14, go to the end.
print(printer(200,500,lst))   # No errors for indexes that should be "out of bounds".
print(printer(5,10, lst))
print(printer(4,5, lst))
# Negative numbers work too. In the case below we start at the 5th last item and move toward the 2nd to last item.
print(printer(-5,-2, lst))
print(printer(-20,-1, lst))  # note that this list finishes at 19, not 20.

# and for good measure, a few strings:
print("\nSLICING STRINGS...")
a_string = "Hello how are you?"
print(printer("","", a_string)) # The whole string aka a 'shallow copy'
print(printer(0,5, a_string))
print(printer(6,9, a_string))
print(printer(10,13, a_string))
print(printer(14, 17, a_string))
print(printer(17, "", a_string))


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
Can we change the values inside strings?
Lets try!
Can you write Python code for it?

a_string = "123"
a_string[0] = "a"   # <-- Error; strings are an "immutable" data type in Python.


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
In both of the above cases we are using a step of size 10. If we start at 0 that means we get:
10,20,30...

but if we start at 5 then the sequence we get is
5, 15, 25...

In the case of the string example above, the patten has a length of four and then repeats. Thus, if we start with n charater and have a step of 4 the resulting pattern with be "nnnnnn".
Reversing lists with step
The very last thing I want to show you about a the step argument is that if you set step to -1 it will reverse the string/list.
For example:
Can you write Python code for it?

a_list = list(range(1, 11))

print(a_list)
print(a_list[::-1])     # reverses the list


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
In python strings are immutable, which is a fancy way of saying that they are set in stone; once created you just can't change them. Your only option is to create new strings with data you want.
If we create a new string we can use the old variable if we want. But in this case, you didn't change the value of the string. Rather what you did was create a new string and give it a variable name, and thats allowed.
Here is one way we can change the value of 'a_string':
Can you write Python code for it?

a_string = "123"
a_string = "a" + a_string[1:] # slicing, see below.

print(a_string)


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
Now, those bounds I have just given might sound a bit arbitrary, but actually I can explain exactly how they work. Consider the following picture:

So in this picture we have the string ‘hello’. The two rows of numbers represent the indexes of this string. In Python we start counting from 0 which means the first item in a list/string always has an index of 0. And since we start counting at zero then that means the last item in the list/string is len(item)-1 like so:
Can you write Python code for it?

string = "hello"
print(string[0])               # first item
print(string[len(string)-1])   # last item


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
So that explains the first row of numbers in the image. What about the second row? Well, in Python not only can you index forwards you can also index backwards.
Readabily counts...
So basically index [0] will always be the start of the list/string and an index of [-1] will always be the end. If you wanted the middle "l" in "hello" have a choice; either [2] or [-3] will work. And, as a general rule, if code ends up being equivalent your choice should be to go with whatever is more readable.

There should be one-- and preferably only one --obvious way to do it. ~ Zen of Python

For example:
Can you write Python code for it?

a_string = "Hello"
# indexing first item...
print(a_string[0])                 # Readable
print(a_string[-len(a_string)])    # Less readable

print(a_string[-1])                # Readable
print(a_string[len(a_string)-1])   # Less readable
print(a_string[4])                 # Avoid this whereever possible! BAD BAD BAD!!


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
The Range Function
You maybe have observed that I use the 'range' function in some of the above examples. This function doesn't have anything to do with indexing or slicing, but I thought I would briefly talk about it here because although the syntax is different this function works in a very similar way to slicing. More specifically, the range function takes 3 arguments; start, end, step (optional). And these arguments work in a similar way to how start, end and step work with regards slicing. Allow me to demonstrate:
Can you write Python code for it?

list_1 = list(range(1,21))
list_1 = list_1[2::3]
print(list_1)

# The above 3 lines can be refactored to:
list_2 = list(range(3, 21, 3))
print(list_2)


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
To index a list inside a list the syntax is to add another [{integer}] on the end. Repeat until you get to the required depth.
list[{integer}][{integer}]

In the case of the above the value 100 was nested inside so many lists that it took a lot of effort to tease it out.  Structures like this are hard to work with, which is why the usual advice is to 'flatten' your lists wherever possible.
With this said, nested structures are not all bad. A really common way of representing a grid in Python is to use nested lists. In which case, we can index any square we want by first indexing the 'row' and then the 'column'. Like so:
grid[row][column]

If you ever want to build simple board games (chess, connect 4, etc) you might find the representation useful. In code:
Can you write Python code for it?

grid = [ ["0"] * 5 for _ in range(5) ]  # building a nested list, in style. 'List Comprehensions' are not covered in this course.

print("The Grid looks like this...:", grid[2:], "\n")
# Note: "grid[2:]" above is a 'slice' (more on slicing below), in this case I'm using slicing to truncate the results,
# observe that three lists get printed, not five.

def print_grid():
    """This function simply prints grid, row by row."""
    for row in grid: # This is a for-loop, more on these later!
        print(row)

print_grid()
print("\n")
grid[0][0]     = "X" # Top-left corner
grid[0][-1]    = "Y" # Top-right corner
grid[-1][0]    = "W" # Bottom-left corner
grid[-1][-1]   = "Z" # Bottom-right corner
grid[2][2]     = "A" # Somewhere near the middle
print_grid()

# Quick note, since the corners index are defined by 0 and -1, these numbers should work for all nxn grids.


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
What can we do with indexing?
Obviously we can do a lot with indexing, in the cases of lists, for example, we change the value of the list at position ‘i’. Its simple to do that:
Can you write Python code for it?

a_list = [1,2,3]
print(a_list)
a_list[-1] = "a"
print(a_list)
a_list[0] = "c"
print(a_list)
a_list[1] = "b"
print(a_list)


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
You might wonder what is wrong with index[4] to reference the end of the list.
The problem with using index[4] instead of [-1] is that the former way of doing things is considerably less readable. Without actually checking the length of the input the meaning of index[4] is somewhat ambiguous; is this the end? Near the beginning/middle? Meanwhile [-1] always refers to the end regardless of input size, and so therefore its meaning is always clear even when we don’t know the size of the input.
Index[len(a_string)-1] meanwhile always refers to the end of the list but it is considerably more verbose and less readable than the simple [-1].
The Index Method
The string class AND the list class both have an index method, and now that we have just covered indexing we are in a position to understand its output.
Basically, we ask if an item is in a string/list. And if it is, the method returns an index for that item. For example:
Can you write Python code for it?

a_list = ["qwerty", "dave", "magic johnson", "qwerty"]
a_string = "Helllllllo how ya doin fam?"

# notice that Python returns the index of the first match.
print(a_list.index("qwerty"))
print(a_string.index("l"))

# if item is not in the list, you get an value error:
print(a_list.index("chris"))


I am working on the file "A Beginners Guide to Python/15. Indexing & Slicing.ipynb".
The first task is:
You will note a small difference between the two ways of doing things. When we slice we start the the count at 2 whereas with range we start the count at 3. The difference is the result of the fact the range function is dealing with numbers, whereas the slice is using indexing (e.g. list_1[2] is the number 3).
And just as with slicing, a step of -1 counts backwards...
Can you write Python code for it?

list_3 = list(range(10, -1, -1)) # this says: "start at the number 10 and count backwards to 0

# please remember that start points are inclusive BUT endpoints are exclusive,
# if we want to include 0 in the results we must have an endpoint +1 of our target.
# in this case the number one past zero (when counting backwards) is -1.

print(list_3)


I am working on the file "A Beginners Guide to Python/16. Sorting.ipynb".
The first task is:
In this case, we have a list containing booleans (more on those later) and integers.
Before we even start to question what Python is doing it might be worth asking ourselves the following question:

“How would we sort a list like this?”

Does it really make sense to sort by size and then shove True, False in the middle? A more 'human' approach might be to sort by type and then by size.
[False, True, -4, -3, -2, 2, 3, 4]

In any case, I can explain to you Pythons weird look decision to put True, False right in the smack of the list by spending a few moments explaining that Booleans (i.e. True/False) are NOT ‘special’ in Python. As a matter of fact, True is just the number 1 and False is just the number 0. Here, let me show you:
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/16. Sorting.ipynb".
The first task is:
Is a quick final point, I'd like to stress than any function that takes one input ('x') and returns a number can be used as our sorting method.
And so, if we wanted shuffle a list into a random order we could actually sort the list. Which may sound counter-intuitive at first, but really, when you think about it, it makes some sense; all sorting is doing is arranging some numbers according so some rule, the rule itself could be anything we like.
Can you write Python code for it?

import random

the_list = [8, 8, 8, 0, 0, 9, 1, 2, 3, 4, 5, 55, 55, 55, 55, 324343434, 40000, 50, 40, 1, 1, 1, 1, 1, 3, 33, 33, 98]
random_shuffle = sorted(the_list, key=lambda x: random.random())

print(the_list)
print(random_shuffle)


I am working on the file "A Beginners Guide to Python/16. Sorting.ipynb".
The first task is:
Once you understand that True/False are actually numbers in Python (specifically 1 and 0) its easy to understand why the sort function put these two values smack centre in our list of numbers.
So yeah, to protect yourself against "junk" output you have to really careful about what you are doing; a good question to ask yourself is:

"Does this task I'm asking of Python even make sense?"

Alright, before wrapping up this section on sorting lets checkout the optional argument 'key'. Basically, the sort function, when given this optional argument will sort the data according to the specified key. Its probably easiest if I just show you...
Can you write Python code for it?

the_list = [8, 8, 8, 0, 0, 9, 1, 2, 3, 4, 5, 55, 55, 55, 55, 324343434, 40000, 50, 40, 1, 1, 1, 1, 1, 3, 33, 33, 98]

by_default    = sorted(the_list)
by_count      = sorted(the_list, key= lambda x: the_list.count(x)) # Lambda is explained below!
by_digits_num = sorted(the_list, key= lambda x: len(str(x)))

print("Default sort (small to large)...", by_default,
      "\nSort by 'count' (i.e. how many times each number occurs)...", by_count,
      "\nSort by 'number of digits'...", by_digits_num, sep="\n")


I am working on the file "A Beginners Guide to Python/16. Sorting.ipynb".
The first task is:
This works by creating a small two element structure as our key. We sort by the first item, and then, if there is a tie we sort by the second item. So for example the pair (2,0) comes before the pair (2,1) because 1 is greater than 0. Likewise (6,-10) comes after (2,0) and (2,1) because 6 is greater than both 0 and 1.
If I want the values in the opposite order (large-to-small), then a quick trick I can employ is the flip the sign of the number when creating the key. This means that large numbers become very small numbers and small numbers become large. Thus, in this case Python is still sorting from small-to-large; we made it work by changing the numbers.
in the example above, I think it would have been better to write the following:
by_len_then_size_large_first = sorted(the_list, key=lambda x: (len(str(x)), x), reverse=True)

However this "flip the sign" trick can be useful if you ever what to sort category X by largest first and category Y by smallest first (or vice-versa). For example, maybe you want to sort by age first and then by largest salary.
Can you write Python code for it?

## Each mini list is a person, salary first and age second. For example the first person earns £100 and is 23yrs old.
age_and_salaries = [[100, 23], [100, 19], [200, 27], [200, 19], [300, 33]]

age_then_wealth = sorted(age_and_salaries, key=lambda x: (x[1], -x[0]))
wealth_then_age = sorted(age_and_salaries, key=lambda x: (-x[1], x[0]))

print(age_then_wealth)
print(wealth_then_age)


I am working on the file "A Beginners Guide to Python/17. If, else, Logic, and Laziness.ipynb".
The first task is:
A note on Python's "Lazy" evaluation...
Lets play a simple game. The rules:
1. I give you two statements, P, Q and an operator (operator will always be one of "and/or")
1. Both statements are truth functional (i.e. statements that are True or False)
1. Your job is to evaluate the expression (P operator Q) in the fastest time possible.
1. You get to choose what to evaluate first, i.e. your options are P then Q, or Q then P.
Alright, here's the first question:

P = "There are an infinite number of twin primes"
Q = "7 + 7 is an odd number"
operator = And

So, to once again reiterate the question:

Is (P and Q) True/False and what is the fastest strategy for solving it?

Answer:
The statement (P and Q) is False, and the fastest strategy is to calculate statement Q first.

Why? Well, in order to prove that (P and Q) is False we only have to prove that either P or Q is False. In this case, its pretty easy to see that Q is false, therefore we know the answer of (P and Q) without having to prove the twin primes conjecture.
Alright, lets try one more time.

P = 7 is a prime number
Q = "NP = P"  (wiki entry for the np = p problem)
operator = or

Answer:
“The statement (P or Q) is True, and the fastest strategy is to calculate statement P first."

Similar to the problem above, to prove (P or Q) is True we only have to prove either P or Q is True. In this case, we can quickly check 7 is prime and thus we can solve the problem without even attempting to solve the np=p problem (which btw, has a million dollar prize for the first person to prove it).
Okay, so how can we use this information in our Python programmes? Well Python uses the same "lazy" evaluation as we did above; if we know A is True we don't have to calculate B in order to know (A or B) is True.
So how can we take advantage of this? Well, the syntax is simple, Python evaluates left-to-right. So in the case of P or Q Python ALWAYS checks P first. To take advantage then, what we should do is give Python the easiest statement first and then the slower one....
Can you write Python code for it?

import time

def timesink_function(x):
    """
    Returns x, after 5 secs
    """
    time.sleep(5)
    return x

## Experiment # 1
print("True or wait(true)...")
t1 = time.time()
_  = True or timesink_function(True) # the important line
t2 = time.time()
print("Time Taken was {} secs".format(round(t2-t1, 1)))
print("\n")

## Experiment # 2
print("wait(true) or true...")
t1 = time.time()
_  = timesink_function(True) or True # the important line
t2 = time.time()
print("Time Taken was {} secs".format(round(t2-t1, 1)))
print("\n")


I am working on the file "A Beginners Guide to Python/17. If, else, Logic, and Laziness.ipynb".
The first task is:
And & Or
Now we are going to add to the complexity a little by explaining how we can combine expressions into larger ones.
Why might we want to do this?
Well say for instance you want to write some code that returns a number that is divisible by 5 OR divisible by 10. Maybe you want to write some code that checks if a number is odd AND also a perfect square (eg. 9, 25).
As it turns out, Python has the "and"/"or" commands and for the most part they will work how we understand them in English. But, perhaps we should make the effort to be precise. The table below tells you what the output of the operator is for all values of A and B.

Okay, cool. So how do we use and/or in Python? The good news is that syntax is super intuitive:
{value} and {value_2}    returns True/False
{value} or  {value_2}    returns True/False

yes thats right, the keyword for 'and' in Python is the word 'and', 'or' in Python is also the same as English. Alright, lets run a few examples shall we:
Can you write Python code for it?

x = 10

print(x > 9 and x < 11)  # is x greater than 9 AND less than 11. Note, we could refactor this to: 11 > x > 9
print( isinstance(x, str) or isinstance(x, int))   # is x a string OR a integer

print(x % 5 == 0 and x % 2 ==0) # is x divisible by 5 AND 2

# Once again, remember 0 = False and 1 = True
print(0 or 0)  # False or False  = False
print(1 or 0)  # True or False   = True
print(0 and 0) # False AND False = False
print(0 and 1) # False AND True  = False

a = True
# Basic logic...
print(a or not a)  # Tautology, ALWAYS TRUE  (for any a)
print(a and not a) # Contraction, AlWAYS FALSE (for any a)


I am working on the file "A Beginners Guide to Python/17. If, else, Logic, and Laziness.ipynb".
The first task is:
In Experiments 5 and 6 are just like 3 and 4, except that we are not looking at the "and" condition. There is once again a time difference. Python gets to be lazy in one case but not in the other.
In short, understanding Python’s lazy evaluation can speed up you code considerably (without any cost to readability). As a general rule, to make use of lazy evaluation all you need to do is put the thing that is fastest to evaluate first and the slowest thing last. For maximum efficiency you would need to figure out what it minimum about of work you need to do in order to arrive at an answer.
Homework
Your challenge this week is to evaluate the following formula as fast as possible:
(a or b) and ( (b and not b) or c )

Where:

a takes 2 seconds to return a true/false value
b takes 3 seconds to return a true/false value
c takes 4 seconds to return a true/false value

Have a bit of play and see how quickly you can make it execute.
Just be careful not to change the meaning of the expression. The parenthesis, just like in maths group the elements. For example "(a or b) and c" is not the same as "a or (b and c)".
Can you write Python code for it?

import time
import random
from functools import partial

def sleep_bool(x, b):
    """
    waits for x secs, then returns true/false
    """
    time.sleep(x)
    return b

a = partial(sleep_bool, 2, True)
b = partial(sleep_bool, 3, False)
c = partial(sleep_bool, 4, False)

t1 = time.time()
expression = ( a() or b() ) and ( (b() and not b() ) or c() )    ## <-- change this line
t2 = time.time()

print("Time Taken was {} secs".format(round(t2-t1, 1)))
print("\n")

## Can you beat 9 secs ???


I am working on the file "A Beginners Guide to Python/17. If, else, Logic, and Laziness.ipynb".
The first task is:
In experiments 3 and 4 we are comparing 'False or wait' against 'wait or false'. In this case there is no difference in time. Thats because the Truth of 'False or X' or 'X or False' actually simplifies to 'X'. Python cannot take any shortcuts here, it has to workout what X is, and that takes 5 secs.
Can you write Python code for it?

## Experiment 5
print("False and wait(true)...")
t1 = time.time()
_  = False and timesink_function(True) # the important line
t2 = time.time()
print("Time Taken was {} secs".format(round(t2-t1, 1)))
print("\n")

## Experiment 6
print("wait(true) and False...")
t1 = time.time()
_  = timesink_function(True) and False # the important line
t2 = time.time()
print("Time Taken was {} secs".format(round(t2-t1, 1)))
print("\n")


I am working on the file "A Beginners Guide to Python/17. If, else, Logic, and Laziness.ipynb".
The first task is:
Why does '[0]' equal True and '[]' equal False? The answer is that when we ask Python ‘if a exists’ Python decides that statement is True if a is NOT empty. In other words, an empty string/list is basically the same as not existing and hence returns False. In this particular case exists([0]) is True because the list contains the element "0". This is also why [False] returns True; its True because the list contains the element "False".
The way this works is a bit tricky to get your hand around, but once you do get it you can start writing some really nice idiomatic code.
Readibility counts...
This is a minor detour, but I feel that I should not be simply teaching you guys how to do stuff, I should be trying to teach you guys how to do stuff in the most 'Pythonic' way possible. This is a good juncture to talk a little about style.
Consider the following code:
if variable != []:
    {do something}

OR:

if variable == True:
    {do something}

The first code snippet asks if 'variable' is an empty list. If it isn't empty we enter the main body of code and do something (see indentation lecture). The second snippet of code asks if our value is equal to the value True. In many cases code like this code can be written to be more 'Pythonic'. You see, both these statements are essentially asking 'if variable exists do X', which means we can refactor this to:
if variable:
    {do something}

Alright, let's try another example...
if variable == {value}:
    return True
else:
    return False

So this code is part of a function. The function returns True if the variable is equal to some value and returns False otherwise. Just as before it is important to note that this code works, BUT, it can be rewritten like this:
return True if variable == {value} else False

But guess what, we can further refactor this code, since "==" always returns a boolean (ie. True/False) the "True if" is simply not necessary. So even better is:
return variable == {value}

Thats four lines of code condensed into one simple expression. Neat...
Can you write Python code for it?

a = input("\nGive me variable 'a' :                         ")
b = input("Now give me variable 'b' :                     ")
op = input("give me an operator (e.g '==', '!=', '>') :    ")

string = "{} {} {}".format(a, op, b)
print("\nThe statement is {}...".format(string), "The statement is {}".format(eval(string)), sep="\n   ")


I am working on the file "A Beginners Guide to Python/18. Indentation.ipynb".
The first task is:
<img src= attachment:indentguide.png) style="width: 200px;"/>
Okay, so what does indentation actually do? Basically, its a way to tell Python how to move around your code. Lets look at the image below:

The numbers in grey '(1)', '(2)' and so on represent the indentation level, and the coloured squares represent the 'blocks' of code. At the highest level is the 'black square', in this particular instance in contains a function named 'function' and a variable named 'Z'.
The line of code "if A" decides whether we enter the 'lime-green-box' or not, if  A happens to be false we skip all of the code with lime-green-box and instead just move to the else statement which tells us return "something something". Likewise, the "if B" line of code decides whether we enter the 'turquoise-box' of code (or not). If not, we return A. The only time this function returns D is when A,B,C,and D are all True, and that is because this is the only path that leads to the line ‘return D’.
In short, indentation is a way of 'sectioning off' code. As a matter of fact Python completely ignores code blocks it doesn’t enter. Here's proof:
Can you write Python code for it?

a = 10

if a == 11:
    a += "abc"                  # a (an int) + "abc" should be a type error!
    10/0                        # dividing by zero should raise a zero division error!
    dave + hat + seven_turnips  # should be a name error, none of these things are defined!
else:
    print(a*a)


I am working on the file "A Beginners Guide to Python/18. Indentation.ipynb".
The first task is:
In the above code snippet we have three lines of error prone code; we cant add ints to strings and we cant divide by zero and we cant add objects that have yet to be defined. and yet, Python ran just fine. The reason being I have already eluded to; Python ignores those three lines of code because 'A' does not equal 11.
To summarise quickly indentation is all about organising code and controlling the 'flow' of the program. Messing up indentation is a VERY COMMON beginner error to make, so don't feel bad if you end up incorrectly indenting bits of code.
Let me quickly show you how easy it is.
Can you write Python code for it?

for i in range(1, 11):
    square = i*i
print(square)


I am working on the file "A Beginners Guide to Python/18. Indentation.ipynb".
The first task is:
So our code was called on the numbers 2, 5, 33 and 100. If then printed out a few statements, you should notice the last two lines contradict each other, 100 cannot be both even and odd!
How did this happen, well, we used an if statement when we should have used an elif statement. You see, what is currently happening is that Python first checks to see if 100 is even (it is) and so it executes the relevant code block (i.e printing 100 is even). Next it sees another if statement which asks if he current number is greater than 10 (it is), and so 100 is odd AND..." gets printed.
Basically, if we have multiple if statements next to each of Python checks each one (if statements, at the same indentation level, are independent). Whereas an elif statement is only checked if the if statement above it equates to False. Thus, a simple fix to our program is to use 'elif' instead of 'if'. Like so:
Can you write Python code for it?

for number in [2, 5, 33, 100]:
    if number % 2 == 0:
        print("{} is even".format(number))
    elif number > 10:    # <--- This is the ONLY LINE that has changed.
        print("{} is odd AND greater than 10".format(number))
    else:
        pass


I am working on the file "A Beginners Guide to Python/18. Indentation.ipynb".
The first task is:
So this code goes through the numbers 1-to-10 and calculates the square of the number. Now in this case only one number gets printed (100), which happens to be 10x10.
Okay, so now lets take the same bit of code, but this time we are going to indent the print statement. Before we run it I want you to have a guess as to what you think will happen.
Can you write Python code for it?

for i in range(1, 11):
    square = i*i
    print(square) # <--- now indented...


I am working on the file "A Beginners Guide to Python/18. Indentation.ipynb".
The first task is:
Unlike before, we get an error message this time. In this case the problem is the b=2 line, followed by an intended print statement. The fix here is to either indent b=2 or dedent the print statement. The correct approach will be dependant upon what your code is suppose to do. And lastly:
Can you write Python code for it?

a = b =  ""

# Version (A):
if a:
    pass
    if b:
        pass

# Version (B):
if a:
    pass
if b:
    pass


I am working on the file "A Beginners Guide to Python/19. For-Loops.ipynb".
The first task is:
From this code snippet I want you to understand a few things. The first is that "num" takes on all the values 1...5 sequentially. The other (main) thing I want you to realise is that within the context of the loop "num" works just like any other normal variable name. And that means we can test it (is it prime, even, divisible by 6, etc) or operate on it (e.g. +,-,/, etc), append it to lists and so on.
Okay, lets heat things up a little. Lets imagine we have a task which states:

find every single possible sequence of length 2 (XY) where X and Y are in the English alphabet.
for example: "aa", "ab", "ac"... "zz"

Okay, how many combinations are there? Well, we have 26 possible letters and for each letter there are 26 continuations. 26*26= 676. Thats a lot of work for a human being to do by hand, but fortunately for us we have an unthinking machine that can do it all the grunt work. How could we go about solving this task?
Can you write Python code for it?

import string
alphabet = string.ascii_lowercase # this string is simply abc...xyz

combinations = []
for letter in alphabet:
    for letter2 in alphabet: # a for loop-inside a for-loop! Sexy.
        sequence = letter + letter2  # aa, ab, ac, etc
        combinations.append(sequence)

print(len(combinations)) # length of the list
print(combinations[:10], "...", combinations[-9:]) # because the list is so long we are printing two smaller slices of it.


I am working on the file "A Beginners Guide to Python/19. For-Loops.ipynb".
The first task is:
Okay so this code starts with a list of 2 items, and for each item it adds a new item to the list (item + 2). Because we are constantly adding one to the list we never reach the end of the list, and so therefore the program never terminates.
The 'bandage' fix in place here is a loop_counter. This simply keeps check of how many times we have gone through the loop. Once we hit 100 loops we execute "break" to escape the loop. And thus the program doesn't waltz toward infinity.
There is another (often better) way to handle this issue however, and that is to separate out the thing we wish to iterate over and the thing we wish to change. Here's an example:
Can you write Python code for it?

lst = [1,2]

for item in lst[:]:  # <--- see lecture on splicing, lst[:] is a COPY of the lst, not the list itself.
    lst.append(item+2)
print(lst)


I am working on the file "A Beginners Guide to Python/19. For-Loops.ipynb".
The first task is:
So as you guys can see, it wasn't actually that difficult to enumerate all the combinations. Using for-loops inside for loops is a powerful way of expressing this type of problem but one should always be aware of the maths involved if we wanted to write code in this way for sequence of length five thats 26**5 or 11881376 possible combinations. And so, although double for-loops is a super useful tool to have at ones disposal the reality is an algorithm of the order O(n**2) (see wiki's 'Big O notation' article) isn't practical for large problem sets.
Cautionary tale, beware of the infinite!
When using for and while loops it is possible to accidentally create programs that never terminate. And thats err, usually bad.
There are several ways to make this mistake and below I'm going to showcase one particular issue. Although, since I don't want to crash my computer I have added a 'safety valve' which will allow us to run the code safely.
Can you write Python code for it?

lst = [1,2]

loop_counter = 0
for item in lst:
    lst.append(item+2)
    loop_counter +=1         # every time we go through the loop, we add one to our counter.
    if exit_counter == 100:  # if we have gone through the loop 100 times...
        break                # we escape the loop.
print(lst)


I am working on the file "A Beginners Guide to Python/19. For-Loops.ipynb".
The first task is:
The difference here, as noted in the comment, is that lst[:] is a copy of lst and this copy doesn't actually change as lst changes, and so therefore the program terminates.
Homework Assignment
Your task this week is to make a program named "fizzbuzz". The rules

Your program should go through the numbers 1 to 100,
if the number is even, print "fizz"
if the number is divisible by 5 print "buzz"
if the number is both even and divisible by 5 print "fizzbuzz"
otherwise just print the number.

For extra points:
For extra points you need to make your code easily modifiable. I want to be able to:

change the string printed out (e.g change 'fizz' to 'dave', or some other string)
be able to change the total
change one(or both) of the rules (e.g. change the rule to be divisible by 6, not 5)

To do this you should ask the user what the rules should be (Hint you should use Pythons "input" Function).
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/20. Functions & Namespaces.ipynb".
The first task is:
Basically, the "A" inside the function is separate from the "A" outside of it. Here's another example:
Can you write Python code for it?

def variables():
    x = 10
    y = 10

print(x + y) # <--- note the error message; NameError


I am working on the file "A Beginners Guide to Python/20. Functions & Namespaces.ipynb".
The first task is:
In short, self-contained blocks of code (e.g. functions) have their own 'namespace', and that means if a variable is defined inside that block then that variable is said to be defined locally.
Homework Assignment
Your homework assignment this week is to write a function called 'average'. it takes a list as input and returns the arithmetic average. You should assume the list only contains numbers.
It should have a docstring explaining what it does (hint: """text""").
Hints:
* len(input) will give you the length of the list.
* sum(input) will add everything up.
* If the list is empty, return the string "EMPTY LIST"
 FOR BONUS POINTS

You are ONLY allowed to use addition and for-loops (The use of LEN and SUM is not allowed!).

The bonus problem is a bit tricky but I believe in you guys. Good luck!
Can you write Python code for it?


I am working on the file "A Beginners Guide to Python/20. Functions & Namespaces.ipynb".
The first task is:
Simple stuff right? Okay lets move on and look at the body and return statement. I’m going to create a function that calculates the hypotenuse of a triangle when given sides 'a' and 'b' (i.e. the Pythagorean Theorem).
Can you write Python code for it?

from math import sqrt # <-- importing the square root function from the math module.

def pythagoras (a, b):
    c2 = a** 2 + b ** 2
    return sqrt(c2) # note, this line would be math.sqrt(c2) if we had written 'import math' instead of 'from math...'

# Lets call it!
round(pythagoras(10, 23), 3)


I am working on the file "A Beginners Guide to Python/20. Functions & Namespaces.ipynb".
The first task is:
So our pythagoras function takes the arguments 'a' and 'b'. The second line calculates 'c2' and once we have that we return the square root of 'c2'.
Okay, lets look at a more complex bit of code, lets try to calcuate whether n is prime or not.
Can you write Python code for it?

def is_prime(num):
    """Returns True if number is prime, False otherwise"""
    if num <= 1:
        return False    # numbers <= 1 are not prime

    # check for factors
    for i in range(2,num): # for loop that iterates 2-to-num. Each number in the iteration is called "i"
        if (num % i) == 0: # modular arithmetic; this asks if num is divisible by i (with no remainder).
            return False
    return True