using System; using System.Collections.Generic; // collection of non-negative integers (called addresses) with non-negative priorities (called distances) // based on binary heap // main operations: // * Offer: offer distance to an address // * TakeClosest: get and remove address with shortest offered distance // only the shortest of multiple offered distances to a specific address is kept // user must ensure not to offer a distance shorter than those of addresses already taken // (easily achieved by the typical usage of this class) // memory consumption: 4 bytes per potential address + 8 bytes per address in collection // time consumption: O(log n) for both main operations, where n is the current size of the collection namespace Common { /// /// Priority queue on bounded integer address space. /// class AddressPriorityQueue { /// /// Creates an empty collection. /// /// maximum address allowed public AddressPriorityQueue(int maxAddress) { if (maxAddress < 0 || maxAddress > 0x7FFFFFFE) throw new Exception("AddressPriorityQueue: Maximum capacity is 1<<31 addresses!"); this.maxAddress = maxAddress; allocatedSize = 4 * (int)Math.Sqrt(maxAddress + 1); // good for addresses in a 2D-space binaryHeap = new HeapItem[allocatedSize]; binaryHeapSize = 0; states = new uint[maxAddress + 1]; lastTakenDistance = 0; taken = 0; } /// /// Offer address with distance. Might have been offered with same or different distance before. /// /// the address /// the distance /// true if address was new or offer was better than all former offers for this address public bool Offer(int address, int distance) { if (address < 0 || address > maxAddress) throw new Exception(string.Format("AddressPriorityQueue.Offer: Address '{0}' is not in valid range!", address)); uint state = states[address]; if ((state & wasTakenFlag) != 0) { if (state == (Disallowed | wasTakenFlag)) throw new Exception(string.Format("AddressPriorityQueue.Offer: Ambiguous usage! Address '{0}' was both offered and disallowed!", address)); else return false; // if class is used correctly, the new offer must be worse than one before at this point } else { if (distance < 0 || distance >= Unknown) throw new Exception(string.Format("AddressPriorityQueue.Offer: Distance '{0}' is not in valid range!", distance)); int index = (int)state - 1; bool isShorter = true; if (state > 0) // means address is already contained in binary heap isShorter = (distance < binaryHeap[index].distance); if (isShorter) { if (distance < lastTakenDistance) 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)); #region binary heap grow algo if (index < 0) { // address is not yet contained in binary heap, insert at bottom index = binaryHeapSize; binaryHeapSize++; if (binaryHeapSize > allocatedSize) { allocatedSize *= 2; if (allocatedSize > maxAddress + 1) allocatedSize = maxAddress + 1; Array.Resize(ref binaryHeap, allocatedSize); } } // correct binary heap, new item can only move up (either shorter distance than before or at bottom) while (index > 0) { int parent = (index - 1) / 2; if (distance >= binaryHeap[parent].distance) break; binaryHeap[index] = binaryHeap[parent]; states[binaryHeap[index].address] = (uint)index + 1; index = parent; } binaryHeap[index].distance = distance; binaryHeap[index].address = address; states[binaryHeap[index].address] = (uint)index + 1; #endregion return true; } else return false; } } /// /// Does nothing but returns same value as Offer(...) would. /// /// the address /// the distance /// true if address was new or offer was better than all former offers for this address public bool TestOffer(int address, int distance) { if (address < 0 || address > maxAddress) throw new Exception(string.Format("AddressPriorityQueue.TestOffer: Address '{0}' is not in valid range!", address)); uint state = states[address]; if ((state & wasTakenFlag) != 0) { if (state == (Disallowed | wasTakenFlag)) throw new Exception(string.Format("AddressPriorityQueue.TestOffer: Ambiguous usage! Address '{0}' was both offered and disallowed!", address)); else return false; } else { if (distance < 0 || distance >= Unknown) throw new Exception(string.Format("AddressPriorityQueue.TestOffer: Distance '{0}' is not in valid range!", distance)); if (state > 0) // means address is already contained in binary heap return distance < binaryHeap[state - 1].distance; else return true; } } /// /// Marks address as disallowed, which changes the Distance(address) query result and forbids Offer calls for this address. /// /// the address public void Disallow(int address) { if (address < 0 || address > maxAddress) throw new Exception(string.Format("AddressPriorityQueue.Disallow: Address '{0}' is not in valid range!", address)); uint state = states[address]; if (state != (Disallowed | wasTakenFlag)) { if (state != 0) throw new Exception(string.Format("AddressPriorityQueue.Disallow: Ambiguous usage! Address '{0}' was both offered and disallowed!", address)); states[address] = (Disallowed | wasTakenFlag); } } /// /// Get and remove address with shortest offered distance. /// /// the address /// the distance /// true if operation was successful, false for empty collection public bool TakeClosest(out int address, out int distance) { if (binaryHeapSize == 0) { address = 0; distance = 0; return false; } address = binaryHeap[0].address; distance = binaryHeap[0].distance; states[address] = (uint)distance | wasTakenFlag; lastTakenDistance = distance; taken++; #region binary heap shrink algo binaryHeapSize--; if (binaryHeapSize > 0) { HeapItem last = binaryHeap[binaryHeapSize]; int index = 0; int child = 1; while (child < binaryHeapSize) { if (child < binaryHeapSize - 1 && binaryHeap[child].distance > binaryHeap[child + 1].distance) child++; // right child instead of left if (last.distance <= binaryHeap[child].distance) break; binaryHeap[index] = binaryHeap[child]; states[binaryHeap[index].address] = (uint)index + 1; index = child; child = 2 * child + 1; } binaryHeap[index] = last; states[last.address] = (uint)index + 1; } #endregion return true; } /// /// Remove all addresses from collection and delete calculations. Collection returns to state as after /// new except that possibly resized memory is kept. /// public void Clear() { if (binaryHeapSize + taken > 0) { binaryHeapSize = 0; Array.Clear(states, 0, maxAddress + 1); lastTakenDistance = 0; taken = 0; } } /// /// Status tracker. Returns shortest distance to an address, if that address was already taken. /// Otherwise, returns Unknown. /// If address was disallowed, returns Disallowed. /// /// address /// shortest distance to address public int Distance(int address) { uint state = states[address]; if ((state & wasTakenFlag) != 0) return (int)(state & stateDataMask); else return Unknown; } public const int Disallowed = 0x7FFFFFFF; public const int Unknown = 0x7FFFFFFE; /// /// Number of different addresses offered. /// public int Count { get { return binaryHeapSize + taken; } } /// /// Number of addresses taken from the collection using TakeClosest. /// public int Taken { get { return taken; } } #region private stuff protected const uint wasTakenFlag = 0x80000000; protected const uint stateDataMask = 0x7FFFFFFF; protected struct HeapItem { public int address; public int distance; } protected HeapItem[] binaryHeap; protected int binaryHeapSize; // multiple funtion memory with address as index: // wasTakenFlag indicates that address should not be taken anymore, // flag is set after address was returned by TakeClosest or disallowed // if wasTakenFlag is not set, lower 31 bits contain index in binaryHeap + 1 (0 meaning not contained) // if wasTakenFlag is set, lower 31 bits contain shortest distance resp. Disallowed protected uint[] states; protected int maxAddress; protected int allocatedSize; protected int lastTakenDistance; protected int taken; #endregion } }