Extremely inefficient python code - python

I have made a program to allow users to input the largest possible hypotenuse of a right-angled triangle and my program will list down a list of all possible sides of the triangles. Problem is, the program takes forever to run when I input a value such as 10000. Any suggestions on how to improve the efficiency of the program?
Code:
largest=0
sets=0
hypotenuse=int(input("Please enter the length of the longest side of the triangle"))
for x in range(3,hypotenuse):
for y in range(4, hypotenuse):
for z in range(5,hypotenuse):
if(x<y<z):
if(x**2+y**2==z**2):
commonFactor=False
for w in range(2,x//2):
if (x%w==0 and y%w==0 and z%w==0):
commonFactor=True
break
if not(commonFactor):
print(x,y,z)
if(z>largest):
largest=z
sets+=1
print("Number of sets: %d"%sets)
print("Largest hypotenuse is %d"%largest)
Thanks!

like this?
hypothenuse=10000
thesets=[]
for x in xrange(1, hypothenuse):
a=math.sqrt(hypothenuse**2-x**2)
if(int(a)==a):
thesets.append([x,a])
print "amount of sets: ", len(thesets)
for i in range(len(thesets)):
print thesets[i][0],thesets[i][1], math.sqrt(thesets[i][0]**2+ thesets[i][1]**2)
edit: changed so you can print the sets too, (this method is in O(n), which is the fastest possible method i guess?) note: if you want the amount of sets, each one is given twice, for example: 15*2=9*2+12*2 = 12*2+9**2
Not sure if i understand your code correctly, but if you give in 12, do you than want all possible triangles with hypothenuse smaller than 12? or do you than want to know the possibilities (one as far as i know) to write 12*2=a*2+b**2?
if you want all possibilities, than i will edit the code a little bit
for all possibilities of a*2+b*2 = c**2, where c< hypothenuse (not sure if that is the thing you want):
hypothenuse=15
thesets={}
for x in xrange(1,hypothenuse):
for y in xrange(1,hypothenuse):
a=math.sqrt(x**2+y**2)
if(a<hypothenuse and int(a)==a):
if(x<=y):
thesets[(x,y)]=True
else:
thesets[(y,x)]=True
print len(thesets.keys())
print thesets.keys()
this solves in O(n**2), and your solution does not even work if hypothenuse=15, your solution gives:
(3, 4, 5)
(5, 12, 13)
Number of sets: 2
while correct is:
3
[(5, 12), (3, 4), (6, 8)]
since 5*2+12*2=13*2, 3*2+4*2=5*2, and 6*2+8*2=10**2, while your method does not give this third option?
edit: changed numpy to math, and my method doesnt give multiples either, i just showed why i get 3 instead of 2, (those 3 different ones are different solutions to the problem, hence all 3 are valid, so your solution to the problem is incomplete?)

Here's a quick attempt using pre-calculated squares and cached square-roots. There are probably many mathematical optimisations.
def find_tri(h_max=10):
squares = set()
sq2root = {}
sq_list = []
for i in xrange(1,h_max+1):
sq = i*i
squares.add(sq)
sq2root[sq] = i
sq_list.append(sq)
#
tris = []
for i,v in enumerate(sq_list):
for x in sq_list[i:]:
if x+v in squares:
tris.append((sq2root[v],sq2root[x],sq2root[v+x]))
return tris
Demo:
>>> find_tri(20)
[(3, 4, 5), (5, 12, 13), (6, 8, 10), (8, 15, 17), (9, 12, 15), (12, 16, 20)]

One very easy optimization is to arbitrarily decide x <= y. e.g., if (10,15,x) is not a solution then (15,10,x) will not be a solution either. This also means that if 2x**2 > hypoteneuse**2 then you can terminate the algorithm as there is no solution.

Related

Python - Combination of Numbers Summing to Greater than or Equal to a Value

I was recently posed the following interview question to be answered in Python - given a list of quantity-value pairs, find the optimal combination(s) of sets of values whose sum is as close to, and at least as large as, some provided value.
For example, given: [(1, $5), (3, $10), (2, $15)], and a desired value of $36, the answer would be [(2,$15), (1,$10)] or [(1,$15), (2,$10), (1,$5)]. The reason is that $40 is the least sum greater than or equal to $36 that can be achieved, and these are the two ways to achieve that sum.
I got stumped. Does anyone have a solution?
The numbers are so small you can just brute force it:
In []:
notes = [(1, 5), (3, 10), (2, 15)]
wallet = [n for a, b in notes for n in [b]*a]
combs = {sum(x): x for i in range(1, len(wallet)) for x in it.combinations(wallet, i)}
target = 36
for i in sorted(combs):
if i >= target:
break
i, combs[i]
Out[]:
(40, (5, 10, 10, 15))
You can extend this for all combinations, just replace the combs dictionary comprehension with:
combs = {}
for i in range(1, len(wallet)):
for x in it.combinations(wallet, i):
combs.setdefault(sum(x), set()).add(x)
...
i, combs[i]
Out[]:
(40, {(5, 10, 10, 15), (10, 15, 15)})

Subsetting a list to add up to elements in another list

Does anyone have some thoughts on elegant code and math using python to solve the following problem?
I have two lists of numbers:
A=[83.4,108,-240.2]
B=[10.3,96.7,-5.5,-20.4,30.9,2.1,-6.1,51.5,37.7,-25,-10.7,-250.4,-14.2,56.4,-11.5,163.9,-146.6,-2.6,7.9,-13.2]
I know that the B can be divided into three lists that contain elements of B such that the three lists together contain all of the elements in B but the three lists have no overlapping elements. The sum of these three lists will add up to the three elements in A.
I can do the brute force method, which is to create all possible combinations of the elements of B into three sets, but the number of possibilities blows up very quickly with the number of elements in B. I've also looked at the knapsack problem, but that seems to require only positive values.
This is indeed a variant of the subset sum problem:
In computer science, the subset sum problem is one of the important problems in complexity theory and cryptography. The problem is this: given a set (or multiset) of integers, is there a non-empty subset whose sum is zero? For example, given the set {−7, −3, −2, 5, 8}, the answer is yes because the subset {−3, −2, 5} sums to zero. The problem is NP-complete.
An equivalent problem is this: given a set of integers and an integer s, does any non-empty subset sum to s?
Proof that it's NP-complete:
The easiest way to prove that some new problem is NP-complete is first to prove that it is in NP, and then to reduce some known NP-complete problem to it.
It is in NP because it can be verified in polynomial time: given a potential solution, simply add up the numbers in the subsets and see if they correspond to the numbers in A. And, you can reduce the subset problem to this problem in polynomial time: Given set x and target sum s, let A = [s, sum(x) - s] and B = x.
It being NP-complete, there is no way to solve this quickly in the general case, using Python or otherwise:
Although any given solution to an NP-complete problem can be verified quickly (in polynomial time), there is no known efficient way to locate a solution in the first place; indeed, the most notable characteristic of NP-complete problems is that no fast solution to them is known. That is, the time required to solve the problem using any currently known algorithm increases very quickly as the size of the problem grows. As a consequence, determining whether or not it is possible to solve these problems quickly, called the P versus NP problem, is one of the principal unsolved problems in computer science today.
As #Claudiu explained well such problems are NP complete, and you can not solve them in a efficient or general way, but in this case as a special and not much efficient way you can play itertools module like following :
>>> from itertools import combinations,product,chain
>>> length=len(B)
>>> subset_lenght=[(i,j,k) for i,j,k in combinations(range(1,length),3) if i+j+k==length]
>>> all_combinations={i:combinations(B,i) for i in range(1,length-2)}
>>> for i,j,k in subset_lenght:
... for t,p,m in product(all_combinations[i],all_combinations[j],all_combinations[k]):
... if not set(t)&set(p)&set(m) and map(sum,(t,p,m))==A:
... print chain.fromiterable(t,p,m)
In this approach first of all you need all the possible lengths which those sum is equal to your main list length, for that aim you can use following list comprehension :
>>> [(i,j,k) for i,j,k in combinations(range(1,len(B)),3) if i+j+k==len(B)]
[(1, 2, 17), (1, 3, 16), (1, 4, 15), (1, 5, 14), (1, 6, 13), (1, 7, 12), (1, 8, 11), (1, 9, 10), (2, 3, 15), (2, 4, 14), (2, 5, 13), (2, 6, 12), (2, 7, 11), (2, 8, 10), (3, 4, 13), (3, 5, 12), (3, 6, 11), (3, 7, 10), (3, 8, 9), (4, 5, 11), (4, 6, 10), (4, 7, 9), (5, 6, 9), (5, 7, 8)]
Then you need to get all the combinations of the main list with the length 1 to len(main_list)-3 (17 in this case but since the range doesn't contains the last number we will put a number 1 grater) so, since we need to access this combinations with those length we can use a dict comprehension to create a dictionary with the partition length as key and the combinations as value :
>>> all_combinations={i:combinations(B,i) for i in range(1,length-2)}
And at last you need to get the the combinations based on subset_lenght items and then choose the ones that hasn't any intersection and those sum is equal to those corresponding item in A.

How to name a function that convert a list of integer into a list of pair?

I have a list, each element represents a size of sets.
a = [10, 20, 5] # we have 3 bags of balls, each has 10, 20, 5 balls
I want to write a function to convert it into a list of pairs, each pair is a pair of the lower and upper limits of sets.
b = [(0, 9), (10, 29), (30, 34)] # if we give a serial number to each ball, then #0 ~ #9 ball will be in bag #1, #10 ~ #29 ball will be in bag #2, and so on.
now I've wroted a function called a_to_b:
a_to_b(a) => b
In this case, what should be the appropriate concept to describe a, b and the function a_to_b?
In another word, what should be the appropriate name for a, b and a_to_b? also, what should I call an element in a or b?
Please excuse my poor English, thank you kindly help very much.
Any name which makes some sense will do. In general there's no science to this - just provide appropriately descriptive comments so that others (and you, later) can understand what it does without reading the code.
The only time when there is some science to it is when the function is a well known function. For example, if you have a function which rounds a float down to the nearest lesser value with no fractional part, you should probably call it floor. A function which pairs up items should probably be called zip, because again, that's the usual name.
In [56]: def ranges(L):
answer = []
top = 0
for num in L:
answer.append((top, top+num-1)); top += num
return answer
....:
In [57]: L = [10, 20, 5]
In [58]: ranges(L)
Out[58]: [(0, 9), (10, 29), (30, 34)]
Hope this helps

Return a sequence of a variable length whose summation is equal to a given integer

In the form f(x,y,z) where x is a given integer sum, y is the minimum length of the sequence, and z is the maximum length of the sequence. But for now let's pretend we're dealing with a sequence of a fixed length, because it will take me a long time to write the question otherwise.
So our function is f(x,r) where x is a given integer sum and r is the length of a sequence in the list of possible sequences.
For x = 10, and r = 2, these are the possible combinations:
1 + 9
2 + 8
3 + 7
4 + 6
5 + 5
Let's store that in Python as a list of pairs:
[(1,9), (2,8), (3,7), (4,6), (5,5)]
So usage looks like:
>>> f(10,2)
[(1,9), (2,8), (3,7), (4,6), (5,5)]
Back to the original question, where a sequence is return for each length in the range (y,x). I the form f(x,y,z), defined earlier, and leaving out sequences of length 1 (where y-z == 0), this would look like:
>>> f(10,1,3)
[{1: [(1,9), (2,8), (3,7), (4,6), (5,5)],
2: [(1,1,8), (1,2,7), (1,3,6) ... (2,4,4) ...],
3: [(1,1,1,7) ...]}]
So the output is a list of dictionaries where the value is a list of pairs. Not exactly optimal.
So my questions are:
Is there a library that handles this already?
If not, can someone help me write both of the functions I mentioned? (fixed sequence length first)?
Because of the huge gaps in my knowledge of fairly trivial math, could you ignore my approach to integer storage and use whatever structure the makes the most sense?
Sorry about all of these arithmetic questions today. Thanks!
The itertools module will definately be helpful as we're dealing with premutations - however, this looks suspiciously like a homework task...
Edit: Looks like fun though, so I'll do an attempt.
Edit 2: This what you want?
from itertools import combinations_with_replacement
from pprint import pprint
f = lambda target_sum, length: [sequence for sequence in combinations_with_replacement(range(1, target_sum+1), length) if sum(sequence) == target_sum]
def f2(target_sum, min_length, max_length):
sequences = {}
for length in range(min_length, max_length + 1):
sequence = f(target_sum, length)
if len(sequence):
sequences[length] = sequence
return sequences
if __name__ == "__main__":
print("f(10,2):")
print(f(10,2))
print()
print("f(10,1,3)")
pprint(f2(10,1,3))
Output:
f(10,2):
[(1, 9), (2, 8), (3, 7), (4, 6), (5, 5)]
f(10,1,3)
{1: [(10,)],
2: [(1, 9), (2, 8), (3, 7), (4, 6), (5, 5)],
3: [(1, 1, 8),
(1, 2, 7),
(1, 3, 6),
(1, 4, 5),
(2, 2, 6),
(2, 3, 5),
(2, 4, 4),
(3, 3, 4)]}
The problem is known as Integer Partitions, and has been widely studied.
Here you can find a paper comparing the performance of several algorithms (and proposing a particular one), but there are a lot of references all over the Net.
I just wrote a recursive generator function, you should figure out how to get a list out of it yourself...
def f(x,y):
if y == 1:
yield (x, )
elif y > 1:
for head in range(1, x-y+2):
for tail in f(x-head, y-1):
yield tuple([head] + list(tail))
def f2(x,y,z):
for u in range(y, z+1):
for v in f(x, u):
yield v
EDIT: I just see it is not exactly what you wanted, my version also generates duplicates where only the ordering differs. But you can simply filter them out by ordering all results and check for duplicate tuples.

Summing Consecutive Ranges Pythonically

I have a sumranges() function, which sums all the ranges of consecutive numbers found in a tuple of tuples. To illustrate:
def sumranges(nums):
return sum([sum([1 for j in range(len(nums[i])) if
nums[i][j] == 0 or
nums[i][j - 1] + 1 != nums[i][j]]) for
i in range(len(nums))])
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> print sumranges(nums)
7
As you can see, it returns the number of ranges of consecutive digits within the tuple, that is: len((1, 2, 3, 4), (1), (5, 6), (19, 20), (24), (29), (400)) = 7. The tuples are always ordered.
My problem is that my sumranges() is terrible. I hate looking at it. I'm currently just iterating through the tuple and each subtuple, assigning a 1 if the number is not (1 + previous number), and summing the total. I feel like I am missing a much easier way to accomplish my stated objective. Does anyone know a more pythonic way to do this?
Edit: I have benchmarked all the answers given thus far. Thanks to all of you for your answers.
The benchmarking code is as follows, using a sample size of 100K:
from time import time
from random import randrange
nums = [sorted(list(set(randrange(1, 10) for i in range(10)))) for
j in range(100000)]
for func in sumranges, alex, matt, redglyph, ephemient, ferdinand:
start = time()
result = func(nums)
end = time()
print ', '.join([func.__name__, str(result), str(end - start) + ' s'])
Results are as follows. Actual answer shown to verify that all functions return the correct answer:
sumranges, 250281, 0.54171204567 s
alex, 250281, 0.531121015549 s
matt, 250281, 0.843333005905 s
redglyph, 250281, 0.366822004318 s
ephemient, 250281, 0.805964946747 s
ferdinand, 250281, 0.405596971512 s
RedGlyph does edge out in terms of speed, but the simplest answer is probably Ferdinand's, and probably wins for most pythonic.
My 2 cents:
>>> sum(len(set(x - i for i, x in enumerate(t))) for t in nums)
7
It's basically the same idea as descriped in Alex' post, but using a set instead of itertools.groupby, resulting in a shorter expression. Since sets are implemented in C and len() of a set runs in constant time, this should also be pretty fast.
Consider:
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> flat = [[(x - i) for i, x in enumerate(tu)] for tu in nums]
>>> print flat
[[1, 1, 1, 1], [1, 4, 4], [19, 19, 22, 26, 396]]
>>> import itertools
>>> print sum(1 for tu in flat for _ in itertools.groupby(tu))
7
>>>
we "flatten" the "increasing ramps" of interest by subtracting the index from the value, turning them into consecutive "runs" of identical values; then we identify and could the "runs" with the precious itertools.groupby. This seems to be a pretty elegant (and speedy) solution to your problem.
Just to show something closer to your original code:
def sumranges(nums):
return sum( (1 for i in nums
for j, v in enumerate(i)
if j == 0 or v != i[j-1] + 1) )
The idea here was to:
avoid building intermediate lists but use a generator instead, it will save some resources
avoid using indices when you already have selected a subelement (i and v above).
The remaining sum() is still necessary with my example though.
Here's my attempt:
def ranges(ls):
for l in ls:
consec = False
for (a,b) in zip(l, l[1:]+(None,)):
if b == a+1:
consec = True
if b is not None and b != a+1:
consec = False
if consec:
yield 1
'''
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> print sum(ranges(nums))
7
'''
It looks at the numbers pairwise, checking if they are a consecutive pair (unless it's at the last element of the list). Each time there's a consecutive pair of numbers it yields 1.
This could probably be put together in a more compact form, but I think clarity would suffer:
def pairs(seq):
for i in range(1,len(seq)):
yield (seq[i-1], seq[i])
def isadjacent(pair):
return pair[0]+1 == pair[1]
def sumrange(seq):
return 1 + sum([1 for pair in pairs(seq) if not isadjacent(pair)])
def sumranges(nums):
return sum([sumrange(seq) for seq in nums])
nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
print sumranges(nums) # prints 7
You could probably do this better if you had an IntervalSet class because then you would scan through your ranges to build your IntervalSet, then just use the count of set members.
Some tasks don't always lend themselves to neat code, particularly if you need to write the code for performance.
There is a formula for this, the sum of the first n numbers, 1+ 2+ ... + n = n(n+1) / 2 . Then if you want to have the sum of i-j then it is (j(j+1)/2) - (i(i+1)/2) this I am sure simplifies but you can work that out. It might not be pythonic but it is what I would use.

Categories