convert Dictionary<int, Enumerable> to Dictionary<int, Enumerable> inverting content - c#

for clarity lets say we have students and classes, its a many to many relationship.
I have a Dictionary where the key is the student id and the Enumerable is a collection of classes(say we just have the id ) and I want to revert this to a Dictionary of classId, students
is there a way to do this with Linq? I can think of a way to do this with loops but I m sure there is a way to do this.

var newDic = dic
.SelectMany(pair => pair.Value
.Select(val => new { Key = val, Value = pair.Key }))
.GroupBy(item => item.Key)
.ToDictionary(gr => gr.Key, gr => gr.Select(item => item.Value));

This should do:
var invertedDic = dic
.SelectMany(x => x.Value, (pair, value) => new {key = pair.Key, value = value})
.GroupBy(x => x.Value, x => x.Key, (value, key) => new {value, key})
.ToDictionary(x => x.Value, x => x.Key);

Related

Group dictionary to other dictionary via intermediate map

I have a dictionary that map between certain keys
var map = new Dictionary<string, string>(){ {"A", "One"}, {"B", "One"}, {"C", "Two"} };
I also have a dictionary containing values
var values = new Dictionary<string, double>() { {"A", 2.0}, {"B", 1.0}, {"C", 1.0} };
I want to map the values into a third dictionary so that the value of map is the key of the new dictionary. I.e. I want the new dictionary to be
var result = new Dictionary<string, double>() {{"One", 3.0}, {"Two", 1.0}};
This can of course be achieved using loops like so
var result = new Dictionary<string, double>();
foreach (var kv in values)
{
var key = map[kv.Key];
var value = kv.Value;
if (result.TryGetValue(key, out var temp))
result[key] = value + temp;
else
result.Add(key, value);
}
Or something like that...
But is it possible to do this in a one-liner using linq?
Try this,
var result = map
.Join(values, x => x.Key, y => y.Key, (x, y) => (x.Value, y.Value))
.GroupBy(x => x.Item1)
.ToDictionary(x => x.Key, y => y.Sum(s => s.Item2));
DotNet Fiddle: https://dotnetfiddle.net/tWd9j5
Looks like you're looking for Join method
var result = map.Join(values, x => x.Key, y => y.Key, (x, y) => (x.Value, y.Value)).ToDictionary(x => x.Item1, y => y.Item2);
And to handle duplication of value
var result = map
.Join(values, x => x.Key, y => y.Key, (x, y) => (x.Value, y.Value))
.GroupBy(x => x.Item1)
.ToDictionary(x => x.Key, y => y.First().Item2);
Sure you can.
var result = (from m in map
join v in values
on m.Key equals v.Key
group v.Value by m.Value into sameVal
select new { MValue = sameVal.Key, VValue = sameVal.Sum() }).ToDictionary(a => a.MValue, b => b.VValue);

How to find duplicate values in List<Dictionary<string, object>>?

I have a List of Dictionary<string, object>.
How to find duplicate values in all dictionaries by value?
You can find duplicate values with their occurrences using LINQ.
It gives you duplicate values and its occurrences (index in list and key in dictionary).
var duplicates = dicList
.SelectMany((x, i) => x.Select(p => new { Index = i, Key = p.Key, Value = p.Value }))
.GroupBy(x => x.Value)
.Where(x => x.Count() > 1)
.Select(x => new
{
Value = x.First().Value,
Occurrences = x.Select(o => new { Index = o.Index, Key = o.Key })
});
If you just want duplicate values then use simplified version
var duplicates = listOfDic
.SelectMany(x => x.Values)
.GroupBy(x => x)
.Where(x => x.Count() > 1);
Old classic loop
var uniqueValues = new HashSet<object>();
var duplicateValues = new List<object>();
foreach (var value in yourDictionaries.SelectMany(dict => dict.Values))
{
if (uniqueValues.Add(value) == false)
{
duplicateValues.Add(value);
}
}
SelectMany is a key method for getting all values of all dictionaries.
If you are fan of LINQ you can convert it to the LINQ expression for example by using Aggregate or GroupBy
Use linq for compact code:
List<Dictionary<string, object>> list = new List<Dictionary<string, object>>();
list.SelectMany(dictionary => dictionary.Values).GroupBy(d => d).Where(x => x.Count() >1);

Convert to Dictionary and Fill missing items

