3

I have two dictionaries like Dic1<string,string> and Dic2<string,string> I want to generate a new list of the values for the keys that exist in both Dic1 and Dic2 So for example if

Dic1: <12,"hi">, <14,"bye">
Dic2: <12,"hello">, <18,"bye">

Then List should be: "hi" , "hello"

I tried to work with Dic1.Keys.Intersect but couldn't quite figure it out yet.

What I tried: Dic1.Keys.Intersect(Dic2.Keys).ToList(t => t.Values);
Bohn
  • 26,091
  • 61
  • 167
  • 254
  • You could create an intersecting `Dictionary` first then go through all the keys in that dictionary to get a list. See this post http://stackoverflow.com/questions/10685142/c-sharp-dictionaries-intersect – locoboy Dec 30 '14 at 20:32

4 Answers4

6

Here you are:

var dic1 = new Dictionary<int, string> { { 12, "hi" }, { 14, "bye" } };
var dic2 = new Dictionary<int, string> { { 12, "hello" }, { 18, "bye" } };
HashSet<int> commonKeys = new HashSet<int>(dic1.Keys);
commonKeys.IntersectWith(dic2.Keys);
var result = 
    dic1
    .Where(x => commonKeys.Contains(x.Key))
    .Concat(dic2.Where(x => commonKeys.Contains(x.Key)))
    // .Select(x => x.Value) // With this additional select you'll get only the values.
    .ToList();

The result list contains { 12, "hi" } and { 12, "hello" }

The HashSet is very usefull for intersections.


