Today:
list(zip([1,2,3,4], ["a","s","d","f"]))
Let us write a function that will flatten a list. I.e. it takes a list of lists, and returns a list containing all the elements that it sees in any sublists.
for example: flatten([1,2,[1,2,3,4],[1,[2,[5,6]]]])
should return [1,2,1,2,3,4,1,2,5,6]
.
When there is recursion, there is always a basic idea you can write down, like this:
the flattening of a list is the flattening of the first element of the list concatenated with the flattening of the rest of the list. so we could say something like: flatten(xs) = flatten(xs[0]) + flatten(xs[1:])
def flatten(xs):
return flatten(xs[0]) + flatten(xs[1:])
flatten([1,2,[3,4]])
What happened? Where is the mistake? The mistake is that we are calling flatten(xs[0])
, but xs[0]
might not be a list, e.g. xs[0]
might be a number, in which case we want flatten(5)
to return 5
. To check whether something is a list, you use isinstance
.
def flatten(xs):
if isinstance(xs, list):
return flatten(xs[0]) + flatten(xs[1:])
return [xs]
flatten([1,2,[3,4]])
more mistakes?!?!! what is the problem? It's that we are calling flatten[xs[1:]]
when xs
is a list of length 1, so there is no xs[1]
.
def flatten(xs):
if isinstance(xs, list):
if len(xs)>0:
return flatten(xs[0]) + flatten(xs[1:])
else:
return []
return [xs]
flatten([1,2,[3,4]])
flatten([1,2,[3,4,[1,2,1,2,2,[9,9]]]])
It works!
These expressions/functins allow you to do a lot using lists. Expecially with one-liners.
Especially lambda
is used quite often. map
, reduce
and filter
are not used very often. BUT: it's important for us that we learn the ideas that they represent. That's why we will do exercises with them.
A lot of times we write functions like:
def f(x,y,...):
return ...some_expression_using_x_y_...
There is a nice shortcut for this.
lambda x,y,... : ...some_expression_using_x_y_...
f = lambda x: x*x
f(5)
(lambda x,y : x + y)(2,5)
lambda
comes from Lambda Calculus, which was a model of computation that mathematicians thought about before computers existed. It was invented by Alonso Church (who was the advisor of Alan Turing)
Say I want to apply a function to every element in a list.
map(f,xs)
, returns: [f(xs[0]), f(xs[1]),...]
. i.e. it applies f to each element of xs.
from math import floor
list(map(floor, [1.234, 2.1234145, 3.42424, 4.525]))
Remark: In Python 2, this was just map
, but in Python 3, we have to se list(map(...))
to get a list.
In fact, this is the same as using list comprehension:
[floor(x) for x in [1.234, 2.1234145, 3.42424, 4.525]]
We won't be using map
much and will use list comprehension instead. I did use map
in the past for parallel computing. If you call map(f,xs)
, then Python knows that f(xs[0]), f(xs[1]),...
are all separate computations that don't depend on each other. So it can run each computation in parallel on separate cores of your processor (if you ask it to do so). This is called multithreading.
When used, map is often combined with lambda.
list(map(lambda x: 2**x, range(15)))
This would be much nicer with list comprehensions but still good to know.
[2**x for x in range(15)]
filter(f, xs)
returns a list containing the elements of xs for which f
returns True
(or something else Python interprets as True
)
# our trusty old isprime
# note that isprime returns bool
def isprime(n):
if n <= 1:
return False
d = 2
while d*d<=n: # so clever!!!
if n % d == 0:
return False
d += 1
return True
list(filter(isprime, range(20)))
But again we already know how to do this:
[x for x in range(20) if isprime(x)]
This is the one that's really new for us because it makes very nice one-liners, but it's in a library called functools
. (it used to be standard in Python 2)
reduce(f, xs)
, takes a function f(x,y)
of two variables, and applies the function first to x[0]
and x[1]
, getting f(x[0], x[1])
. And then applies f
to f(x[0], x[1])
and x[2]
, getting f(f(x[0], x[1]), x[2])
,...
from functools import reduce
reduce(lambda x, y: x+y, range(10))
Let's see what happened:
lambda x, y: x+y
is the addition function. range[10]
is [0,1,2,3,4,5,6,7,8,9]
reduce(lambda x, y: x+y, range(10))
first computes 0+1
, takes the result 1
and adds it to 2
, then takes the result 3
and adds it to the next element 3
.
reduce(lambda x, y: x*y, range(1,10))
$362880$ is $10!$
factorial = lambda n: reduce(lambda x, y : x*y, range(1,n))
factorial(10)
Do you remember the homework problem when we were supposed to write a function dupli(xs,k)
that takes a list and returns the same list but with each element repeated k
times:
xs = [1,2,4,1,2]
diplo = lambda xs, k: reduce(lambda x, y: x+y, map(lambda a: k*[a], xs))
# this is clearly not a good way to implement this function!! It's hard to read.
# still, it's cool that we can do this and it's good exercise for the brain
diplo(xs, 3)
apply(n,f,x)
below doing? apply = lambda n,f,x : reduce((lambda y, g : g(y)),([x] + n*[f]))
apply(1, lambda x: x+1, 1)
apply(3, lambda x: x*x, 2)