Skip to content

Instantly share code, notes, and snippets.

@miraclx
Last active August 4, 2024 01:41
Show Gist options
  • Select an option

  • Save miraclx/9fc4639a6104ad6ec40fc52753ac8e6a to your computer and use it in GitHub Desktop.

Select an option

Save miraclx/9fc4639a6104ad6ec40fc52753ac8e6a to your computer and use it in GitHub Desktop.
Generate a n-ary tree under specific constraints

n-ary

Generate a n-ary tree under specific constaints

Generate a n-ary tree under specific constaints

Usage: node n-ary.js [opts] <leaves> <branches>

Options:
  -a              Ascending
  -r              Reverse
  -l              Show level outlines
  -t <spec>       Table representation (default: `h`)
                  - `v` for vertical   (`v+` to invert)
                  - `h` for horizontal (`h+` to invert)
  --raw           Print raw tree to stdout, do not write to file
  -o <file>       Output file (default: `tree.md`)
  --debug         Print all nodes
  -h, --help      Show this help

Generated Tree

Parameters
  • Leaves: 3
  • Branches: 2
  • Order: Descending
  • Level Outlines: no
  • Table Representation: Horizontal (top-down)
node n-ary.js 3 2

Interactive View

graph TB
    n1(1)
    n2(2)
    n3(3)
    n4(4) --> n1 & n2
    n5(5) --> n3
    n6(6) --> n4 & n5
Loading

Generated Tree

Parameters
  • Leaves: 3
  • Branches: 2
  • Order: Descending (Reversed)
  • Level Outlines: no
  • Table Representation: Horizontal (top-down)
node n-ary.js 3 2 -r

Interactive View

graph TB
    n3(3)
    n2(2)
    n1(1)
    n5(5) --> n3 & n2
    n4(4) --> n1
    n6(6) --> n5 & n4
Loading

Generated Tree

Parameters
  • Leaves: 5
  • Branches: 2
  • Order: Ascending
  • Level Outlines: yes
  • Table Representation: Vertical (right-left)
node n-ary.js 5 2 -a -l -t v

Interactive View

graph LR
  subgraph g4["Level 4"]
    style g1 fill:none
    n7(7)
    n8(8)
    n9(9)
    n10(10)
    n11(11)
  end
  subgraph g3["Level 3"]
    style g2 fill:none
    n4(4) --> n7 & n8
    n5(5) --> n9 & n10
    n6(6) --> n11
  end
  subgraph g2["Level 2"]
    style g3 fill:none
    n2(2) --> n4 & n5
    n3(3) --> n6
  end
  subgraph g1["Level 1"]
    style g4 fill:none
    n1(1) --> n2 & n3
  end
Loading

Generated Tree

Parameters
  • Leaves: 5
  • Branches: 2
  • Order: Ascending (Reversed)
  • Level Outlines: yes
  • Table Representation: Horizontal (top-down)
node n-ary.js 5 2 -a -r -l

Interactive View

graph TB
  subgraph g4["Level 4"]
    style g1 fill:none
    n11(11)
    n10(10)
    n9(9)
    n8(8)
    n7(7)
  end
  subgraph g3["Level 3"]
    style g2 fill:none
    n6(6) --> n11 & n10
    n5(5) --> n9 & n8
    n4(4) --> n7
  end
  subgraph g2["Level 2"]
    style g3 fill:none
    n3(3) --> n6 & n5
    n2(2) --> n4
  end
  subgraph g1["Level 1"]
    style g4 fill:none
    n1(1) --> n3 & n2
  end
Loading

Generated Tree

Parameters
  • Leaves: 9
  • Branches: 2
  • Order: Ascending
  • Level Outlines: no
  • Table Representation: Horizontal (top-down)
node n-ary.js 9 2 -a

Interactive View

