September Morn Update
This commit is contained in:
parent
4e2d22a71d
commit
aac6731f2c
6856 changed files with 141342 additions and 21127 deletions
300
Task/Visualize-a-tree/JavaScript/visualize-a-tree-2.js
Normal file
300
Task/Visualize-a-tree/JavaScript/visualize-a-tree-2.js
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
(() => {
|
||||
'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();
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue