8
\$\begingroup\$

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 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)

\$\endgroup\$

3 Answers 3

7
\$\begingroup\$

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 each go_somewhere function. You might as well remove this and put it once and for all after your call to move_in_a_direction(step_size). Now, the go_somewhere function doesn't need to be given a step anymore.

  • Notice that the different function go_somewhere are just a call to setheading with a custom parameter. You could transform move_dict to map numbers to angles and call turtle.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)
\$\endgroup\$
4
\$\begingroup\$

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)
\$\endgroup\$
4
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.