One method is to create a copy of the array, think of it as if you have put each of the names onto a piece of paper and put them into a hat, we'll call this the pool
. Now as you randomly select a team out of the pool
remove that team from the pool
before you select again:
Random rand = new Random();
string[] teams = { "Team 1", "Team 2", "Team 3", "Team 4", "Team 5", "Team 6", "Team 7", "Team 8" };
Console.WriteLine("The available teams are: ");
for (int i = 0; i < teams.Length; i++)
{
Console.WriteLine(teams[i]);
}
List<int> pool = new List<int>(Enumerable.Range(0, teams.Length));
int round = 0;
while(pool.Any())
{
round++;
int index = rand.Next(0, pool.Count);
int r1 = pool[index];
pool.RemoveAt(index);
index = rand.Next(0, pool.Count);
int r2 = pool[index];
pool.RemoveAt(index);
Console.Write($"Round {round}: {teams[r1]} vs {teams[r2]}");
int winner = rand.Next(0, 2) == 1 ? r1 : r2;
Console.WriteLine($" - Winner: {teams[winner]}");
}
As mentioned in the comments, there is no need to create a new instance of Random
, just reuse the existing one.
I'm not a huge fan of this syntax, logic like this is often easier to understand when object references are using instead of keeping an array of indexes into another array, however this closly approximates OPs issue.
One execution of this has the following output:
The available teams are:
Team 1
Team 2
Team 3
Team 4
Team 5
Team 6
Team 7
Team 8
Round 1: Team 5 vs Team 4 - Winner: Team 4
Round 2: Team 6 vs Team 1 - Winner: Team 6
Round 3: Team 3 vs Team 2 - Winner: Team 3
Round 4: Team 8 vs Team 7 - Winner: Team 8
In the scenario of pulling names out of a hat often what we do is put the names in the hat, shuffle them around, and then we draw them out sequentially. So we can actually simplify the logic somewhat by sorting the pool
rather than removing the items at a random index, once the collection is sorted once, the outcome from there is already random enough.
List<int> pool = new List<int>(Enumerable.Range(0, teams.Length).OrderBy(x => rand.Next()));
int round = 0;
while (pool.Any())
{
round++;
int r1 = pool[0];
int r2 = pool[1];
// remove the entries before the next round
pool.RemoveAt(0);
pool.RemoveAt(0); // previous remove has shifted the array
Console.Write($"Round {round}: {teams[r1]} vs {teams[r2]}");
// still randomly select a winner from the two selected
int winner = rand.Next(0, 2) == 1 ? r1 : r2;
Console.WriteLine($" - Winner: {teams[winner]}");
}
It's a subtle difference, but randomly sorting the array allows you to write more deterministic code, and code that is more predicable is a lot easier to debug, the result is still random, in fact now that we don't have to remove items from the pool, we can simply enumerate the pool array:
int [] pool = Enumerable.Range(0, teams.Length).OrderBy(x => rand.Next()).ToArray();
for (int index = 0, round = 1; index < pool.Length; index += 2, round ++)
{
int r1 = pool[index];
int r2 = pool[index + 1];
Console.Write($"Round {round}: {teams[r1]} vs {teams[r2]}");
// still randomly select a winner from the two selected
int winner = rand.Next(0, 2) == 1 ? r1 : r2;
Console.WriteLine($" - Winner: {teams[winner]}");
}
If the input teams might be an odd number, which can happen, just put a check in there and give the last team a bye.
var t = teams.ToList();
t.Add("Team 9");
teams = t.ToArray();
// now that there is an odd number of teams...
int[] p = Enumerable.Range(0, teams.Length).OrderBy(x => rand.Next()).ToArray();
for (int index = 0, round = 1; index < p.Length; index += 2, round ++)
{
if (p.Length > index + 1)
{
int r1 = p[index];
int r2 = p[index + 1];
Console.Write($"Round {round}: {teams[r1]} vs {teams[r2]}");
// still randomly select a winner from the two selected
int winner = rand.Next(0, 2) == 1 ? r1 : r2;
Console.WriteLine($" - Winner: {teams[winner]}");
}
else
{
int rb = p[index];
Console.Write($"Round {round}: {teams[rb]} *** bye ***");
}
}
An output for the above is:
Round 1: Team 4 vs Team 2 - Winner: Team 4
Round 2: Team 5 vs Team 3 - Winner: Team 3
Round 3: Team 8 vs Team 1 - Winner: Team 1
Round 4: team 9 vs Team 6 - Winner: team 9
Round 5: Team 7 *** bye ***
For the previous execution the pool
has the following content:
[4,2,5,3,8,1,9,6,7]
At no point did we need to manipulate the original collection of teams
, other than to add the 9th one ;)
So by simply randomising the list of addresses of the original references we can achieve randomised output using deterministic logic.