tmaurst
tmaurst

Reputation: 691

.Net LINQ - Filter a dictionary using another dictionary

I have two dictionaries of the same type, A and B.

Dictionary<string, IEnumerable<object>>

I'm using object to represent a complex type having a property 'Id'.

I'm looking for all items in A having objects that exist in B (using Id), but under a different key. It's basically to tell if an object has moved keys. A is the new dictionary and B is the old.

Is there a reasonable way to accomplish this using LINQ? I would like the result to be a dictionary of all key-value pairs in A meeting the criteria. Thanks in advance.

Upvotes: 4

Views: 1381

Answers (4)

Marcelo Estriga
Marcelo Estriga

Reputation: 79

Use the Join operator (see join clause (C# Reference)):

var dictionary = (        
        from a in (from entry in A from Value in entry.Value select new { entry.Key, Value }) 
        join b in (from entry in B from Value in entry.Value select new { entry.Key, Value }) 
        on ((dynamic)a.Value).Id equals ((dynamic)b.Value).Id
        where a.Key != b.Key 
        select a
    ).ToDictionary(a => a.Key, a => a.Value);

Upvotes: 0

John Wu
John Wu

Reputation: 52210

In terms of searchability, your dictionary has it backwards; it is efficient for looking up an object given a string, but you need to be able to look up the strings for a given object. An efficient data structure for this purpose would be a Lookup<object,string>.

First, use ToLookup() to create a lookup table where the key is the object and the value is the list of keys in both list A and B. Use Union (instead of Concat) to eliminate duplicates.

var lookup = listA
    .Union( listB )
    .ToLookup( pair => pair.Value, pair => pair.Key );

Once you have the lookup, the problem is trivial.

var results = lookup.Where( x => x.Count() > 1);

See this DotNetFiddle for a working example with sample data.

Upvotes: 3

s-s
s-s

Reputation: 380

I use Interface IHasId for use Id propert:

public interface IHasId
{
    int Id { get; }
}

And class AAA that inherited the interface:

public class AAA: IHasId
{
    public int Id { get; set; }
}

Here the linq you look for:

Dictionary<string, IEnumerable<IHasId>> A = new Dictionary<string, IEnumerable<IHasId>>();
A.Add("111", new List<IHasId> { new AAA { Id = 1 }, new AAA { Id = 2 } });
A.Add("333", new List<IHasId> { new AAA { Id = 3 } });
Dictionary<string, IEnumerable<IHasId>> B = new Dictionary<string, IEnumerable<IHasId>>();
B.Add("111", new List<IHasId> { new AAA { Id = 1 }});
B.Add("222", new List<IHasId> { new AAA { Id = 2 }});
B.Add("333", new List<IHasId> { new AAA { Id = 3 } });

var res = A.Where(a => a.Value.Any(c => B.Any(v => v.Value
           .Select(x => x.Id).Contains(c.Id) && a.Key != v.Key))).ToList();

In this example it return key 111 that has the object with Id = 2 that moved from key 222 to key 111

If you want the result as dictionary you can change the ToList with ToDictionary:

var res = A.Where(a => a.Value.Any(c => B.Any(v => v.Value
           .Select(x => x.Id).Contains(c.Id) && a.Key != v.Key)))
           .ToDictionary(a=>a.Key, a=>a.Value);

If you want in the new dictionary only the values that has change, like in the example key 111 and value with only the object with Id = 2, you can do it like this:

var res = A.Select(a => new KeyValuePair<string, IEnumerable<IHasId>>(a.Key, 
           a.Value.Where(c => B.Any(v => v.Value.Select(x => x.Id).Contains(c.Id) && a.Key != v.Key))))
           .Where(a=>a.Value.Count() > 0)
           .ToDictionary(a => a.Key, a => a.Value);

Upvotes: 2

Alex Skalozub
Alex Skalozub

Reputation: 2576

If you need A entries with original objects, it could be:

var result = A.Where(a => B.Any(b => b.Key != a.Key && b.Value.Intersect(a.Value).Any()));

If you need A entries with only matching objects from B, it could be:

var result = A.Select(a => new KeyValuePair<string, IEnumerable<object>>(a.Key, B.Where(b => b.Key != a.Key).SelectMany(b => b.Value.Intersect(a.Value)))).Where(x => x.Value.Any());

You can provide a custom equality comparer for Intersect to match items by Id or whatever.

Use new Dictionary<string, IEnumerable<object>>(result) if you need it as a dictionary.

Upvotes: 1

Related Questions