A pascal matrix is a two-dimensional square matrix holding numbers from   Pascal's triangle,   also known as   binomial coefficients   and which can be shown as   nCr.

Shown below are truncated   5-by-5   matrices   M[i, j]   for   i,j   in range   0..4. 


A Pascal upper-triangular matrix that is populated with   jCi:

[[1, 1, 1, 1, 1],
 [0, 1, 2, 3, 4],
 [0, 0, 1, 3, 6],
 [0, 0, 0, 1, 4],
 [0, 0, 0, 0, 1]]

A Pascal lower-triangular matrix that is populated with   iCj   (the transpose of the upper-triangular matrix):

[[1, 0, 0, 0, 0],
 [1, 1, 0, 0, 0],
 [1, 2, 1, 0, 0],
 [1, 3, 3, 1, 0],
 [1, 4, 6, 4, 1]]

A Pascal symmetric matrix that is populated with   i+jCi:

[[1, 1, 1, 1, 1],
 [1, 2, 3, 4, 5],
 [1, 3, 6, 10, 15],
 [1, 4, 10, 20, 35],
 [1, 5, 15, 35, 70]]



Task

Write functions capable of generating each of the three forms of   n-by-n   matrices.

Use those functions to display upper, lower, and symmetric Pascal   5-by-5   matrices on this page.

The output should distinguish between different matrices and the rows of each matrix   (no showing a list of 25 numbers assuming the reader should split it into rows).



Note
 
The   Cholesky decomposition   of a Pascal symmetric matrix is the Pascal lower-triangle matrix of the same size.




