RosettaCodeData/Task/Visualize-a-tree/JavaScript/visualize-a-tree-3.js

131 lines
3.7 KiB
JavaScript

(() => {
'use strict';
// drawTree :: Bool -> Tree String -> String
const drawTree = blnCompact => tree => {
// Simple decorated-outline style of ascii tree drawing,
// with nodeless lines pruned out if blnCompact is True.
const xs = draw(tree);
return unlines(
blnCompact ? (
xs.filter(
s => s.split('')
.some(c => !' │'.includes(c))
)
) : xs
);
};
// draw :: Tree String -> [String]
const draw = node => {
// shift :: String -> String -> [String] -> [String]
const shift = (first, other, xs) =>
zipWith(
append,
cons(first, replicate(xs.length - 1, other)),
xs
);
// drawSubTrees :: [Tree String] -> [String]
const drawSubTrees = xs => {
const lng = xs.length;
return 0 < lng ? (
1 < lng ? append(
cons(
'│',
shift('├─ ', '│ ', draw(xs[0]))
),
drawSubTrees(xs.slice(1))
) : cons('│', shift('└─ ', ' ', draw(xs[0])))
) : [];
};
return append(
lines(node.root.toString()),
drawSubTrees(node.nest)
);
};
// TEST -----------------------------------------------
const main = () => {
const tree = Node(
'Alpha', [
Node('Beta', [
Node('Epsilon', []),
Node('Zeta', []),
Node('Eta', [])
]),
Node('Gamma', [Node('Theta', [])]),
Node('Delta', [
Node('Iota', []),
Node('Kappa', []),
Node('Lambda', [])
])
]);
return [true, false]
.map(blnCompact => drawTree(blnCompact)(tree))
.join('\n\n');
};
// 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 || []
});
// append (++) :: [a] -> [a] -> [a]
// append (++) :: String -> String -> String
const append = (xs, ys) => xs.concat(ys);
// chars :: String -> [Char]
const chars = s => s.split('');
// cons :: a -> [a] -> [a]
const cons = (x, xs) => [x].concat(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
// length :: [a] -> Int
const length = xs =>
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;
// lines :: String -> [String]
const lines = s => s.split(/[\r\n]/);
// replicate :: Int -> a -> [a]
const replicate = (n, x) =>
Array.from({
length: n
}, () => x);
// take :: Int -> [a] -> [a]
const take = (n, xs) =>
xs.slice(0, n);
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// Use of `take` and `length` here allows zipping with non-finite lists
// i.e. generators like cycle, repeat, iterate.
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = (f, xs, ys) => {
const
lng = Math.min(length(xs), length(ys)),
as = take(lng, xs),
bs = take(lng, ys);
return Array.from({
length: lng
}, (_, i) => f(as[i], bs[i], i));
};
// MAIN ---
return main();
})();