Visualize a tree

Solution:
(() => {
    'use strict';

    // UTF8 character-drawn tree, with options for compacting vs
    // centering parents, and for pruning out nodeless lines.

    const example = `
               ┌ Epsilon
       ┌─ Beta ┼─── Zeta
       │       └──── Eta
 Alpha ┼ Gamma ─── Theta
       │       ┌─── Iota
       └ Delta ┼── Kappa
               └─ Lambda`

    // drawTree2 :: Bool -> Bool -> Tree String -> String
    const drawTree2 = blnCompact => blnPruned => tree => {
        // Tree design and algorithm inspired by the Haskell snippet at:
        // https://doisinkidney.com/snippets/drawing-trees.html
        const
            // Lefts, Middle, Rights
            lmrFromStrings = xs => {
                const [ls, rs] = Array.from(splitAt(
                    Math.floor(xs.length / 2),
                    xs
                ));
                return Tuple3(ls, rs[0], rs.slice(1));
            },
            stringsFromLMR = lmr =>
            Array.from(lmr).reduce((a, x) => a.concat(x), []),
            fghOverLMR = (f, g, h) => lmr => {
                const [ls, m, rs] = Array.from(lmr);
                return Tuple3(ls.map(f), g(m), rs.map(h));
            };

        const lmrBuild = (f, w) => wsTree => {
            const
                leftPad = n => s => ' '.repeat(n) + s,
                xs = wsTree.nest,
                lng = xs.length,
                [nChars, x] = Array.from(wsTree.root);

            // LEAF NODE --------------------------------------
            return 0 === lng ? (
                Tuple3([], '─'.repeat(w - nChars) + x, [])

                // NODE WITH SINGLE CHILD -------------------------
            ) : 1 === lng ? (() => {
                const indented = leftPad(1 + w);
                return fghOverLMR(
                    indented,
                    z => '─'.repeat(w - nChars) + x + '─' + z,
                    indented
                )(f(xs[0]));

                // NODE WITH CHILDREN -----------------------------
            })() : (() => {
                const
                    cFix = x => xs => x + xs,
                    treeFix = (l, m, r) => compose(
                        stringsFromLMR,
                        fghOverLMR(cFix(l), cFix(m), cFix(r))
                    ),
                    _x = '─'.repeat(w - nChars) + x,
                    indented = leftPad(w),
                    lmrs = xs.map(f);
                return fghOverLMR(
                    indented,
                    s => _x + ({
                        '┌': '┬',
                        '├': '┼',
                        '│': '┤',
                        '└': '┴'
                    })[s[0]] + s.slice(1),
                    indented
                )(lmrFromStrings(
                    intercalate(
                        blnCompact ? [] : ['│'],
                        [treeFix(' ', '┌', '│')(lmrs[0])]
                        .concat(init(lmrs.slice(1)).map(
                            treeFix('│', '├', '│')
                        ))
                        .concat([treeFix('│', '└', ' ')(
                            lmrs[lmrs.length - 1]
                        )])
                    )
                ));
            })();
        };
        const
            measuredTree = fmapTree(
                v => {
                    const s = ' ' + v + ' ';
                    return Tuple(s.length, s)
                }, tree
            ),
            levelWidths = init(levels(measuredTree))
            .reduce(
                (a, level) => a.concat(maximum(level.map(fst))),
                []
            ),
            treeLines = stringsFromLMR(
                levelWidths.reduceRight(
                    lmrBuild, x => x
                )(measuredTree)
            );
        return unlines(
            blnPruned ? (
                treeLines.filter(
                    s => s.split('')
                    .some(c => !' │'.includes(c))
                )
            ) : treeLines
        );
    };

    // TESTS ----------------------------------------------
    const main = () => {

        // tree :: Tree String
        const tree = Node(
            'Alpha', [
                Node('Beta', [
                    Node('Epsilon', []),
                    Node('Zeta', []),
                    Node('Eta', [])
                ]),
                Node('Gamma', [Node('Theta', [])]),
                Node('Delta', [
                    Node('Iota', []),
                    Node('Kappa', []),
                    Node('Lambda', [])
                ])
            ]);

        // tree2 :: Tree Int
        const tree2 = Node(
            1,
            [
                Node(2, [
                    Node(4, []),
                    Node(5, [Node(7, [])])
                ]),
                Node(3, [
                    Node(6, [
                        Node(8, []),
                        Node(9, [])
                    ])
                ])
            ]
        );

        // strTrees :: String
        const strTrees = ([
            'Compacted (parents not all vertically centered):',
            drawTree2(true)(false)(tree2),
            'Fully expanded, with vertical centering:',
            drawTree2(false)(false)(tree),
            'Vertically centered, with nodeless lines pruned out:',
            drawTree2(false)(true)(tree),
        ].join('\n\n'));

        return (
            console.log(strTrees),
            strTrees
        );
    };

    // GENERIC FUNCTIONS ----------------------------------

    // Node :: a -> [Tree a] -> Tree a
    const Node = (v, xs) => ({
        type: 'Node',
        root: v, // any type of value (consistent across tree)
        nest: xs || []
    });

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

    // Tuple3 (,,) :: a -> b -> c -> (a, b, c)
    const Tuple3 = (a, b, c) => ({
        type: 'Tuple3',
        '0': a,
        '1': b,
        '2': c,
        length: 3
    });

    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (f, g) => x => f(g(x));

    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        0 < xs.length ? (() => {
            const unit = 'string' !== typeof xs[0] ? (
                []
            ) : '';
            return unit.concat.apply(unit, xs);
        })() : [];

    // fmapTree :: (a -> b) -> Tree a -> Tree b
    const fmapTree = (f, tree) => {
        const go = node => Node(
            f(node.root),
            node.nest.map(go)
        );
        return go(tree);
    };

    // fst :: (a, b) -> a
    const fst = tpl => tpl[0];

    // identity :: a -> a
    const identity = x => x;

    // init :: [a] -> [a]
    const init = xs =>
        0 < xs.length ? (
            xs.slice(0, -1)
        ) : undefined;

    // intercalate :: [a] -> [[a]] -> [a]
    // intercalate :: String -> [String] -> String
    const intercalate = (sep, xs) =>
        0 < xs.length && 'string' === typeof sep &&
        'string' === typeof xs[0] ? (
            xs.join(sep)
        ) : concat(intersperse(sep, xs));

    // intersperse(0, [1,2,3]) -> [1, 0, 2, 0, 3]

    // intersperse :: a -> [a] -> [a]
    // intersperse :: Char -> String -> String
    const intersperse = (sep, xs) => {
        const bln = 'string' === typeof xs;
        return xs.length > 1 ? (
            (bln ? concat : x => x)(
                (bln ? (
                    xs.split('')
                ) : xs)
                .slice(1)
                .reduce((a, x) => a.concat([sep, x]), [xs[0]])
            )) : xs;
    };

    // iterateUntil :: (a -> Bool) -> (a -> a) -> a -> [a]
    const iterateUntil = (p, f, x) => {
        const vs = [x];
        let h = x;
        while (!p(h))(h = f(h), vs.push(h));
        return vs;
    };

    // 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

    // length :: [a] -> Int
    const length = xs =>
        (Array.isArray(xs) || 'string' === typeof xs) ? (
            xs.length
        ) : Infinity;

    // levels :: Tree a -> [[a]]
    const levels = tree =>
        iterateUntil(
            xs => 1 > xs.length,
            ys => [].concat(...ys.map(nest)),
            [tree]
        ).map(xs => xs.map(root));

    // maximum :: Ord a => [a] -> a
    const maximum = xs =>
        0 < xs.length ? (
            xs.slice(1).reduce((a, x) => x > a ? x : a, xs[0])
        ) : undefined;

    // nest :: Tree a -> [a]
    const nest = tree => tree.nest;

    // root :: Tree a -> a
    const root = tree => tree.root;

    // splitAt :: Int -> [a] -> ([a], [a])
    const splitAt = (n, xs) =>
        Tuple(xs.slice(0, n), xs.slice(n));

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

    // MAIN ---
    return main();
})();


