Display an outline as a nested table: Difference between revisions

→‎{{header|JavaScript}}: Updated primitives, reshaped to handle forests (multiple roots) and foreground parallels with other versions
(→‎{{header|JavaScript}}: Updated primitives, reshaped to handle forests (multiple roots) and foreground parallels with other versions)
Line 673:
=={{header|JavaScript}}==
<lang javascript>(() => {
'"use strict'";
 
// ----------- NESTED TABLES FROM OUTLINE ------------
 
// wikiTablesFromOutline :: [String] -> String -> String
const wikiTablesFromOutline = colorSwatch =>
outline => forestFromIndentedLines(
indentLevelsFromLines(lines(outline))
)
.map(wikiTableFromTree(colorSwatch));
 
 
// wikiTableFromTree :: [String] -> Tree String -> String
const wikiTableFromTree = colorSwatch =>
compose(
wikiTableFromRows,
levels,
paintedTree(colorSwatch),
widthLabelledTree,
ap(paddedTree(""))(treeDepth)
);
 
// ---------------------- TEST -----------------------
// main :: IO ()
const main = () => {
Line 688 ⟶ 709:
and write out a table with 'colspan' values
either as a wiki table,
or as HTML.`;
 
console.logreturn wikiTablesFromOutline([
wikiTableFromForest("#ffffe6",
forestOfEvenDepth("#ffebd2",
map(compose("#f0fff0",
"#e6ffff",
coloredKeyLines(Object.keys(dictColors)),
paintedTree('backColor')('yellow'),"#ffeeff"
measuredTree])(outline);
))(
forestFromOutline(outline)
)
)
)
);
};
 
// TRANSLATION OF OUTLINE TO NESTED TABLE --------- TREE STRUCTURE FROM NESTED TEXT ---------
 
// forestFromOutlineforestFromIndentedLines :: [(Int, String)] -> [Tree String]
// [Tree String]
const forestFromOutline = s =>
const forestFromIndentedLines = tuples => {
// A list of trees, derived from an
//const indentedgo outline= text.xs =>
forestFromLineIndents 0 < xs.length ? (() => {
indentLevelsFromLines(lines(s)) // First line and its sub-tree,
const [indented, body] = Array.from(
);
xs[0]
),
[tree, rest] = Array.from(
span(compose(lt(indented), fst))(
tail(xs)
)
);
 
// followed by the rest.
// wikiTableFromForest :: [Tree (String, Dict)] -> String
return [
const wikiTableFromForest = forest => {
// Lines of wiki markup representing a nested Node(body)(go(tree))
// with 'colspan' values for parent nodes, ].concat(go(rest));
// and varying background colors.})() : [];
 
const tableRows = trees => {
return go(tuples);
const rows = tail(levels(Node('virtual')(trees)));
return unlines(concatMap(row =>
cons('|-')(
map(cell => {
const
dct = cell[1],
color = dct.backColor,
width = dct.leafSum;
return '| ' + (
Boolean(color) ? (
'style="background: ' +
dictColors[color] + '; "'
) : ''
) + (
1 < width ? (
' colspan=' + str(width)
) : ''
) + ' | ' + cell[0];
})(row)
)
)(rows));
};
return '{| class="wikitable" style="text-align: center;"\n' +
tableRows(forest) +
'\n|}'
};
 
 
// indentLevelsFromLines :: [String] -> [(Int, String)]
const indentLevelsFromLines = xs => {
// A list of (indentLevel, Text) tuples.
const
pairs = xs.map(compose(
firstArrowx => bimap(length),(cs span=> cs.join(isSpace""))(
span(isSpace)(list(x)),
indentUnit = maybe(1)(fst )(
find(x => 0 < x[0])(pairs),
)indentUnit = pairs.reduce(
return pairs.map (a, tpl) => {
firstArrow(flip(div)(indentUnit)) const i = tpl[0];
 
);
return 0 < i ? (
i < a ? i : a
) : a;
},
Infinity
);
 
return [Infinity, 0].includes(indentUnit) ? (
pairs
) : pairs.map(first(n => n / indentUnit));
};
 
// ------------ TREE PADDED TO EVEN DEPTH ------------
// forestFromLineIndents :: [(Int, String)] -> [Tree String]
 
const forestFromLineIndents = tuples => {
// paddedTree :: a //-> ATree lista of-> nestedInt trees-> derivedTree froma
const paddedTree = padValue =>
// a list of indented lines.
const// goAll =descendants xsexpanded =>to same depth
// with empty nodes 0where < xsneeded.length ? (() => {
node => const [n, s]depth => Array.from(xs[0]);{
const go = n //=> Linestree indented under this line,=>
//1 tupled< withn all? the(() rest.=> {
const [firstTreeLines, rest] const children = Array.fromnest(tree);
 
span(x => n < x[0])(xs.slice(1))
return Node(root(tree))(
(
0 < children.length ? (
children
) : [Node(padValue)([])]
).map(go(n - 1))
);
})() : tree;
 
return go(depth)(node);
};
 
// treeDepth :: Tree a -> Int
const treeDepth = tree =>
foldTree(
() => xs => 0 < xs.length ? (
1 + maximum(xs)
) : 1
)(tree);
 
// ------------- SUBTREE WIDTHS MEASURED -------------
 
// widthLabelledTree :: Tree a -> Tree (a, Int)
const widthLabelledTree = tree =>
// A tree in which each node is labelled with
// the width of its own subtree.
foldTree(x => xs =>
0 < xs.length ? (
Node(Tuple(x)(
xs.reduce(
(a, node) => a + snd(root(node)),
0
)
))(xs)
) : Node(Tuple(x)(1))([])
)(tree);
 
// -------------- COLOR SWATCH APPLIED ---------------
 
// paintedTree :: [String] -> Tree a -> Tree (String, a)
const paintedTree = colorSwatch =>
tree => 0 < colorSwatch.length ? (
Node(
Tuple(colorSwatch[0])(root(tree))
)(
zipWith(compose(fmapTree, Tuple))(
cycle(colorSwatch.slice(1))
)(
nest(tree)
)
)
) : fmapTree(Tuple(""))(tree);
 
// --------------- WIKITABLE RENDERED ----------------
 
// wikiTableFromRows ::
// [[(String, (String, Int))]] -> String
const wikiTableFromRows = rows => {
const
cw = color => width => {
const go = w =>
1 < w ? (
`colspan=${w} `
) : "";
 
return `style="background:${color}; "` + (
` ${go(width)}`
);
// This first tree}, and then the rest.
cellText = ctw => return [Node(s)(go(firstTreeLines))]{
const [color, tw] = Array.concatfrom(go(rest)ctw);
})() : const [txt, width] = Array.from(tw);
 
return go(tuples);
return 0 < txt.length ? (
`| ${cw(color)(width)}| ${txt}`
) : "| |";
},
classText = "class=\"wikitable\"",
styleText = "style=\"text-align:center;\"",
header = `{| ${classText} ${styleText}\n|-`,
tableBody = rows.map(
cells => cells.map(cellText).join("\n")
).join("\n|-\n");
 
return `${header}\n${tableBody}\n|}`;
};
 
// ------------------ GENERIC TREES ------------------
// forestOfEvenDepth :: [Tree (a, Dict)] -> [Tree (a, Dict)]
 
const forestOfEvenDepth = measuredTrees => {
// ANode :: treea padded-> downwards[Tree soa] that-> everyTree brancha
const Node = v =>
// descends to the same depth.
const// goConstructor =for na =>Tree treenode =>which connects a
// value of some 1kind >=to na ?list (of zero or
// more child treetrees.
xs => ) : Node(tree.root)({
type: 0 < tree.nest.length ? ("Node",
root: tree.nest.map(go(n - 1))v,
nest: xs || ) : [Node(Tuple('')({}))([])]
});
 
return measuredTrees.map(go(
 
1 + maximumBy(x => root(x)[1].layerSum)(
// fmapTree :: (a -> b) -> Tree a -> Tree measuredTreesb
const fmapTree = f => {
).root[1].layerSum
));// A new tree. The result of a
// structure-preserving application of f
// to each root in the existing tree.
const go = t => Node(
f(t.root)
)(
t.nest.map(go)
);
 
return go;
};
 
// BACKGROUND COLOURS FOR SECTIONS OF THE TREE ----
 
// coloredKeyLinesfoldTree :: (a -> [Stringb] -> b) -> Tree a -> b
const foldTree = f => {
// Tree (String, Dict) -> Tree (String, Dict)
// The catamorphism on trees. A summary
const coloredKeyLines = colorNames => node =>
// value obtained by a depth-first fold.
Node(root(node))(
const go = tree zipWith(paintedTree('backColor'))=> f(
takeroot(node.nest.lengthtree)(
cycle)(tail(colorNames))
nest(tree).map(go)
)(node.nest)
);
 
return go;
// paintedTree :: String -> a -> Tree (b, dict) -> Tree (b, dict)
const paintedTree = k => v => node => {
const go = x =>
Node(Tuple(root(x)[0])(
insertDict(k)(v)(root(x)[1])
))(nest(x).map(go));
return go(node);
};
 
 
// dictColors :: Dict
// levels :: Tree a -> [[a]]
const dictColors = {
const levels = tree yellow:=> '#ffffe6',{
// A list of lists, grouping the root
orange: '#ffebd2',
// values of each level of the tree.
green: '#f0fff0',
blue:const '#e6ffff'go = (a, node) => {
const [h, ...t] = 0 < a.length ? (
pink: '#ffeeff',
gray: '' a
) : [
[],
[]
];
 
return [
[node.root, ...h],
...node.nest.slice(0)
.reverse()
.reduce(go, t)
];
};
 
return go([], tree);
};
 
 
// nest :: Tree a -> [a]
// GENERIC FUNCTIONS ----------------------------
const nest = tree => {
// Allowing for lazy (on-demand) evaluation.
// If the nest turns out to be a function –
// rather than a list – that function is applied
// here to the root, and returns a list.
const xs = tree.nest;
 
return "function" !== typeof xs ? (
xs
) : xs(root(tree));
};
 
 
// root :: Tree a -> a
const root = tree =>
// The value attached to a tree node.
tree.root;
 
// --------------------- GENERIC ---------------------
 
// Just :: a -> Maybe a
const Just = x => ({
type: '"Maybe'",
Nothing: false,
Just: x
});
 
// Node :: a -> [Tree a] -> Tree a
const Node = v => xs => ({
type: 'Node',
root: v, // any type of value (consistent across tree)
nest: xs || []
});
 
// Nothing :: Maybe a
const Nothing = () => ({
type: '"Maybe'",
Nothing: true,
});
 
 
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a => b => ({
type:b 'Tuple',=> ({
'0' type: a"Tuple",
'1' "0": ba,
length "1": 2b,
length: 2
});
});
 
 
// apFn :: (a -> b -> c) -> (a -> b) -> (a -> c)
const ap = f =>
// Applicative instance for functions.
// f(x) applied to g(x).
g => x => f(x)(
g(x)
);
 
 
// bimap :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
const bimap = f =>
// Tuple instance of bimap.
// A tuple of the application of f and g to the
// first and second values respectively.
g => tpl => Tuple(f(tpl[0]))(
g(tpl[1])
);
 
 
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
x// =>A fs.reduceRight((a,function f)defined =>by f(a),the x);right-to-left
// composition of all the functions in fs.
fs.reduce(
(f, g) => x => f(g(x)),
x => x
);
 
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
xs.flatMap(f);
 
// cons :: a -> [a] -> [a]
const cons = x => xs =>
Array.isArray(xs) ? (
[x].concat(xs)
) : 'GeneratorFunction' !== xs.constructor.constructor.name ? (
x + xs
) : ( // Existing generator wrapped with one additional element
function*() {
yield x;
let nxt = xs.next()
while (!nxt.done) {
yield nxt.value;
nxt = xs.next();
}
}
)();
 
// cycle :: [a] -> Generator [a]
const cycle = function* cycle(xs) {
// An infinite repetition of xs,
// from which an arbitrary prefix
// may be taken.
const lng = xs.length;
let i = 0;
 
while (true) {
yield( xs[i]);
i = (1 + i) % lng;
}
}
 
// div :: Int -> Int -> Int
const div = x => y => Math.floor(x / y);
 
 
// find :: (a -> Bool) -> [a] -> Maybe a
const find = p => xs => {
const i = xs.findIndex(p);
return -1 !== i ? (
Just(xs[i])
) : Nothing();
};
 
 
// firstArrowfirst :: (a -> b) -> ((a, c) -> (b, c))
const firstArrowfirst = f =>
// A simple function lifted to one which applies
// to a tuple, transforming only its first item.
xy => Tuple(f(xy[0]))({
const tpl = Tuple(f(xy[0]))(xy[1]);
);
 
// flip :: (a -> b -> c) ->return bArray.isArray(xy) ->? a -> c(
Array.from(tpl)
const flip = f =>
1 < f.length ? () : tpl;
(a, b) => f(b, a)};
) : (x => y => f(y)(x));
 
// foldTree :: (a -> [b] -> b) -> Tree a -> b
const foldTree = f => tree => {
const go = node => f(node.root)(
node.nest.map(go)
);
return go(tree);
};
 
// foldl1 :: (a -> a -> a) -> [a] -> a
const foldl1 = f => xs =>
1 < xs.length ? xs.slice(1)
.reduce(uncurry(f), xs[0]) : xs[0];
 
// fromEnum :: Enum a => a -> Int
const fromEnum = x =>
typeof x !== 'string' ? (
x.constructor === Object ? (
x.value
) : parseInt(Number(x))
) : x.codePointAt(0);
 
// fst :: (a, b) -> a
const fst = tpl => tpl[0];
// First member of a pair.
tpl[0];
 
// gt :: Ord a => a -> a -> Bool
const gt = x => y =>
'Tuple' === x.type ? (
x[0] > y[0]
) : (x > y);
 
// insertDict :: String -> a -> Dict -> Dict
const insertDict = k => v => dct =>
Object.assign({}, dct, {
[k]: v
});
 
// isSpace :: Char -> Bool
const isSpace = c => /\s/.test(c);
// True if c is a white space character.
(/\s/u).test(c);
 
// 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 =>
// Returns Infinity over objects without finite
(Array.isArray(xs) || 'string' === typeof xs) ? (
// length. This enables zip and zipWith to choose
// the shorter argument when one is non-finite,
// like cycle, repeat etc
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
xs.length
) : Infinity;
 
// levels :: Tree a -> [[a]]
const levels = tree => {
const xs = [
[root(tree)]
];
let level = [tree].flatMap(nest);
while (0 < level.length) {
xs.push(level.map(root));
level = level.flatMap(nest);
}
return xs;
};
 
// lines :: String -> [String]
const lines = s => s.split(/[\r\n]/);
// A list of strings derived from a single
// string delimited by newline and or CR.
0 < s.length ? (
s.split(/[\r\n]+/u)
) : [];
 
// maybe :: b -> (a -> b) -> Maybe a -> b
const maybe = v =>
// Default value (v) if m is Nothing, or f(m.Just)
f => m => m.Nothing ? v : f(m.Just);
 
// maplist :: (aStringOrArrayLike -b => b) -> [a] -> [b]
const maplist = f => xs =>
(Array.isArray(// xs) ?itself, (if it is an Array,
// or an Array derived from xs.
Array.isArray(xs) ? (
xs
) : xsArray.splitfrom('')).map(fxs || []);
 
// max :: Ord a => a -> a -> a
const max = a => b => gt(b)(a) ? b : a;
 
// maximumBylt (<) :: (Ord a -=> a -> Ordering) -> [a] -> aBool
const maximumBylt = f => xsa =>
0b <=> xs.lengtha ?< (b;
xs.slice(1)
.reduce((a, x) => 0 < f(x)(a) ? x : a, xs[0])
) : undefined;
 
// measuredTree :: Tree a -> Tree (a, (Int, Int, Int))
const measuredTree = tree => {
// A tree in which each node is tupled with
// a (leafSum, layerSum, nodeSum) measure of its sub-tree,
// where leafSum is the number of descendant leaves,
// and layerSum is the number of descendant levels,
// and nodeSum counts all nodes, including the root.
// Index is a position in a zero-based top-down
// left to right series.
// For additional parent indices, see parentIndexedTree.
const whni = (w, h, n, i) => ({
leafSum: w,
layerSum: h,
nodeSum: n,
index: i
});
let i = 0;
return foldTree(
x => {
let topDown = i++;
return xs => Node(
Tuple(x)(
0 < xs.length ? (() => {
const dct = xs.reduce(
(a, node) => {
const dimns = node.root[1];
return whni(
a.leafSum + dimns.leafSum,
max(a.layerSum)(
dimns.layerSum
),
a.nodeSum + dimns.nodeSum,
topDown
);
}, whni(0, 0, 0, topDown)
);
return whni(
dct.leafSum,
1 + dct.layerSum,
1 + dct.nodeSum,
topDown
);
})() : whni(1, 0, 1, topDown)
)
)(xs);
}
)(tree);
};
 
// nestmaximum :: TreeOrd a -=> [a] -> a
const nestmaximum = treexs => tree.nest;(
// The largest value in a non-empty list.
ys => 0 < ys.length ? (
ys.slice(1).reduce(
(a, y) => y > a ? (
y
) : a, ys[0]
)
) : undefined
)(list(xs));
 
// root :: Tree a -> a
const root = tree => tree.root;
 
// snd :: (a, b) -> b
const snd = tpl => tpl[1];
// Second member of a pair.
tpl[1];
 
 
// span :: (a -> Bool) -> [a] -> ([a], [a])
const span = p => xs => {
const// iLastLongest =prefix of xs.length -consisting of elements 1;which
// all satisfy p, tupled with the remainder of xs.
return splitAt(
until(ixs => iLast < i || !p(xs[i]))({
const i = xs.findIndex(x succ=> !p(x));
)(0)
)(xs);
};
 
// splitAt :: Int -> [a] return ->1 ([a],!== [a])i ? (
const splitAt = n => Tuple(xs.slice(0, =>i))(
Tuple( xs.slice(0, ni))(
xs.slice(n )
) : Tuple(xs)([]);
};
 
// str :: a -> String
const str = x => x.toString();
 
// succ :: Enum a => a -> a
const succ = x => 1 + x;
 
// tail :: [a] -> [a]
const tail = xs => 0 < xs.length ? xs.slice(1) : [];
// A new list consisting of all
// items of xs except the first.
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
(ys => 0 < ys.length ? ys.slice(1) : [])(
xs
)
) : (take(1)(xs), xs);
 
 
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = n => xs =>
// The first n elements of a list,
'GeneratorFunction' !== xs.constructor.constructor.name ? (
// string of characters, or stream.
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];
}));
 
// uncurry :: (a -> b -> c) -> ((a, b) -> c)
const uncurry = f =>
function() {
const
args = Array.from(arguments),
a = 1 < args.length ? (
args
) : args[0]; // Tuple object.
return f(a[0])(a[1]);
};
 
// unlinesuncons :: [Stringa] -> StringMaybe (a, [a])
const unlinesuncons = xs => xs.join('\n');{
// Just a tuple of the head of xs and its tail,
// Or Nothing if xs is an empty list.
const lng = length(xs);
 
// until :: (a -> Bool) ->return (a0 ->< alng) ->? a -> a(
const until = p => f => x =Infinity > {lng ? (
let v = x; // Finite list
while (!p(v)) v = f Just(vTuple(xs[0])(xs.slice(1)));
return v; ) : (() => {
// Lazy generator
const nxt = take(1)(xs);
 
return 0 < nxt.length ? (
Just(Tuple(nxt[0])(xs))
) : Nothing();
})()
) : Nothing();
};
 
 
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f => xs => ys =>
// A list with the length of the shorter of
xs.slice(
// xs and 0ys, Math.min(xs.length,defined ys.length)by zipping with a
).map((x// custom function, i)rather than =>with f(x)(ys[i]));the
// default tuple constructor.
xs => ys => {
const n = Math.min(length(xs), length(ys));
 
return Infinity > n ? (
(([as, bs]) => Array.from({
length: n
}, (_, i) => f(as[i])(
bs[i]
)))([xs, ys].map(
take(n)
))
) : zipWithGen(f)(xs)(ys);
};
 
 
// zipWithGen :: (a -> b -> c) ->
// Gen [a] -> Gen [b] -> Gen [c]
const zipWithGen = f => ga => gb => {
const go = function* (ma, mb) {
let
a = ma,
b = mb;
 
while (!a.Nothing && !b.Nothing) {
const
ta = a.Just,
tb = b.Just;
 
yield f(fst(ta))(fst(tb));
a = uncons(snd(ta));
b = uncons(snd(tb));
}
};
 
return go(uncons(ga), uncons(gb));
};
 
// MAIN ---
return main();
// return sj(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)
| |
| |
|}
 
9,659

edits