graph TB
    n12(12)
    n13(13)
    n14(14)
    n15(15)
    n16(16)
    n17(17)
    n18(18)
    n19(19)
    n20(20)
    n7(7) --> n12 & n13
    n8(8) --> n14 & n15
    n9(9) --> n16 & n17
    n10(10) --> n18 & n19
    n11(11) --> n20
    n4(4) --> n7 & n8
    n5(5) --> n9 & n10
    n6(6) --> n11
    n2(2) --> n4 & n5
    n3(3) --> n6
    n1(1) --> n2 & n3
Loading

Generated Tree

Parameters
  • Leaves: 13
  • Branches: 3
  • Order: Descending
  • Level Outlines: yes
  • Table Representation: Horizontal (top-down)
node n-ary.js 13 3 -l

Interactive View

graph TB
  subgraph g1["Level 1"]
    style g1 fill:none
    n1(1)
    n2(2)
    n3(3)
    n4(4)
    n5(5)
    n6(6)
    n7(7)
    n8(8)
    n9(9)
    n10(10)
    n11(11)
    n12(12)
    n13(13)
  end
  subgraph g2["Level 2"]
    style g2 fill:none
    n14(14) --> n1 & n2 & n3
    n15(15) --> n4 & n5 & n6
    n16(16) --> n7 & n8 & n9
    n17(17) --> n10 & n11 & n12
    n18(18) --> n13
  end
  subgraph g3["Level 3"]
    style g3 fill:none
    n19(19) --> n14 & n15 & n16
    n20(20) --> n17 & n18
  end
  subgraph g4["Level 4"]
    style g4 fill:none
    n21(21) --> n19 & n20
  end
Loading

Generated Tree

Parameters
  • Leaves: 50
  • Branches: 5
  • Order: Ascending
  • Level Outlines: no
  • Table Representation: Horizontal (top-down)
node n-ary.js 50 5 -a

Interactive View

graph TB
    n14(14)
    n15(15)
    n16(16)
    n17(17)
    n18(18)
    n19(19)
    n20(20)
    n21(21)
    n22(22)
    n23(23)
    n24(24)
    n25(25)
    n26(26)
    n27(27)
    n28(28)
    n29(29)
    n30(30)
    n31(31)
    n32(32)
    n33(33)
    n34(34)
    n35(35)
    n36(36)
    n37(37)
    n38(38)
    n39(39)
    n40(40)
    n41(41)
    n42(42)
    n43(43)
    n44(44)
    n45(45)
    n46(46)
    n47(47)
    n48(48)
    n49(49)
    n50(50)
    n51(51)
    n52(52)
    n53(53)
    n54(54)
    n55(55)
    n56(56)
    n57(57)
    n58(58)
    n59(59)
    n60(60)
    n61(61)
    n62(62)
    n63(63)
    n4(4) --> n14 & n15 & n16 & n17 & n18
    n5(5) --> n19 & n20 & n21 & n22 & n23
    n6(6) --> n24 & n25 & n26 & n27 & n28
    n7(7) --> n29 & n30 & n31 & n32 & n33
    n8(8) --> n34 & n35 & n36 & n37 & n38
    n9(9) --> n39 & n40 & n41 & n42 & n43
    n10(10) --> n44 & n45 & n46 & n47 & n48
    n11(11) --> n49 & n50 & n51 & n52 & n53
    n12(12) --> n54 & n55 & n56 & n57 & n58
    n13(13) --> n59 & n60 & n61 & n62 & n63
    n2(2) --> n4 & n5 & n6 & n7 & n8
    n3(3) --> n9 & n10 & n11 & n12 & n13
    n1(1) --> n2 & n3