Voronoi diagram

A Voronoi diagram is a diagram consisting of a number of sites.

Each Voronoi site ''s''  also has a Voronoi cell consisting of all points closest to ''s''.

;Task:
Demonstrate how to generate and display a Voroni diagram.

See algo [[K-means++ clustering]].

Solution:
===Version #1.===
The obvious route to this in JavaScript would be to use Mike Bostock's D3.js library.

There are various examples of Voronoi tesselations,

some dynamic:

https://bl.ocks.org/mbostock/d1d81455dc21e10f742f

some interactive:

https://bl.ocks.org/mbostock/4060366

and all with source code, at https://bl.ocks.org/mbostock

===Version #2.===
I would agree: using D3.js library can be very helpful. But having stable and compact algorithm in Python (Sidef) made it possible to develop looking the same Voronoi diagram in "pure" JavaScript.
A few custom helper functions simplified code, and they can be used for any other applications.


Water collected between towers

In a two-dimensional world, we begin with any bar-chart (or row of close-packed 'towers', each of unit width), and then it rains,
completely filling all convex enclosures in the chart with water.

9               ##           9               ##
8               ##           8               ##
7     ##        ##           7     ####
6     ##  ##    ##           6     ######
5 ##  ##  ##  ####           5 ##########
4 ##  ##  ########           4 ############
3 ######  ########           3 ##############
2 ################  ##       2 ##################
1 ####################       1 ####################

