7 concepts23 worked examples27 exercises5 exam-level7 figures
What are you here for?
12 Random walks: three small classes, one walk function, and the numbers and pictures that come out of running it many times
Start with this
One question before you read anything. Getting it wrong is the point: it shows you what this section is for.
§12.1 — what seeding a generator does
Three dice rolls with a in the middle. Nothing about walks yet: this is only about what random.seed controls. If you have not met the module before, read the answer, that is what it is here for.
Find(a) Write exactly what this program prints, both lines.
Given
import random
random.seed(4)
a = random.randint(1, 6)
b = random.randint(1, 6)
random.seed(4)
c = random.randint(1, 6)
print(a, b, c)
print(a == c, b == c)
IPython console
In [1]: %run untitled0.py
The third roll is the first roll again, because the stream was restarted from the same place. That is the whole content of seed: it chooses where the sequence begins, not how random the numbers are. It is also the reason a seed call belongs above the loop and not inside it.
Hint 1/4
Decide what the second seed call does to the stream before you think about the numbers themselves.
Hint 2/4
Seeding fixes where the stream starts, so the first number after a seed call is always the same for that seed. Rolls keep coming from the same stream until it is reset.
Hint 3/4
The program seeds with 4, takes two rolls into a and b, seeds with 4 again, and takes one roll into c. So c repeats the first roll of the stream, not the second.
Hint 4/4
One line with the three numbers, then one line with the two comparisons.
Show solution
Track the stream
$$a = \text{first roll of stream } 4$$
the seed call puts the generator at a known place
$$b = \text{second roll of stream } 4$$
no seeding between them, so the stream continues
$$c = \text{first roll of stream } 4$$
the second seed call restarts the same stream
Read the comparisons
$$a == c \Rightarrow \texttt{True}$$
both are the first roll of the same stream
$$b == c \Rightarrow \texttt{False}$$
the second roll of a stream need not equal the first, and here it does not
Independent check: the two comparison values must follow from the first line whatever the numbers are, and they do, since the first and third numbers printed are equal while the second differs.
A seed is a position in a stream. Anything that resets the position resets the results.
What this looks like in Spyder
The tab next to Help. It lists every name your program left behind with its type and its size, so it answers the one question a listing cannot: what is in this name right now. The stepper on this page shows the same four columns.
Spyder 5.5.1 on Linux, running the program in its own editor. A different version moves a few toolbar icons; the panes are named the same.
A farmer stands in the middle of a field. Every second she takes one step, north, south, east or west, and the direction is whatever the second happens to bring.
After a hundred thousand seconds, how far from the middle is she? Two answers sound equally good:
the steps cancel on average, so she is back near the middle
a hundred thousand steps is a long way, so she is far off
Nothing you can do on paper settles which one it is.
By the end of this section you can write the program that answers this, say what its distance does when the number of steps is multiplied by four, and draw the one picture that shows a reader the same thing.
In 60 seconds
A simulation is three cooperating classes plus one function that runs the process once, and the answer is never one run: it is the mean of many runs, reported with its and drawn once.
Any unbiased walk on a grid. The four pairs are read as (change in x, change in y), each has one chance in four, and a biased walk is the same call on a list that is not balanced.
Whenever a step is taken. The call returns a new object and leaves the old one alone, which is what makes it safe for two drunks to share one starting place.
Distance from the start
$$d = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2}$$
The number a walk reports. It is the straight line back to the start, so a walk of 20 steps can report 2.83, and a walk that comes home reports 0.
Reporting the result. The doubling rule holds for the fair walk; a walk with a bias in it grows with the number of steps instead, which is how you tell the two apart from the numbers alone.
Three most common mistakes
Reporting one trial as the answer. Ten walks of the same length landed between 2.0 and 15.3 on this page, so a single number from a single walk is not a measurement of anything.
Leaving the drunk where the last trial left him. The reported distances still look right, since each walk measures from where it started, but the walker is never at the again, so every plotted end location and every distance from the origin belongs to a different experiment.
Reading the start location after the loop instead of before it. The distance then comes out 0.0 for every walk, and 0.0 looks like an answer rather than like a bug.
The weights are labs 20, midterm 40 and final 40, so this week is final exam material. The ten labs end with the plotting lab, and the course archive holds no lab, so this material is practised in the exam.
In one archived final paper the two heaviest questions had the two shapes this page drills:
write a class with named methods, then a script that uses it
trace a short program and write its exact output
That is one paper and not a rule, so read your own paper's point table.
How much time do you have?
10 minutes
The one function every question here is built on, the three lines that make a trial honest, and the four boxed results.
The 60-second card · The walk function, and the checks that make it believable · One walk answers nothing · Formula card
45 minutes
Enough to write the whole simulation from a written specification and to trace someone else's version: the three classes, what each one owns, the walk function and its , and the trial loop that makes the mean mean something.
The 60-second card · One step, picked at random · Location: a place that hands back a place · Field: a dictionary from walker to place · The walk function, and the checks that make it believable · One walk answers nothing · Scaffolding comes off · B · computation
full read
Everything, including the growth rule and how a biased walk breaks it, the choice of picture for each question, and the twenty two ways this material goes wrong.
The 60-second card · Recall first · Conventions · One step, picked at random · Location: a place that hands back a place · Field: a dictionary from walker to place · The walk function, and the checks that make it believable · One walk answers nothing · How the mean grows, and what a bias does to it · Drawing the answer · Method boxes · Look-alike pairs · Scaffolding comes off · Full exam-style question · Practice set · Mistake ledger · Glossary
By the end of this section
Predict what one random step produces: read a choice call over a list of pairs, unpack the pair, and say what seeding does to a run.
Trace a Location through a move call: say which object is new, which one is unchanged, and what the distance between two places comes out as.
Explain what the field's dictionary holds, what a second addDrunk call for the same drunk does, and which key a move rebinds.
Write the walk function so that it reports the distance from the starting place, and test it with walks of zero, one and two steps.
Run many trials of the same walk, report the mean with its largest and smallest, and reset the walker before every trial.
Compare growth rates from a table of means: say what quadrupling the steps does to a fair walk and what it does to a walk with a bias.
Choose the picture that answers the question asked, give each class its own plotting style, and name what a chosen axis limit can hide.
Syllabus coverage
covered
Random Walks — covered
The drunkard's walk as the course builds it
one random from a list of pairs
the Location
Field and Drunk abstractions
a walk function that returns the distance from the start
trials over that function
the mean and its spread
sanity checks on tiny walks
biased walkers written as subclasses
covered
Data Visualization — covered
Choosing and finishing the picture: the scatter of end locations against the mean distance curve, one style per walker class handed out by a small class, title, axis names and legend, identical axis limits when two panels are compared, and what a frame that hugs the data hides.
covered
Chapter 14 — covered
The chapter this week's line names. Its sections are the walk itself, the sanity checks, the step to several walker classes at once, the plotting of the results, and fields where the places themselves behave oddly, which is an exercise in this page's field block.
deferred
fitting a curve to the measured means — deferred
The measured means on this page sit almost exactly on a square root curve, and the natural next question is how to fit that curve and measure how well it fits.
Deferred to the next section, understanding experimental data, where the syllabus puts fitting and where the fit, not the simulation, is the subject. This page measures the growth by quadrupling the steps and comparing means, which needs nothing but division.
off syllabus
the same simulation written with arrays — off syllabus
The array library can hold the trial distances and take their mean in one call, and a lab that already has the data in arrays would do it that way.
Not in this week's line. It appears here in one sentence only, because the mean of a list of distances is the same number either way and this page keeps to lists so that the trial loop stays visible. Not examinable from this page.
Recall first
A class, its constructor and self
class Location(object): with def __init__(self, x, y): inside it, and self.x = x creating a slot on the object being built. A call Location(0, 0) runs that method and hands back the object.
All three classes of the simulation are written this way, and one of them is written twice on this page, once correctly and once with one line changed.
Inheritance and overriding one method
class UsualDrunk(Drunk): inherits everything Drunk has, and a method defined again in the subclass replaces the inherited one for objects of the subclass.
Every kind of walker on this page is a subclass that changes exactly one method, takeStep, and nothing else.
A dictionary key can be any object
d[key] = value stores under the key, key in d asks whether it is there, and for k in d: walks the keys. An object you defined yourself is usable as a key, and it is its own key: two objects built separately are two different keys even when their contents look the same.
The field is one dictionary whose keys are drunk objects, and the two mistakes it invites both come from that sentence.
Two names for one object
After b = a there is one object and two names for it. a is b is then True. A method that changes the object is seen through both names; a method that returns a new object is not.
Two drunks are put on the same starting Location on this page, and whether that is safe depends entirely on this distinction.
Unpacking a tuple into two names
xDist, yDist = (1, 0) binds 1 to the first name and 0 to the second. The right hand side must have exactly as many items as there are names on the left.
A step arrives as one pair and is used as two numbers, in one line, in the field's move method.
Try it yourself first (1 questions)
1§12.3 — two objects that print the same
Two drunk objects are built with the same name and both are used as dictionary keys. This is the trap this section is built on, so getting it wrong here is useful.
Find(a) Write exactly what this program prints, all three lines.
Given
classDrunk(object):
def__init__(self, name):
self.name = name
def__repr__(self):
returnself.name
one = Drunk('Homer')
two = Drunk('Homer')
field = {}
field[one] = 'left'
field[two] = 'right'print(len(field))
print(field[one], field[two])
print(one == two)
IPython console
In [1]: %run untitled0.py
The class defines only how a drunk prints, and printing has nothing to do with being a key. Both objects went in, so len is 2, and one == two is False because no comparison method was defined, which leaves the default one: two objects are equal only if they are the same object.
Hint 1/4
Ask how many keys the dictionary ends up with before you ask what is stored under them.
Hint 2/4
A dictionary key is the object, not what the object prints as. Two objects built by two separate calls are two different keys unless the class says otherwise.
Hint 3/4
Here one and two are two separate Drunk('Homer') calls, and each is used once as a key, so both entries survive and each keeps its own value.
Hint 4/4
Two entries, then the two values in the order they were asked for, then False.
Independent check: if the two objects had counted as one key, the second assignment would have replaced the first and the second printed line would read right right. It does not.
The name inside an object is for the reader. The identity of the object is what the dictionary uses.
Notation
symbol
reads as
means
watch out
$(dx, dy)$
the pair change in x, change in y
One step, written as a tuple. The first number moves you east when it is positive, the second moves you north.
It is one object, not two arguments. A step is unpacked with xDist, yDist = drunk.takeStep() before it can be used as two numbers.
$random.choice(seq)$
pick one item of seq at random
Returns one of the items, each with the same chance. On a list of four pairs, each pair has one chance in four.
It returns the item, not its index, and it does not remove it. Calling it twice can give the same item twice.
$random.seed(n)$
start the generator at n
Fixes where the stream of random numbers begins, so the whole run repeats exactly on the same interpreter.
Seeding inside the trial loop makes every trial identical, which looks like a working simulation and measures nothing. Seed once, at the top.
$loc.move(dx, dy)$
the place dx, dy away from loc
A new Location, offset from this one. The place it was called on is not changed.
The result has to be kept: loc.move(1, 0) on its own line does nothing at all, the same way sorted(xs) on its own line does nothing.
$a.distFrom(b)$
the distance from a to b
The straight line between two places, from the two coordinate differences.
Symmetric, so the order of a and b does not matter, but the number is a distance and never negative. A negative result means a subtraction is inside the square root by mistake.
$f.drunks[d]$
the place the field has drunk d standing on
One entry of the field's dictionary. The key is the drunk object itself, the value is a Location.
Two drunks with the same name are two different keys, because a drunk object is its own key. The names are for printing, not for looking up.
Conventions used here
Where this section sits, and what it is practised in.
This week comes after the classes, the complexity and the plotting sections, and it uses all three.
The lab sequence has ten labs and the last is the plotting lab, so the archive holds no lab paper for this material. Treat it as exam material: the questions here are shaped like exam questions, not lab steps.
A student who looks for the matching lab and finds none concludes the week is optional. The syllabus line says otherwise.
Every printed block is a real run, and the seeded ones repeat.
Each block of output on this page came out of an interpreter, character for character, and none of it was predicted by eye. A program whose result depends on chance is seeded at the top with random.seed, so it prints the same thing on every run.
Two cautions come with that:
a different interpreter version can turn the same seed into a different stream
a seeded simulation is still a simulation, and the seed makes the run repeatable rather than the answer exact
In a programming course the printed answer is the whole claim, and a claim about a random program that cannot be reproduced cannot be checked by the reader.
Distance means the straight line back to the start.
Every distance here is the straight line from where a walk started to where it ended, computed from the two coordinate differences. Two numbers it is not:
the length of the path walked, which is only the number of steps
the count of different places visited
A 20 step walk that ends two east and two north of the start has distance 2.83.
The question the simulation answers is about where the walker ended up, and the two other numbers are easy to compute and easy to report by accident.
How numbers are printed here.
A distance kept for arithmetic is kept in full. A distance shown to a reader goes through format(value, '.2f'), or '.1f' inside a table, and the rounding happens only at the printing. Where a raw float is printed on purpose, the point is to show what Python prints when nobody formats it.
Rounding early and then summing is how a reported mean stops matching the numbers it was computed from.
What this page may use, and what it leaves alone.
Assumed from the earlier sections: numbers, strings, if, while, for, range, len, min, max, sum, sorted, format, def and return, lists, tuples, dictionaries, files, two dimensional lists, classes and inheritance, and the pyplot drawing calls. Added here: the random module, and only three of its functions, choice, seed and randint. Left alone: comprehensions, f-strings, enumerate, zip, and the array library.
The exam is closed book with a printed list of allowed functions, so anything outside that list has to be earned rather than assumed.
Naming, and the two styles the course uses.
The lecture files for this week use the mixed case style, takeStep, moveDrunk, getLoc, numSteps, with capitalised class names. This page keeps it, because the code an exam asks you to read is written in it.
Other weeks of the same course use underscores, get_name, and both are accepted. What is not accepted is switching style inside one file.
Reading a method called takeStep as if it were a new name when you have only seen take_step costs seconds that an exam does not give back.
What the pictures on this page are.
The figures are drawn from the numbers the programs printed, not photographed from a screen, so every tick value can be checked against a number in the text. Colours carry a role and keep it:
blue is where a walk starts
red is what was measured
orange is a helper line or a frame
purple is a second series being compared
A picture that disagrees with the numbers printed beside it teaches the disagreement, and a colour that means one thing in one figure and another thing in the next teaches nothing.
12.1One step, picked at random
One step is a pair picked from a list of four, and the picking can be told where to start.
The question in the opening cannot be settled on paper, so we build the walk instead. It begins with a single step.
Solvable with what we have
Compute the distance between two places whose coordinates we know.
Add a known step to a known position and get the next position.
Run a loop a hundred thousand times without complaining.
Not solvable yet
Say which way the next step goes, because nothing in the problem says.
Answer the farmer's question from one walk, since the next walk differs.
Settle it by argument, because the two arguments in the opening both sound right.
The cancelling argument, made precise on the smallest case:
the average of the four possible steps is the pair (0, 0)
so the average of two steps is (0, 0) as well
so the expected distance after two steps looks like zero
Two steps is small enough to check by hand. There are sixteen equally likely walks, and the first worked example counts them.
Why it fails
Averaging the steps is not averaging the distance. Four of the sixteen walks do come home, with distance 0.
The other twelve land 1.41 or 2 away, and a distance is never negative, so nothing cancels them. The mean distance after two steps is 1.21, not 0. The walk has to be measured.
RuleRule 12.1: one random step
Conditions
The allowed steps are written as a list of pairs, stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)], and each pair is read as (change in x, change in y).
random.choice returns one item of the list, each item with the same chance. On four items that is one chance in four.
The step arrives as one object and is taken apart with xDist, yDist = drunk.takeStep().
random.seed(n) fixes where the stream of choices starts, so the whole run repeats. One call, at the top of the program.
The module is random, imported with import random, and this page uses three of its functions: choice, seed and randint.
Read it as: pick one of the four pairs, with the same chance for each, then read that pair as how far east and how far north this step goes.
The four pairs in stepChoices, drawn where they act. The arrows are the whole model: length one, parallel to an axis, and one chance in four each. Everything else in this section is this picture repeated.
choice takes one sequence and picks from inside it, so the four pairs have to be one list. Written as four arguments it raises a TypeError about the number of arguments, which at least fails loudly.
where it ends
how many of the 16
distance
back at the start
4
0
one east or west and one north or south
8
1.4142
two steps out in one direction
4
2
Read the last two columns together: only a quarter of the walks come home, and the mean of the sixteen distances is 1.2071. The groups are not the same size, which is the part the cancelling argument misses.
counting all sixteen two step walks, to see whether they cancel
The cancelling argument says the mean distance should be zero, and two steps is small enough to settle by counting.
Each step is one of four, so two steps make sixteen walks, all equally likely. Group them by where they end.
The program below builds the same sixteen walks and prints the group sizes and the mean:
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
distances = []
for first in stepChoices:
for second in stepChoices:
x = first[0] + second[0]
y = first[1] + second[1]
distances.append((x**2 + y**2)**0.5)
home = distances.count(0.0)
far = distances.count(2.0)
print('walks:', len(distances))
print('came home:', home)
print('two away:', far)
print('mean:', format(sum(distances) / len(distances), '.4f'))
It prints:
walks: 16
came home: 4
two away: 4
mean: 1.2071
FindThe mean distance after two steps.
Given
four possible steps, so sixteen two step walks, all equally likely
distance is measured from the start, in a straight line
Solution
Group the sixteen walks by where they end
$$4 \text{ walks} \rightarrow d = 0$$
each first step has exactly one opposite second step, and there are four first steps
$$4 \text{ walks} \rightarrow d = 2$$
the same step twice, once for each of the four directions
$$8 \text{ walks} \rightarrow d = \sqrt{2} \approx 1.4142$$
the remaining ones turn a corner, one unit east or west and one unit north or south
a mean over equally likely outcomes is the plain average of the sixteen numbers
$$= \frac{11.3137 + 8}{16} = \boxed{1.2071}$$
so the distance does not cancel: it comes out a little over one step's length
Answer $$\boxed{\text{mean distance after 2 steps} = 1.2071}$$
Check
Independent check against a run of the real simulation: a walk of two steps was repeated a hundred times and the distances that ever appeared were 0.0, 1.4142 and 2.0, which are exactly the three groups counted here, and nothing else appeared.
Sixteen walks is the largest case that can be counted by hand.
The steps cancel and the distances do not. That is why the rest of this section measures distances instead of averaging steps.
five steps out of a seeded stream
The first thing to see is what one call actually hands back. This program seeds the generator, then asks for five steps:
import random
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
random.seed(4)
for s inrange(5):
print(random.choice(stepChoices))
It prints:
(0, -1)
(1, 0)
(0, 1)
(-1, 0)
(-1, 0)
Five pairs, and they are pairs rather than numbers: the second one goes east, the third north. The same seed gives the same five on the same interpreter, which is what makes a page like this one checkable.
Independent check: the same five steps are the first five of the twenty step walk drawn later in this section, and that picture shows the path leaving the origin southwards and returning to it on the fourth step, which matches these pairs.
A seeded run is a run you can argue about with someone else, because they can reproduce it.
Checkpoint
§12.1 — which call picks one step
Four lines that all look like a way to pick one of four steps. One of them does it.
Find(a) Which line binds step to one of the four pairs, each pair with the same chance?
Ask what each call is being given: one sequence, several arguments, or something that is not a sequence at all.
Hint 2/4
random.choice takes one sequence and returns one of its items. Indexing a list with [0] first hands over one pair, and choosing inside a pair returns a number.
Hint 3/4
The list here is stepChoices, whose items are the four pairs themselves, so the sequence to pass is the list and nothing else.
Independent check on types: the line that works must return something that xDist, yDist = step can unpack, and only a two item pair can be unpacked into two names.
When a call misbehaves, name the argument first. Three of these four lines are argument mistakes.
⚠ Seeding inside the trial loop
The seed call is about reproducibility, and it feels safest next to the code it is making reproducible.
wrong$$\texttt{for t in range(numTrials):}\\\quad \texttt{random.seed(0)}\\\quad \texttt{distances.append(walk(f, d, 100))}$$
right$$\texttt{random.seed(0)}\\\texttt{for t in range(numTrials):}\\\quad \texttt{distances.append(walk(f, d, 100))}$$
⚠ Passing the pairs to choice as separate arguments
In English you choose between four things, so four things go in.
A step sounds like one thing, and one thing sounds like one number.
wrong$$\texttt{x = x + random.choice(stepChoices)}$$
right$$\texttt{xDist, yDist = random.choice(stepChoices)}\\\texttt{x = x + xDist}$$
12.2Location: a place that hands back a place
A Location holds two coordinates, and its move method returns a new place instead of changing this one.
A step is a pair of numbers. To use it, there has to be something for it to be applied to.
DefinitionDefinition 12.2: the Location class
Conditions
Location(x, y) builds one place. The two coordinates are whole numbers here, because every step is one unit along an axis.
move(deltaX, deltaY) returns Location(self.x + deltaX, self.y + deltaY). It does not touch the object it was called on.
getX() and getY() hand back the two coordinates, and distFrom(other) hands back the .
__repr__ is defined so that printing a place, or a list or dictionary holding places, shows <x, y> rather than an address.
$$\boxed{\texttt{loc.move(dx, dy)} = \texttt{Location(loc.x+dx,\ loc.y+dy)}\qquad d = \sqrt{(x_1-x_2)^2+(y_1-y_2)^2}}$$
Read the first half as: asking a place to move gives you a different place and leaves this one where it was. Read the second as: the distance between two places is the square root of the sum of the two squared coordinate differences.
What here = start.move(0, 1) does to memory. Two names, two boxes, and the first box exactly as it was. This is the reason a field can put several drunks on one starting place without them dragging each other around.
Looks like this, but is not
A move method could just as well update the place it is called on, and return itself so that calls can be chained:
It works for one walker and breaks silently for two. Anybody else holding that place sees it move as well, including the start location a walk saved so it could measure the distance at the end. The worked example below runs both versions.
one move, and what is left behind
The class as this course writes it, with one move and three questions asked of the result:
classLocation(object):
def__init__(self, x, y):
"""x and y are numbers"""self.x = x
self.y = y
defmove(self, deltaX, deltaY):
"""Hands back a NEW Location deltaX, deltaY away from this one"""return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
"""Straight line distance from this place to other"""
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5def__repr__(self):
return'<' + str(self.x) + ', ' + str(self.y) + '>'
start = Location(0, 0)
here = start.move(0, 1)
print(start, here)
print(start.distFrom(here))
print(here is start)
It prints:
<0, 0> <0, 1>
1.0False
The third line is the one worth pausing on. here and start are different objects, so the distance between them is not zero, and the place saved in start is still usable as a measuring point.
FindThe three printed lines, and which object each name holds.
Given
start = Location(0, 0)
here = start.move(0, 1)
Solution
Build and move
$$\texttt{start} \rightarrow \texttt{<0, 0>}$$
the constructor stores the two coordinates and nothing else
$$\texttt{here} \rightarrow \texttt{<0, 1>}$$
move returns a second object, one unit north
Measure and compare
$$d = \sqrt{(0-0)^2+(0-1)^2} = 1.0$$
one coordinate difference is zero, so the root is the other difference, and one step north is one unit
$$\texttt{here is start} \rightarrow \texttt{False}$$
the identity test asks whether the two names hold the same object, and move made a new one
Independent check on the printed form: <0, 0> can only come from the __repr__ of this class, so the first line also proves that printing a Location shows its coordinates rather than its address.
A method that returns a new object is a question. A method that changes the object is an order. Location asks.
the mutating version, and the two names it ruins
The same class with the counterexample's move in it, and two names pointing at one place:
One object was built and three names hold it, so the single move shows up in all three. Nothing raised an error, and this is what makes the mutating version dangerous rather than merely wrong.
FindWhat the three names print after one move, and why.
Given
a move method that assigns to self.x and self.y and returns self
homer and marge both bound to the same BadLocation
Independent check against the correct class: the same three lines with the returning version of move print <1, 0> <0, 0> <0, 0> and False, so the difference is visible in the printed output and not only in an argument about style.
When two names share an object, the safe method is the one that does not write into it.
Checkpoint
§12.2 — distance both ways round
Two places and three distance questions, one of which is a place measured against itself.
Find(a) Write exactly what this program prints, all three lines.
Three and four give five, the triangle worth keeping in your head for checking work by eye.
the second line must equal the first, because squaring throws the signs away
the third is the check every distance function should pass: a place is zero away from itself
the trailing .0 says the root handed back a float, not an int
Hint 1/4
Take the two coordinate differences first and only then the square root, and decide before computing whether the order of the two places can matter.
Hint 2/4
The distance is the square root of the sum of the two squared differences. Squaring removes any sign, so swapping the two places cannot change the result.
Hint 3/4
The places here are Location(1, 2) and Location(4, 6), so the differences are 3 and 4, and the third call measures a place against itself.
Hint 4/4
The same number twice, then zero, and all three printed as floats.
Show solution
Measure a to b
$$\Delta x = 1-4 = -3,\quad \Delta y = 2-6 = -4$$
the method subtracts the other place from this one, so both differences are negative here
$$d = \sqrt{9+16} = 5.0$$
the squares turn both signs positive
Measure the other way, and against itself
$$\Delta x = 3,\ \Delta y = 4 \rightarrow 5.0$$
the same squares, so the same root
$$\sqrt{0+0} = 0.0$$
a place is nowhere from itself, and the power of 0.5 returns a float
Answer $$\boxed{5.0\;/\;5.0\;/\;0.0}$$
Check
Independent check by a different route: the two places differ by 3 east and 4 north, and 3, 4, 5 is the standard right triangle, so the hypotenuse is exactly 5 with no rounding involved.
A distance function that can return a negative number, or something other than 0 for a place against itself, is broken and can be caught in two lines.
⚠ Calling move and throwing the result away
Method calls that change things are common, so a call on its own line looks like it did something.
right$$\texttt{a.getX() == b.getX() and a.getY() == b.getY()}$$
12.3Field: a dictionary from walker to place
The field remembers which place each drunk stands on, and a move rebinds only that drunk's key.
Locations know about places. Something has to remember who is standing where, and to ask the walker which way he is going.
DefinitionDefinition 12.3: the Field class
Conditions
A field owns one dictionary, self.drunks = {}, whose keys are drunk objects and whose values are Locations.
addDrunk(d, loc) writes self.drunks[d] = loc. For a drunk who is already there this is not an error and not a second entry: it moves him to loc.
moveDrunk(drunk) asks the drunk for a step, takes the place he is on, and stores the place that move hands back. The field never decides a direction itself.
getLoc(drunk) returns the place, and for a drunk who was never added it prints Drunk not in field and returns None.
Two drunks may be added on the same Location object, and that is safe here only because move never writes into a place.
Read it as: the field is a lookup table from walker to place, and moving a walker means replacing his entry with the place his own step leads to. The walker chooses the direction, the field does the bookkeeping.
The field before and after one move. Both drunks start on the same Location object, and moveDrunk(Homer) rebinds one key to a new box while the other key stays where it was. Nothing was written into a place.
Looks like this, but is not
The method is called addDrunk, so calling it twice for the same drunk should put him in twice, or at least be refused:
The third line is a dictionary assignment to a key that is already there, so it overwrites. The field still has one drunk in it and he has been teleported back to the origin. Used on purpose that is how a trial is reset; used by accident it deletes half a walk.
a field, a scripted walker, and two moves
The field's three methods, driven by a walker whose steps are known in advance so that the output can be predicted. The step list cycles, so this walker goes east, north, east, north:
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
def__repr__(self):
return'<' + str(self.x) + ', ' + str(self.y) + '>'classField(object):
"""A dictionary from a drunk to the place that drunk is standing on."""def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defgetDrunks(self):
returnself.drunks
defmoveDrunk(self, drunk):
if drunk notinself.drunks:
print('Drunk not in field')
else:
xDist, yDist = drunk.takeStep()
currentLocation = self.drunks[drunk]
self.drunks[drunk] = currentLocation.move(xDist, yDist)
defgetLoc(self, drunk):
if drunk notinself.drunks:
print('Drunk not in field')
else:
returnself.drunks[drunk]
classScriptedDrunk(object):
def__init__(self, name, steps):
self.name = name
self.steps = steps
self.next = 0deftakeStep(self):
step = self.steps[self.next % len(self.steps)]
self.next = self.next + 1return step
def__repr__(self):
returnself.name
f = Field()
tester = ScriptedDrunk('Tester', [(1, 0), (0, 1)])
f.addDrunk(tester, Location(0, 0))
print(f.getLoc(tester))
f.moveDrunk(tester)
f.moveDrunk(tester)
print(f.getLoc(tester))
print(f.getDrunks())
It prints:
<0, 0>
<1, 1>
{Tester: <1, 1>}
The last line is the field's own dictionary, printed as it is. Both the key and the value print readably because both classes define __repr__.
FindThe three printed lines.
Given
a whose steps are (1, 0) then (0, 1), repeating
added at Location(0, 0), then two moveDrunk calls
Solution
Add, and read the place back
$$\texttt{f.drunks = \{Tester: <0, 0>\}}$$
addDrunk writes one entry, and getLoc reads it back unchanged
Move twice
$$\texttt{(1, 0)} \rightarrow \texttt{<1, 0>}$$
the first step of the script is east, and the field stores the place move returns
$$\texttt{(0, 1)} \rightarrow \texttt{<1, 1>}$$
the second step is north, applied to the place stored by the first move rather than to the origin
Read the dictionary
$$\texttt{\{Tester: <1, 1>\}}$$
one key, one value, and the key prints as its name because the walker class defines a repr
Independent check by counting steps: two steps were taken, one east and one north, so the walker must be one east and one north of the origin whatever the code looks like, and <1, 1> is that place.
A walker with a known script is how you test the machinery. Randomness comes back once the machinery is known to work.
two keys sharing one place, and only one of them moving
The sharing question, in a dictionary small enough to print. Two names are stored under two keys and both hold the same Location object:
Independent check against the mutating version of move: with that version the same three lines print True, then both entries at <1, 0>, then <1, 0> for the origin, so this output is evidence about which move is in use.
Sharing a place is safe. Sharing a place with a method that writes into it is not. The design, not the sharing, is what decides.
Checkpoint
§12.3 — one drunk under two names
A drunk object is bound to two names and used as a key twice. The question is how many entries the dictionary ends up with.
Find(a) Write exactly what this program prints, all three lines.
Given
classDrunk(object):
def__init__(self, name):
self.name = name
def__repr__(self):
returnself.name
homer = Drunk('Homer')
alsoHomer = homer
places = {}
places[homer] = (0, 0)
places[alsoHomer] = (3, 4)
print(len(places))
print(places[homer])
print(homer is alsoHomer)
IPython console
In [1]: %run untitled0.py
One object was built, so there is one key however many names point at it. The second assignment moved that drunk to (3, 4) instead of adding a second one.
Compare the pretest, where two separate Drunk('Homer') calls gave two keys. Constructor calls are what count, not names.
Hint 1/4
Count the objects before counting the keys. The two names are the distraction here.
Hint 2/4
A dictionary key is an object. Assigning through a second name for the same object writes to the same key and replaces the value.
Hint 3/4
Here alsoHomer = homer makes one object with two names, so both assignments use one key, and the second one wins.
Hint 4/4
One entry, the value from the second assignment, and True.
Show solution
Count what exists
$$\texttt{Drunk('Homer')} \text{ called once}$$
one object, and the second name is an assignment rather than a construction
$$\texttt{homer is alsoHomer} \rightarrow \texttt{True}$$
which is exactly why the two assignments collide
Apply the two assignments
$$\texttt{places[homer] = (0, 0)}$$
creates the single entry
$$\texttt{places[alsoHomer] = (3, 4)}$$
same key, so the value is replaced and the length stays 1
Independent check: if these were two keys the length would be 2 and the lookup through the first name would still give (0, 0). Both printed values contradict that, and they do it in two different ways.
In this section the same sentence explains a reset and a bug: assigning to an existing key replaces its value.
⚠ Calling addDrunk again in the middle of a walk
The name sounds like adding, and adding somebody who is already there sounds harmless.
wrong$$\texttt{for s in range(numSteps):}\\\quad \texttt{f.addDrunk(d, origin)}\\\quad \texttt{f.moveDrunk(d)}$$
right$$\texttt{f.addDrunk(d, origin)}\\\texttt{for s in range(numSteps):}\\\quad \texttt{f.moveDrunk(d)}$$
⚠ Expecting two walkers with the same name to be one key
The printed form of a drunk is his name, and two identical names look like one walker.
right$$\texttt{if other in f.getDrunks():}\\\quad \texttt{print(f.getLoc(other).getX())}$$
12.4The walk function, and the checks that make it believable
One walk moves the drunk numSteps times and returns how far the end is from the start.
Three classes are in place and nothing has used them together yet. One function does, and it is the function every question in this section is built on.
MethodMethod 12.4: walk(f, d, numSteps)
Conditions
The starting place is read BEFORE the loop, start = f.getLoc(d). After the loop it is the ending place, and the distance from a place to itself is zero.
The loop is for s in range(numSteps): and its body is one f.moveDrunk(d). The loop variable is not used, which is the signal that the body does not depend on which step this is.
The value returned is a number, start.distFrom(f.getLoc(d)), not a place. A caller that wants the place asks the field for it.
numSteps of 0 is legal and must return 0.0. This is the first thing to test and the cheapest.
The docstring says what is assumed and what comes back, in the shape the course uses: Assumes: f a Field, d a Drunk in f, numSteps an int >= 0.
$$\boxed{\begin{aligned}&\texttt{start = f.getLoc(d)}\\&\texttt{for s in range(numSteps): f.moveDrunk(d)}\\&\texttt{return start.distFrom(f.getLoc(d))}\end{aligned}}$$
Read it as: remember where he is, move him that many times, then measure from the remembered place to where he now is. The order of the three lines is the whole content: remembering after moving measures nothing.
One real walk of twenty steps, drawn from the seeded run on this page. The purple line is the path, the dashed orange line is what walk returns. The path crosses itself and passes through the start again, which is why the two numbers have nothing to do with each other.
Looks like this, but is not
Every step has length one, so a walk of numSteps steps should have walked numSteps, and that is the distance:
defwalk(f, d, numSteps):
for s inrange(numSteps):
f.moveDrunk(d)
return numSteps
That is the length of the path, and the path folds back on itself. The walk in the figure above took 20 steps, stood on the origin again on the fourth, and ended 2.83 from where it began.
a scripted walk of four steps, and a walk of none
The function, driven by a walker whose steps repeat east, north, east, north, so that the answer is known before the program runs:
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5def__repr__(self):
return'<' + str(self.x) + ', ' + str(self.y) + '>'classField(object):
def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defmoveDrunk(self, drunk):
xDist, yDist = drunk.takeStep()
self.drunks[drunk] = self.drunks[drunk].move(xDist, yDist)
defgetLoc(self, drunk):
returnself.drunks[drunk]
classScriptedDrunk(object):
def__init__(self, name, steps):
self.name = name
self.steps = steps
self.next = 0deftakeStep(self):
step = self.steps[self.next % len(self.steps)]
self.next = self.next + 1return step
def__repr__(self):
returnself.name
defwalk(f, d, numSteps):
"""Assumes f a Field, d a drunk in f, numSteps an int >= 0. Moves d numSteps times and returns the straight line distance between where d ended and where d started."""
start = f.getLoc(d)
for s inrange(numSteps):
f.moveDrunk(d)
return start.distFrom(f.getLoc(d))
f = Field()
tester = ScriptedDrunk('Tester', [(1, 0), (0, 1), (1, 0), (0, 1)])
f.addDrunk(tester, Location(0, 0))
print(walk(f, tester, 4))
print(f.getLoc(tester))
f.addDrunk(tester, Location(0, 0))
print(walk(f, tester, 0))
It prints:
2.8284271247461903
<2, 2>
0.0
The first line is the raw float. Python prints every digit it has when nothing formats the value, and 2.8284271247461903 is what a square root of 8 looks like when it is printed unformatted.
FindThe three printed lines.
Given
steps (1, 0), (0, 1) repeating, so four steps go east, north, east, north
added at Location(0, 0), then walk(f, tester, 4) and walk(f, tester, 0)
Independent check on the middle line: the printed place <2, 2> must satisfy the first line, and the square root of 2 squared plus 2 squared is 2.83, which it does. The two lines were produced by different calls, so they agree by construction only if the function is right.
A zero step walk returning 0.0 is the cheapest test this function has. Run it before you trust any number.
the same function with two lines swapped
What happens when the starting place is read after the loop rather than before it. Everything else is identical, and the walker really does move:
Independent check that the bug is in the measuring and not in the walking: the printed coordinates are 5 and 5, which is exactly where ten alternating steps must end, so the moving half of the function is correct.
A bug that returns a plausible looking number for every input is worse than one that raises. This one returns 0.0 forever.
the three cheap tests: zero steps, one step, two steps
Before believing a simulation, run it on the cases whose answers are known. This program collects every distance that appears over a hundred walks, for walks of zero, one and two steps:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5classField(object):
def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defmoveDrunk(self, drunk):
xDist, yDist = drunk.takeStep()
self.drunks[drunk] = self.drunks[drunk].move(xDist, yDist)
defgetLoc(self, drunk):
returnself.drunks[drunk]
classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defwalk(f, d, numSteps):
start = f.getLoc(d)
for s inrange(numSteps):
f.moveDrunk(d)
return start.distFrom(f.getLoc(d))
defdistancesSeen(numSteps, numTrials):
"""The set of distances a walk of numSteps ever produces in numTrials"""
seen = []
d = UsualDrunk()
for t inrange(numTrials):
f = Field()
f.addDrunk(d, Location(0, 0))
dist = walk(f, d, numSteps)
if dist notin seen:
seen.append(dist)
seen.sort()
return seen
random.seed(7)
print(distancesSeen(0, 100))
print(distancesSeen(1, 100))
print(distancesSeen(2, 100))
It prints:
[0.0]
[1.0]
[0.0, 1.4142135623730951, 2.0]
The third line is the interesting one. Two steps can end 1.41 away, which the naive guess of nothing or two misses, and it is the group of eight corner walks counted at the start of this section.
FindWhat the three lines show, and what each one rules out.
Given
walks of 0, 1 and 2 steps, a hundred trials of each, seed 7
the set of distances that appeared, sorted
Solution
Read the first two lines
$$\texttt{[0.0]}$$
a walk of no steps can only end where it started, so any other value here would be a bug in the loop bounds
$$\texttt{[1.0]}$$
one step of length one, in any of four directions, is distance one from the start every time
Read the third
$$\texttt{[0.0, 1.4142135623730951, 2.0]}$$
the three groups, and nothing else appeared in a hundred trials
$$\text{no } 1.0 \text{ and no } 3.0$$
two steps cannot leave the walker an odd distance away, and the printed set agrees
Independent check against the hand count made earlier: the sixteen two step walks were grouped into distances 0, 1.41 and 2, and this run of the actual simulation produced that set and no other value. The hand count and the program agree, which is what a sanity check is for.
Tiny cases have known answers. That is the only reason to run them, and it is enough of a reason.
Checkpoint
§12.4 — how many times the body runs
Two loops and a list, with no walking in them. The point is the count, because the count is what numSteps controls.
Find(a) Write exactly what this program prints, all three lines.
Given
count = 0for s inrange(1, 10):
count = count + 1print(count)
positions = []
x = 0for s inrange(4):
x = x + 1
positions.append(x)
print(positions)
print(len(positions), positions[-1])
IPython console
In [1]: %run untitled0.py
The first number is 9 and not 10, which is the one character difference between a walk of numSteps steps and a walk one step short.
range(1, 10) gives 9 values, from 1 to 9
range(4) gives 4, from 0 to 3, so the list has 4 items
the last item is 4 because the body adds one before appending
Hint 1/4
Count the values the loop variable takes before you look at what the body does with them.
Hint 2/4
range(a, b) yields the values from a up to b minus 1, so it produces b minus a values. range(n) produces n of them, starting at 0.
Hint 3/4
The first loop is range(1, 10), so it runs 9 times, and the second is range(4), so the list gets 4 items and x ends at 4.
Hint 4/4
Nine, then the four item list, then its length and its last item.
Counting steps from one is how people count, and the loop variable is unused so nothing complains.
wrong$$\texttt{for s in range(1, numSteps)} \rightarrow \texttt{numSteps} - 1 \text{ moves}$$
right$$\texttt{for s in range(numSteps)} \rightarrow \texttt{numSteps} \text{ moves}$$
12.5One walk answers nothing: trials, and what a reset protects
A simulation reports the mean of many trials, and each trial puts the walker back where he began.
The walk function hands back one number. Asked the same question twice it hands back two different numbers, so one of them cannot be the answer.
MethodMethod 12.5: simWalks and the report
Conditions
simWalks(numSteps, numTrials) calls walk numTrials times and collects the distances in a list. It returns the list, not the mean, so that the caller can also see the spread.
Every trial begins by putting the walker back: f.addDrunk(homer, origin), or a fresh Field for each trial. Without it the walker simply carries on from where the previous trial left him.
The report is three numbers, sum(distances)/len(distances), max(distances) and min(distances). A mean on its own hides how wide the answers were.
The seed call belongs above the trial loop. Inside it, every trial repeats the same walk and the mean of identical numbers is that number.
numTrials is how many walks, numSteps is how long each one is. They are different knobs and the mean depends on both.
Read it as: add the distances of all the walks and divide by how many walks there were, which is the number of trials and never the number of steps.
The ten distances from the seeded run below, each drawn as one dot, with the mean marked in orange. The blue span is the range of the answers. Any one of those dots could have been reported as the result, which is the argument for trials in one picture.
Looks like this, but is not
A long walk is a lot of walking, so one very long walk should be evidence enough:
print(walk(f, homer, 100000))
Length is not repetition. A hundred thousand steps gives one sample of the question, and the next run of the same line gives a different number. Ten walks of a hundred steps on this page landed between 2.0 and 15.3.
how many trials
what came out
would you report it
1
6.0
no, the next walk gave 11.0
1 again
11.0
no, and the two differ by 5.0
10
8.44
with its range, 2.0 to 15.3
500
8.6
yes, and it barely moves between seeds
Read down the middle column. The first two rows are the same experiment and they disagree by more than half the value, and the last two rows agree with each other to within 0.2 while using different seeds.
ten walks of a hundred steps, reported three ways
The trial loop, with the ten distances printed one per line and then the three report numbers:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5classField(object):
def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defmoveDrunk(self, drunk):
xDist, yDist = drunk.takeStep()
self.drunks[drunk] = self.drunks[drunk].move(xDist, yDist)
defgetLoc(self, drunk):
returnself.drunks[drunk]
classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defwalk(f, d, numSteps):
start = f.getLoc(d)
for s inrange(numSteps):
f.moveDrunk(d)
return start.distFrom(f.getLoc(d))
defsimWalks(numSteps, numTrials):
"""Runs numTrials walks of numSteps and returns the list of distances"""
homer = UsualDrunk()
origin = Location(0, 0)
distances = []
for t inrange(numTrials):
f = Field()
f.addDrunk(homer, origin)
distances.append(walk(f, homer, numSteps))
return distances
random.seed(3)
distances = simWalks(100, 10)
for dist in distances:
print(format(dist, '.1f'))
mean = sum(distances) / len(distances)
print('Mean:', format(mean, '.2f'))
print('Max:', format(max(distances), '.2f'))
print('Min:', format(min(distances), '.2f'))
The mean is 8.44 and no single walk reported that. The closest was 8.6, the two extremes were 2.0 and 15.3, and that spread of 13.3 is more than one and a half times the mean.
FindThe ten distances and the three report numbers.
Given
numSteps 100, numTrials 10, seed 3
a fresh Field and a reset to the origin in every trial
Solution
Read the loop
$$\texttt{for t in range(10)}$$
ten trials, so the list ends with ten numbers in it
Independent check on the mean by bracketing: the mean of ten numbers must lie between their smallest and largest, so it has to be between 2.00 and 15.30, and a glance at the ten values puts it near the middle of the list rather than at either end.
Ten trials of a hundred steps is a thousand moveDrunk calls.
Report all three numbers. A mean without its range invites the reader to trust it more than it deserves.
one walk against five hundred walks
The same drunk, the same hundred steps, asked three times. The first two are single walks and the third is the mean of five hundred:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defdistFrom(self, other):
xDist = self.x - other.x
yDist = self.y - other.y
return (xDist**2 + yDist**2)**0.5classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defoneWalk(d, numSteps):
start = Location(0, 0)
here = start
for s inrange(numSteps):
xDist, yDist = d.takeStep()
here = here.move(xDist, yDist)
return start.distFrom(here)
random.seed(31)
homer = UsualDrunk()
print('first walk ', format(oneWalk(homer, 100), '.1f'))
print('second walk ', format(oneWalk(homer, 100), '.1f'))
total = 0for t inrange(500):
total = total + oneWalk(homer, 100)
print('mean of 500 ', format(total / 500, '.1f'))
It prints:
first walk 6.0
second walk 11.0
mean of 5008.6
The two single walks differ by almost a factor of two. Either one of them, written down alone, would look like a measurement.
FindHow far apart two single answers can be, and where the mean sits.
Giventwo single walks of 100 steps, then the mean of 500 walks, seed 31
Solution
Compare the two single walks
$$6.0 \text{ against } 11.0$$
the same question, the same length, and a ratio of almost two between the answers
$$\text{neither is wrong}$$
both are real walks; what is wrong is calling either one the answer
Read the mean of five hundred
$$8.6$$
between the two single answers, and this is the number that stays put when the program is run again with another seed
the price of a stable answer, and it is paid in a second
Answer $$\boxed{6.0,\ 11.0,\ \text{mean } 8.6}$$
Check
Independent check against a different run: the ten trial run earlier in this section, with another seed, reported a mean of 8.44 for the same question. Two independent estimates within 0.2 of each other is the kind of agreement a single walk never gives.
The number of trials buys stability. The number of steps changes the question being asked.
what the reset actually protects, measured both ways
The reset is easy to drop, and dropping it does not make the distances look wrong, which is why it needs measuring rather than arguing about. This program runs four trials twice, once with the reset and once without, and prints two distances for each trial: from where that trial started, and from the origin of the whole run:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5classField(object):
def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defmoveDrunk(self, drunk):
xDist, yDist = drunk.takeStep()
self.drunks[drunk] = self.drunks[drunk].move(xDist, yDist)
defgetLoc(self, drunk):
returnself.drunks[drunk]
classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
deftrial(f, d, numSteps, origin, reset):
"""One trial. Returns the distance walked from where this trial started, and the distance from the origin of the whole run."""if reset:
f.addDrunk(d, origin)
start = f.getLoc(d)
for s inrange(numSteps):
f.moveDrunk(d)
end = f.getLoc(d)
return start.distFrom(end), origin.distFrom(end)
deffourTrials(reset):
random.seed(3)
d = UsualDrunk()
origin = Location(0, 0)
f = Field()
f.addDrunk(d, origin)
for t inrange(4):
fromStart, fromOrigin = trial(f, d, 100, origin, reset)
print(' trial', t + 1, 'from start', format(fromStart, '.1f'),
'from origin', format(fromOrigin, '.1f'))
print('with the reset')
fourTrials(True)
print('without the reset')
fourTrials(False)
Independent check that the two halves really ran the same walks: the first column agrees value for value across both halves, which it can only do if the same seeded stream of steps was used in both, so the only difference between the halves is the reset.
Say what a trial measures before you write it. Here it is displacement over numSteps steps, and that sentence is what tells you the reset belongs inside the loop.
Checkpoint
§12.5 — reporting a list of distances
Five distances from an earlier run are already in a list. This is only the reporting half: mean, ends and spread.
Find(a) Write exactly what this program prints, all four lines.
Independent check on the mean: it must sit between 5.7 and 15.3, and 10.14 does. A second check is that five numbers averaging 10.14 must sum to 50.7, which is the sum used.
Format at the printing, never before the arithmetic.
⚠ Dividing by the number of steps instead of the number of trials
Both numbers are in the function's parameter list and both are large.
The reported distances stay believable, so nothing looks broken.
wrong$$\texttt{for t in range(numTrials):}\\\quad \texttt{distances.append(walk(f, d, n))}$$
right$$\texttt{for t in range(numTrials):}\\\quad \texttt{f.addDrunk(d, origin)}\\\quad \texttt{distances.append(walk(f, d, n))}$$
⚠ Seeding inside the trial loop
It makes the run repeatable, which is what was asked for, and it does that.
wrong$$\text{10 identical trials} \rightarrow \text{mean} = \text{that one distance}$$
right$$\text{seed once} \rightarrow \text{10 different trials, mean over all of them}$$
12.6How the mean grows, and what a bias does to it
Quadruple the steps of a fair walk and the mean distance doubles; a biased walk grows with the steps.
The mean is a stable number now, so the question from the opening can finally be asked properly: what does it do when the walk gets longer?
RuleRule 12.6: the growth of the mean distance
Conditions
For the fair walk, measured over walks of 25, 100, 400 and 1600 steps: multiplying the steps by four multiplies the mean distance by about two.
Equivalently, the mean divided by the square root of the number of steps stays put. In the runs on this page that ratio was between 0.87 and 0.92 over a 64 fold range of walk lengths.
Doubling the steps therefore multiplies the mean by about 1.4 and not by 2. Measured: 9.16 at 100 steps and 12.52 at 200.
For a walk with a bias, one direction is more likely than its opposite, so there is a of a fixed amount per step and the mean distance grows in proportion to the number of steps. Measured: four times the steps gave about four times the distance.
All of this is measured here rather than derived. The course's tool for this question is the simulation, and the numbers above are what it reported.
$$\boxed{\text{fair walk: } \bar d \approx 0.9\sqrt{n}\qquad \text{biased walk: } \bar d \propto n}$$
Read the first as: the mean distance of a fair walk goes like the square root of the number of steps, so four times the walking buys twice the distance. Read the second as: a walk that leans in one direction covers ground in proportion to how long it walks.
The measured means against the number of steps, from the run below. The dashed orange pair of lines marks one quadrupling: from 100 steps to 400 the mean goes from 8.85 to 18.49. The x axis is not linear, and the note under the picture says what that does to the shape.
Looks like this, but is not
Twice as much walking should get you twice as far, so the mean after 200 steps should be twice the mean after 100:
100 steps -> 9.16, so 200 steps -> 18.3?
Measured, 200 steps gave 12.52, which is 1.37 times the 100 step mean rather than 2. It takes four times the steps to double the mean, and 400 steps did give 18.35. The worked example below is that measurement.
steps
fair mean
biased mean
biased over fair
100
9.0
20.8
2.3
400
17.9
80.9
4.5
1600
36.4
319.1
8.8
Read the last column downwards: the gap between the two walkers is not a fixed factor, it doubles every time the steps are quadrupled. Two different growth rates always separate like that, and a single row would have hidden it.
quadrupling the steps, four times over
Three hundred trials at each of four walk lengths, each length four times the last. The third column divides the mean by the square root of the number of steps:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5classField(object):
def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defmoveDrunk(self, drunk):
xDist, yDist = drunk.takeStep()
self.drunks[drunk] = self.drunks[drunk].move(xDist, yDist)
defgetLoc(self, drunk):
returnself.drunks[drunk]
classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defwalk(f, d, numSteps):
start = f.getLoc(d)
for s inrange(numSteps):
f.moveDrunk(d)
return start.distFrom(f.getLoc(d))
defmeanDistance(numSteps, numTrials):
d = UsualDrunk()
total = 0for t inrange(numTrials):
f = Field()
f.addDrunk(d, Location(0, 0))
total = total + walk(f, d, numSteps)
return total / numTrials
random.seed(11)
print('steps mean mean/sqrt(steps)')
for numSteps in [25, 100, 400, 1600]:
mean = meanDistance(numSteps, 300)
ratio = mean / numSteps**0.5print(numSteps, format(mean, '.2f'), format(ratio, '.2f'))
It prints:
steps mean mean/sqrt(steps)
254.370.871008.850.8840018.490.92160036.910.92
The middle column roughly doubles down the table and the third column barely moves. Those are two ways of saying the same thing, and the second one is the one that generalises.
FindWhat the mean does when the number of steps is multiplied by four.
Independent check on the arithmetic of the last row: 0.92 times the square root of 1600, which is 40, gives 36.8, and the measured mean was 36.91. The rule read forwards reproduces the measurement it came from.
The last row alone is 300 trials of 1600 steps, so 480,000 moves, and it ran in about a second.
A growth rule is easiest to see by dividing it out. If the quotient stays flat, the rule is the divisor.
doubling the steps, which does not double the distance
The obvious guess deserves its own measurement. Three hundred trials at 100, 200 and 400 steps:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defdistFrom(self, other):
xDist = self.x - other.x
yDist = self.y - other.y
return (xDist**2 + yDist**2)**0.5classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defmeanDistance(numSteps, numTrials):
d = UsualDrunk()
total = 0for t inrange(numTrials):
start = Location(0, 0)
here = start
for s inrange(numSteps):
xDist, yDist = d.takeStep()
here = here.move(xDist, yDist)
total = total + start.distFrom(here)
return total / numTrials
random.seed(19)
means = []
stepCounts = [100, 200, 400]
for numSteps in stepCounts:
means.append(meanDistance(numSteps, 300))
for i inrange(len(means)):
print(stepCounts[i], format(means[i], '.2f'))
print('doubling once:', format(means[1] / means[0], '.2f'))
print('doubling twice:', format(means[2] / means[0], '.2f'))
Independent check against the other table: the run at 100 steps here gave 9.16 and the earlier run with a different seed gave 8.85, so the two independent estimates of the same quantity agree to within about three per cent, which is the size of wobble to expect at 300 trials.
When a ratio comes out near 1.4, suspect a square root. When it comes out near the factor itself, suspect a drift.
a walker who leans east, and the rule he breaks
One subclass, one changed line: the step list holds east twice, so east is twice as likely as west. Everything else is the fair walk:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5classField(object):
def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defmoveDrunk(self, drunk):
xDist, yDist = drunk.takeStep()
self.drunks[drunk] = self.drunks[drunk].move(xDist, yDist)
defgetLoc(self, drunk):
returnself.drunks[drunk]
classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
classEastDrunk(object):
"""Same four steps, but east appears twice, so east is twice as likely"""deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 0)]
return random.choice(stepChoices)
defwalk(f, d, numSteps):
start = f.getLoc(d)
for s inrange(numSteps):
f.moveDrunk(d)
return start.distFrom(f.getLoc(d))
defmeanDistance(drunkClass, numSteps, numTrials):
d = drunkClass()
total = 0for t inrange(numTrials):
f = Field()
f.addDrunk(d, Location(0, 0))
total = total + walk(f, d, numSteps)
return total / numTrials
random.seed(13)
drunkClasses = [UsualDrunk, EastDrunk]
for drunkClass in drunkClasses:
print(drunkClass.__name__)
for numSteps in [100, 400, 1600]:
mean = meanDistance(drunkClass, numSteps, 200)
print(' ', numSteps, format(mean, '.1f'))
The fair column doubles per quadrupling, as before. The biased column multiplies by about four, which is the signature of a drift rather than of a wander.
FindHow the two growth patterns differ, and where the numbers come from.
Given
UsualDrunk against EastDrunk, whose list is [(0,1), (0,-1), (1,0), (-1,0), (1,0)]
walks of 100, 400 and 1600 steps, 200 trials each, seed 13
Independent check at the longest walk: the drift estimate 0.2 times 1600 is 320, and the measured mean was 319.1. The estimate came from counting entries in the step list, not from the simulation, so the two are genuinely separate routes to the same number.
Count the step list before running anything. An unbalanced list means a drift, and a drift beats a wander at every length worth simulating.
Checkpoint
§12.6 — dividing the growth out of a table
Four means are already measured and sit in a list. The program divides each by the square root of its step count, then takes two ratios.
Find(a) Write exactly what this program prints, all six lines.
Given
means = [4.37, 8.85, 18.49, 36.91]
stepCounts = [25, 100, 400, 1600]
for i inrange(len(means)):
ratio = means[i] / stepCounts[i]**0.5print(stepCounts[i], format(ratio, '.2f'))
print(format(means[3] / means[2], '.2f'))
print(format(means[1] / means[0], '.2f'))
IPython console
In [1]: %run untitled0.py
The first four lines are flat, near 0.9, over step counts that span a factor of 64.
the square roots are 5, 10, 20 and 40, which makes the division easy to check by eye
the last two lines are quadruplings, and both come out at 2
flat third column and ratio 2 per quadrupling are the same fact said twice
Hint 1/4
Decide what the loop prints per pass and what the two prints after it are comparing, before doing any arithmetic.
Hint 2/4
Dividing a mean by the square root of its step count leaves a number that stays put if the mean grows like that square root. The last two prints are ratios of means, four times apart in steps.
Hint 3/4
The means again, 4.37, 8.85, 18.49 and 36.91, against step counts 25, 100, 400 and 1600. The square roots are 5, 10, 20 and 40.
Hint 4/4
Four lines of step count and ratio, then the 1600 against 400 ratio, then the 100 against 25 ratio.
Independent check joining the two halves: if the third column is flat at c then the ratio over a quadrupling must be exactly 2, since the square root of four is two. The printed ratios are 2.00 and 2.03, so the two halves of the table agree.
A flat quotient is a proof of a growth rule that fits on one line of output.
⚠ Reading the square root rule as a doubling rule
Twice the walking sounds like twice the distance, and nothing in the simulation objects.
wrong$$n \times 2 \Rightarrow \bar d \times 2$$
right$$n \times 2 \Rightarrow \bar d \times \sqrt{2} \approx 1.4,\qquad n \times 4 \Rightarrow \bar d \times 2$$
⚠ Claiming a trend from two rows
Two points make a ratio, and a ratio looks like a pattern.
right$$\text{three or more rows, and the quotient } \frac{\bar d}{\sqrt n} \text{ checked at each}$$
⚠ Comparing means that came from different trial counts
The trial count feels like a detail of how long the program ran, not like part of the measurement.
wrong$$\bar d(100, \texttt{numTrials}{=}10) \text{ vs } \bar d(400, \texttt{numTrials}{=}300)$$
right$$\text{same } \texttt{numTrials} \text{ in every row of one table}$$
12.7Drawing the answer: which picture, and what a frame hides
Pick the picture that answers the question asked, give each walker class its own style, and say what the frame is.
The numbers now say something. A reader takes it in faster from a picture, and can be misled by the wrong picture just as fast.
MethodMethod 12.7: from a simulation to a figure
Conditions
Name the question first. Where do the walks end goes to a scatter of end locations; how does the distance grow goes to the mean against the number of steps; what does one walk look like goes to a path, and answers nothing about the mean.
Build the lists before drawing anything, and print them. A wrong list draws a wrong picture and no drawing call complains.
One style per walker class, handed out in a fixed order so that the same class keeps its style across figures. The lecture files do this with a list of format strings and an index.
Finish every panel: title, xlabel, ylabel, legend naming the series in the order they were drawn, and axis([xmin, xmax, ymin, ymax]) when the frame matters.
When two panels are compared, give them the same limits. Different limits make different pictures of the same numbers.
Read it as: decide what is being asked, get the numbers into lists and look at them, draw one series per call, then finish the panel so that somebody who did not write it can read it.
Two hundred walks of each kind, drawn with identical limits. This is the figure that answers does the biased walker drift, and it answers it without a single number: the red cloud sits on the start, the purple one has left it.
Looks like this, but is not
Let each panel choose its own frame, since each should show its own data as clearly as possible:
The two clouds then look alike, both filling their own panel, and the finding disappears into the axis numbers where nobody reads it. The shift is the answer, so the frame has to be shared.
the two lists a scatter needs, built and inspected
A scatter takes a list of x values and a list of y values, so the simulation has to hand back both. This is the data half, with no drawing in it:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defendLocations(numSteps, numTrials):
"""Two lists, the x values and the y values of the final places"""
d = UsualDrunk()
xVals = []
yVals = []
for t inrange(numTrials):
here = Location(0, 0)
for s inrange(numSteps):
xDist, yDist = d.takeStep()
here = here.move(xDist, yDist)
xVals.append(here.getX())
yVals.append(here.getY())
return xVals, yVals
random.seed(0)
xVals, yVals = endLocations(100, 200)
print('walks:', len(xVals))
print('first three x:', xVals[0], xVals[1], xVals[2])
print('first three y:', yVals[0], yVals[1], yVals[2])
print('x from', min(xVals), 'to', max(xVals))
print('y from', min(yVals), 'to', max(yVals))
near = 0for i inrange(len(xVals)):
if (xVals[i]**2 + yVals[i]**2)**0.5 <= 5:
near = near + 1print('ended within 5 of the start:', near)
It prints:
walks: 200
first three x: 6 -49
first three y: 2811
x from -21 to 21
y from -19 to 19
ended within 5 of the start: 39
Two hundred walks, ending between 21 west and 21 east, and 39 of them within 5 of the start. Those numbers are what the picture has to agree with.
a drawing call needs two lists of the same length, and the count is printed first for exactly that reason
$$x \in [-21, 21],\quad y \in [-19, 19]$$
so a frame from minus 25 to 25 holds every point with a little room, which is how the limits in the figure were chosen
Sanity check one number against the growth rule
$$39 \text{ of } 200 \text{ within } 5$$
about one walk in five ended close to home, so most walks did not come back
$$\bar d \approx 0.9\sqrt{100} = 9$$
the rule from the previous block says the typical end is around 9 away, and a cloud with a fifth of its points inside 5 is consistent with that
Answer $$\boxed{200 \text{ points},\ x \in [-21, 21],\ y \in [-19, 19]}$$
Check
Independent check on the printed ranges: the largest distance from the origin the cloud can show is bounded by 100, since the walk has 100 unit steps, and the widest coordinate seen is 21. Both are consistent, and a value above 100 would have proved the program wrong.
Print the lists and their lengths before drawing. It is the only check a plotting call will not do for you.
the drawing calls for the scatter, in order
The same simulation with the pyplot calls attached. The drawing is six lines at the end, and five of the six are labelling:
import random
from matplotlib.pyplot import clf, plot, title, xlabel, ylabel, legend, axis
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
classField(object):
def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defmoveDrunk(self, drunk):
xDist, yDist = drunk.takeStep()
self.drunks[drunk] = self.drunks[drunk].move(xDist, yDist)
defgetLoc(self, drunk):
returnself.drunks[drunk]
classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defendLocations(numSteps, numTrials):
"""Two lists: the x values and the y values of the final places"""
d = UsualDrunk()
xVals = []
yVals = []
for t inrange(numTrials):
f = Field()
f.addDrunk(d, Location(0, 0))
for s inrange(numSteps):
f.moveDrunk(d)
xVals.append(f.getLoc(d).getX())
yVals.append(f.getLoc(d).getY())
return xVals, yVals
random.seed(0)
xVals, yVals = endLocations(100, 200)
clf()
plot(xVals, yVals, 'ro', label='end of walk')
plot([0], [0], 'k*', label='start')
title('Where 200 walks of 100 steps ended')
xlabel('steps east')
ylabel('steps north')
legend(loc='upper left')
axis([-40, 40, -40, 40])
There is nothing printed to show here, which is the point: a plotting program's output is the figure. The left panel of the figure above is this program's picture, drawn from the same seeded run.
FindWhat each of the six drawing lines contributes.
Given
xVals and yVals from endLocations(100, 200)
a red circle per walk, a black star at the start
Solution
Draw the two series
$$\texttt{plot(xVals, yVals, 'ro', label='end of walk')}$$
one call per series, and the label is what the legend will show for it
$$\texttt{plot([0], [0], 'k*', label='start')}$$
the start is one point, so it is passed as two one item lists rather than as two numbers
a figure with no axis names cannot be read by anybody who did not write the program
$$\texttt{axis([-40, 40, -40, 40])}$$
a square frame centred on the start, so that a shift in any direction would be visible rather than cropped
Answer $$\boxed{\text{two plot calls, four labelling calls}}$$
Check
Independent check on the chosen limits against the printed data: the widest coordinates in the run were 21 and 19, so a frame of 40 in each direction holds every point, and no point can be lost outside the frame.
Choose the frame from the data you printed, and keep it symmetric when the question is whether the cloud has moved.
one style per class, handed out in a fixed order
With several walker classes in one figure, each needs its own look, and the look has to stay with the class. The lecture files keep a list of format strings and an index; this is the same thing wrapped in a small class:
classStyleIterator(object):
"""Hands out the next plotting style each time it is asked"""def__init__(self, styles):
self.styles = styles
self.index = 0defnextStyle(self):
style = self.styles[self.index]
self.index = (self.index + 1) % len(self.styles)
return style
styles = StyleIterator(['ro-', 'mo--', 'bo:'])
names = ['UsualDrunk', 'EastDrunk', 'ColdDrunk', 'UsualDrunk']
for name in names:
print(name, styles.nextStyle())
Independent check by counting: three styles for four requests must repeat at least one style, and the printed output repeats exactly one, the first. A wrapping bug would have repeated the last style instead, or raised.
A style that travels with the class makes two figures comparable. A style chosen per figure does not.
the frame that hides the finding
Four yearly means that barely move, and the two frames a program could give them:
means = [11.3, 11.9, 12.4, 12.8]
low = min(means)
high = max(means)
print('lowest', low, 'highest', high)
print('span of the data', format(high - low, '.1f'))
print('span of a frame that starts at zero', format(high - 0, '.1f'))
print('rise as a share of the low value',
format(100 * (high - low) / low, '.1f'), 'percent')
It prints:
lowest 11.3 highest 12.8
span of the data 1.5
span of a frame that starts at zero 12.8
rise as a share of the low value 13.3 percent
The data spans 1.5. A frame that starts at zero is 12.8 tall, so the whole story occupies about a tenth of the panel and looks flat. A frame from 11.3 to 12.8 fills the panel with that same story and looks like a steep climb.
FindHow much of the panel the data occupies under each frame.
Given
means 11.3, 11.9, 12.4 and 12.8
two candidate frames, one from zero and one hugging the data
Solution
Measure the data against the frame
$$\text{span of the data} = 12.8 - 11.3 = 1.5$$
the actual movement, in the units of the measurement
$$\frac{1.5}{12.8} \approx 0.12$$
under a frame that starts at zero the data uses about an eighth of the height, so the line looks flat
Say the rise in a way that does not depend on the frame
$$\frac{1.5}{11.3} \times 100 = 13.3\%$$
a share of the starting value, which is a number rather than a picture and cannot be restyled
$$\text{report both}$$
give the frame you chose and the rise as a number, and the reader can check one against the other
Independent check that the two frames describe the same data: the printed lowest and highest values are 11.3 and 12.8 in both cases, so nothing about the measurement changed between the two pictures. Only the mapping from numbers to pixels did.
State the frame and give one frame free number beside the picture. A figure that needs its own axis limits to make its point is making the axis limits' point.
Checkpoint
§12.7 — the picture that answers the drift question
A report has to show whether the east biased walker drifts away from the start or merely wanders like the fair one. Four figures are on offer and all four are drawable from the data already collected.
Find(a) Which figure answers the question asked?
Givenend locations of 200 fair walks and 200 biased walks, 100 steps each
Hint 1/4
Say out loud what would count as an answer to the question, then ask which figure shows that and only that.
Hint 2/4
A comparison needs both groups drawn on one scale. Where the clouds sit is the finding, so anything that moves or hides the position of a cloud removes the finding.
Hint 3/4
The data is two sets of end locations from the same walk length, so both sets can be drawn as clouds in the same units, and the question is about where each cloud sits.
Hint 4/4
The two clouds, side by side, with the same limits in both panels.
Show solution
Name what would count as an answer
$$\text{answer} = \text{where each cloud sits}$$
drift is a position, not a rate and not a single trajectory
each cloud fills its panel and the shift moves into the axis numbers
$$\text{one path} \rightarrow \text{one sample}$$
a single walk cannot separate drift from luck
$$\text{mean curve} \rightarrow \text{growth}$$
it answers a different question, and answers it well
Answer $$\boxed{\text{two scatters, one frame}}$$
Check
Independent check against the figure in this block: drawn with shared limits, the biased cloud is visibly to the right of the fair one and its mean east position is 20.3, so the picture and the number agree.
Write down what would count as an answer before choosing a chart type. The chart type follows from it.
⚠ Comparing two panels that have different axis limits
Each panel looks best when its own data fills it, and the drawing library does that by default.
wrong$$\texttt{axis([-25,25,-25,25])} \text{ and } \texttt{axis([0,40,-20,20])}$$
right$$\texttt{axis([-25,40,-25,25])} \text{ in both panels}$$
⚠ Naming the series in the legend in the wrong order
The legend is written last, by which time the order of the drawing calls has been forgotten.
right$$\texttt{plot(..., label='fair')} \text{ per call, then } \texttt{legend()}$$
⚠ Drawing one walk to answer a question about many
A single path is the most interesting looking picture in the whole section.
wrong$$\text{one path} \Rightarrow \text{claim about } \bar d$$
right$$\text{200 end locations, or the mean against } n$$
From a written specification to a working simulation
Any exam part that describes a process and asks for the numbers it produces. It is also the order to write the code in, which is not the order the specification lists things in.
Say what one trial measures, in one sentence
Here it is the distance from where this walk started to where it ended, over numSteps steps. That sentence decides where the reset goes and what the report means. Write it down before any code.
Write the pieces that do not know about each other
A place that can offer a new place and measure distances, a walker that can offer a step, and a container that remembers who is where. Each one needs nothing from the other two except the method names.
Write the one function that runs the process once
Read the starting place, loop numSteps times, measure. Three lines, and their order is the whole content of the function.
Test it on the cases whose answers you know
Zero steps must give 0.0 and one step must give 1.0. Drive it with a walker whose steps are scripted, so a wrong answer is a bug and not bad luck. This step costs two minutes and catches the swapped lines, the off by one and the returned place.
Wrap it in trials, with the reset inside the loop
A fresh field, or an addDrunk call, at the top of every trial. Collect the distances in a list rather than summing as you go, so the ends are still available at the end.
Report three numbers, then draw one picture
Mean, largest, smallest, each through a format call. Then the single figure that answers the question that was asked, labelled, with its frame chosen from the data you printed.
Where it goes wrong
Step 1 skipped, so the reset is argued about instead of derived.
Step 4 skipped, which is how a function that always returns 0.0 gets reported as a result.
Step 5 with the seed call inside the loop, which turns numTrials identical walks into a very stable wrong answer.
Step 6 with a mean and no range, which hides that the answers ran from 2.0 to 15.3.
Reading a growth rule off a table of means
A table of means at several walk lengths, and a question asking what happens when the walk gets longer, or whether a walker is biased.
Check that the rows are comparable
Same walker, same trial count, only the number of steps changing. A row with a different numTrials belongs to a different table.
Make the step counts a constant factor apart
25, 100, 400, 1600 is four times per row. Two rows cannot tell you a rule, so aim for at least three gaps.
Take the ratio of neighbouring means
If the ratios sit near 2 while the steps go up by 4, the mean is following the square root. If they sit near 4, the mean is following the steps themselves and the walker has a drift.
Divide the candidate law out and look for flatness
Compute the mean over the square root of the steps for every row. A column that stays put is the proof; a column that climbs or falls says the law is the wrong one. On this page the column was 0.87 to 0.92 across a 64 fold range.
Where it goes wrong
Ratios taken between rows that are not the same factor apart, so the numbers mean nothing.
A rule declared from two rows.
The flatness column computed with the rounded means rather than the real ones, which moves the last digit and starts an argument about nothing.
move returns a new place: two names stay independent
The class from this section, with three names holding one place, and one move:
Independent check: the identity test prints True, so no second object was ever built.
The two classes differ by two lines and agree on every printed character until a second name holds the same place.
How to tell them apart
Ask what the method returns. A method that returns a new object of its own class leaves its caller's data alone; a method that returns self almost always wrote into it. In this section the walk function saves the starting place and measures from it at the end, so a move that writes into places makes every distance 0.0.
Scaffolding comes off
The common skeleton
Write down what one trial measures, in one sentence.
Put the walker on a known starting place and keep that place.
Apply the steps one at a time, each to the place the last one produced.
Measure from the kept starting place to the place you ended on.
Report the number through a format call, and say how many steps produced it.
1 · fully worked
a scripted walk of six steps, measured
The whole skeleton on one walk. The steps are given, so nothing here depends on chance:
FindWhere the walk ends and how far that is from the start.
Given
steps [(0,1), (1,0), (0,1), (1,0), (0,-1), (1,0)]
start at Location(0, 0)
Solution
Keep the starting place
$$\texttt{start = Location(0, 0)}$$
the name start is never reassigned, which is what makes the measurement at the end possible
$$\texttt{here = start}$$
one object with two names for now, and that is safe because move does not write into places
Apply the six steps
$$\Delta x = 0+1+0+1+0+1 = 3$$
three of the six steps move east and none moves west
$$\Delta y = 1+0+1+0-1+0 = 1$$
two steps north and one south, so one north in total
$$\texttt{here} = \texttt{<3, 1>}$$
each move is applied to the place the previous move returned
Measure and report
$$d = \sqrt{3^2 + 1^2} = \sqrt{10}$$
the two differences are 3 and 1, measured from the kept start
$$\sqrt{10} = 3.1622\ldots \rightarrow 3.16$$
printed through format with two decimals, as every distance shown to a reader here is
Answer $$\boxed{\texttt{<3, 1>},\ d = 3.16}$$
Check
Independent check by parity: six steps, and the walker is 4 grid units from home along the grid, so both are even, as they must be. Also 3.16 lies between 3 and 4, which is where the diagonal of a 3 by 1 rectangle has to lie.
Six steps, distance 3.16. The number of steps is an upper bound on the distance and nothing more.
2 · you write the reasoning
Easier than the one above, and the reasons are gone. Three scripted steps, east, north, east:
Work out the two printed lines, and write your own reason next to each step before opening the model reasons.
reasoning
The starting place is kept in its own name, because the measurement at the end needs it.
reasoning
Two of the three steps have a 1 in the x part, and no step has a negative x part.
reasoning
One step has a 1 in the y part and nothing cancels it.
reasoning
Each move is applied to the place the previous move returned, so the pairs add up.
reasoning
The distance is the root of the sum of the squared differences, printed with two decimals through format.
3 · find the buried error
Harder than the two above, and already written. A student was asked for a function that runs numTrials walks of numSteps steps with a fair walker and prints the mean distance.
It runs, and it prints a plausible number. Two of its four marked steps are wrong:
import random
defsimulate(numSteps, numTrials):
d = UsualDrunk()
origin = Location(0, 0)
f = Field()
f.addDrunk(d, origin)
total = 0for t inrange(numTrials):
random.seed(0)
total = total + walk(f, d, numSteps)
print(format(total / numSteps, '.2f'))
the two buried errors (2)
⚠ step 3
The seed call is inside the trial loop, so every trial replays the same walk. The mean of numTrials identical distances is that one distance, and it looks stable because it is the same number every time.
Repeatability was asked for, and the seed call looks safest right next to the code it is making repeatable.
right
Move it above the loop, or out of the function entirely and into the script that calls it.
⚠ step 4
The total is divided by numSteps instead of by numTrials. With 100 trials of 1000 steps the reported mean is a hundred times too small.
Both numbers are parameters of the function and both are large, and the phrase how much walking was done points at the wrong one.
4 · the bare problem
§12.5 — the whole simulation, from a spec
No scaffolding this time. A specification of the kind an exam gives, and the three numbers it wants printed.
Find
(a) Write the whole program: the three classes, the walk function, a simWalks that returns the list of distances, and a report function. Print one line per walk length.
(b) Say which line makes each trial independent of the one before it.
Given
a fair walker with the four unit steps
walks of 10, 100 and 1000 steps, 100 trials each
for each length: mean, largest and smallest distance, two decimals
the run must be repeatable, and the seed is 42
Hint 1/4
Write down what one trial measures before writing any code, then list the pieces the specification names: three classes, walk, simWalks, report.
Hint 2/4
One walk is start, loop, measure. One trial is a reset then a walk. The report is mean, max and min over the list of distances, each through a format call.
Hint 3/4
The specification again: 10, 100 and 1000 steps, 100 trials each, seed 42 once at the top, and two decimals on all three reported numbers.
Hint 4/4
The program below prints three lines, one per walk length, and the reset is the addDrunk call at the top of the trial loop.
Show solution
Say what one trial measures
$$\text{distance from the origin after numSteps steps}$$
this sentence is what puts the reset inside the trial loop rather than above it
Write walk, then the trial loop
$$\texttt{start = f.getLoc(d)} \text{ first}$$
the measurement needs the starting place, and after the loop it is gone
ten times the steps should multiply the mean by about 3.16, and both ratios sit near it, which is the check that the program measures what it claims
Answer $$\boxed{\text{three report lines, reset inside the trial loop}}$$
Check
Independent check against the growth rule: 0.9 times the square root of 1000 is 28.5, and the program reported 28.78 for that row without ever being told the rule.
An exam answer that prints its own check is worth more than one that prints only the answer.
Full exam-style question
report a fair walk at three lengths, in exam formatexam format
An exam part in the shape the archived paper uses: a described function and a script that runs it. What is asked for:
a report(numSteps, numTrials, drunkClass) that runs the trials
it prints the walk length, the mean, the largest and the smallest
it returns the mean
a script calling it for 10, 100 and 1000 steps, 50 trials, seeded
The three classes are the ones from this section, unchanged:
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5classField(object):
def__init__(self):
self.drunks = {}
defaddDrunk(self, d, loc):
self.drunks[d] = loc
defmoveDrunk(self, drunk):
xDist, yDist = drunk.takeStep()
self.drunks[drunk] = self.drunks[drunk].move(xDist, yDist)
defgetLoc(self, drunk):
returnself.drunks[drunk]
classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defwalk(f, d, numSteps):
start = f.getLoc(d)
for s inrange(numSteps):
f.moveDrunk(d)
return start.distFrom(f.getLoc(d))
defsimWalks(numSteps, numTrials, drunkClass):
d = drunkClass()
distances = []
for t inrange(numTrials):
f = Field()
f.addDrunk(d, Location(0, 0))
distances.append(walk(f, d, numSteps))
return distances
defreport(numSteps, numTrials, drunkClass):
"""Prints one line per walk length: mean, max and min distance"""
distances = simWalks(numSteps, numTrials, drunkClass)
mean = sum(distances) / len(distances)
print(drunkClass.__name__, 'walk of', numSteps, 'steps')
print(' mean', format(mean, '.2f'), 'max', format(max(distances), '.2f'),
'min', format(min(distances), '.2f'))
return mean
random.seed(17)
for numSteps in [10, 100, 1000]:
report(numSteps, 50, UsualDrunk)
Sample Run:
UsualDrunk walk of 10 steps
mean 3.14max7.07min0.00
UsualDrunk walk of 100 steps
mean 9.35max19.70min1.41
UsualDrunk walk of 1000 steps
mean 29.89max65.60min1.41
FindThe program, and the reading of its three lines of output.
Given
a fair walker with the four unit steps
walks of 10, 100 and 1000 steps, 50 trials each, seed 17
print the length, mean, max and min; return the mean
both near 3.16, so the report agrees with the rule measured earlier in this section
Read the ends, not only the means
$$\min = 0.00 \text{ at 10 steps}$$
some of the fifty short walks came home, which is likely at 10 steps and rare at 1000
$$\max = 65.60 \text{ at 1000 steps}$$
more than twice the mean of that row, which is the kind of number a mean on its own hides
Answer $$\boxed{3.14,\ 9.35,\ 29.89 \text{ with their ends reported}}$$
Check
Independent check on the middle row: 0.9 times the square root of 100 is 9.0, and the reported mean is 9.35, which is inside the wobble of 50 trials. The rule and this run were produced by different programs with different seeds.
A report function with the walker class as a parameter is the answer to two exam questions instead of one.
Practice
A · concept 4 questions
1§12.2 — what a move leaves behind
A claim about the place a move was called on, of the kind a true or false question opens with.
Find(a) True or false: after the second line, start is one step north of where it was.
Given
start = Location(0, 0)
here = start.move(0, 1)
Hint 1/4
Decide how many Location objects exist after the two lines before deciding what either name shows.
Hint 2/4
move returns a new Location built from the coordinates of this one. It assigns to nothing, so no existing object changes.
Hint 3/4
Here the second line binds the new place to here, and start is not on the left of any assignment after the first line.
Hint 4/4
The claim is false: start is still the origin, and here is the place one north of it.
Show solution
Count and read
$$\text{two objects}$$
one from the constructor, one from the move
$$\texttt{start} \rightarrow \texttt{<0, 0>}$$
it was never reassigned and nothing writes into a Location
Answer $$\boxed{\text{False}}$$
Check
Independent check: here is start prints False, which is only possible if two objects exist, and the first of them is unchanged.
Ask what a method returns before asking what it changed.
2§12.5 — where a seed call belongs
A claim about making a simulation repeatable, of the kind that sounds responsible.
Find(a) True or false: seeding at the top of every trial makes the reported mean more reliable.
Given
a trial loop that runs 100 walks of 100 steps
random.seed(0) written as the first line inside the loop
Hint 1/4
Ask what the second trial does differently from the first one under this arrangement.
Hint 2/4
A seed call repositions the stream of random numbers. The same seed gives the same numbers, so the same walk.
Hint 3/4
Here every trial reseeds with 0, so all 100 trials walk the same 100 steps and the list of distances holds one value 100 times.
Hint 4/4
The claim is false: the mean becomes a single trial's distance, reported with more confidence than a single trial deserves.
Show solution
Follow the trials
$$\text{trial 1: stream from seed 0}$$
the first walk uses the first 100 numbers of that stream
$$\text{trial 2: stream from seed 0 again}$$
the same 100 numbers, so the same steps and the same distance
Read the report
$$\text{mean of } n \text{ copies of } d = d$$
nothing is averaged over, so the trial count buys nothing
$$\max = \min = d$$
and the range printed beside the mean is zero, which is the visible symptom
Answer $$\boxed{\text{False}}$$
Check
Independent check available on any run: with the seed inside the loop the largest and smallest distances come out equal, and a hundred fair walks of a hundred steps never agree like that.
A range of zero over many trials is a bug report, not a result.
3§12.6 — nine times the steps
A fair walker has already been measured at 100 steps, where the mean distance came out about 9. The question is about a longer walk of the same kind.
Find(a) About what is the mean distance after 900 steps?
Given
fair walker, four unit steps
mean distance at 100 steps is about 9
Hint 1/4
Ask what the number of steps was multiplied by, and what the growth rule does with that factor.
Hint 2/4
The mean of a fair walk grows like the square root of the number of steps, so the mean is multiplied by the square root of the factor the steps were multiplied by.
Hint 3/4
Here the steps go from 100 to 900, so the factor is 9, and the mean at 100 steps was about 9.
Hint 4/4
The square root of 9 is 3, so the mean is about three times larger.
Show solution
Find the factor
$$\frac{900}{100} = 9$$
the steps were multiplied by nine
$$\sqrt{9} = 3$$
the mean is multiplied by the square root of that factor
Apply it
$$9 \times 3 = 27$$
the mean at 100 steps, multiplied by three
$$0.9\sqrt{900} = 27$$
the same answer from the rule written the other way, which is the check
Answer $$\boxed{\approx 27}$$
Check
Independent check against the measured table: 400 steps gave 18.49 and 1600 gave 36.91, so 900 steps should land between those two, and 27 does.
Multiply the steps by a factor, multiply the mean by its square root. That is the whole rule.
4§12.3 — two walkers on one place
A claim about sharing a starting place between two walkers, which is what the lecture programs actually do.
Find(a) True or false: the two walkers can be added on the same Location object without their walks interfering.
Given
origin = Location(0, 0)
f.addDrunk(homer, origin) and f.addDrunk(marge, origin)
Hint 1/4
Ask what a move does to the place it was called on, and then ask what the field stores.
Hint 2/4
move returns a new place and writes into nothing, and moveDrunk assigns that new place to one key of the dictionary.
Hint 3/4
Here both keys start out holding the same object, and moving Homer assigns a new place to Homer's key only.
Hint 4/4
The claim is true, and it is true because of how move is written, not because of how the field is written.
Show solution
Follow one move
$$\texttt{move} \rightarrow \text{new object}$$
the shared place is read and never written to
$$\texttt{self.drunks[drunk] = ...}$$
one key is rebound, and the other key is not mentioned
Answer $$\boxed{\text{True}}$$
Check
Independent check: printing the whole dictionary after one move shows one entry changed and one unchanged, which is measured earlier in this section.
Safe sharing is a property of the methods, not of the container.
B · computation 5 questions
1§12.1 — three classes, one seeded step
The , a random walker and a scripted walker in one file. The scripted one is asked for three steps and the random one for one, after a seed call.
Find(a) Write exactly what this program prints, all three lines.
Given
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5def__repr__(self):
return'<' + str(self.x) + ', ' + str(self.y) + '>'classDrunk(object):
"""Base class. It knows a name and nothing about walking."""def__init__(self, name=None):
self.name = name
def__repr__(self):
ifself.name != None:
returnself.name
return'Anonymous'classUsualDrunk(Drunk):
"""Picks one of the four unit steps, each equally likely."""deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
classScriptedDrunk(Drunk):
"""Takes the steps written on a list, in order. Used for testing."""def__init__(self, name, steps):
Drunk.__init__(self, name)
self.steps = steps
self.next = 0deftakeStep(self):
step = self.steps[self.next % len(self.steps)]
self.next = self.next + 1return step
homer = UsualDrunk('Homer')
tester = ScriptedDrunk('Tester', [(1, 0), (0, 1)])
print(homer, tester)
print(tester.takeStep(), tester.takeStep(), tester.takeStep())
random.seed(5)
print(homer.takeStep())
IPython console
In [1]: %run untitled0.py
Three things are worth naming here.
both objects print through the repr inherited from Drunk, so the first line is the two names
the scripted walker wraps: asked a third time it starts the list again
the last line is fixed by the seed, and the same seed gives the same step on the same interpreter
Hint 1/4
Take the printed lines one at a time, and for the first one ask what each object's printed form comes from.
Hint 2/4
The base class defines how a drunk prints. A scripted walker hands out its steps in order and wraps round with the remainder operator. A seeded choice is fixed once the seed is fixed.
Hint 3/4
The script here is [(1, 0), (0, 1)], asked three times, and the random walker is asked once after random.seed(5).
Hint 4/4
The two names, then the three scripted steps with the first one repeated, then the seeded step.
Show solution
Print the objects
$$\texttt{Homer Tester}$$
the subclasses do not define a repr, so the inherited one runs and it returns the name
Ask the scripted walker three times
$$\texttt{(1, 0) (0, 1)}$$
the first two calls hand out the list in order
$$(2 \bmod 2) = 0 \rightarrow \texttt{(1, 0)}$$
the third call wraps to the front of the list
Ask the random walker once
$$\texttt{(1, 0)}$$
the first item of the stream from seed 5, which happens to be east
Independent check on the middle line: the scripted list has two items and three were requested, so exactly one item must appear twice, and the repeated one must be the first.
A scripted walker is a test instrument. Its wrapping behaviour is part of the instrument.
2§12.2 — a chain of moves and one identity test
Three places built from one, then a distance and two identity tests.
Find(a) Write exactly what this program prints, all three lines.
Given
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defgetX(self):
returnself.x
defgetY(self):
returnself.y
defdistFrom(self, other):
xDist = self.x - other.getX()
yDist = self.y - other.getY()
return (xDist**2 + yDist**2)**0.5def__repr__(self):
return'<' + str(self.x) + ', ' + str(self.y) + '>'
a = Location(2, 3)
b = a.move(1, 1)
c = b.move(-1, 0)
print(a, b, c)
print(a.distFrom(c))
print(b is c, a is a.move(0, 0))
IPython console
In [1]: %run untitled0.py
The last line is the one to take seriously: a move of zero is still a move.
a.move(0, 0) builds a new object with the same coordinates
so the identity test is False even though the two places are at the same spot
a and c differ only in y, so their distance is 1.0 and the x parts cancel
Hint 1/4
Build the three places on paper first, then answer the distance and the two identity questions from them.
Hint 2/4
Each move returns a new place built from the one it was called on, so a chain of moves leaves every earlier place intact. An identity test asks whether two names hold the same object.
Hint 3/4
Here a is <2, 3>, b is one east and one north of a, and c is one west of b. The last line also moves a by zero.
Hint 4/4
The three places, then the distance from a to c, then two answers that are both False.
Show solution
Build the three places
$$a = \texttt{<2, 3>},\ b = \texttt{<3, 4>}$$
the move adds one to each coordinate of a
$$c = \texttt{<2, 4>}$$
one west of b, which puts it directly north of a
Measure and test identity
$$d = \sqrt{0^2 + 1^2} = 1.0$$
a and c share an x coordinate, so only the y difference is left
$$\texttt{b is c} \rightarrow \texttt{False}$$
different objects and different coordinates
$$\texttt{a is a.move(0, 0)} \rightarrow \texttt{False}$$
same coordinates, different object, because move always builds
Independent check on the distance: c is directly north of a by one unit on the grid, so the straight line distance has to be exactly 1.0 with no rounding.
Same coordinates is not the same object. In this design that distinction is what keeps walkers apart.
3§12.5 — trials outside, steps inside
A counter is bumped once per step, with the trial loop outside and the step loop inside. The same function is called with the two numbers swapped.
Find(a) Write exactly what this program prints, both lines.
Given
classCounter(object):
def__init__(self):
self.n = 0defbump(self):
self.n = self.n + 1returnself.n
deftrials(numTrials, numSteps):
c = Counter()
results = []
for t inrange(numTrials):
for s inrange(numSteps):
c.bump()
results.append(c.n)
return results
print(trials(3, 4))
print(trials(4, 3))
IPython console
In [1]: %run untitled0.py
Both calls end at 12, because both do the same total amount of work.
the list length is the number of trials, and the step between its entries is the number of steps
so the two arguments are not interchangeable: they change the shape of the result even when the total is the same
this is exactly the numTrials against numSteps distinction, with the walking taken out
Hint 1/4
Work out what the counter holds at the end of each trial, and note that it is never reset between trials.
Hint 2/4
The inner loop runs numSteps times per trial and the counter keeps its value across trials, so the value appended at the end of trial t is t times numSteps.
Hint 3/4
The calls are trials(3, 4) and then trials(4, 3), and each call builds a fresh counter inside the function.
Hint 4/4
Three multiples of four, then four multiples of three.
Show solution
Run the first call
$$\text{trial 1} \rightarrow 4$$
four bumps, and the counter starts at zero
$$\text{trials 2, 3} \rightarrow 8, 12$$
the counter is not reset, so each trial adds four more
Run the second call
$$3, 6, 9, 12$$
four trials of three steps, so the entries step by three and there are four of them
$$3 \times 4 = 4 \times 3$$
the totals match, which is why the last entry is the same in both lists
Independent check by length: the first list must have as many entries as there are trials, 3 and 4 respectively, and it does. The final entry equals the product in both cases.
numTrials sets how many numbers you get. numSteps sets how large each one is.
4§12.5 — a mean that does not print tidily
Six distances are already measured. The program takes their mean and prints it three ways, then prints the spread.
Find(a) Write exactly what this program prints, all four lines.
Given
distances = [7.2, 13.0, 4.5, 21.1, 9.8, 5.6]
total = 0for dist in distances:
total = total + dist
mean = total / len(distances)
print(mean)
print(format(mean, '.2f'))
print(round(mean, 1))
print(max(distances) - min(distances))
IPython console
In [1]: %run untitled0.py
61.2 divided by 6 is 10.2 on paper, and the first line shows what the interpreter actually holds.
the tail of digits is the binary representation, not an error in the data
format(mean, '.2f') gives the string 10.20, keeping the trailing zero
round(mean, 1) gives the number 10.2, and printing a number drops the trailing zero
Hint 1/4
Compute the sum first, then decide what each of the four prints does to it.
Hint 2/4
Dividing two floats can leave a value that has no exact binary form, and an unformatted print shows every digit Python holds. format and round both cut it, in different ways.
Hint 3/4
The distances again: 7.2, 13.0, 4.5, 21.1, 9.8 and 5.6. Their sum is 61.2 and there are six of them, and the last print is a subtraction of two of them.
Hint 4/4
A long float, then two decimals, then one decimal, then the difference of the largest and the smallest.
Show solution
Sum and divide
$$\text{sum} = 61.2,\quad n = 6$$
the loop adds the six values in the order written
$$\frac{61.2}{6} = 10.2$$
on paper, but in binary the result carries a tail and the unformatted print shows it
Independent check on the mean: it must lie between 4.5 and 21.1, and 10.2 does. The tail of digits is far below the precision of the measurements, which is why it is a printing question and not a data question.
Report a mean through format. Keep the unformatted value for any arithmetic that follows.
5§12.6 — three runs turned into a growth table
Three sets of trial distances at three walk lengths, and the two columns a growth question needs.
Find(a) Write exactly what this program prints, all three lines.
Given
defmeanOf(values):
total = 0for v in values:
total = total + v
return total / len(values)
runs = [[3.2, 3.9, 3.7], [6.9, 7.4, 7.3], [14.0, 14.9, 14.0]]
stepCounts = [16, 64, 256]
for i inrange(len(runs)):
mean = meanOf(runs[i])
print(stepCounts[i], format(mean, '.1f'), format(mean / stepCounts[i]**0.5, '.2f'))
IPython console
In [1]: %run untitled0.py
The ratio column is flat at about 0.90 while the step count goes up by a factor of 16.
the step counts are 16, 64 and 256, whose square roots are the whole numbers 4, 8 and 16
so each ratio can be checked by eye: 3.6 over 4, 7.2 over 8, 14.3 over 16
the last one prints as 0.89 rather than 0.90 because the mean used in the division is 14.3 and not a rounder number
Hint 1/4
Work out the three means first, then the divisor each one is compared against.
Hint 2/4
The second column is the mean of that run, and the third is the mean divided by the square root of the step count. A flat third column is the signature of .
Hint 3/4
The runs again: three distances at 16 steps, three at 64 and three at 256, and the square roots of those step counts are 4, 8 and 16.
Hint 4/4
Three lines, each with the step count, the mean to one decimal and the ratio to two.
Independent check across the rows: the step count is multiplied by four from row to row, so the means should roughly double, and 3.6, 7.2, 14.3 does exactly that. The two columns agree with each other.
Choose step counts whose square roots are whole numbers when you want a table you can check without a calculator.
C · exam level 5 questions
1§12.4 — the walk function and its three tests
An exam part that asks for the function and for the evidence that it works. The evidence is the part most answers leave out.
Find
(a) Write walk(f, d, numSteps) with a docstring in the shape the course uses.
(b) Write a checkWalk(numSteps, steps) that runs one scripted walk and prints the step count, the ending place and the distance to two decimals.
(c) Call it for 0 steps, for 1 step, and for the six step script east, north, east, north, south, east, and say what each line of output rules out.
Given
a Location class with move, getX, getY and distFrom
a Field class with addDrunk, moveDrunk and getLoc
a walker whose steps are given as a list, so that answers are predictable
Hint 1/4
Write down the three lines of the walk function before anything else, and then ask what a test would have to print to catch each of the three ways those lines go wrong.
Hint 2/4
The function reads the starting place, loops numSteps times and measures from the saved place. A scripted walker makes the expected answer computable by hand, so a wrong number is a bug rather than bad luck.
Hint 3/4
The three calls again: 0 steps, 1 step, and the script [(0, 1), (1, 0), (0, 1), (1, 0), (0, -1), (1, 0)]. Their expected distances are 0.00, 1.00 and the root of 10.
Hint 4/4
The program below prints three lines, and the first two of them are the ones that catch the common bugs.
Show solution
Write walk
$$\texttt{start = f.getLoc(d)}$$
first, because after the loop this place is gone
$$\texttt{return start.distFrom(f.getLoc(d))}$$
a number, not a place, and measured from the saved place
Make the answers predictable
$$\text{scripted steps}$$
the expected distance can be computed by hand, which is what makes a failed test mean something
$$0 \rightarrow 0.00,\ 1 \rightarrow 1.00$$
the two cheapest cases, and both are forced
Check the six step case by hand
$$\Delta x = 3,\ \Delta y = 1$$
three easts and one net north out of the six steps
$$\sqrt{10} = 3.16$$
which is what the third line printed
Answer $$\boxed{0.00,\ 1.00,\ 3.16}$$
Check
Independent check on the third line: the six step script ends at <3, 1>, printed by the program itself, and the distance to <3, 1> from the origin is the root of 10, 3.162. The place and the distance were computed by different methods.
A test is only a test if you knew the answer before you ran it.
2§12.3 — a field with holes in it
The chapter ends with fields where the places themselves behave oddly, and this is that exercise in its smallest form. A subclass, one overridden method, and a scripted walker so that the output is predictable.
Find
(a) Write OddField, a subclass of Field whose constructor takes the holes dictionary and whose moveDrunk teleports the walker when he lands on a hole.
(b) Print the walker's place after each of four steps, with the hole at (2, 0) leading back to the origin.
Given
a Field class with addDrunk, moveDrunk and getLoc
holes given as a dictionary from a coordinate pair to a Location
a scripted walker who always steps east, added at the origin
Hint 1/4
Ask what the subclass adds to what the base class already does, and in which order the two things have to happen.
Hint 2/4
A subclass method can call the base class version by name, Field.moveDrunk(self, drunk), and then do its own extra work. The teleport is a dictionary lookup on the coordinates of the place the walker landed on.
Hint 3/4
The holes again, {(2, 0): Location(0, 0)}, and a walker who steps east every time, starting at the origin.
Hint 4/4
The program below prints four lines plus a line each time the walker falls into the hole.
Show solution
Extend the method
$$\texttt{Field.moveDrunk(self, drunk)}$$
the ordinary step happens first, so the walker is on the place that may be a hole
$$\texttt{key = (here.getX(), here.getY())}$$
a pair of numbers can be a dictionary key, while a Location object would be a different key for every visit
Follow the four steps
$$\texttt{<1, 0>}, \text{ then } \texttt{<2, 0>} \rightarrow \texttt{<0, 0>}$$
the second step lands on the hole and the walker is sent back to the origin
$$\text{the cycle repeats}$$
steps three and four do the same thing, so the walker is trapped between the origin and one east
Independent check on the key choice: using the Location object itself as the hole key would never match, because each move builds a new object, and the printed output would show the walker sailing past <2, 0> to <3, 0> and <4, 0>.
When a lookup has to match a value rather than an object, key the dictionary on the value.
3§12.6 — deriving a measured mean from a step list
Two walkers were measured at the same walk length and the numbers are far apart. One of them has a step list that is not balanced, and the question is whether its number can be predicted from the list alone.
Find
(a) Compute the average change in x and in y per step for ColdDrunk, straight from its step list.
(b) Use it to predict the mean distance after 400 steps, and compare with the measured 100.5.
(c) Say why the same calculation predicts nothing useful for UsualDrunk.
Given
import random
classLocation(object):
def__init__(self, x, y):
self.x = x
self.y = y
defmove(self, deltaX, deltaY):
return Location(self.x + deltaX, self.y + deltaY)
defdistFrom(self, other):
xDist = self.x - other.x
yDist = self.y - other.y
return (xDist**2 + yDist**2)**0.5classUsualDrunk(object):
deftakeStep(self):
stepChoices = [(0, 1), (0, -1), (1, 0), (-1, 0)]
return random.choice(stepChoices)
classColdDrunk(UsualDrunk):
"""Walks like the usual drunk, except that a step south is twice as long"""deftakeStep(self):
stepChoices = [(0, 1), (0, -2), (1, 0), (-1, 0)]
return random.choice(stepChoices)
defmeanDistance(drunkClass, numSteps, numTrials):
d = drunkClass()
total = 0for t inrange(numTrials):
start = Location(0, 0)
here = start
for s inrange(numSteps):
xDist, yDist = d.takeStep()
here = here.move(xDist, yDist)
total = total + start.distFrom(here)
return total / numTrials
random.seed(23)
for drunkClass in [UsualDrunk, ColdDrunk]:
print(drunkClass.__name__, format(meanDistance(drunkClass, 400, 200), '.1f'))
It prints:
UsualDrunk 17.5
ColdDrunk 100.5
ColdDrunk's step list is [(0, 1), (0, -2), (1, 0), (-1, 0)]
Hint 1/4
Average the four pairs of the step list before thinking about the walk at all. The question is whether that average is zero.
Hint 2/4
The average change per step is the sum of the four pairs divided by four, since each is equally likely. Over n steps the walker drifts by n times that average, and a drift that is not zero grows in proportion to n.
Hint 3/4
The list again: [(0, 1), (0, -2), (1, 0), (-1, 0)], and the measured mean at 400 steps is 100.5 for ColdDrunk against 17.5 for UsualDrunk.
Hint 4/4
The average step is a quarter of a unit south, so after 400 steps the drift is about 100, which is the measured number.
Show solution
Average the four pairs
$$\bar{\Delta x} = \frac{0+0+1-1}{4} = 0$$
east and west appear once each, so they cancel
$$\bar{\Delta y} = \frac{1-2+0+0}{4} = -0.25$$
the south step is twice as long as the north one, and nothing balances that
Scale it up to 400 steps
$$400 \times 0.25 = 100$$
a fixed drift per step accumulates in proportion to the number of steps
$$100 \text{ against the measured } 100.5$$
within half a step, from a calculation that never ran the simulation
Try the same thing on the fair walker
$$\bar{\Delta x} = \bar{\Delta y} = 0$$
every step has an opposite of the same length
$$\text{predicted } 0,\ \text{measured } 17.5$$
so for the fair walker the drift calculation is the wrong tool and the square root rule is the right one
Answer $$\boxed{\text{drift } 0.25 \text{ per step} \Rightarrow 100 \text{ at } 400 \text{ steps}}$$
Check
Independent check at another length: the same calculation predicts 25 at 100 steps and 400 at 1600, and both are four times apart exactly as the measured biased column was, which is the growth pattern of a drift rather than of a wander.
Average the step list first. If the average is not zero, the walker has a drift and the square root rule is the wrong rule.
4§12.7 — a figure described in words
A figure specification of the kind a lab paper or an exam gives, with the data already available from the simulation. Write the drawing half only.
Find
(a) Write the drawing part of the program, taking the styles from a list so that each class keeps its own look.
(b) Say why the x axis is drawn on a logarithmic scale here and what would go wrong on a linear one.
Given
means for two walker classes at walk lengths 25, 100, 400 and 1600
one panel, one line per walker class, markers on the points
title, both axis names, a legend naming the classes, and the x axis on a logarithmic scale because the step counts quadruple
Hint 1/4
List the five things the specification names before writing any call, and decide which of them are one call each.
Hint 2/4
One drawing call per series, the style taken from a list indexed by the series number, then the labelling calls. A logarithmic x axis is semilogx in place of plot.
Hint 3/4
The step counts again, 25, 100, 400 and 1600, each four times the last, and there are two walker classes to draw.
Hint 4/4
The program below draws both series in a loop and finishes the panel with four labelling calls.
which is the right frame for a rule stated as a multiplication
Answer $$\boxed{\text{two drawing calls in a loop, four labelling calls}}$$
Check
Independent check on the styles: the list has two entries for two classes, so no class shares a look with another, and the legend has as many entries as there are drawing calls.
A specification that names a scale is naming the question. Quadrupling steps is a multiplicative question.
5§12.5 — one wrong line in a working report
A report function that runs, prints four numbers and is wrong. The walker, the field and the walk function are the ones from this page.
Find(a) Which line makes the reported mean wrong?
Given
defreport(numSteps, numTrials):
d = UsualDrunk()
origin = Location(0, 0)
distances = []
for t inrange(numTrials):
f = Field()
f.addDrunk(d, origin)
distances.append(walk(f, d, numSteps))
print(numTrials, format(sum(distances) / numSteps, '.2f'),
format(max(distances), '.2f'), format(min(distances), '.2f'))
Hint 1/4
Ask what each printed number is supposed to be, then check the arithmetic of the one that is a mean.
Hint 2/4
A mean is a sum divided by how many values went into the sum. The list holds one distance per trial, so its length is numTrials.
Hint 3/4
Here the list is appended to once per trial, so it holds numTrials distances, and the printed mean divides by numSteps instead.
Hint 4/4
The division in the print call is the wrong one.
Show solution
Count the values
$$\texttt{len(distances)} = \texttt{numTrials}$$
the append runs once per pass of the trial loop
$$\text{divisor should be } \texttt{numTrials}$$
a mean divides by how many values there are
Read the effect
$$\frac{\text{sum}}{\texttt{numSteps}}$$
with 100 trials of 1000 steps this is a thousand times too small, and nothing raises
$$\text{other lines correct}$$
the fresh field and the addDrunk call are the reset, and the append is once per trial
Independent check with numbers: the largest and smallest distances are printed unmodified, so a mean that comes out far below the printed minimum is impossible for a correct mean and is what this bug produces.
Check a reported mean against the printed minimum. A mean below the minimum is a divisor bug.
D · interleaved 3 questions
1§12.5 — a helper that keeps what it collected
A function collects values into a list and hands the list back. It is called twice, with different data each time.
Find(a) Write exactly what this program prints, all three lines.
Given
defcollect(values, into=[]):
for v in values:
into.append(v)
return into
first = collect([1.0, 2.0])
second = collect([3.0])
print(first)
print(second)
print(first is second)
IPython console
In [1]: %run untitled0.py
One list, two names, three values.
the default is built once at definition time, not once per call
so the second call appends to the list the first call already filled
a collector for trial distances written this way carries the previous experiment's numbers into the next one
Hint 1/4
Ask when the list in the parameter position is created, and how many times that happens.
Hint 2/4
A default value is built once, when the function is defined, and the same object is used on every call that leaves the parameter out. Appending to it changes that one object.
Hint 3/4
Here both calls leave the parameter out, so both append into the same list: first 1.0 and 2.0, then 3.0.
Hint 4/4
Both names show the same three values, and the identity test is True.
Show solution
Locate the construction
$$\texttt{into=[]} \text{ at definition time}$$
the empty list is one object, built once, and stored with the function
$$\text{both calls omit it}$$
so both calls append into that same object
Read the two names
$$\texttt{[1.0, 2.0, 3.0]} \text{ twice}$$
the first call's returned list and the second call's returned list are the same list
$$\texttt{first is second} \rightarrow \texttt{True}$$
which is the proof that one object is being reused
Independent check: if a fresh list were built per call, the second line would hold one value and the identity test would be False. Both printed values rule that out.
Collect into a list the caller passes in, or build the list inside the function. A default of [] is a shared object.
2§12.1 — a base class and one overridden method
Two classes, one of which replaces a single method of the other, and three questions asked of the pair.
Find(a) Write exactly what this program prints, all three lines.
Given
classDrunk(object):
def__init__(self, name=None):
self.name = name
deftakeStep(self):
return (0, 0)
def__repr__(self):
ifself.name != None:
returnself.name
return'Anonymous'classColdDrunk(Drunk):
"""Any step is fine, but south is twice as long as the others"""deftakeStep(self):
return (0, -2)
a = Drunk()
b = ColdDrunk('Bart')
print(a, b)
print(a.takeStep(), b.takeStep())
print(isinstance(b, Drunk))
IPython console
In [1]: %run untitled0.py
The subclass changed one method and inherited everything else.
Drunk() was built with no name, so the repr returns Anonymous
ColdDrunk did not redefine the repr, so Bart prints through the inherited one
isinstance is True because a ColdDrunk is a Drunk, which is what lets one walk function serve every walker class
Hint 1/4
Decide for each printed value which class's method runs, before working out what that method returns.
Hint 2/4
A method defined in the subclass replaces the inherited one for objects of the subclass. Anything not redefined is inherited as it stands, including the printed form.
Hint 3/4
Here the base object is built with no name, so its repr returns the word for a nameless drunk, and the subclass redefines takeStep only.
Hint 4/4
The two printed forms, then the two steps, then the membership test.
Show solution
Print the two objects
$$\texttt{Anonymous}$$
the base object has no name, and the repr has a branch for exactly that case
Independent check on the last line: the walk function in this section takes any object with a takeStep method, and it ran on this subclass earlier in the page, which is the same fact the True reports.
Override the one method that differs. Every walker class in this section is four lines long.
3§12.5 — counting the work a simulation does
A walker that counts its own steps instead of taking them, run three times with different trial and step counts.
Find(a) Write exactly what this program prints, all three lines.
Given
classStepCounter(object):
def__init__(self):
self.steps = 0deftakeStep(self):
self.steps = self.steps + 1return (1, 0)
defsimWalks(numSteps, numTrials, d):
for t inrange(numTrials):
for s inrange(numSteps):
d.takeStep()
return d.steps
d = StepCounter()
print(simWalks(100, 10, d))
d2 = StepCounter()
print(simWalks(1000, 10, d2))
d3 = StepCounter()
print(simWalks(100, 100, d3))
IPython console
In [1]: %run untitled0.py
The second and third calls do the same amount of work by different routes.
the cost of a simulation is numTrials times numSteps, so both come to ten thousand
what the two runs buy is not the same: one gives 10 long walks, the other 100 short ones
each call gets a fresh counter, which is why the third line is not the running total of all three
Hint 1/4
Work out how many times the inner loop body runs in total for each call, and notice which object is reused between calls.
Hint 2/4
A nested loop runs the outer count times the inner count, so the step total is numTrials times numSteps. Each call here is given its own counter object.
Hint 3/4
The three calls again: 100 steps by 10 trials, then 1000 steps by 10 trials, then 100 steps by 100 trials.
Hint 4/4
A thousand, then ten thousand, then ten thousand again.
Show solution
Multiply
$$100 \times 10 = 1000$$
the inner loop runs numSteps times per trial, and there are numTrials trials
The key is the walker object. Writing to a key that is already there replaces the value, which is how a reset works and how a walk gets deleted by accident.
k is the number of entries in the step list, and entries may repeat, which is how a probability is written into the list.
From a simulation to a figure
$$\text{question} \rightarrow \text{lists} \rightarrow \text{one call per series} \rightarrow \text{labels and limits}$$
Two panels that are being compared take the same axis limits, and each walker class keeps one style across figures.
Check yourself
Close the page and write, from memory:
the four step pairs and the call that picks one
what move returns and what it leaves alone
what the field's dictionary maps to what, and what a second addDrunk call for one walker does
the three lines of the walk function, in order
the two lines that make a trial honest
what four times the steps does to the mean, fair and biased
Then write the simulation from the last rung's specification without looking, and run the zero and one step checks before comparing.
Write the step list and the call that picks one step from it, and say what a second seed call with the same number does to the stream?
c-one-step
Say which object is new after a move, which one is unchanged, and compute the distance between two places whose coordinates differ by 3 and 4?
c-location
Say what the field holds, what happens when addDrunk is called twice for one walker, and why two walkers can share one Location object here?
c-field
Write the walk function from memory and say what each of the zero, one and two step checks would catch?
c-walk
Write the trial loop with its reset, report the three numbers, and say where the seed call belongs and why?
c-trials
Predict the mean at 900 steps from the mean at 100, and compute a walker's drift straight from its step list?
c-growth
Choose the figure for a stated question, give each walker class its own style, and say what a frame chosen from the data can hide?
c-visualize
Glossary (22 terms)
random walkrastgele yürüyüş
A path built one random step at a time, where nothing about the next step depends on the ones already taken.
drunkard's walksarhoş yürüyüşü
The random walk this chapter uses as its example: one unit step per second, north, south, east or west, each equally likely.
simulationsimülasyon
A program that runs a process many times to answer a question about it, when the question cannot be settled by argument on paper.
trialdeneme
One complete run of the process being simulated. Here one trial is one walk of numSteps steps, starting from the origin.
unit stepbirim adım
One move of length one, parallel to an axis, written as a pair of coordinate changes such as (0, 1) for north.
seed
The number that chooses where the stream of random numbers begins. The same seed reproduces the same run on the same interpreter, which is what makes a claim about a random program checkable.
sözde rastgele
Produced by a rule rather than by chance, but spread out like chance. This is why a seed can reproduce a whole run exactly.
deterministik
Producing the same result every time from the same input. A scripted walker is deterministic; a seeded random walker is deterministic in the same sense once the seed is fixed.
scripted walker
A walker whose steps are read from a list in order, used on this page to test the machinery. Its answers can be computed by hand, so a wrong answer is a bug rather than bad luck.
sanity check
A tiny case whose answer is known before the program is run: zero steps must give 0.0 and one step must give 1.0. Its only purpose is to fail when something is broken.
meanortalama
The sum of the measured values divided by how many there are. In a simulation the divisor is the number of trials and never the number of steps.
spreadyayılım
How far apart the answers of separate trials are, reported here as the largest and the smallest distance beside the mean.
resetsıfırlama
Putting the walker back on the starting place before a trial, with an addDrunk call or a fresh field, so that the trial measures a walk from there rather than the next leg of a longer one.
straight line distancedoğrusal uzaklık
The length of the line from where a walk started to where it ended, computed from the two coordinate differences. The number a walk reports.
yol uzunluğu
The total distance walked, which for unit steps is just the number of steps. An upper bound on the straight line distance and usually much larger than it.
originbaşlangıç noktası
The place a walk starts from, Location(0, 0) on this page. Distances are measured from it when every trial resets to it.
driftsürüklenme
A fixed average movement per step, which appears when the step list is not balanced. It accumulates in proportion to the number of steps.
biasyanlılık
A step list in which some direction is more likely, or longer, than its opposite. A biased walker has a drift and breaks the square root rule.
square root growthkarekök büyümesi
The pattern a fair walk's mean distance follows: multiplying the number of steps by four multiplies the mean by about two.
saçılım grafiği
A figure with one mark per observation at its two coordinates, used here to show where many walks ended without saying anything about the order they ran in.
A small object that hands out the next plotting style each time it is asked, so that each walker class keeps one look across figures.
base classtaban sınıf
The class a subclass extends. Here it holds the name and the printed form, and it is not useful on its own: every walker that can actually walk is a subclass.
What comes next
§13 · Understanding Experimental Data (Chapter 18)
The means measured here sit almost exactly on a square root curve, and this page checked that by dividing the curve out by hand. Next comes the machinery for fitting a curve to measured points and for saying how well it fits.
Sources
kitapJohn Guttag, Introduction to Computation and Programming Using Python, with Application to Understanding Data, Second Edition, Chapter 14 The chapter this week's syllabus line names. Its sections are the walk itself, the sanity checks, the step to several walker classes, the plotting of the results, and fields whose places behave oddly.
ders malzemesiThe week's lecture slide deck on random walks Source of the problem statement, the three abstractions and the walk function's docstring. The deck states that it was adapted from MIT introductory computer science materials under a Creative Commons licence.
ders malzemesiThe week's three lecture programs, one walk, ten walks and five walkers, together with the Location, Field and Drunk files Source of the naming style used here, of the plotting of final locations with one colour per walker, and of the mean, largest and smallest report. The programs draw with pyplot and print their numbers unformatted.
sabitThe language's own documentation for the random module Used for the three functions this page allows itself, choice, seed and randint, and for the statement that a seeded run repeats on the same interpreter.