Skip to content
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
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"semi": false,
"semi": true,
"singleQuote": true,
"arrowParens": "avoid"
}
263 changes: 224 additions & 39 deletions index.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,226 @@
import { Tree } from './index'
import { Tree } from './index';

describe('Tree', () => {
test('should insert root node', () => {
const treeNode = new Tree()
treeNode.insert(3)
expect(treeNode.root).toStrictEqual({ value: 3 })
})

test('should insert left subTree', () => {
const treeNode = new Tree()
treeNode.insert(3)
treeNode.insert(1)
expect(treeNode.root).toStrictEqual({ value: 3, left: { value: 1 } })
})

test('should insert left and right subTree', () => {
const treeNode = new Tree()
treeNode.insert(3)
treeNode.insert(1)
treeNode.insert(5)
expect(treeNode.root).toStrictEqual({
value: 3,
left: { value: 1 },
right: { value: 5 },
})
})

test('should insert under right sub tree(leaf node)', () => {
const treeNode = new Tree()
treeNode.insert(3)
treeNode.insert(1)
treeNode.insert(5)
treeNode.insert(10)
expect(treeNode.root).toStrictEqual({
value: 3,
left: { value: 1 },
right: { value: 5, right: { value: 10 } },
})
})
})
describe('insert', () => {
test('should insert root node', () => {
const treeNode = new Tree();
treeNode.insert(3);
expect(treeNode.root).toStrictEqual({ value: 3 });
});

test('should insert left subTree', () => {
const treeNode = new Tree();
treeNode.insert(3);
treeNode.insert(1);
expect(treeNode.root).toStrictEqual({ value: 3, left: { value: 1 } });
});

test('should insert left and right subTree', () => {
const treeNode = new Tree();
treeNode.insert(3);
treeNode.insert(1);
treeNode.insert(5);
expect(treeNode.root).toStrictEqual({
value: 3,
left: { value: 1 },
right: { value: 5 },
});
});

test('should insert under right sub tree(leaf node)', () => {
const treeNode = new Tree();
treeNode.insert(3);
treeNode.insert(1);
treeNode.insert(5);
treeNode.insert(10);
expect(treeNode.root).toStrictEqual({
value: 3,
left: { value: 1 },
right: { value: 5, right: { value: 10 } },
});
});

test('should able to insert complicated tree', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.insert(8);
treeNode.insert(7);
treeNode.insert(9);
treeNode.insert(12);
treeNode.insert(11);
treeNode.insert(13);
expect(treeNode.root).toStrictEqual({
value: 5,
right: {
value: 10,
left: {
value: 8,
left: {
value: 7,
},
right: {
value: 9,
},
},
right: {
value: 12,
left: {
value: 11,
},
right: {
value: 13,
},
},
},
});
});
});
describe('remove', () => {
describe('return false', () => {
test('if no next node', () => {
const treeNode = new Tree();
treeNode.insert(5);
expect(treeNode.remove(10)).toBe(false);
});
});
describe('PASS', () => {
test('should NOT able to remove node if tree is NOT existing', () => {
const treeNode = new Tree();
expect(treeNode.remove(3)).toBe(false);
});

test('should able to remove root node', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.remove(5);
expect(treeNode.root).toBe(undefined);
});

test('should able to remove parent node', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.remove(10);
expect(treeNode.root).toStrictEqual({ value: 5 });
});

test('should able to remove parent node', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.remove(10);
expect(treeNode.root).toStrictEqual({
value: 5,
});
});

test('should able to remove parent node only without delete other node', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.insert(20);
treeNode.remove(10);
expect(treeNode.root).toStrictEqual({
value: 5,
right: {
value: 20,
right: undefined,
left: undefined,
},
});
});

test('should able to remove node only without delete the child node and parent node', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.insert(20);
treeNode.insert(30);
treeNode.remove(20);
expect(treeNode.root).toStrictEqual({
value: 5,
right: {
value: 10,
right: {
value: 30,
right: undefined,
left: undefined,
},
},
});
});