In the example above, a bar chart representing the values [5, 3, 7, 2, 6, 4, 5, 9, 1, 2] has filled, collecting 14 units of water.

Write a function, in your language, from a given array of heights, to the number of water units that can be held in this way, by a corresponding bar chart.

Calculate the number of water units that could be collected by bar charts representing each of the following seven series:

   [[1, 5, 3, 7, 2],
    [5, 3, 7, 2, 6, 4, 5, 9, 1, 2],
    [2, 6, 3, 5, 2, 8, 1, 4, 2, 2, 5, 3, 5, 7, 4, 1],
    [5, 5, 5, 5],
    [5, 6, 7, 8],
    [8, 7, 7, 6],
    [6, 7, 10, 7, 6]]

See, also:

* Four Solutions to a Trivial Problem - a Google Tech Talk by Guy Steele
* Water collected between towers on Stack Overflow, from which the example above is taken)
* An interesting Haskell solution, using the Tardis monad, by Phil Freeman in a Github gist.

Solution:
(() => {
    "use strict";

    // --------- WATER COLLECTED BETWEEN TOWERS ----------

    // waterCollected :: [Int] -> Int
    const waterCollected = xs =>
        sum(filter(lt(0))(
            zipWith(subtract)(xs)(
                zipWith(min)(
                    scanl1(max)(xs)
                )(
                    scanr1(max)(xs)
                )
            )
        ));

    // ---------------------- TEST -----------------------
    const main = () => [
        [1, 5, 3, 7, 2],
        [5, 3, 7, 2, 6, 4, 5, 9, 1, 2],
        [2, 6, 3, 5, 2, 8, 1, 4, 2, 2, 5, 3, 5, 7, 4, 1],
        [5, 5, 5, 5],
        [5, 6, 7, 8],
        [8, 7, 7, 6],
        [6, 7, 10, 7, 6]
    ].map(waterCollected);

    // --------------------- GENERIC ---------------------

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a =>
        b => ({
            type: "Tuple",
            "0": a,
            "1": b,
            length: 2
        });

    // filter :: (a -> Bool) -> [a] -> [a]
    const filter = p =>
        // The elements of xs which match
        // the predicate p.
        xs => [...xs].filter(p);

    // lt (<) :: Ord a => a -> a -> Bool
    const lt = a =>
        b => a < b;

    // max :: Ord a => a -> a -> a
    const max = a =>
        // b if its greater than a,
        // otherwise a.
        b => a > b ? a : b;

    // min :: Ord a => a -> a -> a
    const min = a =>
        b => b < a ? b : a;

    // scanl :: (b -> a -> b) -> b -> [a] -> [b]
    const scanl = f => startValue => xs =>
        xs.reduce((a, x) => {
            const v = f(a[0])(x);

            return Tuple(v)(a[1].concat(v));
        }, Tuple(startValue)([startValue]))[1];

    // scanl1 :: (a -> a -> a) -> [a] -> [a]
    const scanl1 = f =>
        // scanl1 is a variant of scanl that
        // has no starting value argument.
        xs => xs.length > 0 ? (
            scanl(f)(
                xs[0]
            )(xs.slice(1))
        ) : [];

    // scanr :: (a -> b -> b) -> b -> [a] -> [b]
    const scanr = f =>
        startValue => xs => xs.reduceRight(
            (a, x) => {
                const v = f(x)(a[0]);

                return Tuple(v)([v].concat(a[1]));
            }, Tuple(startValue)([startValue])
        )[1];

    // scanr1 :: (a -> a -> a) -> [a] -> [a]
    const scanr1 = f =>
        // scanr1 is a variant of scanr that has no
        // seed-value argument, and assumes that
        // xs is not empty.
        xs => xs.length > 0 ? (
            scanr(f)(
                xs.slice(-1)[0]
            )(xs.slice(0, -1))
        ) : [];

    // subtract :: Num -> Num -> Num
    const subtract = x =>
        y => y - x;

    // sum :: [Num] -> Num
    const sum = xs =>
        // The numeric sum of all values in xs.
        xs.reduce((a, x) => a + x, 0);

    // 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 => xs.map(
            (x, i) => f(x)(ys[i])
        ).slice(
            0, Math.min(xs.length, ys.length)
        );

    // MAIN ---
    return main();
})();