Loading
let fs = require('node:fs');
let zlib = require('node:zlib');
class Node {
constructor(id, level) {
this.id = id;
this.level = level;
this.children = [];
}
push(childId) {
this.children.push(childId);
}
}
function generateNaryTree({ leaves, branches, reverse }) {
if (leaves <= 0 || branches <= 0) return [];
if (branches === 1 && leaves > 1) return "Branches cannot be 1 when leaves are greater than 1";
let nodes = [];
let nodeId = 0;
let level = 0;
// Helper function to create nodes
function createNodes(count, level) {
let createdNodes = [];
let max = count + nodeId;
let min = nodeId;
let offset = id => reverse ? (max + min) - id : id + 1;
for (let i = 0; i < count; i++) {
createdNodes.push(new Node(offset(nodeId++, min, max, reverse), level));
}
return createdNodes;
}
// Determine the number of nodes at the last level
let lastLevelCount = leaves;
let currentLevelNodes = createNodes(lastLevelCount, ++level);
nodes.push(...currentLevelNodes);
// Generate nodes for levels above the last level
while (currentLevelNodes.length > 1) {
let nextLevelCount = Math.ceil(currentLevelNodes.length / branches);
let parentNodes = createNodes(nextLevelCount, ++level);
nodes.push(...parentNodes);
for (let i = 0; i < currentLevelNodes.length; i++) {
let parentNode = parentNodes[Math.floor(i / branches)];
parentNode.push(currentLevelNodes[i].id);
}
currentLevelNodes = parentNodes;
}
return nodes;
}
let visOrd = repr => repr == "v" ? "LR" : repr == "v+" ? "RL" : repr == "h" ? "TB" : repr == "h+" ? "BT" : null;
function toMermaid({tree, branches, repr, ascending, outlineLevels}) {
let offset = (id, max) => ascending ? max - id + 1: id;
let ord = visOrd(repr);
if (!ord) return ord;
let graph = `graph ${ord}`;
let level, maxHeight = Math.floor((Math.log(tree.length) / Math.log(branches)) + 1);
for (let entry of tree) {
let id = offset(entry.id, tree.length);
let lhs = `n${id}(${id})`;
let rhs = entry.children.length > 0 ? ` --> ${entry.children.map((c) => `n${offset(c, tree.length)}`).join(" & ")}` : "";
if (outlineLevels && level !== entry.level) {
if (level) graph += "\n end";
level = entry.level;
let level$ = ascending ? maxHeight - level + 1 : level;
graph += `\
\n subgraph g${level$}["Level ${level$}"]\
\n style g${level} fill:none`;
}
graph += `\n ${lhs}${rhs}`;
}
return outlineLevels ? graph + "\n end" : graph;
}
function linked(code) {
let mermaidOpts = {
theme: "default", // default, neutral, forest, dark, base
};
let data = {
code,
mermaid: JSON.stringify(mermaidOpts),
autoSync: true,
rough: false,
updateDiagram: false,
panZoom: true
};
let payload = new TextEncoder().encode(JSON.stringify(data));
let encoded = zlib.deflateSync(payload, {level: 9});
let fragment = encoded.toString("base64").replace(/=/g, "").replace(/[+\/]/g, e=>e == "+" ? "-" : "_");
return `https://mermaid.live/view#pako:${fragment}`;
}
const DEFAULT_FILE = "tree.md";
const DEFAULT_REPR = "h";
function main() {
let args = process.argv.slice(2);
if (args.length < 2 || args.includes("-h") || args.includes("--help")) {
console.error("Generate a n-ary tree under specific constaints");
console.error();
console.error("Usage: node n-ary.js [opts] <leaves> <branches>");
console.error();
console.error("Options:");
console.error(` -a Ascending`);
console.error(` -r Reverse`);
console.error(` -l Show level outlines`);
console.error(` -t <spec> Table representation (default: \`${DEFAULT_REPR}\`)`);
console.error(` - \`v\` for vertical (\`v+\` to invert)`);
console.error(` - \`h\` for horizontal (\`h+\` to invert)`);
console.error(` --raw Print raw tree to stdout, do not write to file`);
console.error(` -o <file> Output file (default: \`${DEFAULT_FILE}\`)`);
console.error(" --debug Print all nodes");
console.error(" -h, --help Show this help");
process.exit(1);
}
let leaves = parseInt(args[0]);
let branches = parseInt(args[1]);
let ascending = args.includes("-a");
let reverse = args.includes("-r");
let outlineLevels = args.includes("-l");
let tree = generateNaryTree({leaves, branches, reverse: reverse ? !ascending : ascending});
if (typeof tree === "string") {
console.error(tree);
process.exit(1);
}
let file = !args.includes("--raw") && DEFAULT_FILE;
let idx;
if (file && (idx = args.indexOf("-o")) !== -1) {
file = args[idx + 1];
}
let repr = DEFAULT_REPR;
if ((idx = args.indexOf("-t")) !== -1) {
repr = args[idx + 1];
}
let treeVis;
if (!(treeVis = toMermaid({tree, branches, repr, ascending, outlineLevels}))) {
console.error("Invalid table representation format");
process.exit(1);
}
if (args.includes("--debug")) {
for (let node of tree) {
console.error(node);
// console.error(`${node.id} --> ${JSON.stringify(node.children)} (nodes: ${node.level})`);
}
console.error("-".repeat(30));
}
let link = linked(treeVis);
let data = `\
# Generated Tree
<details>
<summary> Parameters </summary>
- Leaves: ${leaves}
- Branches: ${branches}
- Order: ${ascending ? "Ascending" : "Descending"}${reverse ? " (Reversed)" : ""}
- Level Outlines: ${outlineLevels ? "yes" : "no"}
- Table Representation: ${
repr.startsWith("v")
? `Vertical ${repr.endsWith("+") ? "(left-right)" : "(right-left)"}`
: `Horizontal ${repr.endsWith("+") ? "(bottom-up)" : "(top-down)"}`
}
\`\`\`console
node n-ary.js ${leaves} ${branches}\
${ascending ? " -a" : ""}\
${reverse ? " -r" : ""}\
${outlineLevels ? " -l" : ""}\
${repr !== DEFAULT_REPR ? ` -t ${repr}` : ""}\
${file !== DEFAULT_FILE ? ` -o ${file}` : ""}
\`\`\`
</details>
<div align="center">
[Interactive View](https://mermaid.live/view#pako:eNo908tq3EAQBdBfEb0I1zAG1aP1WngR8gfJKsxGeOQZgyUNirQIxv8epqsqu-IW0unbQp_pdb1MaUjXbbzfql_fz0tVLQR6KgODbRCIDQq1ISPb0KCxoUVrQ4fOhh69DVSDah8J5G8nBvn7SUAukILcoAxyhRqQO9SCXKIO5Bb1INe4BrvGBI4uDHaNBewaK9g1zmDXuAG7xi24faqen1-qhapv1cIWd-DOY3nEanEP7j3Oj7ix66shtcftI-4sJgh53D9iqi1nCIdZUDJVBCKxKCyZKwrRWBSYXM6QHItCk9sNpIlFwdnxFhKF2Ro73kGiMhecHe8hUZoLzoZrDY3aXHA2XAkaxbngYrgyNJpLwcVwFWg0l4KL4arQaC4FF8czNJpLwcXxBhrNpbeohUZnrcvnJFt00OisXBZiix4anVXLItt_USNHZ7WTZEKOtlpOonaSzMjRVss1ZLuGLMjRNpdryJxOaZ62eXy_pCF9ntN-m-bpnIZzukxv4_Gxn9NXOqXx2Neff5fXNOzbMZ3Sth7XWxrexo8_0ykd98u4Tz_ex-s2zv_T-7j8XtfZHvn6ByNE-do)
\`\`\`mermaid
${treeVis}
\`\`\`
</div>
`;
if (file) {
fs.writeFileSync(file, data);
console.log(`Tree written to \`${file}\``);
} else console.log(treeVis);
console.error("-".repeat(30));
console.error("View interactive diagram at:");
console.error(" -", link);
console.error("-".repeat(30));
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment