2

I wanna find all combinations for coin change. 1, 2, 5, 10, 20, 50, 100 und 200. ( 1 cent , 2cent ..) if coin is over 500 (5 euro) , it should give -1.My code works perfectly with those testcases: numOfSplits 10 (11) numOfSplits 20 (41) numOfSplits 100 (4563) . When i try with test cases numOfSplits 200 or 500 , it will gives C stack overflow error. How can i make better my code ?

numOfSplits :: Integer -> Integer  
numOfSplits a  
    | (abs a) > 500 = -1  
    | (abs a) == 0 = 0  
    | otherwise = intzahler (makeChange [200,100,50,20,10,5,2,1] (abs a) 200)  

intzahler :: [[Integer]] -> Integer  
intzahler array  
    | array == [] = 0  
    | otherwise = 1 + intzahler (tail array)  

makeChange :: [Integer] -> Integer -> Integer -> [[Integer]]  
makeChange coins amount maxCoins  
    | amount < 0  = []  
    | amount == 0 = [[]]  
    | null coins  = []  
    | amount `div` maximum coins > maxCoins = [] -- optimisation  
    | amount > 0  =  
        do x <- coins  
           xs <- makeChange (filter (<= x) coins)  
                            (amount - x)  
                            (maxCoins - 1)  
           guard (genericLength (x:xs) <= maxCoins)  
           return (x:xs)

I changed my code to this code and i got no longer stack overflow error but now my code works so slowly . Example : for numOfSplits 500 , it makes longer than 30 minutes how can i make that quicklier?

     numOfSplits :: Integer -> Integer  
numOfSplits a
    | (abs a) > 500 = -1
    | (abs a) == 0 = 0
    | otherwise = fromIntegral . length $ makeChange [200,100,50,20,10,5,2,1] (abs a) 

makeChange :: [Integer] -> Integer ->  [[Integer]]
makeChange coins amount 
   | amount < 0  = []
   | amount == 0 = [[]]
   | null coins  = []
   | amount > 0  =
        do x <- coins
          xs <- makeChange (filter (<= x) coins) (amount - x)
           return (x:xs)
