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()
});
Related
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);
Using the following linq code, how can I add dense_rank to my results? If that's too slow or complicated, how about just the rank window function?
var x = tableQueryable
.Where(where condition)
.GroupBy(cust=> new { fieldOne = cust.fieldOne ?? string.Empty, fieldTwo = cust.fieldTwo ?? string.Empty})
.Where(g=>g.Count()>1)
.ToList()
.SelectMany(g => g.Select(cust => new {
cust.fieldOne
, cust.fieldTwo
, cust.fieldThree
}));
This does a dense_rank(). Change the GroupBy and the Order according to your need :)
Basically, dense_rank is numbering the ordered groups of a query so:
var DenseRanked = data.Where(item => item.Field2 == 1)
//Grouping the data by the wanted key
.GroupBy(item => new { item.Field1, item.Field3, item.Field4 })
.Where(#group => #group.Any())
// Now that I have the groups I decide how to arrange the order of the groups
.OrderBy(#group => #group.Key.Field1 ?? string.Empty)
.ThenBy(#group => #group.Key.Field3 ?? string.Empty)
.ThenBy(#group => #group.Key.Field4 ?? string.Empty)
// Because linq to entities does not support the following select overloads I'll cast it to an IEnumerable - notice that any data that i don't want was already filtered out before
.AsEnumerable()
// Using this overload of the select I have an index input parameter. Because my scope of work is the groups then it is the ranking of the group. The index starts from 0 so I do the ++ first.
.Select((#group , i) => new
{
Items = #group,
Rank = ++i
})
// I'm seeking the individual items and not the groups so I use select many to retrieve them. This overload gives me both the item and the groups - so I can get the Rank field created above
.SelectMany(v => v.Items, (s, i) => new
{
Item = i,
DenseRank = s.Rank
}).ToList();
Another way is as specified by Manoj's answer in this question - But I prefer it less because of the selecting twice from the table.
So if I understand this correctly, the dense rank is the index of the group it would be when the groups are ordered.
var query = db.SomeTable
.GroupBy(x => new { x.Your, x.Key })
.OrderBy(g => g.Key.Your).ThenBy(g => g.Key.Key)
.AsEnumerable()
.Select((g, i) => new { g, i })
.SelectMany(x =>
x.g.Select(y => new
{
y.Your,
y.Columns,
y.And,
y.Key,
DenseRank = x.i,
}
);
var denseRanks = myDb.tblTestReaderCourseGrades
.GroupBy(x => new { x.Grade })
.OrderByDescending(g => g.Key.Grade)
.AsEnumerable()
.Select((g, i) => new { g, i })
.SelectMany(x =>
x.g.Select(y => new
{
y.Serial,
Rank = x.i + 1,
}
));
im pretty new to linq-lambda. I have a MySQL query that pulls the name of items in two tables union'd together. Then pull another column of the specialties that the items fall under.
my working query is this:
Select
p.title,
GROUP_CONCAT(spec.categoryname) genre
From
(SELECT FullName AS title, TypeId as typeid, Id as id FROM programs
UNION
SELECT FullName, TypeId, Id FROM products)
AS p
Inner join specialtymembers mem on (ItemType = p.typeid AND ItemId = p.id)
Inner join specialties spec on mem.Category = spec.id
GROUP BY p.title
ORDER BY p.title
now my problem is... that i have to somehow convert this into linq lambda. My attempt is this:
var allitems =
_programContentService.Products.Select(r => r.FullName)
.Union(_programContentService.Programs.Select(q => q.FullName))
.Join(_programContentService.SpecialtyMembers, z => z.ItemType, q => q.ItemType,
(z, q) => new {z, q})
.Join(_programContentService.Specialties, x => x.Id, z => z.Category,
(x, z) => new {x,z})
.Select(#t => new SelectListItem { Name = q.FillName.ToString(), Genre = "SOME HOW HAVE TO USE A LOOPING FEATURE??" });
UPDATE:
var allitems = _programContentService
.ProgramProductViews
.Select(x => new {x.FullName, x.TypeId, x.Id})
.Join(
_programContentService.SpecialtyMembers,
type => new {type.TypeId, type.Id},
member => new {TypeId = member.ItemType, Id = member.ItemId},
(type, member) => new {type.FullName, member.Category})
.Join(
_programContentService.Specialties,
q => q.Category,
specialty => specialty.Id,
(q, specialty) => new { q.FullName, specialty.SpecialtyName })
.GroupBy(x=>x.FullName)
.AsEnumerable()
.Select(x => new SelectListItem
{
Value = x.Key,
Text = String.Join(",", x.Select(q=>q.SpecialtyName))
});
i have a feeling that I am close...
I think this will get you what you want. It is untested, if you have issues let me know and I will work it out.
var allitems =
_programContentService
.Products
.Select(x => new { x.FullName, x.TypeId, x.Id })
.Union(
_programContentService
.Programs
.Select(x => new { x.FullName, x.TypeId, x.Id }))
.Join(
_programContentService.SpecialtyMembers,
type => new { type.TypeId, type.Id },
member => new { TypeId = member.ItemType, Id = member.ItemId },
(type, member) => new { type.FullName, member.Category })
.Join(
_programContentService.Specialties,
x => x.Category,
specialty => specialty.Id,
(x, specialty) => new { x.FullName, specialty.CategoryName })
.GroupBy(x => x.FullName)
.AsEnumerable()
.Select(x => new SelectListItem
{
Value = x.Key,
Text = String.Join(",", x.Select(y => y.CategoryName))
});
I'm trying to partition some comma separated lines into groups of size 2 at max.
How can i convert the collection of groups to list of lists as below?
I expect the partitions to be 3 first and then 4 after grouping.
List<string> chunk = new List<string>()
{
"a,b,c",
"a,d,e",
"b,c,d",
"b,e,d",
"b,f,g",
"e"
};
var partitons = chunk.GroupBy(c => c.Split(',')[0], (key, g) => g);
var groups = partitons.Select(x => x.Select((i, index) => new { i, index }).GroupBy(g => g.index / 2, e => e.i));
IEnumerable<IEnumerable<string>> parts = groups.Select(???)
This is what I wanted
var parts = groups.SelectMany(x => x).Select(y => y.Select(z => z));
Try this:
partitons = groups.Select(x => x.SelectMany(y => y));
I get this:
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);
}