Skip to content

Commit c96a255

Browse files
author
Alexander Belov
committed
Fixes
1 parent c383488 commit c96a255

File tree

2 files changed

+177
-12
lines changed

2 files changed

+177
-12
lines changed
Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
// tslint:disable:no-expression-statement
22
import test from 'ava';
3+
import { SegmentTree } from './segment-tree';
34

4-
test('test', t => {
5-
t.is(true, true);
5+
test('creates tree instance', t => {
6+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
7+
const tree = new SegmentTree(input, Math.min);
8+
9+
t.truthy(tree);
10+
});
11+
12+
test('creates tree instance and has query function', t => {
13+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
14+
const tree = new SegmentTree(input, Math.min);
15+
16+
t.truthy(tree.query);
17+
});
18+
19+
test('returns query result when input length equal 1', t => {
20+
const input = [1];
21+
const tree = new SegmentTree(input, Math.min, -Infinity);
22+
23+
t.is(tree.query(0, 1), 1);
624
});

src/lib/segment-tree/segment-tree.ts

Lines changed: 157 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ export class SegmentTree {
1212
/**
1313
* Operation function
1414
*
15-
* @type {function}
15+
* @type {function<*>}
1616
* @private
1717
*/
18-
private operation: () => {};
18+
private operation: (...args) => number;
1919

2020
/**
2121
* Fallback value for non-intersect intervals
@@ -37,30 +37,177 @@ export class SegmentTree {
3737
* @param {function} operation
3838
* @param {number} fallbackValue
3939
*/
40-
constructor(input: number[], operation: () => {}, fallbackValue: number = 0) {
40+
constructor(
41+
input: number[],
42+
operation: (...args) => number,
43+
fallbackValue: number = 0
44+
) {
4145
this.input = input;
4246
this.operation = operation;
4347
this.fallbackValue = fallbackValue;
4448

4549
this.tree = this.createTree();
50+
51+
this.buildTree();
52+
}
53+
54+
/**
55+
* Query on segment tree in context of this.operation function
56+
*
57+
* @param {number} queryLeftIndex
58+
* @param {number} queryRightIndex
59+
* @return {number}
60+
*/
61+
public query(queryLeftIndex, queryRightIndex): number {
62+
const leftIndex = 0;
63+
const rightIndex = this.input.length - 1;
64+
65+
return this.queryRecursive(
66+
queryLeftIndex,
67+
queryRightIndex,
68+
leftIndex,
69+
rightIndex
70+
);
4671
}
4772

73+
/**
74+
* @returns {number[]}
75+
*/
4876
private createTree(): number[] {
49-
let segmentTreeArrayLength;
50-
const inputArrayLength = this.input.length;
77+
let arrayLength = this.input.length;
5178

52-
let treeArrayLength = inputArrayLength;
79+
if (!arrayLength) {
80+
return [];
81+
}
5382

54-
if (!isPowerOfTwo(inputArrayLength)) {
83+
if (arrayLength && !isPowerOfTwo(arrayLength)) {
5584
// If original array length is not a power of two then we need to find
5685
// next number that is a power of two and use it to calculate
5786
// tree array size. This is happens because we need to fill empty children
5887
// in perfect binary tree with nulls.And those nulls need extra space.
59-
treeArrayLength = nearestHighestPowerOfTwoResult(inputArrayLength + 1);
88+
arrayLength = nearestHighestPowerOfTwoResult(arrayLength + 1);
89+
}
90+
91+
return new Array(2 * arrayLength - 1).fill(null);
92+
}
93+
94+
/**
95+
* Build segment tree.
96+
*/
97+
private buildTree(): void {
98+
const leftIndex = 0;
99+
const rightIndex = this.input.length - 1;
100+
101+
this.buildTreeRecursively(leftIndex, rightIndex);
102+
}
103+
104+
/**
105+
* Build segment tree recursively.
106+
*
107+
* @param {number} leftIndex
108+
* @param {number} rightIndex
109+
* @param {number} position
110+
*/
111+
private buildTreeRecursively(leftIndex, rightIndex, position = 0): void {
112+
// If low input index and high input index are equal that would mean
113+
// the we have finished splitting and we are already came to the leaf
114+
// of the segment tree. We need to copy this leaf value from input
115+
// array to segment tree.
116+
if (leftIndex === rightIndex) {
117+
return void (this.tree[position] = this.input[leftIndex]);
60118
}
61119

62-
segmentTreeArrayLength = 2 * treeArrayLength - 1;
120+
// Split input array on two halves and process them recursively.
121+
const middleIndex = (leftIndex + rightIndex) >> 1;
122+
123+
// Process left half of the input array.
124+
this.buildTreeRecursively(
125+
leftIndex,
126+
middleIndex,
127+
this.computeLeftChildIndex(position)
128+
);
63129

64-
return new Array(segmentTreeArrayLength).fill(null);
130+
// Process right half of the input array.
131+
this.buildTreeRecursively(
132+
middleIndex + 1,
133+
rightIndex,
134+
this.computeRightChildIndex(position)
135+
);
136+
137+
// Once every tree leaf is not empty we're able to build tree bottom up using
138+
// provided operation function.
139+
this.tree[position] = this.operation(
140+
this.tree[this.computeLeftChildIndex(position)],
141+
this.tree[this.computeRightChildIndex(position)]
142+
);
143+
}
144+
145+
/**
146+
* Query on segment tree recursively in context of this.operation function
147+
*
148+
* @param {number} queryLeftIndex left index of the query
149+
* @param {number} queryRightIndex right index of the query
150+
* @param {number} leftIndex left index of input array segment
151+
* @param {number} rightIndex right index of input array segment
152+
* @param {number} position root position in binary tree
153+
* @return {number}
154+
*/
155+
private queryRecursive(
156+
queryLeftIndex,
157+
queryRightIndex,
158+
leftIndex,
159+
rightIndex,
160+
position = 0
161+
): number {
162+
if (queryLeftIndex <= leftIndex && queryRightIndex >= rightIndex) {
163+
// Total overlap
164+
return this.tree[position];
165+
}
166+
167+
if (queryLeftIndex > rightIndex || queryRightIndex < leftIndex) {
168+
// No overlap. Return fallback value
169+
return this.fallbackValue;
170+
}
171+
172+
// Partial overlap
173+
const middleIndex = (leftIndex + rightIndex) >> 1;
174+
175+
const leftOperationResult = this.queryRecursive(
176+
queryLeftIndex,
177+
queryRightIndex,
178+
leftIndex,
179+
middleIndex,
180+
this.computeLeftChildIndex(position)
181+
);
182+
183+
const rightOperationResult = this.queryRecursive(
184+
queryLeftIndex,
185+
queryRightIndex,
186+
middleIndex + 1,
187+
rightIndex,
188+
this.computeRightChildIndex(position)
189+
);
190+
191+
return this.operation(leftOperationResult, rightOperationResult);
192+
}
193+
194+
/**
195+
* Computes left child index
196+
*
197+
* @param {number} position
198+
* @returns {number}
199+
*/
200+
private computeLeftChildIndex(position: number): number {
201+
return (position << 1) + 1;
202+
}
203+
204+
/**
205+
* Computes right child index
206+
*
207+
* @param {number} position
208+
* @returns {number}
209+
*/
210+
private computeRightChildIndex(position: number): number {
211+
return (position << 1) + 2;
65212
}
66213
}

0 commit comments

Comments
 (0)