Mert
  • 55
  • 8
  • 3
    Side note: using -1 for error is totally gross in the haskell world. Use `Maybe Integer` to do that (`Just x` for success, `Nothing` for failure). – luqui Nov 11 '14 at 00:26
  • Using Prelude.genericLength rather than intzahler _may_ help the compiler figure out a better execution path... – Erin Call Nov 11 '14 at 00:33
  • 1
    You are not expecting any lengths longer than the size of an `Int` (I should hope, that is very long), so I would recommend using `fromIntegral . length` instead of `genericLength`, both where you use `genericLength` and in place of `intzahler`. `genericLength` is really for when you have a strange `Num` instance like lazy naturals. It is not tail-recursive, so it's quite bad for strict data types like `Int` and `Integer`, and could certainly cause a stack overflow. – luqui Nov 11 '14 at 00:37
  • @luqui, there's actually a disgusting pair of rules for `genericLength` with `Int` and `Integer`, but I agree with you that it should be avoided. – dfeuer Nov 11 '14 at 00:50
  • Pattern matching would be a good thing to use. Also, why are you taking absolute values? Also also, `div` is fairly expensive (especially with GHC's current native code generator), so you should avoid it. Multiply both sides by `maximum coins` to turn the thing into multiplication. – dfeuer Nov 11 '14 at 00:51
  • @luqui can you make these changes ? because im really beginner and cant find how can i change my code . – Mert Nov 11 '14 at 01:03
  • Delete the entire body of the intzahler function, except for the type signature, and replace it with `intzahler = fromIntegral . length`. – user2407038 Nov 11 '14 at 01:08
  • @Mert - What version of GHC are you using? With 7.8.3 there doesn't seem to be any practical difference between using `length` and `intzahler`. – ErikR Nov 11 '14 at 03:30
  • @Mert, on the whole this code is actually pretty well-written. I'm surprised to hear that you're a beginner. – luqui Nov 11 '14 at 05:15
  • To add to what @luqui said in the second comment: I'd also say use `fromIntegral . length`, and also because `length` (unlike `genericLength`) uses unboxed `Int` type (`Int#`) which is.. I don't want to say faster (regardless if true), but optimized. (It probably is slightly faster though). – MasterMastic Nov 11 '14 at 10:39

2 Answers2

7

Solving this problem quickly, and thus avoiding exhausting the resources of the computer, such as the stack, requires that you reuse the partial answers that were previously computed.

Let's pretend we wanted to solve a similar problem where we seek to find out how many ways we can make 15 cents using only 1, 2, or 5 cent coins. We have two concerns we will tackle - the first is solving the problem correctly. The second is solving the problem quickly.

Solving the problem correctly

To solve the problem correctly, we need to avoid re-counting combinations of coins that we have already counted. For example, we can make 15 cents by:

  • 2 five cent coins, 5 one cent coin
  • 1 five cent coin, 5 one cent coins, 1 five cent coin
  • 2 one cent coins, 1 five cent coin, 2 one cent coins, 1 five cent coins, 1 one cent coin

All of the above examples use the same combination of coins. They all use 2 five cent coins and 5 one cent coins, counted out in different orders.

We can avoid the above problem by always issuing our coins in the same order. This suggests a simple algorithm for how many ways we can make a certain amount of change from a list of coins. We can either use one of the first type of coin, or we can commit to never using that type of coin again.

waysToMake 0 _             = 1
waysToMake x _     | x < 0 = 0 
waysToMake x []            = 0
waysToMake x (c:cs)        = waysToMake (x-c) (c:cs) + waysToMake x cs

The preceding cases cover the boundary conditions. Assuming there are no problematic zero or negatively valued coins, there is 1 way to make 0. There are 0 ways to make a negative (< 0) amount of change. There are no ways to make change if you have no types of coins to make change with.

Let's see what happens if we try to evaluate waysToMake 15 [1,2,5]. We'll evaluate each waysToMake each step to keep things short.

waysToMake 15 [1,2,5]

waysToMake 14 [1,2,5] + waysToMake 15 [2,5]

waysToMake 13 [1,2,5] + waysToMake 14 [2,5] + waysToMake 13 [2,5] + waysToMake 15 [5]

waysToMake 12 [1,2,5] + waysToMake 13 [2,5] + waysToMake 12 [2,5] + waysToMake 14 [5]
+ waysToMake 11 [2,5] + waysToMake 13 [5] + waysToMake 10 [5] + waysToMake 15 []

waysToMake 11 [1,2,5] + waysToMake 12 [2,5] + waysToMake 11 [2,5] + waysToMake 13 [5]
+ waysToMake 10 [2,5] + waysToMake 12 [5] + waysToMake 9 [5] + waysToMake 14 []
+ waysToMake 9 [2,5] + waysToMake 11 [5] + waysToMake 8 [5] + waysToMake 13 []
+ waysToMake 5 [5] + waysToMake 10 [] + 0

The first three steps don't seem too suspicious, but we have already encountered waysToMake 13 [2,5] twice. In the fourth step we see waysToMake 12 [2, 5], waysToMake 11 [2, 5], waysToMake 13 [5] all of which we have seen before. We can see that we are going to be repeating most of the other expressions we generate, which will themselves generate expressions that repeat expressions. Ugh; my limited mental computer is already complaining that there's too much work to do. We could look for a better order to use the coins in (there is one) but it would still repeat subproblems, which would themselves repeat subproblems, etc.

Solving the problem quickly

There's a faster way to do this. Each step, we add together the numbers from using fewer of a coin and not using that coin. Let's make a table, and record each result as we calculate it. Each step, we will need the number from further left in the table (use one of the first coin) and one down the table (don't use any of the first coin). We'll eventually explore the whole table. We can start by filling in the numbers on the left and bottom edges covered by the boundary conditions.

Coins\Change  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5]       1  
  [2,5]       1 
    [5]       1 
     []       1 0 0 0 0 0 0 0 0 0  0  0  0  0  0  0

Now we'll add all the numbers that can be computed from the numbers already in the table. Using a 5 cent coin requires the number 5 spots to the left, as well as the number one spot down.

Coins\Change  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5]       1  
  [2,5]       1 
    [5]       1 0 0 0 0 1
     []       1 0 0 0 0 0 0 0 0 0  0  0  0  0  0  0

Using a 2 cent coin requires the number 2 sports to the left, as well as the number one spot down.

Coins\Change  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5]       1  
  [2,5]       1 0 1
    [5]       1 0 0 0 0 1 0 0 0 0  1
     []       1 0 0 0 0 0 0 0 0 0  0  0  0  0  0  0

Using a 1 cent coin requires the number 1 spot to the left, as well as the number one spot down.

Coins\Change  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5]       1 1 
  [2,5]       1 0 1 0 1
    [5]       1 0 0 0 0 1 0 0 0 0  1  0  0  0  0  1
     []       1 0 0 0 0 0 0 0 0 0  0  0  0  0  0  0

