Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Js Tree Algo. #314

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions tree/breadth-first-search/__test__/breadthFirstSearch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import BinaryTreeNode from '../../../../data-structures/tree/BinaryTreeNode';
import breadthFirstSearch from '../breadthFirstSearch';

describe('breadthFirstSearch', () => {
it('should perform BFS operation on tree', () => {
const nodeA = new BinaryTreeNode('A');
const nodeB = new BinaryTreeNode('B');
const nodeC = new BinaryTreeNode('C');
const nodeD = new BinaryTreeNode('D');
const nodeE = new BinaryTreeNode('E');
const nodeF = new BinaryTreeNode('F');
const nodeG = new BinaryTreeNode('G');

nodeA.setLeft(nodeB).setRight(nodeC);
nodeB.setLeft(nodeD).setRight(nodeE);
nodeC.setLeft(nodeF).setRight(nodeG);

// In-order traversing.
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');

const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();

// Traverse tree without callbacks first to check default ones.
breadthFirstSearch(nodeA);

// Traverse tree with callbacks.
breadthFirstSearch(nodeA, {
enterNode: enterNodeCallback,
leaveNode: leaveNodeCallback,
});

expect(enterNodeCallback).toHaveBeenCalledTimes(7);
expect(leaveNodeCallback).toHaveBeenCalledTimes(7);

// Check node entering.
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B');
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('C');
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('D');
expect(enterNodeCallback.mock.calls[4][0].value).toEqual('E');
expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F');
expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G');

// Check node leaving.
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('B');
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C');
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('D');
expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('E');
expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('F');
expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('G');
});

it('allow users to redefine node visiting logic', () => {
const nodeA = new BinaryTreeNode('A');
const nodeB = new BinaryTreeNode('B');
const nodeC = new BinaryTreeNode('C');
const nodeD = new BinaryTreeNode('D');
const nodeE = new BinaryTreeNode('E');
const nodeF = new BinaryTreeNode('F');
const nodeG = new BinaryTreeNode('G');

nodeA.setLeft(nodeB).setRight(nodeC);
nodeB.setLeft(nodeD).setRight(nodeE);
nodeC.setLeft(nodeF).setRight(nodeG);

// In-order traversing.
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');

const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();

// Traverse tree without callbacks first to check default ones.
breadthFirstSearch(nodeA);

// Traverse tree with callbacks.
breadthFirstSearch(nodeA, {
allowTraversal: (node, child) => {
// Forbid traversing left half of the tree.
return child.value !== 'B';
},
enterNode: enterNodeCallback,
leaveNode: leaveNodeCallback,
});

expect(enterNodeCallback).toHaveBeenCalledTimes(4);
expect(leaveNodeCallback).toHaveBeenCalledTimes(4);

// Check node entering.
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C');
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F');
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G');

// Check node leaving.
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('C');
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('F');
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('G');
});
});
58 changes: 58 additions & 0 deletions tree/breadth-first-search/breadthFirstSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Queue from '../../../data-structures/queue/Queue';

/**
* @typedef {Object} Callbacks
* @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal -
* Determines whether BFS should traverse from the node to its child.
* @property {function(node: BinaryTreeNode)} enterNode - Called when BFS enters the node.
* @property {function(node: BinaryTreeNode)} leaveNode - Called when BFS leaves the node.
*/

/**
* @param {Callbacks} [callbacks]
* @returns {Callbacks}
*/
function initCallbacks(callbacks = {}) {
const initiatedCallback = callbacks;

const stubCallback = () => {};
const defaultAllowTraversal = () => true;

initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal;
initiatedCallback.enterNode = callbacks.enterNode || stubCallback;
initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback;

return initiatedCallback;
}

/**
* @param {BinaryTreeNode} rootNode
* @param {Callbacks} [originalCallbacks]
*/
export default function breadthFirstSearch(rootNode, originalCallbacks) {
const callbacks = initCallbacks(originalCallbacks);
const nodeQueue = new Queue();

// Do initial queue setup.
nodeQueue.enqueue(rootNode);

while (!nodeQueue.isEmpty()) {
const currentNode = nodeQueue.dequeue();

callbacks.enterNode(currentNode);

// Add all children to the queue for future traversals.

// Traverse left branch.
if (currentNode.left && callbacks.allowTraversal(currentNode, currentNode.left)) {
nodeQueue.enqueue(currentNode.left);
}

// Traverse right branch.
if (currentNode.right && callbacks.allowTraversal(currentNode, currentNode.right)) {
nodeQueue.enqueue(currentNode.right);
}

callbacks.leaveNode(currentNode);
}
}
102 changes: 102 additions & 0 deletions tree/depth-first-search/__test__/depthFirstSearch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import BinaryTreeNode from '../../../../data-structures/tree/BinaryTreeNode';
import depthFirstSearch from '../depthFirstSearch';

