I decided to implement a random walk.
The rules
The rules are easy, an object is moved in the direction dictated by random or pseudo-random numbers. If you want to read more about it, see the Wikipedia Page.
A simple example
Let's say we decide:
1 = "Go right"
2 = "Go down"
3 = "Go left"
4 = "Go up"
We then generate the random or pseudo-random sequence: 3,1,4,1
.
And we convert it to directions:
- Left
- Right
- Up
- Right
we obtain the following:
enter image description here
A more complex example
Runnning my code with:
step_size = 15
step_number = 1000
Generated the following walk:
Random walk
Running the code with the exact same parameters may yield completely different results because pseudo-random numbers are used.
I think my code is well written and fully Pep8 compliant, two things bother me
I define many
go_<direction>
methods to go to absolute directions, I could have done something like:turn_of_random_deegres() turtle.forward(step)
But it looked cleaner to me, albeit a little longer, to define absolute moving.
- The
go_<direction>
methods change between logo and standard edition. (Control-Find setheading in this doc-page)
- The
The code
import turtle
import random
def go_right(step):
turtle.setheading(0)
turtle.forward(step)
def go_up(step):
turtle.setheading(90)
turtle.forward(step)
def go_left(step):
turtle.setheading(180)
turtle.forward(step)
def go_down(step):
turtle.setheading(270)
turtle.forward(step)
def make_random_walk(step_size, step_number):
move_dict = {1: go_up,
2: go_right,
3: go_left,
4: go_down
}
for _ in range(step_number):
move_in_a_direction = move_dict[random.randint(1, 4)]
move_in_a_direction(step_size)
if __name__ == "__main__":
turtle.hideturtle()
turtle.speed("fastest")
make_random_walk(15, 1000)
3 Answers 3
Your code looks nice and seems to be fully PEP 8 compliant indeed. The fact that you have 1 blank line between functions except in one place where you have 2 puzzles me a bit but that's not a huge issue.
You can actually make your code much easier. Here are a individual steps, you'll find the final code at the end.
Notice that you call
turtle.forward(step)
in eachgo_somewhere
function. You might as well remove this and put it once and for all after your call tomove_in_a_direction(step_size)
. Now, thego_somewhere
function doesn't need to be given astep
anymore.Notice that the different function
go_somewhere
are just a call tosetheading
with a custom parameter. You could transformmove_dict
to map numbers to angles and callturtle.setheading(move_dict[random.randint(1, 4))
.Notice that your map is just converting 1 into 0, 2 into 90, 3 into 180, 4 into 270. This can be substitued with a simple operation : remove 1 then multiply by 90. You now have
turtle.setheading((random.randint(1, 4) - 1) * 90)
.Notice that generating an integer in [0, 1, 2, 3] makes things even easier as you don't need to remove 1.
Here's the corresponding code :
def make_random_walk(step_size, step_number):
for _ in range(step_number):
turtle.setheading(90 * random.randint(0, 3))
turtle.forward(step_size)
All of the go_*
functions are implemented with the same code. The only difference is the value passed to setheading()
. You could create one common implementation and replace them with calls to that function.
def go(step, heading):
turtle.setheading(heading)
turtle.forward(step)
def go_down(step):
go(step, 270)
Extracting repeated code will ensure that, if a bug exists in the common code, you won't have to remember to fix it in all the locations. It can also lead to a smaller overall code size. With this code the benefit is minimal, but as code gets more complex it will help a lot.
Instead of creating a dictionary and randomly indexing a specific value, you can use random.choice()
. It will do the work to select a random element.
move_options = (go_up, go_right, go_left, go_down)
for _ in range(step_number):
move_in_a_direction = random.choice(move_options)
move_in_a_direction(step_size)
I think that you can simplify the simulation like this without losing any clarity:
import turtle
import random
def go(heading, step_size):
turtle.setheading(heading)
turtle.forward(step_size)
def random_walk(step_size, steps):
# Assumes turtle.mode('standard')
DIRECTIONS = (EAST, NORTH, WEST, SOUTH) = (0, 90, 180, 270)
for _ in range(steps):
go(random.choice(DIRECTIONS), step_size)
if __name__ == '__main__':
turtle.hideturtle()
turtle.speed('fastest')
random_walk(15, 1000)
I've changed the terminology from right/left to east/west, since the former could be misinterpreted as a relative turn rather than an absolute heading. In any case, the designations are entirely optional.
The use of a dict is not ideal. You end up hard-coding 1–4 twice.
Be consistent with your parameter names. step_size
is more descriptive than step
, so use it for all of the functions. step_number
sounds like the nth step to me, and would be slightly better named steps
.