Python MemoryError occurring with large loop - python

I'm trying to create a script to solve a question for me on Project Euler, but it keeps returning a MemoryError. I have absolutely no idea why.
divisible = True
possible = dict()
for i in xrange(1, 100000000):
for n in xrange(1, 21):
if i%n != 0:
divisible = False
else:
if i in possible:
possible[i].append(n)
else:
possible[i] = [n]
if len(possible[i]) == 20:
print i
break
Python seems to think it occurs on this line possible[i] = [n]

The problem is in your line
if len(possible[i]) == 20:
You mean to say
if len(possible) == 20:
As it is, your code will keep on running - and presumably, since the the loop count is so large, some stack fills up...
Also - although I don't know for sure what you are trying to achieve, your break command is in the innermost loop - so you break out of it, then go around again... and since the length will only be exactly 20 once, you are still stuck. Check your logic.
for example, the following small change to your code produces useful output (although I don't know if it's useful for you... but it might give you some ideas):
divisible = True
possible = dict()
for i in xrange(1, 100000000):
for n in xrange(1, 21):
if i%n != 0:
divisible = False
else:
if i in possible:
possible[i].append(n)
else:
possible[i] = [n]
if len(possible) == 20:
print i
break
else:
print i, possible[i]
Output:
1 [1]
2 [1, 2]
3 [1, 3]
4 [1, 2, 4]
5 [1, 5]
6 [1, 2, 3, 6]
7 [1, 7]
8 [1, 2, 4, 8]
9 [1, 3, 9]
10 [1, 2, 5, 10]
11 [1, 11]
12 [1, 2, 3, 4, 6, 12]
13 [1, 13]
14 [1, 2, 7, 14]
15 [1, 3, 5, 15]
16 [1, 2, 4, 8, 16]
17 [1, 17]
18 [1, 2, 3, 6, 9, 18]
19 [1, 19]
20
EDIT reading through the code more carefully, I think what you are trying to do is find the number that has exactly 20 factors; thus your condition was correct. The problem is that you are storing all the other terms as well - and that is a very very large number of lists. If you are only after this last number (after all the only output is i just before the break), then you really don't need to keep all the other terms. The following code does just that - it's been running merrily on my computer, taking about 20 MB of memory for the longest time now (but no answer yet...)
divisible = True
possible = [];
biggest = 0;
bigN = 100000000;
for i in xrange(1, bigN):
for n in xrange(1, 21):
if i%n != 0:
divisible = False
else:
if len(possible) > 0:
possible.append(n)
else:
possible = [n]
if len(possible) >= 20:
print i
print possible
break
else:
if bigN < 1000:
print i, possible; # handy for debugging
if biggest < len(possible):
biggest = len(possible);
possible = []
The "manual" way to calculate what you are doing is finding the prime factors for all numbers from 1 to 20; counting the largest number of times a prime occurs in each; and taking their product:
2 = 2
3 = 3
4 = 22
5 = 5
6 = 2 3
7 = 7
8 = 222
9 = 33
10 = 2 5
11 = 11
12 = 22 3
13 = 13
14 = 2 7
15 = 3 5
16 = 2222
17 = 17
18 = 2 33
19 = 19
20 = 22 5
Answer: (2*2*2*2)*(3*3)*5*7*11*13*17*19 = 232792560

The memory error is occuring due to the size of :
possible = dict()
One you keep pushing integers into it, its size keeps on growing, and you get memory error.
Carefully see if that can be avoided in the solution.eg. If the answer requires only to tell the count of factors, and not all factors, then do not store all values in a list and calculate its length.
Instead increment counters for each number.
I am not sure what the question is, but this can be replaced by this :
if len(possible[i]) == 20:
print i
break
can be :
if i in possible:
possible[i] += 1
else:
possible[i] = 0
if possible[i] == 20:
print i
break

A quick back of the envelope calculation. You have something like 100000000 integers which if you stored them all would be something on the order of 0.4 Gb (in C). Of course, these are python integers, so each one is more than 4 bytes ... On my system, each is 24(!) bytes which takes your 0.4 Gb up to 2.34 Gb. Now you have each one stored in up to 21 lists ... So that's (up to) an additional 21 pointers to each. Assuming a 4byte pointer to an int, you can see that we're already starting to consume HUGE amounts of memory.
Also note that for performance reasons, lists are over-allocated. It's likely that you're using way more memory than you need because your lists aren't full.
Of course, you're not actually storing them all, and you have an early break condition which (apparently) isn't getting hit. It's likely that you have a logic error in there somewhere.

The check should be outside the innerloop for it to terminate properly. Otherwise, the program would never stop, at intended exit.
divisible = True
possible = dict()
for i in xrange(1, 100000000):
for n in xrange(1, 21):
if i%n != 0:
divisible = False
else:
if i in possible:
possible[i].append(n)
else:
possible[i] = [n]
if len(possible[i]) == 20:
print i
break
BTW, a faster method would be to find LCM than going for a bruteforce method like this.
edit:
One variant which uses no memory.
divisible = True
possible = []
for i in xrange(0, 1000000000):
count = 0
for n in xrange(1, 21):
if i%n != 0:
divisible = False
else:
count += 1
if count == 20:
possible.append(i)
print i
else:
print "\r", "%09d %d %d" % (i, 232792560, count),
print possible

I would say you need to change the approach here. You need solutions which fit under the 1 minute rule. And discussing the solution here defeats the very purpose of Project Euler. So I would suggest that you think of a different approach to solve the problem. An approach which might solve the problem in less than a second.
As for the memory issue, with the current approach, it is almost impossible to get rid of it. So changing the approach will solve this issue too. Though this post does not answer your question, it is in line with Project Euler's principles!

Related

How could I call hundreds in python?

When I split certain numbers into "ones" and "tens" in python, like that (98) we code >>>
num = 98
ones = num % 10
tens = num // 10
print(ones) = 8
print(tens) = 9
So, if I have 3 digits number like 321 (based on the code have shown previously) the ones will be 1, tens = 32 . and I need python to execute 3 and 2 separately !!
First, you can use divmod:
tens, ones = divmod(98, 10)
tens
# 9
ones
# 8
Second, you could write a generic function to get all digits regardless of magnitude:
def digs(n):
while n:
n, dig = divmod(n, 10)
yield dig
ones, tens, hundreds, *_ = digs(321)
It will always produce all digits starting with ones:
[*digs(12345)]
# [5, 4, 3, 2, 1]
Of course, the simplest is just string conversion:
[*map(int, str(12345))]
# [1, 2, 3, 4, 5]
This is a generic Python code to split the digits (ones, tens, hundreds, and so on)
num = int(input())
count = 0
while (num != 0):
mod = num % 10
div = num // 10
count = count + 1
print ("The number in position", count, " is ", mod)
num = div
This code works by repeated division.
You can write a function doing the modulo operations, put those in a list, make the division by then and repeat until the end, reverse the list:
def split_number(number):
result = []
while number != 0:
result.append(number % 10)
number = number // 10
result.reverse()
return result
split_number(12345)
[1, 2, 3, 4, 5]
like this :
321 / 100
3.21
>>> int(3.21)
3
But you have already made a better thing for 10 why you don't make it for 100?

How to check index values in a loop without incrementing the index? [duplicate]

