Group dictionary to other dictionary via intermediate map - c#

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);

Related

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);

zipping/merging two sorted lists

i have two sorted dictionaries both with the type signature
i.e.
SortedDictionary<decimal, long> A
SortedDictionary<decimal, long> B
I want to merge the two lists where the key is the same, thus creating a new list like
SortedDictionary<decimal, KeyValuePair<long,long>>
or
SortedDictionary<decimal, List<long>>
This may not be the best way of approacing the situation but could someone give me a heads up on how to do this or a better way to approach it.
This is what I've got:
SortedDictionary<decimal, List<long>> merged = new SortedDictionary<decimal, List<long>>
(
A.Union(B)
.ToLookup(x => x.Key, x => x.Value)
.ToDictionary(x => x.Key, x => new List<long>(x))
);
EDIT: Above solution selects keys not included in both collections. This should select where keys are same:
SortedDictionary<decimal, List<long>> merged = new SortedDictionary<decimal, List<long>>
(
A.Where(x=>B.ContainsKey(x.Key))
.ToDictionary(x => x.Key, x => new List<long>(){x.Value, B[x.Key]})
);
You can do this simply using LINQ:
var query = from a in A
join b in B
on a.Key equals b.Key
select new {
Key = a.Key,
Value = Tuple.Create(a.Value, b.Value)
};
var merged = new SortedDictionary<decimal, Tuple<long, long>>(
query.ToDictionary(x => x.Key, x => x.Value)
);
I think you should use Tuple<long, long> as your TValue in the merged dictionary.
Another LINQ way of doing this that I think captures the intent better in terms of set operations:
SortedDictionary<decimal, long> a = new SortedDictionary<decimal, long>();
SortedDictionary<decimal, long> b = new SortedDictionary<decimal, long>();
a.Add(0, 10);
a.Add(1, 10);
a.Add(2, 100);
a.Add(100, 1);
b.Add(0, 4);
b.Add(4, 4);
b.Add(2, 10);
var result = a.Union(b)
.GroupBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.Select(y => (long)y.Value).ToList());
Try something like this, it not easy:
Dictionary<decimal, long> dic1 = new Dictionary<decimal, long>{ {3,23}, {2,3}, {5,4}, {6,8}};
Dictionary<decimal, long> dic2 = new Dictionary<decimal, long>{ {3,2}, {2,5}, {5,14}, {12,2}};
//recover shared keys (the keys that are present in both dictionaries)
var sharedKeys = dic1.Select(dic => dic.Key).Intersect(dic2.Select(d2=>d2.Key));
sharedKeys.Dump();
//add to the fìnal dictionary
var final = new Dictionary<decimal, List<long>>();
foreach(var shk in sharedKeys) {
if(!final.ContainsKey(shk))
final[shk] = new List<long>();
final[shk].Add(dic1[shk]);
final[shk].Add(dic2[shk]);
}
**EDIT**
//Skip below part if you need only keys present on both dictionaries.
///-----------------------------------------------------------------
//get unique keys present in Dic1 and add
var nonsharedkeys1 = dic1.Select(d=>d.Key).Where(k=>!sharedKeys.Contains(k));
foreach(var nshk in nonsharedkeys1) {
final[nshk] = new List<long>();
final[nshk].Add(dic1[nshk]);
}
//get unique keys present in Dic2 and add
var nonsharedkeys2 = dic2.Select(d=>d.Key).Where(k=>!sharedKeys.Contains(k));
foreach(var nshk in nonsharedkeys2) {
final[nshk] = new List<long>();
final[nshk].Add(dic2[nshk]);
}
Should work for you.
You could "abuse" Concat and Aggregate like this:
var A = new SortedDictionary<decimal,long>();
var B = new SortedDictionary<decimal,long>();
A.Add(1, 11);
A.Add(2, 22);
A.Add(3, 33);
B.Add(2, 222);
B.Add(3, 333);
B.Add(4, 444);
var C = A.Concat(B).Aggregate(
new SortedDictionary<decimal, List<long>>(),
(result, pair) => {
List<long> val;
if (result.TryGetValue(pair.Key, out val))
val.Add(pair.Value);
else
result.Add(pair.Key, new[] { pair.Value }.ToList());
return result;
}
);
foreach (var x in C)
Console.WriteLine(
string.Format(
"{0}:\t{1}",
x.Key,
string.Join(", ", x.Value)
)
);
The resulting output:
1: 11
2: 22, 222
3: 33, 333
4: 444
This is pretty much the same as if you wrote a "normal" foreach and would in fact work on any IEnumerable<KeyValuePair<decimal, long>> (not just SortedDictionary<decimal, long>) and is easy to extend to more than two input collections if needed.
Unfortunately, it also completely disregards the fact that the input SortedDictionary is, well, sorted, so performance is not optimal. For optimal performance you'd have to fiddle with linearly advancing separate IEnumerator for each of the input sorted dictionaries, while constantly comparing the underlying elements - you could completely avoid TryGetValue that way...

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);

convert Dictionary<int, Enumerable> to Dictionary<int, Enumerable> inverting content

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);

Categories