0

Brief description: Randomly select two items from an array and then remove them from the array, and then randomly select one item from two already randomly selected items.

I know this has probably been asked before, But all the answers I could find I didn't know how to apply to my code.

Here is my code:

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]);
}

Random rnd1 = new Random();
int r1 = rnd1.Next(teams.Length);
Random rnd2 = new Random();
int r2 = rnd2.Next(teams.Length);
Console.WriteLine("Round 1: " + teams[r1] + " vs " + teams[r2]);

How can I make it so that the two teams will always be different, and so that I can do a Round 2 which will be another two different teams?

Another thing that I need to do is to make it so that when the two teams are picked for a round (let's say it's Team 3 and Team 7) to randomly select out of those two teams so I can decide a winner. I've tried a bunch of things and none of it worked, I mostly need help with the first problem but if anyone can help with the second one as well that'd be appreciated.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Mars
  • 19
  • 3
  • 1
    As a side note, there should be no need for two `Random` instances. One `Random` should be enough for all the random numbers that you want to generate. Regarding the core issue, could you include in the question an example of a desirable output of the program? – Theodor Zoulias May 31 '21 at 18:03
  • A simple way out is just to compare the result to previous results, if it is too close a match to a previous result then just try again, this is a classic _do{ get next result}while(result != previous);_ Otherwise, you could exclude one or both teams from the array before randomizing, this can be achieved with simple linq queries. – Chris Schaller Jun 01 '21 at 05:45
  • 1
    If you randomly select two items from the same array (assuming uniform distribution), there's no need to then randomise the order of those two items. You are as likely to have selected `b` then `a` as you are to have selected `a` then `b`. – Damien_The_Unbeliever Jun 01 '21 at 07:57

2 Answers2

0

In order to implement it we really need to keep track of the round a team has advanced to.

With 8 teams the max number of rounds that can be played are 3.

Before doing anything we can shuffle our team list and then couple them in order to play their game. The winning team will advance to the next round.

The first thing is to define our class Team:

public class Team
{
    public string Name { get; set; }
    public int Round { get; set; }
}

Then what we have to do is create two nested loops.

The outer loop simulates the round being played - while the inner loop simulates the current game between two team dictating the winner.

Putting it all together we have:

public class Team
{
    public string Name { get; set; }
    public int Round { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        List<Team> teams = new List<Team>
        {
            new Team
            {
                Name = "Team 1",
                Round = 0
            },
            new Team
            {
                Name = "Team 2",
                Round = 0
            },
            new Team
            {
                Name = "Team 3",
                Round = 0
            },
            new Team
            {
                Name = "Team 4",
                Round = 0
            },
            new Team
            {
                Name = "Team 5",
                Round = 0
            },
            new Team
            {
                Name = "Team 6",
                Round = 0
            },
            new Team
            {
                Name = "Team 7",
                Round = 0
            },
            new Team
            {
                Name = "Team 8",
                Round = 0
            }
        };

        for (int j = 0; j < teams.Count / (teams.Count / 4); j++)
        {
            //select current round and shuffles the teams
            Random rng = new Random();
            var currentPlayingTeams = teams.Where(x => x.Round == j).Select(c => c).ToList().OrderBy(x => rng.Next()).ToList();

            if (currentPlayingTeams.Count > 1)
            {
                Console.WriteLine($"---------------- Round {j + 1}----------------------");

                for (int i = 0; i < currentPlayingTeams.Count; i += 2)
                {
                    var currentMatch = currentPlayingTeams.GetRange(i, 2);

                    Console.WriteLine($"{currentMatch[0].Name} VS {currentMatch[1].Name}");
                    System.Threading.Thread.Sleep(1000);

                    Random ran = new Random();
                    var matchWinner = ran.Next(0, 2);

                    Console.WriteLine($"{currentMatch[matchWinner].Name} Won");
                    System.Threading.Thread.Sleep(1000);

                    teams.FirstOrDefault(x => x.Name == currentMatch[matchWinner].Name).Round++;
                }
            }
        }

        var winner = teams.OrderByDescending(v => v.Round).First();
        Console.WriteLine("---------------- Result----------------------");
        Console.WriteLine($"The winning team is {winner.Name}");
        Console.ReadLine();
    }
}

Since we have shuffled - we can then pick the first 2 and make them play.

The implementation requires the number of teams to be a power of 2n (4, 8,16,32 etc).

Alex Leo
  • 2,781
  • 2
  • 13
  • 29
0

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.

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81