繁体   English   中英

对于数字列表,查找累积和保持在范围内的所有组合

[英]For a list of numbers, find all combinations whose cumulative sum stays within bounds

假设我有以下列表: [A, A, A, A, A, B, B, B]其中A=1, B=-1

查找此列表的所有组合通常很容易(8nCr3),但是当累积和达到某些界限值(例如 0 和 4)时,我想省略排列。

因此上面的例子是不正确的,因为累积和是 [1, 2, 3, 4 , 5 , 4 , 3, 2]。

排列[A, A, B, B, A, A, B, A]也不好,因为这里的累积和是 [1, 2, 1, 0 , 1, 2, 1, 2]。

我的问题是:如何计算两个组件的任何有限列表的排列数?

如果存在的话,我会对一个简单地插入分析解决方案的 function 感到最满意,但迭代/递归 function 也应该没问题。

编辑:我实际上并不需要知道排列是什么样的。 只要他们的数量就足够了。

一种可能的解决方案是使用itertools.permutations生成排列,然后检查每个排列。

这种方法存在一些问题:

  • 无效排列的数量可能很大,所以我们会生成很多无效排列。
  • 如果排列的第一项使我们超出了界限,我们知道所有具有相同开头的排列都是无效的,但是没有办法让itertools.permutations跳过它们,所以无论如何我们都必须生成它们
  • 我们必须重新计算每个新排列的所有部分和。

因此,我们宁愿构建排列并在每一步测试它们。 这样,我们可以在项目越界时立即中止整个分支。

递归解决方案:

 def bounded_permutations(data, lower, upper, start=None, curr_sum=0):
    if start is None:
        start = []
        
    for idx_first, first in enumerate(data):
        new_sum = curr_sum + first
        if not lower < new_sum < upper:
            continue
        new_data = data[:]
        del new_data[idx_first]
        new_start = start + [first]
        if not new_data:  # we used all values in the list!
            yield new_start
        else:
            yield from bounded_permutations(new_data, lower, upper, new_start, new_sum)

使用您的数据运行示例(好吧,几乎,我在复制粘贴时丢失了一个“A”):

A=1
B=-1
data = [A, A, A, A, B, B, B]
            
count=0
for p in bounded_permutations(data, 0, 4):
    count += 1
    print(p, count)
    

Output:

[1, 1, 1, -1, 1, -1, -1] 1
[1, 1, 1, -1, 1, -1, -1] 2
[1, 1, 1, -1, -1, 1, -1] 3
[1, 1, 1, -1, -1, 1, -1] 4
[1, 1, 1, -1, 1, -1, -1] 5
.
.
.
[1, 1, -1, 1, -1, 1, -1] 575
[1, 1, -1, 1, -1, 1, -1] 576

在这里,我们得到 576 个有效排列,总共 7 个。= 5040。我们也可以用len(list(bounded_permutations(data, 0, 4)))来计算它们。

由于您的数据中有重复的值,您将获得许多相同的排列。 如果您只想保留唯一的,您可以使用set 请注意,您必须将不可散列的列表转换为可散列的元组才能在集合中使用它们:

uniques = set(map(tuple, bounded_permutations(data, 0, 4)))
print(uniques)
print('number of unique permutations:', len(uniques))

Output:

{(1, 1, -1, 1, 1, -1, -1), 
 (1, 1, -1, 1, -1, 1, -1),
 (1, 1, 1, -1, 1, -1, -1),
 (1, 1, 1, -1, -1, 1, -1)}
number of unique permutations: 4

您可以使用itertools生成排列和累积列表。 返回其累积列表不包含某些特定值的唯一排列的紧凑解决方案是:

import itertools

def contain_specific_value(lst, value_lst):
    return any([True if (item in lst) else False for item in value_lst])

def accumulated_permutations_without_specific_numbers(main_lst, specific_number_lst):
    p = list(itertools.permutations(main_lst, len(main_lst)))
    res = [y for y in p if not contain_specific_value(list(itertools.accumulate(y)), specific_number_lst)]
    return list(set(res))

您只需要将某些绑定值作为列表传递给 function。

对于您的清单:

A=1
B=-1
my_list = [A, A, A, A, B, B, B]

res = accumulated_permutations_without_specific_numbers(my_list, [0,4])

Output:

(1, 1, 1, -1, -1, 1, -1)
(1, 1, -1, 1, -1, 1, -1)
(1, 1, -1, 1, 1, -1, -1)
(1, 1, 1, -1, 1, -1, -1)

累计名单:

[1, 2, 3, 2, 1, 2, 1]
[1, 2, 1, 2, 1, 2, 1]
[1, 2, 1, 2, 3, 2, 1]
[1, 2, 3, 2, 3, 2, 1]

这是一个使用动态编程(记忆)的递归解决方案,以便立即处理非常大的列表:

from functools import lru_cache

def boundCumSum(A,loBound,hiBound):
    sRange = range(loBound+1,hiBound)
    
    @lru_cache()
    def count(s,nPos,nNeg):
        if nPos==0 and nNeg==0: return int(s == 0)
        if nPos<0 or nNeg<0 or not s in sRange: return 0
        return count(s-1,nPos-1,nNeg)+count(s+1,nPos,nNeg-1)
    
    nPos   = sum(a>0 for a in A)
    nNeg   = sum(a<0 for a in A)
    return count(sum(A),nPos,nNeg)

