-
Notifications
You must be signed in to change notification settings - Fork 0
/
FastRangeReducer.cs
97 lines (86 loc) · 3.74 KB
/
FastRangeReducer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public sealed class FastRangeReducer
{
readonly (int, double)[][] _preCachedAsTree;
readonly Func<(int, double), (int, double), (int, double)> _reducer;
public FastRangeReducer(Span<double> rangeToInspect, Func<(int, double), (int, double), (int, double)> reducer)
{
_reducer = reducer;
_preCachedAsTree = PreCacheAsTree(rangeToInspect);
}
public static (int, double) Min((int, double) newItem, (int, double) accumulator)
{
return newItem.Item2 < accumulator.Item2 ? newItem : accumulator;
}
public static (int, double) Max((int, double) newItem, (int, double) accumulator)
{
return newItem.Item2 > accumulator.Item2 ? newItem : accumulator;
}
private (int, double)[][] PreCacheAsTree(Span<double> rangeToInspect)
{
var lengthTotal = rangeToInspect.Length;
var treeLevels = (int)Math.Ceiling(1 + Math.Log2(lengthTotal));
var result = new (int, double)[treeLevels][];
if (result[0] == null) result[0] = new (int, double)[lengthTotal];
for (var i = 0; i < lengthTotal; i++)
{
result[0][i] = (i, rangeToInspect[i]);
}
for (var level = 1; level < treeLevels; level++)
{
var divider = level == 0 ? 1 : 2 << (level - 1);
var lengthAtThisLevel = lengthTotal / divider + Math.Sign(lengthTotal % divider);
if (result[level] == null) result[level] = new (int, double)[lengthAtThisLevel];
var loopEndUpperBound = level == 1 ? lengthTotal : result[level - 1].Length;
for (var i = 0; i < lengthAtThisLevel; i++)
{
var startIndex = i * 2;
var best = (result[level - 1][startIndex].Item1, result[level - 1][startIndex].Item2);
var loopEnd = Math.Min((i + 1) * 2, loopEndUpperBound);
for (var j = startIndex + 1; j < loopEnd; j++)
{
best = _reducer(result[level - 1][j], best);
}
result[level][i] = best;
}
}
return result;
}
public (int, double) GetResultForRange(int start, int end)
{
var position = start;
var level = 0;
var best = _preCachedAsTree[0][start];
var dividerOnThisLevel = 1;
var nextLevelDistance = dividerOnThisLevel * 2;
var distanceToNextRoundNumber = (nextLevelDistance - position % nextLevelDistance) % nextLevelDistance;
var currentLevel = _preCachedAsTree[level];
do
{
if (distanceToNextRoundNumber == 0 && position + nextLevelDistance <= end)
{
level++;
dividerOnThisLevel *= 2;
nextLevelDistance *= 2;
distanceToNextRoundNumber = (nextLevelDistance - position % nextLevelDistance) % nextLevelDistance;
currentLevel = _preCachedAsTree[level];
continue;
}
var positionPlusDividerOnThisLevel = position + dividerOnThisLevel;
if (positionPlusDividerOnThisLevel > end && level > 0)
{
level--;
dividerOnThisLevel /= 2;
nextLevelDistance /= 2;
distanceToNextRoundNumber = (nextLevelDistance - position % nextLevelDistance) % nextLevelDistance;
currentLevel = _preCachedAsTree[level];
continue;
}
var addressForThisLevel = position / dividerOnThisLevel;
var candidate = currentLevel[addressForThisLevel];
best = _reducer(candidate, best);
position = positionPlusDividerOnThisLevel;
distanceToNextRoundNumber -= dividerOnThisLevel;
} while (position <= end);
return best;
}
}