test('should able to remove node only without delete the child node and parent node', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.insert(8);
treeNode.insert(7);
treeNode.insert(9);
treeNode.remove(10);
expect(treeNode.root).toStrictEqual({
value: 5,
right: {
value: 8,
left: {
value: 7,
},
right: {
value: 9,
},
},
});
});

test('should able to remove node only without delete the child node and parent node', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.insert(3);
treeNode.insert(2);
treeNode.remove(3);
expect(treeNode.root).toStrictEqual({
value: 5,
right: { value: 10 },
left: { value: 2, left: undefined, right: undefined },
});
});

test('should able to remove node only without delete the child node and parent node', () => {
const treeNode = new Tree();
treeNode.insert(5);
treeNode.insert(10);
treeNode.insert(8);
treeNode.insert(7);
treeNode.insert(9);
treeNode.insert(12);
treeNode.insert(11);
treeNode.insert(13);
treeNode.remove(10);
expect(treeNode.root).toStrictEqual({
value: 5,
right: {
value: 8,
left: {
value: 7,
},
right: {
value: 9,
right: {
value: 12,
left: {
value: 11,
},
right: {
value: 13,
},
},
},
},
});
});
});
});
});
79 changes: 69 additions & 10 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,84 @@
type ITreeNode<TValue> = {
left?: ITreeNode<TValue>
right?: ITreeNode<TValue>
value: TValue
}
left?: ITreeNode<TValue>;
right?: ITreeNode<TValue>;
value: TValue;
};

export class Tree<TValue extends number> {
public root?: ITreeNode<TValue>
public root?: ITreeNode<TValue>;

public insert(value: TValue) {
if (this.root) {
this._insert(this.root, value)
this._insert(this.root, value);
} else {
this.root = {
value,
}
};
}
}

private _insert(node: ITreeNode<TValue>, value: TValue) {
const direction = value >= node.value ? 'right' : 'left'
if (node[direction] !== undefined) this._insert(node[direction]!, value) // eslint-disable-line @typescript-eslint/no-non-null-assertion
else node[direction] = { value }
const direction = this._getDirection(node, value);
const nextNode = node[direction];
if (nextNode) this._insert(nextNode, value);
else node[direction] = { value };
}

private _getDirection(node: ITreeNode<TValue>, value: TValue) {
return value >= node.value ? 'right' : 'left';
}

public remove(value: TValue): boolean {
if (!this.root) return false;
if (value === this.root.value) {
this.root = undefined;
return true;
} else {
return this._remove(this.root, value);
}
}

private _remove(node: ITreeNode<TValue>, value: TValue): boolean {
const direction = this._getDirection(node, value);
const nextNode = node[direction];

if (!nextNode) {
return false;
}

if (nextNode.value !== value) return this._remove(nextNode, value);

if (nextNode.left && nextNode.right && nextNode.left.right) {
node[direction] = {
value: nextNode.left.value,
left: nextNode.left.left,
right: {
value: nextNode.left.right.value,
right: {
value: nextNode.right.value,
left: nextNode.right.left,
right: nextNode.right.right,
},
},
};
return true;
} else if (nextNode.left) {
node[direction] = {
value: nextNode.left.value,
left: nextNode.left.left,
right: nextNode.left.right,
};
return true;
} else if (nextNode.right) {
node[direction] = {
value: nextNode.right.value,
left: nextNode.right.left,
right: nextNode.right.right,
};
return true;
} else {
delete node[direction];
return true;
}
}
}
14 changes: 11 additions & 3 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import type { Config } from '@jest/types'
import type { Config } from '@jest/types';

const config: Config.InitialOptions = {
preset: 'ts-jest',
testEnvironment: 'node',
verbose: true,
coveragePathIgnorePatterns: ['./dist/', './node_modules/', './coverage/'],
modulePathIgnorePatterns: ['<rootDir>/dist/'],
}
export default config
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
};
export default config;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "create binary search",
"main": "index.ts",
"scripts": {
"test": "jest --collectCoverage",
"test": "jest --coverage",
"dev": "tsc --watch",
"lint": "eslint --ext .ts . --fix",
"format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\""
Expand Down