I\'m looking for an efficient polynomial-time solution to the following problem:
Implement a recursive function node x y for calculating the (x,y)-th number in a num
First of all, sorry if this is long. I wanted to explain the step by step thought process.
To start off with, you need one crucial fact: You can represent the "answer" at each "index" by a list of paths. For all the zeros, this is [[]]
, for your base case it is [[1]]
, and for example, for 0,2
it is [[6,1,1],[6,1,1],[6,1,1]]
. This may seem like some redundancy, but it simplifies things down the road. Then, extracting the answer is head . head
if the list is non empty, or const 0
if it is.
This is very useful because you can store the answer as a list of rows (the first row would be '[[1]], [], [] ...
) and the results of any given row depend only on the previous row.
Secondly, this problem is symmetrical. This is pretty obvious.
The first thing we will do will mirror the definition of fib
very closely:
type Path = [[Integer]]
triangle' :: [[Path]]
triangle' = ([[1]] : repeat []) : map f triangle'
We know this must be close to correct, since the 2nd row will depend on the first row only, the third on the 2nd only, etc. So the result will be
([[1]] : repeat []) : f ([[1]] : repeat []) : f ....
Now we just need to know what f
is. Firstly, its type: [Path] -> [Path]
. Quite simply, given the previous row, return the next row.
Now you may see another problem arising. Each invocation of f
needs to know how many columns in the current row. We could actually count the length of non-null elements in the previous row, but it is simpler to pass the parameter directly, so we change map f triangle'
to zipWith f [1..] triangle'
, giving f
the type Int -> [Path] -> [Path]
.
f
needs to handle one special case and one general case. The special case is x=0
, in this case we simply treat the x+1,y-1
and x-1,y-1
recursions the same, and otherwise is identical to gn
. Lets make two functions, g0
and gn
which handle these two cases.
The actually computation of gn
is easy. We know for some x
we need the elements x-1, x, x+1
of the previous row. So if we drop x-1
elements before giving the previous row to the x
th invocation of gn
, gn
can just take the first 3 elements and it will have what it needs. We write this as follows:
f :: Int -> [Path] -> [Path]
f n ps = g0 ps : map (gn . flip drop ps) [0..n-1] ++ repeat []
The repeat []
at the end should be obvious: for indices outside the triangle, the result is 0
.
Now writing g0
and gs
is really quite simple:
g0 :: [Path] -> Path
g0 (a:b:_) = map (s:) q
where
s = sum . concat $ q
q = b ++ a ++ b
gn :: [Path] -> Path
gn (a:b:c:_) = map (s:) q
where
s = sum . concat $ q
q = a ++ b ++ c
On my machine this version is about 3-4 times faster than the fastest version I could write with normal recursion and memoization.
The rest is just printing or pulling out the number you want.
triangle :: Int -> Int -> Integer
triangle x y = case (triangle' !! y) !! (abs x) of
[] -> 0
xs -> head $ head xs
triList :: Int -> Int -> Path
triList x y = (triangle' !! y) !! (abs x)
printTri :: Int -> Int -> IO ()
printTri width height =
putStrLn $ unlines $ map unwords
[[ p $ triangle x y | x <- [-x0..x0]] | y <- [0..height]]
where maxLen = length $ show $ triangle 0 height
x0 = width `div` 2
p = printf $ "%" ++ show maxLen ++ "d "