phylojs
Version:
A simple typescript library for phylogenetic trees
167 lines (166 loc) • 5.72 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Node = void 0;
// Node constructor
class Node {
/**
* The constructor of the `Node` class.
*
* @param {number} id node `id` number
*/
constructor(id) {
this.id = id;
this.parent = undefined;
this.children = [];
this.height = undefined;
this.branchLength = undefined;
this.label = undefined;
this.annotation = {};
this.hybridID = undefined;
this.rttDist = undefined;
}
// Node methods
/** Ensure nodes with unique IDs have unique hashes. */
toString() {
return `node#${this.id}`;
}
/**
* Appends child node to `children`
* @param {Node} child
*/
addChild(child) {
this.children.push(child);
child.parent = this;
}
/**
* Removes a node from `children`
* @param {Node} child
*/
removeChild(child) {
const idx = this.children.indexOf(child);
this.children.splice(idx, 1);
child.parent = undefined; // Set parent of the removed child to undefined
}
/** Checks if a node is root */
isRoot() {
return this.parent === undefined;
}
/** Check if a node is a Leaf */
isLeaf() {
return this.children.length === 0;
}
/** Checks if node only has one child */
isSingleton() {
return this.children.length === 1;
}
/** Checks if a node is a hybrid node */
isHybrid() {
return this.hybridID !== undefined;
}
/**
* Gets ancestral nodes
* @param {Node} node
*/
_getAncestors(node) {
if (node.isRoot()) {
return [node];
}
else {
return node.parent
? [node].concat(this._getAncestors(node.parent))
: [node];
}
}
/** Gets ancestral nodes. Wraps `_getAncestors` to handle concat types. */
getAncestors() {
return this._getAncestors(this);
}
/**
* Returns true if this node is left of the argument on the
* tree. If one node is the direct ancestor of the other,
* the result is undefined.
*
* @param {Node} other
*
*/
isLeftOf(other) {
const ancestors = this.getAncestors().reverse();
const otherAncestors = other.getAncestors().reverse();
let i;
for (i = 1; i < Math.min(ancestors.length, otherAncestors.length); i++) {
if (ancestors[i] != otherAncestors[i]) {
const mrca = ancestors[i - 1];
return (mrca.children.indexOf(ancestors[i]) <
mrca.children.indexOf(otherAncestors[i]));
}
}
return undefined;
}
/** Produce a deep copy of the clade below this node */
copy() {
const nodeCopy = new Node(this.id);
nodeCopy.height = this.height;
nodeCopy.branchLength = this.branchLength;
nodeCopy.label = this.label;
for (const key in this.annotation)
nodeCopy.annotation[key] = this.annotation[key];
nodeCopy.id = this.id;
nodeCopy.hybridID = this.hybridID;
for (let i = 0; i < this.children.length; i++)
nodeCopy.addChild(this.children[i].copy());
return nodeCopy;
}
/**
* Apply a function `f()` to each node in a subtree descending from `node` in pre-order
* @param {Node} node Node from which to apply `f()` pre-order
*/
applyPreOrder(f) {
const res = [];
const stack = [this]; // Initialize the stack with the root node.
while (stack.length > 0) {
const currentNode = stack.pop(); // Pop the last node from the stack.
if (!currentNode)
continue; // If the node is null or undefined, skip.
const thisRes = f(currentNode);
res.push(thisRes); // Apply f to the current node and collect the result.
// Push children to the stack in reverse order to maintain the correct traversal order.
for (let i = currentNode.children.length - 1; i >= 0; i--) {
stack.push(currentNode.children[i]);
}
}
return res;
}
/**
* Apply a function `f()` to each node in a subtree descending from `node` in post-order
* @param {Node} node Node from which to apply `f()` post-order
*/
applyPostOrder(f) {
const res = [];
const stack = [this]; // Initialize the stack with the root node.
const visited = new Set(); // Track visited nodes.
while (stack.length > 0) {
const currentNode = stack[stack.length - 1]; // Peek the last node from the stack.
if (!currentNode) {
stack.pop(); // If the node is null or undefined, remove it from the stack.
continue;
}
let allChildrenVisited = true;
for (let i = currentNode.children.length - 1; i >= 0; i--) {
if (!visited.has(currentNode.children[i])) {
stack.push(currentNode.children[i]); // Push unvisited children to the stack.
allChildrenVisited = false;
}
}
if (allChildrenVisited) {
const lastNode = stack.pop(); // Pop the last node from the stack.
if (lastNode) {
visited.add(lastNode); // Mark the node as visited.
const thisRes = f(lastNode);
res.push(thisRes); // Apply f to the current node and collect the result.
}
}
}
return res;
}
}
exports.Node = Node;