【问题标题】:Find all unique combinations of a fixed size to reach a given average range查找固定大小的所有唯一组合以达到给定的平均范围
【发布时间】:2020-03-23 16:10:13
【问题描述】:

我有一个整数范围,例如

big_list = [1, 2, ..., 100]

我需要找到这个范围内所有固定长度的数字子集,对于 k=5,它们的平均值在 50 以内(如 45-55)。例如我们的固定大小为 6,平均约为 50

sample = [71, 20, 23, 99, 25, 60]

问题是列表必须是唯一的,不能有重复的数字。

顺序无关紧要,因此 [71, 20, 23, 99, 25, 60] 和 [20, 71, 23, 99, 25, 60] 只是一种组合。

我正在考虑仅使用 itertools 生成所有组合并根据我的标准过滤掉。但是运行时间会很糟糕,因为大的数字列表可能从 10 到 400 不等。

如何根据上述条件生成一组列表

【问题讨论】:

  • 如果你的列表是随机数列表还是数字序列?该示例使其看起来像 1 - 100。
  • 就我而言,这是一个序列(如果这样可以让事情变得更快),抱歉没有澄清。
  • 什么算作“大约 50”?究竟是 45-55,还是只是“喜欢”那个范围?
  • 理想情况下尽可能接近 50,但最大距离应该是给定数字 (50) 的 +-5。我正在尝试在这种情况下生成尽可能多的数据,并且我打算在之后使用数据集后过滤掉
  • 在大多数情况下,生成所有可能的组合然后过滤掉一些组合是不现实的,因为对于长列表来说组合的数量是巨大的。或许根据一些规则只生成“好的”组合会更好。

标签: python algorithm combinations


【解决方案1】:

订单是微不足道的。

只需安排 i-th 数字大于 (i-1)-th 的约束即可。在下面的算法中,您通过将 left 递增 1 来递归

要获得 45 到 55 之间的平均值,请考虑递归公式

import math

# left: previous number is left-1
# s: current sum
# d: depth
# c: combination
def fill(left, s, d, c):
  if d == 0:
    return print(c, sum(c)/n)

  # constraint c_sum >= 45*n
  # we look for minimal i such that 
  # sum + i + 100+99+...+(100-(depth-1)+1) >= 45*n
  # sum + i + 100*(d-1) - (d-2)(d-1)/2 >= 45*n
  # i >= 45*n - 100*(d-1) - (d-2)(d-1)/2 - sum
  # 
  # constraint c_sum <= 55*n
  # we look for maximal right such that
  # sum + i + (i+1)+...+(i+(d-1)) <= 55*n
  # sum + (d-1)*i + d(d-1)/2 <= 55*n
  # i <= ( 55*n - d(d-1)/2 - sum )/(d-1)
  minleft = max(left, math.ceil(minBound*n - 100*(d-1) - (d-2)*(d-1)/2 - s))
  if d == 1:
    maxright = min(100, maxBound*n-s)
  else:
    maxright = min(100, math.floor(( maxBound*n - d*(d-1)/2 - s )/(d-1)) )
  for i in range(minleft, maxright+1):
    newsum = s + i
    c[d-1] = i
    fill(i+1, newsum, d-1, c)

n = 6
minBound = 45
maxBound = 55
fill(0, 0, n, [0]*n)

在进一步的 cmets 之后,op 对上述组合完全不感兴趣,而是对所有组合中没有数字可以出现两次的组合感兴趣

算法可以简化为最基本的算法:

n = 300
c = list(range(1, n))
while len(c) >= 6:
  print([c.pop(), c.pop(), c.pop(), c.pop(0), c.pop(0), c.pop(0)])

【讨论】:

  • 谢谢!但是有没有办法做到这一点,使数字不重复?代码的示例输出:[238, 21, 8, 2, 1, 0] 45.0, [239, 21, 8, 2, 1, 0] 45.16666666666664。有没有办法做到这一点,一旦使用了一个数字,它就不会尝试重新使用它? [239, 21, 8, 2, 1, 0] 那么它将不再使用 239,21,8,2,1,0 生成组合?
  • 但是如果数字不能重复,那么只需取 100&1,99&2,98&3 进行第一次组合,然后取 97&4,96&5,95&6 等直到你到达中间@Helpthisamatuerprogrammer
  • 对不起,我不太明白这是什么意思
  • 只取combi [100, 1, 99, 2, 98, 3]。由于您不能再使用这些数字,请使用 [97, 4, 96, 5, 95, 6] 等等@Helpthisamatuerprogrammer
  • 非常感谢!我会提出一个描述更准确的问题,并在下次提出问题时尝试提供更好的描述。
【解决方案2】:

您可以将递归与生成器一起使用:

def combo(d, k, c = []):
   if len(c) == 6:
      yield c
   else:
      for i in d:
        _c = (sum(c)+i)/float(len(c)+1)
        if i not in c and (len(c) + 1 < 6 or 50-k <= _c <= 50+k):
            yield from combo(d, k, c+[i])

当然,正如@japreiss 所指出的,这个问题会产生非常糟糕的最坏情况时间复杂度。然而,一种可能的解决方法是将combo 视为一个迭代器池,并在代码中的其他地方简单地按需访问生成的组合。例如,要访问前 100 个结果:

result = combo(range(1, 100), 5)
for _ in range(100):
   print(next(result))

输出:

[1, 2, 3, 67, 98, 99]
[1, 2, 3, 67, 99, 98]
[1, 2, 3, 68, 97, 99]
[1, 2, 3, 68, 98, 99]
[1, 2, 3, 68, 99, 97]
[1, 2, 3, 68, 99, 98]
[1, 2, 3, 69, 96, 99]
[1, 2, 3, 69, 97, 98]
[1, 2, 3, 69, 97, 99]
[1, 2, 3, 69, 98, 97]
...

【讨论】:

  • 谢谢!但是有没有办法做到这一点,使数字是唯一的?用过就不能再用了?例如,一旦生成 [1, 2, 3, 67, 98, 99],这些数字都不会再次出现?
  • 你给的不是组合而是排列
猜你喜欢
  • 2018-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多