This question already has answers here:
CodingBat Python - List 2 sum(67)
(10 answers)
Closed 1 year ago.
The challenge is from codingbat - Sum the elements of the array except for all values between 6 and 7, not counting the 6 or the 7 either.
My strategy was create a new array "valid" and append valid elements of the array nums to the "valid" array, and then sum the valid array.
Because I need the index to check for a 7 once a 6 is detected, my plan was to enumerate the array, and when the value 6 is found, to loop through the remainder of the array until a 7 is found, not appending the values 6-7 to the array "valid" in the process.
I ran my code through the MIT Python tutor to see where it was going wrong, and when I increment the index after i+1 != 7 (with the intention of checking to see if the next value in the array is a 7) it increments the index (it finishes working with that index of the array and moves on to the next one, rather than sticking in the code for the 6 and continuing to loop through and check for a 7).
Appreciate any help with this, especially a solution that sticks to the original strategy but implements it correctly in the code.
def sum67(nums):
valid = []
for i,c in enumerate(nums):
if c == 6:
#when a 6 comes up
if nums[i+1] == 7:
#if the next number is a 7
i+=1
break
#increment and break
else:
i+=1
#increment
else:
valid.append(c)
return sum(valid)
sum67([1, 2, 2, 6, 99, 99, 7])
Edit 1:
The desired output for
sum67([1, 2, 2, 6, 99, 99, 7])
is 5. (Because 1 + 2 + 2 = 5, and the 6, 99, 99, and 7 are excluded because they are or are between a 6 and a 7)
Using an iterator, and a membership check to search for and skip to after the 7:
def sum67(nums):
it = iter(nums)
return sum(x for x in it if x != 6 or 7 not in it)
This goes over each number, and usually (if it's not a 6) includes it in the sum. If it is a 6, then search for the next 7 and don't include any of that in the sum (and then the next iteration continues after the 7).
Edit: Ermahgerd... CodingBat ridiculously somehow deletes iter (says 'iter' is not defined) even though it's one of the most fundamental builtin functions in Python. Oh well, we can use a genexp instead, that gets accepted there:
def sum67(nums):
it = (x for x in nums)
return sum(x for x in it if x != 6 or 7 not in it)
You could do this to avoid creating an extra array:
def sum67(nums):
isWithin67 = False
totalSum = 0
for n in nums:
if n == 6:
isWithin67 = True
continue
if n == 7:
isWithin67 = False
continue
if isWithin67:
continue
totalSum += n
return totalSum
You can achieve this simply by flagging whether the list is currently between 6 and 7 or not:
def sum67(nums):
valid = []
include = True
for c in nums:
if c == 6:
include = False
if include:
valid.append(c)
if c == 7:
include = True
return sum(valid)
print(sum67([1, 2, 2, 6, 99, 99, 7]))
Output as requested

Summing the numbers in a list except for the first even number in Python?

i am tryin to write a function that sums all of the numbers except for the first even number on the list. a break function came into my mind for exiting the loop when encountered with the first even number but i couldn't managed to build it. i need help. thanks.
for example:
numbers = [4, 6, 7, 8, 9, 10]
totsum must be 6 + 7 + 8 + 9 + 10 = 40 (skipped the first even number '4')
here is my incomplete code:
nmbrs = [4, 6, 7, 8, 9, 10]
def totsum_effen(n):
#sum the total numbers except for the first even number
while True:
sum = 0
for i in nmbrs:
if i % 2 == 0:
break
sum = sum + i
return (sum)
I'd just sum it all and subtract the first even number (if it exists).
>>> numbers = [4, 6, 7, 8, 9, 10]
>>> sum(numbers) - next((x for x in numbers if x % 2 == 0), 0)
40
What you can do is just sum the array and subtract the first even number
nmbrs = [4, 6, 7, 8, 9, 10]
def totsum_effen(nmbrs):
#sum the total numbers except for the first even number
even_nmbrs = [num for num in nmbrs if num % 2 == 0]
temp_sum = sum(nmbrs)
if len(even_nmbrs) > 0:
temp_sum -= even_nmbrs[0]
return temp_sum
Try this;
nmbrs = [4, 6, 7, 8, 9, 10]
def totsum_effem():
nmbrs_copy = nmbrs.copy()
for index, num in enumerate(nmbrs):
if num % 2 == 0:
del nmbrs_copy[index]
break
return sum(nmbrs_copy)
>>> print(totsum_effem())
40
Here's another pythonic one liner (python3.x only):
In [36]: sum(num) - next(filter(lambda x: not x % 2, num + [0]))
Out[36]: 40
Your code will fail to add the odd numbers before the first even number.
This way you can keep a check and as soon as the first even number occurs skip that and continue with the loop.
def totsum_effen(nmbrs):
flag = 0
sum = 0
for i in nmbrs:
if flag == 0 and i % 2 == 0:
flag = 1
else:
sum += i
return sum
Firstly, instead of using break, you can try using continue. It simply skips the rest of the code in the loop for the current iteration and jumps to the next iteration.
Secondly, use a count variable to check for the first even number in the list.
Please check the following code. It's just an implementation of the things mentioned above.
nmbrs = [4, 6, 7, 8, 9, 10]
def totsum_effen(nmbrs) :
sum = 0
count = 0
for i in nmbrs :
if i%2==0 and count==0 :
count = count + 1
continue
sum = sum + i
return sum
print('Printing the sum of all the numbers in the list except the 1st even number....')
print(totsum_effen(nmbrs))
What you want to achieve can be done more efficiently with more complex code but that requires a deep understanding of Python. I re-write your code in beginner easy to understand Python with explanations for a few mistakes in your code and few things to pay attention to. Let me know if you can understand it.
#sum the total numbers except for the first even number (1)
def totsum_effen(n): #(2)
sum = 0 #(3)
ignore_first_even = True #(4)
for i in n:
if(i % 2 == 0 and ignore_first_even): #(5)
ignore_first_even = False
continue
sum = sum + i
return (sum)
print(totsum_effen(nmbrs))
you should place description of your function the line before the function definition block
In your code you the parameter n was never used. pass in parameter(s) you want to use in the function and refer to those parameters.
Usually, you want to declare your variable use for cumulation or totaling oustide of for loop or while loop. so it does not get reset every loop.
use a flat ignore_first_even to ignore the first even number by using ignore_first_even in an if statment in 4
if the number is even and the ignore_first_even is true then we set the ignore_first_even to false. Notice that ignore_first_even can never be set to true again this way we only ignore the first even number. Then we continue to the next iteration without adding this number to our sum
def totsum_effen(n):
#sum the total numbers except for the first even number
sum = 0
flag = False
for val in nmbrs:
if not flag:
if val % 2 == 0:
flag = True
else:
sum += val
else:
sum = sum + val
return (sum)
nmbrs = [4, 6, 7, 8, 9, 10]

if a number is divisible by all the entries of a list then

This came up while attempting Problem 5 of Project Euler, I'm sorry if this is vague or obvious I am new to programming.
Suppose I have a list of integers
v = range(1,n) = [1, ..., n]
What I want to do is this:
if m is divisible by all the entries of v then I want to set
m/v[i] for i starting at 2 and iterating up
then I want to keep repeating this process until I eventually get something which is not divisible by all the entries of v.
Here is a specific example:
let v=[1,2,3,4] and m = 24
m is divisible by 1, 2, 3, and 4, so we divide m by 2 giving us
m=12 which is divisible by 1, 2, 3, and 4 , so we divide by 3
giving us m=4 which is not divisible by 1, 2, 3, and 4. So we stop here.
Is there a way to do this in python using a combination of loops?
I think this code will solve your problem:
i=1
while(True):
w=[x for x in v if (m%x)==0]
if(w==v):
m/=v[i]
i+=1
continue
elif(m!=v):
break
Try this out of size, have a feeling this is what you were asking for:
v = [1,2,3,4]
m = 24
cont = True
c = 1
d = m
while cont:
d = d/c
for i in v:
if d % i != 0:
cont = False
result = d
break
c+=1
print (d)
Got an output of 4.
I think this piece of code should do what you're asking for:
v = [1,2,3,4]
m = 24
index = 1
done = False
while not done:
if all([m % x == 0 for x in v]):
m = m // v[index]
if index + 1 == len(v):
print('Exhausted v')
done = True
else:
index += 1
else:
done = True
print('Not all elements in v evenly divide m')
That said, this is not the best way to go about solving Project Euler problem 5. A more straightforward and faster approach would be:
solved = False
num = 2520
while not solved:
num += 2520
if all([num % x == 0 for x in [11, 13, 14, 16, 17, 18, 19, 20]]):
solved = True
print(num)
In this approach, we known that the answer will be a multiple of 2520, so we increment the value we're checking by that amount. We also know that the only values that need to be checked are in [11, 13, 14, 16, 17, 18, 19, 20], because the number in the range [1,20] that aren't in that list are factors of at least one of the numbers in the list.

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