The Rat Eats The Cheese
May 14, 2019
We represent the maze as a vector of vectors, upside down; here is our function for making a random maze:
(define (make-cheese n fill) (define (rand-row) (let ((row (make-vector n 0))) (do ((k 0 (+ k 1))) ((= k n) row) (when (< (random 1.0) fill) (vector-set! row k 1))))) (set! nrows n) (set! ncols n) (let ((rows (make-vector n #f))) (do ((k 0 (+ k 1))) ((= k n)) (vector-set! rows k (rand-row))) rows))
Now we make a maze:
(define nrows #f) (define ncols #f) (define cheese (make-cheese 20 0.15)) > (display cheese) #(#(0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0) #(0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) #(0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0) #(0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0) #(0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0) #(1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0) #(0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) #(0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0) #(0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0) #(0 1 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 1 0 0) #(0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1 0 0) #(0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0) #(0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1 0 0 1) #(0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0) #(1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 1 0 0) #(0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0) #(0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1) #(0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0) #(0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1) #(0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0)
Then the rat moves through the maze, eating cheese as he finds it; we memoize the function because otherwise the double recursion would cause exponential runtime:
(define-memoized (rat r c) (if (or (= r nrows) (= c ncols)) 0 (+ (vector-ref (vector-ref cheese r) c) (max (rat (+ r 1) c) (rat r (+ c 1))))))
Here’s a sample, showing the timing:
> (time (rat 0 0)) (time (rat 0 ...)) no collections 0 ms elapsed cpu time 0 ms elapsed real time 48 bytes allocated 9
You can run the program at https://ideone.com/sbMDXx.
@programmingpraxis: The read link does not work. Having read the text at the run link, am I correct in assuming that the total amount of cheese is the amount consumed in all of the different permutations of paths?
@Steve: Fixed link. Thank you for pointing that out. You are trying to find the maximal amount of cheese that can be consumed on any single route.
In Python. A table is used with the cheese that can be eaten from the (row, col) position. The table is filled from the top right to the bottom left. The value at the bottom left is the solution.
It is not necessary to keep the whole table. Only the last 2 rows have to be kept.
def cheese(grid):
Only te last 2 rows of the table need to be kept.
And is not necessary to keep the last line.
@programmingpraxis: If I move one position at a time, either to the right or downward, in your 20×20 matrix, I can achieve a count of at least 14. However, your result was 9.
Am I missing something?
Thanks, Steve
Klong version
Here’s a Haskell version. I like graphs, so I’ve cast it as a shortest weighted path problem.
Haskell:
foldl (\b a -> let r = 0 : zipWith (+) (zipWith max b r) a in tail r)
(map (const 0) $ head ys) ys
[2,2,3,5,5,7,7,7,7,7,8,8,8,9,9,9,12,14,15,16]
it’s also in the Project Euler, I think.
Here’s a dynamic programming solution in Python. The code was slightly simplified by padding the table with a row of zeros on the bottom and column of zeros on the left. The code was slightly complicated by only retaining two rows of the table.
Output: