【问题标题】:How to collect results of recursive backtracking?如何收集递归回溯的结果?
【发布时间】:2019-08-06 17:41:13
【问题描述】:

我正在自学递归回溯。对于骰子求和问题,我不知道如何优雅地收集结果。

作为参考,这是我的代码,它只打印任何符合条件的骰子。理想情况下,我想改变它,而不是打印输出,我可以建立一个列表,列出那些选择的骰子并返回它。

下面是不符合我要求的代码

def dice_sum(num_dice: int, target_sum: int) -> None:
    dice_sum_helper(num_dice, target_sum, [])


def dice_sum_helper(num_dice: int, target_sum: int, chosen: List[int]) -> None:
    if num_dice == 0 and sum(chosen) == target_sum:
        print(chosen)
    elif num_dice == 0:
        pass
    else:
        for i in range(1, 7):
            chosen.append(i)
            dice_sum_helper(num_dice - 1, target_sum, chosen)
            chosen.pop()

我希望它做这样的事情

from typing import List
DiceResult = List[List[int]]


def dice_sum(num_dice: int, target_sum: int) -> DiceResult:
    return dice_sum_helper(num_dice, target_sum, [])


def dice_sum_helper(num_dice: int, target_sum: int, chosen: List[int]) -> DiceResult:
    if num_dice == 0 and sum(chosen) == target_sum:
        # Return the value that meets the constraints
        return chosen
    elif num_dice == 0:
        pass
    else:
        for i in range(1, 7):
            chosen.append(i)
            # Return the result of my recursive call and build the list of lists?
            result = dice_sum_helper(num_dice - 1, target_sum, chosen)
            return result.append(result)
            # End of that logic
            chosen.pop()

我更多的是寻找要使用的理论或模式,而不是确切的代码。如果不使用外部列表,我无法完全获取代码来收集和附加每个结果。

【问题讨论】:

    标签: python recursion backtracking recursive-backtracking


    【解决方案1】:

    您可以传递一个“结果”列表来存储结果:

    from typing import List
    DiceResult = List[List[int]]
    
    
    def dice_sum(num_dice: int, target_sum: int) -> DiceResult:
        results = []
        dice_sum_helper(num_dice, target_sum, [], results)
        return results
    
    
    def dice_sum_helper(num_dice: int, target_sum: int, chosen: List[int], results: DiceResult):
        if num_dice == 0 and sum(chosen) == target_sum:
            # Store the value that meets the constraints
            results.append(chosen.copy())
        elif num_dice == 0:
            pass
        else:
            for i in range(1, 7):
                chosen.append(i)
                dice_sum_helper(num_dice - 1, target_sum, chosen, results)
                chosen.pop()
    

    请注意,如果订购无关紧要,这将返回许多重复项。您可能想研究更改它以计算每次递归的较小目标总和,并以某种方式对其进行记忆,从而节省大量工作。另见例如Calculate the number of ways to roll a certain number

    【讨论】:

    • 我可能做错了什么,当我尝试使用添加到列表参数方法获取结果时,我得到了一个空列表列表。我试过修改我的代码并直接粘贴你的代码。使用 2 个骰子和 9 的 target_sum,我得到结果(左侧)与预期(右侧)断言 [[], [], [], []] == [[3, 6], [4, 5] , [5, 4], [6, 3]]
    • 抱歉,我在存储结果时错过了copy()(否则通过引用传递,您的pop()ing 会在程序继续运行时删除结果:)),现在已修复。
    【解决方案2】:

    您可以使用yieldyield from 从您的函数返回结果:

    from typing import List
    
    def dice_sum(num_dice: int, target_sum: int) -> None:
        yield from dice_sum_helper(num_dice, target_sum, [])
    
    
    def dice_sum_helper(num_dice: int, target_sum: int, chosen: List[int]) -> None:
        if num_dice == 0 and sum(chosen) == target_sum:
            yield chosen[:]
        elif num_dice == 0:
            pass
        else:
            for i in range(1, 7):
                chosen.append(i)
                yield from dice_sum_helper(num_dice - 1, target_sum, chosen)
                chosen.pop()
    
    # you can store the results e.g. to list: 
    # results = list(dice_sum(3, 12))
    
    for dices in dice_sum(3, 12):
        for d in dices:
            print('{: ^4}'.format(d), end='|')
        print()
    

    打印:

     1  | 5  | 6  |
     1  | 6  | 5  |
     2  | 4  | 6  |
     2  | 5  | 5  |
     2  | 6  | 4  |
     3  | 3  | 6  |
     3  | 4  | 5  |
     3  | 5  | 4  |
     3  | 6  | 3  |
     4  | 2  | 6  |
     4  | 3  | 5  |
     4  | 4  | 4  |
     4  | 5  | 3  |
     4  | 6  | 2  |
     5  | 1  | 6  |
     5  | 2  | 5  |
     5  | 3  | 4  |
     5  | 4  | 3  |
     5  | 5  | 2  |
     5  | 6  | 1  |
     6  | 1  | 5  |
     6  | 2  | 4  |
     6  | 3  | 3  |
     6  | 4  | 2  |
     6  | 5  | 1  |
    

    【讨论】:

      【解决方案3】:

      对于优雅的既定目标,我会采用更简单的设计,没有辅助函数,也没有传递额外的参数:

      def dice_sum(num_dice, target_sum):
          solutions = []
      
          for i in range(1, 7):
              if target_sum < i:
                  continue
      
              if target_sum == i:
                  if num_dice == 1:
                      solutions.append([i])
      
                  continue
      
              for solution in dice_sum(num_dice - 1, target_sum - i):
                  solutions.append([i] + solution)
      
          return solutions
      
      print(dice_sum(3, 5))
      

      输出

      > python3 test.py
      [[1, 1, 3], [1, 2, 2], [1, 3, 1], [2, 1, 2], [2, 2, 1], [3, 1, 1]]
      >
      

      为了提高效率,您可以添加:

      if target_sum - i < num_dice - 1:
                  continue
      

      在内部 for 循环之前避免递归,如果没有希望。

      【讨论】:

      【解决方案4】:

      具有早期中止条件的递归生成器,因此关于掷骰顺序,您不会得到重复的解决方案。 IE。我认为您的骰子无法识别,因此数字的顺序无关紧要。

      def dice_sum (num_dice, target_sum, start=1):
          if num_dice == 1 and 0 < target_sum < 7:
              yield (target_sum, )
              return
          if num_dice * 6 < target_sum or num_dice > target_sum:
              return
          for i in xrange(start, 7):
              for option in dice_sum(num_dice - 1, target_sum - i, i):
                  if i > option[0]:
                      return
                  yield (i, ) + option
      
      >>> tuple(dice_sum(3, 12))
      ((1, 5, 6), (2, 4, 6), (2, 5, 5), (3, 3, 6), (3, 4, 5), (4, 4, 4))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-12-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-18
        相关资源
        最近更新 更多