/// <summary> /// A common feature of the above techniques—indeed, the key technique that /// allows us to track the decayed weights efficiently—is that they maintain /// counts and other quantities based on g(ti − L), and only scale by g(t − L) /// at query time. But while g(ti −L)/g(t−L) is guaranteed to lie between zero /// and one, the intermediate values of g(ti − L) could become very large. For /// polynomial functions, these values should not grow too large, and should be /// effectively represented in practice by floating point values without loss of /// precision. For exponential functions, these values could grow quite large as /// new values of (ti − L) become large, and potentially exceed the capacity of /// common floating point types. However, since the values stored by the /// algorithms are linear combinations of g values (scaled sums), they can be /// rescaled relative to a new landmark. That is, by the analysis of exponential /// decay in Section III-A, the choice of L does not affect the final result. We /// can therefore multiply each value based on L by a factor of exp(−α(L′ − L)), /// and obtain the correct value as if we had instead computed relative to a new /// landmark L′ (and then use this new L′ at query time). This can be done with /// a linear pass over whatever data structure is being used." /// </summary> private void Rescale() { var lockTaken = false; try { _lock.Enter(ref lockTaken); var oldStartTime = _startTime.GetValue(); _startTime.SetValue(_clock.Seconds); var scalingFactor = Math.Exp(-_alpha * (_startTime.GetValue() - oldStartTime)); var keys = new List<double>(_values.Keys); foreach (var key in keys) { var sample = _values[key]; _values.Remove(key); var newKey = key * Math.Exp(-_alpha * (_startTime.GetValue() - oldStartTime)); var newSample = new WeightedSample(sample.Value, sample.UserValue, sample.Weight * scalingFactor); _values[newKey] = newSample; } // make sure the counter is in sync with the number of stored samples. _count.SetValue(_values.Count); } finally { if (lockTaken) { _lock.Exit(); } } }
private void Update(long value, string userValue, long timestamp) { var lockTaken = false; try { _lock.Enter(ref lockTaken); var itemWeight = Math.Exp(_alpha * (timestamp - _startTime.GetValue())); var sample = new WeightedSample(value, userValue, itemWeight); var random = 0.0; // Prevent division by 0 while (random.Equals(.0)) { random = ThreadLocalRandom.NextDouble(); } var priority = itemWeight / random; var newCount = _count.GetValue(); newCount++; _count.SetValue(newCount); if (newCount <= _sampleSize) { _values[priority] = sample; } else { var first = _values.Keys[_values.Count - 1]; if (first < priority) { _values.Remove(first); _values[priority] = sample; } } } finally { if (lockTaken) { _lock.Exit(); } } }