/// <summary> /// Initialises a new instance of the MoreComplexDataStructures.UniqueRandomGenerator class. /// </summary> /// <param name="rangeStart">The inclusive start of the range to generate random numbers from.</param> /// <param name="rangeEnd">The inclusive end of the range to generate random numbers from.</param> /// <exception cref="System.ArgumentException">Parameter 'rangeEnd' is less than parameter 'rangeStart'.</exception> /// <exception cref="System.ArgumentException">The total inclusive range exceeds Int64.MaxValue.</exception> public UniqueRandomGenerator(Int64 rangeStart, Int64 rangeEnd) { if (rangeEnd < rangeStart) { throw new ArgumentException($"Parameter '{nameof(rangeEnd)}' must be greater than or equal to parameter '{nameof(rangeStart)}'.", nameof(rangeEnd)); } if (rangeStart < 1) { if (rangeStart + Int64.MaxValue <= rangeEnd) { throw new ArgumentException("The total inclusive range cannot exceed Int64.MaxValue.", nameof(rangeEnd)); } } else if (rangeEnd > -2) { if (rangeEnd - Int64.MaxValue >= rangeStart) { throw new ArgumentException("The total inclusive range cannot exceed Int64.MaxValue.", nameof(rangeEnd)); } } rangeTree = new RangeTree(); var baseRange = new LongIntegerRange(rangeStart, rangeEnd - rangeStart + 1); var baseRangeAndCounts = new RangeAndSubtreeCounts(baseRange, 0, 0); rangeTree.Add(baseRangeAndCounts); this.rangeStart = rangeStart; this.rangeEnd = rangeEnd; numbersGeneratedCount = 0; randomGenerator = new DefaultRandomGenerator(); }
/// <summary> /// Returns a unique random number from within the range. /// </summary> /// <returns>The random number.</returns> /// <exception cref="System.InvalidOperationException">No further unique numbers remain in the range.</exception> public Int64 Generate() { if (NumbersRemainingCount == 0) { throw new InvalidOperationException("Cannot generate a random number as no further unique numbers exist in the specified range."); } WeightBalancedTreeNode <RangeAndSubtreeCounts> currentNode = rangeTree.RootNode; Int64 returnNumber = 0; while (true) { // Decide whether to stop at this node, or to move left or right Int64 randomRange = currentNode.Item.LeftSubtreeRangeCount + currentNode.Item.Range.Length + currentNode.Item.RightSubtreeRangeCount; Int64 randomNumber = randomGenerator.Next(randomRange); if (randomNumber < currentNode.Item.LeftSubtreeRangeCount) { // Move to the left child node currentNode.Item.LeftSubtreeRangeCount--; currentNode = currentNode.LeftChildNode; } else if (randomNumber >= (currentNode.Item.LeftSubtreeRangeCount + currentNode.Item.Range.Length)) { // Move to the right child node currentNode.Item.RightSubtreeRangeCount--; currentNode = currentNode.RightChildNode; } else { // Stop traversing at the current node returnNumber = (randomNumber - currentNode.Item.LeftSubtreeRangeCount) + currentNode.Item.Range.StartValue; break; } } if (returnNumber == currentNode.Item.Range.StartValue) { if (currentNode.Item.Range.Length == 1) { if (currentNode.LeftChildNode != null && currentNode.RightChildNode != null) { // The current node will be swapped with the next less than or next greater than, so need to update the subtree range counts between the current and swapped nodes WeightBalancedTreeNode <RangeAndSubtreeCounts> swapNode = null; if (currentNode.LeftSubtreeSize > currentNode.RightSubtreeSize) { // The current node will be swapped with the next less than swapNode = rangeTree.GetNextLessThan(currentNode); swapNode.Item.LeftSubtreeRangeCount = currentNode.Item.LeftSubtreeRangeCount - swapNode.Item.Range.Length; swapNode.Item.RightSubtreeRangeCount = currentNode.Item.RightSubtreeRangeCount; } else { // The current node will be swapped with the next greater than swapNode = rangeTree.GetNextGreaterThan(currentNode); swapNode.Item.LeftSubtreeRangeCount = currentNode.Item.LeftSubtreeRangeCount; swapNode.Item.RightSubtreeRangeCount = currentNode.Item.RightSubtreeRangeCount - swapNode.Item.Range.Length; } // Update the subtree range counts Func <WeightBalancedTreeNode <RangeAndSubtreeCounts>, Nullable <Boolean>, Boolean> updateSubtreeRangeCountFunc = (node, nodeTraversedToFromLeft) => { if (node == currentNode) { return(false); } if (nodeTraversedToFromLeft.HasValue) { if (nodeTraversedToFromLeft == true) { node.Item.LeftSubtreeRangeCount -= swapNode.Item.Range.Length; } else { node.Item.RightSubtreeRangeCount -= swapNode.Item.Range.Length; } } return(true); }; rangeTree.TraverseUpToNode(swapNode, updateSubtreeRangeCountFunc); } rangeTree.Remove(currentNode.Item); } else { // Shorten the range on the left side currentNode.Item.Range.StartValue++; currentNode.Item.Range.Length--; } } else if (returnNumber == (currentNode.Item.Range.StartValue + currentNode.Item.Range.Length - 1)) { // Shorten the range on the right side currentNode.Item.Range.Length--; } else { // Split the range var leftSideRange = new LongIntegerRange(currentNode.Item.Range.StartValue, returnNumber - currentNode.Item.Range.StartValue); var newNodeItem = new RangeAndSubtreeCounts(leftSideRange, currentNode.Item.LeftSubtreeRangeCount, currentNode.Item.RightSubtreeRangeCount); var newNode = new WeightBalancedTreeNode <RangeAndSubtreeCounts>(newNodeItem, null); currentNode.Item.Range.Length -= (returnNumber - currentNode.Item.Range.StartValue + 1); currentNode.Item.Range.StartValue = returnNumber + 1; WeightBalancedTreeNode <RangeAndSubtreeCounts> upperNode = null; if (currentNode.LeftSubtreeSize > currentNode.RightSubtreeSize) { // 'Push' the current node down right if (currentNode == rangeTree.RootNode) { rangeTree.RootNode = newNode; } else { if (currentNode.ParentNode.LeftChildNode == currentNode) { currentNode.ParentNode.LeftChildNode = newNode; } else { currentNode.ParentNode.RightChildNode = newNode; } newNode.ParentNode = currentNode.ParentNode; } newNode.LeftChildNode = currentNode.LeftChildNode; currentNode.LeftChildNode.ParentNode = newNode; newNode.RightChildNode = currentNode; newNode.LeftSubtreeSize = currentNode.LeftSubtreeSize; newNode.RightSubtreeSize = 1 + currentNode.RightSubtreeSize; newNode.Item.LeftSubtreeRangeCount = currentNode.Item.LeftSubtreeRangeCount;; newNode.Item.RightSubtreeRangeCount = currentNode.Item.Range.Length + currentNode.Item.RightSubtreeRangeCount; currentNode.ParentNode = newNode; currentNode.LeftChildNode = null; currentNode.LeftSubtreeSize = 0; currentNode.Item.LeftSubtreeRangeCount = 0; upperNode = newNode; } else { // 'Push' the new node down left newNode.LeftChildNode = currentNode.LeftChildNode; newNode.RightChildNode = null; newNode.LeftSubtreeSize = currentNode.LeftSubtreeSize; newNode.RightSubtreeSize = 0; newNode.Item.LeftSubtreeRangeCount = currentNode.Item.LeftSubtreeRangeCount; newNode.Item.RightSubtreeRangeCount = 0; newNode.ParentNode = currentNode; if (newNode.LeftChildNode != null) { newNode.LeftChildNode.ParentNode = newNode; } currentNode.LeftChildNode = newNode; currentNode.LeftSubtreeSize++; currentNode.Item.LeftSubtreeRangeCount += newNode.Item.Range.Length; upperNode = currentNode; } // Update the subtree sizes Action <WeightBalancedTreeNode <RangeAndSubtreeCounts>, Nullable <Boolean> > incrementSubtreeSizeAction = (node, nodeTraversedToFromLeft) => { if (nodeTraversedToFromLeft.HasValue) { if (nodeTraversedToFromLeft.Value == true) { node.LeftSubtreeSize++; } else { node.RightSubtreeSize++; } } }; rangeTree.TraverseUpFromNode(upperNode, incrementSubtreeSizeAction); // Balance the tree rangeTree.BalanceTreeUpFromNode(upperNode); } numbersGeneratedCount++; return(returnNumber); }