Find 4 numbers which sum is given number - python

Need to find 4 numbers from list which sum will be equal to given "sum_"
def find_four(nums, sum_):
if len(nums) < 4:
return
i = 0
i2 = 1
i3 = 2
i4 = 3
while True:
num_sum = nums[i] + nums[i2] + nums[i3] + nums[i4]
if num_sum == sum_:
return [nums[i], nums[i2], nums[i3], nums[i4]]
elif i == len(nums) - 4:
return
elif i2 == len(nums) - 3:
i += 1
elif i3 == len(nums) - 2:
i2 += 1
elif i4 == len(nums) - 1:
i3 += 1
elif i4 != len(nums):
i4 += 1
My code works good with some lists like:
find_four([1, 1, 1, 5, 1, 5, 7], 10) or
find_four([4, 6, 1, 4, 1, 6, 2], 13).
But it don't work with some for example'
find_four([7, 5, 1, 4, 1, 6, 2], 11) (need to print 7, 1, 1, 2)
What is wrong ?? :(

There's no reason for nested loops.
import itertools
import operator as op
from functools import reduce
def find_N(nums, target, N=4):
for combo in list(itertools.combinations(nums, N)):
if reduce(op.add, combo) == target:
return combo
return []
>>> find_N([1,2,3,4,5,6,7,8], target=10)
(1, 2, 3, 4)
>>> find_N([1,2,3,4,5,6,7,8], target=10, N=3)
(1, 2, 7)
>>> find_N([1,2,3,4,5,6,7,8], target=10, N=2)
(2, 8)
Observations:
Many things are pre-built in Python, use them instead of recreating the wheel
Generalization is good. You don't want to change your function if N changes from 4
sum isn't a sound variable name as it clobbers the built-in function of that name

You should probably sort nums before processing it in chunks of 4, as you're doing.
def find_four(nums, sum_):
if len(nums) < 4:
return
nums = sorted(nums) # Sort nums here
i = 0
i2 = 1
i3 = 2
i4 = 3
while True:
num_sum = nums[i] + nums[i2] + nums[i3] + nums[i4]
if num_sum == sum_:
return [nums[i], nums[i2], nums[i3], nums[i4]]
elif i == len(nums) - 4:
return
elif i2 == len(nums) - 3:
i += 1
elif i3 == len(nums) - 2:
i2 += 1
elif i4 == len(nums) - 1:
i3 += 1
elif i4 != len(nums):
i4 += 1
This will produce for 7, 1, 1, 2 for find_four([7, 5, 1, 4, 1, 6, 2], 11). Note that the order of the numbers will not be preserved.

Simply use itertools.combinations. Below code will return all pairs from which has summation equals to sum_. I am setting combinations to take 4 numbers by passing 4 default in the argument. You can alternate amount of numbers used for calculating sum by changing target.
import itertools
from itertools import combinations
def find_four(nums, sum_,target=4):
return([pair for pair in itertools.combinations(nums,target) if sum(pair) == sum_])
nums=[1,2,3,4,1,5,6,7,8,9,10,11,12]
example:-
>>> find_four([1,2,3,4,1,5,6,7,8,9,10,11,12],10)
[(1, 2, 3, 4), (1, 2, 1, 6), (1, 3, 1, 5), (2, 3, 4, 1)]
>>>find_four([1,2,3,4,1,5,6,7,8,9,10,11,12],10,3)
[(1, 2, 7), (1, 3, 6), (1, 4, 5), (1, 1, 8), (2, 3, 5), (2, 1, 7), (3, 1, 6), (4, 1, 5)]

You can do this simpler using 4 loops
def find_four(arr, sum_):
for i in range(len(arr)):
for j in range(i + 1, len(arr)):
for t in range(j + 1, len(arr)):
for k in range(t + 1, len(arr)):
if arr[i] + arr[j] + arr[t] + arr[k] == sum_:
return [arr[i],arr[j],arr[t],arr[k]]
return []
Input
find_four([1, 1, 1, 5, 1, 5, 7], 10)
find_four([4, 6, 1, 4, 1, 6, 2], 13)
find_four([7, 5, 1, 4, 1, 6, 2], 11)
Output
[1, 1, 1, 7]
[4, 6, 1, 2]
[7, 1, 1, 2]

I notice that each collection of numbers in the question is a bag or multiset, rather than a set. That is, each collection contains at least one repetition of one of its members. This guaranteed to be the case, it can be more appropriate to calculate all multiset combinations and thereby avoid processing repeated combinations.
>>> from sympy.utilities.iterables import multiset_combinations
>>> def find_four(nums, sum_):
... for c in multiset_combinations(nums, 4):
... if sum(c) == sum_:
... return c
... return {}
...
>>> find_four([1, 1, 1, 5, 1, 5, 7], 10)
[1, 1, 1, 7]
>>> find_four([4, 6, 1, 4, 1, 6, 2], 13)
[1, 2, 4, 6]
>>> find_four([7, 5, 1, 4, 1, 6, 2], 11)
[1, 1, 2, 7]

Related

Python list element patterns

I have a VERY simple question.
Honestly, I'm so sorry to ask you this stupid question but I really don't know how to do this.
I have a list like this.
p = [2, 5, 1, 2, 4, 1, 2, 5, 1, 2, 4, 1, 2, 4, 1, 2, 5, 1, 2, 4]
The elements in the list are usually the repetition of either 2, 4, 1 or 2, 5, 1.
Sometimes, there are no 1 at the end of 2, 4 or 2, 5.
I want to put 3 in the new list where the 3 consecutive elements of 2, 4, 1 or 2, 5, 1 are.
2, 4, 1 -> 3
2, 5, 1 -> 3
And I want to put 2 in the new list where the elements of 2, 4 or 2, 5 are.
2, 4 -> 2
2, 5 -> 2
Also, if there is either 2, 4 or 2, 5 at the end of the last 2, 4, 1 or 2, 5, 1, I need to put 3.
[2, 4, 1, 2, 5, 1, 2, 5] ==> [3, 3, 3]
However, if there are more than 2 of either 2, 4 or 2, 5 in a row after 2, 4, 1 or 2, 5, 1, I want to put 2 in the list like below.
[2, 4, 1, 2, 5, 2, 5] ==> [3, 2, 2]
My simple code below doesn't give me 2 at the end of the new list. Why is that?
Actually, I altered my code a lot and I am not getting what I want...
Any help will be GREATLY appreciated!
new_list = []
p = [2, 5, 1, 2, 4, 1, 2, 5, 1, 2, 4, 1, 2, 4, 1, 2, 5, 1, 2, 4]
#p = [2, 5, 1, 2, 4, 1, 2, 5, 1, 2, 4, 1, 2, 4, 1, 2, 5, 1, 2, 4, 2, 4]
#p = [2, 4]
for i in range(0, len(p)):
if i <= len(p) - 3:
if p[i] == 2 and p[i+1] >= 4:
if p[i+2] !=1:
new_list.append(2)
elif p[i+2] == 1:
new_list.append(3)
print(new_list)
Let's do it step by step :
new_list = []
p = [2, 5, 1, 2, 4, 1, 2, 5, 1, 2, 4, 1, 2, 4, 1, 2, 5, 1, 2, 4]
# p = [2, 4, 1, 2, 5, 1, 2, 5]
# p = [2, 4, 1, 2, 5, 2, 5]
sublists = []
# First we split the list on each 2
for i in p:
if i == 2:
sublists.append([2])
else:
sublists[-1].append(i)
# Then we add the size of each sublist to the new list
new_list = [len(x) for x in sublists]
# This is the same as:
# for i in sublists:
# new_list.append(len(i))
# Finally we check the particular case of a 2 at the end of the new list
if len(new_list) > 0 and new_list[-1] == 2:
new_list[-1] = 3
if len(new_list) > 1 and new_list[-2] == 2:
new_list[-1] = 2
print(new_list)
It's often a good idea when you are dealing with multiple elements in a list to split it into sub-lists for more clarity.
I hope you find that useful.
This is my method of doing it:
new_list = []
p = [2, 5, 1, 2, 4, 1, 2, 5, 1, 2, 4, 1, 2, 4, 1, 2, 5, 1, 2, 4]
while len(p) > 0:
if p[0] == 2:
if len(p) >= 3:
if p[1] == 4 and p[2] == 1:
new_list.append(3)
p.pop(0)
p.pop(0)
p.pop(0)
elif p[1] == 4 and p[2] != 1:
new_list.append(2)
p.pop(0)
p.pop(0)
elif p[1] == 5 and p[2] == 1:
new_list.append(3)
p.pop(0)
p.pop(0)
p.pop(0)
elif p[1] == 5 and p[2] != 1:
new_list.append(2)
p.pop(0)
p.pop(0)
else:
if p[1] == 4:
new_list.append(2)
p.pop(0)
p.pop(0)
elif p[1] == 5:
new_list.append(2)
p.pop(0)
p.pop(0)
print(new_list)
Firstly, we being a while loop that works until the list p has 0 elements.
Then, we check if the first element of the list starts with a 2. In this case it does. We then check if the second and third elements are 4 and 1. If they are, we add 3 to the new list, and then remove these 3 terms from the list so that their is no repetition.
We check this for the second and third elements 5 and 1 as well, and we also check if the third element is not 1. If it is not 1, we add a 2.
Then, the code is repeated in a if statement. This is because, when the list only has 2 elements left, the 2, 4 in this case, their would be an index error when we check if p[2] == 1, as their is no 3rd element. Therefore, when their are only 2 elements, we check to see if they are 2, 4 or 2, 5, and if so, add 2 to the list.
The code works as long as the first element of the list is a 2, which it seems like it always is.

Use clipping or zero padding

Write a function that accept an array of N elements, and converts it into a 9-element array. Use clipping or zero padding to obtain the desired output. Care should be taken to place the middle element of the input array as the middle element of output array.
Examples
A = [ 2, 5, 1 ] # N == 3, so zero padding to make the output size 9
output = [0, 0, 0, 2, 5, 1, 0, 0, 0]
A = [ 2, 3, 7, 4 ]
output = [0, 0, 2, 3, 7, 4, 0, 0, 0] or [0, 0, 0, 2, 3, 7, 4, 0, 0] # Because there are two middle elements if N is even
A = [ 1, 3, 3, 4, 7, 6, 4, 3, 3, 2, 2, 2, 1 ] # N == 13, so remove 4 elements (2 at the right, 2 at the left) to make the output size 9
output = [3, 4, 7, 6, 4, 3, 3, 2, 2]
A = [ 1, 2, 3, 3, 4, 7, 6, 4, 3, 3, 2, 2, 2, 1 ]
output = [3, 3, 4, 7, 6, 4, 3, 3, 2] or [3, 4, 7, 6, 4, 3, 3, 2, 2]
Imagine you want an output list of n=9 with zeros padding and an input list A.
Then treat the thing as separate problems:
If len(A) == n return A
If len(A) > n then return the middle n elements of A.
Else return a list that starts with (n - len(A)) // 2 zeros, then A, then ends with n - (n - len(A)) // 2 - len(A) zeros (however much is left over from n elements).
E.g.
def fulllist(A, n):
if len(A) == n:
return A
if len(A) > n:
start = (len(A) - n) // 2
return A[start:start+n]
if len(A) < n:
leftpad = [0] * ((n - len(A)) // 2)
rightpad = [0] * (n - len(leftpad) - len(A))
return leftpad + A + rightpad
This gives the following as output:
>>> fulllist([2, 5, 1], 9)
[0, 0, 0, 2, 5, 1, 0, 0, 0]
>>> fulllist([2, 3, 7, 4], 9)
[0, 0, 2, 3, 7, 4, 0, 0, 0]
>>> fulllist([1, 3, 3, 4, 7, 6, 4, 3, 3, 2, 2, 2, 1], 9)
[3, 4, 7, 6, 4, 3, 3, 2, 2]
>>> fulllist([1, 2, 3, 3, 4, 7, 6, 4, 3, 3, 2, 2, 2, 1], 9)
[3, 3, 4, 7, 6, 4, 3, 3, 2]
The above function was written to match the pseudocode, but it would be better to alter it slightly, eliminating case 1. by treating it as a special case of 2. or 3.. That way the code is shorter, and more important, the function always returns a brand new list in every case instead of sometimes returning a reference to the input list, which could get confused in subsequent code and lead to some bugs. I chose to merge 1. with 2.
def fulllist(A, n):
if len(A) >= n:
start = (len(A) - n) // 2
return A[start:start+n]
else:
leftpad = [0] * ((n - len(A)) // 2)
rightpad = [0] * (n - len(leftpad) - len(A))
return leftpad + A + rightpad
Or you can shorten the code further by abusing the fact that [0] * -1 == [].
def fulllist(A, n):
leftpad = [0] * ((n - len(A)) // 2)
rightpad = [0] * (n - len(leftpad) - len(A))
start = (len(A) - n) // 2 if len(A) > n else 0
contents = A[start:start+n]
return leftpad + contents + rightpad
Although that may be going too far for some and it might make the code harder to understand. I like it though.
you can try something like this:
def padding(arr,n):
diff = abs(n - len(arr))
start, end = int(diff / 2), diff - int(diff / 2)
if len(arr) < n:
for i in range(start):
arr.insert(0,0)
for i in range(end):
arr.append(0)
else:
for i in range(start):
arr.pop(0)
for i in range(end):
arr.pop(-1)
return arr
A = [ 1, 3, 3, 4, 7, 6, 4, 3, 3, 2, 2, 2, 1 ]
print(padding(A,9))
Try -
def padding(l1):
flag = 0
while len(l1)>=9:
if len(l1) == 9:
return l1
if flag %2== 0:
l1.pop()
else:
l1.pop(0)
flag+=1
if len(l1)<9:
iterations = (9- len(l1))//2
for _ in range(iterations):
l1.insert(0,0)
l1.append(0)
if iterations % 2 == 0:
l1.append(0)
return l1
padding(A)

Fast python algorithm to find all possible partitions from a list of numbers that has subset sums equal to given ratios

Say I have a list of 20 random integers from 0 to 9. I want to divide the list into N subsets so that the ratio of subset sums equal to given values, and I want to find all possible partitions. I wrote the following code and got it work for the N = 2 case.
import random
import itertools
#lst = [random.randrange(10) for _ in range(20)]
lst = [2, 0, 1, 7, 2, 4, 9, 7, 6, 0, 5, 4, 7, 4, 5, 0, 4, 5, 2, 3]
def partition_sum_with_ratio(numbers, ratios):
target1 = round(int(sum(numbers) * ratios[0] / (ratios[0] + ratios[1])))
target2 = sum(numbers) - target1
p1 = [seq for i in range(len(numbers), 0, -1) for seq in
itertools.combinations(numbers, i) if sum(seq) == target1
and sum([s for s in numbers if s not in seq]) == target2]
p2 = [tuple(n for n in numbers if n not in seq) for seq in p1]
return list(zip(p1, p2))
partitions = partition_sum_with_ratios(lst, ratios=[4, 3])
print(partitions[0])
Output:
((2, 0, 1, 2, 4, 6, 0, 5, 4, 4, 5, 0, 4, 5, 2), (7, 9, 7, 7, 3))
If you calculate the sum of each subset, you will find the ratio is 44 : 33 = 4 : 3, which are exactly the input values. However, I want the function to work for any number of subsets. For example, I expect
partition_sum_with_ratio(lst, ratios=[4, 3, 3])
to return something like
((2, 0, 1, 2, 4, 6, 0, 5, 4, 4, 3), (5, 0, 4, 5, 2, 7), (9, 7, 7))
I have been thinking about this problem for a month and I found this to be extremely hard. My conclusion is that this problem can only be solved by a recursion. I would like to know if there are any relatively fast algorithm for this. Any suggestions?
Yes, recursion is called for. The basic logic is to do a bipartition into one part and the rest and then recursively split the rest in all possible ways. I've followed your lead in assuming that everything is distinguishable, which creates a lot of possibilities, possibly too many to enumerate. Nevertheless:
import itertools
def totals_from_ratios(sum_numbers, ratios):
sum_ratios = sum(ratios)
totals = [(sum_numbers * ratio) // sum_ratios for ratio in ratios]
residues = [(sum_numbers * ratio) % sum_ratios for ratio in ratios]
for i in sorted(
range(len(ratios)), key=lambda i: residues[i] * ratios[i], reverse=True
)[: sum_numbers - sum(totals)]:
totals[i] += 1
return totals
def bipartitions(numbers, total):
n = len(numbers)
for k in range(n + 1):
for combo in itertools.combinations(range(n), k):
if sum(numbers[i] for i in combo) == total:
set_combo = set(combo)
yield sorted(numbers[i] for i in combo), sorted(
numbers[i] for i in range(n) if i not in set_combo
)
def partitions_into_totals(numbers, totals):
assert totals
if len(totals) == 1:
yield [numbers]
else:
for first, remaining_numbers in bipartitions(numbers, totals[0]):
for rest in partitions_into_totals(remaining_numbers, totals[1:]):
yield [first] + rest
def partitions_into_ratios(numbers, ratios):
totals = totals_from_ratios(sum(numbers), ratios)
yield from partitions_into_totals(numbers, totals)
lst = [2, 0, 1, 7, 2, 4, 9, 7, 6, 0, 5, 4, 7, 4, 5, 0, 4, 5, 2, 3]
for part in partitions_into_ratios(lst, [4, 3, 3]):
print(part)

arrangements backtracking python

I have a hard time trying to figure out how to generate the arrangements via backtracking on python, it is something they asked us at university
A group of n (n<=10) persons, numbered from 1 to n are placed on a row
of chairs, but between every two neighbor persons some conflict of
interests appeared. Display all the possible modalities to replace the
persons, such that between any two persons in conflict stays one or at
most two other persons.
I managed to modify the code for the permutations and the queens but i don`t really know where to put the condition for example k is the number and k must be different from the previous number in the string +1 and must bee different from the next number+1
The list of person sitting on the chairs is 1 2 3 4 (is impossible for less then 3 persons)
one right solution will be 1 3 4 2 and 3 1 4 2
Here is the code:
class Permutations(Backtracking):
def __init__(self, n):
Backtracking.__init__(self, n)
def _init_value(self, k):
return 0
def _next_value(self, n, k, v):
if v < n:
return v + 1
return None
def _cond(self, k, possible, v):
if v is None:
return False
try:
possible[:k].index(v)
return False
except ValueError:
return True
def _solution(self, n, k, possible):
return k == n-1
def _handle_solution(self, n, k, possible):
print(possible)
def chairs(soln, i=0):
if i == len(soln):
yield tuple(soln)
for j in xrange(i, len(soln)):
if i == 0 or soln[j] not in (soln[i - 1] + 1, soln[i - 1] - 1):
soln[i], soln[j] = soln[j], soln[i]
for s in chairs(soln, i + 1):
yield s
soln[i], soln[j] = soln[j], soln[i]
print list(chairs(range(1, 5)))
Code:
def possible_solution(remaining, sol=None):
sol = sol or []
if not remaining:
yield sol
else:
for i, candidate in enumerate(remaining):
if not sol or abs(sol[-1] - candidate) != 1:
new_sol = sol + [candidate]
new_remaining = remaining[:i] + remaining[i+1:]
for x in possible_solution(new_remaining, new_sol):
yield x
Test code:
def possible_solutions(neighbors):
for solution in possible_solution(neighbors):
print solution
print '-' * 30
possible_solutions([1, 2, 3])
print '-' * 30
possible_solutions([1, 2, 3, 4])
print '-' * 30
possible_solutions([1, 2, 3, 4, 5])
Results:
------------------------------
------------------------------
[2, 4, 1, 3]
[3, 1, 4, 2]
------------------------------
[1, 3, 5, 2, 4]
[1, 4, 2, 5, 3]
[2, 4, 1, 3, 5]
[2, 4, 1, 5, 3]
[2, 5, 3, 1, 4]
[3, 1, 4, 2, 5]
[3, 1, 5, 2, 4]
[3, 5, 1, 4, 2]
[3, 5, 2, 4, 1]
[4, 1, 3, 5, 2]
[4, 2, 5, 1, 3]
[4, 2, 5, 3, 1]
[5, 2, 4, 1, 3]
[5, 3, 1, 4, 2]

How to split an integer into a list of digits?

Suppose I have an input integer 12345. How can I split it into a list like [1, 2, 3, 4, 5]?
Convert the number to a string so you can iterate over it, then convert each digit (character) back to an int inside a list-comprehension:
>>> [int(i) for i in str(12345)]
[1, 2, 3, 4, 5]
return array as string
>>> list(str(12345))
['1', '2', '3', '4', '5']
return array as integer
>>> map(int,str(12345))
[1, 2, 3, 4, 5]
I'd rather not turn an integer into a string, so here's the function I use for this:
def digitize(n, base=10):
if n == 0:
yield 0
while n:
n, d = divmod(n, base)
yield d
Examples:
tuple(digitize(123456789)) == (9, 8, 7, 6, 5, 4, 3, 2, 1)
tuple(digitize(0b1101110, 2)) == (0, 1, 1, 1, 0, 1, 1)
tuple(digitize(0x123456789ABCDEF, 16)) == (15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
As you can see, this will yield digits from right to left. If you'd like the digits from left to right, you'll need to create a sequence out of it, then reverse it:
reversed(tuple(digitize(x)))
You can also use this function for base conversion as you split the integer. The following example splits a hexadecimal number into binary nibbles as tuples:
import itertools as it
tuple(it.zip_longest(*[digitize(0x123456789ABCDEF, 2)]*4, fillvalue=0)) == ((1, 1, 1, 1), (0, 1, 1, 1), (1, 0, 1, 1), (0, 0, 1, 1), (1, 1, 0, 1), (0, 1, 0, 1), (1, 0, 0, 1), (0, 0, 0, 1), (1, 1, 1, 0), (0, 1, 1, 0), (1, 0, 1, 0), (0, 0, 1, 0), (1, 1, 0, 0), (0, 1, 0, 0), (1, 0, 0, 0))
Note that this method doesn't handle decimals, but could be adapted to.
[int(i) for i in str(number)]
or, if do not want to use a list comprehension or you want to use a base different from 10
from __future__ import division # for compatibility of // between Python 2 and 3
def digits(number, base=10):
assert number >= 0
if number == 0:
return [0]
l = []
while number > 0:
l.append(number % base)
number = number // base
return l
While list(map(int, str(x))) is the Pythonic approach, you can formulate logic to derive digits without any type conversion:
from math import log10
def digitize(x):
n = int(log10(x))
for i in range(n, -1, -1):
factor = 10**i
k = x // factor
yield k
x -= k * factor
res = list(digitize(5243))
[5, 2, 4, 3]
One benefit of a generator is you can feed seamlessly to set, tuple, next, etc, without any additional logic.
like #nd says but using the built-in function of int to convert to a different base
>>> [ int(i,16) for i in '0123456789ABCDEF' ]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
>>> [int(i,2) for i in "100 010 110 111".split()]
[4, 2, 6, 7]
Another solution that does not involve converting to/from strings:
from math import log10
def decompose(n):
if n == 0:
return [0]
b = int(log10(n)) + 1
return [(n // (10 ** i)) % 10 for i in reversed(range(b))]
Using join and split methods of strings:
>>> a=12345
>>> list(map(int,' '.join(str(a)).split()))
[1, 2, 3, 4, 5]
>>> [int(i) for i in ' '.join(str(a)).split()]
[1, 2, 3, 4, 5]
>>>
Here we also use map or a list comprehension to get a list.
Strings are just as iterable as arrays, so just convert it to string:
str(12345)
Simply turn it into a string, split, and turn it back into an array integer:
nums = []
c = 12345
for i in str(c):
l = i.split()[0]
nums.append(l)
np.array(nums)

Categories