Weird numbers

In number theory, a perfect either).

In other words, the sum of the proper divisors of the number (divisors including 1 but not itself) is greater than the number itself (the number is ''abundant''), but no subset of those divisors sums to the number itself (the number is not ''semiperfect'').

For example:

* '''12''' is ''not'' a weird number.
** It is abundant; its proper divisors '''1, 2, 3, 4, 6''' sum to '''16''' (which ''is'' > 12),
** but it ''is'' semiperfect, e.g.:     '''6 + 4 + 2 == 12'''.
* '''70''' ''is'' a weird number.
** It is abundant; its proper divisors '''1, 2, 5, 7, 10, 14, 35''' sum to '''74''' (which ''is'' > 70),
** and there is no subset of proper divisors that sum to '''70'''.

;Task:
Find and display, here on this page, the first '''25''' weird numbers.

;Related tasks:
:* Abundant, deficient and perfect number classifications
:* Proper divisors

;See also:
:* OEIS: A006037 weird numbers
:* Wikipedia: weird number
:* MathWorld: weird number

Solution:
(() => {
    'use strict';

    // main :: IO ()
    const main = () =>
        take(25, weirds());

    // weirds :: Gen [Int]
    function* weirds() {
        let
            x = 1,
            i = 1;
        while (true) {
            x = until(isWeird, succ, x)
            console.log(i.toString() + ' -> ' + x)
            yield x;
            x = 1 + x;
            i = 1 + i;
        }
    }

    // isWeird :: Int -> Bool
    const isWeird = n => {
        const
            ds = descProperDivisors(n),
            d = sum(ds) - n;
        return 0 < d && !hasSum(d, ds)
    };

    // hasSum :: Int -> [Int] -> Bool
    const hasSum = (n, xs) => {
        const go = (n, xs) =>
            0 < xs.length ? (() => {
                const
                    h = xs[0],
                    t = xs.slice(1);
                return n < h ? (
                    go(n, t)
                ) : (
                    n == h || hasSum(n - h, t) || hasSum(n, t)
                );
            })() : false;
        return go(n, xs);
    };

    // descProperDivisors :: Int -> [Int]
    const descProperDivisors = n => {
        const
            rRoot = Math.sqrt(n),
            intRoot = Math.floor(rRoot),
            blnPerfect = rRoot === intRoot,
            lows = enumFromThenTo(intRoot, intRoot - 1, 1)
            .filter(x => (n % x) === 0);
        return (
            reverse(lows)
            .slice(1)
            .map(x => n / x)
        ).concat((blnPerfect ? tail : id)(lows))
    };

    // GENERIC FUNCTIONS ----------------------------

    // 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));
    };

    // id :: a -> a
    const id = x => x;

    // reverse :: [a] -> [a]
    const reverse = xs =>
        'string' !== typeof xs ? (
            xs.slice(0).reverse()
        ) : xs.split('').reverse().join('');

    // succ :: Enum a => a -> a
    const succ = x => 1 + x;

    // sum :: [Num] -> Num
    const sum = xs => xs.reduce((a, x) => a + x, 0);

    // tail :: [a] -> [a]
    const tail = xs => 0 < xs.length ? xs.slice(1) : [];

    // take :: Int -> [a] -> [a]
    // take :: Int -> String -> String
    const take = (n, xs) =>
        'GeneratorFunction' !== xs.constructor.constructor.name ? (
            xs.slice(0, n)
        ) : [].concat.apply([], Array.from({
            length: n
        }, () => {
            const x = xs.next();
            return x.done ? [] : [x.value];
        }));

    // until :: (a -> Bool) -> (a -> a) -> a -> a
    const until = (p, f, x) => {
        let v = x;
        while (!p(v)) v = f(v);
        return v;
    };

    // MAIN ---
    return main();
})();


