-1

I'm trying and failing, actually, to get missing items from two lists, each list with his one type.

So, let's say I have two lists List<Game> and List<ValidGame> which contains all items that I should have in my app.

LE: Where properties Id and Name are the same on both lists.

public class Game {
   public Guid Id {get; set;}
   public string Name {get; set;}
   public List<Platform> Platforms {get; set;}
}

public class Platform {
   public Type PlatformType {get; set;}
   public string Version {get; set;}
}

public enum Type
{
     Unknown,
     iOS,
     Android
}

and ValidGame looks like this:

 public class ValidGame {
   public Guid Id {get; set;}
   public string Name {get; set;}
 }

How can I get the ValidGames that are not on the List<Game> but for each platform? Short story, I'm trying to find for each platform what game I'm missing.

Thanks a lot in advance!!

Any suggestions would be appreciated.

2 Answers2

0

Assuming you have the following lists:

List<ValidGame> validGames;
List<Game> games;

I would suggest something along the lines of:

  1. For each Game object in games, creating a collection of 'platform and game ID' objects
    (resulting in one created object per Platform object in the Game's Platforms)
  2. Flattening the generated collections into one big collection
  3. Grouping the 'platform and game ID' object collection by platform type and version
  4. Creating a dictionary where the Platform object is the key, and a collection of the game IDs of the games that are already on that platform is the value
  5. For each entry in the dictionary, evaluate which game IDs are missing

For a simple example (with simplified GUID values), let's say games contains two objects:

{
    Id: ABC
    Name: "GameA"
    Platforms:
    [
        { PlatformType: Android, Version: "V1" },
        { PlatformType: Android, Version: "V2" },
    ]
},
{
    Id: DEF
    Name: "GameB"
    Platforms:
    [
        { PlatformType: Android, Version: "V1" },
        { PlatformType: iOS, Version: "V1" },
    ]
},

The results of step 1--4 would look roughly like the following:

Result of step 1 + 2: Collection
--------------------------------
{ ( Platform: { PlatformType: Android, Version: "V1" }, GameId: ABC ) },
{ ( Platform: { PlatformType: Android, Version: "V2" }, GameId: ABC ) },
{ ( Platform: { PlatformType: Android, Version: "V1" }, GameId: DEF ) },
{ ( Platform: { PlatformType: iOS, Version: "V1" }, GameId: DEF ) },
Result of step 3: Grouping
--------------------------
{ Key: "Android_V1":
    { ( Platform: { PlatformType: Android, Version: "V1" }, GameId: ABC ) },
    { ( Platform: { PlatformType: Android, Version: "V1" }, GameId: DEF ) },
},
{ Key: "Android_V2":
    { ( Platform: { PlatformType: Android, Version: "V2" }, GameId: ABC ) },
},
{ Key: "iOS_V1":
    { ( Platform: { PlatformType: iOS, Version: "V1" }, GameId: DEF ) },
}
Result of step 4: Dictionary of game IDs already present on each platform
-------------------------------------------------------------------------
{
    Key: { PlatformType: Android, Version: "V1" },
    Value: { ABC, DEF }
},
{
    Key: { PlatformType: Android, Version: "V2" },
    Value: { ABC }
},
{
    Key: { PlatformType: iOS, Version: "V1" },
    Value: { DEF }
},

Without having verified it in an IDE, the implementation of step 1--4 could be roughly as follows:

Dictionary<Platform, IEnumerable<Guid>> gameIdsPerPlatform = games
    // Step 1--2:
    .SelectMany(game => game.Platforms
        .Select(platform => (Platform: platform, GameId: game.Id)))
    // Step 3:
    .GroupBy(platformAndGameId =>
        $"{platformAndGameId.Platform.PlatformType}_{platformAndGameId.Platform.Version}")
    // Step 4:
    .ToDictionary(
        gr => gr.First().Platform,
        gr => gr.Select(platformAndGameId => platformAndGameId.GameId));

Here, several methods from the System.Linq namespace are used:


In step 5, .ExceptBy() (also from the System.Linq namespace) may be used to find which games are missing from each platform:

Dictionary<Platform, List<ValidGame>> missingGamesPerPlatform = gameIdsPerPlatform
    .ToDictionary(
        kvp => kvp.Key,
        kvp => validGames
            .ExceptBy(kvp.Value.Select(game => game.Id), validGame => validGame.Id)
            .ToList()
    );

Note:
This suggested approach assumes that the uniqueness of a game (whether it's an existing game or a valid game) is defined by the Game's/ValidGame's Id value (i.e. if two Game/ValidGame objects have identical Name values, they will also have identical Id values). If that is a wrong assumption, changes need to be made for this approach to work.

Astrid E.
  • 2,280
  • 2
  • 6
  • 17
  • Thanks for your answer! I used some parts of you solution. So, on the List I added also the PlatformType foreach item of this list. Then, flattening the List and mapping it to another list of type List. Then I did an Except on those lists and voila :). It worked like a charm. Thank you again! – Zenitha Mealthow Mar 21 '23 at 07:15
  • Great to hear that you found a solution that works for you! Glad to be able to help :) – Astrid E. Mar 21 '23 at 09:03