We'll go one more step. We can see that in just another 13 simple steps we'll compute the number for 15 in the top row, and we'll be done.

Coins\Change  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5]       1 1 2 
  [2,5]       1 0 1 0 1 1 1
    [5]       1 0 0 0 0 1 0 0 0 0  1  0  0  0  0  1
     []       1 0 0 0 0 0 0 0 0 0  0  0  0  0  0  0

Here's the table after all the steps. My mental computer had no difficulty calculating that waysToMake 15 [1,2,5] = 18.

Coins\Change  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5]       1 1 2 2 3 4 5 6 7 8 10 11 13 14 16 18
  [2,5]       1 0 1 0 1 1 1 1 1 1  2  1  2  1  2  2
    [5]       1 0 0 0 0 1 0 0 0 0  1  0  0  0  0  1
     []       1 0 0 0 0 0 0 0 0 0  0  0  0  0  0  0

If we found a better order to use the coins in (there is one) we wouldn't need to fill out all of the table, but it'd be approximately the same amount of work.

We can make a table like this in Haskell using an Array from Data.Array in the array package.

The general plan for using a table is going to be a - make a table defined in terms of our function waysToMake. Wherever waysToMake would recurse into itself, look the result up in the table instead. We have two wrinkles to deal with on the way.

The first wrinkle is that an Array requires that the index into the array be an instance of Ix. Our lists of coins don't make good array indexes. Instead, we can replace the list of coins with the number of coins we have skipped. 0 for the first row, 1 for the second, and the length of the list of coins for the last row.

The second wrinkle is that we want to look beyond the edge of the table. We could define a special lookup procedure to fill the part outside the table with 0, we can change the code so it never looks outside the table, or we could make an extra-large table that's twice as big. I'm going to skip all of those routes, and make checking that the value falls in the table part of the table's responsibility.

waysToMake x coins = waysToMake' (x,0)
    where
        tabled = tableRange ((0,0),(x,length coins)) waysToMake'
        waysToMake' (n, s) = waysToMake'' n (drop s coins)
            where                    
                waysToMake'' 0  _              = 1
                waysToMake'' n  _     | n <  0 = 0
                waysToMake'' n []              = 0
                waysToMake'' n (c:cs)          = tabled (n-c, s) + tabled (n, s+1)

tableRange makes a function that memorizes its results within some bounds. It builds an Array holding the lazily-evaluated results of a function within those bounds. The function it returns checks to see if the argument falls within the range of the bounds, and looks up the result from the table if it does, otherwise it asks the original function directly.

tableRange :: Ix a => (a, a) -> (a -> b) -> (a -> b)
tableRange bounds f = lookup
    where
        lookup x = if inRange bounds x then table ! x else f x
        table = makeArray bounds f

makeArray is a convenience function that makes an array containing the supplied function f applied to each index in the supplied bounds.

makeArray :: Ix i => (i, i) -> (i -> e) -> Array i e
makeArray bounds f = listArray bounds . map f $ range bounds

Our code now runs almost instantly, even for much larger problems like waysToMake 10000 [200,100,50,20,10,5,2,1].

We could go on to discuss how to make this "tabling" or "memoing" or "memoizing" or "dynamic programming" code for recursive functions generic, but I think that discussion would belong to a different question. It's a good topic if you want to learn about a very practical use of fixed points of functions.

Cirdec
  • 24,019
  • 2
  • 50
  • 100
0

Are you compiling your program or just running it in it ghci? Also what platform are you on?

numOfSplits 200 should only take around 6 secs when compiled.

Here is your code on ideaone.com:

https://ideone.com/sBWsvP

and for an input of 180 it runs in less than 5 seconds (which is the maximum running time allowed by that site.)

As Andrew Lorente pointed out, your intzahler function is the same as genericLength or even length, although in this case it doesn't seem to make that much of a difference.

Community
  • 1
  • 1
ErikR
  • 51,541
  • 9
  • 73
  • 124
  • That still sounds rather slow to me. – dfeuer Nov 11 '14 at 01:28
  • 1
    yeah, but the algorithm isn't using any memoization or dynamic programming. My point of running it on ideaone is to show that it shouldn't overflow the stack, and so I suspect there is something wrong with the OP's system. – ErikR Nov 11 '14 at 02:25
  • It doesn't overflow the stack on my computer either, but I see no reason why the algorithm shouldn't legitimately overflow the stack on some system. – Cirdec Nov 11 '14 at 03:11