How to resolve the algorithm Display an outline as a nested table step by step in the JavaScript programming language
How to resolve the algorithm Display an outline as a nested table step by step in the JavaScript programming language
Table of Contents
Problem Statement
The graphic representation of outlines is a staple of mind-mapping and the planning of papers, reports, and speeches. Given a outline with at least 3 levels of indentation, for example: write a program in your language which translates your outline into a nested table, with WikiTable or HTML colspan values attached (where needed) to parent nodes in the nested table. The WikiTable at the top of this page was generated from the indented outline shown above, producing the following markup string: Use background color to distinguish the main stages of your outline, so that the subtree of each node at level two is consistently colored, and the edges between adjacent subtrees are immediately revealed.
Display your nested table on this page.
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Display an outline as a nested table step by step in the JavaScript programming language
The provided code is a functional JavaScript program that takes an outline in a specific format as input and generates a nested table from it.
Here's a breakdown of how the code works:
-
Parsing the Outline:
wikiTablesFromOutline
: This function takes a list of color swatches as input and returns a function that takes an outline as input.outline
: This is the input outline that needs to be parsed.forestFromIndentedLines
: This function takes a list of tuples (indentation level, line) and returns a list of tree nodes. It parses the outline, measuring the indentation of each line and building a tree structure where the indentation level represents the nesting of nodes.indentLevelsFromLines
: This function takes a list of lines and returns a list of tuples (indentation level, line). It calculates the indentation levels for each line in the outline.
-
Padding the Tree:
paddedTree
: This function takes a padding value and a tree as inputs and returns a new tree with empty nodes added to ensure that all descendants have the same depth.treeDepth
: This function calculates the depth of a tree.widthLabelledTree
: This function labels each node in a tree with the width of its subtree.
-
Applying the Color Swatch:
paintedTree
: This function takes a list of color swatches and a tree as inputs and returns a new tree with the nodes colored according to the swatch.
-
Generating the Wiki Table:
wikiTableFromRows
: This function takes a list of lists of cells and returns a wiki table string.- Each cell in the table consists of a color and a width, where the color is used to style the cell background and the width is used to set the colspan attribute.
-
Tree Manipulation Functions:
Node
: Constructor for a tree node with a value (root) and a list of child nodes (nest).fmapTree
: Applies a function to each root value in a tree, preserving the tree structure.foldTree
: Folds a tree using a function that takes the root value and a list of child results and returns a new result.levels
: Returns a list of lists of root values, grouped by their level in the tree.nest
: Returns the list of child nodes for a given tree node.root
: Returns the root value of a tree node.
-
Generic Functions:
Just
: Constructor for a Maybe type representing a Just value.Nothing
: Constructor for a Maybe type representing a Nothing value.Tuple
: Constructor for a tuple.apFn
: Applicative instance for functions.ap
: Applies a function to the results of two other functions, given the functions themselves.bimap
: Applies two functions to a tuple, one to the first element and the other to the second element.compose
: Composes a list of functions from right to left.cycle
: Generates an infinite repetition of a given array, from which an arbitrary prefix may be taken.first
: Lifts a function to one that applies to a tuple, transforming only its first item.fst
: Returns the first element of a tuple.isSpace
: Checks if a character is a white space character.length
: Returns the length of an array or string, or Infinity for generators.lines
: Splits a string into a list of strings by newlines.list
: Converts an array-like object to an array.lt
: Compares two values using the less-than operator.maximum
: Returns the largest value in a non-empty list.snd
: Returns the second element of a tuple.span
: Separates a list into a prefix that satisfies a given predicate and the remaining list.tail
: Returns a new list consisting of all items of xs except the first.take
: Returns the first n elements of a list or string.uncons
: Pattern-matches a list to a tuple of its head and tail, or Nothing if the list is empty.zipWith
: Zips two lists together using a custom function.zipWithGen
: Zips two generators together using a custom function.
-
Main Function:
main
: This function calls the outline-to-table conversion function with a predefined color swatch and logs the generated table to the console.
When you execute this code, it will take an outline, parse it into a nested tree structure, apply a color swatch to it, and generate a wiki table from the colored tree. The generated table will be displayed with colored cells according to the provided color swatch.
Source code in the javascript programming language
(() => {
"use strict";
// ----------- NESTED TABLES FROM OUTLINE ------------
// wikiTablesFromOutline :: [String] -> String -> String
const wikiTablesFromOutline = colorSwatch =>
outline => forestFromIndentedLines(
indentLevelsFromLines(lines(outline))
)
.map(wikiTableFromTree(colorSwatch))
.join("\n\n");
// wikiTableFromTree :: [String] -> Tree String -> String
const wikiTableFromTree = colorSwatch =>
compose(
wikiTableFromRows,
levels,
paintedTree(colorSwatch),
widthLabelledTree,
ap(paddedTree(""))(treeDepth)
);
// ---------------------- TEST -----------------------
// main :: IO ()
const main = () => {
const 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.`;
return wikiTablesFromOutline([
"#ffffe6",
"#ffebd2",
"#f0fff0",
"#e6ffff",
"#ffeeff"
])(outline);
};
// --------- TREE STRUCTURE FROM NESTED TEXT ---------
// forestFromIndentedLines :: [(Int, String)] ->
// [Tree String]
const forestFromIndentedLines = tuples => {
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.
return [
Node(body)(go(tree))
].concat(go(rest));
})() : [];
return go(tuples);
};
// indentLevelsFromLines :: [String] -> [(Int, String)]
const indentLevelsFromLines = xs => {
const
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 ------------
// paddedTree :: a -> Tree a -> Int -> Tree a
const paddedTree = padValue =>
// All descendants expanded to same depth
// with empty nodes where needed.
node => depth => {
const go = n => tree =>
1 < n ? (() => {
const children = nest(tree);
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 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 ------------------
// Node :: a -> [Tree a] -> Tree a
const Node = v =>
// 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 || []
});
// fmapTree :: (a -> b) -> Tree a -> Tree b
const fmapTree = f => {
// 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;
};
// foldTree :: (a -> [b] -> b) -> Tree a -> b
const foldTree = f => {
// The catamorphism on trees. A summary
// value obtained by a depth-first fold.
const go = tree => f(
root(tree)
)(
nest(tree).map(go)
);
return go;
};
// levels :: Tree a -> [[a]]
const levels = tree => {
// A list of lists, grouping the root
// values of each level of the tree.
const go = (a, node) => {
const [h, ...t] = 0 < a.length ? (
a
) : [
[],
[]
];
return [
[node.root, ...h],
...node.nest.slice(0)
.reverse()
.reduce(go, t)
];
};
return go([], tree);
};
// nest :: Tree a -> [a]
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
});
// Nothing :: Maybe a
const Nothing = () => ({
type: "Maybe",
Nothing: true
});
// Tuple (,) :: a -> b -> (a, b)
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
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
);
// cycle :: [a] -> Generator [a]
const cycle = function* (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;
}
};
// first :: (a -> b) -> ((a, c) -> (b, c))
const first = f =>
// A simple function lifted to one which applies
// to a tuple, transforming only its first item.
xy => {
const tpl = Tuple(f(xy[0]))(xy[1]);
return Array.isArray(xy) ? (
Array.from(tpl)
) : tpl;
};
// fst :: (a, b) -> a
const fst = tpl =>
// First member of a pair.
tpl[0];
// isSpace :: Char -> Bool
const isSpace = c =>
// True if c is a white space character.
(/\s/u).test(c);
// length :: [a] -> Int
const length = xs =>
// 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
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
xs.length
) : Infinity;
// lines :: String -> [String]
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)
) : [];
// list :: StringOrArrayLike b => b -> [a]
const list = xs =>
// xs itself, if it is an Array,
// or an Array derived from xs.
Array.isArray(xs) ? (
xs
) : Array.from(xs || []);
// lt (<) :: Ord a => a -> a -> Bool
const lt = a =>
b => a < b;
// maximum :: Ord a => [a] -> a
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));
// snd :: (a, b) -> b
const snd = tpl =>
// Second member of a pair.
tpl[1];
// span :: (a -> Bool) -> [a] -> ([a], [a])
const span = p =>
// Longest prefix of xs consisting of elements which
// all satisfy p, tupled with the remainder of xs.
xs => {
const i = xs.findIndex(x => !p(x));
return -1 !== i ? (
Tuple(xs.slice(0, i))(
xs.slice(i)
)
) : Tuple(xs)([]);
};
// tail :: [a] -> [a]
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 -> String -> String
const take = n =>
// The first n elements of a list,
// string of characters, or stream.
xs => "GeneratorFunction" !== xs
.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat(...Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
// uncons :: [a] -> Maybe (a, [a])
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]
const zipWith = f =>
// A list with the length of the shorter of
// 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 ---
return main();
})();
You may also check:How to resolve the algorithm Reverse a string step by step in the Racket programming language
You may also check:How to resolve the algorithm Maximum triangle path sum step by step in the Picat programming language
You may also check:How to resolve the algorithm Non-decimal radices/Output step by step in the MATLAB / Octave programming language
You may also check:How to resolve the algorithm Proper divisors step by step in the ALGOL 68 programming language
You may also check:How to resolve the algorithm Roman numerals/Encode step by step in the Action! programming language