I have a collection persons that contains id and name:
Dictionary<int, string> persons;
1 John
2 Pitter
3 Carl
Then I have a collection with person ids by organization:
IDictionary<int, IEnumerable<int>> workers;
100 - [1,2]
101 - []
102 - [3]
And finally I have the main entity that contains only OrganizationId:
entity.OrganizationId = 100;
I need to go through workers and get all ids of persons (it will give: 1,2), to finally get the names of those persons from persons. How can I do that?
If you just want the persons from this particular OrganisationId:
var personsFromOrganisation = workers[OrganizationId].Select(i => persons[i]);
If you want an object that directly associates all OrganisationId to corresponding persons (here using a dictionary):
var organisationsIds = entities.Select(e => e.OrganisationId).Distinct();
var personsByOrganisation = organisationsIds.ToDictionary(id => id, id => workers[id].Select(pid => persons[pid]));
No need for LINQ to get the ID's:
IEnumerable<int> orgWorkers = null;
bool containsOrg = workers.TryGetValue(entity.OrganizationId, out orgWorkers);
Now it's simple to get the names:
List<string> workerNames = new List<string>();
if(containsOrg)
{
workerNames = orgWorkers
.Where(id => persons.ContainsKey(id))
.Select(id => persons[id])
.ToList();
}
You can get the needed Ids using a Sub-Query and then Join by that:
var personsByOrganization =
from p in persons
join id in workers.Where(x => x.Key == entity.OrganizationId)
.SelectMany(x => x.Value)
on p.Key equals id
select p;
No-Join Approach:
HashSet<int> Ids = new HashSet<int>(workers.Where(x => x.Key == entity.OrganizationId)
.SelectMany(x => x.Value));
Dictionary<int, string> personsByOrganization =
persons.Where(x => Ids.Contains(x.Key))
.ToDictionary(x => x.Key, x => x.Value);
Related
I have two dictionaries of the same type, A and B.
Dictionary<string, IEnumerable<object>>
I'm using object to represent a complex type having a property 'Id'.
I'm looking for all items in A having objects that exist in B (using Id), but under a different key. It's basically to tell if an object has moved keys. A is the new dictionary and B is the old.
Is there a reasonable way to accomplish this using LINQ? I would like the result to be a dictionary of all key-value pairs in A meeting the criteria. Thanks in advance.
I use Interface IHasId for use Id propert:
public interface IHasId
{
int Id { get; }
}
And class AAA that inherited the interface:
public class AAA: IHasId
{
public int Id { get; set; }
}
Here the linq you look for:
Dictionary<string, IEnumerable<IHasId>> A = new Dictionary<string, IEnumerable<IHasId>>();
A.Add("111", new List<IHasId> { new AAA { Id = 1 }, new AAA { Id = 2 } });
A.Add("333", new List<IHasId> { new AAA { Id = 3 } });
Dictionary<string, IEnumerable<IHasId>> B = new Dictionary<string, IEnumerable<IHasId>>();
B.Add("111", new List<IHasId> { new AAA { Id = 1 }});
B.Add("222", new List<IHasId> { new AAA { Id = 2 }});
B.Add("333", new List<IHasId> { new AAA { Id = 3 } });
var res = A.Where(a => a.Value.Any(c => B.Any(v => v.Value
.Select(x => x.Id).Contains(c.Id) && a.Key != v.Key))).ToList();
In this example it return key 111 that has the object with Id = 2 that moved from key 222 to key 111
If you want the result as dictionary you can change the ToList with ToDictionary:
var res = A.Where(a => a.Value.Any(c => B.Any(v => v.Value
.Select(x => x.Id).Contains(c.Id) && a.Key != v.Key)))
.ToDictionary(a=>a.Key, a=>a.Value);
If you want in the new dictionary only the values that has change, like in the example key 111 and value with only the object with Id = 2, you can do it like this:
var res = A.Select(a => new KeyValuePair<string, IEnumerable<IHasId>>(a.Key,
a.Value.Where(c => B.Any(v => v.Value.Select(x => x.Id).Contains(c.Id) && a.Key != v.Key))))
.Where(a=>a.Value.Count() > 0)
.ToDictionary(a => a.Key, a => a.Value);
In terms of searchability, your dictionary has it backwards; it is efficient for looking up an object given a string, but you need to be able to look up the strings for a given object. An efficient data structure for this purpose would be a Lookup<object,string>.
First, use ToLookup() to create a lookup table where the key is the object and the value is the list of keys in both list A and B. Use Union (instead of Concat) to eliminate duplicates.
var lookup = listA
.Union( listB )
.ToLookup( pair => pair.Value, pair => pair.Key );
Once you have the lookup, the problem is trivial.
var results = lookup.Where( x => x.Count() > 1);
See this DotNetFiddle for a working example with sample data.
If you need A entries with original objects, it could be:
var result = A.Where(a => B.Any(b => b.Key != a.Key && b.Value.Intersect(a.Value).Any()));
If you need A entries with only matching objects from B, it could be:
var result = A.Select(a => new KeyValuePair<string, IEnumerable<object>>(a.Key, B.Where(b => b.Key != a.Key).SelectMany(b => b.Value.Intersect(a.Value)))).Where(x => x.Value.Any());
You can provide a custom equality comparer for Intersect to match items by Id or whatever.
Use new Dictionary<string, IEnumerable<object>>(result) if you need it as a dictionary.
Use the Join operator (see join clause (C# Reference)):
var dictionary = (
from a in (from entry in A from Value in entry.Value select new { entry.Key, Value })
join b in (from entry in B from Value in entry.Value select new { entry.Key, Value })
on ((dynamic)a.Value).Id equals ((dynamic)b.Value).Id
where a.Key != b.Key
select a
).ToDictionary(a => a.Key, a => a.Value);
I have Class1 like:
{
string Name,
string Sex
}
And I have a List<Class1> with 100 items where 50 are Males and 50 are Females, how do I get 10 groups of 5Males and 5Females each with LINQ?
I already manage to get the list grouped in 10 groups but not distributed evenly by sex.
var foo = My100List.Select((person, index) => new {person, index})
.GroupBy(x => x.index%10)
.Select(i => new Group
{
Name= "Group" + i.Key,
Persons= i.Select(y => y.person).ToList()
});
The code above don't distribute by sex.
Try this (untested):
int groupSize = 5;
var foo = My100List.GroupBy(x => x.Sex)
.SelectMany(g => g.Select((x, i) => new { Person = x, Group = i / groupSize}))
.GroupBy(x => x.Group)
.Select(g => new Group
{
Name = "Group" + g.Key,
Persons = g.Select(x => x.Person).ToList()
});
EDIT
Tested and confirmed. The above code works.
Add .OrderBy for sex before the .Select
Tested and working:
var foo = My100List.OrderBy(p => p.Sex).Select((person, index) => new {person, index})
.GroupBy(x => x.index%10)
.Select(i => new Group
{
Name= "Group" + i.Key,
Persons= i.Select(y => y.person).ToList()
});
I search more on this site for "get 4 top item from each group", but there are many topic about get first item from each group like this
var rr = db.Products
.GroupBy(x => x.ProductSubTypeCategoryId, (key, g) => g.OrderBy(e => e.PersianName)
.Take(4).ToList());
or
var rr = db.Products
.GroupBy(x => x.ProductSubTypeCategoryId).Select(g => new { pname = g.Key, count = g.Count() });
but return only first item from each group. How can I change the code to get 4 items from each group?
Try this:
var rr = db.Products.GroupBy(x => x.ProductSubTypeCategoryId).Select(g => new { GroupName = g.Key, Items = g.Take(4).ToList() });
This should give you an anonymous object with a GroupName property that returns the ProductSubTypeCategoryId and an Items property that returns a list of up to 4 items for each group.
Try something like this with SelectMany()
var rr = db.Products
.GroupBy(x => x.ProductSubTypeCategoryId)
.SelectMany(g => g.OrderBy(e => e.PersianName).Take(4))
.ToList();
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);
}
How can I do GroupBy multiple columns in LINQ
Something similar to this in SQL:
SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>
How can I convert this to LINQ:
QuantityBreakdown
(
MaterialID int,
ProductID int,
Quantity float
)
INSERT INTO #QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM #Transactions
GROUP BY MaterialID, ProductID
Use an anonymous type.
Eg
group x by new { x.Column1, x.Column2 }
Procedural sample:
.GroupBy(x => new { x.Column1, x.Column2 })
Ok got this as:
var query = (from t in Transactions
group t by new {t.MaterialID, t.ProductID}
into grp
select new
{
grp.Key.MaterialID,
grp.Key.ProductID,
Quantity = grp.Sum(t => t.Quantity)
}).ToList();
For Group By Multiple Columns, Try this instead...
GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new
{
Key1 = key.Column1,
Key2 = key.Column2,
Result = group.ToList()
});
Same way you can add Column3, Column4 etc.
Since C# 7 you can also use value tuples:
group x by (x.Column1, x.Column2)
or
.GroupBy(x => (x.Column1, x.Column2))
C# 7.1 or greater using Tuples and Inferred tuple element names (currently it works only with linq to objects and it is not supported when expression trees are required e.g. someIQueryable.GroupBy(...). Github issue):
// declarative query syntax
var result =
from x in inMemoryTable
group x by (x.Column1, x.Column2) into g
select (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity));
// or method syntax
var result2 = inMemoryTable.GroupBy(x => (x.Column1, x.Column2))
.Select(g => (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity)));
C# 3 or greater using anonymous types:
// declarative query syntax
var result3 =
from x in table
group x by new { x.Column1, x.Column2 } into g
select new { g.Key.Column1, g.Key.Column2, QuantitySum = g.Sum(x => x.Quantity) };
// or method syntax
var result4 = table.GroupBy(x => new { x.Column1, x.Column2 })
.Select(g =>
new { g.Key.Column1, g.Key.Column2 , QuantitySum= g.Sum(x => x.Quantity) });
You can also use a Tuple<> for a strongly-typed grouping.
from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
LastName = grouping.Key.Item1,
FirstName = grouping.Key.Item2,
MiddleName = grouping.Key.Item3,
DayCount = grouping.Count(),
AmountBilled = grouping.Sum(x => x.Rate),
}
Though this question is asking about group by class properties, if you want to group by multiple columns against a ADO object (like a DataTable), you have to assign your "new" items to variables:
EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
.Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
var Dups = ClientProfiles.AsParallel()
.GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
.Where(z => z.Count() > 1)
.Select(z => z);
var Results= query.GroupBy(f => new { /* add members here */ });
A thing to note is that you need to send in an object for Lambda expressions and can't use an instance for a class.
Example:
public class Key
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
This will compile but will generate one key per cycle.
var groupedCycles = cycles.GroupBy(x => new Key
{
Prop1 = x.Column1,
Prop2 = x.Column2
})
If you wan't to name the key properties and then retreive them you can do it like this instead. This will GroupBy correctly and give you the key properties.
var groupedCycles = cycles.GroupBy(x => new
{
Prop1 = x.Column1,
Prop2= x.Column2
})
foreach (var groupedCycle in groupedCycles)
{
var key = new Key();
key.Prop1 = groupedCycle.Key.Prop1;
key.Prop2 = groupedCycle.Key.Prop2;
}
group x by new { x.Col, x.Col}
.GroupBy(x => (x.MaterialID, x.ProductID))
.GroupBy(x => x.Column1 + " " + x.Column2)
For VB and anonymous/lambda:
query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })