I am having a problem with my code mapping a random walk in 3D space. The purpose of this code is to simulate N steps of a random walk in 3 dimensions. At each step, a random direction is chosen (north, south, east, west, up, down) and a step of size 1 is taken in that direction. Here is my code:
import random # this helps us generate random numbers
N = 30 # number of steps
n = random.random() # generate a random number
x = 0
y = 0
z = 0
count = 0
while count <= N:
if n < 1/6:
x = x + 1 # move east
n = random.random() # generate a new random number
if n >= 1/6 and n < 2/6:
y = y + 1 # move north
n = random.random() # generate a new random number
if n >= 2/6 and n < 3/6:
z = z + 1 # move up
n = random.random() # generate a new random number
if n >= 3/6 and n < 4/6:
x = x - 1 # move west
n = random.random() # generate a new random number
if n >= 4/6 and n < 5/6:
y = y - 1 # move south
n = random.random() # generate a new random number
if n >= 5/6:
z = z - 1 # move down
n = random.random() # generate a new random number
print("(%d,%d,%d)" % (x,y,z))
count = count + 1
print("squared distance = %d" % (x*x + y*y + z*z))
The problem is I am getting more than a single step between each iteration. I've added comments showing the difference in steps between iterations.
Here are the first 10 lines of the output:
(0,-1,0) #1 step
(0,-2,0) #1 step
(1,-3,1) #4 steps
(1,-4,1) #1 step
(1,-3,1) #1 step
(1,-2,1) #1 step
(2,-2,0) #2 steps
(2,-2,0) #0 steps
(2,-2,0) #0 steps
(2,-1,0) #1 step
If you remove the multiple n = random.random() from within the if statements and replace by a single n = random.random() at start of the while loop then there will be only one step per loop.
The issues lies with this line in each of your if statement : n = random.random(). Basically you should had used if and else if so if in a single iteration one if has been executed then it does not get executed again. Also you can add this line at the end of the all if statements instead of repeating it.n = random.random()
Replacing multiple if statements with chain of if-elif-else solved the problem. Also, you could iterate using for instead of while. And in addition - you don't need to regenerate n under each if.
import random # this helps us generate random numbers
N = 30 # number of steps
x = 0
y = 0
z = 0
for _ in range(N):
n = random.random() # generate a new random number
if n < 1/6:
x += 1 # move east
elif 1/6 <= n < 2/6:
y += 1 # move north
elif 2/6 <= n < 3/6:
z += 1 # move up
elif 3/6 <= n < 4/6:
x -= 1 # move west
elif 4/6 <= n < 5/6:
y -= 1 # move south
else:
z -= 1 # move down
print("(%d,%d,%d)" % (x, y, z))
print("squared distance = %d" % (x*x + y*y + z*z))
This is because you are setting n in each if-block which is satisfying next if blocks as well.
Just take out n = random.random() from all if blocks and place it just before end of while loop.
Tip:
To debug this I would've printed the value of n in each if case.
Related
Introduction
I am taking an online Introduction to Computer Science course for which I was given the task to create a function that takes in a coordinate and returns its corresponding "spiral index". I have a working function, but the online learning platform tells me that my code takes too long to execute (I am currently learning about code complexity and Big O notation).
The Problem Statement
All numbers on an infinite grid that extends in all four directions can be identified with a single number in the following manner:
Where 0 corresponds with the coordinate (0, 0), 5 corresponds with the coordinate (-1, 0), and 29 with (3, 2).
Create a function which returns the spiral index of any pair of coordinates that are input by the user.
Examples:
spiral_index(10, 10) returns 380.
spiral_index(10, -10) returns 440.
spiral_index(3, 15) returns 882.
spiral_index(1000, 1000) returns 3998000.
My Approach
def spiral_index(x, y):
if x == 0 and y == 0:
return 0
pos = [1, 0]
num = 1
ring_up = 0
ring_left = 0
ring_down = 0
ring_right = 0
base = 3
while pos != [x, y]:
if ring_up < base - 2:
pos[1] += 1
ring_up += 1
num += 1
elif ring_left < base - 1:
pos[0] -= 1
ring_left += 1
num += 1
elif ring_down < base - 1:
pos[1] -= 1
ring_down += 1
num += 1
elif ring_right < base:
pos[0] += 1
ring_right += 1
num += 1
else:
base = base + 2
ring_up = 0
ring_left = 0
ring_down = 0
ring_right = 0
return num
The above code is able to find the correct index for every coordinate, and (on my laptop) computes spiral_index(1000, 1000) in just over 2 seconds (2.06).
Question
I have seen some solutions people have posted to a similar problem. However, I am wondering what is making my code so slow? To my knowledge, I believe that it is executing the function in linear time (is that right?). How can I improve the speed of my function? Can you post a faster function?
The course told me that a for loop is generally faster than a while loop, and I am guessing that the conditional statements are slowing the function down as well.
Any help on the matter is greatly appreciated!
Thanks in advance.
First of all, your solution takes two coordinates, A and B, and is O(AB), which can be considered quadratic. Second of all, the similar problem involves constructing the entire spiral, which means there is no better solution. You only have to index it, which means there's no reason to traverse the entire spiral. You can simply find which ring of the spiral you're on, then do some math to figure out the number. The center has 1 element, the next ring has 8, the next 16, the next 24, and it always increases by 8 from there. This solution is constant time and can almost instantly calculate spiral_index(1000, 1000).
def spiral_index(x, y):
ax = abs(x)
ay = abs(y)
# find loop number in spiral
loop = max(ax, ay)
# one less than the edge length of the current loop
edgelen = 2 * loop
# the numbers in the inner loops
prev = (edgelen - 1) ** 2
if x == loop and y > -loop:
# right edge
return prev + y - (-loop + 1)
if y == loop:
# top edge
return prev + loop - x + edgelen - 1
if x == -loop:
# left edge
return prev + loop - y + 2 * edgelen - 1
if y == -loop:
# bottom edge
return prev + x + loop + 3 * edgelen - 1
raise Exception("this should never happen")
x = int(input("Pick one small number: "))
y = int(input("And a bigger number: "))
if x > y:
print ("Doesn't work. ")
elif x < y:
for i in range(x,y):
if i%7 == 0 and i%5 !=0:
z = sum(i)
print (z)
You probably want to sum i one at a time like this:
z = 0
for n in range(x,y):
if n%7 == 0 and n%5 !=0:
n += i
print("Running total:", z)
print("Final total:", z)
If you want to use sum instead you have to apply it to a list:
filtered_list = []
for n in range(x,y):
if n % 7 == 0 and n % 5 !=0:
filtered_list.append(n)
print("Final total:", sum(filtered_list))
or using a generator expression:
print(sum(n for n in range(x, y) if n%7 == 0 and n%5 !=0))
There is also a shortcut to get all the multiples of 7 within range(x, y) :
multiples = range(x + 7 - x % 7, y, 7)
Then you only have to check the second condition (not divisible by 5)
print(sum(n for n in multiples if n%5 !=0))
You could also create 2 sets and calculate the difference:
def multiples(start, stop, d):
""" This function returns the set of all multiples of d between start and stop """
return set(range(start + divisor - start % divisor, stop, divisor))
print(sum(multiples(x, y, 7) - multiples(x, y, 5))
Finally a more maths approach. Your if condition is selecting the multiples of 7 and filtering out the numbers that are multiples of both 5 and 7. This is equivalent to subtracting the sum of the multiples of the least common multiple of 5 and 7 (which is 35) from the sum of the multiples of 7 within the range...
# we need a function from the math module that calculates the greatest common denominator
# we will use this to help calculate the least common multiple
from math import gcd
def sum_of_multiples(start, stop, mult):
""" Calculate sum of multiples of mult that lie in the range start, stop """
start //= mult
stop //= mult
return mult * (stop - start) * (stop + start + 1) / 2
lcm = 5 * 7 // gcd(5, 7) # The least common multiple of 5 and 7 = 35
print("Total:", sum_of_multiples(x, y, 7) - sum_of_multiples(x, y, lcm))
(Of course in the above you could just write 35 instead of lcm. I show the calculation in case you want to apply this using other numbers.)
You are getting that exception because sum expects an object that can be iterated through or in other words can return an iterator, such as a list, or tuple.
A proper way would have been to utilize the iterator object returned by the range function to get the cumulative value of all the numbers between x and y which are factors for 7 but not factors of 5.
x = int(input("Pick one small number: "))
y = int(input("And a bigger number: "))
if x > y:
print ("Doesn't work. ")
elif x < y:
print(sum([i for i in range(x, y) if i%7 == 0 and i%5 != 0]))
I am using Python 3. My code below attempts to simulate N steps of a random walk in 3 dimensions. At each step, a random direction is chosen (north, south, east, west, up, down) with 1/6 probability each and a step of size 1 is taken in that direction. The new location is then printed. The starting location is the origin (0,0).
Even though there are no error messages, the code does not work. We should move only one step either in x, y or z. However, in the output, I see that sometimes I don't move at all or sometimes I move in more than one direction.
Here is my code:
import random
N = 30
n = random.random()
x = 0
y = 0
z = 0
count = 0
while count <= N:
if n < 1/6:
x = x + 1
n = random.random()
if n >= 1/6 and n < 2/6:
y = y + 1
n = random.random()
if n >= 2/6 and n < 3/6:
z = z + 1
n = random.random()
if n >= 3/6 and n < 4/6:
x = x - 1
n = random.random()
if n >= 4/6 and n < 5/6:
y = y - 1
n = random.random()
if n >= 5/6:
z = z - 1
n = random.random()
print("(%d,%d,%d)" % (x,y,z))
count = count + 1
print("squared distance = %d" % (x*x + y*y + z*z))
How do you think I can fix the problem?
Thanks a lot.
You should use elif instead of so many ifs. Every time the if is evaluated, the value of n changes and then may qualify for the next if.
Not only should you use elif, even for performance, but you don't need the multiple n = random.random() statements in the loop -- one will do:
import random
N = 30
x = 0
y = 0
z = 0
for _ in range(N):
n = random.random()
if n < 1/6:
x += 1
elif 1/6 <= n < 2/6:
y += 1
elif 2/6 <= n < 3/6:
z += 1
elif 3/6 <= n < 4/6:
x -= 1
elif 4/6 <= n < 5/6:
y -= 1
elif n >= 5/6:
z -= 1
print(f"({x},{y},{z})") # python 3.6ism
print("squared distance = {}".format(x*x + y*y + z*z))
Regardless the version of Python you are using, you need to implement the answer provided by #cdlane.
If you are using Python 2.X your other problem is that Python is interpreting your numbers as ints. To fix that you need to add . to the denominator i.e.
if n < 1/6.:
instead of
if n < 1/6:
1/6 and other fractions are interpreted as ints - you can check it yourself by typing print 1/6 which will give you 0 or printing the actual type with print type(1/6) - which will yield <type 'int'>.
Because of that when you run your program all your ns will satisfy only the last condition (all will be greater than 0).
This question already has an answer here:
Random walk's weird outcome in python 3?
(1 answer)
Closed 6 years ago.
I am having unexpected outputs with the following code:
import random
N = 30 # number of steps
n = random.random() # generate a random number
x = 0
y = 0
z = 0
count = 0
while count <= N:
if n < 1/3:
x = x + 1 # move east
n = random.random() # generate a new random number
if n >= 1/3 and n < 2/3:
y = y + 1 # move north
n = random.random() # generate a new random number
if n >= 2/3:
z = z + 1 # move up
n = random.random() # generate a new random number
print("(%d,%d,%d)" % (x,y,z))
count = count + 1
When I run the code, the problem is:
Code output displays 31 coordinates, 1 more than the number of steps (N) variable.
Each iteration for 1 step should take only 1 step but it sometimes take multiple steps.
When I tested the code, the problem is ensured. To test the code, I assigned N = 1, and saw the following output:
(-1,0,1) This should be the initial step, but it took multiple steps (both x-1 and z+1), how could this happen?
(-2,0,1) Number of step variable (N) = 1 but this is the second output, why was it displayed?
Thanks for helping
N is 30, so count goes from 0 to 30. Since 30 <= 30 you will run the loop for count=0, 1, ..., 29 AND 30 which is 31 times
When you take a step, you don't ensure that another step is NOT taken. If random happens, you could enter the second or third if after already being in a previous one in the same loop iteration
You are dividing two ints which will only result in another int. So basically your code is do the following:
if n < 0:
x = x + 1 # move east
n = random.random() # generate a new random number
if n >= 0 and n < 1:
y = y + 1 # move north
n = random.random() # generate a new random number
if n >= 1:
z = z + 1 # move up
n = random.random()
fix by changing each if line to include a floating point number
if n < 1.0/3
I'm wondering why this won't work:
n = 10
x = 1
while x < n:
x += 1
n += n * (x - 1)
print n
I've already assigned n = 10, placing it in the while loop should be like placing a 10 there. Running it in Terminal acts like I've placed a raw_input() there.
This works:
n = 10
x = 1
while x < 10:
x += 1
n += n * (x - 1)
print n
It looks the same to me...
Thanks!
In the first example, n, your limit, is increasing (n += n * (x - 1)) and you have an infinite loop. In the second one the limit is constant (10) so it terminates.
Because, in the while loop, for each iteration, you also update n
while x < n:
x += 1
n += n * (x - 1)
So, after the first few iterations, n is no longer 10, and it keeps increasing.
I finally understand it after doing more loops of similar type. The reason is that this creates an infinite loop because in:
n = 10
x = 1
while x < n:
x += 1
n += n * (x - 1)
print n
The n increases along with x:
x += 1
n += n * (x - 1)
So x never catches up with n and causes an infinite loop.
This works because the 10 never increases and x can catch up with it.
n = 10
x = 1
while x < 10:
x += 1
n += n * (x - 1)
print n