(() => {
    'use strict';
 
    // -------------------PASCAL MATRIX--------------------
 
    // pascalMatrix :: ((Int, Int) -> (Int, Int)) ->
    // Int -> [Int]
    const pascalMatrix = f =>
        n => map(compose(binomialCoefficient, f))(
            range([0, 0], [n - 1, n - 1])
        );
 
    // binomialCoefficient :: (Int, Int) -> Int
    const binomialCoefficient = nk => {
        const [n, k] = Array.from(nk);
        return enumFromThenTo(k)(
            pred(k)
        )(1).reduceRight((a, x) => quot(
            a * succ(n - x)
        )(x), 1);
    };
 
    // ------------------------TEST------------------------
    // main :: IO ()
    const main = () => {
        const matrixSize = 5;
        console.log(intercalate('\n\n')(
            zipWith(
                k => xs => k + ':\n' + showMatrix(matrixSize)(xs)
            )(['Lower', 'Upper', 'Symmetric'])(
                apList(
                    map(pascalMatrix)([
                        identity, //              Lower
                        swap, //                  Upper
                        ([a, b]) => [a + b, b] // Symmetric
                    ])
                )([matrixSize])
            )
        ));
    };
 
    // ----------------------DISPLAY-----------------------
 
    // showMatrix :: Int -> [Int] -> String
    const showMatrix = n =>
        xs => {
            const
                ks = map(str)(xs),
                w = maximum(map(length)(ks));
            return unlines(
                map(unwords)(chunksOf(n)(
                    map(justifyRight(w)(' '))(ks)
                ))
            );
        };
 
    // -----------------GENERIC FUNCTIONS------------------
 
    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a =>
        b => ({
            type: 'Tuple',
            '0': a,
            '1': b,
            length: 2
        });
 
    // apList (<*>) :: [(a -> b)] -> [a] -> [b]
    const apList = fs =>
        // The sequential application of each of a list
        // of functions to each of a list of values.
        xs => fs.flatMap(
            f => xs.map(f)
        );
 
    // chunksOf :: Int -> [a] -> [[a]]
    const chunksOf = n =>
        xs => enumFromThenTo(0)(n)(
            xs.length - 1
        ).reduce(
            (a, i) => a.concat([xs.slice(i, (n + i))]),
            []
        );
 
    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
        x => fs.reduceRight((a, f) => f(a), x);
 
    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        0 < xs.length ? (
            xs.every(x => 'string' === typeof x) ? (
                ''
            ) : []
        ).concat(...xs) : xs;
 
    // cons :: a -> [a] -> [a]
    const cons = x =>
        xs => [x].concat(xs);
 
    // enumFromThenTo :: Int -> Int -> Int -> [Int]
    const enumFromThenTo = x1 =>
        x2 => y => {
            const d = x2 - x1;
            return Array.from({
                length: Math.floor(y - x2) / d + 2
            }, (_, i) => x1 + (d * i));
        };
 
    // enumFromTo :: Int -> Int -> [Int]
    const enumFromTo = m =>
        n => Array.from({
            length: 1 + n - m
        }, (_, i) => m + i);
 
    // fst :: (a, b) -> a
    const fst = tpl =>
        // First member of a pair.
        tpl[0];
 
    // identity :: a -> a
    const identity = x =>
        // The identity function. (`id`, in Haskell)
        x;
 
    // intercalate :: String -> [String] -> String
    const intercalate = s =>
        // The concatenation of xs
        // interspersed with copies of s.
        xs => xs.join(s);
 
    // justifyRight :: Int -> Char -> String -> String
    const justifyRight = n =>
        // The string s, preceded by enough padding (with
        // the character c) to reach the string length n.
        c => s => n > s.length ? (
            s.padStart(n, c)
        ) : s;
 
    // length :: [a] -> Int
    const length = xs =>
        // Returns Infinity over objects without finite
        // length. This enables zip and zipWith to choose
        // the shorter argument when one is non-finite,
        // like cycle, repeat etc
        (Array.isArray(xs) || 'string' === typeof xs) ? (
            xs.length
        ) : Infinity;
 
 
    // liftA2List :: (a -> b -> c) -> [a] -> [b] -> [c]
    const liftA2List = f => xs => ys =>
        // The binary operator f lifted to a function over two
        // lists. f applied to each pair of arguments in the
        // cartesian product of xs and ys.
        xs.flatMap(
            x => ys.map(f(x))
        );
 
    // map :: (a -> b) -> [a] -> [b]
    const map = f =>
        // The list obtained by applying f to each element of xs.
        // (The image of xs under f).
        xs => (Array.isArray(xs) ? (
            xs
        ) : xs.split('')).map(f);
 
    // maximum :: Ord a => [a] -> a
    const maximum = xs =>
        // The largest value in a non-empty list.
        0 < xs.length ? (
            xs.slice(1).reduce(
                (a, x) => x > a ? (
                    x
                ) : a, xs[0]
            )
        ) : undefined;
 
    // pred :: Enum a => a -> a
    const pred = x =>
        x - 1;
 
    // quot :: Int -> Int -> Int
    const quot = n => m => Math.floor(n / m);
 
    // The list of values in the subrange defined by a bounding pair.
 
    // range([0, 2]) -> [0,1,2]
    // range([[0,0], [2,2]])
    //  -> [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2]]
    // range([[0,0,0],[1,1,1]])
    //  -> [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1]]
 
    // range :: Ix a => (a, a) -> [a]
    function range() {
        const
            args = Array.from(arguments),
            ab = 1 !== args.length ? (
                args
            ) : args[0],
            [as, bs] = [ab[0], ab[1]].map(
                x => Array.isArray(x) ? (
                    x
                ) : (undefined !== x.type) &&
                (x.type.startsWith('Tuple')) ? (
                    Array.from(x)
                ) : [x]
            ),
            an = as.length;
        return (an === bs.length) ? (
            1 < an ? (
                traverseList(x => x)(
                    as.map((_, i) => enumFromTo(as[i])(bs[i]))
                )
            ) : enumFromTo(as[0])(bs[0])
        ) : [];
    };
 
    // snd :: (a, b) -> b
    const snd = tpl => tpl[1];
 
    // str :: a -> String
    const str = x => x.toString();
 
    // succ :: Enum a => a -> a
    const succ = x =>
        1 + x;
 
    // swap :: (a, b) -> (b, a)
    const swap = ab =>
        // The pair ab with its order reversed.
        Tuple(ab[1])(
            ab[0]
        );
 
    // take :: Int -> [a] -> [a]
    // take :: Int -> String -> String
    const take = n =>
        // The first n elements of a list,
        // string of characters, or stream.
        xs => xs.slice(0, n);
 
    // traverseList :: (Applicative f) => (a -> f b) -> [a] -> f [b]
    const traverseList = f =>
        // Collected results of mapping each element
        // of a structure to an action, and evaluating
        // these actions from left to right.
        xs => 0 < xs.length ? (() => {
            const
                vLast = f(xs.slice(-1)[0]),
                t = vLast.type || 'List';
            return xs.slice(0, -1).reduceRight(
                (ys, x) => liftA2List(cons)(f(x))(ys),
                liftA2List(cons)(vLast)([
                    []
                ])
            );
        })() : [
            []
        ];
 
    // unlines :: [String] -> String
    const unlines = xs =>
        // A single string formed by the intercalation
        // of a list of strings with the newline character.
        xs.join('\n');
 
    // unwords :: [String] -> String
    const unwords = xs =>
        // A space-separated string derived
        // from a list of words.
        xs.join(' ');
 
    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = f =>
        // A list constructed by zipping with a
        // custom function, rather than with the
        // default tuple constructor.
        xs => ys => {
            const
                lng = Math.min(length(xs), length(ys)),
                vs = take(lng)(ys);
            return take(lng)(xs)
                .map((x, i) => f(x)(vs[i]));
        };
 
    // MAIN ---
    return main();
})();