【问题标题】:Efficient way to find partial permutations of list equal to a specific sum找到等于特定总和的列表部分排列的有效方法
【发布时间】:2020-07-29 19:43:55
【问题描述】:

我有一组浮点值,我需要所有可能的排列,它们加在一起会产生一个特定的数字。此外,我需要所有排列恰好包含八个值。

因此,例如,我有以下一组值:

[0.5, 0.8, 1, 1.5, 2, 2.5, 3, 5, 7.5, 10, 12.5, 15, 17.5, 20, 25, 30]

我需要得到 8 个元素的所有排列,加起来为 20。

有效的代码如下:

N = [0.5, 0.8, 1, 1.5, 2, 2.5, 3, 5, 7.5, 10, 12.5, 15, 17.5, 20, 25, 30]
target = 20

accum = 0

for i1 in N:
  accum += i1
  for i2 in N:
    accum += i2
    for i3 in N:
      accum += i3
      for i4 in N:
        accum += i4
        for i5 in N:
          accum += i5
          for i6 in N:
            accum += i6
            for i7 in N:
              accum += i7
              for i8 in N:
                accum += i8
                if accum > target:
                  accum -= i8
                  break
                elif accum == target:
                  print(i1, i2, i3, i4, i5, i6, i7, i8)
                  accum -= i8
                  break
                accum -= i8
              accum -= i7
            accum -= i6
          accum -= i5
        accum -= i4
      accum -= i3
    accum -= i2
  accum -= i1

其中一些结果是这样给出的:

0.5 0.5 0.5 0.5 0.5 0.5 2 15
0.5 0.5 0.5 0.5 0.5 0.5 15 2
0.5 0.5 0.5 0.5 0.5 1.5 1 15
0.5 0.5 0.5 0.5 0.5 1.5 15 1
0.5 0.5 0.5 0.5 0.5 2 0.5 15
0.5 0.5 0.5 0.5 0.5 2 3 12.5
0.5 0.5 0.5 0.5 0.5 2 12.5 3
0.5 0.5 0.5 0.5 0.5 2 15 0.5
0.5 0.5 0.5 0.5 0.5 2.5 2.5 12.5
0.5 0.5 0.5 0.5 0.5 2.5 5 10
0.5 0.5 0.5 0.5 0.5 2.5 7.5 7.5
0.5 0.5 0.5 0.5 0.5 2.5 10 5
0.5 0.5 0.5 0.5 0.5 2.5 12.5 2.5
0.5 0.5 0.5 0.5 0.5 3 2 12.5

我可以稍后使用 excel 过滤结果。我不需要复杂的代码,我只需要最终的结果集。可以看出,这段代码效率极低,因为它需要大量的时间来处理,并且还会给出大量的重复值。我没有深厚的编程知识,但我知道递归方法效率更高。我怎样才能得到我需要的结果?

