Skip to content

Commit

Permalink
Added alias method
Browse files Browse the repository at this point in the history
# Added
- Added alias method and tests.
  • Loading branch information
mackysoft committed Jul 30, 2021
1 parent f6e6f9c commit 3158913
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Runtime.CompilerServices;
using MackySoft.Choice.Internal;

namespace MackySoft.Choice {

/// <summary>
/// <para> The fastest algorithm. </para>
/// <para> It takes O(n) run time to set up, but the selection is performed in O(1) run time, </para>
/// <para> where n is number of weights. </para>
/// <para> Therefore, this is a very effective algorithm for selecting multiple items. </para>
/// </summary>
public class AliasWeightedSelectMethod : IWeightedSelectMethod, IDisposable {

struct Alias {

public int index;
public float probability;

public Alias (int index,float probability) {
this.index = index;
this.probability = probability;
}

public override string ToString () {
return $"{{ [{index}] : {probability} }}";
}

}

TemporaryArray<Alias> m_Aliases;

~AliasWeightedSelectMethod () {
Dispose();
}

public int SelectIndex (TemporaryArray<float> weights,float value) {
if (value == 1f) {
return weights.Length - 1;
}
float r = value * weights.Length;
int i = (int)Math.Floor(r);
Alias alias = m_Aliases[i];
return ((r - i) > alias.probability) ? alias.index : i;
}

public void Calculate (TemporaryArray<float> weights) {
int size = weights.Length;

m_Aliases = TemporaryArray<Alias>.Create(size);
for (int i = 0;i < size;i++) {
m_Aliases[i] = new Alias(-1,1f);
}

using (var smalls = TemporaryArray<Alias>.CreateAsList(size))
using (var bigs = TemporaryArray<Alias>.CreateAsList(size)) {
float average = Sum(weights) / size;
for (int i = 0;i < size;i++) {
float weight = weights[i];
Alias alias = new Alias(i,weight / average);
if (weight < average) {
smalls.Add(alias);
} else {
bigs.Add(alias);
}
}

int si = 0, bi = 0;
Alias? small = smalls[0];
Alias? big = bigs[0];
while (small.HasValue && big.HasValue) {
m_Aliases[small.Value.index] = new Alias(big.Value.index,small.Value.probability);
big = new Alias(big.Value.index,big.Value.probability - (1f - small.Value.probability));

if (big.Value.probability < 1f) {
small = big;
bi++;
big = (bi < bigs.Length) ? (Alias?)bigs[bi] : null;
} else {
si++;
small = (si < smalls.Length) ? (Alias?)smalls[si] : null;
}
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static float Sum (TemporaryArray<float> weights) {
float result = 0f;
for (int i = 0;i < weights.Length;i++) {
result += weights[i];
}
return result;
}

#region IDisposable Support

bool m_IsDisposed;

public void Dispose () {
if (!m_IsDisposed) {
m_Aliases.Dispose();

m_IsDisposed = true;
}
}

#endregion

}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public static class WeightedSelectMethod {
/// </summary>
public static IWeightedSelectMethod Binary => new BinaryWeightedSelectMethod();

/// <summary>
/// <para> The fastest algorithm. </para>
/// <para> It takes O(n) run time to set up, but the selection is performed in O(1) run time, </para>
/// <para> where n is number of weights. </para>
/// <para> Therefore, this is a very effective algorithm for selecting multiple items. </para>
/// </summary>
public static IWeightedSelectMethod Alias => new AliasWeightedSelectMethod();

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@ public void Binary_ReturnValidValue_1 () {
Assert.AreSame(source.LastOrDefault(x => x.weight > 0f).item,weightedSelector.SelectItem(1f));
}

[Test]
public void Alias_ReturnValidValue ([Random(0f,1f,10)] float value) {
var source = ItemEnumerableGenerator.GenerateEnumerable(100).ToArray();
var weightedSelector = source.ToWeightedSelector(x => x.item,x => x.weight,WeightedSelectMethod.Alias);
Assert.IsNotNull(weightedSelector.SelectItem(value));
}

[Test, Repeat(100)]
public void Alias_ReturnValidValue_0 () {
var source = ItemEnumerableGenerator.GenerateEnumerable(100).ToArray();
var weightedSelector = source.ToWeightedSelector(x => x.item,x => x.weight,WeightedSelectMethod.Alias);
Assert.AreSame(weightedSelector.FirstOrDefault(p => p.Value > 0f).Key,weightedSelector.SelectItem(0f));
}

[Test, Repeat(100)]
public void Alias_ReturnValidValue_1 () {
var source = ItemEnumerableGenerator.GenerateEnumerable(100).ToArray();
var weightedSelector = source.ToWeightedSelector(x => x.item,x => x.weight,WeightedSelectMethod.Alias);
Assert.AreSame(source.LastOrDefault(x => x.weight > 0f).item,weightedSelector.SelectItem(1f));
}

[Test]
[Description(
"Compare the results of all algorithms and find the algorithm that returned unexpected value.\n" +
Expand Down
1 change: 1 addition & 0 deletions MackySoft.Choice.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Assets\MackySoft\MackySoft.Choice\Runtime\IWeightedSelector.cs" />
<Compile Include="Assets\MackySoft\MackySoft.Choice\Runtime\WeightedSelectMethod\AliasWeightedSelectMethod.cs" />
<Compile Include="Assets\MackySoft\MackySoft.Choice\Runtime\EnumerableConversion.cs" />
<Compile Include="Assets\MackySoft\MackySoft.Choice\Runtime\WeightedSelector\EmptyWeightedSelector.cs" />
<Compile Include="Assets\MackySoft\MackySoft.Choice\Runtime\Internal\RuntimeHelpers.cs" />
Expand Down

0 comments on commit 3158913

Please sign in to comment.