Just out of curiostiy I compared all six solutions (hopefully didn't missed any) and the times are as following:

@EZI        Intersect2   GroupBy         ~149ms
@Selman22   Intersect3   Keys.Intersect   ~41ms
@dbc        Intersect4   Where1           ~22ms
@dbc        Intersect5   Where2           ~18ms
@dbc        Intersect5   Classic          ~11ms
@t3chb0t    Intersect1   HashSet          ~66ms

class Program
{
    static void Main(string[] args)
    {
        var dic1 = new Dictionary<int, string>();
        var dic2 = new Dictionary<int, string>();

        Random rnd = new Random(DateTime.Now.Millisecond);
        for (int i = 0; i < 100000; i++)
        {
            int id = 0;

            do { id = rnd.Next(0, 1000000); } while (dic1.ContainsKey(id));
            dic1.Add(id, "hi");

            do { id = rnd.Next(0, 1000000); } while (dic2.ContainsKey(id));
            dic2.Add(id, "hello");
        }

        List<List<string>> results = new List<List<string>>();

        using (new AutoStopwatch(true)) { results.Add(Intersect1(dic1, dic2)); }
        Console.WriteLine("Intersect1 elapsed in {0}ms (HashSet)", AutoStopwatch.Stopwatch.ElapsedMilliseconds);

        using (new AutoStopwatch(true)) { results.Add(Intersect2(dic1, dic2)); }
        Console.WriteLine("Intersect2 elapsed in {0}ms (GroupBy)", AutoStopwatch.Stopwatch.ElapsedMilliseconds);

        using (new AutoStopwatch(true)) { results.Add(Intersect3(dic1, dic2)); }
        Console.WriteLine("Intersect3 elapsed in {0}ms (Keys.Intersect)", AutoStopwatch.Stopwatch.ElapsedMilliseconds);

        using (new AutoStopwatch(true)) { results.Add(Intersect4(dic1, dic2)); }
        Console.WriteLine("Intersect4 elapsed in {0}ms (Where1)", AutoStopwatch.Stopwatch.ElapsedMilliseconds);

        using (new AutoStopwatch(true)) { results.Add(Intersect5(dic1, dic2)); }
        Console.WriteLine("Intersect5 elapsed in {0}ms (Where2)", AutoStopwatch.Stopwatch.ElapsedMilliseconds);

        using (new AutoStopwatch(true)) { results.Add(Intersect7(dic1, dic2)); }
        Console.WriteLine("Intersect7 elapsed in {0}ms (Old style :-)", AutoStopwatch.Stopwatch.ElapsedMilliseconds);

        Console.ReadKey();
    }

    static List<string> Intersect1(Dictionary<int, string> dic1, Dictionary<int, string> dic2)
    {
        HashSet<int> commonKeys = new HashSet<int>(dic1.Keys);
        commonKeys.IntersectWith(dic2.Keys);
        var result =
            dic1
            .Where(x => commonKeys.Contains(x.Key))
            .Concat(dic2.Where(x => commonKeys.Contains(x.Key)))
            .Select(x => x.Value)
            .ToList();
        return result;
    }

    static List<string> Intersect2(Dictionary<int, string> dic1, Dictionary<int, string> dic2)
    {
        var result = dic1.Concat(dic2)
                    .GroupBy(x => x.Key)
                    .Where(g => g.Count() > 1)
                    .SelectMany(g => g.Select(x => x.Value))
                    .ToList();
        return result;
    }

    static List<string> Intersect3(Dictionary<int, string> dic1, Dictionary<int, string> dic2)
    {
        var result =
            dic1
            .Keys
            .Intersect(dic2.Keys)
            .SelectMany(key => new[] { dic1[key], dic2[key] })
            .ToList();
        return result;
    }

    static List<string> Intersect4(Dictionary<int, string> dic1, Dictionary<int, string> dic2)
    {
        var result =
            dic1.
            Where(pair => dic2.ContainsKey(pair.Key))
            .SelectMany(pair => new[] { dic2[pair.Key], pair.Value }).ToList();
        return result;
    }

    static List<string> Intersect5(Dictionary<int, string> dic1, Dictionary<int, string> dic2)
    {
        var result =
            dic1
            .Keys
            .Where(dic2.ContainsKey).SelectMany(k => new[] { dic1[k], dic2[k] })
            .ToList();
        return result;
    }

    static List<string> Intersect7(Dictionary<int, string> dic1, Dictionary<int, string> dic2)
    {
        var list = new List<string>();
        foreach (var key in dic1.Keys)
        {
            if (dic2.ContainsKey(key))
            {
                list.Add(dic1[key]);
                list.Add(dic2[key]);
            }
        }
        return list;
    }
}

class AutoStopwatch : IDisposable
{
    public static readonly Stopwatch Stopwatch = new Stopwatch();

    public AutoStopwatch(bool start)
    {
        Stopwatch.Reset();
        if (start) Stopwatch.Start();
    }
    public void Dispose()
    {
        Stopwatch.Stop();
    }
}
t3chb0t
  • 16,340
  • 13
  • 78
  • 118
  • 1
    _Then List should be: "hi" , "hello"_ and it is. Picking the strings out is not a problem and you even have more information for later use. I've added a `Select` :-) – t3chb0t Dec 30 '14 at 20:41
  • 3
    "With this additional select you'll get only the values." The values is all they were asking for lol.. That's why i brought it up. – austin wernli Dec 30 '14 at 20:45
  • I guess I just missed it because I read about the dictionary intersection and wrote the code without thinking mutch about the result. All I knew was that the keys should be the common ones :-] – t3chb0t Dec 30 '14 at 20:47
  • @austinwernli but mine is faster :-) – t3chb0t Dec 30 '14 at 21:02
  • it's not that because LINQ is slower, but the code in that answer has so many redundancy. Try comparing this with the code in my answer. I would like to see the results. and just FYI, Intersect is already using a mechanism similar to HashSet to get common items in two sequence. – Selman Genç Dec 30 '14 at 21:03
  • @Selman22 the code in your answer is ever 4 times slower. The slowest one so far ;-) – t3chb0t Dec 30 '14 at 21:06
  • @Selman22 I made a mistake. Your code is even two times faster! Sorry! I took the wrong Intersect function. No need to reset timers. I use three of them. – t3chb0t Dec 30 '14 at 21:09
  • 1
    @Blake yeah! and now you even know which one is the fastest one :-] – t3chb0t Dec 30 '14 at 21:13
  • yeah I really need fast and low memory consumption for this project – Bohn Dec 30 '14 at 21:21
  • 2
    I just added the two versions from [my answer below](http://stackoverflow.com/questions/27712031/finding-the-intersection-of-two-dictionaries/27712126#27712126) into the test harness above. On my computer, I'm getting the best performance with `dic1.Where(pair => dic2.ContainsKey(pair.Key)).SelectMany(pair => new[] { pair.Value, dic2[pair.Key] }).ToList();` – dbc Dec 30 '14 at 21:53
  • 1
    @dbc indeed... and yet it looks so _complicated_ :-) - sorry for missing your code - it wasn't on purpose I've somehow overseen it. – t3chb0t Dec 30 '14 at 22:02
  • Actually, a correction: now the 1st, simpler version is running a bit faster. Not sure why; maybe I had them confused?. Also, in the test harness, which ever one is run first seems to be at a disadvantage. – dbc Dec 30 '14 at 22:07
  • 1
    I've updated the code and it now includes all five solutions. – t3chb0t Dec 30 '14 at 22:13
  • 1
    Thank you so much for all the research :) – Bohn Dec 30 '14 at 22:45
