Search code examples
pythonrecursionbacktrackingrecursive-backtracking

How to collect results of recursive backtracking?


I'm teaching myself recursive backtracking. For a dice summing problem I can't figure out how to elegantly collect the results.

For reference here's my code that just prints any dice roll which meets the criteria. Ideally I want to change it so instead of printing the output, I can build up a list of those chosen dice and return it.

Below here is code that does not do what I want

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()

Instead I want it to do something like this

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()

I'm looking more for the theory or pattern to use than the exact code. I can't quite get the code to collect and append each result without using an external list working.


Solution

  • You can pass a 'results' list in which to store the results:

    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()
    

    Note this will be returning many duplicates, if ordering does not matter. You may want to investigate changing this to calculate a smaller target sum each recursion, and memoizing that in some way so it save on a lot of work. See also e.g. Calculate the number of ways to roll a certain number