/// <summary> /// Sets the status for the specified integer to false. /// </summary> /// <param name="inputInteger">The integer to set the status for.</param> public void SetStatusFalse(Int64 inputInteger) { LongIntegerRange rangeToModify = null; // Create an integer range based on the inputted integer LongIntegerRange inputIntegerRange = new LongIntegerRange(inputInteger, 1); // Attempt to find the range holding the inputted integer Tuple <Boolean, LongIntegerRange> nextLowerRange = rangeStatuses.GetNextLessThan(inputIntegerRange); // Check whether a range starting with the specified integer exists in the tree if (rangeStatuses.Contains(inputIntegerRange) == true) { // Need special handling if inputInteger is Int64.MaxValue if (inputInteger == Int64.MaxValue) { rangeToModify = new LongIntegerRange(inputInteger, 1); } else { rangeToModify = rangeStatuses.GetNextLessThan(new LongIntegerRange(inputInteger + 1, 1)).Item2; } } // Otherwise check whether the specified integer exists in the next lower range else if (nextLowerRange.Item1 == true && (inputInteger <= (nextLowerRange.Item2.StartValue + nextLowerRange.Item2.Length - 1))) { rangeToModify = nextLowerRange.Item2; } if (rangeToModify != null) { // Handle the case where the inputted integer is at the start of the range to modify if (inputInteger == rangeToModify.StartValue) { if (rangeToModify.Length == 1) { rangeStatuses.Remove(rangeToModify); } else { rangeToModify.Length = rangeToModify.Length - 1; rangeToModify.StartValue = rangeToModify.StartValue + 1; } } // Handle the case where the inputted integer is at the end of the range to modify else if (inputInteger == (rangeToModify.StartValue + rangeToModify.Length - 1)) { rangeToModify.Length = rangeToModify.Length - 1; } // Handle the case where the inputted integer is in the middle of the range to modify else { LongIntegerRange newRange = new LongIntegerRange(inputInteger + 1, rangeToModify.Length - (inputInteger - rangeToModify.StartValue + 1)); rangeToModify.Length = inputInteger - rangeToModify.StartValue; rangeStatuses.Add(newRange); } count--; } }
/// <summary> /// Sets a contiguous range of statuses to true when all statuses are false. /// </summary> /// <param name="rangeStart">The inclusive start of the range of integers to set to true.</param> /// <param name="rangeEnd">The inclusive end of the range of integers to set to true.</param> /// <exception cref="System.InvalidOperationException">Statuses for integers have been set to true.</exception> /// <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 void SetRangeTrue(Int64 rangeStart, Int64 rangeEnd) { if (rangeStatuses.Count != 0) { throw new InvalidOperationException("A range of statuses can only be set true when all existing statuses are false."); } 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)); } } var newRange = new LongIntegerRange(rangeStart, rangeEnd - rangeStart + 1); rangeStatuses.Add(newRange); count = newRange.Length; }
/// <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> /// Initialises a new instance of the MoreComplexDataStructures.UniqueRandomGenerator+RangeAndSubtreeCounts class. /// </summary> /// <param name="range">The range of integers.</param> /// <param name="leftSubtreeRangeCount">The total count of integers in the left subtree of a node holding an instance of this class.</param> /// <param name="rightSubtreeRangeCount">The total count of integers in the right subtree of a node holding an instance of this class.</param> public RangeAndSubtreeCounts(LongIntegerRange range, Int64 leftSubtreeRangeCount, Int64 rightSubtreeRangeCount) { ValidateSubtreeRangeCount(leftSubtreeRangeCount, "leftSubtreeRangeCount"); ValidateSubtreeRangeCount(rightSubtreeRangeCount, "rightSubtreeRangeCount"); this.range = range; this.leftSubtreeRangeCount = leftSubtreeRangeCount; this.rightSubtreeRangeCount = rightSubtreeRangeCount; }
/// <summary> /// Returns the lowest unflagged number in the range. /// </summary> /// <returns>A tuple containing 2 values: a boolean indicating whether any flagged numbers exist (false if no flagged numbers exist), and the lowest unflagged number in the range (or 0 if no unflagged numbers exist).</returns> public Tuple <Boolean, Int64> GetLowestUnflaggedNumber() { if (rangeStorer.Count == 0) { return(new Tuple <Boolean, Int64>(false, 0)); } else { LongIntegerRange lowestRange = rangeStorer.MinimumRange; return(new Tuple <Boolean, Int64>(true, lowestRange.StartValue)); } }
/// <summary> /// Returns the highest unflagged number in the range. /// </summary> /// <returns>A tuple containing 2 values: a boolean indicating whether any flagged numbers exist (false if no flagged numbers exist), and the highest unflagged number in the range (or 0 if no unflagged numbers exist).</returns> public Tuple <Boolean, Int64> GetHighestUnflaggedNumber() { if (rangeStorer.Count == 0) { return(new Tuple <Boolean, Int64>(false, 0)); } else { LongIntegerRange highestRange = rangeStorer.MaximumRange; return(new Tuple <Boolean, Int64>(true, highestRange.StartValue + highestRange.Length - 1)); } }
/// <summary> /// Sets the status for the specified integer to true. /// </summary> /// <param name="inputInteger">The integer to set the status for.</param> /// <exception cref="System.InvalidOperationException">The class cannot support storing statuses for greater than Int64.MaxValue integers.</exception> public void SetStatusTrue(Int64 inputInteger) { if (count == Int64.MaxValue) { throw new InvalidOperationException("The class cannot support storing statuses for greater than Int64.MaxValue integers."); } // Create an integer range based on the inputted integer LongIntegerRange inputIntegerRange = new LongIntegerRange(inputInteger, 1); // Check whether a range starting with the specified integer already exists in the tree if (rangeStatuses.Contains(inputIntegerRange) == true) { return; } Tuple <Boolean, LongIntegerRange> nextLowerRange = rangeStatuses.GetNextLessThan(inputIntegerRange); Tuple <Boolean, LongIntegerRange> nextGreaterRange = rangeStatuses.GetNextGreaterThan(inputIntegerRange); // Check whether the specified integer exists in the next lower range if (nextLowerRange.Item1 == true && (inputInteger <= (nextLowerRange.Item2.StartValue + nextLowerRange.Item2.Length - 1))) { return; } // Handle the case where the new integer 'merges' two existing ranges if (IntegerIsImmediatelyGreaterThanRange(inputInteger, nextLowerRange) && IntegerIsImmediatelyLessThanRange(inputInteger, nextGreaterRange)) { rangeStatuses.Remove(nextGreaterRange.Item2); nextLowerRange.Item2.Length = nextLowerRange.Item2.Length + 1 + nextGreaterRange.Item2.Length; } // Handle the case where the new integer 'extends' the next lower range by 1 else if (IntegerIsImmediatelyGreaterThanRange(inputInteger, nextLowerRange)) { nextLowerRange.Item2.Length = nextLowerRange.Item2.Length + 1; } // Handle the case where the new integer reduces the start value of the next greater range by 1 else if (IntegerIsImmediatelyLessThanRange(inputInteger, nextGreaterRange)) { nextGreaterRange.Item2.StartValue = nextGreaterRange.Item2.StartValue - 1; nextGreaterRange.Item2.Length = nextGreaterRange.Item2.Length + 1; } // Otherwise add a new range starting at 'inputInteger' with length = 1 else { rangeStatuses.Add(inputIntegerRange); } count++; }
/// <summary> /// Returns copies of all number ranges in the underlying tree in descending order. /// </summary> /// <returns>All number ranges in the underlying tree in descending order.</returns> /// <remarks>Note that copies/clones of the actual ranges are returned to prevent modification/invalidation of the underlying tree structure.</remarks> public IEnumerable <LongIntegerRange> GetAllRangesDescending() { if (rangeStatuses.Count == 0) { yield break; } LongIntegerRange maximumRange = rangeStatuses.Max; yield return(new LongIntegerRange(maximumRange.StartValue, maximumRange.Length)); foreach (LongIntegerRange currentRange in rangeStatuses.GetAllLessThan(maximumRange)) { yield return(new LongIntegerRange(currentRange.StartValue, currentRange.Length)); } }
/// <summary> /// Retrieves the current status for the specified integer. /// </summary> /// <param name="inputInteger">The integer to retrieve the status for.</param> /// <returns>The status of the integer.</returns> public Boolean GetStatus(Int64 inputInteger) { // Create an integer range based on the inputted integer LongIntegerRange inputIntegerRange = new LongIntegerRange(inputInteger, 1); // Check whether a range starting with the specified integer exists in the tree if (rangeStatuses.Contains(inputIntegerRange) == true) { return(true); } Tuple <Boolean, LongIntegerRange> nextLowerRange = rangeStatuses.GetNextLessThan(inputIntegerRange); // Check whether the specified integer exists in the next lower range if (nextLowerRange.Item1 == true && (inputInteger <= (nextLowerRange.Item2.StartValue + nextLowerRange.Item2.Length - 1))) { return(true); } return(false); }
/// <summary> /// Sets the random weightings, and the item corresponding to each weighting. /// </summary> /// <param name="weightings">The set of weightings defined by list of tuples containing 2 values: the item to attach to the weighting, and a weighting allocated to random selection of the item (i.e. an item with weight 2 will be twice as likely to be selected as an item with weight 1).</param> /// <exception cref="System.ArgumentException">The specified set of weightings is empty.</exception> /// <exception cref="System.ArgumentException">The total of the specified weightings exceeds Int64.MaxValue.</exception> /// <exception cref="System.ArgumentException">The specified weightings contain a duplicate item.</exception> public void SetWeightings(IList <Tuple <T, Int64> > weightings) { if (weightings.Count == 0) { throw new ArgumentException("The specified set of weightings is empty.", nameof(weightings)); } Clear(); var weightingsAsRanges = new ItemAndWeighting <T> [weightings.Count]; Int64 nextRangeStartValue = 0; Int32 currentIndex = 0; foreach (Tuple <T, Int64> currentWeighting in weightings) { if ((Int64.MaxValue - 1) - nextRangeStartValue < (currentWeighting.Item2 - 1)) { throw new ArgumentException("The total of the specified weightings cannot exceed Int64.MaxValue.", nameof(weightings)); } var currentRange = new LongIntegerRange(nextRangeStartValue, currentWeighting.Item2); weightingsAsRanges[currentIndex] = new ItemAndWeighting <T>(currentWeighting.Item1, currentRange); currentIndex++; weightingsTotal += (currentWeighting.Item2); nextRangeStartValue += currentWeighting.Item2; } foreach (ItemAndWeighting <T> currentItemAndWeighting in weightingsAsRanges) { if (itemToWeightingMap.ContainsKey(currentItemAndWeighting.Item) == true) { throw new ArgumentException($"The specified weightings contain duplicate item with value = '{currentItemAndWeighting.Item.ToString()}'.", nameof(weightings)); } weightingRangesAndItems.Add(currentItemAndWeighting); itemToWeightingMap.Add(currentItemAndWeighting.Item, currentItemAndWeighting.Weighting); } }
/// <summary> /// Initialises a new instance of the MoreComplexDataStructures.WeightedRandomGenerator+ItemAndWeighting class. /// </summary> /// <param name="item">The item attached to the weighting.</param> /// <param name="weighting">The weighting.</param> public ItemAndWeighting(T item, LongIntegerRange weighting) { this.item = item; this.weighting = weighting; }
/// <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); }