Display an outline as a nested table: Difference between revisions

→‎{{header|Python}}: Added a functionally composed variant.
(Added AutoHotkey)
(→‎{{header|Python}}: Added a functionally composed variant.)
Line 1,848:
 
=={{header|Python}}==
===Python: Procedural===
<lang python>
<lang python>"""Display an outline as a nested table. Requires Python >=3.6."""
 
import itertools
Line 2,117:
table_format = "wiki"
 
example(table_format)</lang>
</lang>
 
{{out}}
 
Line 2,151 ⟶ 2,149:
HTML table
 
<pre><table style='text-align: center;'>
<pre>
<table style='text-align: center;'>
<tr>
<tdcolspan="7" style="background-color: #ffffe6;">Display an outline as a nested table.</td>
Line 2,179 ⟶ 2,176:
<td style="background-color: #F9F9F9;"></td>
</tr>
</table></pre>
 
</pre>
===Python: Functional===
<lang python>'''Display an outline as a nested table'''
 
from itertools import chain, cycle, takewhile
from functools import reduce
from operator import add
 
 
# wikiTableFromOutline :: [String] -> String -> String
def wikiTableFromOutline(colorSwatch):
'''Wikitable markup for a nested (colspan) table
representing the indentation of a given outline.
Each key-line point (child of the root) has a
distinct color, inherited by all its descendants.
The first color in the swatch is for the root node.
'''
def go(s):
return wikiTableFromRows(
levels(
paintedTree(colorSwatch)(
widthMeasuredTree(
fullDepthTree(
treeFromOutline(s)
)
)
)
)
)
return go
 
 
# ------------------------- TEST -------------------------
# main :: IO ()
def main():
'''Test'''
 
outline = '''Display an outline as a nested table.
Parse the outline to a tree,
measuring the indent of each line,
translating the indentation to a nested structure,
and padding the tree to even depth.
count the leaves descending from each node,
defining the width of a leaf as 1,
and the width of a parent node as a sum.
(The sum of the widths of its children)
and write out a table with 'colspan' values
either as a wiki table,
or as HTML.'''
 
print(
wikiTableFromOutline([
"#ffffe6",
"#ffebd2",
"#f0fff0",
"#e6ffff",
"#ffeeff"
])(outline)
)
 
 
# ------------------ TREE FROM OUTLINE -------------------
 
# treeFromOutline :: String -> Tree String
def treeFromOutline(s):
'''A tree with the structure of the given outline.'''
return forestFromLevels(
indentLevelsFromLines(
s.splitlines()
)
)[0]
 
 
# -------------- TREE PADDED TO EVEN DEPTH ---------------
 
# fullDepthTree :: Node String -> Node String
def fullDepthTree(tree):
'''A tree padded down even evenly
(with empty string nodes)
to the depth of its deepest subtree.
'''
return paddedTree("")(
treeDepth(tree), tree
)
 
 
# paddedTree :: a -> (Int, Node a) -> Node a
def paddedTree(padValue):
'''A tree vertically padded to a given depth,
with additional nodes, containing padValue,
where needed.
'''
def go(n, tree):
prev = n - 1
return Node(tree.get('root'))([
go(prev, x) for x in (
tree.get('nest') or [Node(padValue)([])]
)
]) if prev else tree
return go
 
 
# treeDepth :: Tree a -> Int
def treeDepth(tree):
'''Maximum number of distinct levels in the tree.
'''
def go(_, xs):
return 1 + max(xs) if xs else 1
return foldTree(go)(tree)
 
 
# ------------ SPANNING WIDTH OF EACH SUBTREE ------------
 
# widthMeasuredTree :: Tree a -> Tree (a, Int)
def widthMeasuredTree(tree):
'''A tree in which each node value is tupled
with the width of the subtree.
'''
def go(x, xs):
return Node((x, 1))([]) if not xs else (
Node((x, reduce(
lambda a, child: a + (
child.get('root')[1]
),
xs,
0
)))(xs)
)
return foldTree(go)(tree)
 
 
# ----------------- COLOR SWATCH APPLIED -----------------
 
# paintedTree :: [String] -> Tree a -> Tree (String, a)
def paintedTree(swatch):
'''A tree in which every node value is tupled with
a hexadecimal color string taken from a swatch list.
The first colour is used for the root node.
The next n colours paint the root's n children.
All descendants of those children are painted with
the same color as their non-root ancestor.
'''
colors = cycle(swatch + [""])
 
def go(tree):
return fmapTree(
lambda x: ("", x)
)(tree) if not swatch else (
Node(
(next(colors), tree.get('root'))
)(
list(map(
lambda k, child: fmapTree(
lambda v: (k, v)
)(child),
colors,
tree.get('nest')
))
)
)
return go
 
 
# ---------------- GENERIC TREE FUNCTIONS ----------------
 