Word wrap

Even today, with proportional fonts and complex layouts, there are still cases where you need to wrap text at a specified column.

;Basic task:
The basic task is to wrap a paragraph of text in a simple way in your language.

If there is a way to do this that is built-in, trivial, or provided in a standard library, show that.  Otherwise implement the minimum length greedy algorithm from Wikipedia.

Show your routine working on a sample of text at two different wrap columns.

;Extra credit:
Wrap text using a more sophisticated algorithm such as the Knuth and Plass TeX algorithm.
If your language provides this, you get easy extra credit,
but you ''must reference documentation'' indicating that the algorithm
is something better than a simple minimum length algorithm.

If you have both basic and extra credit solutions, show an example where
the two algorithms give different results.

Solution:
function wrap (text, limit) {
  if (text.length > limit) {
    // find the last space within limit
    var edge = text.slice(0, limit).lastIndexOf(' ');
    if (edge > 0) {
      var line = text.slice(0, edge);
      var remainder = text.slice(edge + 1);
      return line + '\n' + wrap(remainder, limit);
    }
  }
  return text;
}


Zeckendorf number representation

Just as numbers can be represented in a positional notation as sums of multiples of the powers of ten (decimal) or two (binary); all the positive integers can be represented as the sum of one or zero times the distinct members of the Fibonacci series.

Recall that the first six distinct Fibonacci numbers are:  1, 2, 3, 5, 8, 13.

The decimal number eleven can be written as 0*13 + 1*8 + 0*5 + 1*3 + 0*2 + 0*1 or 010100 in positional notation where the columns represent multiplication by a particular member of the sequence. Leading zeroes are dropped so that 11 decimal becomes 10100.

10100 is not the only way to make 11 from the Fibonacci numbers however;  0*13 + 1*8 + 0*5 + 0*3 + 1*2 + 1*1 or 010011 would also represent decimal 11. For a true Zeckendorf number there is the added restriction that ''no two consecutive Fibonacci numbers can be used'' which leads to the former unique solution.

;Task:
Generate and show here a table of the Zeckendorf number representations of the decimal numbers zero to twenty, in order.

The intention in this task to find the Zeckendorf form of an arbitrary integer.  The Zeckendorf form can be iterated by some bit twiddling rather than calculating each value separately but leave that to another separate task.

;Also see:
*   OEIS A014417   for the the sequence of required results.
*   Brown's Criterion - Numberphile

;Related task:
*   [[Fibonacci sequence]]

Solution:
(() => {
    'use strict';

    const main = () =>
        unlines(
            map(n => concat(zeckendorf(n)),
                enumFromTo(0, 20)
            )
        );

    // zeckendorf :: Int -> String
    const zeckendorf = n => {
        const go = (n, x) =>
            n < x ? (
                Tuple(n, '0')
            ) : Tuple(n - x, '1')
        return 0 < n ? (
            snd(mapAccumL(
                go, n,
                reverse(fibUntil(n))
            ))
        ) : ['0'];
    };

    // fibUntil :: Int -> [Int]
    const fibUntil = n =>
        cons(1, takeWhile(x => n >= x,
            map(snd, iterateUntil(
                tpl => n <= fst(tpl),
                tpl => {
                    const x = snd(tpl);
                    return Tuple(x, x + fst(tpl));
                },
                Tuple(1, 2)
            ))));

    // GENERIC FUNCTIONS ----------------------------

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        0 < xs.length ? (() => {
            const unit = 'string' !== typeof xs[0] ? (
                []
            ) : '';
            return unit.concat.apply(unit, xs);
        })() : [];

    // cons :: a -> [a] -> [a]
    const cons = (x, xs) =>
        Array.isArray(xs) ? (
            [x].concat(xs)
        ) : (x + xs);

    // enumFromTo :: Int -> Int -> [Int]
    const enumFromTo = (m, n) =>
        m <= n ? iterateUntil(
            x => n <= x,
            x => 1 + x,
            m
        ) : [];

    // fst :: (a, b) -> a
    const fst = tpl => tpl[0];

    // iterateUntil :: (a -> Bool) -> (a -> a) -> a -> [a]
    const iterateUntil = (p, f, x) => {
        const vs = [x];
        let h = x;
        while (!p(h))(h = f(h), vs.push(h));
        return vs;
    };

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // 'The mapAccumL function behaves like a combination of map and foldl;
    // it applies a function to each element of a list, passing an accumulating
    // parameter from left to right, and returning a final value of this
    // accumulator together with the new list.' (See Hoogle)

    // mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
    const mapAccumL = (f, acc, xs) =>
        xs.reduce((a, x, i) => {
            const pair = f(a[0], x, i);
            return Tuple(pair[0], a[1].concat(pair[1]));
        }, Tuple(acc, []));

    // reverse :: [a] -> [a]
    const reverse = xs =>
        'string' !== typeof xs ? (
            xs.slice(0).reverse()
        ) : xs.split('').reverse().join('');

    // snd :: (a, b) -> b
    const snd = tpl => tpl[1];

    // tail :: [a] -> [a]
    const tail = xs => 0 < xs.length ? xs.slice(1) : [];

    // takeWhile :: (a -> Bool) -> [a] -> [a]
    // takeWhile :: (Char -> Bool) -> String -> String
    const takeWhile = (p, xs) => {
        const lng = xs.length;
        return 0 < lng ? xs.slice(
            0,
            until(
                i => i === lng || !p(xs[i]),
                i => 1 + i,
                0
            )
        ) : [];
    };

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

    // until :: (a -> Bool) -> (a -> a) -> a -> a
    const until = (p, f, x) => {
        let v = x;
        while (!p(v)) v = f(v);
        return v;
    };

    // MAIN ---
    return main();
})();


Zhang-Suen thinning algorithm

Solution:
function Point(x, y) {
    this.x = x;
    this.y = y;
}
var ZhangSuen = (function () {
    function ZhangSuen() {
    }
    ZhangSuen.image =
    ["                                                          ",
     " #################                   #############        ",
     " ##################               ################        ",
     " ###################            ##################        ",
     " ########     #######          ###################        ",
     "   ######     #######         #######       ######        ",
     "   ######     #######        #######                      ",
     "   #################         #######                      ",
     "   ################          #######                      ",
     "   #################         #######                      ",
     "   ######     #######        #######                      ",
     "   ######     #######        #######                      ",
     "   ######     #######         #######       ######        ",
     " ########     #######          ###################        ",
     " ########     ####### ######    ################## ###### ",
     " ########     ####### ######      ################ ###### ",
     " ########     ####### ######         ############# ###### ",
     "                                                          "];

    ZhangSuen.nbrs = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1]];

    ZhangSuen.nbrGroups = [[[0, 2, 4], [2, 4, 6]], [[0, 2, 6], [0, 4, 6]]];

    ZhangSuen.toWhite = new Array();
    ;
    ZhangSuen.main = function (args) {
        ZhangSuen.grid = new Array(ZhangSuen.image.length);
        for (var r = 0; r < ZhangSuen.image.length; r++)
            ZhangSuen.grid[r] = (ZhangSuen.image[r]).split('');
        ZhangSuen.thinImage();
    };
    ZhangSuen.thinImage = function () {
        var firstStep = false;
        var hasChanged;
        do {
            hasChanged = false;
            firstStep = !firstStep;
            for (var r = 1; r < ZhangSuen.grid.length - 1; r++) {
                for (var c = 1; c < ZhangSuen.grid[0].length - 1; c++) {
                    if (ZhangSuen.grid[r][c] !== '#')
                        continue;
                    var nn = ZhangSuen.numNeighbors(r, c);
                    if (nn < 2 || nn > 6)
                        continue;
                    if (ZhangSuen.numTransitions(r, c) !== 1)
                        continue;
                    if (!ZhangSuen.atLeastOneIsWhite(r, c, firstStep ? 0 : 1))
                        continue;
                    ZhangSuen.toWhite.push(new Point(c, r));
                    hasChanged = true;
                }
            }
            for (let i = 0; i < ZhangSuen.toWhite.length; i++) {
                var p = ZhangSuen.toWhite[i];
                ZhangSuen.grid[p.y][p.x] = ' ';
            }
            ZhangSuen.toWhite = new Array();
        } while ((firstStep || hasChanged));
        ZhangSuen.printResult();
    };
    ZhangSuen.numNeighbors = function (r, c) {
        var count = 0;
        for (var i = 0; i < ZhangSuen.nbrs.length - 1; i++)
            if (ZhangSuen.grid[r + ZhangSuen.nbrs[i][1]][c + ZhangSuen.nbrs[i][0]] === '#')
                count++;
        return count;
    };
    ZhangSuen.numTransitions = function (r, c) {
        var count = 0;
        for (var i = 0; i < ZhangSuen.nbrs.length - 1; i++)
            if (ZhangSuen.grid[r + ZhangSuen.nbrs[i][1]][c + ZhangSuen.nbrs[i][0]] === ' ') {
                if (ZhangSuen.grid[r + ZhangSuen.nbrs[i + 1][1]][c + ZhangSuen.nbrs[i + 1][0]] === '#')
                    count++;
            }
        return count;
    };
    ZhangSuen.atLeastOneIsWhite = function (r, c, step) {
        var count = 0;
        var group = ZhangSuen.nbrGroups[step];
        for (var i = 0; i < 2; i++)
            for (var j = 0; j < group[i].length; j++) {
                var nbr = ZhangSuen.nbrs[group[i][j]];
                if (ZhangSuen.grid[r + nbr[1]][c + nbr[0]] === ' ') {
                    count++;
                    break;
                }
            }
        return count > 1;
    };
    ZhangSuen.printResult = function () {
        for (var i = 0; i < ZhangSuen.grid.length; i++) {
            var row = ZhangSuen.grid[i];
            console.log(row.join(''));
        }
    };
    return ZhangSuen;
}());
ZhangSuen.main(null);