Converting a LINQ query into a Dictionary<string, string[]> - c#

I've got a query that returns something of the following format:
{ "tesla", "model s" }
{ "tesla", "roadster" }
{ "honda", "civic" }
{ "honda", "accord" }
and I'd like to convert that to a dictionary of <string, string[]> like so:
{ "tesla" : ["model s", "roadster"], "honda" : ["civic", "accord"] }
I've tried with this:
var result = query.Select(q => new { q.Manufacturer, q.Car}).Distinct().ToDictionary(q => q.Manufacturer.ToString(), q => q.Car.ToArray());
but so far I am not having any luck. I think what this is doing is actually trying to add individual items like "tesla" : ["model s"] and "tesla" : ["roadster"] and that's why it's failing ... any easy way to accomplish what I am trying to do in LINQ?

You would need to group each item by the key first, then construct the dictionary:
result = query.Select(q => new { q.Manufacturer, q.Car}).Distinct()
.GroupBy(q => q.Manufacturer)
.ToDictionary(g => g.Key,
g => g.Select(q => q.Car).ToArray());
Of course, an ILookup<string, string> much easier:
result = query.Select(q => new { q.Manufacturer, q.Car }).Distinct()
.ToLookup(q => q.Manufacturer, q => q.Car);

You're looking for ToLookup if you would like the results to be grouped into a dictionary-like object:
var result = query.Select(q => new { q.Manufacturer, q.Car})
.Distinct()
.ToLookup(q => q.Manufacturer.ToString(), q => q.Car);
Otherwise you will have to group the results first:
var result = query.Select(q => new { q.Manufacturer, q.Car })
.Distinct()
.GroupBy(q => q.Manufacturer)
.ToDictionary(gg => gg.Key,
gg => gg.Select(q => q.Car).ToArray());

What you want is GroupBy(), followed by ToDictionary().
Example:
var result = query.GroupBy(q => q.Manufacturer).ToDictionary(q => q.Key, q => q.Value.ToArray());
What GroupBy() does is group all the elements that have the same matching key selector. So when you tell it to GroupBy(q => q.Manufacturer), all the elements that have the same Manufacturer will be grouped together as IEnumerable<T>.

Use ToLookup:
var table = pairs.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
foreach(var i in table["tesla"])
Console.WriteLine(i);

Related

Linq - Get a list and sort it by a list of string values

I have a list of guids as string:
This is how i retrive my list of string guids:
List<string> p0 = ctx.PrProjectRating.Select(k => k).GroupBy(g => new { g.PrPIdG }, (key, group) => new { sumR = group.Sum(k => k.PrValue), pidG = key.PrPIdG }).Select(t => t.pidG).ToList();
Now i have another list that contains a field called pidG but this list needs to be ordered by the list of guid strings above.
How do i achiveve this.
i tried:
List<PProject> p = p.OrderBy(c => p0.Contains(c.PIdG)).ToList();
but still the list is not ordered by the string guids in the first list "p0"
You have to do join here
List<string> p0 = ctx.PrProjectRating
.Select(k => k)
.GroupBy(g => new { g.PrPIdG }, (key, group) =>
new { sumR = group.Sum(k => k.PrValue), pidG = key.PrPIdG })
.Select(t => t.pidG).ToList();
var result = p0.Join(p, x => x, c => c.PIdG, (x, c) => c)
.ToList()

Converting Tuple<List<Guid>, string> to Dictionary<Guid, List<string>>

I am trying to converting a Tuple<List<Guid>, string> to Dictionary<Guid, List<string>>. This is what I have so far:
var listOfTuples = GetListOfTuples(); // returns type List<Tuple<List<Guid>, string>>
var transformedDictionary = new Dictionary<Guid, List<string>>();
foreach (var listOfTuple in listOfTuples)
{
foreach (var key in listOfTuple.Item1)
{
if (!transformedDictionary.ContainsKey(key))
transformedDictionary[key] = new List<string> { listOfTuple.Item2 };
else transformedDictionary[key].Add(listOfTuple.Item2);
}
}
Is there a better way of doing this, perhaps using LINQ; SelectMany, Grouping, or toDictionary?
Update: I have tried this, but clearly not working:
listOfTuples.ToList()
.SelectMany(x => x.Item1,(y, z) => new { key = y.Item2, value = z })
.GroupBy(p => p.key)
.ToDictionary(x => x.Key, x => x.Select(m => m.key));
You are close. The problem is with selecting the right key and value
var result = listOfTuples.SelectMany(t => t.Item1.Select(g => (g, str: t.Item2)))
.GroupBy(item => item.g, item => item.str)
.ToDictionary(g => g.Key, g => g.ToList());
The mistake is here (y, z) => new { key = y.Item2, value = z } - you want the key to be the Guid and therefore instead of it being Item2 it should be z which is the Guid. So you can go with the way I wrote it or just
(y, z) => new { key = z, value = y.Item2 }
Also the .ToList() at the beginning is not needed. You say that listOfTuples already returns a list

