Monero
Loading...
Searching...
No Matches
rolling_median.h
Go to the documentation of this file.
1// Copyright (c) 2019-2022, The Monero Project
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification, are
6// permitted provided that the following conditions are met:
7//
8// 1. Redistributions of source code must retain the above copyright notice, this list of
9// conditions and the following disclaimer.
10//
11// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12// of conditions and the following disclaimer in the documentation and/or other
13// materials provided with the distribution.
14//
15// 3. Neither the name of the copyright holder nor the names of its contributors may be
16// used to endorse or promote products derived from this software without specific
17// prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28//
29// Adapted from source by AShelly:
30// Copyright (c) 2011 ashelly.myopenid.com, licenced under the MIT licence
31// https://stackoverflow.com/questions/5527437/rolling-median-in-c-turlach-implementation
32// https://stackoverflow.com/questions/1309263/rolling-median-algorithm-in-c
33// https://ideone.com/XPbl6
34
35#pragma once
36
37#include "misc_language.h"
38
39#include <stdlib.h>
40#include <stdint.h>
41
42namespace epee
43{
44namespace misc_utils
45{
46
47template<typename Item>
49{
50private:
51 Item* data; //circular queue of values
52 int* pos; //index into `heap` for each value
53 int* heap; //max/median/min heap holding indexes into `data`.
54 int N; //allocated size.
55 int idx; //position in circular queue
56 int minCt; //count of items in min heap
57 int maxCt; //count of items in max heap
58 int sz; //count of items in heap
59
60private:
61
62 //returns true if heap[i] < heap[j]
63 bool mmless(int i, int j) const
64 {
65 return data[heap[i]] < data[heap[j]];
66 }
67
68 //swaps items i&j in heap, maintains indexes
69 bool mmexchange(int i, int j)
70 {
71 const int t = heap[i];
72 heap[i] = heap[j];
73 heap[j] = t;
74 pos[heap[i]] = i;
75 pos[heap[j]] = j;
76 return 1;
77 }
78
79 //swaps items i&j if i<j; returns true if swapped
80 bool mmCmpExch(int i, int j)
81 {
82 return mmless(i, j) && mmexchange(i, j);
83 }
84
85 //maintains minheap property for all items below i.
86 void minSortDown(int i)
87 {
88 for (i *= 2; i <= minCt; i *= 2)
89 {
90 if (i < minCt && mmless(i + 1, i))
91 ++i;
92 if (!mmCmpExch(i, i / 2))
93 break;
94 }
95 }
96
97 //maintains maxheap property for all items below i. (negative indexes)
98 void maxSortDown(int i)
99 {
100 for (i *= 2; i >= -maxCt; i *= 2)
101 {
102 if (i > -maxCt && mmless(i, i - 1))
103 --i;
104 if (!mmCmpExch(i / 2, i))
105 break;
106 }
107 }
108
109 //maintains minheap property for all items above i, including median
110 //returns true if median changed
111 bool minSortUp(int i)
112 {
113 while (i > 0 && mmCmpExch(i, i / 2))
114 i /= 2;
115 return i == 0;
116 }
117
118 //maintains maxheap property for all items above i, including median
119 //returns true if median changed
120 bool maxSortUp(int i)
121 {
122 while (i < 0 && mmCmpExch(i / 2, i))
123 i /= 2;
124 return i == 0;
125 }
126
127protected:
129
130public:
131 //creates new rolling_median_t: to calculate `nItems` running median.
133 {
134 int size = N * (sizeof(Item) + sizeof(int) * 2);
135 data = (Item*)malloc(size);
136 pos = (int*) (data + N);
137 heap = pos + N + (N / 2); //points to middle of storage.
138 clear();
139 }
140
142 {
143 N = other.N;
144 int size = N * (sizeof(Item) + sizeof(int) * 2);
145 data = (Item*)malloc(size);
146 memcpy(data, other.data, size);
147 pos = (int*) (data + N);
148 heap = pos + N + (N / 2); //points to middle of storage.
149 idx = other.idx;
150 minCt = other.minCt;
151 maxCt = other.maxCt;
152 sz = other.sz;
153 }
154
156 {
157 memcpy(this, &m, sizeof(rolling_median_t));
158 m.data = NULL;
159 }
161 {
162 free(data);
163 memcpy(this, &m, sizeof(rolling_median_t));
164 m.data = NULL;
165 return *this;
166 }
167
169 {
170 free(data);
171 }
172
173 void clear()
174 {
175 idx = 0;
176 minCt = 0;
177 maxCt = 0;
178 sz = 0;
179 int nItems = N;
180 while (nItems--) //set up initial heap fill pattern: median,max,min,max,...
181 {
182 pos[nItems] = ((nItems + 1) / 2) * ((nItems & 1) ? -1 : 1);
183 heap[pos[nItems]] = nItems;
184 }
185 }
186
187 int size() const
188 {
189 return sz;
190 }
191
192 //Inserts item, maintains median in O(lg nItems)
193 void insert(Item v)
194 {
195 int p = pos[idx];
196 Item old = data[idx];
197 data[idx] = v;
198 idx = (idx + 1) % N;
199 sz = std::min<int>(sz + 1, N);
200 if (p > 0) //new item is in minHeap
201 {
202 if (minCt < (N - 1) / 2)
203 {
204 ++minCt;
205 }
206 else if (v > old)
207 {
208 minSortDown(p);
209 return;
210 }
211 if (minSortUp(p) && mmCmpExch(0, -1))
212 maxSortDown(-1);
213 }
214 else if (p < 0) //new item is in maxheap
215 {
216 if (maxCt < N / 2)
217 {
218 ++maxCt;
219 }
220 else if (v < old)
221 {
222 maxSortDown(p);
223 return;
224 }
225 if (maxSortUp(p) && minCt && mmCmpExch(1, 0))
226 minSortDown(1);
227 }
228 else //new item is at median
229 {
230 if (maxCt && maxSortUp(-1))
231 maxSortDown(-1);
232 if (minCt && minSortUp(1))
233 minSortDown(1);
234 }
235 }
236
237 //returns median item (or average of 2 when item count is even)
238 Item median() const
239 {
240 Item v = data[heap[0]];
241 if (minCt < maxCt)
242 {
243 v = get_mid<Item>(v, data[heap[-1]]);
244 }
245 return v;
246 }
247};
248
249}
250}
void * memcpy(void *a, const void *b, size_t c)
Definition glibc_compat.cpp:16
Definition misc_language.h:39
T get_mid(const T &a, const T &b)
Definition misc_language.h:43
TODO: (mj-xmr) This will be reduced in an another PR.
Definition byte_slice.h:40
int * heap
Definition rolling_median.h:53
int sz
Definition rolling_median.h:58
rolling_median_t(rolling_median_t &&m)
Definition rolling_median.h:155
int size() const
Definition rolling_median.h:187
int N
Definition rolling_median.h:54
void clear()
Definition rolling_median.h:173
int idx
Definition rolling_median.h:55
bool mmless(int i, int j) const
Definition rolling_median.h:63
bool maxSortUp(int i)
Definition rolling_median.h:120
void insert(Item v)
Definition rolling_median.h:193
void minSortDown(int i)
Definition rolling_median.h:86
int maxCt
Definition rolling_median.h:57
rolling_median_t(const rolling_median_t &other)
Definition rolling_median.h:141
int minCt
Definition rolling_median.h:56
Item * data
Definition rolling_median.h:51
~rolling_median_t()
Definition rolling_median.h:168
Item median() const
Definition rolling_median.h:238
rolling_median_t & operator=(rolling_median_t &&m)
Definition rolling_median.h:160
rolling_median_t & operator=(const rolling_median_t &)=delete
int * pos
Definition rolling_median.h:52
void maxSortDown(int i)
Definition rolling_median.h:98
bool mmexchange(int i, int j)
Definition rolling_median.h:69
rolling_median_t(size_t N)
Definition rolling_median.h:132
bool minSortUp(int i)
Definition rolling_median.h:111
bool mmCmpExch(int i, int j)
Definition rolling_median.h:80