我會做這樣的事情:
string[] names = new[] { "Foo", "Bar", "Fix" };
// The weights will be 3, 2, 1
int[] weights = new int[names.Length];
for (int i = 0; i < names.Length; i++)
{
weights[i] = names.Length - i;
}
int[] cumulativeWeights = new int[names.Length];
// The cumulativeWeights will be 3, 5, 6
// so if we generate a number, 1-3 Foo, 4-5 Bar, 6 Fiz
cumulativeWeights[0] = weights[0];
int totalWeight = weights[0];
for (int i = 1; i < cumulativeWeights.Length; i++)
{
cumulativeWeights[i] = cumulativeWeights[i - 1] + weights[i];
totalWeight += weights[i];
}
var rnd = new Random();
while (true)
{
int selectedWeight = rnd.Next(totalWeight) + 1; // random returns 0..5, +1 == 1..6
int ix = Array.BinarySearch(cumulativeWeights, selectedWeight);
// If value is not found and value is less than one or more
// elements in array, a negative number which is the bitwise
// complement of the index of the first element that is
// larger than value.
if (ix < 0)
{
ix = ~ix;
}
Console.WriteLine(names[ix]);
}
我已經建立的weight
陣列。我使用了一種線性方法。第一個元素的權重等於(元素數量),第二個元素的權重爲(元素個數-1)等等。你可以使用你的算法,但如果權重是整數,它會更容易。
然後我計算了一個cumulativeWeights
數組和一個totalWeight
。
然後,我可以提取1
和totalWeight
之間的二進制數,並找到具有cumulativeWeight
的索引即< =隨機數。正在cumulativeWeights
排序(顯然:-)),我可以使用Array.BinarySearch
,它的優點是,如果沒有找到確切的數字,給出下一個最大數字的索引。
現在,隨着double
weight
的IT變得有點複雜了Random
部分:
string[] names = new[] { "Foo", "Bar", "Fix" };
// The weights will be 3.375, 2.25, 1.5
double[] weights = new double[names.Length];
for (int i = 0; i < names.Length; i++)
{
weights[i] = Math.Pow(1.5, names.Length - i);
}
double[] cumulativeWeights = new double[names.Length];
// The cumulativeWeights will be 3.375, 3.375+2.25=5.625, 3.375+2.25+1.5=7.125
// so if we generate a number, 1-3.375 Foo, >3.375-5.625 Bar, >5.625-7.125 Fiz
// totalWeight = 7.125
cumulativeWeights[0] = weights[0];
double totalWeight = weights[0];
for (int i = 1; i < cumulativeWeights.Length; i++)
{
cumulativeWeights[i] = cumulativeWeights[i - 1] + weights[i];
totalWeight += weights[i];
}
var rnd = new Random();
while (true)
{
// random returns (0..1 * totalWeight - 1) + 1 = (0...6.125) + 1 = 1...7.125
double selectedWeight = (rnd.NextDouble() * (totalWeight - 1)) + 1;
int ix = Array.BinarySearch(cumulativeWeights, selectedWeight);
// If value is not found and value is less than one or more
// elements in array, a negative number which is the bitwise
// complement of the index of the first element that is
// larger than value.
if (ix < 0)
{
ix = ~ix;
}
Console.WriteLine(names[ix]);
}
的Random.NextDouble()
方法返回一個數0<=x<1
,我們必須轉換爲我們的體重。
基於這一原則,就可以建立一個使用它的List<T>
類:
public class ListWithWeight<T>
{
private readonly List<T> List = new List<T>();
private readonly List<double> CumulativeWeights = new List<double>();
private readonly Func<int, double> WeightForNthElement;
private readonly Random Rnd = new Random();
public ListWithWeight(Func<int, double> weightForNthElement)
{
WeightForNthElement = weightForNthElement;
}
public void Add(T element)
{
List.Add(element);
double weight = WeightForNthElement(List.Count);
if (CumulativeWeights.Count == 0)
{
CumulativeWeights.Add(weight);
}
else
{
CumulativeWeights.Add(CumulativeWeights[CumulativeWeights.Count - 1] + weight);
}
}
public void Insert(int index, T element)
{
List.Insert(index, element);
double weight = WeightForNthElement(List.Count);
if (CumulativeWeights.Count == 0)
{
CumulativeWeights.Add(weight);
}
else
{
CumulativeWeights.Add(CumulativeWeights[CumulativeWeights.Count - 1] + weight);
}
}
public void RemoveAt(int index)
{
List.RemoveAt(index);
CumulativeWeights.RemoveAt(List.Count);
}
public T this[int index]
{
get
{
return List[index];
}
set
{
List[index] = value;
}
}
public int Count
{
get
{
return List.Count;
}
}
public int RandomWeightedIndex()
{
if (List.Count < 2)
{
return List.Count - 1;
}
double totalWeight = CumulativeWeights[CumulativeWeights.Count - 1];
double selectedWeight = (Rnd.NextDouble() * (totalWeight - 1.0)) + 1;
int ix = CumulativeWeights.BinarySearch(selectedWeight);
// If value is not found and value is less than one or more
// elements in array, a negative number which is the bitwise
// complement of the index of the first element that is
// larger than value.
if (ix < 0)
{
ix = ~ix;
}
// We want to use "reversed" weight, where first items
// weight more:
ix = List.Count - ix - 1;
return ix;
}
}
和
var lst = new ListWithWeight<string>(x => Math.Pow(1.5, x));
lst.Add("Foo");
lst.Add("Bar");
lst.Add("Fix");
lst.RemoveAt(0);
lst.Insert(0, "Foo2");
while (true)
{
Console.WriteLine(lst[lst.RandomWeightedIndex()]);
}
感謝您的回覆。我想過要做類似的事情,但正如我所說,名單將不斷變化。這意味着我會一直更新累計權重中每個連續值的權重。此外,隨着您的實施,項目被選中的機會將取決於其重量,而不是其在列表中的位置。一個好主意,但不幸的是我不需要這裏:( – Camander
@Camander權重數組* *基於列表上的位置。如果刪除一個元素,只需要移除權重數組的最後一個元素(at這點'列表 cumulativeWeights會更好)'並更新'totalWeights' –
xanatos
這就像我會發布的解決方案,如果這還沒有在這裏,除了我會使用搜索樹。孩子的總重量,根據左半部分是大於還是小於你的隨機值,你在樹上向左或向右走,當你走的時候,你減去左邊所有東西的總重量,樹會變得更容易要編輯而不是陣列 – sh1