I have a query as follows:
IDictionary<ClassificationLevel, Int32> stats = context.Exams
.GroupBy(x => x.Classification)
.Select(x => new { Key = x.Key, Count = x.Count() })
// ...
The dictionary ClassificationLevel is has follows:
public enum ClassificationLevel { L1 = 1, L2 = 2, L3 = 3, L4 = 4 }
My problems are:
How to convert the result of the query to IDictionary
The items with Count 0 will not appear in the dictionary.
How to make sure those items appear with value 0.
UPDATED
To get the best performance I think the following should be made:
IDictionary<ClassificationLevel, Int32> stats = context.Exams
.GroupBy(x => x.Classification)
.ToDictionary(x => new { Key = x.Key, Count = x.Count() });
This would close the EF query ...
Then I would find which keys are missing, e.g. which ClassificationLevel items are missing, and add those keys with value 0.
How should I do this?
With a single linq expression.
var stats = context.Exams
.GroupBy(x => x.Classification)
.ToDictionary(x => x.Key, g => g.Count()) // execute the query
.Union(Enum.GetValues(typeof(ClassificationLevel))
.OfType<ClassificationLevel>()
.ToDictionary(x => x, x => 0)) // default empty count
.GroupBy(x => x.Key) // group both
.ToDictionary(x => x.Key, x => x.Sum(y => y.Value)); // and sum
use Enumerable.ToDictionary() and then Enum.GetValues() to fill in the missing values:
IDictionary<ClassificationLevel, Int32> dict = context.Exams
.GroupBy(x => x.Classification)
.ToDictionary(g => g.Key, g => g.Count());
foreach (ClassificationLevel level in Enum.GetValues(typeof(ClassificationLevel)))
if (!dict.ContainsKey(level))
dict[level] = 0;
Or, if Entity Framework balks at the ToDictionary(), I believe you can do the following:
IDictionary<ClassificationLevel, Int32> dict = context.Exams
.GroupBy(x => x.Classification)
.Select(x => new { Key = x.Key, Count = x.Count() })
.AsEnumerable()
.ToDictionary(g => g.Key, g => g.Count);
foreach (ClassificationLevel level in Enum.GetValues(typeof(ClassificationLevel)))
if (!dict.ContainsKey(level))
dict[level] = 0;
You could solve it like this
var enumValues = Enum.GetValues(typeof (EnumType)).Cast<EnumType>().ToArray();
Enumerable.Range((int) enumValues.Min(), (int) enumValues.Max()).ToDictionary(
x => x.Key,
x => context.Exams.Count(e => e.Classification == x)
);
You could use a "Left Outer Join" in LINQ, after that you can use GroupBy + ToDictionary:
var query = from classification in Enum.GetValues(typeof(ClassificationLevel)).Cast<ClassificationLevel>()
join exam in context.Exams on classification equals exam.Classification into gj
from subExam in gj.DefaultIfEmpty()
select new { classification, exam = subExam };
IDictionary<ClassificationLevel, Int32> stats = query
.GroupBy(x => x.classification)
.ToDictionary(g => g.Key, g => g.Count());
This code allows you to loop around your enum.
foreach (ClassificationLevel level in (ClassificationLevel[]) Enum.GetValues(typeof(ClassificationLevel)))
{
}
You could then put something like the following in the middle of the loop:
if(!stats.KeyExists(level))
{
stats.Add(level, 0);
}

creating a new dictionary out of 2 matching ones

labelMap = new Dictionary<string, int>();
branchLineMap = new Dictionary<string, int>();
if one key of the first dictionary matches another key of the other dictionary then I need to make a new dictionary with the value of branchlineMap to become the key and the value of LabelMap to become the value. How do I do this while iterating over the whole dictionary?
Using Where and ToDictionary methods, you can do it like this:
var newDictionary = labelMap
.Where(x => branchLineMap.ContainsKey(x.Key))
.ToDictionary(x => branchLineMap[x.Key], x => x.Value);
You could join the two, using LINQ.
Query syntax:
var newDict = (from b in branchLineMap
join l in labelMap on b.Key equals l.Key
select new { b = b.Value, l = l.Value })
.ToDictionary(x => x.b, x => x.l);
Same thing, using method syntax:
var newDict = branchLineMap.Join(labelMap, b => b.Key, l => l.Key,
(b, l) => new { b = b.Value, l = l.Value })
.ToDictionary(x => x.b, x => x.l);

