def equal_parts(numbers): lst1 = [] lst2 = [] numbers.sort(reserve = True) for i in numbers: if sum(lst1) < sum(lst2): lst1.append(i) else: lst2.append(i) return lst1 return lst2 ''' list(int) -> list(int), list(int) Return two integers list of equal sum as an attempt at a partition of input numbers equal_parts([2, 4, 5, 6, 7, 12, 13, 23, 1, 1, 34]) ([23, 13, 7, 6, 4, 1]),([34, 12, 5, 2, 1]) ''' 

How to divide the input list of integers into two lists with the same sum of elements ??? I do not understand what the problem is ... Help plz))

3 answers 3

So as you do, do not. You are trying to greedily add items to a smaller list (by the way, it’s better to maintain the amount yourself, rather than recommit each time). The simplest example: 5 4 3 3 3. You put your way 5 and 4 in different lists, and then nothing happens.

This classic backpack problem. It is solved either by using dynamic programming (the elements are reasonably small, the sum is up to million-10 for example) or by using some method of branches and borders (the number of elements is within 20-30). In other cases, for a reasonable time / memory, you do not decide it.

You need to first calculate the sum of the entire list, then pseudocode such (DP, bust yourself write). I assume that only positive elements, if there are negative, then you need to make a couple of modifications.

 массив ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ -1 ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ[0] = 0 Ρ†ΠΈΠΊΠ» ΠΏΠΎ списку val - Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ элСмСнт списка Ρ†ΠΈΠΊΠ» i ΠΎΡ‚ суммы списка/2 Π΄ΠΎ val Ссли ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ[i] = -1 ΠΈ ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ[i-val] != -1 Ρ‚ΠΎ ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ[i] = val //ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΎΡ‚Π²Π΅Ρ‚Π° Если ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ[sum/2] = -1 Ρ‚ΠΎ Π²Ρ‹ΠΉΡ‚ΠΈ Π’Π΅ΠΊΡƒΡ‰ΠΈΠΉ = sum/2 Пока Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ != 0 ВывСсти ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ[Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ] Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ = Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ - ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ[Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ] 

Example of implementation:

 def gen_left(numbers): K = sum(numbers) prev = [-1] * (K // 2 + 1) prev[0] = 0 for val in numbers: for i in range(K // 2, val-1, -1): # K//2..val inclusive if prev[i] == -1 and prev[i - val] != -1: prev[i] = val if prev[K//2] == -1: raise ValueError curr = K // 2 while curr > 0: yield prev[curr] curr -= prev[curr] def partition_equal_sums(numbers): left = list(gen_left(numbers)) right = list(numbers) for item in left: # O(n**2) right.remove(item) if sum(right) != sum(left): raise ValueError return left, right 
  • @jfs is 1 list. In 2 all the rest will go. - pavel
  • @jfs yes, thank you) the truth is that the removal per square is ugly a little, but this is not fundamental already. - pavel
  • in order not to O(n**2) , you just need to save indices instead of values ​​(as in my answer with p(i,j) ). I left it as it is, so as not to deviate from your text describing the algorithm. - jfs
  • @jfs take this algorithm into your answer, yes I will delete this answer. To complete the answer came out) the brute force bust and DP - pavel
  • I do not understand very well how it works (I just implemented it one by one on the forehead according to the description and it passes simple tests). Looks like this description , but I'm not sure. In general, I did not publish different algorithms with separate answers, only to soften the notation and the increasing complexity. But in general, it is more convenient to vote and comment on different options in different answers. - jfs

The challenge: is it possible to split a set of positive integers into two parts with equal sums β€” this is the NP problem ( partition problem / number partitioning ), which is NP-complete . NP class means that fast solution algorithms are not known, but if a solution is found, then you can quickly check whether it is true. Although in practice, a solution can be found quickly for small numbers β€” it is the easiest of the most difficult / difficult tasks .

This is a special case of the sum of subsets problem , which in turn can be considered as a special case of the backpack problem .

The answer contains three solutions:

  • Greedy algorithm (does not always work)
  • Brute Force Method β€” Brute Force (O (n 2 n ))
  • Solution using dynamic programming with pseudopolynomial time (O (nk))

Greedy algorithm (does not always work)

The code in question implements the greedy algorithm: take the largest number of those not yet selected and add to the part that has the sum now less:

 def partition_equal_sums_greedy(numbers): parts, sums = ([], []), [0, 0] for largest in sorted(numbers, reverse=True): smaller = sums[1] < sums[0] parts[smaller].append(largest) sums[smaller] += largest if sums[0] != sums[1]: raise ValueError("Greedy algorithm has failed to find the" " number partitioning for {numbers!r}".format(**vars())) return parts 

To find a solution for the example in question is enough

 #XXX DO NOT DO IT return lst1 return lst2 

replaced by:

 return lst1, lst2 

Example:

 >>> partition_equal_sums([2, 4, 5, 6, 7, 12, 13, 23, 1, 1, 34]) ([34, 12, 5, 2, 1], [23, 13, 7, 6, 4, 1]) 

This is a simple fast algorithm, but as @pavel pointed out , the greedy algorithm does not always work for this task: for [5, 4, 3, 3, 3] it returns: ([5, 3], [4, 3, 3]) parts that have unequal amounts.

Here is another example from the link above , for which the greedy algorithm does not work:

 771 121 281 854 885 734 486 1003 83 62 

In the general case, to find a solution for n numbers you may need about 2 n steps (when the magnitude of the numbers is about 2 n ). For comparison: if the largest number in the input does not depend on n , then the greedy algorithm is linear β€” O(n) (when using O (n) sorting).