Merge multiple dictionaries and aggregate values where required

I have three Dictionaries created by calling ToDictionary on a GroupBy projection in LINQ.
var dictionaryOne = _repositoryOne.GetData()
.GroupBy(d => new { d.Property1, d.Property2, d.LocalCcyId})
.ToDictionary(d =>
new
{
d.Key.Property1,
d.Key.Property2,
d.Key.LocalCcyId
},
v => v.Sum(l => ConvertToUsd(effectiveDate, l.LocalCcyId, l.Amount)));
var dictionaryTwo = _repositoryTwo.GetData()
.GroupBy(d => new { d.Property1, d.Property2, d.LocalCcyId})
.ToDictionary(d =>
new
{
d.Key.Property1,
d.Key.Property2,
d.Key.LocalCcyId
},
v => v.Sum(l => ConvertToUsd(effectiveDate, l.LocalCcyId, l.Balance)));
var dictionaryThree = _repositoryThree.GetData()
.GroupBy(d => new { d.Property1, d.Property2, d.LocalCcyId})
.ToDictionary(d =>
new
{
d.Key.Property1,
d.Key.Property2,
d.Key.LocalCcyId
},
v => v.Sum(l => ConvertToUsd(effectiveDate, l.LocalCcyId, l.Total)));
I want to merge these into a dictionary and
i) Sum up the values which are in USD &
ii) Drop the grouping by LocalCcyId column from the Key
The will be instances to the same key occurring in each of the three dictionaries and I need to aggregate the Sums for all such cases. How do I achieve this in LINQ?
Seems to me that this is all you need:
var finalDictionary =
dictionaryOne
.Concat(dictionaryTwo)
.Concat(dictionaryThree)
.GroupBy(x => new { x.Key.Property1, x.Key.Property2 }, x => x.Value)
.ToDictionary(x => new { x.Key.Property1, x.Key.Property2 }, x => x.Sum());
Or, using LINQ syntax (as much as possible) this:
var finalDictionary =
(
from x in dictionaryOne.Concat(dictionaryTwo).Concat(dictionaryThree)
group x.Value by new { x.Key.Property1, x.Key.Property2 }
)
.ToDictionary(x => new { x.Key.Property1, x.Key.Property2 }, x => x.Sum());
Assuming you are querying a remote datasource, running queries twice over the data or convering to USD twice doesn't seem more efficient then taking the dictionaries and combining them, so that's what I did.
First you need to convert each Dictionary to a new anonymous object having the data you need, then group by the properties summing the values:
var allDictionary = dictionaryOne.Select(kv => new { kv.Key.Property1, kv.Key.Property2, kv.Value })
.Concat(dictionaryTwo.Select(kv => new { kv.Key.Property1, kv.Key.Property2, kv.Value }))
.Concat(dictionaryThree.Select(kv => new { kv.Key.Property1, kv.Key.Property2, kv.Value }))
.GroupBy(k2v => new { k2v.Property1, k2v.Property2 })
.ToDictionary(k2vg => new { k2vg.Key.Property1, k2vg.Key.Property2 }, k2vg => k2vg.Sum(k2v => k2v.Value));

How to use GroupBy with ICollections?

