Search code examples
c#listfor-loopvariables

Call different list with similar name in a for loop


I have a few lists that are with names: group1, group2, group3... I need a for loop (lets say for (int i=1; i<=6; i++) and right now I check for example if i is 1 then use group1, if i is 2 use group2 etc... My question is, can I call the list with the 'i' in the end? Like groupi and when i is 1, its group1, when i is 2, its group2 etc. Here is the code:

for (int i = 0; i < groupNum; i++)
{
    if (i == 0)
    {
        foreach (Player p1 in group1)
        {
            foreach (Player p2 in group1)
            {
                if (p1 != p2)
                {
                    if (group1.IndexOf(p1) > group1.IndexOf(p2))
                    {
                        Game game = new Game(tournamentID, p1.id, p2.id);
                        game.status = "Pending";
                        gm.CreateGame(game);
                    }
                }
            }
        }
    }

    else if (i == 1)
    {
        foreach (Player p1 in group2)
        {
            foreach (Player p2 in group2)
            {
                if (p1 != p2)
                {
                    if (group2.IndexOf(p1) > group2.IndexOf(p2))
                    {
                        Game game = new Game(tournamentID, p1.id, p2.id);
                        game.status = "Pending";
                        gm.CreateGame(game);
                    }
                }
            }
        }
    }

    else if (i == 2)
    {
        foreach (Player p1 in group3)
        {
            foreach (Player p2 in group3)
            {
                if (p1 != p2)
                {
                    if (group3.IndexOf(p1) > group3.IndexOf(p2))
                    {
                        Game game = new Game(tournamentID, p1.id, p2.id);
                        game.status = "Pending";
                        gm.CreateGame(game);
                    }
                }
            }
        }
    }

    else if (i == 3)
    {
        foreach (Player p1 in group4)
        {
            foreach (Player p2 in group4)
            {
                if (p1 != p2)
                {
                    if (group4.IndexOf(p1) > group4.IndexOf(p2))
                    {
                        Game game = new Game(tournamentID, p1.id, p2.id);
                        game.status = "Pending";
                        gm.CreateGame(game);
                    }
                }
            }
        }
    }

    else if (i == 4)
    {
        foreach (Player p1 in group5)
        {
            foreach (Player p2 in group5)
            {
                if (p1 != p2)
                {
                    if (group5.IndexOf(p1) > group5.IndexOf(p2))
                    {
                        Game game = new Game(tournamentID, p1.id, p2.id);
                        game.status = "Pending";
                        gm.CreateGame(game);
                    }
                }
            }
        }
    }

    else if (i == 5)
    {
        foreach (Player p1 in group6)
        {
            foreach (Player p2 in group6)
            {
                if (p1 != p2)
                {
                    if (group6.IndexOf(p1) > group6.IndexOf(p2))
                    {
                        Game game = new Game(tournamentID, p1.id, p2.id);
                        game.status = "Pending";
                        gm.CreateGame(game);
                    }
                }
            }
        }
    }

As you can see, the code for different if statements is literally the same but the only difference is in using different lists with similar name (group1, group2..).


Solution

  • In General

    This comes up a lot. No; you cannot programmatically build a variable name. By the time the code is compiled the variable names are gone anyway

    If you ever have variables with names like

    something1
    something2
    something3
    

    etc, then this is a candidate for using an array but remember arrays start from 0

    var something = new Thing[3];
    

    (In your case the Thing is e.g. a Player)

    Now you can have a fixed part and a varying part in your name:

    Before you had    Now you have
    --------------    ------------
    something1        something[0]
    something2        something[1]
    something3        something[2]
    

    Arrays can be looped over with a for or foreach, and they can be accessed at random with hard coded indexes like above. Just like your code only having 3 variables named somethingX your array also has a fixed number of 3 values. Arrays don't grow

    If you need something that works like an array but does grow, use a List:

    var something = new List<Thing>();
    

    This is a list of Thing, just like before you had an array of Thing.


    Also, don't forget about dictionaries, which are like lists, in that they grow, but they can be indexed by anything, so you can programmatically build an index, and unlike arrays/lists which are indexed by an incrementing integer, a dictionary can skip out some indexes:

    var dict = new Dictionary<int, Thing>();
    

    This means "a dictionary indexed by an int and holding a Thing"

    Now, this is possible:

    dict[1] = new Thing();
    dict[2] = new Thing();
    dict[4] = new Thing(); //we skipped
    

    The key can be anything, for example a string:

    var dict = new Dictionary<string, Thing>();
    dict["something1"] = new Thing();
    dict["something2"] = new Thing();
    dict["sometheng4"] = new Thing(); //watch out for typos!
    

    You can programmatically build the key:

    int x = 1;
    dict["something"+x].SomePropertyOfThing = "Hello";
    

    The only thing to note is that dictionary doesn't necessarily store it's contents in any kind of order. If you foreach it you might get the entries out in something2, sometheng4, something1 order. If you need order, you either have to sort the dict.Keys and then use it to access the dictionary, or you use a SortedDictionary or OrderedDictionary depending what you want


    In summary, those are the main kinds of collections we use in C#; they have different strengths and weaknesses and choosing which to use in a situation is often an important engineering decision

    Final point of note; strive to use plural names when working with collections, as it makes the code easier to reason about

    Specifically in this case

    In your particular case you're actually looking for an array of arrays, and a bit of LINQ might make your code easier to deal with:

    var groups = new []{ group1, group2, group3 };
    
    foreach(var g in groups)
      foreach(var p1 in g)
        foreach(var p2 in g.Where(p => p.id > p1.id)) 
          gm.CreateGame(new Game(tournamentID, p1.id, p2.id){status = "Pending"});
    

    If you want to make just one tournament for a particular group N, replace the foreach(var g in groups) with var g = groups[N]


    This logic you have of "for each player 1, for each player 2, if they aren't the same player, find the index of the player 1, find the index of the player 2, if the one index is less than the two index" contains redundant logic, and it wastes resources finding things it has already found

    Saying "for every player1 in the group, for every player2 whose id is greater than player1" cuts all that out. Player 2 must be a different player to Player 1 because their id is greater. There is no need to look up the index; you could shortcut your code to comparing the IDs rather than doing a notequals check then finding indexes then comparing

    If your ID values aren't intrinsically comparable in a greater than sense for some reason (I assumed they would be ints) you can use a bit more linq to assign an index i to each player p in a group:

    var groups = new []{
      group1.Select((p,i) => (p,i)),
      group2.Select((p,i) => (p,i)),
      group3.Select((p,i) => (p,i))
    };
    

    This make a tuple of the player, and the index they're at, so a similar logic as before can work:

    foreach(var g in groups)
      foreach(var p1t in g)
        foreach(var p2t in g.Where(pt => pt.i > p1t.i)) 
          gm.CreateGame(new Game(tournamentID, p1t.p.id, p2t.p.id){status = "Pending"});
    

    This time the Where demands the index i be greater for player 2

    You should name your properties using PascalCase, not camelCase