I have three domain objects:
Child, Classroom and ChildClassroom. Here are lists of each:
var childrens = new List<Child>() {
new Child() { ChildId = 1, FirstName = "Chris" },
new Child() { ChildId = 2, FirstName = "Jenny" },
new Child() { ChildId = 3, FirstName = "Dave" },
};
var classrooms = new List<Classroom>() {
new Classroom() { ClassroomId = 1, FullName = "Kindergarten" },
new Classroom() { ClassroomId = 2, FullName = "Elementary" },
new Classroom() { ClassroomId = 3, FullName = "Secondary" },
};
var childclassrooms = new List<ChildClassroom>() {
new ChildClassroom() { ClassroomId = 1, ChildId = 1 },
new ChildClassroom() { ClassroomId = 2, ChildId = 1 },
new ChildClassroom() { ClassroomId = 3, ChildId = 2 },
};
What I want is:
var childClassroomRelationships = new object[] {
new {
childid = 1,
classrooms = new object[] {
new { classroomId = 1, occupied = true },
new { classroomId = 2, occupied = true },
new { classroomId = 3, occupied = false }
},
...
};
What's the way to go about this in Linq?
You could do this:
var childClassroomRelationships = (
from child in children
select {
childid = child.ChildId,
classrooms = (
from classroom in classrooms
select new {
classroomId = classroom.ClassroomId,
occupied = childclassrooms.Any(
cc => cc.ChildId == child.ChildId),
// Since you wanted an array.
}).ToArray()
// Since you wanted an array.
}).ToArray();
What's very important here is that a join should not be used here, if it was, you would get inner join semantics, which would cause children who are not in any classrooms to not show up (which it seems you don't want from the example you gave).
Note that this will materialize all sequences because of the calls to ToArray.
Also, it is slightly inefficient, in that to check the occupancy, it has to reiterate the entire childclassroms sequence every time.
This can be improved by "indexing" the childclassrooms map for efficient lookup, like so:
IDictionary<int, HashSet<int>> classroommap = (
from mapping in childclassrooms
group mapping.ClassroomId by mapping.ChildId into g
select g).ToDictionary(g => g.Key, g => new HashSet<int>(g));
This will give you a map of HashSet<int> instances which you can look up the child in once you know the classroom. With that, the first query becomes:
var childClassroomRelationships = (
from child in children
select {
childid = child.ChildId,
classrooms = (
from classroom in classrooms
select new {
classroomId = classroom.ClassroomId,
occupied = classroommap.ContainsKey(child.ChildId) &&
classroommap[child.ChildId].
Contains(classroom.ClassroomId),
// Since you wanted an array.
}).ToArray()
// Since you wanted an array.
}).ToArray();
var kidsInClass = (
from kid in childrens
from c in classrooms
select new {
ChildID = kid.ChildId,
classrooms = (
from cc in childclassrooms
select new {
ClassroomID = c.ClassroomId,
Occupied = cc.ChildId == kid.ChildId
}).ToArray()
}).ToArray();
Related
I have collection that I need to group if the parent key is common AND if the date field is within n (e.g. 2) hours of each other.
Sample data:
List<DummyObj> models = new List<DummyObj>()
{
new DummyObj { ParentKey = 1, ChildKey = 1, TheDate = DateTime.Parse("01/01/2020 00:00:00"), Name = "Single item - not grouped" },
new DummyObj { ParentKey = 2, ChildKey = 2, TheDate = DateTime.Parse("01/01/2020 01:00:00"), Name = "Should be grouped with line below" },
new DummyObj { ParentKey = 2, ChildKey = 3, TheDate = DateTime.Parse("01/01/2020 02:00:00"), Name = "Grouped with above" },
new DummyObj { ParentKey = 2, ChildKey = 4, TheDate = DateTime.Parse("01/01/2020 04:00:00"), Name = "Separate item as greater than 2 hours" },
new DummyObj { ParentKey = 2, ChildKey = 5, TheDate = DateTime.Parse("01/01/2020 05:00:00"), Name = "Grouped with above" },
new DummyObj { ParentKey = 3, ChildKey = 6, TheDate = DateTime.Parse("01/01/2020 05:00:00"), Name = "Single item - not grouped" }
};
private class DummyObj
{
public int ParentKey { set; get; }
public int ChildKey { set; get; }
public DateTime TheDate { set; get; }
public string Name { set; get; }
}
The resulting grouping should be (child keys):
{[1]}, {[2,3]}, {[4,5]}, {[6]}
I could group by parent key first then loop through comparing the individual items within the groups but hoping for a more elegant solution.
As always, thank you very much.
public static void Test()
{
var list = GetListFromDb(); //returns List<DummyObj>;
var sortedList = new List<DummyObj>();
foreach(var g in list.GroupBy(x => x.ParentKey))
{
if(g.Count() < 2)
{
sortedList.Add(g.First());
}
else
{
var datesInGroup = g.Select(x => x.TheDate);
var hoursDiff = (datesInGroup.Max() - datesInGroup.Min()).TotalHours;
if(hoursDiff <= 2)
{
string combinedName = string.Join("; ", g.Select(x => x.Name));
g.First().Name = combinedName;
sortedList.Add(g.First());
}
else
{
//now it's the mess
DateTime earliest = g.Select(x => x.TheDate).Min();
var subGroup = new List<DummyObj>();
foreach(var line in g)
{
if((line.TheDate - earliest).TotalHours > 2)
{
//add the current subgroup entry to the sorted group
subGroup.First().Name = string.Join("; ", subGroup.Select(x => x.Name));
sortedList.Add(subGroup.First());
//new group needed and new earliest date to start the group
sortedList = new List<DummyObj>();
sortedList.Add(line);
earliest = line.TheDate;
}
else
{
subGroup.Add(line);
}
}
//add final sub group, i.e. when there's none that are over 2 hours apart or the last sub group
if(subGroup.Count > 1)
{
subGroup.First().Name = string.Join("; ", subGroup.Select(x => x.Name));
sortedList.Add(subGroup.First());
}
else if(subGroup.Count == 1)
{
sortedList.Add(subGroup.First());
}
}
}
}
}
Here you go:
List<DummyObj> models = new List<DummyObj>()
{
new DummyObj { ParentKey = 1, ChildKey = 1, TheDate = DateTime.Parse("01/01/2020 00:00:00"), Name = "Single item - not grouped" },
new DummyObj { ParentKey = 2, ChildKey = 2, TheDate = DateTime.Parse("01/01/2020 01:00:00"), Name = "Should be grouped with line below" },
new DummyObj { ParentKey = 2, ChildKey = 3, TheDate = DateTime.Parse("01/01/2020 02:00:00"), Name = "Grouped with above" },
new DummyObj { ParentKey = 2, ChildKey = 4, TheDate = DateTime.Parse("01/01/2020 04:00:00"), Name = "Separate item as greater than 2 hours" },
new DummyObj { ParentKey = 2, ChildKey = 5, TheDate = DateTime.Parse("01/01/2020 05:00:00"), Name = "Grouped with above" },
new DummyObj { ParentKey = 3, ChildKey = 6, TheDate = DateTime.Parse("01/01/2020 05:00:00"), Name = "Single item - not grouped" }
};
List<List<DummyObj>> groups =
models
.GroupBy(x => x.ParentKey)
.Select(xs => xs.OrderBy(x => x.TheDate).ToList())
.SelectMany(xs => xs.Skip(1).Aggregate(new[] { xs.Take(1).ToList() }.ToList(), (a, x) =>
{
if (x.TheDate.Subtract(a.Last().Last().TheDate).TotalHours < 2.0)
{
a.Last().Add(x);
}
else
{
a.Add(new [] { x }.ToList());
}
return a;
}))
.ToList();
string output =
String.Join(", ",
groups.Select(x =>
$"{{[{String.Join(",", x.Select(y => $"{y.ChildKey}"))}]}}"));
That gives me:
{[1]}, {[2,3]}, {[4,5]}, {[6]}
I have a strange question :)
I have a object list looking like this:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
I would like to sort the list providing a "start id"
For example, if I provide "start id" 3, the result should look like this:
Id
Name
3
Patric
4
Theodor
1
Marcus
2
Mattias
I have no idea where to start, so I really need some help from you coding gods
The list is from a sql table, but it does not matter for me where the sort take place (in sql query or in c# code)
Try this:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
var start_id = 3;
var max_id = list.Max(y => y.Id);
var result =
from x in list
orderby (x.Id + max_id - start_id) % max_id
select x;
I get:
With LINQ to objects you can do something like that:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
var startId = 3;
var result = list
.GroupBy(i => i.Id >= startId ? 1 : 0) // split in two groups
.OrderByDescending(g => g.Key) // sort to have the group with startId first
.Select(g => g.OrderBy(i => i.Id)) // sort each group
.SelectMany(i => i) // combine result
.ToList();
Console.WriteLine(string.Join(", ", result.Select(i => i.Id))); // prints "3, 4, 1, 2"
You require 2 criteria to apply:
Order ascending by Id.
Return the Ids greater than threshold before the Ids less than threshold.
You can try:
var offset = 3;
var sorted1 = list
.OrderBy(item => item.Id < offset)
.ThenBy(item => item.Id);
The OrderBy condition yields true if Id is less than offset and false otherwise.
true is greater than false and therefore is returned later
A dirty way could also be:
var offset = 3;
var sorted2 = list
.OrderBy(item => unchecked((uint)(item.Id - offset)));
Here the offset is subtracted from Id and the result converted to unsigned int to make the negative values become very large positive ones. A little hacky. Might not work with queries against SQL providers.
Here's a toy Non-Linq Version
object[] ShiftList(int id)
{
var list = new dynamic[]
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
Span<dynamic> listSpan = list;
int indexFound = -1;
for (int i = 0; i < list.Length; i++)
{
if (listSpan[i].Id == id)
{
indexFound = i;
}
}
if (indexFound is -1)
{
return list;
}
var left = listSpan.Slice(0, indexFound);
var right = listSpan[indexFound..];
object[] objs = new object[list.Length];
Span<object> objSpan = objs;
right.CopyTo(objSpan);
left.CopyTo(objSpan[right.Length..]);
return objs;
}
Try using foreach and iterate over each object in your list:
foreach (var item in list)
{
}
from here you should be able to use some of the collection methods for a list to reorder your list.
I have a collection of group users which has a GroupId and UserId. I need to filter out any duplicate GroupId/UserId objects which may exist in the collection. How can I write a non-query expression GroupBy to filter out the duplicate rows? The following example is adapted from a group by example that I found online but I'm not quite clear on how to refine this code for my particular scenario:
var groupByResults =
groupUsers.GroupBy(
x => x.GroupId,
x => x.UserId,
(key, g) => new
{
[?] = key,
[?] = g.ToList()
}
);
If your data looks like the list below you can group by the compound key then take the first value in each group. The OrderBy is optional
var groupUsers = new List<dynamic>() {
new { groupId = 1, userId = 1, name = "a" },
new { groupId = 1, userId = 1, name = "b" },
new { groupId = 1, userId = 2, name = "c" }
};
var result = groupUsers
.GroupBy(u => new { u.groupId, u.userId} )
.Select(g => g.OrderBy(u => u.name).FirstOrDefault());
To find out the duplicated userId, groupId.
GroupBy userId, groupId
Count if any group item >=2
SelectMany the collection
Code:
var duplicatedUsers = groupUsers
.GroupBy(gu => new { gu.UserId, gu.GroupId })
.Where(g => g.Count() >= 2)
.SelectMany(g => g)
Following code will be helpful to you,
class GroupUsers
{
public int GroupId {get;set;}
public int UserId {get;set;}
}
public class Program
{
public static void Main()
{
var groupUsers = new List<GroupUsers>() {
new GroupUsers{ GroupId = 1, UserId = 1},
new GroupUsers{ GroupId = 1, UserId = 1},
new GroupUsers{ GroupId = 1, UserId = 2},
new GroupUsers{ GroupId = 1, UserId = 2},
new GroupUsers{ GroupId = 1, UserId = 3},
new GroupUsers{ GroupId = 1, UserId = 4},
new GroupUsers{ GroupId = 1, UserId = 5},
new GroupUsers{ GroupId = 1, UserId = 3}
};
var result1 = groupUsers
.GroupBy(u => new { u.GroupId, u.UserId} )
.Where(g => g.Count()>=2) // check for duplicate value by checking whether the count is greater than or equal to 2.
.SelectMany(g=>g); // flatten the list
foreach(var user in result1) // Iterate over the result
{
Console.WriteLine(user.GroupId +" "+user.UserId);
}
// Or
var result2 = from a in groupUsers
group a by new{a.GroupId, a.UserId} into grp
where grp.Count()>=2
from g in grp select new{g}
foreach(var user in result2)
{
Console.WriteLine(user.g.GroupId +" "+user.g.UserId);
}
}
}
Scenario is,
im using EntityFrame Work 6. I have a DB have table having structure below.
cat_id geo_id parent_id geo_name
Root 1 NULL Pakistan
Province 2 1 Punjab
District 3 2 Attock
City 4 3 Attock
City 5 3 Fateh Jang
City 6 3 Hasan Abdal
Table have hierarchical data in relational form,as you can see.
I want to traverse this hierarchy, want to specific parent level, If im at geo_id 6 then I want to go parent_id 3 and get value Attock or want to go to parent_id 2 and want to get value Punjab.
Moral of the story is,standing at any child,want traverse till specified parent or grand parent,not entire hierarchy. Below is code I have tried but it gives me only its immediate parent.
More Shortly, Want a LINQ query,which will return name of specified parent or grand parent,For Example. I can ask my query, "hey! im Hasan Abdal(City),tell me my Province"
Province = (from cp in db.geo_hierarchy
join mp in db.geo_hierarchy on cp.parent_id equals mp.geo_id
where cp.geo_name == risk.geo_hierarchy.geo_name
select mp.geo_name).FirstOrDefault()
see full code below,it used in inside LINQ query's select clause
Risklists = (from risk in db.risk_cat_detail
where risk.occurance_date.Value.Year==2014 && risk.occurance_date.Value.Month>=6 && risk.occurance_date.Value.Month<=9
select new risk_cat_detail_contract()
{
cat_id = risk.cat_id,
catdesc = risk.category_main.cat_name,
risk_cat_detail_id = risk.risk_cat_detail_id,
trans_date = risk.trans_date.Value,
occurance_date = risk.occurance_date.Value,
occurance_time = risk.occurance_time,
geo_id = risk.geo_id,
geo_desc = risk.geo_hierarchy.geo_name,
Province = (from cp in db.geo_hierarchy
join mp in db.geo_hierarchy on cp.parent_id equals mp.geo_id
where cp.geo_name == risk.geo_hierarchy.geo_name
select mp.geo_name).FirstOrDefault()
}).ToList<risk_cat_detail_contract>();
Help me out,Thanks in Advance
Try this:
var geo_hierarchy = new []
{
new { cat_id = "Root", geo_id = 1, parent_id = (int?)null, geo_name = "Pakistan", },
new { cat_id = "Province", geo_id = 2, parent_id = (int?)1, geo_name = "Punjab", },
new { cat_id = "District", geo_id = 3, parent_id = (int?)2, geo_name = "Attock", },
new { cat_id = "City", geo_id = 4, parent_id = (int?)3, geo_name = "Attock", },
new { cat_id = "City", geo_id = 5, parent_id = (int?)3, geo_name = "Fateh Jang", },
new { cat_id = "City", geo_id = 6, parent_id = (int?)3, geo_name = "Hasan Abdal", },
};
var map = geo_hierarchy.ToDictionary(x => x.geo_id);
Func<int, string, string> up = null;
up = (geo_id, cat_id) =>
{
var record = map[geo_id];
return
record.cat_id == cat_id
? record.geo_name
: (record.parent_id.HasValue
? up(record.parent_id.Value, cat_id)
: null);
};
var result = up(5, "Province"); // -> "Punjab"
I'm having trouble with getting a my linq query correct. I've been resisting doing this with foreach loops because I'm trying to better understand linq.
I have following data in LinqPad.
void Main()
{
var events = new[] {
new {ID = 1, EventLevel = 1, PatientID = "1", CodeID = "2", Occurences = 0 },
new {ID = 2, EventLevel = 2, PatientID = "1", CodeID = "2", Occurences = 0 },
new {ID = 3, EventLevel = 1, PatientID = "2", CodeID = "1", Occurences = 0 },
new {ID = 4, EventLevel = 3, PatientID = "2", CodeID = "2", Occurences = 0 },
new {ID = 5, EventLevel = 1, PatientID = "3", CodeID = "3", Occurences = 0 },
new {ID = 6, EventLevel = 3, PatientID = "1", CodeID = "4", Occurences = 0 }
};
var filter = new FilterCriterion();
var searches = new List<FilterCriterion.Occurence>();
searches.Add(new FilterCriterion.Occurence() { CodeID = "1", MinOccurences = 2, MaxOccurences = 3 });
searches.Add(new FilterCriterion.Occurence() { CodeID = "2", MinOccurences = 2, MaxOccurences = 3 });
filter.Searches = searches;
var summary = from e in events
let de = new
{
PatientID = e.PatientID,
CodeID = e.CodeID
}
group e by de into t
select new
{
PatientID = t.Key.PatientID,
CodeID = t.Key.CodeID,
Occurences = t.Count(d => t.Key.CodeID == d.CodeID)
};
var allCodes = filter.Searches.Select(i => i.CodeID);
summary = summary.Where(e => allCodes.Contains(e.CodeID));
// How do I find the original ID property from the "events" collection and how do I
// eliminate the instances where the Occurences is not between MinOccurences and MaxOccurences.
foreach (var item in summary)
Console.WriteLine(item);
}
public class FilterCriterion
{
public IEnumerable<Occurence> Searches { get; set; }
public class Occurence
{
public string CodeID { get; set; }
public int? MinOccurences { get; set; }
public int? MaxOccurences { get; set; }
}
}
The problem I have is that need to filter the results by the MinOccurences and MaxOccurences filter property and in the end I want the "events" objects where the IDs are 1,2,3 and 4.
Thanks in advance if you can provide help.
To access event.ID at the end of processing you need to pass it with your first query. Alter select to this:
// ...
group e by de into t
select new
{
PatientID = t.Key.PatientID,
CodeID = t.Key.CodeID,
Occurences = t.Count(d => t.Key.CodeID == d.CodeID),
// taking original items with us
Items = t
};
Having done that, your final query (including occurrences filter) might look like this:
var result = summary
// get all necessary data, including filter that matched given item
.Select(Item => new
{
Item,
Filter = searches.FirstOrDefault(f => f.CodeID == Item.CodeID)
})
// get rid of those without matching filter
.Where(i => i.Filter != null)
// this is your occurrences filtering
.Where(i => i.Item.Occurences >= i.Filter.MinOccurences
&& i.Item.Occurences <= i.Filter.MaxOccurences)
// and finally extract original events IDs
.SelectMany(i => i.Item.Items)
.Select(i => i.ID);
This produces 1, 2 as result. 3 and 4 are left out as they don't get past occurrences filtering.
I have run your program in linqpad.
My understanding is that you want to filter using filter.MinOccurences and filter.MaxOccurences on Occurences count of result data set.
You can add additional filters using Where clause.
if (filter.MinOccurences.HasValue)
summary = summary.Where (x=> x.Occurences >= filter.MinOccurences);
if (filter.MaxOccurences.HasValue)
summary = summary.Where (x=> x.Occurences <= filter.MaxOccurences);