Combining information from multiple lists

Viewed 66

I have multiple string[] that are defined like this:

var allEntities = new[] { "a", "b", "c", "d", "e", "f", "g", "h", "i" }
var entitiesWithPriority = new[] { "c", "d", "g" }
var entitiesWithIssue = new [] { "d", "h", "i" }
var entitiesWith... = new [] { ... }

Now I'd like to compile a single list from those arrays by using the following class structure:

class Entity {
    public string Name { get; set; }
    public bool HasPriority { get; set; }
    public bool HasIssue { get; set; }
}

The result of the operation should therefore be something like this:

[
    { "name": "a", "hasPriority": false, "hasIssue": false }
    { "name": "b", "hasPriority": false, "hasIssue": false }
    { "name": "c", "hasPriority": true, "hasIssue": false }
    { "name": "d", "hasPriority": true, "hasIssue": true }
    { "name": "e", "hasPriority": false, "hasIssue": false }
    { "name": "f", "hasPriority": false, "hasIssue": false }
    { "name": "g", "hasPriority": true, "hasIssue": false }
    { "name": "h", "hasPriority": false, "hasIssue": true }
    { "name": "i", "hasPriority": false, "hasIssue": true }
]

I could do something in the lines of the following code, but I think this solution is not perfect at all:

var result = allEntities.Select(entityName => new Entity()
{
    Name = entityName,
    HasPriority = entitiesWithPriority.Contains(entityName),
    HasIssue = entitiesWithIssue.Contains(entityName)
});

How can I compile such a list while being performance efficient.

4 Answers

You can use a Dictionary or a Hashset to reduce Any and Contains complexity from O(n) to O(1):

The idea above can still be used for large lists to replace list/array/IEnumarable<> Contains which is O(n) with Hashset's Contains which is O(1).

var entitiesWithPriorityMap = entitiesWithPriority.ToDictionary(e => e.entityName, e => true);  
var entitiesWithIssueMap = entitiesWithIssue.ToDictionary(e => e.entityName, e => true);

var result = allEntities.Select(entityName => new Entity()
{
    Name = entityName,
    HasPriority = entitiesWithPriorityMap.ContainsKey(entityName),
    HasIssue = entitiesWithIssueMap.ContainsKey(entityName)
});

Note: This works when dealing with Linq2Object nor Linq2Sql (cannot generate SQL out of Contains in this scenario).

Hashset version

var entitiesWithPriorityMap = new HashSet<Entity>(entitiesWithPriority);
var entitiesWithIssueMap = new HashSet<Entity>(entitiesWithIssue);

// replace ContainsKey with Contains

You could join them together:

var entities = 
     from name in allEntities
     join pr in entitiesWithPriority on name equals pr into np
     from priority in np.DefaultIfEmpty()
     join iss in entitiesWithIssue on name equals iss into npi
     from issue in npi.DefaultIfEmpty()
     select new Entity 
     { 
         Name = name, 
         HasPriority = !string.IsNullOrEmpty(priority), 
         HasIssue = !string.IsNullOrEmpty(issue)
     };

First create a dictionary <string>,<entity> and populate it from all entities with all hasXXX defaults to false

After that, lookup is O(1)

foreach (var key in EntitiesWithPriority)
{
   dict[key].hasPriority = true
}

and so on

If the construction can be List<> instead from an array, we can create a List<> with the following generic type: List<Dictionary<string, dynamic>> To achieve such construction you need to create following method:

public List<List<Dictionary<string, dynamic>>> MethodName()
    {
        var list = new List<List<Dictionary<string, dynamic>>>();

        HasPriority = false;
        HasIssue = false;

        foreach (var item in allEntities)
        {
            if (entitiesWithPriority.Contains(item.ToString()))
                HasPriority = true;
            if (entitiesWithIssue.Contains(item.ToString()))
                HasIssue = true;
            list.Add(new List<Dictionary<string, dynamic>>()
            {
                (new Dictionary<string, dynamic>() { { "name", item } }),
                (new Dictionary<string, dynamic>() { { "hasPriority", HasPriority } }),
                (new Dictionary<string, dynamic>() { { "hasIssue", HasIssue } })
            });
        }

        return list;
    }

I'm not sure if all properties in your class are necessary. In case you'd want to add more arrays with conditions you should modify foreach loop.

Related