September Morn Update
This commit is contained in:
parent
4e2d22a71d
commit
aac6731f2c
6856 changed files with 141342 additions and 21127 deletions
331
Task/Visualize-a-tree/Python/visualize-a-tree-5.py
Normal file
331
Task/Visualize-a-tree/Python/visualize-a-tree-5.py
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
'''Textually visualized tree, with vertically-centered parent nodes'''
|
||||
|
||||
from functools import reduce
|
||||
from itertools import (chain, takewhile)
|
||||
|
||||
'''
|
||||
┌ Epsilon
|
||||
├─── Zeta
|
||||
┌─ Beta ┼──── Eta
|
||||
│ │ ┌───── Mu
|
||||
│ └── Theta ┤
|
||||
Alpha ┤ └───── Nu
|
||||
├ Gamma ────── Xi ─ Omicron
|
||||
│ ┌─── Iota
|
||||
└ Delta ┼── Kappa
|
||||
└─ Lambda
|
||||
'''
|
||||
# Tree style and algorithm inspired by the Haskell snippet at:
|
||||
# https://doisinkidney.com/snippets/drawing-trees.html
|
||||
|
||||
|
||||
# drawTree2 :: Bool -> Bool -> Tree a -> String
|
||||
def drawTree2(blnCompact):
|
||||
'''Monospaced UTF8 left-to-right text tree in a
|
||||
compact or expanded format, with any lines
|
||||
containing no nodes optionally pruned out.
|
||||
'''
|
||||
def go(blnPruned, tree):
|
||||
# measured :: a -> (Int, String)
|
||||
def measured(x):
|
||||
'''Value of a tree node
|
||||
tupled with string length.
|
||||
'''
|
||||
s = ' ' + str(x) + ' '
|
||||
return len(s), s
|
||||
|
||||
# lmrFromStrings :: [String] -> ([String], String, [String])
|
||||
def lmrFromStrings(xs):
|
||||
'''Lefts, Mid, Rights.'''
|
||||
i = len(xs) // 2
|
||||
ls, rs = xs[0:i], xs[i:]
|
||||
return ls, rs[0], rs[1:]
|
||||
|
||||
# stringsFromLMR :: ([String], String, [String]) -> [String]
|
||||
def stringsFromLMR(lmr):
|
||||
ls, m, rs = lmr
|
||||
return ls + [m] + rs
|
||||
|
||||
# fghOverLMR
|
||||
# :: (String -> String)
|
||||
# -> (String -> String)
|
||||
# -> (String -> String)
|
||||
# -> ([String], String, [String])
|
||||
# -> ([String], String, [String])
|
||||
def fghOverLMR(f, g, h):
|
||||
def go(lmr):
|
||||
ls, m, rs = lmr
|
||||
return (
|
||||
[f(x) for x in ls],
|
||||
g(m),
|
||||
[h(x) for x in rs]
|
||||
)
|
||||
return lambda lmr: go(lmr)
|
||||
|
||||
# leftPad :: Int -> String -> String
|
||||
def leftPad(n):
|
||||
return lambda s: (' ' * n) + s
|
||||
|
||||
# treeFix :: (Char, Char, Char) -> ([String], String, [String])
|
||||
# -> [String]
|
||||
def treeFix(l, m, r):
|
||||
def cfix(x):
|
||||
return lambda xs: x + xs
|
||||
return compose(stringsFromLMR)(
|
||||
fghOverLMR(cfix(l), cfix(m), cfix(r))
|
||||
)
|
||||
|
||||
def lmrBuild(w, f):
|
||||
def go(wsTree):
|
||||
nChars, x = wsTree['root']
|
||||
_x = ('─' * (w - nChars)) + x
|
||||
xs = wsTree['nest']
|
||||
lng = len(xs)
|
||||
|
||||
# linked :: String -> String
|
||||
def linked(s):
|
||||
c = s[0]
|
||||
t = s[1:]
|
||||
return _x + '┬' + t if '┌' == c else (
|
||||
_x + '┤' + t if '│' == c else (
|
||||
_x + '┼' + t if '├' == c else (
|
||||
_x + '┴' + t
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# LEAF ------------------------------------
|
||||
if 0 == lng:
|
||||
return ([], _x, [])
|
||||
|
||||
# SINGLE CHILD ----------------------------
|
||||
elif 1 == lng:
|
||||
def lineLinked(z):
|
||||
return _x + '─' + z
|
||||
rightAligned = leftPad(1 + w)
|
||||
return fghOverLMR(
|
||||
rightAligned,
|
||||
lineLinked,
|
||||
rightAligned
|
||||
)(f(xs[0]))
|
||||
|
||||
# CHILDREN --------------------------------
|
||||
else:
|
||||
rightAligned = leftPad(w)
|
||||
lmrs = [f(x) for x in xs]
|
||||
return fghOverLMR(
|
||||
rightAligned,
|
||||
linked,
|
||||
rightAligned
|
||||
)(
|
||||
lmrFromStrings(
|
||||
intercalate([] if blnCompact else ['│'])(
|
||||
[treeFix(' ', '┌', '│')(lmrs[0])] + [
|
||||
treeFix('│', '├', '│')(x) for x
|
||||
in lmrs[1:-1]
|
||||
] + [treeFix('│', '└', ' ')(lmrs[-1])]
|
||||
)
|
||||
)
|
||||
)
|
||||
return lambda wsTree: go(wsTree)
|
||||
|
||||
measuredTree = fmapTree(measured)(tree)
|
||||
levelWidths = reduce(
|
||||
lambda a, xs: a + [max(x[0] for x in xs)],
|
||||
levels(measuredTree),
|
||||
[]
|
||||
)
|
||||
treeLines = stringsFromLMR(
|
||||
foldr(lmrBuild)(None)(levelWidths)(
|
||||
measuredTree
|
||||
)
|
||||
)
|
||||
return [
|
||||
s for s in treeLines
|
||||
if any(c not in '│ ' for c in s)
|
||||
] if (not blnCompact and blnPruned) else treeLines
|
||||
|
||||
return lambda blnPruned: (
|
||||
lambda tree: '\n'.join(go(blnPruned, tree))
|
||||
)
|
||||
|
||||
|
||||
# TEST ----------------------------------------------------
|
||||
# main :: IO ()
|
||||
def main():
|
||||
'''Trees drawn in varying formats'''
|
||||
|
||||
# tree1 :: Tree Int
|
||||
tree1 = Node(1)([
|
||||
Node(2)([
|
||||
Node(4)([
|
||||
Node(7)([])
|
||||
]),
|
||||
Node(5)([])
|
||||
]),
|
||||
Node(3)([
|
||||
Node(6)([
|
||||
Node(8)([]),
|
||||
Node(9)([])
|
||||
])
|
||||
])
|
||||
])
|
||||
|
||||
# tree :: Tree String
|
||||
tree2 = Node('Alpha')([
|
||||
Node('Beta')([
|
||||
Node('Epsilon')([]),
|
||||
Node('Zeta')([]),
|
||||
Node('Eta')([]),
|
||||
Node('Theta')([
|
||||
Node('Mu')([]),
|
||||
Node('Nu')([])
|
||||
])
|
||||
]),
|
||||
Node('Gamma')([
|
||||
Node('Xi')([Node('Omicron')([])])
|
||||
]),
|
||||
Node('Delta')([
|
||||
Node('Iota')([]),
|
||||
Node('Kappa')([]),
|
||||
Node('Lambda')([])
|
||||
])
|
||||
])
|
||||
|
||||
print(
|
||||
'\n\n'.join([
|
||||
'Fully compacted (parents not all centered):',
|
||||
drawTree2(True)(False)(
|
||||
tree1
|
||||
),
|
||||
'Expanded with vertically centered parents:',
|
||||
drawTree2(False)(False)(
|
||||
tree2
|
||||
),
|
||||
'Centered parents with nodeless lines pruned out:',
|
||||
drawTree2(False)(True)(
|
||||
tree2
|
||||
)
|
||||
])
|
||||
)
|
||||
|
||||
|
||||
# GENERIC -------------------------------------------------
|
||||
|
||||
# Node :: a -> [Tree a] -> Tree a
|
||||
def Node(v):
|
||||
'''Contructor for a Tree node which connects a
|
||||
value of some kind to a list of zero or
|
||||
more child trees.
|
||||
'''
|
||||
return lambda xs: {'type': 'Tree', 'root': v, 'nest': xs}
|
||||
|
||||
|
||||
# compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
|
||||
def compose(g):
|
||||
'''Right to left function composition.'''
|
||||
return lambda f: lambda x: g(f(x))
|
||||
|
||||
|
||||
# concatMap :: (a -> [b]) -> [a] -> [b]
|
||||
def concatMap(f):
|
||||
'''A concatenated list over which a function has been mapped.
|
||||
The list monad can be derived by using a function f which
|
||||
wraps its output in a list,
|
||||
(using an empty list to represent computational failure).
|
||||
'''
|
||||
return lambda xs: list(
|
||||
chain.from_iterable(map(f, xs))
|
||||
)
|
||||
|
||||
|
||||
# fmapTree :: (a -> b) -> Tree a -> Tree b
|
||||
def fmapTree(f):
|
||||
'''A new tree holding the results of
|
||||
applying f to each root in
|
||||
the existing tree.
|
||||
'''
|
||||
def go(x):
|
||||
return Node(f(x['root']))(
|
||||
[go(v) for v in x['nest']]
|
||||
)
|
||||
return lambda tree: go(tree)
|
||||
|
||||
|
||||
# foldr :: (a -> b -> b) -> b -> [a] -> b
|
||||
def foldr(f):
|
||||
'''Right to left reduction of a list,
|
||||
using the binary operator f, and
|
||||
starting with an initial accumulator value.
|
||||
'''
|
||||
def g(x, a):
|
||||
return f(a, x)
|
||||
return lambda acc: lambda xs: reduce(
|
||||
g, xs[::-1], acc
|
||||
)
|
||||
|
||||
|
||||
# intercalate :: [a] -> [[a]] -> [a]
|
||||
# intercalate :: String -> [String] -> String
|
||||
def intercalate(x):
|
||||
'''The concatenation of xs
|
||||
interspersed with copies of x.
|
||||
'''
|
||||
return lambda xs: x.join(xs) if isinstance(x, str) else list(
|
||||
chain.from_iterable(
|
||||
reduce(lambda a, v: a + [x, v], xs[1:], [xs[0]])
|
||||
)
|
||||
) if xs else []
|
||||
|
||||
|
||||
# iterate :: (a -> a) -> a -> Gen [a]
|
||||
def iterate(f):
|
||||
'''An infinite list of repeated
|
||||
applications of f to x.
|
||||
'''
|
||||
def go(x):
|
||||
v = x
|
||||
while True:
|
||||
yield v
|
||||
v = f(v)
|
||||
return lambda x: go(x)
|
||||
|
||||
|
||||
# levels :: Tree a -> [[a]]
|
||||
def levels(tree):
|
||||
'''A list of the nodes at each level of the tree.'''
|
||||
return list(
|
||||
map_(map_(root))(
|
||||
takewhile(
|
||||
bool,
|
||||
iterate(concatMap(nest))(
|
||||
[tree]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# map :: (a -> b) -> [a] -> [b]
|
||||
def map_(f):
|
||||
'''The list obtained by applying f
|
||||
to each element of xs.
|
||||
'''
|
||||
return lambda xs: list(map(f, xs))
|
||||
|
||||
|
||||
# nest :: Tree a -> [Tree a]
|
||||
def nest(t):
|
||||
'''Accessor function for children of tree node.'''
|
||||
return t['nest'] if 'nest' in t else None
|
||||
|
||||
|
||||
# root :: Tree a -> a
|
||||
def root(t):
|
||||
'''Accessor function for data of tree node.'''
|
||||
return t['root'] if 'root' in t else None
|
||||
|
||||
|
||||
# MAIN ---
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue