## 6.009 Fall 2017, Lecture 2 - Decomposing a Program into Functions
## Basics of thinking about functions and how they connect to other code
def divide(n, m):
"""Plain old numeric division (packaged as a function for pedagogical purposes)
Precondition: n and m are positive integers, and m is nonzero.
Postcondition: m*r is in the interval (n - m, n]."""
assert n >= 0
assert m > 0
# Here we check the precondition.
# If this check fails, the *caller* is to blame!
r = n // m
assert n - m < m * r
assert m * r <= n
# Here we check the postcondition.
# If *this* check fails, it's *this* function that is to blame!
return r
# A quick demonstration of named and optional parameters:
def divideFancy(n, m=2):
return n / m
def maximum(ns):
"""Find the largest element of a list of integers.
Precondition: input list ns is nonempty and contains only integers.
Postcondition: result (1) is in ns and (2) is >= every element of ns."""
# Enforce the precondition:
assert len(ns) > 0
best = ns[0]
for n in ns:
if n > best:
best = n
# We could just have written max(ns) in the first place, but this example
# has educational value.
# Let's check both parts of the postcondition.
found = False
for n in ns:
assert best >= n
if best == n:
found = True
assert found
# We can do it more concisely (and perhaps less understandably, at first)
# using more built-in Python goodness.
assert best in ns
assert all(best >= other for other in ns)
return best
def average(ns):
"""Find the average of a list of integers.
Precondition: input list ns is nonempty and contains only integers.
Postcondition: return value is the average value of the list."""
assert len(ns) > 0
# Here we check a precondition of this function.
# It is crucial that this condition hold, or we would violate the preconditions of 'maximum' and 'minimum'!
total = 0
for n in ns:
total += n
r = total / len(ns)
# Sometimes it's prohibitively inconvenient to check the full postcondition.
# For instance, the only appropriate check might look just like this function itself,
# since the English description was so vague (or at least not algorithmic).
# We can still add some sanity-check properties.
assert r >= min(ns)
assert r <= maximum(ns)
return r
def gcd(x, y):
"""Precondition: y is greater than zero.
Postcondition: returns greatest common divisor of x and y."""
assert y >= 0
xinit = x
yinit = y
while y > 0:
x, y = y, x % y # Not the same as x = y followed by y = x % y
# Check: x is truly a common divisor.
assert xinit % x == 0
assert yinit % x == 0
# Check: there is no greater common divisor.
assert all (xinit % i != 0 or yinit % i != 0
for i in range(x+1, min(xinit, yinit)+1))
return x
def test():
assert divide(1, 2) == 0
assert divide(8, 2) == 4
assert divide(7, 2) == 3
assert divide(9, 2) == 4
assert maximum([1, 2, 3]) == 3
assert maximum([1, 3, 2]) == 3
assert maximum([1, 2, 1, 2]) == 2
assert average([2, 4, 6, 8]) == 5
# Getting lazy with test cases here.... ;)
# assert gcd(7, -3) == 8
# Not fair: precondition violated!
assert gcd(13, 144) == 1
assert gcd(8, 2) == 2
assert gcd(15, 45) == 15
assert gcd(6, 21) == 3
# When you're writing functions to be used by other people,
# it's often rude to ask those folks to poke around your source files!
# Instead, you can use *documentation generators* to turn your comments into pretty web pages.
# For instance, we can document the above by running, at a command line in this directory:
# pydoc -w lecture2
# Now check out lecture2.html.
# Much fancier documentation tools are available, but this is a good start.
## Drawing pictures in web browsers
import svg
# Here we import a simple code library created just for this lecture demo.
# We'll be creating pictures in a format called Scalable Vector Graphics (SVG):
# https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
# The details aren't too important, as they are hidden inside the 'svg' library imported above.
def fish():
"""A small artistic masterpiece: a drawing of a fish"""
return svg.group([svg.line(x1=150, y1=75, x2=200, y2=25),
svg.line(x1=150, y1=75, x2=200, y2=125),
svg.curve(x1=200, y1=25, x2=-100, y2=75, x3=200, y3=125),
svg.circle(x=120, y=70, radius=10),
svg.line(x1=50, y1=75, x2=5, y2=35),
svg.line(x1=50, y1=75, x2=5, y2=110),
svg.line(x1=10, y1=30, x2=10, y2=110)])
def draw_fish():
"""Here's an example of how to render a drawing to an HTML file that you can open in your web browser.
By default, that file will be written into /tmp/test.html."""
svg.render(fish())
def getting_further_to_the_right(pics, dist):
"""Given a picture list 'pics' and a distance multiplier 'dist',
displace each ith element of pics further to the right by i*dist pixels.
In other words, we might start with a list of pictures all stacked on top of each other,
and we want to spread them out uniformly over horizontal space."""
for i in range(len(pics)):
pics[i] = svg.translate(pics[i], x=dist*i)
def same_size_fish():
"""Three fish in a row"""
rs = [fish()] * 3
getting_further_to_the_right(rs, 180)
return svg.group(rs)
def getting_smaller(pics, by):
"""Similar to 'getting_further_to_the_right'.
We modify each element of a list, size-scaling the ith element by a factor of by to the ith power."""
for i in range(len(pics)):
pics[i] = svg.scale(pics[i], by=by**i)
def school1():
"""Here's a group of fish all trying to eat each other, but not lined up so well in space."""
all_fish = [fish()] * 5
getting_smaller(all_fish, 0.8)
getting_further_to_the_right(all_fish, 250)
return svg.group(all_fish)
def getting_further_to_the_right_and_shrinking(pics, dist, by):
"""This function combines 'getting_further_to_the_right' and 'getting_smaller',
taking the scaling into account in deciding how far to the right to put each picture."""
skip = 0
for i in range(len(pics)):
pics[i] = svg.translate(svg.scale(pics[i], by=by**i), x=skip)
skip += dist * (by**i)
def school():
"""Now we can draw a proper-looking school of fish trying to eat each other."""
all_fish = [fish()] * 5
getting_further_to_the_right_and_shrinking(all_fish, 180, 0.8)
return svg.group([svg.group(all_fish),
svg.text("Uh oh", x=620, y=20),
svg.line(x1=600, y1=30, x2=618, y2=20)])
def rectangle(x1, y1, x2, y2):
"""With lines available as a primitive, it's natural to build up hollow rectangles.
This function draws one with corners (x1, y1) and (x2, y2)."""
return svg.group([svg.line(x1=x1, y1=y1, x2=x2, y2=y1),
svg.line(x1=x2, y1=y1, x2=x2, y2=y2),
svg.line(x1=x2, y1=y2, x2=x1, y2=y2),
svg.line(x1=x1, y1=y2, x2=x1, y2=y1)])
def tv():
"""Another simple shape: something looking vaguely like a television set"""
return svg.group([rectangle(0, 0, 100, 100),
rectangle(10, 10, 90, 80),
svg.circle(x=15, y=90, radius=5)])
def watches_tv(pic):
"""A generic operation on pictures: make one instance of the picture watch another, smaller instance on TV.
Warning: the pixel arithmetic here isn't very flexible, but it works well enough for a few examples here."""
return svg.group([pic,
svg.translate(tv(), x=220)])
def watches_self_on_tv(pic):
"""A generic operation on pictures: make one instance of the picture watch another, smaller instance on TV.
Warning: the pixel arithmetic here isn't very flexible, but it works well enough for a few examples here."""
return svg.group([pic,
svg.translate(tv(), x=220),
svg.translate(svg.scale(pic, by=.3), x=240, y=15)])
def fish_watches_self():
"""Example: the fish watches itself."""
return watches_self_on_tv(fish())
def nested_watching(pic, levels):
"""We can do weird things with repeated nesting! levels tells us how many
times to repeat calling watches_self_on_tv."""
for _ in range(levels):
pic = watches_self_on_tv(pic)
return pic
def incredible_shrinking_fish():
return [svg.scale(fish(), by=1/i)
for i in range(1, 5)]
def the_fittest_at_time(t):
"""Behold the harsh Darwinian reality of the undersea world.
Returns the fish that survive at time step t.
Each time step sees the smallest fish eaten. *GULP*!"""
all_fish = [fish()] * (5 - t // 5)
getting_further_to_the_right_and_shrinking(all_fish, 180, 0.8)
return svg.translate(svg.group(all_fish), x=t*5)
def smug_fish():
"""The last frame of survival of the fittest"""
return svg.group([the_fittest_at_time(24),
svg.text("Heh heh", x=330, y=20),
svg.line(x1=320, y1=35, x2=338, y2=25)])
def survival_of_the_fittest():
"""Animates some fish eating each other (sort of)."""
return [the_fittest_at_time(t)
for t in range(25)] + [smug_fish()]
def render_survival_of_the_fittest():
svg.render_animation(survival_of_the_fittest())