/// <summary> /// Returns an item randomly, based on the specified weightings. /// </summary> /// <returns>The item generated.</returns> /// <exception cref="System.InvalidOperationException">No weightings are defined.</exception> public T Generate() { if (weightingRangesAndItems.Count == 0) { throw new InvalidOperationException("No weightings are defined."); } Int64 randomIndex = randomGenerator.Next(weightingsTotal) + weightingStartOffset; // Create an item and weighting with the random index var randomItemAndWeighting = new ItemAndWeighting <T>(default(T), new LongIntegerRange(randomIndex, 1)); if (weightingRangesAndItems.Contains(randomItemAndWeighting) == true) { // Overwrite 'randomItemAndWeighting' with the actual item and weighting from the tree (which will contain the correct item... not a default) randomItemAndWeighting = weightingRangesAndItems.Get(randomItemAndWeighting); } else { // If a range starting with the random index doesn't exist, get the next lower range (which will include the random index) randomItemAndWeighting = weightingRangesAndItems.GetNextLessThan(randomItemAndWeighting).Item2; } return(randomItemAndWeighting.Item); }
/// <summary> /// Removes an item and its weighting from the generator. /// </summary> /// <param name="item">The item to remove.</param> /// <remarks>This method consumes O(n * log(n)) time complexity (where n is the number of weightings defined).</remarks> /// <exception cref="System.ArgumentException">A weighting for the specified item does not exist.</exception> public void RemoveWeighting(T item) { if (itemToWeightingMap.ContainsKey(item) == false) { throw new ArgumentException("A weighting for the specified item does not exist.", nameof(item)); } var itemAndWeightingToRemove = new ItemAndWeighting <T>(item, itemToWeightingMap[item]); Int32 numberOfLowerRanges = weightingRangesAndItems.GetCountLessThan(itemAndWeightingToRemove); Int32 numberOfHigherRanges = weightingRangesAndItems.GetCountGreaterThan(itemAndWeightingToRemove); // Remove the weighting corresponding to the specified item weightingRangesAndItems.Remove(itemAndWeightingToRemove); itemToWeightingMap.Remove(item); // TODO: Could potentially refactor each half of if statement since most codes lines are the same... abstract GetNextLessThan() and GetNextGreaterThan() behind lambdas (and also have a lambda negating rangeToRemove.Length for -= and += operators) if (numberOfLowerRanges < numberOfHigherRanges) { // Shuffle up the values of all the ranges lower than the one removed Tuple <Boolean, ItemAndWeighting <T> > nextLowerRangeResult = weightingRangesAndItems.GetNextLessThan(itemAndWeightingToRemove); while (nextLowerRangeResult.Item1 == true) { nextLowerRangeResult.Item2.Weighting.StartValue += itemAndWeightingToRemove.Weighting.Length; itemToWeightingMap[nextLowerRangeResult.Item2.Item] = nextLowerRangeResult.Item2.Weighting; nextLowerRangeResult = weightingRangesAndItems.GetNextLessThan(nextLowerRangeResult.Item2); } weightingStartOffset += itemAndWeightingToRemove.Weighting.Length; } else { // Shuffle down the values of all the ranges higher than the one removed Tuple <Boolean, ItemAndWeighting <T> > nextHigherRangeResult = weightingRangesAndItems.GetNextGreaterThan(itemAndWeightingToRemove); while (nextHigherRangeResult.Item1 == true) { nextHigherRangeResult.Item2.Weighting.StartValue -= itemAndWeightingToRemove.Weighting.Length; itemToWeightingMap[nextHigherRangeResult.Item2.Item] = nextHigherRangeResult.Item2.Weighting; nextHigherRangeResult = weightingRangesAndItems.GetNextGreaterThan(nextHigherRangeResult.Item2); } } weightingsTotal -= itemAndWeightingToRemove.Weighting.Length; if (weightingRangesAndItems.Count == 0) { weightingStartOffset = 0; } }
/// <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); } }
#pragma warning disable 1591 public Int32 CompareTo(ItemAndWeighting <T> other) { return(this.weighting.CompareTo(other.weighting)); }