Restructuring object

I'd like to take an object like this:
SortedList<string, SortedList<int, SortedList<DateTime, double>>> Data
and, for a given 'int' value (key of first nested sorted list), restructure it like this:
SortedList<DateTime, SortedList<string, double>>
or, better yet, this:
SortedList<DateTime, double[]>
where each 'double[]' has as many elements as there are KeyValue pairs in the SortedList.
I'm guessing Linq is the way to go, but can't figure it out. Thanks for any suggestions.
digEmAll beat me to it, but here's the second case in query comprehension syntax:
int desiredInt = //whatever...
var query = from pair in Data
from pair2 in pair.Value
where pair2.Key == desiredInt
from pair3 in pair2.Value
group pair3.Value by pair3.Key into grp
select new { grp.Key, Value = grp.ToArray() };
var result = new SortedList<DateTime, double[]>(query.ToDictionary(a => a.Key, a => a.Value));
int givenKey = ...;
var variant1 = new SortedList<DateTime, SortedList<string, double>>(
Data.Select(pair => new { str = pair.Key, dateValues = pair.Value[givenKey] })
.Where(pair => pair.dateValues != null)
.SelectMany(pair => pair.dateValues.Select(dateValue => new { pair.str, date = dateValue.Key, value = dateValue.Value }))
.GroupBy(pair => pair.date)
.ToDictionary(group => group.Key, group => new SortedList<string, double>(group.ToDictionary(triple => triple.str, triple => triple.value)))
);
var variant2 = new SortedList<DateTime, double[]>(
Data.Select(pair => new { str = pair.Key, dateValues = pair.Value[givenKey] })
.Where(pair => pair.dateValues != null)
.SelectMany(pair => pair.dateValues.Select(dateValue => new { pair.str, date = dateValue.Key, value = dateValue.Value }))
.GroupBy(pair => pair.date)
.ToDictionary(group => group.Key, group => group.Select(triple => triple.value).ToArray())
);
Your transformation is not possible if you use the full resolution of DateTime unless your system regularizes the inserted DateTime value somehow. Even very rapid inserts can occur on a different tick. If you do regularize it then you can get your values as follows:
Dictionary<DateTime, double[]> results = (from d1 in Data
from d2 in d1.Value
where d2.Key == 1
from d3 in d2.Value
group d3 by d3.Key into d3Group
select new {Key = d3Group.Key, Value = (from d4 in d3Group
select d4.Value).ToArray()
}).ToDictionary(element => element.Key, element => element.Value);
SortedList<DateTime, double[]> newSortedList = new SortedList<DateTime, double[]>(results);
The second case is pretty neat:
var dateGroups = Data.SelectMany(x => x.Value)
.SelectMany(x => x.Value)
.GroupBy(x => x.Key)
.ToSortedList(g => g.Key,
g => g.Select(x => x.Value).ToArray());
The first case instead seems wrong, I suspect it should be:
SortedList<DateTime, SortedList<string, double[]>>
If so, the code to get that is the following:
var dict =
(from x in Data
from y in x.Value
from z in y.Value
select new { StrKey = x.Key, IntKey = y.Key, DateKey = z.Key, Value = z.Value })
.GroupBy(x => x.DateKey)
.ToSortedList(g1 => g1.Key,
g1 => g1.GroupBy(x => x.StrKey)
.ToSortedList(g2 => g2.Key,
g2 => g2.Select(y => y.Value).ToArray()));
Where ToSortedList is the following extension:
public static class Exts
{
public static SortedList<TK, TV> ToSortedList<TEl, TK, TV>(
this IEnumerable<TEl> elements,
Func<TEl, TK> keySelector,
Func<TEl, TV> valueSelector)
{
if(elements == null || keySelector == null || valueSelector == null)
throw new ArgumentNullException("An argument of ToSortedList is null");
var dict = new SortedList<TK, TV>();
foreach (var el in elements)
dict.Add(keySelector(el), valueSelector(el));
return dict;
}
}
Phoog's answer is good, but maybe you should consider ILookup instead of SortedList...
ILookup<DateTime, double> result =
(
from pair1 in Data
from pair2 in pair1.Value
where pair2.Key == givenInt
from pair3 in pair2.Value
from theDouble in pair3.Value
select new {theDateTime = pair3.Key, theDouble = theDouble }
)
.ToLookup(x => x.theDateTime, x => x.theDouble);

Categories