Back and forth loop Python - python

I want to create an infinite loop that counts up and down from 0 to 100 to 0 (and so on) and only stops when some convergence criterion inside the loop is met, so basically something like this:
for i in range(0, infinity):
for j in range(0, 100, 1):
print(j) # (in my case 100 lines of code)
for j in range(100, 0, -1):
print(j) # (same 100 lines of code as above)
Is there any way to merge the two for loops over j into one so that I don't have write out the same code inside the loops twice?

Use the chain method of itertools
import itertools
for i in range(0, infinity):
for j in itertools.chain(range(0, 100, 1), range(100, 0, -1)):
print(j) # (in my case 100 lines of code)
As suggested by #Chepner, you can use itertools.cycle() for the infinite loop:
from itertools import cycle, chain
for i in cycle(chain(range(0, 100, 1), range(100, 0, -1))):
....

As well as the other answers you can use a bit of maths:
while(True):
for i in range(200):
if i > 100:
i = 200 - i

Here's yet another possibility:
while notConverged:
for i in xrange(-100, 101):
print 100 - abs(i)

If you've got a repeated set of code, use a function to save space and effort:
def function(x, y, x, num_from_for_loop):
# 100 lines of code
while not condition:
for i in range(1, 101):
if condition:
break
function(x, y, z, i)
for i in range(100, 0, -1):
if condition:
break
function(x, y, z, i)
You could even use a while True

If you're using Python 3.5+, you can using generic unpacking:
for j in (*range(0, 100, 1), *range(100, 0, -1)):
or prior to Python 3.5, you can use itertools.chain:
from itertools import chain
...
for j in chain(range(0, 100, 1), range(100, 0, -1)):

up = True # since we want to go from 0 to 100 first
while True: #for infinite loop
# For up == True we will print 0-->100 (0,100,1)
# For up == False we will print 100-->0 (100,0,-1)
start,stop,step = (0,100,1) if up else (100,0,-1)
for i in range(start,stop,step):
print(i)
up = not up # if we have just printed from 0-->100 (ie up==True), we want to print 100-->0 next so make up False ie up = not up( True)
# up will help toggle, between 0-->100 and 100-->0

def up_down(lowest_value, highest_value):
current = lowest_value
delta = 1
while True: # Begin infinite loop
yield current
current += delta
if current <= lowest_value or current >= highest_value:
delta *= -1 # Turn around when either limit is hit
This defines a generator, which will continue to yield values for as long as you need. For example:
>>> u = up_down(0, 10)
>>> count = 0
>>> for j in u:
print(j) # for demonstration purposes
count += 1 # your other 100 lines of code here
if count >= 25: # your ending condition here
break
0
1
2
3
4
5
6
7
8
9
10
9
8
7
6
5
4
3
2
1
0
1
2
3
4