To tell the difference: for n = 1000 , a linear algorithm requires order 1000 of steps, while still 10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376 February 1000.

This is the reason why tasks from the NP class are considered difficult / complex (if NP! = P , then the exponential solution is the best that can be hoped for in the general case, if we do not consider non-deterministic solutions).

Brute Force Method β€” Brute Force

Here is a solution to a complete enumeration of all the options for splitting the input list into two parts and checking whether these parts have equal sums:

 from itertools import compress, product def partition_equal_sums_brute_force(numbers): if sum(numbers) % 2 == 0: # even sum for selectors in product([0, 1], repeat=len(numbers)): if sum(compress(numbers, selectors)) == sum(numbers) // 2: return (list(compress(numbers, selectors)), list(compress(numbers, (not s for s in selectors)))) raise ValueError("Can't partition into two parts with equal sums" " for {numbers!r}".format(**vars())) 

product([0, 1], repeat=len(numbers)) simply generates all binary numbers of len(numbers) length, for example, for len(numbers) == 3 : 000 001 010 011 100 101 110 111 (as list 0 , 1 digits). compress(numbers, selectors) returns those numbers from numbers , in those places where there are one ( 1 ) in selectors .

This is an O(n*2**n) algorithm that can be improved to O(2**n) , if you do not recalculate each time the amount for parts.

Example:

 >>> partition_equal_sums([2, 4, 5, 6, 7, 12, 13, 23, 1, 1, 34]) ([7, 13, 34], [2, 4, 5, 6, 12, 23, 1, 1]) >>> partition_equal_sums([5, 4, 3, 3, 3]) ([3, 3, 3], [5, 4]) >>> partition_equal_sums([771, 121, 281, 854, 885, 734, 486, 1003, 83, 62]) ([121, 885, 486, 1003, 83, 62], [771, 281, 854, 734]) 

Although, in the worst case, random input may require exponential time (the deterministic case) to find a solution for all known algorithms for this problem, but in practice, for many inputs, a solution can be found much faster.

Solution using dynamic pseudo polynomial time programming

It is easy to know whether a given set of numbers can be split into two parts with equal amounts if the amount is small:

 import functools def can_partition(numbers): @functools.lru_cache(maxsize=None) def p(s, j): return p(s, j - 1) or p(s - numbers[j - 1], j - 1) if j else (s == 0) n = len(numbers) s, odd = divmod(sum(numbers), 2) return not odd and p(s, n) 

where p(s, j) true if the sum of some subset of numbers[:j] is equal to s . p(s, n) tells whether there exists a subset of numbers with a sum equal to s . For an even total, this means that you can split numbers into two parts with equal amounts.

This is the implementation of the O(kn) Python algorithm for the partitioning problem , which is a special case of the algorithm for the sum of the subsets (in Russian) .

Example:

 >>> can_partition([1, 2]) False >>> can_partition([1, 2, 3]) True 

To get the desired two lists from p(s, j) :

 def partition_equal_sums(numbers): @functools.lru_cache(maxsize=None) def p(s, j): return p(s, j - 1) or p(s - numbers[j - 1], j - 1) if j else (s == 0) j = len(numbers) s, odd = divmod(sum(numbers), 2) if odd or not p(s, j): raise ValueError("Can't partition into two parts with equal sums" " for {numbers!r}".format(**vars())) left, right = [], [] while s: while p(s, j): # sum(some_subset(numbers[:j])) == s j -= 1 right.append(numbers[j]) s -= right[-1] # sum(some_subset(numbers[:j])) == s - numbers[j] left.append(right.pop()) return left, numbers[:j] + right 

functools.lru_cache(maxsize=None) caches the returned values ​​of p(s,j) , which allows us to obtain an O(NK) (pseudopolynomial) algorithm. Using p(s,j) , he looks for an index j such that:

 sum(some_subset(numbers[:j+1])) == s sum(some_subset(numbers[:j])) != s 

that is, without the numbers[j] element, the previous numbers (index <j ) the sum of s cannot dial. Therefore, numbers[j] is chosen as a term and the cycle is repeated for s - numbers[j] sums, while the remaining sum is not zero. By construction, sum(left) == sum(numbers) // 2 and since this code is executed only for an even total sum ( sum(numbers) % 2 == 0 ), then sum(right) == sum(numbers) // 2 and the solution is found ( right contains all elements from numbers that are not in the left list and sum(left) == sum(right) ).

    I can offer a method of complete enumeration of all possible combinations:

     def next_indexes_set(indexes, numCount): count = len(indexes) for reverseIndex in range(1, count + 1): if indexes[-reverseIndex] < (numCount - reverseIndex): indexes[-reverseIndex] += 1 for index in range(count - reverseIndex + 1, count): indexes[index] = indexes[index - 1] + 1 return True return False def split_list(numbers): numCount = len(numbers) fullSum = sum(numbers) searchSum = fullSum // 2 for count in range(numCount // 2 + 1, 1, -1): indexes = [i for i in range(count)] while 1: curSum = sum([numbers[i] for i in indexes]) if curSum == searchSum: indexes.sort(reverse=True) listA = [numbers[i] for i in indexes] listB = numbers[:] for i in indexes: del listB[i] return listA, listB if not next_indexes_set(indexes, numCount): break return None, None if __name__ == "__main__": numbers = [4,3,1,2,4] * 5 print(numbers, sum(numbers)) listA, listB = split_list(numbers) if listA: print(listA, sum(listA)) print(listB, sum(listB)) 

    Just keep in mind that increasing the size of a split list greatly increases the execution time.