Skip to content

Commit

Permalink
Merge pull request #63 from SapiensAnatis/fix/pick-n
Browse files Browse the repository at this point in the history
Fix Pick() returning the same value multiple times
  • Loading branch information
ndsvw committed Mar 16, 2024
2 parents 3812e77 + f447b7a commit ac86336
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 10 deletions.
21 changes: 21 additions & 0 deletions FluentRandomPicker.Tests/PrioritizedElementsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ public void Weight3ValuesWith3DifferentWeights_WeightsMatter()

Assert.That.ProbabilitiesMatter(pickable, valueChancesPairs: valueChancesPairs);
}

[TestMethod]
public void Weight3ValuesWith3DifferentWeights_PickN_WeightsMatter()
{
var elements = new PrioritizedElement<char>[]
{
new PrioritizedElement<char> {Value = 'a', Priority = 7},
new PrioritizedElement<char> {Value = 'b', Priority = 2},
new PrioritizedElement<char> {Value = 'c', Priority = 1},
};

var pickable = Out.Of()
.PrioritizedElements(elements)
.WithValueSelector(x => x.Value)
.AndWeightSelector(x => x.Priority);

var valueChancesPairs = new[] { ('a', 0.7), ('b', 0.2), ('c', 0.1) };

Assert.That.PickNProbabilitiesMatter(pickable, valueChancesPairs: valueChancesPairs);
}


[TestMethod]
public void Percentage3ValuesWith3DifferentPercentages_PercentagesMatter()
Expand Down
23 changes: 23 additions & 0 deletions FluentRandomPicker.Tests/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,27 @@ public static void ProbabilitiesMatter<T>(this Assert assert, IPick<T> pickable,
Assert.IsTrue(occurrences[value] <= tries * chance * (1 + acceptedDeviation));
}
}

public static void PickNProbabilitiesMatter<T>(this Assert _, IPick<T> pickable, int tries = 1_000_000,
double acceptedDeviation = 0.25, params (T value, double chance)[] valueChancesPairs)
{
var occurrences = new Dictionary<T, long>(valueChancesPairs.Length);

foreach (T value in pickable.Pick(tries))
{
if (occurrences.ContainsKey(value))
occurrences[value]++;
else
occurrences.Add(value, 1);
}

foreach(var (value, chance) in valueChancesPairs)
{
if (chance == 0 && !occurrences.ContainsKey(value))
continue;

Assert.IsTrue(occurrences[value] >= tries * chance * (1 - acceptedDeviation));
Assert.IsTrue(occurrences[value] <= tries * chance * (1 + acceptedDeviation));
}
}
}
30 changes: 20 additions & 10 deletions FluentRandomPicker/Picker/DefaultPicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,33 @@ public PickResult<IEnumerable<T>> Pick()
return new PickResult<IEnumerable<T>>(Enumerable.Empty<T>());

var prioritySum = _pairs.Sum(v => (long)v.Priority);
var values = Enumerable.Repeat(PickPrioritized(prioritySum), _numberOfElementsToPick);
var values = PickPrioritized(prioritySum);
return new PickResult<IEnumerable<T>>(values);
}

private T PickPrioritized(long prioritySum)
private IEnumerable<T> PickPrioritized(long prioritySum)
{
var n = (long)(_rng.NextDouble() * prioritySum);
for (var i = 0; i < _numberOfElementsToPick; i++)
{
yield return GetValue();
}

yield break;

long localSum = 0;
foreach (var pair in _pairs)
T GetValue()
{
localSum += pair.Priority;
var n = (long)(_rng.NextDouble() * prioritySum);

if (localSum >= n + 1)
return pair.Value;
}
long localSum = 0;
foreach (var pair in _pairs)
{
localSum += pair.Priority;

if (localSum >= n + 1)
return pair.Value;
}

throw new ArgumentException("Sum of priorities was wrong", nameof(prioritySum));
throw new ArgumentException("Sum of priorities was wrong", nameof(prioritySum));
}
}
}

0 comments on commit ac86336

Please sign in to comment.