5
var d1 = new Dictionary<int, string>() { { 12, "hi" }, { 14, "bye" } };
var d2 = new Dictionary<int, string>() { { 12, "hello" }, { 18, "bye" } };

var res = d1.Concat(d2)
            .GroupBy(x => x.Key)
            .Where(g => g.Count() > 1)
            .SelectMany(g => g.Select(x=>x.Value))
            .ToList();
EZI
  • 15,209
  • 2
  • 27
  • 33
  • Can you please explain what is g.Count() > 1 for? Thanks. – Bohn Dec 30 '14 at 20:48
  • 1
    @Blake it means it is common for two dicts. if it is 1, than it means it is only in one dict. – EZI Dec 30 '14 at 20:49
  • there is no need for neither Concat nor GroupBy. The Intersect in the question will work just fine. this is just overhead. – Selman Genç Dec 30 '14 at 20:57
  • @Selman22 `The Intersect in the question will work just fine`, but you would loose the *values* , All you have would be `12` :) – EZI Dec 30 '14 at 21:18
  • I mean it will work to get Intersection of Keys (which is most of your code is trying to do up to SelectMany) . You can get the values using indexer like in my answer. – Selman Genç Dec 30 '14 at 21:20
  • @Selman22 I read it, it is just another way of doing it. But I wouldn't use it for a large dictionary to access with indexer. Any way, what is the point of your comment? – EZI Dec 30 '14 at 21:23
  • Point of my comment was: _It's unnecessary to use 4 methods to do same job while you can do it with one method and much more efficiently_ – Selman Genç Dec 30 '14 at 21:25
  • @Selman22 Of course you can post some links to back it up, since I don't think the same. – EZI Dec 30 '14 at 21:27
  • see the tests in the accepted answer. it proves my point. I'm not gonna keep trying to convince you since I don't care whether you believe it or not. – Selman Genç Dec 30 '14 at 21:28
  • @Selman22 `I'm not gonna keep trying to convince you since I don't care whether you believe it or not` Do you think I do care your comments and thoughts ? You started this silly conversation. If you didn't like it, downvote it. BTW: I think you already did it. – EZI Dec 30 '14 at 21:29
  • No, I stated something that is correct and you kept denying it. I have right to comment on wherever I want. You should learn to accept that sometimes you can be wrong and the upvote count doesn't _always_ determine whether your answer is best and efficient. – Selman Genç Dec 30 '14 at 21:40
  • @Selman22, No for what? You didn't downvote? do it to prove it. Or for what? BTW: upvotes really don't show anything, when seeing your reps. – EZI Dec 30 '14 at 21:57
  • No for _You started this silly conversation_, this was about informing people about the code in the answer. the only silliness here is your childishness. grow up – Selman Genç Dec 30 '14 at 22:01
  • It becomes personally, would you mind if I downvote you as a response? – EZI Dec 30 '14 at 22:05
2

You can filter the keys in Dic1 using Where, then convert them to values like so:

var values = Dic1.Keys.Where(Dic2.ContainsKey).SelectMany(k => new[] { Dic1[k], Dic2[k] })
    .ToList();

This should be as efficient as the lookup operations on Dic1 and Dic2, which is typically log(n) or better, and does not require building any temporary hash sets or lookup tables.

Here's a version that avoids one of the dictionary lookups at the cost of being a bit less pretty:

var values = Dic1.Where(pair => Dic2.ContainsKey(pair.Key)).SelectMany(pair => new[] { pair.Value, Dic2[pair.Key] })
    .ToList();

Update

My time tests (using t3chb0t's handy test harness) show the first version actually runs faster. It's simpler, so of the two, prefer that. But the fastest I have found so far doesn't use Linq at all, at 7 ms for 1000000 intersections vs 13 for the Linq version:

    static List<string> Intersect7(Dictionary<int, string> dic1, Dictionary<int, string> dic2)
    {
        var list = new List<string>();
        foreach (var key in dic1.Keys)
        {
            if (dic2.ContainsKey(key))
            {
                list.Add(dic1[key]);
                list.Add(dic2[key]);
            }
        }
        return list;
    }

It's in an old style though so you probably don't want this.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

You just need to get the values using indexer:

 var values = Dic1.Keys.Intersect(Dic2.Keys)
             .SelectMany(key => new[] { Dic1[key], Dic2[key] })
             .ToList();
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • 1
    @downvoters (except EZI), if there is something wrong with this answer let me know, or if you are just being childish let me know that either.don't just downvote and hide yourself, let's see your reasons if there is any. – Selman Genç Dec 30 '14 at 22:36