Reputation: 47
I have a list contains another list where I supposed to get the common elements.
Class model:
PlanInfo has shiftName, ilist of Plan. Plan has start time, end time
public class Plan
{
public int Start { get; set; }
public int End { get; set; }
}
public class PlanInfo
{
public string ShiftName { get; set; }
public IList<Plan> lstPlan { get; set; }
}
iList of PlanInfo contains
[“shift1”, [1000,1200]],
[“shift2”,[1000,1100]],
[“shift3”,[1000,1200]]
Expected output in this should be empty
since 1000,1200 doesn’t exist in shift2
[“shift1”, [1000,1200]],
[“shift2”,[[1000,1200],[1000,1100]],
[“shift3”,[1000,1200]]
Should return [1000,1200]
since it’s common in all lists.
I tried using intersect, but here IList<PlanInfo
is not fixed length. it could have more than one records.
Kindly suggest me which LINQ
query serve the above result
Upvotes: 1
Views: 841
Reputation: 35093
Hmm, If I understand the requirements: Given a list of PlanInfo, find any Plans common to all PlanInfo...
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.ShiftName, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
This assumes that a Plan can only be counted once within a PlanInfo. (No duplicate plans) This also assumes that the plan info references for the same start/end times are pointing to the same object instance. If not, then you cannot group on the Plan, you will need a unique key (Like a plan ID) to group on. If these are EF entities pulled from a DbContext then they will be the same reference.
First get the total # of plan infos. In your example this would return 3.
Next, for all plan infos, use SelectMany
to fetch the Plans, but compose that down into the PlanInfo.ShiftName + the Plan. This flattens your one to many. Next group by the Plan so that we can count the # of PlanInfos that each Plan appears in. Any/all counts that match the total number of PlanInfos means a Plan that appears in all PlanInfos, Select the Key
to get that grouped Plan(s) and that should have it.
Edit: adding an example...
[Test]
public void TestPlanCheck()
{
var plan1 = new Plan { Start = 1, End = 2 };
var plan2 = new Plan { Start = 2, End = 3 };
var plan3 = new Plan { Start = 3, End = 4 };
var planInfos = new List<PlanInfo>
{
new PlanInfo{ Name = "Test1", Plans = new []{ plan1, plan2}.ToList() },
new PlanInfo{Name = "Test2", Plans = new []{plan2, plan3}.ToList()},
new PlanInfo{Name = "Test3", Plans = new []{ plan3, plan2}.ToList() }
};
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.Name, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
}
private class Plan
{
public int Start { get; set; }
public int End { get; set; }
}
private class PlanInfo
{
public string Name { get; set; }
public List<Plan> Plans { get; set; }
}
That was the test I had run using these stub classes. In this case the test will return back 1 match, for the Plan 2 value.
To outline the issue with ensuring plan references for the same start/end times match: If the setup looked like this:
[Test]
public void TestPlanCheck()
{
var plan1 = new Plan { Start = 1, End = 2 };
var plan2A = new Plan { Start = 2, End = 3 };
var plan2B = new Plan { Start = 2, End = 3 };
var plan3 = new Plan { Start = 3, End = 4 };
var planInfos = new List<PlanInfo>
{
new PlanInfo{ Name = "Test1", Plans = new []{ plan1, plan2A}.ToList() },
new PlanInfo{Name = "Test2", Plans = new []{plan2B, plan3}.ToList()},
new PlanInfo{Name = "Test3", Plans = new []{ plan3, plan2B}.ToList() }
};
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.Name, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
}
In this case even though plan 2A and 2B have the same start/end time, the group by would not group them together because they represent 2 references to 2 objects. This though would be fine:
var plan2A = new Plan { Start = 2, End = 3 };
var plan2B = plan2A;
Both point to the same reference. If you do have different references for the same plan ranges, you would need a planID then group on a PlanId. Ideally though I would check why the references don't match because they should to avoid potential errors based on assumptions of equality.
Upvotes: 1
Reputation: 20095
One can use Aggregate
with Intersect
on PlanInfo.Plans
like:
var plansCommon = planInfoList.Select(p => p.Plans)
.Aggregate<IEnumerable<Plan>>((p1, p2) =>
p1.Intersect(p2, new PlanComparer()))
.ToList();
// Implement IEqualityComparer
class PlanComparer : IEqualityComparer<Plan>
{
public bool Equals(Plan x, Plan y)
{
if (x.Start == y.Start &&
x.End == y.End)
return true;
return false;
}
public int GetHashCode(Plan obj)
{
return obj.Start.GetHashCode() ^ obj.End.GetHashCode();
}
}
The Intersect
will recursively apply on Plans
list of each PlanInfo
to provide list of Plan
common across all.
Upvotes: 0