I use GroupBy to grouping objects like:
var categories=
tovRDb.MyObjects.AsEnumerable().Where(t => myHashSet.Contains(t.Id))
.GroupBy(t => t.Specialization, t => t,
(key, g) => new {Name = key.Name, CategoryItems = g.ToList()})
.OrderBy(t => string.IsNullOrEmpty(t.Name))
.ThenBy(t => t.Name)
.ToList();
It's works fine.
But now i want to group objects where Specialization is ICollection<>.
For example:
MyObject1: "Berlay's Meat"
Specializations{Chicken, Pork, Beef}
MyObject2: "Wonday's Chickery"
Specializations{Chicken}
MyObject3: "Rooki's Meat"
Specializations{Chicken, Pork}
And after group by:
Pork{MyObject1: "Berlay's Meat",MyObject3: "Rooki's Meat"}
Beef{MyObject1: "Berlay's Meat"}
Chicken{MyObject1: "Berlay's Meat",MyObject2: "Wonday's Chickery", MyObject3: "Rooki's Meat"}
Any advises?
Withcategories as:
var categories = tovRDb.MyObjects.AsEnumerable().Where(t => myHashSet.Contains(t.Id));
var catsGrouped = categories.SelectMany(
x => x.Specializations, // Specializations is an IEnumerable<Specialization>
(x, y) => new
{
Category = x,
Specialization = y,
}).GroupBy(x => x.Specialization, x => x.Category)
.ToArray();
I used the SelectMany to "multiply" each category for its specializations... Then I regrouped the result by Specialization. The result is a IGrouping<Specialization, MyObject>[]

ienumerable <string> to dictionary <string, int>

I'm using the following code to split array of strings into list.
private List<string> GenerateTerms(string[] docs)
{
return docs.SelectMany(doc => ProcessDocument(doc)).Distinct().ToList();
}
private IEnumerable<string> ProcessDocument(string doc)
{
return doc.Split(' ')
.GroupBy(word => word)
.OrderByDescending(g => g.Count())
.Select(g => g.Key)
.Take(1000);
}
What I want to do is replace the list returned with
Dictionary <string, int>
i.e. instead of returned list , i want to return Dictionary
Could anyone help ?? thanks in advance.
string doc = "This is a test sentence with some words with some words repeating like: is a test";
var result = doc.Split(' ')
.GroupBy(word => word)
.OrderByDescending(g=> g.Count())
.Take(1000)
.ToDictionary(r => r.Key ,r=> r.Count());
EDIT:
I believe you are looking to get a final dictionary from array of strings, based on words as key and their final count as values. Since dictionary can't contain duplicate values, so you will not be required to use Distict.
You have to re-write your methods as:
private Dictionary<string,int> GenerateTerms(string[] docs)
{
List<Dictionary<string, int>> combinedDictionaryList = new List<Dictionary<string, int>>();
foreach (string str in docs)
{
//Add returned dictionaries to a list
combinedDictionaryList.Add(ProcessDocument(str));
}
//return a single dictionary from list od dictionaries
return combinedDictionaryList
.SelectMany(dict=> dict)
.ToLookup(pair => pair.Key, pair => pair.Value)
.ToDictionary(group => group.Key, group => group.Sum(value => value));
}
private Dictionary<string,int> ProcessDocument(string doc)
{
return doc.Split(' ')
.GroupBy(word => word)
.OrderByDescending(g => g.Count())
.Take(1000)
.ToDictionary(r => r.Key, r => r.Count());
}
Then you can call it like:
string[] docs = new[]
{
"This is a test sentence with some words with some words repeating like: is a test",
"This is a test sentence with some words with some words repeating like: is a test",
"This is a test sentence with some words",
"This is a test sentence with some words",
};
Dictionary<string, int> finalDictionary = GenerateTerms(docs);
Try this:
string[] docs = {"aaa bbb", "aaa ccc", "sss, ccc"};
var result = docs.SelectMany(doc => doc.Split())
.GroupBy(word => word)
.OrderByDescending(g => g.Count())
.ToDictionary(g => g.Key, g => g.Count())
.Take(1000);
EDIT:
var result = docs.SelectMany(
doc => doc.Split()
.GroupBy(word => word)
.OrderByDescending(g => g.Count())
.Take(1000))
.Select(g => new {Word = g.Key, Cnt = g.Count()})
.GroupBy(t => t.Word)
.ToDictionary(g => g.Key, g => g.Sum(t => t.Cnt));
Without any additional cruft the following should work.
return doc.Split(' ')
.GroupBy(word => word)
.ToDictionary(g => g.Key, g => g.Count());
Tailor it via Take, OrderBy etc as is necessary for your situation.
Try something like this:
var keys = new List<string>();
var values = new List<string>();
var dictionary = keys.ToDictionary(x => x, x => values[keys.IndexOf(x)]);

Categories

Resources