Skip to content

Commit ff1a70d

Browse files
committed
Update: added new tests
1 parent e0edfbf commit ff1a70d

File tree

2 files changed

+144
-19
lines changed

2 files changed

+144
-19
lines changed

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

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
import test from 'ava';
33
import { SegmentTree } from './segment-tree';
44

5+
const findSumLinear = array => array.reduce((s, v) => s + v, 0);
6+
const findMaxLinear = array => {
7+
let max = -Infinity;
8+
for (let i = 0; i < array.length; ++i) {
9+
if (max < array[i]) {
10+
max = array[i];
11+
}
12+
}
13+
return max;
14+
};
15+
516
test('creates tree instance', t => {
617
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
718
const tree = new SegmentTree(input, Math.min);
@@ -16,9 +27,103 @@ test('creates tree instance and has query function', t => {
1627
t.truthy(tree.query);
1728
});
1829

30+
test('returns fallback value when input length is zero', t => {
31+
const input = [];
32+
const tree = new SegmentTree(input, (a, b) => a + b, 42);
33+
34+
t.is(tree.query(0, 0), 42);
35+
});
36+
1937
test('returns query result when input length equal 1', t => {
2038
const input = [1];
21-
const tree = new SegmentTree(input, Math.min, -Infinity);
39+
const tree = new SegmentTree(input, Math.min);
2240

2341
t.is(tree.query(0, 1), 1);
2442
});
43+
44+
test('query without arguments returns overall sum', t => {
45+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
46+
const tree = new SegmentTree(input, (a, b) => a + b);
47+
48+
t.is(tree.query(), 2);
49+
});
50+
51+
test('works with indexes outside an input array 1', t => {
52+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
53+
const tree = new SegmentTree(input, (a, b) => a + b);
54+
55+
t.is(tree.query(5, 50), -7);
56+
});
57+
58+
test('works with indexes outside an input array 2', t => {
59+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
60+
const tree = new SegmentTree(input, (a, b) => a + b);
61+
62+
t.is(tree.query(-55, 5), 11);
63+
});
64+
65+
test('works with indexes outside an input array 3', t => {
66+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
67+
const tree = new SegmentTree(input, (a, b) => a + b);
68+
69+
t.is(tree.query(-5500, 5000), 2);
70+
});
71+
72+
test('works with indexes outside an input array 4', t => {
73+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
74+
const tree = new SegmentTree(input, (a, b) => a + b, 42);
75+
76+
t.is(tree.query(3400, 5000), 42);
77+
});
78+
79+
test('works with indexes outside an input array 5', t => {
80+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
81+
const tree = new SegmentTree(input, (a, b) => a + b, 42);
82+
83+
t.is(tree.query(-5400, -3000), 42);
84+
});
85+
86+
test('works with swapped indexes', t => {
87+
const input = [1, 2, 3, 4, -1, 2, -2, -3, -4, 10, -10];
88+
const tree = new SegmentTree(input, (a, b) => a + b);
89+
90+
t.is(tree.query(5, 2), 8);
91+
});
92+
93+
test('returns sum of large arrays', t => {
94+
const input = Array(1e7)
95+
.fill(0)
96+
.map((_, i) => Math.floor((Math.random() - 0.5) * 1e3));
97+
98+
const minIndex = 1000;
99+
const maxIndex = 1e6;
100+
101+
// O(n) sum
102+
const linearSum = findSumLinear(input.slice(minIndex, maxIndex + 1));
103+
104+
const tree = new SegmentTree(input, (a, b) => a + b);
105+
106+
// O(logn) sum
107+
const segmentTreeSum = tree.query(minIndex, maxIndex);
108+
109+
t.is(segmentTreeSum, linearSum);
110+
});
111+
112+
test('returns max of large arrays', t => {
113+
const input = Array(1e7)
114+
.fill(0)
115+
.map((_, i) => Math.floor((Math.random() - 0.5) * 1e9));
116+
117+
const minIndex = 1e4;
118+
const maxIndex = 1e7 - 1e2;
119+
120+
// O(n) max
121+
const linearMax = findMaxLinear(input.slice(minIndex, maxIndex + 1));
122+
123+
const tree = new SegmentTree(input, Math.max);
124+
125+
// O(logn) max
126+
const segmentTreeMax = tree.query(minIndex, maxIndex);
127+
128+
t.is(segmentTreeMax, linearMax);
129+
});

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

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,30 @@ export class SegmentTree {
77
* @type {number[]}
88
* @private
99
*/
10-
private input: number[] = [];
10+
private readonly input: number[] = [];
1111

1212
/**
1313
* Operation function
1414
*
1515
* @type {function<*>}
1616
* @private
1717
*/
18-
private operation: (...args) => number;
18+
private readonly operation: (...args) => number;
1919

2020
/**
2121
* Fallback value for non-intersect intervals
2222
*
2323
* @type {function}
2424
* @private
2525
*/
26-
private fallbackValue: number = 0;
26+
private readonly fallbackValue: number = 0;
2727

2828
/**
2929
* Tree instance
3030
*
3131
* @type {number[]}
3232
*/
33-
private tree: number[] = null;
33+
private readonly tree: number[] = null;
3434

3535
/**
3636
* @param {number[]} input
@@ -48,7 +48,9 @@ export class SegmentTree {
4848

4949
this.tree = this.createTree();
5050

51-
this.buildTree();
51+
if (input.length) {
52+
this.buildTree();
53+
}
5254
}
5355

5456
/**
@@ -58,16 +60,30 @@ export class SegmentTree {
5860
* @param {number} queryRightIndex
5961
* @return {number}
6062
*/
61-
public query(queryLeftIndex, queryRightIndex): number {
62-
const leftIndex = 0;
63-
const rightIndex = this.input.length - 1;
63+
public query(
64+
queryLeftIndex = 0,
65+
queryRightIndex = this.input.length - 1
66+
): number {
67+
const minIndex = 0;
68+
const maxIndex = this.input.length - 1;
6469

65-
return this.queryRecursive(
66-
queryLeftIndex,
67-
queryRightIndex,
68-
leftIndex,
69-
rightIndex
70-
);
70+
if (
71+
(queryLeftIndex < minIndex && queryRightIndex < minIndex) ||
72+
(queryLeftIndex > maxIndex && queryRightIndex > maxIndex)
73+
) {
74+
return this.fallbackValue;
75+
}
76+
77+
if (queryLeftIndex > queryRightIndex) {
78+
const tmp = queryLeftIndex;
79+
queryLeftIndex = queryRightIndex;
80+
queryRightIndex = tmp;
81+
}
82+
83+
queryLeftIndex = Math.max(0, Math.min(maxIndex, queryLeftIndex));
84+
queryRightIndex = Math.max(0, Math.min(maxIndex, queryRightIndex));
85+
86+
return this.queryRecursive(queryLeftIndex, queryRightIndex);
7187
}
7288

7389
/**
@@ -153,12 +169,16 @@ export class SegmentTree {
153169
* @return {number}
154170
*/
155171
private queryRecursive(
156-
queryLeftIndex,
157-
queryRightIndex,
158-
leftIndex,
159-
rightIndex,
172+
queryLeftIndex = 0,
173+
queryRightIndex = this.input.length - 1,
174+
leftIndex = 0,
175+
rightIndex = this.input.length - 1,
160176
position = 0
161177
): number {
178+
if (!this.tree.length) {
179+
return this.fallbackValue;
180+
}
181+
162182
if (queryLeftIndex <= leftIndex && queryRightIndex >= rightIndex) {
163183
// Total overlap
164184
return this.tree[position];

0 commit comments

Comments
 (0)