From: Raffa Date: 2008-01-30T23:34:57+09:00 Subject: Re: Making Change (#154) - can process inputs like "make_change 1_000_000_000_000 4 3 7 6 85 98 54 22 3423 34 509 435 243 345" in < 0.2 sec. (in super no- optimization / ultra-verbose mode) - no use of recursion & stack - no use of incremental/decremental operations - it is not necessary to sort the input array of coins (when the input array tends to infinite this is important) - possibility to add coins after the processing don't affect the previous memoizing array at all (ony incremental change). Optimal for store/retrieve table. - it is simple do it manual (pencil / notebook), cause all to do is to take note of some x/y integer part and rest . - (this code only generates the table as discussed in "manual algorithm", useful only to perceive the relative speed/scalability of the solution. Don't look at it! Full of boring shit) manual algorithm: take pencil / notebook step 1: trace a x/y axis, report the amount y, coins on x, eg make_change 6, [4, 3, 1] becomes: 6| | | | | ------------------- 4 3 1 step 2: in every matrix(x,y) cell report the integer part and the rest of the division between the corrispondent amount/coin, in the format ",. If there is a rest, push it in the y axis, too: 6| 1,2|2,0|6,0 2| ------------------- 4 |3 | 1 '2' is, obviously, the rest between 6 and 4 step 3: proceed as step 2, until there are no rests to process. Where the integer part of the division is 0, don't consider it, puts a "-" char (nil): 6|1,2|2,0|6,0 2| - | - |2,0 ------------------- 4 | 3 | 1 ok, finished. This is the result table. This has to be read in this way: - The number of possible solutions are the number of cells that ands with ",0" : 2,0 at matrix(0,1); 6,0 at matrix(0,2); 2,0 at matrix (1,2) - If the cell containinng ",0" is a level 1 cell [ matrix(0,x)] this is also THE possible solution, and the formula is * (3 *2; 6 * 1). represents also, obviously, the number of coin, number_of_coins that will be used to compute the obtimal solution - if the ",0" is not a level 1 cell, this is only a part of the solution, exactly the last "node", SURELY the last node of a solution. In this case the rest in the y coordinate of the cell ('2' in the example, below 6, the rest of 6 and 4 ) has to be considered as an index 'linked' to a 'cell' with the identical rest. In the matrix(0,0) there is the cell "1,2". [Geralizing, when we have a "n,0" cell.value case, we have to now what amount we are processing, then use it to find a "?,n" cell]. We have to do this operation n times, until we 'parse' a cell with base ndex 0 [matrix(0,n)] (or we can sum the amounts till total==input amount). - at every intermediate step (previous point), we have to compute "integer_part * relative coin, so for the example: when we found the "2,0" cell (matrix (x,y) where x>0) we compute amount 2 of coin 1 + (find a cell with rest 2 (2 as the number of the vertical axys) -> "1,2" of matrix(0,0) ... + amount 1 of coin 4 the last "cell" is a top one then we can stop this computation. (or: the total 2*1 + 1*4 == 6[input value]). we can set to 'nil' the cell so processed. proceeding in this way all the possible 'goal' permutation are: - 6 * 1 - 2 * 1 + 1 * 4 - 2*3 (tree as tree are the 0-rest 'cells') the optimal solution is, obviously, the one with minor total amounts (the third one) that's all. ------------------------------------------------------- more complex example make_change 100, [20,15,9,7,5,6,4,3] step 1: trace x/y axis, put 100 on the y, coins on the y. divide 100 for every coin and fill the cells consequently, in the format integer_part,rest. Push every rest on the y axys | 100| 5,0|6,10|11,1|14,2|20,0|16,4|25,0|33,1 10 | | 1 | | 2 | | 4 | | 1 | | --------|----|----|----|----|----|----|--- 20| 15 |9 | 7 | 5 | 6 | 4 | 3 step 2: proceed in the same way of step 1: divide each rest on the y with the coin on the x and eventually push rests (if presents). Proceed until the table is full, not more elements on the y to process. Note: ultra-verbose / no-optimization 100| 5,0|6,10|11,1|14,2|20,0|16,4|25,0|33,1 10 | - | - |1,1 | 1,3| 2,0 | 1,4 | 2,2|3,1 1 | - |---------------------------------- 2 | |----------------------------------- 4 | |------------------------- |1,0|1,1 1 | ----------------------------------------- 1---------------------------------------------- 3-------------------------------------|1,0 4---------------------------------1,0 | 1,1 2------------------------------------------- 1------------------------------------------- 1-------------------------------------------- 1---------------------------------------------- --------|----|----|----|----|----|----|--- 20|15 | 9 | 7 | 5 | 6 | 4 | 3 Ok done, by hand, in a few secs, simply not? Now, let's query this table Q- how many solutions has this problem? R- 7, as 7 are the cells ",O" Q- let's go, candidate! list me these 8 kids R- then... there are 3 single-step solutions (4 top-level ",0"s cells): 1)- 5 * 20c ---------------------------- total of 5 coins 2)- 20 * 5c----------------------------- total of 20 coins 3)- 25 * 4c--------------------------- total of 25 coins (till now the best solution is the first) then we have a "2,0" at matrix (1,4) so we compute 2*5c (5 is the coin on x axys). This is not a top-level cell then we see at the vertical axys value (10) and we use it to find a cell with ",10" value. Here it is at matrix(0,1) "6,10", that becomes 6*15c. This is a top-level cell, then we can end this path. This solution is, then: 2*5c + 6*15. (and in effect 2*5+6*15) =100. So: 4)- 2*5c + 6*15 -------------------- total of 8 coins Similarly we proceed with the "1,0" cell on (6,4): 1*4 + .......... the y axys value for it is 4 the we have to find a cell ",4", ok proceed ops!! we have two cells with ",4"!!! the "1,4" at matrix(1,5) and the "16,4" at (0,5). Which one we can take? mumbe mumble. let's try with the toplevel cell (toplevel=stop computation). Then the result, adding the previous computation is 16*6 + 1*4... =100 bingo! This is a solution 5) 16*6+1*4 ----------------------------- total of 17 coins and following the other path? Not being a toplevel cell we compute the partial (1*6) curr tot partial: 1*4 + 1*6 and we watch at the y axys of "1,4", "10", and we use this number to find a cell ",10", ok "6,10" at matrix(0,1) [toplevel] then we add this partial (6*15) and we have the solution 6) 1*4c + 1*6c + 6*15c--------------------- total of 8 coins then we proceed Q- But here we have a complication, it's not linear, you have said: no stack, and that we can nil-lize the cells considered, here... R- The solution is absolutely linear, it's true that there can be more occurrences (",y") cells for a y given but let's see the entire y axys amounts (where we push the rests). You see that the "4" case compares 2 times, and both with cells "1,0", then is true: every computed cell can be set to null, every solution is on the board and stop. let's proceed in speedy-mode with the last solution. we've just popped the 1,0 at matrix(8,6) then remains the one at matrix(7,7), (y axys amount:3) 1*3 + ... lookup the "1,3" at matrix(1,3) [not topleve, y axys amount:10] 1*7 + lookup for 10, found at matrix (0,1) 6*15 [top level! stop computation] 7)- 1*3c + 1*7c + 6*15c ----------------------- total of 8 coins __________________END________________ the best solution is the number 1: 5*20 ------------------------------------------------------------------------------------ Consideration & optimization - This solution can be terribly improved, at least for memory concerns. (eg if a division give me a rest x just computed and with all values 'nil' we can erase it. See the 1-rest in the example). - I think the obtimal solution is to use an hash for the rests for simple lookups, the value can be an array of occurrences. Or... - The code below is terrible, full of variables not used and so on. The purpose (for me) is to generate the table and stop. (Retrieving the solutions is a stupid affair, and i want to think at the best data structure for store the memoizing array [matrix is useful only for presentation], before the last step) -------------------------------------------------------- CODE: Node = Struct.new(:amount, :cost, :rest,:coin) def make_change(amount, coins = [25, 10, 5, 1]) return [ ] if amount.zero? #coins = coins.sort_by { |coin| -coin } #@r =Hash.new # rests (indexed) queue=[amount] j=0 @matrix=Array.new @row=Array.new @amounts=Array.new @results=Array.new @rests=Hash.new loop do amount = queue.shift @amounts << amount return if amount == nil coins.each_with_index do |coin, idxCoin| cost=amount/coin #cost=nil if amount=0 rest= amount%coin #puts "#{amount}-#{coin}-#{rest}" row="#{cost},#{rest}" #row="#{cost},#{coin}" row = nil if cost==0 #if cost==0 # @row << nil #else # @row<<"#{cost},#{rest}" #end @row << row #aResult= #@results<< "#{cost}*#{coin}" if cost >0 and rest ==0 #Node.new(amount,cost,0,coin) if cost > 0 and rest ==0 #@rests[rest] ="#{cost}*#{coin}" if cost >0 and rest >0 #@r[rest] += Node.new(cost,coin) #print "- #{rest}" queue << rest if cost > 0 and rest >0 #puts "(#{cost}-#{rest})" # p queue.size end @matrix << @row @row=[] #return if queue.size> 50 end end if __FILE__ == $PROGRAM_NAME amount, *coins = ARGV.map { |n| Integer(n) } abort "Usage: #{$PROGRAM_NAME} AMOUNT [COIN[ COIN]...]" unless amount coins.empty? ? make_change(amount) : make_change(amount, coins) @matrix.each_with_index {|x,i| puts "*amount:#{@amounts[i]}*" + x.inspect} #puts @results.inspect #puts @rests.inspect end __END__