0

I'm trying to produce a list of string Key combinations when each Key shares a value, below is an extract from my code

var keyCombos = new List<List<string>>();
var dict = new Dictionary<string, List<int>>();
dict.Add("A", new List<int>() { 1, 2, 3 });
dict.Add("B", new List<int>() { 1, 2, 3, 4 });
dict.Add("C", new List<int>() { 1, 4, 5 });

Above is a Dictionary which contains a string Key and an integer list as its value, the keyCombos list will hold all Key combinations for shared integer list values, my expected output for keyCombos would be below

["A","B","C"] //1 is common
["A","B"] //1,2,3 are common
["B","C"] //1,4 are common
["C"] //5

The order of the combinations is not important, so far with my code using various foreach loops I can only combinations of 2 Keys, not 1 or 3.

foreach(var k1 in dict.Keys)
            {
                List<int> a1 = dict[k1];
                foreach (var k2 in dict.Keys)
                {
                    if (k1 != k2)
                    {
                        List<int> a2 = dict[k2];

                        if(a1.Intersect(a2).Count()>0)
                        {
                            var matches = false;
                            foreach(var combo in keyCombos)
                            {
                                if ((combo.Contains(k1)) && (combo.Contains(k2)))
                                {
                                    matches = true;
                                }
                            }
                            if (!matches)
                            {
                                keyCombos.Add(new List<string>() { k1, k2 });
                            }
                        }
                        else
                        {
                            keyCombos.Add(new List<string>() { k1 });
                        }
                    }                    
                }
            }
chillydk147
  • 121
  • 1
  • 1
  • 8

4 Answers4

2

Using linq you can do like this

var enumarable = dict.SelectMany(g => g.Value.Select(k => new { Key = g.Key, Value = k}
));
var result = enumarable.GroupBy(g => g.Value, (key, group) => group.Select(g => g.Key).ToList());
keyCombos = result.Distinct(AnonymousComparer.Create((List<string> mc) => string.Join(";", mc))).ToList();

AnonymousComparer is a class for creating the IEqualityComparer. It is from AnonymousComparer nuget package.

Or just so

keyCombos = dict.SelectMany(g => g.Value.Select(k => new { Key = g.Key, Value = k })).GroupBy(g => g.Value, (key, group) => group.Select(g => g.Key).ToList()).Distinct(AnonymousComparer.Create((List<string> mc) => string.Join(";", mc))).ToList();

First, using SelectMany we exclude the list from value in the dictionary and get all key-value pairs with duplicate keys .
After that we just group this new collection by the value and select key from group. And finally we select unique groups by values.

Alexcei Shmakov
  • 1,801
  • 2
  • 16
  • 27
2

I just wanted to clarify, you want to return all elements that are “common/intersection” between the passed “keys” right?

In case of [“A”, “B”], shouldn’t 1,2,3 be common instead of 2,3 only. If so, please update in the question statement.

And in case of [“C”] there shouldn’t be a common since there’s only one key. Correct?

If yes, then you should try something like the following:

    public List<int> FindIntersection(string[] keys, Dictionary<string, List<int>> dict)
    {
        if(keys.Length <= 1)
        {
            return null;
        }

        HashSet<int> commonElements = new HashSet<int>();
        dict[keys[0]].ForEach(x => commonElements.Add(x));

        for(int i = 1; i < keys.Length; i++)
        {
            commonElements.IntersectWith(dict[keys[i]]);
        }

        return commonElements.ToList();
    }
Mikaal Anwar
  • 1,630
  • 5
  • 16
1

I believe the easiest way is to get the distinct integers from the dictionary first, then loop through them and use linq to get the keys where that integer is in the list. Like:

var distinctKeyCombos = dict.SelectMany(x => x.Value).Distinct();

 foreach(var i in distinctKeyCombos)
 {
     var keys = dict.Where(p => p.Value.Contains(i)).Select(p => p.Key);
 }
Kevin
  • 2,238
  • 1
  • 9
  • 12
0

You can do it in LINQ using SelectMany and GroupBy:

    var pivot = dict.SelectMany(kv => kv.Value.Select(v => new {v, kv.Key}))
        .GroupBy(x => x.v);

The group's key will be the int value and its contents will be an anonymous type containing the value and the letter.

It works by first pairing each letter with the int value in a pair (you could also use System.ValueTuple here instead of the anonymous type). It then flattens that list of lists into a single list and finally groups them by the int value.

Instead of storing your values in a Dictionary in the first place it might be easier to store them as simple pairs ("A", 1), ... and then you can easily retrieve them in either way: grouped by letter or grouped by int.

Ian Mercer
  • 35,804
  • 6
  • 87
  • 121