它的工作方式是从值的总和(始终是累积总和中的最后一个值)开始,然后通过可用的 +1 和 -1 向后工作。 递归部分只需要知道还有多少正负数,并且目标是在它们用尽时达到零,而不会超出界限。 这允许使用 lru_cache 装饰器(来自 functools)将以前的结果保存在 memory 中并避免重新计算它们。 这种记忆是获得良好性能的关键,因为递归过程通常具有相同的值模式来计算。

示例输出:

boundCumSum([1,1,1,1,1,-1,-1,-1],0,4)
# 8

boundCumSum([1,1,1,1,1,-1,-1,-1],-2,3)
# 21

boundCumSum([1]*15+[-1]*12,0,4)
# 4096

boundCumSum([1]*150+[-1]*120,0,40)
# 1772031749108746260736075351494970301882401049674141497351113308699264759272661

为了验证这一点,可以使用生成器 function 来显示实际解决方案及其累积和:

def genCumSum(A,loBound,hiBound,combo=[]):
    if not A: yield combo; return
    for v,i in {v:i for i,v in enumerate(A)}.items():
        if v<=loBound or v >=hiBound: continue
        yield from genCumSum(A[:i]+A[i+1:],loBound-v,hiBound-v,combo+[v])

...

from itertools import accumulate

for i,solution in enumerate(genCumSum([1,1,1,1,1,-1,-1,-1],0,4),1):
    print(i,solution,list(accumulate(solution)))

1 [1, 1, 1, -1, 1, -1, 1, -1] [1, 2, 3, 2, 3, 2, 3, 2]
2 [1, 1, 1, -1, 1, -1, -1, 1] [1, 2, 3, 2, 3, 2, 1, 2]
3 [1, 1, 1, -1, -1, 1, 1, -1] [1, 2, 3, 2, 1, 2, 3, 2]
4 [1, 1, 1, -1, -1, 1, -1, 1] [1, 2, 3, 2, 1, 2, 1, 2]
5 [1, 1, -1, 1, 1, -1, 1, -1] [1, 2, 1, 2, 3, 2, 3, 2]
6 [1, 1, -1, 1, 1, -1, -1, 1] [1, 2, 1, 2, 3, 2, 1, 2]
7 [1, 1, -1, 1, -1, 1, 1, -1] [1, 2, 1, 2, 1, 2, 3, 2]
8 [1, 1, -1, 1, -1, 1, -1, 1] [1, 2, 1, 2, 1, 2, 1, 2]

...

for i,solution in enumerate(genCumSum([1,1,1,1,1,-1,-1,-1],-2,3),1):
    print(i,solution,list(accumulate(solution)))

1 [1, 1, -1, 1, -1, 1, -1, 1] [1, 2, 1, 2, 1, 2, 1, 2]
2 [1, 1, -1, 1, -1, -1, 1, 1] [1, 2, 1, 2, 1, 0, 1, 2]
3 [1, 1, -1, -1, 1, 1, -1, 1] [1, 2, 1, 0, 1, 2, 1, 2]
4 [1, 1, -1, -1, 1, -1, 1, 1] [1, 2, 1, 0, 1, 0, 1, 2]
5 [1, 1, -1, -1, -1, 1, 1, 1] [1, 2, 1, 0, -1, 0, 1, 2]
6 [1, -1, 1, 1, -1, 1, -1, 1] [1, 0, 1, 2, 1, 2, 1, 2]
7 [1, -1, 1, 1, -1, -1, 1, 1] [1, 0, 1, 2, 1, 0, 1, 2]
8 [1, -1, 1, -1, 1, 1, -1, 1] [1, 0, 1, 0, 1, 2, 1, 2]
9 [1, -1, 1, -1, 1, -1, 1, 1] [1, 0, 1, 0, 1, 0, 1, 2]
10 [1, -1, 1, -1, -1, 1, 1, 1] [1, 0, 1, 0, -1, 0, 1, 2]
11 [1, -1, -1, 1, 1, 1, -1, 1] [1, 0, -1, 0, 1, 2, 1, 2]
12 [1, -1, -1, 1, 1, -1, 1, 1] [1, 0, -1, 0, 1, 0, 1, 2]
13 [1, -1, -1, 1, -1, 1, 1, 1] [1, 0, -1, 0, -1, 0, 1, 2]
14 [-1, 1, 1, 1, -1, 1, -1, 1] [-1, 0, 1, 2, 1, 2, 1, 2]
15 [-1, 1, 1, 1, -1, -1, 1, 1] [-1, 0, 1, 2, 1, 0, 1, 2]
16 [-1, 1, 1, -1, 1, 1, -1, 1] [-1, 0, 1, 0, 1, 2, 1, 2]
17 [-1, 1, 1, -1, 1, -1, 1, 1] [-1, 0, 1, 0, 1, 0, 1, 2]
18 [-1, 1, 1, -1, -1, 1, 1, 1] [-1, 0, 1, 0, -1, 0, 1, 2]
19 [-1, 1, -1, 1, 1, 1, -1, 1] [-1, 0, -1, 0, 1, 2, 1, 2]
20 [-1, 1, -1, 1, 1, -1, 1, 1] [-1, 0, -1, 0, 1, 0, 1, 2]
21 [-1, 1, -1, 1, -1, 1, 1, 1] [-1, 0, -1, 0, -1, 0, 1, 2]

...

print(sum(1 for _ in genCumSum([1]*15+[-1]*12,0,4)))
# 4096

# The last example (with 270 items in the list) would take forever to check

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM