| 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 | }
|
|---|