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;
}
}
}