Monday, January 19, 2009

Erlang solution for Euler problem 15

In project Euler problem number 15 we are asked to determine the number of routes (without backtracking) from corner to corner in a 20 x 20 grid.

My first attempt to solve this (see code for approach A below) used a straight-forward recursive brute-force approach based on the observation the the number routes R to get from corner to corner in a rectange of size (X,Y) obeys the following relation: R(X,Y) = R(X-1,Y) + R(X,Y-1) with the boundary conditions R(X,0) = 1 and R(0,Y) = 1.

The program for approach A produces the correct answers. However, the run-time increased so rapidly with the problem size that it was hopeless to compute the answer for a 20 x 20 grid within a minute. The following shows the graph of the run-time versus the grid size.
My second attempt (see code for approach B below) is based on the observation that approach A ends up computing the answer for R(X,Y) not just once but many times during the recursion. To avoid that, we store the answer for R(X,Y) in a dictionary the first time it is computed. If we need the answer again, we retrieve the answer from the dictionary thereby avoiding another deep recursion. This approach computed R(20,20) in less than 2 milliseconds on my Apple MacBook Pro.

P.S.: After reading the notes for the problem, I realized there is a much more elegant way to solve this problem. I won't spoil it by giving that answer away.

Code for approach A:

%% Project Euler, problem 15
%%
%% Find number of corner-to-corner routes (without backtracking) in a 20x20 grid

-module(euler15a).
-author('Cayle Spandon').

-export([solve/0, solve/1, routes_in_grid/2]).

routes_in_grid(X, Y) ->
if
(X == 0) or (Y == 0) ->
1;
true ->
routes_in_grid(X - 1, Y) + routes_in_grid(X, Y - 1)
end.

solve() ->
solve(20).

solve(N) ->
routes_in_grid(N, N).


Code for approach B:

%% Project Euler, problem 15
%%
%% Find number of corner-to-corner routes (without backtracking) in a 20x20 grid

-module(euler15b).
-author('Cayle Spandon').

-export([solve/0, solve/1, routes_in_grid/2]).

routes_in_grid(X, Y) ->
Solutions = dict:new(),
{Routes, _} = routes_in_grid(X, Y, Solutions),
Routes.

routes_in_grid(0, _Y, Solutions) ->
{1, Solutions};

routes_in_grid(_X, 0, Solutions) ->
{1, Solutions};

routes_in_grid(X, Y, Solutions) ->
case dict:find({X, Y}, Solutions) of
{ok, Routes} ->
{Routes, Solutions};
error ->
{Routes1, Solutions1} = routes_in_grid(X - 1, Y, Solutions),
{Routes2, Solutions2} = routes_in_grid(X, Y - 1, Solutions1),
NewRoutes = Routes1 + Routes2,
NewSolutions = dict:store({X, Y}, NewRoutes, Solutions2),
{NewRoutes, NewSolutions}
end.

solve() ->
solve(20).

solve(N) ->
routes_in_grid(N, N).

No comments: