Module 1 Probability
Introduction to Python
We will be using Python and C++ for the proramming tasks in this course. We need to assume knowledge of basic C++, but nothing of Python.
Programming topics that might turn up on the exam is:
- list and vector comprehension
- inline functions (lambdas)
- reduce, map and filter
- lazyness
We will discuss these topics (and others) in more detail in the last module of this course. For now we do a short crash course, so that you can familiarise yourself with some of the syntax and can start writing code.
We start with some Python.
Setup
First of all you should install python. The recommended build is WinPython which includes all necessary libraries. Once downloaded, start the IDE (called Spyder). Type
# This is a comment
print("Hello World")
and run your first program. Congratulations! You are now a Python programmer.</p>
Variables
With that out of the way, lets declare some variables
a = 10
b = 10.0
c, d = 3,2 # simultaneosly assigns c and d
e = "Hello World"
f = True
Type information is inferred from the context. We can see what types our variables have been assigned by
print(a, " is of type ", type(a))
print(b, " is of type ", type(b))
print(c, " is of type ", type(c))
print(d, " is of type ", type(d))
print(e, " is of type ", type(e))
print(f, " is of type ", type(f))
Arithmetic operations
Similar to C++, however
print(2**3) # exponentiation
print(10 / 4) # float division
print(10 / 2) # float division
print(10// 4) # integer division
Comparison and logical operations
Comparison is as in C++
Logical operations are written with english
x and y # x && y
x or y # x || y
not x # !x
Bitwise logical operations are as in C++.
Conditionals
If statements can be written as follows
if 11 > 10: print("True")
else: print("False")
Several statements can be executed using semi-colon
if 11 > 10: print("True"); print("Twice")
else: print("False"); print("Twice")
by including a whole block.
if 11 > 10:
print("True")
print("Twice")
else:
print("False")
print("Twice")
Blocks are indicated by indentation (so no curly brackets). C++ "else if" is "elif"
if 10 > 11:
print("False")
elif 11 > 10:
print("True")
else:
print("False")
A long code line can be split up by using backslash, for instance,
variable1 = variable2 + \
variable2
Loops
Note, the ++ operator does not work in Python. Here is a while loop
i = 0
while i < 100:
print(i)
i += 1
The equivalent of the C++ for loop
for(int i = 0; i < 100; i++) {
std::cout << i << std::endl;
}
doesn't exist in Python. Instead, for-loops in Python need to iterate over containers (or something similar). More about containers in a moment, for now
for i in range(100):
print(i)
will do. Loops can of course be nested
for i in range(10):
for j in range(10):
print((i,j))
Functions
Complicated transformations of data should be put in functions. We look at two approaches to defining functions in Python. First
def myadd(a, b):
return a+b
Which is called by:
myadd(10,20)
A nice thing about function is that they work for any type which has the operator +. E.g. you don't need a separate function if you want to add floating point numbers. Calling
myadd(10.3,20.1)
will work fine. Another approach is to give an inline definition, using lambdas. This is extremely useful for short functions with short lifespans. The inline definition needs the "lambda" keyword
myadd = lambda a, b : a+b
The function is called in the same way. More about lambdas in Module 6.
Modules
Modules are Python's name for external libraries. For instance, say you want to use the function random() which computes a pseudo-random floating point number in $[0,1)$. The function resides in the library random. Any of the following would allow you to use random:
# import everything from library
import random
# call function by
random.random()
#import everything, but change name
import random as rnd
# call function by
rnd.random()
# select what to import from library
from random import random
#call function by
random()
# import everything from library
from random import *
# call function by
random()
Containers
When we would like to do computations with more data than can be held in a single variable, we need to put them in a container of some kind. For now we work with lists and tuples.
Lists, tuples and strings
Some variable declarations creating containers
a = "This is a string"
b = [1,4,9,25,36] # list declaration
c = (1,4,9,25,36) # tuple declaration
d = range(5,15) # range declaration
e = list(range(10)) # a range changed into a list
f = [[1,2,3,4],[4,5,6,7]] # a list of lists
A range is a sequence of numbers which is not stored in memory. So the following statements are equivalent
# these two containers are equal
g = [5,6,7,8,9,10,11,12,13,14]
g = list(range(5,10))
# these two containers are equal
h = [0,1,2,3,4]
h = list(range(5))
You can read elements using [ ] and either access one element or a selection of elements
print(a[0])
print(b[1])
print(c[-1]) # index from the back
print(d[:3]) # elements 0,1,2
print(e[1:3]) # elements 1,2
print(f[1])
print(f[1][1])
print(f[1][2:]) # elements 3,4 in the list f[1]
Only lists allow assignments. The following statements all result in errors
a[1] = 1
c[1] = 1
d[1] = 1
Some valid assignments
b[0] = 1
b[1:4] = [1,2,3,4]1
b[-1] = 101
f[0] = 101
f[1] = [0,0,0,0]1
f[1][2] = 5
Try them out and figure out what happens.
A final remark:
a=[]
b=()
creates the empty list and the empty tuple, respectively.
Python for-loops
There are no equivalent of the C-style for-loop. You can however loop over containers
for i in [1,2,3,4,5]:
print(i)
Ranges are sequences of numbers not stored in memory, which you can iterate over
for i in range(1,6):
print(i)
You can also create containers using for loops (called "list-comprehension"), for example:
a = [i for i in range(5)]
b = [i*i for i in range(5) if i % 2 == 0]
c = [myadd(10,i) for i in range(5)]
d = [i+j for i in range(2) for j in range(2)]
There is a whole range of other objects you can iterate over, but that will have to wait until later in the course
Lambdas, map, filter and reduce
The operation "map" takes as input a function and a list, and then returns the result of applying the inline function to each element of the list. The list itself is left unchanged. For instance the code
a = [1,2,3,4,5,6,7,8,9,10,11,12,13]
b = list(map( lambda x: x*x, a))
computes the squares of the numbers in the list a, and puts them in a list b. The operation "filter" takes as input a predicate (i.e a function returning boolean) and a list, and then returns all the elements of the list where the function evaluates to true. For example the code
b = list(filter(lambda x: x < 10,b))
removes all elements greater than $9$ from the list $b$. Both map and list return results which are not yet computed, but which we still can iterate over,
# no storage or computations needed so far
b = filter(lambda x: x < 10, b)
# computation happens here
for i in b:
print(i)
# b is now empty
But iterating over those objects does empty them of elements. For now, I suggest always converting the result to list.
Before explaining the function reduce (which is found in the library functools), lets consider an example
from functools import reduce
b = [1,3,5,7,9]
b = reduce(lambda x, y: x+y, b)
which sums the element of the list b. Reduce applies the function to the first two elements of the list, computing $1+3$ with answer $4$. It then computes $4+5=9$, using the answer from the previous computation and the next element $5$ in the list. Then $9+7=16$ is computed, and finally $16+9=25$.
You don't have to use lambdas with these operations, for instance
def myadd(x,y):
return x+y
b = reduce(myadd, b)
is an alternative.
Map, reduce and filter are operatons which are typically used when you do what is called functional programming, which is the subject of Module 6. For now we conclude by introducing one additional operation. The line
d = list(zip(mycontainer, myothercontainer))
creates a list of tuples from the elements of two containers. Just like the operations above, the elements are not computed until you ask for them. The two lines
d = list(zip([1,2,3],[4,5,6]))
d = [(1,4),(2,5),(3,6)]
have exactly the same result.
It is also possible to implement these functions ourselves using what we have learned so far. In this example we implement map and filter using use list-comprehension:
# 1st definition
mymap = lambda f, l : [f(x) for x in l]
# 2nd definition
def mymap(f, l):
return [f(x) for x in l]
# 1st definition
myfilter = lambda p, l : [x for x in l if p(x)]
# 2nd definition
def myfilter(p, l):
return [x for x in l if p(x)]
Our implementations can be called without using the added "list" in front.
Different styles
Python is a rich language with many possibilities for expressing a solution to a problem. Lets look at a simple problem where we create a container with some numbers and perform an operation on those numbers. We will
- create a container containing squares $i^2$ for $i=0,\cdots,20$,
- then create a container for odd squares $i^2$ for $i=1,3,5,\cdots, 19$,
- and finally compute the alternating sum of both, i.e. $(1^2-2^2+3^2-\cdots)$ and $1^2-9^2+25^2-\cdots$.
First attempt:
# create container for squares
squares = []
for i in range(21):
# append adds an element to the back of a list
squares.append(i*i)
# create container for odd squares
oddsquares = []
for i in range(21):
if i % 2 == 1: oddsquares.append(i*i)
# create a function for alternating sum
def altsum(ls):
sgn = 1
retval = 0
for i in ls:
retval += i * sgn
sgn = -sgn
return retval
# print results
print("sum:", altsum(squares))
print("sum:", altsum(oddsquares))
Now lets do the same with list-comprehension or map and filter:
from functools import reduce
squares = [x*x for x in range(1,21)]
oddsquares = [x*x for x in range(1,21) if x % 2 == 1]
squaretuples = list(zip(squares, range(100)))
oddsquaretuples = list(zip(oddsquares, range(100)))
altsquares = [x*(-1)**y for (x,y) in squaretuples]
altsquares = list(map(lambda w: w[0]*(-1)**w[1] , squaretuples))
altoddsquares = [x*(-1)**y for (x,y) in oddsquaretuples]
print(sumsquares)
print(sumoddsquares)
squares = list(map( lambda x : x*x , range(1,21)))
oddsquares = list(filter(lambda x : x % 2 == 1 , squares))
squaretuples = list(zip(squares, range(100)))
oddsquaretuples = list(zip(oddsquares, range(100)))
altsquares = list(map(lambda w: w[0]*(-1)**w[1] , \
squaretuples))
altoddsquares = list(map(lambda w: w[0]*(-1)**w[1] \
, oddsquaretuples))
sumsquares = reduce(lambda x, y: x+y, altsquares)
sumoddsquares = reduce(lambda x, y: x+y, altoddsquares)
print(sumsquares)
print(sumoddsquares)
The first solution is definitely easier to read, especially after just one semester of programming. But in this course you should also learn how to program in the other, more functional, style.