Permutations based on sum without limiting number of items permuted - python

import itertools as itt
layer_thickness=[1,2,3,4,5]
permu= itt.permutations(layer_thickness,5)
permu_list=list(permu)
for i in permu_list:
if sum(i)==15:
print(i)
Here, I want permutations of the elements in the layer_thickness and those sum of the permutations should be to 5. But the number of elements in prmutation is not constrained by any limit unless it gives the desired sum.
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 , 2 2 2 2 2 2 2 1, etc are should be an element also.
what modifications should I do to achieve that?

You cant create all permutation as list for any total - it will simply hug too much memory:
Assuming [1,2,3,4,5] as numbers, 1 is your lowest element.
Assuming you want to reach a total = 15 - your "biggest" solution is (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1).
To create all possible permutation of 5 numbers over 15 places you need to create 15**5 list entries: 15**5 = 2.562.890.625 possible number permutations
Storing each combination as 1 byte this would need 2.56 Terabyte of ram. Doubt you got that much.
The best you can do is to generate only numbers that work and quit out as soon as you reached your total. To do that for a fixed number set you can start with this question: Finding all possible combinations of numbers to reach a given sum
Using that and provide a "maximized" list of your numbers that can be added up to achieve your goal might lead to something:
def subset_sum(numbers, target, partial=[], partial_sum=0):
if partial_sum == target:
yield partial
if partial_sum >= target:
return
for i, n in enumerate(numbers):
remaining = numbers[i + 1:]
yield from subset_sum(remaining, target, partial + [n], partial_sum + n)
Credit: https://stackoverflow.com/a/4633515/7505395
def calc_nums_for_permutate(nums, total):
"""Creates a list of numbers from num - each single number is added as
many times as itself fits into total"""
return [n for n in nums for _ in range(total//n)]
if __name__ == "__main__":
nums = [1, 2, 3, 4, 5]
total = 15
print( *subset_sum( calc_nums_for_permutate(nums, total), total))
This will not work for all and any inputs - good luck with your runtime, this will still work reasonably well for a total = 10 - for a total = 15 it will take more time then I needed to format/copy paste and formulate this answer here.

Related

How do i optimize this code to run for larger values? [duplicate]

This question already has answers here:
Elegant Python code for Integer Partitioning [closed]
(11 answers)
Closed 1 year ago.
I'm writing a python function that takes an integer value between 3 and 200 as input, calculates the number of sums using unique nonzero numbers that will equal the number and prints the output.
For example; with 3 as input 1 will be printed because only 1 + 2 will give 3, with 6 as input 3 will be printed because 1+2+3, 1+5 and 2+4 equal 6.
My code works well only for numbers less than 30 after which it starts getting slow. How do I optimize my code to run efficiently for all input between 3 and 200.
from itertools import combinations
def solution(n):
count = 0
max_terms = 0
num = 0
for i in range(1,n):
if num + i <= n:
max_terms += 1
num = num + i
for terms in range(2,max_terms + 1):
for sample in list(combinations(list(range(1,n)),terms)):
if sum(sample) == n:
count += 1
print(count)
Generating all combinations is indeed not very efficient as most will not add up to n.
Instead, you could use a recursive function, which can be called after taking away one partition (i.e. one term of the sum), and will solve the problem for the remaining amount, given an extra indication that future partitions should be greater than the one just taken.
To further improve the efficiency, you can use memoization (dynamic programming) to avoid solving the same sub problem multiple times.
Here is the code for that:
def solution(n, least=1, memo={}):
if n < least:
return 0
key = (n, least)
if key in memo: # Use memoization
return memo[key]
# Counting the case where n is not partitioned
# (But do not count it when it is the original number itself)
count = int(least > 1)
# Counting the cases where n is partitioned
for i in range(least, (n + 1) // 2):
count += solution(n - i, i + 1)
memo[key] = count
return count
Tested the code with these arguments. The comments list the sums that are counted:
print(solution(1)) # none
print(solution(2)) # none
print(solution(3)) # 1+2
print(solution(4)) # 1+3
print(solution(5)) # 1+4, 2+3
print(solution(6)) # 1+2+3, 1+5, 2+4
print(solution(7)) # 1+2+4, 1+6, 2+5, 3+4
print(solution(8)) # 1+2+5, 1+3+4, 1+7, 2+6, 3+5
print(solution(9)) # 1+2+6, 1+3+5, 2+3+4, 1+8, 2+7, 3+6, 4+5
print(solution(10)) # 1+2+3+4, 1+2+7, 1+3+6, 1+4+5, 2+3+5, 1+9, 2+8, 3+7, 4+6
your question isn't clear enough. So, I'm making some assumptionns...
So, what you want is to enter a number. say 4 and then, figure out the total combinations where two different digits add up to that number. If that is what you want, then the answer is quite simple.
for 4, lets take that as 'n'. 'n' has the combinations 1+3,2+2.
for n as 6, the combos are - 1+5,2+4,3+3.
You might have caught a pattern. (4 and 6 have half their combinations) also, for odd numbers, they have combinations that are half their previous even number. i.e. - 5 has (4/2)=2 combos. i.e. 1+4,2+3 so...
the formula to get the number for comnbinations are -
(n)/2 - this is if you want to include same number combos like 2+2 for 4 but, exclude combos with 0. i.e. 0+4 for 4
(n+1)/2 - this works if you want to exclude either the combos with 0 i.e. 0+4 for 4 or the ones with same numbers i.e. 2+2 for 4.
(n-1)/2 - here, same number combos are excluded. i.e. 2+2 wont be counted as a combo for n as 4. also, 0 combos i.e. 0+4 for 4 are excluded.
n is the main number. in these examples, it is '4'. This will work only if n is an integer and these values after calculations are stored as an integer.
3 number combos are totally different. I'm sure there's a formula for that too.

How can I print out all the results?

Here the problem is to get all 4 pairs factors of pretty big number 17309205.
Results should be
{1,1,1,17309205}
{1,1,3,5769735} etc..
I tried with 4 nested for loops but it took too long, so I tried a different idea.
My way of thinking is to find every possible pairs of factors and then filter it out for those containing 4 pairs. But now I only get one result.
And also the results isn't printed in t​he way it should be.
def s(target, numbers,memo={}):
if target == 0:
return 0
if target == 1:
return 1
if target < 0:
pass
if target in memo:
return memo[target]
result = []
for n in numbers:
if target%n !=0:
pass
else:
dominator = target/n
result = s(dominator, numbers, memo)
memo[target] = [result,[n,dominator]]
return memo[target]
v = list(range(2,17309205))
print(s(17309205,v))
I see a possible solution. First you have to calculate all the prime factors of the number.
For that you need to create a function which yields prime numbers sequentially.
The function should store the primes in a list and test if the next non tested number is divisible by any prime in the list. If the number is not divisible by any prime in the list, then the number is prime.
For example when you have the list of primes [2,3,5,7] test divisibility of 8 by 2,3,5 and 7. It is divisible by 2 so it is not prime, then go with 9, etc... When reach 11 it is prime so put it in the list and yield 11.
Once calculated all the factor primes of the number you gave, you have to assign each factor to one of 4 places in a list. Then "put" all factors in its corrsponding place by multiplying the number in the place by the factor. You start with [1,1,1,1].
To get all 4 pair factors you need all possible assignments of factors. That is the set product of [0,1,2,3] with itself N times, where N is the number of factors.
(how many pairs are there for N factors?)
For example if there are 5 factors, then an element of the set product is [1, 0, 2, 3, 3] which assign first factor to place 1, second factor to place 0, third factor to place 2 and fourth a fifth factor to place 3.
For the set product we use itertools.product:
import itertools
assignments = itertools.product([0,1,2,3], repeat = len(factors))
for a in assignments:
pair = [1,1,1,1]
for i, place in enumerate(a):
pair[place] *= factor[i]
pairs.append(pair)
I can get desire results with below code but the problems is being too slow if target is too big.
target = 16
for a in range(1,target+1):
for b in range(1,target+1):
for c in range(1, target+1):
for d in range(1, target+1):
if a<=b<=c<=d and a*b*c*d == target:
print (a,b,c,d)

Find the item in a list with the maximum number of factors that appear in that same list and output maximum (Python)

I am trying to write a function that takes in a list of integers and finds the maximum number of factors that appear in that same list. For example, if I have a list with contents 9,5,6,3,2 then 9 has two factors that appear in the list (9 & 3), 5 has one factor that appears in the list (5), 6 has three factors that appear in the list (6, 3, 2), and so on. In this case, the output would be 3 (6 has the max number of factors that appear in the list with 3 total). This is what I have so far:
def divisibility(keys):
#sort keys in descending order - larger numbers w same divisors
#will always have more factors so we can remove them from the list if they are used
sortkeys = sorted(keys,reverse=True)
#compare max divisibility to divisibility of each number in keys
#(will always be at least 1 since a number is always divisible by itself)
max_divisibility = 1
while len(sortkeys) > 0:
divisibility = 1
#when done testing largest number, remove it from the list
removekeys = [sortkeys[0]]
for i in range(1,len(sortkeys)):
#test largest number
if sortkeys[0] % sortkeys[i] == 0:
divisibility += 1
#remove that number from the list
removekeys.append(sortkeys[i])
if divisibility > max_divisibility:
max_divisibility = divisibility
#remove keys in list
sortkeys = list(set(sortkeys).difference(removekeys))
return max_divisibility
This works sometimes, but in the case mentioned above it fails, because 3 is removed from the list on the first pass, so by the time we get to 6 there are only two factors of 6 left in the list (6 and 2). I know you could just check every single element of the list, but I'd really like to make this as efficient as possible, so any advice there would be appreciated.
A simpler algorithm is the following.
def factors(n, lst):
' Count of factors of n in list lst '
return sum(n % i == 0 for i in lst)
def divisibility(keys):
' Find element in keys with most factors '
return max(factors(k, keys) for k in keys)
lst = [9,5,6,3,2]
print(divisibility(lst)) # 3
You can use sum() in a comprehension to count the factors of each number and then max() to get the highest count:
def maxFactors(N): return max(sum(a%b==0 for b in N) for a in N)
maxFactors([9,5,6,3,2]) # 3
While it may be possible to reduce the complexity from O(n^2) to O(NlogN) or less, the actual performance may not be better because of the higher overhead required.
for example (using sorting and binary search):
from bisect import bisect_right
def maxFactors(N):
sN = sorted(N)
return max(sum(n%sN[i]==0 for i in range(0,bisect_right(sN,n))) for n in N)
This is actually slower than the previous one on the small example [9,5,6,3,2] but performs 3% better for a list of 50 values and 45% better on 500 values.
Some optimization could provide better performance when the data meets specific conditions such as when the ratio of maximum/minimum is smaller than the number of elements.
for example:
from collections import Counter
def maxFactors(N):
fRange = range(1,max(N)//min(N)+1)
cN = Counter(N)
return max(sum(cN[n//f] for f in fRange if n%f==0) for n in cN)
This would give an O(NxF) complexity where F is the ratio of maximum/minimum.
It is also slower than the first one on the small example but runs 8 times faster on range(25,75) and 38 times faster on range(250,750)
To get the best performance you could select one of these 3 based on a quick assessment of the data. For example, use the Counter() approach when the max/min ratio is less than len(N) or use the binary search when len(N) is large enough.
def maxFactors(N):
fRange = range(1,max(N)//min(N)+1)
if len(fRange)<len(N):
cN = Counter(N)
return max(sum(cN[n//f] for f in fRange if n%f==0) for n in cN)
if len(N) < 50:
return max(sum(a%b==0 for b in N) for a in N)
sN = sorted(N)
return max(sum(n%sN[i]==0 for i in range(0,bisect_right(sN,n))) for n in sN)
I like #DarrylG's answer, but still, here's a modified version of the algo in the question, which does print 3:
def divisibility2(keys):
#sort keys in descending order - larger numbers w same divisors
#will always have more factors so we can remove them from the list if they are used
sortkeys = sorted(keys)
#compare max divisibility to divisibility of each number in keys
#(will always be at least 1 since a number is always divisible by itself)
max_divisibility = 1
while len(sortkeys) > 0:
divisibility = 1
#when done testing largest number, remove it from the list
largest = sortkeys.pop()
for elem in sortkeys:
#test largest number
if largest % elem == 0:
divisibility += 1
if divisibility > max_divisibility:
max_divisibility = divisibility
return max_divisibility

Maximum items with given k tokens

I have N elements in array. I can select the first item max of N times, the second item max of N-1 times and so on.
I have K tokens to use and need to use them so I can have the maximum number of items.
arr = [3, 4, 8] where array elements indicates tokens required for i'th item
n = 10 , represents number of tokens I have
Output:
3
Explanation:
We have 2 options here:
1. option 1: 1st item 2 times for 6 tokens (3*2) and second item once for 4 tokens (4*1)
2. option 2: 1st item 3 times for 9 tokens (3*3)
so maximum we can have 3 items
Code:
def process(arr,n):
count = 0
sum = 0
size = len(arr)+1
for i in range(0, len(arr), 1):
size1 = size-1
size -= 1
while((sum+arr[i] <= n) and (size1 > 0)):
size1 = size1 -1
sum = sum + arr[i]
count += 1
return count;
But it worked for only few test cases, it failed for some hidden test cases. I am not sure where I made a mistake. Can anybody help me?
Your greedy approach will fail for the test cases like this:
[8,2,1,1] 10
Your code will return 2 but the maximum will be 6.
I will use a heap of a tuple i.e. heap[(cost_of_ride,max_no_rides)] .
See the code below:
from heapq import *
def process(arr,n):
count = 0
heap = []
for i in range(len(arr)):
heappush(heap,(arr[i],-(len(arr)-i))) # Constructing min-heap with second index as negative of maximum number of rides
while(n>0 and heap):
cost,no_of_rides = heappop(heap)
no_of_rides = -1 * no_of_rides # Changing maximum no_of_rides from negative to positive
div = n//cost
# If the amount of money is not sufficient to calculate the last number of rides user could take
if(div<no_of_rides):
count += div
break
# Else decrement the number of tokens by minimum cost * maximum no_of_rides
else:
count += no_of_rides
n -= no_of_rides*cost
return count;
Time Complexity for the solution is: O(len(arr)*lg(len(arr))) or O(N*lg(N)).
Try:
def process(arr, n, res=[]):
l=len(arr)
for j in range(len(arr)+1):
r=[arr[0]]*j
if(sum(r)==n) or (sum(r)<n) and (l==1):
yield len(res+r)
elif(sum(r)<n):
yield from process(arr[1:], n-sum(r), res+r)
else:
break
The idea is to iterate over all possible combinations of resulting tokens, more precisely - all options for individual token are just this token taken between 0 and N times, where N refers to tokens position, per your logic.
Discarding on the way combinations, which exceed n, ultimately returning generator, which produces lengths of produced vector of all tokens taken in all possible quantities (so in order to address your question - you need to take max(...) from it).
Outputs:
>>> print(max(process([3,4,8],10)))
3
>>> print(max(process([8,2,1,1],10)))
6
>>> print(max(process([10, 8, 6, 4, 2], 30)))
6
#learner your logic doesn't seem to be working properly.
Please try these inputs: arr = [10, 8, 6, 4, 2], n = 30.
As per your description answer should be 6 rides but your code would produce 3
Use a modified form a quickselect, where you select the next pivot based on the sum of the products of cost * max_times, but still sort based on just cost. This is worst-case O(n^2), but expected O(n).

Sum of all numbers

I need to write a function that calculates the sum of all numbers n.
Row 1: 1
Row 2: 2 3
Row 3: 4 5 6
Row 4: 7 8 9 10
Row 5: 11 12 13 14 15
Row 6: 16 17 18 19 20 21
It helps to imagine the above rows as a 'number triangle.' The function should take a number, n, which denotes how many numbers as well as which row to use. Row 5's sum is 65. How would I get my function to do this computation for any n-value?
For clarity's sake, this is not homework. It was on a recent midterm and needless to say, I was stumped.
The leftmost number in column 5 is 11 = (4+3+2+1)+1 which is sum(range(5))+1. This is generally true for any n.
So:
def triangle_sum(n):
start = sum(range(n))+1
return sum(range(start,start+n))
As noted by a bunch of people, you can express sum(range(n)) analytically as n*(n-1)//2 so this could be done even slightly more elegantly by:
def triangle_sum(n):
start = n*(n-1)//2+1
return sum(range(start,start+n))
A solution that uses an equation, but its a bit of work to arrive at that equation.
def sumRow(n):
return (n**3+n)/2
The numbers 1, 3, 6, 10, etc. are called triangle numbers and have a definite progression. Simply calculate the two bounding triangle numbers, use range() to get the numbers in the appropriate row from both triangle numbers, and sum() them.
Here is a generic solution:
start=1
n=5
for i in range(n):
start += len (range(i))
answer=sum(range(start,start+n))
As a function:
def trio(n):
start=1
for i in range(n):
start += len (range(i))
answer=sum(range(start,start+n))
return answer
def sum_row(n):
final = n*(n+1)/2
start = final - n
return final*(final+1)/2 - start*(start+1)/2
or maybe
def sum_row(n):
final = n*(n+1)/2
return sum((final - i) for i in range(n))
How does it work:
The first thing that the function does is to calculate the last number in each row. For n = 5, it returns 15. Why does it work? Because each row you increment the number on the right by the number of the row; at first you have 1; then 1+2 = 3; then 3+3=6; then 6+4=10, ecc. This impy that you are simply computing 1 + 2 + 3 + .. + n, which is equal to n(n+1)/2 for a famous formula.
then you can sum the numbers from final to final - n + 1 (a simple for loop will work, or maybe fancy stuff like list comprehension)
Or sum all the numbers from 1 to final and then subtract the sum of the numbers from 1 to final - n, like I did in the formula shown; you can do better with some mathematical operations
def compute(n):
first = n * (n - 1) / 2 + 1
last = first + n - 1
return sum(xrange(first, last + 1))

Categories

Resources