I became curious if it's possible to implement such kind of triangle oscillator without conditions and enumerations. Well, one option is the following:
def oscillator(magnitude):
i = 0
x = y = -1
double_magnitude = magnitude + magnitude
while True:
yield i
x = (x + 1) * (1 - (x // (double_magnitude - 1))) # instead of (x + 1) % double_magnitude
y = (y + 1) * (1 - (y // (magnitude - 1))) # instead of (y + 1) % magnitude
difference = x - y # difference ∈ {0, magnitude}
derivative = (-1 * (difference > 0) + 1 * (difference == 0))
i += derivative
The idea behind this is to take 2 sawtooth waves with different periods and subtract one from another. The result will be a square wave with values in {0, magnitude}. Then we just substitute {0, magnitude} with {-1, +1} respectively to get derivative values for our target signal.
Let's look at example with magnitude = 5:
o = oscillator(5)
[next(o) for _ in range(21)]
This outputs [0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0].
If abs() is allowed, it can be used for simplicity. For example, the following code gives the same output as above:
[abs(5 - ((x + 5) % 10)) for x in range(21)]

This is more of a partial answer than a direct answer to your question, but you can also use the notion of trigonometric functions and their oscillation to imitate a 'back and forth' loop.
If we have a cos function with an amplitude of 100, shifted left and upwards so that f(x) = 0 and 0 <= f(x) <= 100, we then have the formula f(x) = 50(cos(x-pi)+1) (plot of graph may be found here. The range is what you require, and oscillation occurs so there's no need to negate any values.
>>> from math import cos, pi
>>> f = lambda x: 50*(cos(x-pi)+1)
>>> f(0)
0.0
>>> f(pi/2)
50.0
>>> f(pi)
100.0
>>> f(3*pi/2)
50.0
>>> f(2*pi)
0.0
The issue of course comes in that the function doesn't give integer values so easily, thus it's not that helpful - but this may be useful for future readers where trigonometric functions might be helpful for their case.

I had a similar problem a while ago where I also wanted to create values in the form of an infinite triangle wave, but wanted to step over some values. I ended up using a generator (and the range function as other also have been using):
def tri_wave(min, max, step=1):
while True:
yield from range(min, max, step)
yield from range(max, min, -1 * step)
With carefully selected values on min, max and step (i.e. evenly divisible),
for value in tri_wave(0, 8, 2):
print(value, end=", ")
I get the min and max value only once, which was my goal:
...0, 2, 4, 6, 8, 6, 4, 2, 0, 2, 4, 6, 8, 6, 4...
I was using Python 3.6 at the time.

Here is a simple and straightforward solution that does not require any imports:
index = 0
constant = 100
while True:
print(index)
if index == constant:
step = -1
elif index == 0:
step = 1
index += step

Related

How to rewrite a program to use recursion?

Just started to deal with recursion - I don’t understand everything in it yet. I think that i don't use a basic conditional, but i don't have any idea how to write it. The program itself works and execute everything i need, but there is no recursion.
The idea of the program is that there is a list in which is neede to sum of every x'th number in the list - x here as a step. If x = 0, then the sum is automatically zero. If x is out of range, then the sum is also 0
def sum_elements(nums, x) -> int::
if x not in range(-len(nums), len(nums)) or x == 0:
return 0
if x > 0:
nums = nums[x - 1::x]
return sum(nums)
return sum_elements(nums[::-1], -x)
if __name__ == '__main__':
print(sum_elements([], 0)) # x = 0 -> 0
print(sum_elements([1, 5, 2, 5, 9, 5], 3)) # 2 + 5 = 7
print(sum_elements([5, 6, 10, 20], -2)) # 10 + 5 = 15
print(sum_elements([5, 6, 10, 20], -20)) # x = -20 -> 0
Recursion is when a function calls itself and there a few (non-formal) rules that are always good to keep in the back of your mind when writing these:
1. The base case.
Every recursion function must have a base case that acts as essentially the end of the stack in the recursive call.
2. Every recursive function abides by the non-base(s) and the base case.
In other words, your code must be written in a way that the function either calls itself, or it terminates the recursive call. You can either do this by doing if and else statements, or only writing if statements to catch the base case(s).
3. The input of the function should keep in mind the state of the previous function.
In math, you might remember functions that call themselves (syntax switched for the case of explanation):
f(x)_(n=0) = f(x)_(n=1) + 10
which becomes:
f(x)_(n=1) = ( f(x)_(n=2) + 10 ) + 10
and so on. In essence, you are writing this with code and setting a base case that might say (for the example above, i.e.) "stop when n is 10". If that was the case, you should notice the cascading effect when we are layers deep into that function and when f(x)_(n=10) makes its appearance (and lets says returns 0 + 10) how we would have a final form of f(x)_(n=0) = 0 + 10 + 10 + 10 + ....
So for this function you instead have two inputs, nums and x. These inputs are what we will be modifying as we go down the recursion's stack.
1. Writing our base case.
Writing the base case is typically the easiest part of writing a recursion function. We know, for your problem, the following cases must be caught:
If x is not in the range of the length of nums, then we must return 0.
If len(nums) is 0, then we should return 0.
So lets begin:
def sum_elements(nums, x) -> int:
if len(nums) == 0 or not x in range(-len(nums), len(nums)):
return 0
Notice, however, that range(len([1, 2])) will return range(0, 2) but list(range(0, 2)) will return [0, 1]. Therefore, we must ensure to add a 1 to our len(nums) so that we can truly see if x is within the proper range:
def sum_elements(nums, x) -> int:
if len(nums) == 0 or not x in range(-len(nums), len(nums) + 1):
return 0
Notice that range(-len(nums), len(nums) + 1) for when nums = [1, 2, 3] is equals to range(-3, 4), but list(range(-3, 4)) is equals to [-3, -2, -1, 0, 1, 2, 3]. So therefore, we do not need a -len(nums) + 1 or -len(nums) - 1.
Once we have figured out the base case, we can start working on our actual function. At this point we have done #1 and a portion of #2, but we now must write our non-base(s) case(s).
2. Identifying our other-case(s):
As written in #2, our function input is what is dynamically changing as we go down our function stack. Therefore, we need to think about how we need to modify nums and/or x to fit our purposes. The first thing you should look at, however, is what would happen if we only change one of those variables as we go down the stack.
Keep nums constant, modify x: We know our base case ensures x stays within the constrain of the length of nums in both the positive and negative direction, which is good. However, we must increment x every time the function runs by the original x, or x_0. If we create the function and on every call say x + x, we are not adding the original x to itself, but rather adding the newer x's to itself. This is a problem. Take the following for example:
def sum_elements(nums, x) -> int:
print(nums, x)
# Base case.
if len(nums) == 0 or not x in range(-len(nums), len(nums) + 1):
return 0
# Other case. We must differentiate between positive x, and negative x.
if x > 0:
# Since x is an index that starts at 1, not 0, we must do x-1.
number = nums[x - 1]
else:
# For negative values of x this does not apply. [1, 2][-2] = 1
number = nums[x]
return number + sum_elements(nums, x + x)
Notice how we get:
# [NUMS] x
[1, 2, 3, 4, 5, 6] 2
[1, 2, 3, 4, 5, 6] 4
[1, 2, 3, 4, 5, 6] 8
# OUTPUT
6
and how the x value on the third call is 8. This is no bueno. The more you practice recursion, the more intuitive this concept will become on noticing how changing a certain input might not be the best. You ought to think: "what will this value be when the function continues down the stack?"
Keep x constant, modify nums: If we do this way we should be certain that we will not have issues with the value of x. The issue, then, becomes how we will modify the nums list and use x for our advantage. What we do know, is that x can be technically used as an index, as demonstrated above. So, therefore, what if instead of modifying the index, we modify the list in which that index takes from? Take the following for example:
nums = [1, 2, 3, 4]
x = 2
print(nums) # > [1, 2, 3, 4]
print(nums[x - 1]) # > 2
nums = nums[x:] # > [3, 4]
print(nums[x - 1]) # > 4
So it does seem like we can modify the list and keep a constant x to retrieve the information we want. Awesome! In such case #2 is the way to go.
3. Writing our other-case(s).
So now we will try to now write a function that keeps x constant, but modifies nums. We have a general idea from the code above, and we know from the previous point that we will have to deal with -x and x differently. Therefore, lets write something:
def sum_elements2(nums, x) -> int:
# Base case.
if len(nums) == 0 or not x in range(-len(nums), len(nums) + 1):
return 0
# Other case.
if x >= 0:
number = nums[x - 1]
nums = nums[x:]
else:
number = nums[x]
# Not sure what goes here.
return number + sum_elements(nums, x)
If we test the function above, it seems that it works for any positive x, but won't work for negative values of x. It makes sense, however, that whatever we do to the positive side, we must do the opposite to the negative side. If we try to use nums = nums[:x] we very quickly realize it works. Our final function becomes:
def sum_elements(nums, x) -> int:
# Base case.
if len(nums) == 0 or not x in range(-len(nums), len(nums) + 1):
return 0
# Other case.
if x >= 0:
number = nums[x - 1]
nums = nums[x:]
else:
number = nums[x]
nums = nums[:x]
return number + sum_elements(nums, x)
Running Examples
If we run examples with the above function, we get:
print(sum_elements([1, 2, 3, 4, 5, 6], 2)) # > 2 + 4 + 6 = 12
print(sum_elements([], 0)) # > 0
print(sum_elements([1, 5, 2, 5, 9, 5], 3)) # > 7
print(sum_elements([5, 6, 10, 20], -2)) # > 15
print(sum_elements([5, 6, 10, 20], -20)) # > 0
Maybe this approach can help you understand.
It starts from the first element and sums the rest every x ones.
That is my assumption, as you haven't provided an input and its desired output as an example.
In case you need to start from the xth element the code can be easily modified, I leave it to you to experiment with it.
def sum_elements(nums, x) -> int:
if x>0 and x<=len(nums):
return nums[0] + sum_elements(nums[x:], x)
return 0
lst = [1, 2, 3, 4, 5, 6]
print(sum_elements(lst, 2))
print(sum_elements(lst, 3))
print(sum_elements(lst, 0))
produces
9
5
0
Note: it just demonstrates recursion, but it's not optimal for a number of reasons.
Also it discards negative values of x

Making a list of a geometric progression when the ratio and range are given

Given the positive integer ratio greater than 1, and the non-negative integer n, create a list consisting of the geometric progression of numbers between (and including) 1 and n with a common ratio of ratio. For example, if ratio is 2 and n is 8, the list would be [1, 2, 4, 8].
Associate the list with the variable geom_prog.
I have tried the following code:
r= ratio
geom_prog = []
for i in range(1, n+1):
i *= r
geom_prog.append(i)
For ratio 2 and n = 8:
Expected result: [1, 2, 4, 8]
What I got: [2, 4, 6, 8, 10, 12, 14, 16]
More than anything I'm just wondering what the correct algorithm for getting the correct elements would be. Or if there is a more efficient way to do this problem.
If I understand
r = 2 # set here the factor
geom_prog = []
x = 1 # first element and var to update
n = 8 # last element
for i in range(x, n+1):
geom_prog.append(x)
x *= r
EDIT:
Or more pythonic
[start * ratio**i for i in range(n)]
ref: Python: Generate a geometric progression using list comprehension
r = ratio
geom_prog = []
x = 1
while x <= n:
geom_prog.append(x)
x *= r
The problem is instead of restricting values till 8,
for i in range(1, n+1):
this is telling the program to run the loop for n times.
try this instead:
n = 8
r = 2
geom_prog = []
i = 1 ;
while(i*r <= n):
geom_prog.append(i*r)
i+=1 ;
#print(geom_prog) #returns [2, 4, 6, 8]
Use a simple while loop:
>>> r = 2
>>> n = 8
>>> e = 1
>>> geom_prog = []
>>> while e <= n:
... geom_prog.append(e)
... e *= r
...
>>> geom_prog
[1, 2, 4, 8]
A few good answers have already been posted, but adding this one as well.
you can use the math library to calculate the for loop upper limit as well as each element in the progression without changing your logic too much.
import math
r= 2
geom_prog = []
n = 8
n = int(math.log(n, r))
for i in range(0, n+1):
k = math.pow(r,i)
geom_prog.append(k)
Suppose you want to know how many terms will be involved in advance.
Think of the following question, we want ratio^m <= n where we want to solve for m.
then we have m <= log(n)/log(ratio), since m is an integer, m <= int(log(n)/log(ratio))
import math
n=8
ratio = 2
r= ratio
geom_prog = [1]
for i in range(1, int(math.log(n)/math.log(ratio))+1):
geom_prog.append(geom_prog[-1] * r)
print(geom_prog)
By looping over range(1, n+1), you're making n passes (8 in your case). The termination criteria you're looking for is when the newest element of your set hits (or exceeds) n. Try a while loop:
>>> def geom(ratio, n):
... series = [1]
... while series[-1] < n:
... series.append( series[-1] * ratio )
... return series
...
>>>
>>> geom(2, 8)
[1, 2, 4, 8]
Probably want to add some code to check this will terminate for your parameters (e.g. a ratio of 1 with n > 1 will never terminate), but this should get you started.

Verify whether s list of number is part of the fibonacci series

I have made a function which takes a list as an input and returns a list too.
e.g. input is [4,6,8,10,12]
and output should be [0,0,1,0,0]
because 8 belongs in fibonacci series
my code is
for i in input1:
phi=0.5+0.5*math.sqrt(5.0)
a=phi*i
out =[ i == 0 or abs(round(a) - a) < 1.0 / i];
return out;
This will work:
input1 = [4,6,8,10,12]
out=[]
for i in input1:
phi=0.5+0.5*math.sqrt(5.0)
a=phi*i
out.append(i == 0 or abs(round(a) - a) < 1.0 / i);
To convert bool to int
import numpy
y=numpy.array(out)
new_output = y*1
I would think that the best way is probably to write a function called is_fibonacci, which takes a numerical input and returns True if the input is a fibonacci number, otherwise False. Then you can just do a list comprehension on your initial list input1: return [1 if is_fibonacci(num) else 0 for num in input1]. (Of course, is_fibonacci could automatically return 1 or 0 instead of a Boolean, in which case the list comprehension is even simpler.)
Writing the is_fibonacci function is an interesting exercise that I will leave to you :) (But happy to help if you are struggling with it.)
This should solve I guess
import math
# A function that returns true if x is perfect square
def isPerfectSquare(x):
s = int(math.sqrt(x))
return s * s == x
# Returns true if n is a Fibinacci Number, else false
def isFibonacci(n):
return isPerfectSquare(5 * n * n + 4) or isPerfectSquare(5 * n * n - 4)
i = [4, 6, 8, 10, 12]
print(i)
j = []
# A utility function to test above functions
for item in i:
if (isFibonacci(item) == True):
j.append(1)
else:
j.append(0)
print(j)
Output:
[4, 6, 8, 10, 12]
[0, 0, 1, 0, 0]
This does what you want
def isFibonaccy(inputList):
out = []
for i in inputList:
phi = 0.5 + 0.5 * math.sqrt(5.0)
a = phi * i
out.append(int(i == 0 or abs(round(a) - a) < 1.0 / i))
return out
print(isFibonaccy([4, 6, 8, 10, 12])) # -> [0, 0, 1, 0, 0]

Random sequence and random position? [duplicate]

How can I randomly shuffle a list so that none of the elements remains in its original position?
In other words, given a list A with distinct elements, I'd like to generate a permutation B of it so that
this permutation is random
and for each n, a[n] != b[n]
e.g.
a = [1,2,3,4]
b = [4,1,2,3] # good
b = [4,2,1,3] # good
a = [1,2,3,4]
x = [2,4,3,1] # bad
I don't know the proper term for such a permutation (is it "total"?) thus having a hard time googling. The correct term appears to be "derangement".
After some research I was able to implement the "early refusal" algorithm as described e.g. in this paper [1]. It goes like this:
import random
def random_derangement(n):
while True:
v = [i for i in range(n)]
for j in range(n - 1, -1, -1):
p = random.randint(0, j)
if v[p] == j:
break
else:
v[j], v[p] = v[p], v[j]
else:
if v[0] != 0:
return tuple(v)
The idea is: we keep shuffling the array, once we find that the permutation we're working on is not valid (v[i]==i), we break and start from scratch.
A quick test shows that this algorithm generates all derangements uniformly:
N = 4
# enumerate all derangements for testing
import itertools
counter = {}
for p in itertools.permutations(range(N)):
if all(p[i] != i for i in p):
counter[p] = 0
# make M probes for each derangement
M = 5000
for _ in range(M*len(counter)):
# generate a random derangement
p = random_derangement(N)
# is it really?
assert p in counter
# ok, record it
counter[p] += 1
# the distribution looks uniform
for p, c in sorted(counter.items()):
print p, c
Results:
(1, 0, 3, 2) 4934
(1, 2, 3, 0) 4952
(1, 3, 0, 2) 4980
(2, 0, 3, 1) 5054
(2, 3, 0, 1) 5032
(2, 3, 1, 0) 5053
(3, 0, 1, 2) 4951
(3, 2, 0, 1) 5048
(3, 2, 1, 0) 4996
I choose this algorithm for simplicity, this presentation [2] briefly outlines other ideas.
References:
[1] An analysis of a simple algorithm for random derangements. Merlini, Sprugnoli, Verri. WSPC Proceedings, 2007.
[2] Generating random derangements. Martínez, Panholzer, Prodinger.
Such permutations are called derangements. In practice you can just try random permutations until hitting a derangement, their ratio approaches the inverse of 'e' as 'n' grows.
As a possible starting point, the Fisher-Yates shuffle goes like this.
def swap(xs, a, b):
xs[a], xs[b] = xs[b], xs[a]
def permute(xs):
for a in xrange(len(xs)):
b = random.choice(xrange(a, len(xs)))
swap(xs, a, b)
Perhaps this will do the trick?
def derange(xs):
for a in xrange(len(xs) - 1):
b = random.choice(xrange(a + 1, len(xs) - 1))
swap(xs, a, b)
swap(len(xs) - 1, random.choice(xrange(n - 1))
Here's the version described by Vatine:
def derange(xs):
for a in xrange(1, len(xs)):
b = random.choice(xrange(0, a))
swap(xs, a, b)
return xs
A quick statistical test:
from collections import Counter
def test(n):
derangements = (tuple(derange(range(n))) for _ in xrange(10000))
for k,v in Counter(derangements).iteritems():
print('{} {}').format(k, v)
test(4):
(1, 3, 0, 2) 1665
(2, 0, 3, 1) 1702
(3, 2, 0, 1) 1636
(1, 2, 3, 0) 1632
(3, 0, 1, 2) 1694
(2, 3, 1, 0) 1671
This does appear uniform over its range, and it has the nice property that each element has an equal chance to appear in each allowed slot.
But unfortunately it doesn't include all of the derangements. There are 9 derangements of size 4. (The formula and an example for n=4 are given on the Wikipedia article).
This should work
import random
totalrandom = False
array = [1, 2, 3, 4]
it = 0
while totalrandom == False:
it += 1
shuffledArray = sorted(array, key=lambda k: random.random())
total = 0
for i in array:
if array[i-1] != shuffledArray[i-1]: total += 1
if total == 4:
totalrandom = True
if it > 10*len(array):
print("'Total random' shuffle impossible")
exit()
print(shuffledArray)
Note the variable it which exits the code if too many iterations are called. This accounts for arrays such as [1, 1, 1] or [3]
EDIT
Turns out that if you're using this with large arrays (bigger than 15 or so), it will be CPU intensive. Using a randomly generated 100 element array and upping it to len(array)**3, it takes my Samsung Galaxy S4 a long time to solve.
EDIT 2
After about 1200 seconds (20 minutes), the program ended saying 'Total Random shuffle impossible'. For large arrays, you need a very large number of permutations... Say len(array)**10 or something.
Code:
import random, time
totalrandom = False
array = []
it = 0
for i in range(1, 100):
array.append(random.randint(1, 6))
start = time.time()
while totalrandom == False:
it += 1
shuffledArray = sorted(array, key=lambda k: random.random())
total = 0
for i in array:
if array[i-1] != shuffledArray[i-1]: total += 1
if total == 4:
totalrandom = True
if it > len(array)**3:
end = time.time()
print(end-start)
print("'Total random' shuffle impossible")
exit()
end = time.time()
print(end-start)
print(shuffledArray)
Here is a smaller one, with pythonic syntax -
import random
def derange(s):
d=s[:]
while any([a==b for a,b in zip(d,s)]):random.shuffle(d)
return d
All it does is shuffles the list until there is no element-wise match. Also, be careful that it'll run forever if a list that cannot be deranged is passed.It happens when there are duplicates. To remove duplicates simply call the function like this derange(list(set(my_list_to_be_deranged))).
import random
a=[1,2,3,4]
c=[]
i=0
while i < len(a):
while 1:
k=random.choice(a)
#print k,a[i]
if k==a[i]:
pass
else:
if k not in c:
if i==len(a)-2:
if a[len(a)-1] not in c:
if k==a[len(a)-1]:
c.append(k)
break
else:
c.append(k)
break
else:
c.append(k)
break
i=i+1
print c
A quick way is to try to shuffle your list until you reach that state. You simply try to shuffle your list until you are left with a list that satisfies your condition.
import random
import copy
def is_derangement(l_original, l_proposal):
return all([l_original[i] != item for i, item in enumerate(l_proposal)])
l_original = [1, 2, 3, 4, 5]
l_proposal = copy.copy(l_original)
while not is_derangement(l_original, l_proposal):
random.shuffle(l_proposal)
print(l_proposal)

counting back generations of a number

I am trying to reverse engineer a set of numbers given to me (f,m) I need to go through and find how many generations it takes starting from 1,1 using the algorithm below for each generation:
x = 1
y = 1
new_generation = y+x
x OR y = new_generation
IE, I do not know if X, or Y is changed, the other variable is left the same... A list of possible outputs would look like this for the ending values of 4 and 7:
f = 4
m = 7
[1, 1]
[2, 1, 1, 2]
[3, 1, 2, 3, 3, 2, 1, 3]
[4, 1, 3, 4, 5, 3, 2, 5, 5, 2, 3, 5, 4, 3, 1, 4]
[5, 1, 4, 5, **7, 4**, 3, 7, 7, 5, 2, 7, 7, 2, 5, 7, 7, 3, **4, 7**, 5, 4, 1, 5]
Where every two sets of numbers (2,1) and (1,2) are a possible output. Note the ** denote the answer (in this case the order doesn't matter so long as both m and f have their value in the list).
Clearly there is exponential growth here, so I can't (or it less efficient) to make a list and then find the answer; instead I am using the following code to reverse this process...
def answer(m,f):
#the variables will be sent to me as a string, so here I convert them...
m = (int(m))
f = (int(f))
global counter
#While I have not reduced my given numbers to my starting numbers....
while m != 1 or f != 1:
counter +=1
#If M is greater, I know the last generation added F to M, so remove it
if m > f:
m = m-f
#If F is greater, I know the last generation added M to M, so remove it
elif f > m:
f = f-m
else:
#They can never be the same (one must always be bigger, so if they are the same and NOT 1, it can't be done in any generation)
return "impossible"
return str(counter)
print(answer("23333","30000000000"))
This returns the correct answer (for instance, 4,7 returns "4" which is correct) but it takes to long when I pass larger numbers (I must be able to handle 10^50, insane amount, I know!).
My thought was I should be able to apply some mathematical equation to the number to reduce it and them multiple the generations, but I'm having trouble finding a way to do this that also holds the integrity of the answer (for instance, if I divide the bigger by the smaller, on small numbers (7, 300000) I get a very close (but wrong) answer, however on closer numbers such as (23333, 300000) the answer is no where even close, which makes sense due to the differences in the generation path). Note I have also tried this in a recursive function (to find generations) and using the a non-reversed method (building the list and checking the answer; which was significantly slower for obvious reasons)
Here are some test cases with their answers:
f = "1"
m = "2"
Output: "1"
f = "4"
m = "7"
Output: "4"
f = "4"
m = "2"
Output: "impossible"
Any help is much appreciated! P.S. I am running Python 2.7.6
[EDIT]
The below code is working as desired.
from fractions import gcd
def answer(m,f):
#Convert strings to ints...
m = (int(m))
f = (int(f))
#If they share a common denominator (GCD) return impossible
if gcd(m,f) != 1:
return "impossible"
counter = 0
#While there is still a remainder...
while m != 0 and f != 0:
if m > f:
counter += m // f
#M now equals the remainder.
m %= f
elif f > m:
counter += f // m
f %= m
return str(counter - 1)
This is not a Python question, nor is it really a programming question. This is a problem designed to make you think. As such, if you just get the answer from somebody else, you will gain no knowledge or hindsight from the exercise.
Just add a print(m, f) in your while loop and watch how the numbers evolve for small inputs. For example, try with something like (3, 100): don't you see any way you could speed things up, rather than repeatedly removing 3 from the bigger number?
You are on the right track with the top-down approach you posted. You can speed it up by a huge factor if you use integer division instead of repeated subtraction.
def answer(m, f):
m = int(m)
f = int(f)
counter = 0
while m != 0 and f != 0:
if f > m:
m, f = f, m
print(m, f, counter, sep="\t")
if f != 1 and m % f == 0:
return "impossible"
counter += m // f
m %= f
return str(counter - 1)
Using the above, answer(23333, 30000000000) yields
30000000000 23333 0
23333 15244 1285732
15244 8089 1285733
8089 7155 1285734
7155 934 1285735
934 617 1285742
617 317 1285743
317 300 1285744
300 17 1285745
17 11 1285762
11 6 1285763
6 5 1285764
5 1 1285765
1285769
and answer(4, 7) yields
7 4 0
4 3 1
3 1 2
4
Try a form of recursion:
(Python 2.7.6)
def back():
global f,m,i
if f<m:
s=m//f
i+=s
m-=s*f
elif m<f:
s=f//m
i+=s
f-=s*m
else:
return False
return True
while True:
f=int(raw_input('f = '))
m=int(raw_input('m = '))
i=0
while True:
if f==m==1:
print 'Output:',str(i)
break
else:
if not back():
print 'Output: impossible'
break
print
(Python 3.5.2)
def back():
global f,m,i
if f<m:
s=m//f
i+=s
m-=s*f
elif m<f:
s=f//m
i+=s
f-=s*m
else:
return False
return True
while True:
f=int(input('f = '))
m=int(input('m = '))
i=0
while True:
if f==m==1:
print('Output:',str(i))
break
else:
if not back():
print('Output: impossible')
break
print()
Note: I am a Python 3.5 coder so I have tried to backdate my code, please let me know if there is something wrong with it.
The input format is also different: instead of f = "some_int" it is now f = some_int, and the output is formatted similarly.

Categories