【问题讨论】:

    标签: python recursion permutation


    【解决方案1】:

    这是一个递归解决方案。效率增益取决于:

    • 在递归调用中,不要遍历整个列表,只遍历列表的剩余部分。

    • 无需从头开始对每个组合进行完全求和以对其进行测试,只需在迭代时从目标中减去值即可。

    在这种情况下,可能会进一步提高效率,这取决于列表仅包含正数这一事实,因此如果特定值会超出目标,那么您不需要进行递归调用(elif val < target 测试)。如果没有这个事实,那部分代码将需要是无条件的,然后会花费更长的时间。

    测试用例在旧机器上耗时 0.04 秒。

    def yield_combos(N, num, target):
        for i, val in enumerate(N):
            if num == 1:
                if val == target:
                    yield [val]
            elif val < target:
                for j in yield_combos(N[i:], num - 1, target - val):
                    yield [val] + j
        
    
    N = [0.5, 0.8, 1, 1.5, 2, 2.5, 3, 5, 7.5, 10, 12.5, 15, 17.5, 20, 25, 30]
    target = 20
    num = 8
                        
    for combo in yield_combos(N, num, target):
        print(f"combo={combo} length={len(combo)} total={sum(combo)}")
    

    这给出了:

    combo=[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 2, 15] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 0.5, 1, 1.5, 15] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 0.5, 2, 3, 12.5] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 0.5, 2.5, 2.5, 12.5] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 0.5, 2.5, 5, 10] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 0.5, 2.5, 7.5, 7.5] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 0.5, 5, 5, 7.5] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 1, 1, 1, 15] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 1, 1.5, 3, 12.5] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 1, 2, 2.5, 12.5] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 1, 2, 5, 10] length=8 total=20.0
    combo=[0.5, 0.5, 0.5, 0.5, 1, 2, 7.5, 7.5] length=8 total=20.0
    ... etc ...
    

    以上是基于允许重复元素。如果不是,则在递归调用中使用N[i+1:] 而不是N[i:],尽管在这种情况下没有针对目标20 的解决方案。

    还请注意,这仅按照输入列表中增加索引的顺序给出每个解决方案(在这种情况下增加值,因为输入列表恰好是排序的),而不是它们的每个排列,基于对额外排列的理解是应该避免问题暗示的“重复值”。但是,如果需要额外的排列,那么最有效的做法是最初完全按照上述方式进行搜索(而不是在递归调用中循环遍历整个列表),然后仅排列解决方案的元素作为最后一步。

    【讨论】:

    • 共有20个解决方案,可以重复元素。
    • 啊,我没有意识到这是允许的。好的,那我修改一下。
    • 很容易做到,只需在递归调用中将i+1:改为i:...
    • @DavidDuran 现已实施 - 感谢您的关注。
    • 没问题。顺便说一句,很好的解决方案!
    【解决方案2】:
    import itertools
    
    def func(list, size_perm, target):
         #Generate all permutations
         perms = itertools.permutations(list, size_perm)
         
         #iterate of all permutations and print only if sum == target
         for item in perms:
             if sum(item) == target:
                 print(item)
    N = [0.5, 0.8, 1, 1.5, 2, 2.5, 3, 5, 7.5, 10, 12.5, 15, 17.5, 20, 25, 30]
    func(N, 8, 20)
    

    不太确定这是否是最有效的方法,但这应该可行。如果它不起作用,请告诉我。

    【讨论】:

    • 有问题的数据的解决方案的执行时间和数量是多少?
    • @ipj 运行时不适用于此类问题。要计算所有排列,需要 20!/12!操作。最重要的是,您还需要找到每个排列的总和,这需要线性时间。不过,我还没有在几秒钟内测试过运行时。希望这会有所帮助。
    • 谢谢!我不知道这个“itertools”,我会试一试。
    【解决方案3】:

    最简单的方法是使用itertools 和列表理解:

    import itertools
    
    # Inputs
    N = [0.5, 0.8, 1, 1.5, 2, 2.5, 3, 5, 7.5, 10, 12.5, 15, 17.5, 20, 25, 30]
    target = 20
    
    # Operation
    list_sum=[item for item in itertools.product(N, repeat=8) if sum(item)==target]
    
    # Printing
    print(list_sum)
    

    要查看得到的输出类型,如果只使用 2 个元素,则会得到以下输出:

    [(2.5, 17.5), (5, 15), (7.5, 12.5), (10, 10), (12.5, 7.5), (15, 5), (17.5, 2.5)]
    

    这不是最快的解决方案(递归函数更佳)。请注意,我提供了所有可能的排列,因为我假设元素的顺序可能很重要。否则,可以减少执行时间。

    【讨论】:

    • 有问题的数据解决方案的执行时间和数量是多少?
    • 谢谢!我不知道这个模块,它看起来很有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-04
    • 1970-01-01
    • 1970-01-01
    • 2022-07-23
    • 2021-12-04
    • 1970-01-01
    相关资源
    最近更新 更多