I don't know if this is more idiomatic, but ever since I've seen an infinite list construction for the Fibonacci numbers, I've been in love with it, so here goes nothing.
pascalTriangle :: [[Integer]]
pascalTriangle = [1] : map newRow pascalTriangle
where newRow y = 1 : zipWith (+) y (tail y) ++ [1]
To understand this, perhaps first you should look at simpler examples, e.g. an infinite list of zeros,
zeros = 0 : zeros
or the list of natural numbers,
nats = 0 : map (+1) nats
or my favourite, the list of fibonacci numbers,
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
That being said...
The line
pascalList 1 = [[1], [1, 1]]
is unnecessary, because you've already specified pascalList 0
. The line
[([1] ++ pascalCoeff (last pList) ++ [1])]
is equivalent to
[[1] ++ pascalCoeff (last pList) ++ [1]]
which is equivalent to
[ 1 : pascalCoeff (last pList) ++ [1]]
Whenever possible, I try to use functions from the standard library instead of rolling my own using recursion, so for example instead of
pascalCoeff (x:y:ys) = (x+y) : pascalCoeff (y:ys)
pascalCoeff (x:[]) = []
I'd say that this function takes a list e.g. [1,2,3,4]
and does the following:
[1, 2, 3]
[2, 3, 4]
+ ---------
[3, 5, 7]
so it takes the tail
(all but the first element) of the list, and the init
(all but the last element) of the list, and adds them together element-wise.
There are built-in functions tail
, and init
that yield you these parts from a list, and luckily the function
zipWith (+)
does the adding part, so you can simply say
pascalCoeff y = zipWith (+) (init y) (tail y)
which is, due to the way zipWith
works, the same as
pascalCoeff y = zipWith (+) y (tail y)
Similarly,
listtoString [] = []
listtoString [x] = show x
listtoString (x:xs) = show x ++ " " ++ listtoString xs
could simply be
listToString = unwords . map show
And
justify :: Int -> [String] -> [String]
justify n (x:xs) = (concat (replicate n " ") ++ x) : justify (n-1) xs
justify _ [] = []
could be
justify n ss = zipWith (++) padding ss
where padding = [ replicate k ' ' | k <- [n, n-1 .. 1]]
or equivalently, after eta-reduction
justify n = zipWith (++) padding
where padding = [ replicate k ' ' | k <- [n, n-1 .. 1]]
Due to the fact that lists are linked lists, appending to a list is expensive, while prepending to it is cheap. So if you do decide to construct a list element-by-element, then I'd say you should prepend the new elements, and perhaps after the list is built, use a reverse
.
By the same logic, head
is cheap, last
is expensive.
Here's a possible implementation that uses what I've just said.
pascalList = reverse . pascalList'
pascalList' 0 = [[1]]
pascalList' n = new : old
where new = 1 : pascalCoeff (head old) ++ [1]
old = pascalList' (n-1)
In my opinion it is a good idea to
- separate pure and impure code,
- keep your code as modular as possible,
- write type signatures,
- use a linter like hlint,
- use functions from the standard library instead of writing explicit recursions.
Keeping this in mind, here's how I'd write the printing part.
listToString :: (Show a) => [a] -> String
listToString = unwords . map show
leftPadStrings :: [String] -> [String]
leftPadStrings ss = map leftPad ss
where maxlen = maximum $ map length ss
leftPad s = replicate (div (maxlen - length s) 2) ' ' ++ s
paddedPascalTriangle :: Int -> [String]
paddedPascalTriangle n = leftPadStrings
. map listToString
. take n
$ pascalTriangle
printPascalTriangle :: Int -> IO ()
printPascalTriangle = mapM_ putStrLn . paddedPascalTriangle
Running this, you should get something like the following:
*Main> printPascalTriangle 10
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1