1 | using System;
|
---|
2 | using System.Collections.Generic;
|
---|
3 |
|
---|
4 | // collection of non-negative integers (called addresses) with non-negative priorities (called distances)
|
---|
5 | // based on binary heap
|
---|
6 |
|
---|
7 | // main operations:
|
---|
8 | // * Offer: offer distance to an address
|
---|
9 | // * TakeClosest: get and remove address with shortest offered distance
|
---|
10 |
|
---|
11 | // only the shortest of multiple offered distances to a specific address is kept
|
---|
12 | // user must ensure not to offer a distance shorter than those of addresses already taken
|
---|
13 | // (easily achieved by the typical usage of this class)
|
---|
14 |
|
---|
15 | // memory consumption: 4 bytes per potential address + 8 bytes per address in collection
|
---|
16 | // time consumption: O(log n) for both main operations, where n is the current size of the collection
|
---|
17 |
|
---|
18 | namespace Common
|
---|
19 | {
|
---|
20 | /// <summary>
|
---|
21 | /// Priority queue on bounded integer address space.
|
---|
22 | /// </summary>
|
---|
23 | class AddressPriorityQueue
|
---|
24 | {
|
---|
25 | /// <summary>
|
---|
26 | /// Creates an empty collection.
|
---|
27 | /// </summary>
|
---|
28 | /// <param name="maxAddress">maximum address allowed</param>
|
---|
29 | public AddressPriorityQueue(int maxAddress)
|
---|
30 | {
|
---|
31 | if (maxAddress < 0 || maxAddress > 0x7FFFFFFE)
|
---|
32 | throw new Exception("AddressPriorityQueue: Maximum capacity is 1<<31 addresses!");
|
---|
33 | this.maxAddress = maxAddress;
|
---|
34 | allocatedSize = 4 * (int)Math.Sqrt(maxAddress + 1); // good for addresses in a 2D-space
|
---|
35 | binaryHeap = new HeapItem[allocatedSize];
|
---|
36 | binaryHeapSize = 0;
|
---|
37 | states = new uint[maxAddress + 1];
|
---|
38 | lastTakenDistance = 0;
|
---|
39 | taken = 0;
|
---|
40 | }
|
---|
41 |
|
---|
42 | /// <summary>
|
---|
43 | /// Offer address with distance. Might have been offered with same or different distance before.
|
---|
44 | /// </summary>
|
---|
45 | /// <param name="address">the address</param>
|
---|
46 | /// <param name="distance">the distance</param>
|
---|
47 | /// <returns>true if address was new or offer was better than all former offers for this address</returns>
|
---|
48 | public bool Offer(int address, int distance)
|
---|
49 | {
|
---|
50 | if (address < 0 || address > maxAddress)
|
---|
51 | throw new Exception(string.Format("AddressPriorityQueue.Offer: Address '{0}' is not in valid range!", address));
|
---|
52 | uint state = states[address];
|
---|
53 | if ((state & wasTakenFlag) != 0)
|
---|
54 | {
|
---|
55 | if (state == (Disallowed | wasTakenFlag))
|
---|
56 | throw new Exception(string.Format("AddressPriorityQueue.Offer: Ambiguous usage! Address '{0}' was both offered and disallowed!", address));
|
---|
57 | else
|
---|
58 | return false; // if class is used correctly, the new offer must be worse than one before at this point
|
---|
59 | }
|
---|
60 | else
|
---|
61 | {
|
---|
62 | if (distance < 0 || distance >= Unknown)
|
---|
63 | throw new Exception(string.Format("AddressPriorityQueue.Offer: Distance '{0}' is not in valid range!", distance));
|
---|
64 | int index = (int)state - 1;
|
---|
65 | bool isShorter = true;
|
---|
66 | if (state > 0) // means address is already contained in binary heap
|
---|
67 | isShorter = (distance < binaryHeap[index].distance);
|
---|
68 | if (isShorter)
|
---|
69 | {
|
---|
70 | if (distance < lastTakenDistance)
|
---|
71 | throw new Exception(string.Format("AddressPriorityQueue.Offer: Illegal order of operations! Can't offer distances shorter than those already taken. Last TakeClosest: '{0}' - this Offer: '{1}'.", lastTakenDistance, distance));
|
---|
72 |
|
---|
73 | #region binary heap grow algo
|
---|
74 | if (index < 0)
|
---|
75 | { // address is not yet contained in binary heap, insert at bottom
|
---|
76 | index = binaryHeapSize;
|
---|
77 | binaryHeapSize++;
|
---|
78 | if (binaryHeapSize > allocatedSize)
|
---|
79 | {
|
---|
80 | allocatedSize *= 2;
|
---|
81 | if (allocatedSize > maxAddress + 1)
|
---|
82 | allocatedSize = maxAddress + 1;
|
---|
83 | Array.Resize<HeapItem>(ref binaryHeap, allocatedSize);
|
---|
84 | }
|
---|
85 | }
|
---|
86 |
|
---|
87 | // correct binary heap, new item can only move up (either shorter distance than before or at bottom)
|
---|
88 | while (index > 0)
|
---|
89 | {
|
---|
90 | int parent = (index - 1) / 2;
|
---|
91 | if (distance >= binaryHeap[parent].distance)
|
---|
92 | break;
|
---|
93 | binaryHeap[index] = binaryHeap[parent];
|
---|
94 | states[binaryHeap[index].address] = (uint)index + 1;
|
---|
95 | index = parent;
|
---|
96 | }
|
---|
97 | binaryHeap[index].distance = distance;
|
---|
98 | binaryHeap[index].address = address;
|
---|
99 | states[binaryHeap[index].address] = (uint)index + 1;
|
---|
100 | #endregion
|
---|
101 |
|
---|
102 | return true;
|
---|
103 | }
|
---|
104 | else
|
---|
105 | return false;
|
---|
106 | }
|
---|
107 | }
|
---|
108 |
|
---|
109 | /// <summary>
|
---|
110 | /// Does nothing but returns same value as Offer(...) would.
|
---|
111 | /// </summary>
|
---|
112 | /// <param name="address">the address</param>
|
---|
113 | /// <param name="distance">the distance</param>
|
---|
114 | /// <returns>true if address was new or offer was better than all former offers for this address</returns>
|
---|
115 | public bool TestOffer(int address, int distance)
|
---|
116 | {
|
---|
117 | if (address < 0 || address > maxAddress)
|
---|
118 | throw new Exception(string.Format("AddressPriorityQueue.TestOffer: Address '{0}' is not in valid range!", address));
|
---|
119 | uint state = states[address];
|
---|
120 | if ((state & wasTakenFlag) != 0)
|
---|
121 | {
|
---|
122 | if (state == (Disallowed | wasTakenFlag))
|
---|
123 | throw new Exception(string.Format("AddressPriorityQueue.TestOffer: Ambiguous usage! Address '{0}' was both offered and disallowed!", address));
|
---|
124 | else
|
---|
125 | return false;
|
---|
126 | }
|
---|
127 | else
|
---|
128 | {
|
---|
129 | if (distance < 0 || distance >= Unknown)
|
---|
130 | throw new Exception(string.Format("AddressPriorityQueue.TestOffer: Distance '{0}' is not in valid range!", distance));
|
---|
131 | if (state > 0) // means address is already contained in binary heap
|
---|
132 | return distance < binaryHeap[state - 1].distance;
|
---|
133 | else
|
---|
134 | return true;
|
---|
135 | }
|
---|
136 | }
|
---|
137 |
|
---|
138 | /// <summary>
|
---|
139 | /// Marks address as disallowed, which changes the Distance(address) query result and forbids Offer calls for this address.
|
---|
140 | /// </summary>
|
---|
141 | /// <param name="address">the address</param>
|
---|
142 | public void Disallow(int address)
|
---|
143 | {
|
---|
144 | if (address < 0 || address > maxAddress)
|
---|
145 | throw new Exception(string.Format("AddressPriorityQueue.Disallow: Address '{0}' is not in valid range!", address));
|
---|
146 | uint state = states[address];
|
---|
147 | if (state != (Disallowed | wasTakenFlag))
|
---|
148 | {
|
---|
149 | if (state != 0)
|
---|
150 | throw new Exception(string.Format("AddressPriorityQueue.Disallow: Ambiguous usage! Address '{0}' was both offered and disallowed!", address));
|
---|
151 | states[address] = (Disallowed | wasTakenFlag);
|
---|
152 | }
|
---|
153 | }
|
---|
154 |
|
---|
155 | /// <summary>
|
---|
156 | /// Get and remove address with shortest offered distance.
|
---|
157 | /// </summary>
|
---|
158 | /// <param name="address">the address</param>
|
---|
159 | /// <param name="distance">the distance</param>
|
---|
160 | /// <returns>true if operation was successful, false for empty collection</returns>
|
---|
161 | public bool TakeClosest(out int address, out int distance)
|
---|
162 | {
|
---|
163 | if (binaryHeapSize == 0)
|
---|
164 | {
|
---|
165 | address = 0;
|
---|
166 | distance = 0;
|
---|
167 | return false;
|
---|
168 | }
|
---|
169 |
|
---|
170 | address = binaryHeap[0].address;
|
---|
171 | distance = binaryHeap[0].distance;
|
---|
172 | states[address] = (uint)distance | wasTakenFlag;
|
---|
173 | lastTakenDistance = distance;
|
---|
174 | taken++;
|
---|
175 |
|
---|
176 | #region binary heap shrink algo
|
---|
177 | binaryHeapSize--;
|
---|
178 | if (binaryHeapSize > 0)
|
---|
179 | {
|
---|
180 | HeapItem last = binaryHeap[binaryHeapSize];
|
---|
181 | int index = 0;
|
---|
182 | int child = 1;
|
---|
183 | while (child < binaryHeapSize)
|
---|
184 | {
|
---|
185 | if (child < binaryHeapSize - 1 && binaryHeap[child].distance > binaryHeap[child + 1].distance)
|
---|
186 | child++; // right child instead of left
|
---|
187 | if (last.distance <= binaryHeap[child].distance)
|
---|
188 | break;
|
---|
189 |
|
---|
190 | binaryHeap[index] = binaryHeap[child];
|
---|
191 | states[binaryHeap[index].address] = (uint)index + 1;
|
---|
192 | index = child;
|
---|
193 | child = 2 * child + 1;
|
---|
194 | }
|
---|
195 | binaryHeap[index] = last;
|
---|
196 | states[last.address] = (uint)index + 1;
|
---|
197 | }
|
---|
198 | #endregion
|
---|
199 |
|
---|
200 | return true;
|
---|
201 | }
|
---|
202 |
|
---|
203 | /// <summary>
|
---|
204 | /// Remove all addresses from collection and delete calculations. Collection returns to state as after
|
---|
205 | /// new except that possibly resized memory is kept.
|
---|
206 | /// </summary>
|
---|
207 | public void Clear()
|
---|
208 | {
|
---|
209 | if (binaryHeapSize + taken > 0)
|
---|
210 | {
|
---|
211 | binaryHeapSize = 0;
|
---|
212 | Array.Clear(states, 0, maxAddress + 1);
|
---|
213 | lastTakenDistance = 0;
|
---|
214 | taken = 0;
|
---|
215 | }
|
---|
216 | }
|
---|
217 |
|
---|
218 | /// <summary>
|
---|
219 | /// Status tracker. Returns shortest distance to an address, if that address was already taken.
|
---|
220 | /// Otherwise, returns Unknown.
|
---|
221 | /// If address was disallowed, returns Disallowed.
|
---|
222 | /// </summary>
|
---|
223 | /// <param name="address">address</param>
|
---|
224 | /// <returns>shortest distance to address</returns>
|
---|
225 | public int Distance(int address)
|
---|
226 | {
|
---|
227 | uint state = states[address];
|
---|
228 | if ((state & wasTakenFlag) != 0)
|
---|
229 | return (int)(state & stateDataMask);
|
---|
230 | else
|
---|
231 | return Unknown;
|
---|
232 | }
|
---|
233 | public const int Disallowed = 0x7FFFFFFF;
|
---|
234 | public const int Unknown = 0x7FFFFFFE;
|
---|
235 |
|
---|
236 | /// <summary>
|
---|
237 | /// Number of different addresses offered.
|
---|
238 | /// </summary>
|
---|
239 | public int Count { get { return binaryHeapSize + taken; } }
|
---|
240 |
|
---|
241 | /// <summary>
|
---|
242 | /// Number of addresses taken from the collection using TakeClosest.
|
---|
243 | /// </summary>
|
---|
244 | public int Taken { get { return taken; } }
|
---|
245 |
|
---|
246 | #region private stuff
|
---|
247 | protected const uint wasTakenFlag = 0x80000000;
|
---|
248 | protected const uint stateDataMask = 0x7FFFFFFF;
|
---|
249 |
|
---|
250 | protected struct HeapItem
|
---|
251 | {
|
---|
252 | public int address;
|
---|
253 | public int distance;
|
---|
254 | }
|
---|
255 |
|
---|
256 | protected HeapItem[] binaryHeap;
|
---|
257 | protected int binaryHeapSize;
|
---|
258 |
|
---|
259 | // multiple funtion memory with address as index:
|
---|
260 | // wasTakenFlag indicates that address should not be taken anymore,
|
---|
261 | // flag is set after address was returned by TakeClosest or disallowed
|
---|
262 | // if wasTakenFlag is not set, lower 31 bits contain index in binaryHeap + 1 (0 meaning not contained)
|
---|
263 | // if wasTakenFlag is set, lower 31 bits contain shortest distance resp. Disallowed
|
---|
264 | protected uint[] states;
|
---|
265 |
|
---|
266 | protected int maxAddress;
|
---|
267 | protected int allocatedSize;
|
---|
268 | protected int lastTakenDistance;
|
---|
269 | protected int taken;
|
---|
270 | #endregion
|
---|
271 | }
|
---|
272 | }
|
---|