describe('depthFirstSearch', () => {
it('should perform DFS operation on tree', () => {
const nodeA = new BinaryTreeNode('A');
const nodeB = new BinaryTreeNode('B');
const nodeC = new BinaryTreeNode('C');
const nodeD = new BinaryTreeNode('D');
const nodeE = new BinaryTreeNode('E');
const nodeF = new BinaryTreeNode('F');
const nodeG = new BinaryTreeNode('G');

nodeA.setLeft(nodeB).setRight(nodeC);
nodeB.setLeft(nodeD).setRight(nodeE);
nodeC.setLeft(nodeF).setRight(nodeG);

// In-order traversing.
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');

const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();

// Traverse tree without callbacks first to check default ones.
depthFirstSearch(nodeA);

// Traverse tree with callbacks.
depthFirstSearch(nodeA, {
enterNode: enterNodeCallback,
leaveNode: leaveNodeCallback,
});

expect(enterNodeCallback).toHaveBeenCalledTimes(7);
expect(leaveNodeCallback).toHaveBeenCalledTimes(7);

// Check node entering.
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B');
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('D');
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('E');
expect(enterNodeCallback.mock.calls[4][0].value).toEqual('C');
expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F');
expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G');

// Check node leaving.
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('D');
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('E');
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('B');
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('F');
expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('G');
expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('C');
expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('A');
});

it('allow users to redefine node visiting logic', () => {
const nodeA = new BinaryTreeNode('A');
const nodeB = new BinaryTreeNode('B');
const nodeC = new BinaryTreeNode('C');
const nodeD = new BinaryTreeNode('D');
const nodeE = new BinaryTreeNode('E');
const nodeF = new BinaryTreeNode('F');
const nodeG = new BinaryTreeNode('G');

nodeA.setLeft(nodeB).setRight(nodeC);
nodeB.setLeft(nodeD).setRight(nodeE);
nodeC.setLeft(nodeF).setRight(nodeG);

// In-order traversing.
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');

const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();

// Traverse tree without callbacks first to check default ones.
depthFirstSearch(nodeA);

// Traverse tree with callbacks.
depthFirstSearch(nodeA, {
allowTraversal: (node, child) => {
// Forbid traversing left part of the tree.
return child.value !== 'B';
},
enterNode: enterNodeCallback,
leaveNode: leaveNodeCallback,
});

expect(enterNodeCallback).toHaveBeenCalledTimes(4);
expect(leaveNodeCallback).toHaveBeenCalledTimes(4);

// Check node entering.
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C');
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F');
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G');

// Check node leaving.
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('F');
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('G');
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C');
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('A');
});
});
76 changes: 76 additions & 0 deletions tree/depth-first-search/depthFirstSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @typedef {Object} TraversalCallbacks
*
* @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal
* - Determines whether DFS should traverse from the node to its child.
*
* @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node.
*
* @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node.
*/

/**
* Extend missing traversal callbacks with default callbacks.
*
* @param {TraversalCallbacks} [callbacks] - The object that contains traversal callbacks.
* @returns {TraversalCallbacks} - Traversal callbacks extended with defaults callbacks.
*/
function initCallbacks(callbacks = {}) {
// Init empty callbacks object.
const initiatedCallbacks = {};

// Empty callback that we will use in case if user didn't provide real callback function.
const stubCallback = () => {};
// By default we will allow traversal of every node
// in case if user didn't provide a callback for that.
const defaultAllowTraversalCallback = () => true;

// Copy original callbacks to our initiatedCallbacks object or use default callbacks instead.
initiatedCallbacks.allowTraversal = callbacks.allowTraversal || defaultAllowTraversalCallback;
initiatedCallbacks.enterNode = callbacks.enterNode || stubCallback;
initiatedCallbacks.leaveNode = callbacks.leaveNode || stubCallback;

// Returned processed list of callbacks.
return initiatedCallbacks;
}

/**
* Recursive depth-first-search traversal for binary.
*
* @param {BinaryTreeNode} node - binary tree node that we will start traversal from.
* @param {TraversalCallbacks} callbacks - the object that contains traversal callbacks.
*/
export function depthFirstSearchRecursive(node, callbacks) {
// Call the "enterNode" callback to notify that the node is going to be entered.
callbacks.enterNode(node);

// Traverse left branch only if case if traversal of the left node is allowed.
if (node.left && callbacks.allowTraversal(node, node.left)) {
depthFirstSearchRecursive(node.left, callbacks);
}

// Traverse right branch only if case if traversal of the right node is allowed.
if (node.right && callbacks.allowTraversal(node, node.right)) {
depthFirstSearchRecursive(node.right, callbacks);
}

// Call the "leaveNode" callback to notify that traversal
// of the current node and its children is finished.
callbacks.leaveNode(node);
}

/**
* Perform depth-first-search traversal of the rootNode.
* For every traversal step call "allowTraversal", "enterNode" and "leaveNode" callbacks.
* See TraversalCallbacks type definition for more details about the shape of callbacks object.
*
* @param {BinaryTreeNode} rootNode - The node from which we start traversing.
* @param {TraversalCallbacks} [callbacks] - Traversal callbacks.
*/
export default function depthFirstSearch(rootNode, callbacks) {
// In case if user didn't provide some callback we need to replace them with default ones.
const processedCallbacks = initCallbacks(callbacks);

// Now, when we have all necessary callbacks we may proceed to recursive traversal.
depthFirstSearchRecursive(rootNode, processedCallbacks);
}