0

I'm practicing the concepts of Dynamic Programming (recursion is not my strong suit). I was wondering how my piece of code may be improved so that I can avoid a stack overflow.

Anything helps, thanks!

def coinFlipping(n):
    """
    For n amount of change, return the minimal amount of currency in coins.

    Top-Down Approach (Memoization): Memo dictionary stores already solved
    subproblems to reuse and avoid recomputing. It's essentially recursion 
    but optimized to avoid recomputing results. 
    """
    COINS = {
        "penny": 0.01,
        "nickel": 0.05,
        "dime": 0.10,
        "quarter": 0.25,
        "dollar": 1.00
    }

    memo = {}

    def __coinFlipping(n):

        if n not in memo:
            # Base case (if n is equal to 0.01, 0.05, 0.10, 0.25..)
            if n in COINS.values():
                memo[n] = 1
            else:
                results = []
                coins = (coin for coin in COINS.values() if coin < n)
                for coin in coins:
                    results.append(__coinFlipping(n - coin))
                memo[n] = 1 + min(results)
        else:
            return memo[n]         
    __coinFlipping(n)
    print(memo[n])

coinFlipping(10)

I obtained the following error:

File "/mnt/d/programs/algorithms/dynamicProgramming.py", line 45, in __coinFlipping
    results.append(__coinFlipping(n - coin))
  File "/mnt/d/programs/algorithms/dynamicProgramming.py", line 45, in __coinFlipping
    results.append(__coinFlipping(n - coin))
  File "/mnt/d/programs/algorithms/dynamicProgramming.py", line 45, in __coinFlipping
    results.append(__coinFlipping(n - coin))
  [Previous line repeated 993 more times]
  File "/mnt/d/programs/algorithms/dynamicProgramming.py", line 44, in __coinFlipping
    for coin in coins:
  File "/mnt/d/programs/algorithms/dynamicProgramming.py", line 43, in <genexpr>
    coins = (coin for coin in COINS.values() if coin < n)
RecursionError: maximum recursion depth exceeded in comparison
Will Ness
  • 70,110
  • 9
  • 98
  • 181
Alejandro Armas
  • 308
  • 1
  • 2
  • 10
  • Well, a closure like that is one method of memoization (this answers the “question” in the title - ensure titles relate to the *actual* issue). Except, fix the Too Much Recursion issue. – user2864740 Oct 28 '20 at 01:03
  • That said, using floating point values here is *problematic* due to the limited precision over these values. It would be beneficial to treat 0.05 as 5, 0.25 as 25, etc. – user2864740 Oct 28 '20 at 01:07
  • Wrt the floating point precision, this is **false (!!!)**, as an example: `0.01 + 0.01 + 0.10 == 0.12`. There may also be other logic errors, didn’t look — needs debugging details and focus, especially in the title. – user2864740 Oct 28 '20 at 01:21
  • `__coinFlipping` is not returning anything when there is a cache miss. A workaround could be to remove the `else` clause and always return `memo[n]` regardless of whether there was a cache hit or miss. – dannyadam Oct 28 '20 at 01:24

2 Answers2

1

Thank you @user2864740 and @dannyadam

I'm going to leave my final code for anyone viewing the post afterwards.

def coinFlipping(n):
    """
    For n amount of cents, return the minimal amount of currency in coins.

    Top-Down Approach (Memoization): Memo dictionary stores already solved
    subproblems to reuse and avoid recomputing. It's essentially recursion 
    but optimized to avoid recomputing results. 
    """
    COINS = {
        "dollar": 100,
        "quarter": 25,
        "dime": 10,
        "nickel": 5,
        "penny": 1
    }

    memo = {}

    def __coinFlipping(change):

        if change not in memo:
            # Base case (if n is equal to 0.01, 0.05, 0.10, 0.25..)
            if change in COINS.values():
                memo[change] = 1
            else:
                results = []
                coins = [coin for coin in COINS.values() if coin < change]
                for coin in coins:
                    results.append(__coinFlipping(change - coin))
                memo[change] = 1 + min(results)
        return memo[change]         
            
    __coinFlipping(n)
    print(memo[n])

coinFlipping(42)


After implementing this example, I'm starting to think it was a bad toy example for memoization. I could have just wrote a greedy algorithm to select the maximum valued coin at each step. e.g. subtract 25c from 42c and and 10c from 17c and so forth while incrementing a counter.

Alejandro Armas
  • 308
  • 1
  • 2
  • 10
0

user2864740 is spot on. Change to integers and the code runs.

Sai Sunku
  • 51
  • 7