# Node :: a -> [Tree a] -> Tree a
def Node(v):
'''Constructor for a Tree node which connects a
value of some kind to a list of zero or
more child trees.
'''
return lambda xs: {'root': v, 'nest': xs}
 
 
# fmapTree :: (a -> b) -> Tree a -> Tree b
def fmapTree(f):
'''A new tree holding the results of
an application of f to each root in
the existing tree.
'''
def go(x):
return Node(
f(x.get('root'))
)([go(v) for v in x.get('nest')])
return go
 
 
# foldTree :: (a -> [b] -> b) -> Tree a -> b
def foldTree(f):
'''The catamorphism on trees. A summary
value defined by a depth-first fold.
'''
def go(node):
return f(
node.get('root'),
[go(x) for x in node.get('nest')]
)
return go
 
 
# forestFromLevels :: [(Int, String)] -> [Tree a]
def forestFromLevels(levelValuePairs):
'''A list of trees derived from a list of values paired
with integers giving their levels of indentation.
'''
def go(xs):
if xs:
level, v = xs[0]
children, rest = span(
lambda x: level < x[0]
)(xs[1:])
return [Node(v)(go(children))] + go(rest)
else:
return []
return go(levelValuePairs)
 
 
# indentLevelsFromLines :: [String] -> [(Int, String)]
def indentLevelsFromLines(xs):
'''Each input line stripped of leading
white space, and tupled with a preceding integer
giving its level of indentation from 0 upwards.
'''
indentTextPairs = [
(n, s[n:]) for (n, s)
in (
(len(list(takewhile(isSpace, x))), x)
for x in xs
)
]
indentUnit = len(next(
x for x in indentTextPairs if x[0]
)) or 1
return [
(x[0] // indentUnit, x[1])
for x in indentTextPairs
]
 
 
# levels :: Tree a -> [[a]]
def levels(tree):
'''A list of lists, grouping the root
values of each level of the tree.
'''
return [[tree.get('root')]] + list(
reduce(
zipWithLong(add),
map(levels, tree.get('nest')),
[]
)
)
 
 
# ----------------- WIKITABLE RENDERING ------------------
 
# wikiTableFromRows :: [[(String, (String, Int))]] -> String
def wikiTableFromRows(rows):
'''A wiki table rendering of rows in which each cell
has the form (hexColorString, (text, colspan))
'''
def cw(color, width):
def go(w):
return f' colspan={w}' if 1 < w else ''
return f'style="background: {color}; "{go(width)}'
 
def cellText(cell):
color, (txt, width) = cell
return f'| {cw(color,width) if txt else ""} | {txt}'
 
def go(row):
return '\n'.join([cellText(cell) for cell in row])
 
return '{| class="wikitable" ' + (
'style="text-align: center;"\n|-\n'
) + '\n|-\n'.join([go(row) for row in rows]) + '\n|}'
 
 
# ----------------------- GENERIC ------------------------
 
# isSpace :: Char -> Bool
# isSpace :: String -> Bool
def isSpace(s):
'''True if s is not empty, and
contains only white space.
'''
return s.isspace()
 
 
# span :: (a -> Bool) -> [a] -> ([a], [a])
def span(p):
'''The longest (possibly empty) prefix of xs that
contains only elements satisfying p, tupled with the
remainder of xs. span p xs is equivalent to
(takeWhile p xs, dropWhile p xs).
'''
def match(ab):
b = ab[1]
return not b or not p(b[0])
 
def f(ab):
a, b = ab
return a + [b[0]], b[1:]
 
def go(xs):
return until(match)(f)(([], xs))
return go
 
 
# until :: (a -> Bool) -> (a -> a) -> a -> a
def until(p):
'''The result of repeatedly applying f until p holds.
The initial seed value is x.
'''
def go(f):
def g(x):
v = x
while not p(v):
v = f(v)
return v
return g
return go
 
 
# zipWithLong :: ((a, a) -> a) -> ([a], [a]) -> [a]
def zipWithLong(f):
'''Analogous to map(f, xs, ys)
but returns a list with the length of the *longer*
of xs and ys, taking any surplus values unmodified.
'''
def go(xs, ys):
lxs = list(xs)
lys = list(ys)
i = min(len(lxs), len(lys))
return chain.from_iterable([
map(f, lxs, lys),
lxs[i:],
lys[i:]
])
return go
 
 
# MAIN ---
if __name__ == '__main__':
main()</lang>
{{Out}}
{| class="wikitable" style="text-align: center;"
|-
| style="background: #ffffe6; " colspan=7 | Display an outline as a nested table.
|-
| style="background: #ffebd2; " colspan=3 | Parse the outline to a tree,
| style="background: #f0fff0; " colspan=2 | count the leaves descending from each node,
| style="background: #e6ffff; " colspan=2 | and write out a table with 'colspan' values
|-
| style="background: #ffebd2; " | measuring the indent of each line,
| style="background: #ffebd2; " | translating the indentation to a nested structure,
| style="background: #ffebd2; " | and padding the tree to even depth.
| style="background: #f0fff0; " | defining the width of a leaf as 1,
| style="background: #f0fff0; " | and the width of a parent node as a sum.
| style="background: #e6ffff; " | either as a wiki table,
| style="background: #e6ffff; " | or as HTML.
|-
| |
| |
| |
| |
| style="background: #f0fff0; " | (The sum of the widths of its children)
| |
| |
|}
 
=={{header|Raku}}==
9,659

edits