using System; using System.Collections.Generic; using UnityDebug = UnityEngine.Debug; using UnityRandom = UnityEngine.Random; namespace Core.Extensions { /// /// Extension methods for ILists /// public static class IListExtensions { static readonly Random s_SharedRandom = new Random(); /// /// Select an item from a list using a weighted selection. /// /// This is an O(n) operation, not constant-time like equal random selection. /// An of elements to choose from /// The sum of all the weights of the elements /// A delegate to retrieve the weight of a specific element /// An element randomly selected from public static T WeightedSelection(this IList elements, int weightSum, Func getElementWeight) { int index = elements.WeightedSelectionIndex(weightSum, getElementWeight); return elements[index]; } /// /// Select an item from a list using a weighted selection. /// /// This is an O(n) operation, not constant-time like equal random selection. /// An of elements to choose from /// The sum of all the weights of the elements /// A delegate to retrieve the weight of a specific element /// An element randomly selected from public static T WeightedSelection(this IList elements, float weightSum, Func getElementWeight) { int index = elements.WeightedSelectionIndex(weightSum, getElementWeight); return elements[index]; } /// /// Select the index of an item from a list using a weighted selection. /// /// This is an O(n) operation, not constant-time like equal random selection. /// An of elements to choose from /// The sum of all the weights of the elements /// A delegate to retrieve the weight of a specific element /// The index of an element randomly selected from public static int WeightedSelectionIndex(this IList elements, int weightSum, Func getElementWeight) { if (weightSum <= 0) { throw new ArgumentException("WeightSum should be a positive value", "weightSum"); } int selectionIndex = 0; int selectionWeightIndex = UnityRandom.Range(0, weightSum); int elementCount = elements.Count; if (elementCount == 0) { throw new InvalidOperationException("Cannot perform selection on an empty collection"); } int itemWeight = getElementWeight(elements[selectionIndex]); while (selectionWeightIndex >= itemWeight) { selectionWeightIndex -= itemWeight; selectionIndex++; if (selectionIndex >= elementCount) { throw new ArgumentException("Weighted selection exceeded indexable range. Is your weightSum correct?", "weightSum"); } itemWeight = getElementWeight(elements[selectionIndex]); } return selectionIndex; } /// /// Select the index of an item from a list using a weighted selection. /// /// This is an O(n) operation, not constant-time like equal random selection. /// An of elements to choose from /// The sum of all the weights of the elements /// A delegate to retrieve the weight of a specific element /// The index of an element randomly selected from public static int WeightedSelectionIndex(this IList elements, float weightSum, Func getElementWeight) { if (weightSum <= 0) { throw new ArgumentException("WeightSum should be a positive value", "weightSum"); } int selectionIndex = 0; double selectedWeight = s_SharedRandom.NextDouble() * weightSum; int elementCount = elements.Count; if (elementCount == 0) { throw new InvalidOperationException("Cannot perform selection on an empty collection"); } double itemWeight = getElementWeight(elements[selectionIndex]); while (selectedWeight >= itemWeight) { selectedWeight -= itemWeight; selectionIndex++; if (selectionIndex >= elementCount) { throw new ArgumentException("Weighted selection exceeded indexable range. Is your weightSum correct?", "weightSum"); } itemWeight = getElementWeight(elements[selectionIndex]); } return selectionIndex; } /// /// Shuffle this List into a new array copy /// public static T[] Shuffle(this IList original) { int numItems = original.Count; T[] result = new T[numItems]; for (int i = 0; i < numItems; ++i) { int j = UnityRandom.Range(0, i + 1); if (j != i) { result[i] = result[j]; } result[j] = original[i]; } return result; } /// /// Goes to the next element of the list /// /// An of elements to choose from /// The current index to be changed via reference /// if the list should wrap /// The generic parameter for the list /// true if there is a next item in the list public static bool Next(this IList elements, ref int currentIndex, bool wrap = false) { int count = elements.Count; if (count == 0) { return false; } currentIndex++; if (currentIndex >= count) { if (wrap) { currentIndex = 0; return true; } currentIndex = count - 1; return false; } return true; } /// /// Goes to the previous element of the list /// /// An of elements to choose from /// The current index to be changed via reference /// if the list should wrap /// The generic parameter for the list /// true if there is a previous item in the list public static bool Prev(this IList elements, ref int currentIndex, bool wrap = false) { int count = elements.Count; if (count == 0) { return false; } currentIndex--; if (currentIndex < 0) { if (wrap) { currentIndex = count - 1; return true; } currentIndex = 0; return false; } return true; } } }