0

The structure of your code makes this very difficult, and this would be much easier if each platform had a list of Games, instead of each Game having a list of platforms. However, with the way your code is set up, I'll show you how.

First, in platform, let's override the .Equals() method that all objects inherit, as well as .GetHashCode()

public class Platform 
{
    public Type PlatformType { get; set; }
    public string Version { get; set; }
    
    public override bool Equals(Object obj)
    {
        if ((obj == null) || ! this.GetType().Equals(obj.GetType()))
        {
            return false;
        }
        else
        {
            return (this.PlatformType == obj.PlatformType)
                && (this.Version == obj.Version)
        }
    }

    public override int GetHashCode()
    {
        return (int)PlatformType;
    }
}

This makes comparing two platforms much simpler.

Also make an override of Equals for Game, where the name and the id are the same, and GetHashCode where it returns the game ID.

Now, we are going to create a List<Platform> so we can get the desired result

List<Game> games; // You already have this, I'm just giving it a name.
List<ValidGame> validGames; // You already have this, I'm just giving it a name.

if (!games.Any())
{
    // This is just an example. Add whatever handling you would like here
    throw new Exception("The list of games is null");
}

List<Platform> platforms = new()
{
    games.First()
};

for (int i = 0; i < games.Count; i++)
{
    int platformIndex = -1;

    foreach (Platform platform in platforms)
    {
        for (int j = 0; j < games[i].Platforms.Count; j++)
        {
            if (platform.Equals(games[i].Platforms[j]))
                platformIndex = j;
        }
    }

    if (platformIndex > -1)
    {
        platforms.Add(games[i].Platforms[platformIndex]);
    }
}

The above code creates a non duplicating list of all of the platforms.

With that information, let's create a new List<List<Game>>, the nested list is so we can keep every platform's games separate. The following code continues the above code.

List<List<Game>> gamesInPlatforms = new();

// This will create a list of lists, where the size is based
// on the amount of unique platforms there are.
for (int i = 0; i < platforms.Count; i++)
{
    gamesByPlatforms.Add(new List<Game>());
}

Console.WriteLine("The following valid games are found in no platforms: ");

foreach (ValidGame validGame in validGames)
{
    bool isValidGameFound = false;

    foreach (Game game in games)
    {
        if (game.Equals(ToGame(validGame)))
            isValidGameFound = true;
    }

    if (!isValidGameFound)
        Console.WriteLine($"Name: {validGame.Name}, ID: {validGame.Id}");
}

// This will sort the games into their respective lists
for (int i = 0; i < platforms.Count; i++)
{
    foreach (Game game in games)
    {
        foreach (Platform gamePlatform in game.Platforms)
        {
            if (platforms[i].Equals(gamePlatform))
                gamesByPlatforms[i].Add(game);
        }
    }
}

//This final bit will output the games not found based on platform type
for (int i = 0; i < platforms.Count; i++)
{
    bool isFirstGame = true;
    foreach (ValidGame validGame in validGames)
    {
        if (!gamesByPlatforms[i].Contains(ToGame(validGame)))
        {
            if (isFirstGame)
            {
                isFirstGame = false;
                Console.WriteLine($"Platform, Platform Type: {platforms[i].PlatformType.ToString()} Platform Version: {platforms[i].Version} does not contain the following valid games:");
            }
            Console.WriteLine($"Name: {validGame.Name}, ID: {validGame.Id}");
        }
    }
}

Game ToGame(ValidGame validGame)
{
    Game game = new();
    game.Id = validGame.Id;
    game.Name = validGame.Name;
}

That's it. Now you can see in the console which valid games aren't found in each platform. Hope this helps!

Josh Heaps
  • 296
  • 2
  • 13