Generate a random number x : x [1,100].
- If x [ 1,30] - caught a blue ball
- if x [31.40] - caught a red ball
- if x [41.45] - caught a golden ball
- if x [46.75] - caught a black ball
- if x [76,100] - caught a gray ball
Example of implementation in C#.
The class Ballsbag simulates the drawing of colored balls from a bag.
Simulates two behaviors due to whether or not the ball is removed back in the bag.
For each set of balls inserted in the bag(InsertBallsByColor()
) a BallSet
which keeps the colour and an interval(intervalLeft, intervalRight
).
This interval is calculated according to the number of balls already placed and the number of balls in this set.
private int IntervalLeft() => _ballsInBag + 1;
private int IntervalRight(int count) => _ballsInBag + count;
It is used to determine whether, according to the random value generated, a ball of this color has been taken.
public bool WasPicked(int pick)
{
return pick >= _intervalLeft && pick <= _intervalRight;
}
The probability of a ball coming out of a given color is the relationship between the number of balls of that color in the bag and the total amount of balls in the bag.
public class BallsBag
{
private static readonly int NUM_MAX_TIRAGENS = 5000;
private readonly bool _extractedBallReturnsToBag;
private int _ballsIn;
private readonly IEnumerator<int>_randomGenerator ;
private readonly HashSet<BallSet> _ballSetsInBag;
private int _ballsExtracted;
public BallsBag(int totalBalls, bool extractedBallReturnsToBag)
{
_extractedBallReturnsToBag = extractedBallReturnsToBag;
TotalBalls = totalBalls;
_ballSetsInBag = new HashSet<BallSet>();
_randomGenerator = getRandomGenerator(TotalBalls, extractedBallReturnsToBag);
}
public int TotalBalls { get; }
public int BallsInBag => _ballsIn - _ballsExtracted;
public void InsertBallsByColor(string color, int count)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException(nameof(count), $"{count} não é uma quantidade válida de bolas");
}
if (BallsExceedsMaximum(count))
{
throw new ArgumentOutOfRangeException(nameof(count), "Nº total de bolas excedido");
}
if(BagHaveThisBallColor(color))
{
throw new ArgumentOutOfRangeException(nameof(color), "O saco já tem bolas dessa cor");
}
_ballSetsInBag.Add(new BallSet(color, IntervalLeft, IntervalRight(count)));
_ballsIn = _ballsIn + count;
}
public string ExtractBall()
{
if (AllBallsNotInserted)
{
throw new InvalidOperationException("Ainda não colocou todas a bolas no saco");
}
if (_randomGenerator.MoveNext())
{
_ballsExtracted = _ballsExtracted + (_extractedBallReturnsToBag ? 0 : 1);
var pickedBall = _randomGenerator.Current;
return _ballSetsInBag.First(ball => ball.WasPicked(pickedBall)).Color;
}
throw new InvalidOperationException("O saco está vazio");
}
private bool BagHaveThisBallColor(string color) => _ballSetsInBag.Any(ball => ball.Color.Equals(color));
private bool BallsExceedsMaximum(int count) => _ballsIn + count > TotalBalls;
private int IntervalLeft => _ballsIn + 1;
private int IntervalRight(int count) => _ballsIn + count;
private bool AllBallsNotInserted => _ballsIn < TotalBalls;
private IEnumerator<int> getRandomGenerator(int totalBalls, bool extractedBallReturnsToBag)
{
return extractedBallReturnsToBag ? RandomGenerator(1, totalBalls).GetEnumerator()
: RandomGenerator(1, totalBalls).Distinct()
.Take(totalBalls)
.GetEnumerator();
}
private static IEnumerable<int> RandomGenerator(int minInclued, int maxInclued)
{
var rand = new Random();
var i = 1;
while (i <= NUM_MAX_TIRAGENS)
{
yield return rand.Next(minInclued, maxInclued + 1);
i++;
}
}
private class BallSet
{
public string Color { get; }
private readonly int _intervalLeft;
private readonly int _intervalRight;
public BallSet(string color, int intervalLeft, int intervalRight)
{
Color = color;
_intervalLeft = intervalLeft;
_intervalRight = intervalRight;
}
public bool WasPicked(int pick)
{
return pick >= _intervalLeft && pick <= _intervalRight;
}
public override bool Equals(object obj)
{
var ballSetObj = obj as BallSet;
return ballSetObj != null && Color.Equals(ballSetObj.Color);
}
public override int GetHashCode()
{
return Color.GetHashCode();
}
}
}
Example of use:
private static void Main(string[] args)
{
var ballsBag = new BallsBag(20, extractedBallReturnsToBag: false);
ballsBag.InsertBallsByColor("Azul",6);
ballsBag.InsertBallsByColor("Vermelha", 2);
ballsBag.InsertBallsByColor("Dourada", 1);
ballsBag.InsertBallsByColor("Preta",6);
ballsBag.InsertBallsByColor("Cinza", 5);
Console.WriteLine($"Balls in bag {ballsBag.BallsInBag}");
for (var i = 0; i < ballsBag.TotalBalls; i++)
{
Console.WriteLine($"Tiragem {i+1} - {ballsBag.ExtractBall()} - Balls in bag {ballsBag.BallsInBag}");
}
Console.ReadKey();
}
The weight array looks like one of the best ideas so far, as the code gets cleaner and the probabilities of each item are clearly exposed. Can be modified in the future if necessary, making it possible to change the probabilities and add new items faster.
– DanOver
Maybe you can even dry it up depending on the language. But don’t just stick to that solution, there are different ways to do it, which is best only depends on the specific case. Even, if you want to leave really portable, you need to add the keys instead of using the number 100 literally. So you could simply have a 3 => a 1 => b if it was a "three to one" ratio. It doesn’t even depend on a percentage anymore. It works on any scale.
– Bacco