I just started learning Python and this is the first code I wrote. It's a Random Walk generator. I think I got it to work pretty well but I'm also sure there are more effective and less clunky ways to do this. Could you give me some tips on what to change to make the code shorter/more optimized and just objectively more "pythonian" and better?
# Random Walk in two dimensions
import random
steps = "a"
maximal = "a"
minimal = "a"
Xstep = 0
Ystep = 0
StepSize = 0
PosNeg = 0
def new_line():
print(" ")
def invalid_enter_number():
print("Invalid input! Enter a number.")
new_line()
def invalid_enter_positive():
print("Invalid input! Enter a positive number.")
new_line()
new_line()
print("Random Walk in two dimensions")
new_line()
while type(steps) != int:
try:
steps = int(input("How many steps should be done? "))
while steps <= 0:
invalid_enter_positive()
steps = int(input("How many steps should be done? "))
except:
steps = ValueError
invalid_enter_number()
continue
while type(maximal) != int:
try:
maximal = int(input("How big is the biggest possible step? "))
while maximal <= 0:
invalid_enter_positive()
maximal = int(input("How big is the biggest possible step? "))
except:
maximal = ValueError
invalid_enter_number()
continue
if maximal != 1:
while type(minimal) != int:
try:
minimal = int(input("How big is the smallest possible step? "))
while minimal <= 0 or minimal >= maximal:
if minimal <= 0:
invalid_enter_positive()
minimal = int(input("How big is the smallest possible step? "))
else:
print("Number must be smaller than the biggest possible step!")
new_line()
minimal = int(input("How big is the smallest possible step? "))
except:
minimal = ValueError
invalid_enter_number()
continue
else:
minimal = 1
new_line()
print("Number of steps:", steps)
if maximal != 1:
print("Step size range:", minimal, "-", maximal)
else:
print("Step size: 1")
new_line()
print("Steps:")
while steps > 0:
StepSize = random.randint(minimal, maximal)
PosNeg = random.randint(0, 1)
chance = random.randint(0, 1)
if chance == 0 and PosNeg == 0:
Xstep += StepSize
elif chance == 0 and PosNeg == 1:
Xstep -= StepSize
elif chance == 1 and PosNeg == 0:
Ystep += StepSize
else:
Ystep -= StepSize
print("X:", Xstep, " Y:", Ystep)
steps -= 1
2 Answers 2
Here's one suggestion; other readers will probably offer more. Several times, you need to get an integer from the user. You could create a function to handle such details. The code below shows what the function might look light and how you would use it.
def int_from_user(prompt):
while True:
try:
n = int(input(prompt + ' '))
if n > 0:
return n
else:
raise ValueError()
except ValueError:
print('Invalid input! Enter a positive number.')
steps = int_from_user('How many steps should be done?')
print(steps)
-
\$\begingroup\$ Awesome, thanks! Just one question: What does the
raise
do? If the input value isn't > 0 but also isn't a string so it doesn't make a ValueError then thisraise
forces a ValueError? \$\endgroup\$Vojta– Vojta2021年03月11日 21:15:27 +00:00Commented Mar 11, 2021 at 21:15 -
\$\begingroup\$ @Vojta Yes, try to convert to int, which can fail. Then check for positive-int and, on failure, raise any ValueError merely to trigger the except-clause. It's just a technique so we don't have to duplicate the printing logic. A reasonable alternative (maybe even a better one!) is to define the error message in a variable before the while-loop; then, in the else-clause, just print it -- no need for the semi-mysterious empty ValueError. But there probably are situations where the technique (raising to trigger your own except-clause) is still useful to know about. \$\endgroup\$FMc– FMc2021年03月11日 21:40:10 +00:00Commented Mar 11, 2021 at 21:40
If your concern is speed, I would have some suggestions:
You use the randint function 3x per step, while you only need it once: Just use random.choice() or numpy.randint() to directly select from a range of positive or negative step-sizes. This also gets rid of the excessive if statements. The slowest part is the print! Instead of printing every step, you should only print whenever needed, i.e. every 1000th step. Also instead of the while loop, python is actually faster using for loops.
Here is an example of a 3d random walk with 500 particles in a cube. It's 3d because it generates 3 coordinates per particle. You can change the dimensionality yourself.
N = 500 #simulating 500 particels at once
simulation_length = 10**6
centers = np.random.randint(-500,500,N*3) #cube of length 1000
for i in range(simulation_length):
centers += np.random.randint(-2,3,N*3)
centers = np.clip(centers,-500,500) #cant go outside box, alternatively, you may want to create symetric boundary conditions
#here you would calculate something?
if i%10000 == 0:
print(i)
This code is not the best possible either; you would need to have a look at the validity of the simulation itself (is a strict grid valid?), and @performace it would surely benefit from multi-processing.
-
\$\begingroup\$
N = 500
centers = np.random.randint(-500, 500, N*3)
This just gives me 1500 random numbers between -500 and 500 right? How is that a cube of length 1000? Sorry for a dumb question, I think the jump from 2D to 3D is confusing me... \$\endgroup\$Vojta– Vojta2021年03月14日 13:42:58 +00:00Commented Mar 14, 2021 at 13:42 -
\$\begingroup\$ If you want to describe a single particle in 2d space, then you would need to generate 2 random numbers, or start at (0,0). Here we describe 500 particles in 3d, hence 1500 numbers. \$\endgroup\$KaPy3141– KaPy31412021年03月15日 10:05:22 +00:00Commented Mar 15, 2021 at 10:05