Display an outline as a nested table: Difference between revisions
Content added Content deleted
(→{{header|JavaScript}}: Updated primitives, reshaped to handle forests (multiple roots) and foreground parallels with other versions) |
|||
Line 673: | Line 673: | ||
=={{header|JavaScript}}== |
=={{header|JavaScript}}== |
||
<lang 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 () |
// main :: IO () |
||
const main = () => { |
const main = () => { |
||
Line 688: | Line 709: | ||
and write out a table with 'colspan' values |
and write out a table with 'colspan' values |
||
either as a wiki table, |
either as a wiki table, |
||
or as HTML.` |
or as HTML.`; |
||
return wikiTablesFromOutline([ |
|||
"#ffffe6", |
|||
"#ffebd2", |
|||
"#f0fff0", |
|||
"#e6ffff", |
|||
coloredKeyLines(Object.keys(dictColors)), |
|||
"#ffeeff" |
|||
])(outline); |
|||
))( |
|||
forestFromOutline(outline) |
|||
) |
|||
) |
|||
) |
|||
); |
|||
}; |
}; |
||
// |
// --------- TREE STRUCTURE FROM NESTED TEXT --------- |
||
// |
// forestFromIndentedLines :: [(Int, String)] -> |
||
// [Tree String] |
|||
const forestFromOutline = s => |
|||
const forestFromIndentedLines = tuples => { |
|||
// A list of trees, derived from an |
|||
const go = xs => |
|||
0 < xs.length ? (() => { |
|||
// 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 => { |
|||
Node(body)(go(tree)) |
|||
].concat(go(rest)); |
|||
})() : []; |
|||
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)] |
// indentLevelsFromLines :: [String] -> [(Int, String)] |
||
const indentLevelsFromLines = xs => { |
const indentLevelsFromLines = xs => { |
||
// A list of (indentLevel, Text) tuples. |
|||
const |
const |
||
pairs = xs.map |
pairs = xs.map( |
||
x => bimap(length)(cs => cs.join(""))( |
|||
)) |
span(isSpace)(list(x)) |
||
) |
|||
), |
|||
indentUnit = pairs.reduce( |
|||
(a, tpl) => { |
|||
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 -> Tree a -> Int -> Tree a |
|||
const paddedTree = padValue => |
|||
// a list of indented lines. |
|||
// All descendants expanded to same depth |
|||
// with empty nodes where needed. |
|||
node => depth => { |
|||
const go = n => tree => |
|||
1 < n ? (() => { |
|||
const children = nest(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)}` |
|||
); |
); |
||
}, |
|||
cellText = ctw => { |
|||
. |
const [color, tw] = Array.from(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 => { |
|||
// Node :: a -> [Tree a] -> Tree a |
|||
const Node = v => |
|||
// descends to the same depth. |
|||
// Constructor for a Tree node which connects a |
|||
// value of some kind to a list of zero or |
|||
// more child trees. |
|||
xs => ({ |
|||
type: "Node", |
|||
root: v, |
|||
nest: xs || [] |
|||
}); |
|||
return measuredTrees.map(go( |
|||
1 + maximumBy(x => root(x)[1].layerSum)( |
|||
// fmapTree :: (a -> b) -> Tree a -> Tree b |
|||
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 ---- |
|||
// |
// foldTree :: (a -> [b] -> 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 => f( |
|||
root(tree) |
|||
)( |
|||
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 => { |
|||
// A list of lists, grouping the root |
|||
orange: '#ffebd2', |
|||
// values of each level of the tree. |
|||
green: '#f0fff0', |
|||
const go = (a, node) => { |
|||
const [h, ...t] = 0 < a.length ? ( |
|||
pink: '#ffeeff', |
|||
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 |
// Just :: a -> Maybe a |
||
const Just = x => ({ |
const Just = x => ({ |
||
type: |
type: "Maybe", |
||
Nothing: false, |
Nothing: false, |
||
Just: x |
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 |
// Nothing :: Maybe a |
||
const Nothing = () => ({ |
const Nothing = () => ({ |
||
type: |
type: "Maybe", |
||
Nothing: true |
Nothing: true |
||
}); |
}); |
||
// Tuple (,) :: a -> b -> (a, b) |
// Tuple (,) :: a -> b -> (a, b) |
||
const Tuple = a => |
const Tuple = a => |
||
b => ({ |
|||
type: "Tuple", |
|||
"0": a, |
|||
"1": b, |
|||
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 |
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c |
||
const compose = (...fs) => |
const compose = (...fs) => |
||
// A function defined by the 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] |
// cycle :: [a] -> Generator [a] |
||
function* |
const cycle = function* (xs) { |
||
// An infinite repetition of xs, |
|||
// from which an arbitrary prefix |
|||
// may be taken. |
|||
const lng = xs.length; |
const lng = xs.length; |
||
let i = 0; |
let i = 0; |
||
while (true) { |
while (true) { |
||
yield |
yield xs[i]; |
||
i = (1 + i) % lng; |
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(); |
|||
}; |
}; |
||
// |
// first :: (a -> b) -> ((a, c) -> (b, c)) |
||
const |
const first = f => |
||
// A simple function lifted to one which applies |
// A simple function lifted to one which applies |
||
// to a tuple, transforming only its first item. |
// to a tuple, transforming only its first item. |
||
xy => |
xy => { |
||
xy[1] |
const tpl = Tuple(f(xy[0]))(xy[1]); |
||
); |
|||
return Array.isArray(xy) ? ( |
|||
Array.from(tpl) |
|||
const flip = f => |
|||
) : tpl; |
|||
}; |
|||
) : (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 |
// fst :: (a, b) -> a |
||
const fst = tpl => |
const fst = tpl => |
||
// 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 |
// isSpace :: Char -> Bool |
||
const isSpace = c => |
const isSpace = 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 |
// length :: [a] -> Int |
||
const length = xs => |
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 |
xs.length |
||
) : Infinity; |
) : 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] |
// lines :: String -> [String] |
||
const lines = s => |
const lines = s => |
||
// 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); |
|||
// |
// list :: StringOrArrayLike b => b -> [a] |
||
const |
const list = xs => |
||
// xs itself, if it is an Array, |
|||
// or an Array derived from xs. |
|||
Array.isArray(xs) ? ( |
|||
xs |
xs |
||
) : |
) : Array.from(xs || []); |
||
// max :: Ord a => a -> a -> a |
|||
const max = a => b => gt(b)(a) ? b : a; |
|||
// |
// lt (<) :: Ord a => a -> a -> Bool |
||
const |
const lt = a => |
||
b => a < 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); |
|||
}; |
|||
// |
// maximum :: Ord a => [a] -> a |
||
const |
const maximum = xs => ( |
||
// 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 |
// snd :: (a, b) -> b |
||
const snd = tpl => |
const snd = tpl => |
||
// Second member of a pair. |
|||
tpl[1]; |
|||
// span :: (a -> Bool) -> [a] -> ([a], [a]) |
// span :: (a -> Bool) -> [a] -> ([a], [a]) |
||
const span = p => |
const span = p => |
||
// Longest prefix of xs consisting of elements which |
|||
// all satisfy p, tupled with the remainder of xs. |
|||
return splitAt( |
|||
xs => { |
|||
const i = xs.findIndex(x => !p(x)); |
|||
)(0) |
|||
)(xs); |
|||
}; |
|||
return -1 !== i ? ( |
|||
Tuple(xs.slice(0, i))( |
|||
xs.slice(i) |
|||
) |
|||
); |
) : Tuple(xs)([]); |
||
}; |
|||
// str :: a -> String |
|||
const str = x => x.toString(); |
|||
// succ :: Enum a => a -> a |
|||
const succ = x => 1 + x; |
|||
// tail :: [a] -> [a] |
// tail :: [a] -> [a] |
||
const tail = xs => |
const tail = xs => |
||
// 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 -> [a] -> [a] |
||
// take :: Int -> String -> String |
// take :: Int -> String -> String |
||
const take = n |
const take = n => |
||
// 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) |
xs.slice(0, n) |
||
) : [].concat |
) : [].concat(...Array.from({ |
||
length: n |
length: n |
||
}, () => { |
}, () => { |
||
const x = xs.next(); |
const x = xs.next(); |
||
return x.done ? [] : [x.value]; |
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]); |
|||
}; |
|||
// |
// uncons :: [a] -> Maybe (a, [a]) |
||
const |
const uncons = xs => { |
||
// Just a tuple of the head of xs and its tail, |
|||
// Or Nothing if xs is an empty list. |
|||
const lng = length(xs); |
|||
return (0 < lng) ? ( |
|||
Infinity > lng ? ( |
|||
// Finite list |
|||
Just(Tuple(xs[0])(xs.slice(1))) |
|||
) : (() => { |
|||
// 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] |
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] |
||
const zipWith = f |
const zipWith = f => |
||
// A list with the length of the shorter of |
|||
xs.slice( |
|||
// xs and ys, defined by zipping with a |
|||
// custom function, rather than with 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 --- |
// MAIN --- |
||
return main(); |
return main(); |
||
// return sj(main()); |
|||
})();</lang> |
})();</lang> |
||
{{Out}} |
{{Out}} |
||
{| class="wikitable" style="text-align: |
{| class="wikitable" style="text-align:center;" |
||
|- |
|- |
||
| style="background: |
| style="background:#ffffe6; " colspan=7 | Display an outline as a nested table. |
||
|- |
|- |
||
| style="background: |
| style="background:#ffebd2; " colspan=3 | Parse the outline to a tree, |
||
| style="background: |
| style="background:#f0fff0; " colspan=2 | count the leaves descending from each node, |
||
| style="background: |
| style="background:#e6ffff; " colspan=2 | and write out a table with 'colspan' values |
||
|- |
|- |
||
| style="background: |
| style="background:#ffebd2; " | measuring the indent of each line, |
||
| style="background: |
| style="background:#ffebd2; " | translating the indentation to a nested structure, |
||
| style="background: |
| style="background:#ffebd2; " | and padding the tree to even depth. |
||
| style="background: |
| style="background:#f0fff0; " | defining the width of a leaf as 1, |
||
| style="background: |
| style="background:#f0fff0; " | and the width of a parent node as a sum. |
||
| style="background: |
| style="background:#e6ffff; " | either as a wiki table, |
||
| style="background: |
| style="background:#e6ffff; " | or as HTML. |
||
|- |
|- |
||
| |
| | |
||
| |
| | |
||
| |
| | |
||
| |
| | |
||
| style="background: |
| style="background:#f0fff0; " | (The sum of the widths of its children) |
||
| |
| | |
||
| |
| | |
||
|} |
|} |
||