3
\$\begingroup\$

The code below generates a list of Pascal Coefficients. e.g pascalList 3 outputs [[1], [1,1], [1,2,1], [1,3,3,1] Can we write below sample in more idiomatic way

pascalList 0 = [[1]]
pascalList 1 = [[1], [1, 1]]
pascalList n = let pList = pascalList (n-1)
 in pList ++ [([1] ++ pascalCoeff (last pList) ++ [1])]
 where pascalCoeff (x:y:ys) = (x+y) : pascalCoeff (y:ys)
 pascalCoeff (x:[]) = []

Following code prints above list

listtoString :: [Int] -> String
listtoString [] = []
listtoString [x] = show x
listtoString (x:xs) = show x ++ " " ++ listtoString xs
pascalTriangle :: Int -> IO ()
pascalTriangle n = mapM_ putStrLn (((justify n) . map listtoString) (pascalList n))
justify :: Int -> [String] -> [String]
justify n (x:xs) = (concat (replicate n " ") ++ x) : justify (n-1) xs
justify _ [] = []

The sample output of the pascalTriangle 4 will be

 1
 1 1
 1 2 1
 1 3 3 1
1 4 6 4 1
200_success
146k22 gold badges190 silver badges479 bronze badges
asked Apr 3, 2018 at 13:06
\$\endgroup\$

2 Answers 2

7
\$\begingroup\$

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
answered Apr 3, 2018 at 16:09
\$\endgroup\$
5
  • \$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. \$\endgroup\$ Commented Apr 3, 2018 at 16:33
  • \$\begingroup\$ @Zeta "It is a good idea to separate pure and impure code" was the justification given for the rewrite. \$\endgroup\$ Commented Apr 3, 2018 at 16:58
  • \$\begingroup\$ @Zeta Your comment is justified, because initially I just wanted OP to know about infinite data types, because I think it's a cool concept. Since then I've added some of the more traditional code review. \$\endgroup\$ Commented Apr 3, 2018 at 21:53
  • \$\begingroup\$ If I had have time, I would have used the same concept :). That being said, zipWith func (init xs) (tail xs) is the same as zipWith func xs (tail xs), just as in fibs = 1 : 1 : zibWith (+) fibs (tail fibs). \$\endgroup\$ Commented Apr 4, 2018 at 4:03
  • \$\begingroup\$ @Zeta ah, yes, you are right, thanks. In fact I've used the same property when I rewrote justify. Oh well, I'll edit it in. \$\endgroup\$ Commented Apr 4, 2018 at 5:49
4
\$\begingroup\$

The third case subsumes the second. All possible result lists are prefixes of the same infinite list - let's define that instead. iterate captures this pattern. A combination of zipWith and tail captures pascalCoeff.

pascalList :: [[Int]]
pascalList = flip iterate [1] $ \pList -> 1 : zipWith (+) pList (tail pList) ++ [1]
answered Apr 3, 2018 at 20:31
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.