How can I optimize my code to print amicable numbers? - python

I have tried this following code and it takes a lot of time when I set lower = 0 and upper = 10000
def sumPdivisors(n):
'''This function returns the sum of proper divisors of a number'''
lst = []
for i in range(1,n//2+1):
if n%i == 0:
lst.append(i)
return(sum(lst))
lower = int(input("Enter the lower value of range: "))
upper = int(input("Enter the upper value of range: "))
lst = []
for i in range(lower, upper+1):
if i == 0:
continue
else:
for j in range(i, upper):
if i!=j and sumPdivisors(i) == j and sumPdivisors(j) == i:
lst.append((i,j))
break
print(lst)

There are two things that you could do here.
Memoization
There's already a great explanation of what memoization is elsewhere on this site [link], but here's how it's relevant to your problem:
sumPdivisors is called very frequently in the for-loop at the bottom of your code snippet. For really large inputs n, it will take a long time to run.
sumPdivisors is called with the same input n multiple times.
You can speed things up by saving the result of calling sumPdivisors on different inputs somehow, like in a dictionary that maps integers to the resulting output when you call sumPdivisors with that corresponding integer. This is kind of what memoization is. You're precomputing the possible outputs of sumPdivisors and storing them for later. Read the link for a more in-depth explanation.
Don't add the numbers in sumPdivisors to a list
You can just add these numbers as you iterate instead of appending them to a list, then summing them. This change won't have as great of an impact as adding memoization to your code.

Related

What’s wrong with my recursion for finding primes? (Python)

I’m trying to create a program that lists all the primes below an inputted number, and I came up with the code:
def primes():
num = 20
numlist = list(range(1,num+1))
i = len(numlist)
for j in numlist[2:]:
ans = divisible(j,i)
if ans:
numlist.remove(j)
print(numlist)
def divisible(m,n):
if m!=n and m%n==0:
return True
elif n == 1:
return False
else:
divisible(m, n-1)
primes()
(I used an in-browser IDE so the num part was a proxy for the input.)
My idea was to create a separate function divisible() that when inputted two ints, m and n, would check if n divides m. I'm not sure if I was right in my recursion, but I wrote divisible(m,n-1) the idea was that it would iterate through all the integers from n downward and it would return True if any n divided m, or False if it reached 1.
In the main code, m iterated through all the numbers in a list, and n is the total number of elements in the same list. I put the print(numlist) inside the if statement as an error check. The problem I’m having is nothing is printing. The code returned literally nothing. Is there something I’ve missed in how recursion works here?
There's a lot wrong here:
You've made a common beginner's recursion error in that you have a recursive function that returns a value, but when you call it recursively, you ignore the returned value. You need to deal with it or pass it along.
It seems like this modulus is backward:
if ... and m%n==0:
Maybe it should be:
if ... and n % m == 0:
You're code doesn't appear to be calculating primes. It's looks like it's calculating relative primes to n.
You start your list of numbers at 1:
numlist = list(range(1,num+1))
But you start testing at index 2:
for j in numlist[2:]:
Which is the number 3 and you never check the divisibility of the number 2.
Even with all these fixes, I don't feel your algorithm will work.
Your divisible function doesn't return anything if it falls into else part. change it as
def divisible(m, n):
if m!=m and m%n==0:
return True
elif n==1 :
return False
else:
return divisible(m,n-1)
This should work

How can I improve my code to avoid memory based error?

I wrote a python function largestProduct(n) which returns the largest number made from product of two n-digit numbers. The code runs fine for n untill 3, but shows memory error for n>3. Is there a way I can improve my code to avoid this error?
def largestProduct(n):
number_lst = []
prod_lst = []
count = 0
for i in range(10**(n-1),(10**n)):
number_lst.append(i)
while count!= len(number_lst):
for i in range(len(number_lst)):
prod_lst.append(number_lst[count]*number_lst[i])
count +=1
prod_set = list(set(prod_lst))
return max(prod_lst)
Well, you don't need any storage to loop through what you need:
def largestProduct(n):
range_low = 10**(n-1)
range_high = 10**n
largest = 0
# replace xrange with range for Python 3.x
for i in xrange(range_low, range_high):
for j in xrange(range_low, range_high):
largest = max(largest, i*j)
return largest
But why would you want to do this? The largest product of two n-long numbers is always the largest number you can write with n digits squared, i.e.:
def largestProduct(n):
return (10**n-1)**2
You should consider creating a generator function. In the end you can iterate over the output of your function which only processes each element one by one instead of holding the whole list in memory.
see https://wiki.python.org/moin/Generators

Lost: Defining a function which takes a tuple of numbers and returns the sum of only the even numbers in the tuple

Title pretty much says it all. This is the code I wrote that I've been tinkering around with.
def sum_evens(tup):
for num in tup:
if num % 2 ==0:
total = num+num
print(total)
I'm pretty lost here, any ideas on what I can do?
Thank you in advance!
you need to start total at 0 and add to it when you find matching numbers
def sum_evens(tup):
total = 0
for num in tup:
if num % 2 ==0:
total = total+num
return total
finally you need to return the total to whatever called it so it can be used
there are lots of better ways to do this ... but I just edited your function to work
print sum_evens([1,2,3,4,5,6,7]) # 2+4+6 = 12
You need to learn about list comprehensions. Basically, they are backwards for loops such that you can define a new list by iterating over an old list. You can also add a conditional clause to pick and choose.
In this scenario, we loop through an existing tuple and look for the individual members with a remainder of zero when divided by two. We then construct a list with these members and find a sum.
print sum([x for x in my_tuple if x % 2 == 0])

find triangular numbers between any given two numbers

My code is showing bellow
import math,sys
#create a list with numbers
def create_list():
num_list=[]
for num in range(int(input("insert start point: ")),int(input("Insert end point: "))):
num_list.append(num)
return num_list
#function to find triangular numbers
def get_triangles(numlist):
triangles = []
for i in numlist:
if (check_triangle(i)):
triangles.append(i)
return triangles
#function to check number is triangular or not
def check_triangle(n):
return math.sqrt((8*n)+1).is_integer()
#function main to run the process
def main():
numlist = create_list()
print(get_triangles(numlist))
Even though it seems like the task is completed it was not. I tried it with the range of 0 - 100000000(1*10^8) numbers . it is cause to stuck my laptop any method that can complete this task ?
DO NOT PRINT A LIST THAT LARGE. Instead write it to a file, that way you can open the file afterward. The program can't efficiently write that much information into the console. I find that printing stuff to the console makes a programs a ton less efficient.
Additionally, I read some of the comments on your code and they state it isn't efficient and I would have to concur.
Here is piece of code I wrote up. It takes a bit of interpretation, but I was in a hurry. Just reply if you need help understanding it.
def getTriangles(input1,input2): #input1 is the min value and input2 is the max value
li = [] #the list where all of the numbers will go
i = 0 #an integer that acts how much another layer of the triangle would have
j = 0 #the most current number that it is on
while True: #I whipped up this algorithm in a couple minutes, so there is probably a more efficient way than just looping through all of them, but it is faster than the current one being used
i += 1 #i has to increment to act as the increase of a side
if j > input2: #if the value that could be added is greater than the max number, than just end the function and return the list
return li
if j >= input1: #if the number qualifies the minimum number requirements, then the program will add it to the list, otherwise it will ignore it and continue on with the function
li.append(j)
j += i #this simulates adding in another layer of the triangle to the bottom
This would be a way to use it:
print(getTriangles(1,45))
I trust you can look up how to write content to a file.
It appears that you are just trying to generate all triangle numbers within a range. If this is so, computing them directly is substantially quicker than checking via square root.
Note that you can generate triangular numbers by simply adding consecutive numbers.
T_0 = 0
T_1 = T_0 + 1 = 1
T_2 = T_1 + 2 = 3
T_3 = T_2 + 3 = 6
...
If you want to keep it simple, you can create a function to keep generating these numbers from n = 0, keeping them when they enter the desired range, and continue until it exceeds the upper bound.
def generate_triangular_numbers(a, b):
"""Generates the triangular numbers in [a, b] (inclusive)"""
n, t = 0, 0
triangles = []
while t < a:
n += 1
t += n
while t <= b:
triangles.append(t)
n += 1
t += n
return triangles

Create a long list of random values, no duplicates

I want to create a list given two inputs, and under the condition that there cannot be any duplicates. The list should contain a random sequence of numbers. Then numbers in the list are positive integers.
Input 1: the length of the list (var samples)
Input 2: the highest number of the list (var end)
I know how to do this, but I want the list to contain a vast number of numbers, 1 million numbers, or more.
I have created 2 methods to solve this problem myself, both have their issues, on of them is slow the other produces a MemoryError.
Method 1, MemoryError:
import random
def create_lst_rand_int(end, samples):
if samples > end:
print('You cannot create this list')
else:
lst = []
lst_possible_values = range(0, end)
for item in range(0, samples):
random_choice = random.choice(lst_possible_values)
lst_possible_values.remove(random_choice)
lst.append(random_choice)
return lst
print create_lst_rand_int(1000000000000, 100000000001)
Method 2, slow:
import random
def lst_rand_int(end, samples):
lst = []
# lst cannot exist under these conditions
if samples > end:
print('List must be longer or equal to the highest value')
else:
while len(lst) < samples:
random_int = random.randint(0, end)
if not random_int in lst:
lst.append(random_int)
return lst
print lst_rand_int(1000000000000, 100000000001)
Since neither of my methods work well (method 1 does work better than method 2) I would like to know how I can create a list that meets my requirements better.
Try the solution given in the docs:
http://docs.python.org/2/library/random.html#random.sample
To choose a sample from a range of integers, use an xrange() object as an argument. This is especially fast and space efficient for sampling from a large population: sample(xrange(10000000), 60).
Or, in your case, random.sample(xrange(0,1000000000000), 100000000001)
This is still a giant data structure that may or may not fit in your memory. On my system:
>>> sys.getsizeof(1)
24
So 100000000001 samples will require 2400000000024 bytes, or roughly two terabytes. I suggest you find a way to work with smaller numbers of samples.
Try:
temp = xrange(end+1)
random.sample(temp, samples)
random.sample() does not pick any duplicates.
Since sample always returns a list, you're out of luck with such a large size. Try using a generator instead:
def rrange(min, max):
seen = set()
while len(seen) <= max - min:
n = random.randint(min, max)
if n not in seen:
seen.add(n)
yield n
This still requires memory to store seen elements, but at least not everything at once.
You could use a set instead of a list, and avoid checking for duplicates.
def lr2(end, samples):
lst = set()
# lst cannot exist under these conditions
if samples > end:
print('List must be longer or equal to the highest value')
else:
for _ in range(samples):
random_int = random.randint(0, end)
lst.add(random_int)
return lst
Since your sample size is such a large percentage of the items being sampled, a much faster approach is to shuffle the list of items and then just remove the first or last n items.
import random
def lst_rand_int(end, samples):
lst = range(0, end)
random.shuffle(lst)
return lst[0:samples]
If samples > end it will just return the whole list
If the list is too large for memory, you can break it into parts and store the parts on disc. In that case a random choice should be made to choose a section, then an item in the section and remove it for each sample required.

Categories