Child list in join statement - LINQ - c#

I have 3 collections
List<Brand> brands= new List<Brand>
{
new Brand { ID = 5, Name = "Toyota"},
new Brand { ID = 6, Name = "Mazda"},
new Brand { ID = 7, Name = "Seat"}
};
ModelParam par1 = new ModelParam() { Id = 1, fuelType = "Petrol", enginePower =110, engineTorque = 130 };
ModelParam par2 = new ModelParam() { Id = 2, fuelType = "Petrol", enginePower = 170, engineTorque = 290 };
ModelParam par3 = new ModelParam() { Id = 3, fuelType = "Diesel", enginePower = 140, engineTorque = 280 };
ModelParam par4 = new ModelParam() { Id = 4, fuelType = "Diesel", enginePower = 190, engineTorque = 320 };
List<Model> models = new List<Model>
{
new Model { Id = 1, Name = "CX5", brandId = 6, parameters = new List<ModelParam> {par1, par3} },
new Model { Id = 2, Name = "Corolla", brandId = 5, parameters = new List<ModelParam> {par2, par3} },
new Model { Id = 3, Name = "Leon", brandId = 7, parameters = new List<ModelParam> {par2, par4} }
};
As a result I want to have a query with combination of:
Brand - Model - FuelType - EnginePower.
I wrote this to join brands and model lists:
var result = models.Join(brands,
mod => mod.brandId,
b => b.ID,
(mod, b) => new { b.Name, mod.Name })
.ToList();
The problem is I can't extract fuelType and enginePower from ModelParams from params property in models List. Any tips?

Can this approach work for you?
var result = models.Join(
brands,
mod => mod.brandId,
b => b.ID,
(mod, b) => mod.parameters.Select(parameter => new {
Brand = b.Name,
Model = mod.Name,
FuelType = parameter.fuelType,
EnginePower = parameter.enginePower
}))
.SelectMany(pr => pr)
.ToList();

You should update your result selector Func a little bit and get the required values fuelType and enginePower from parameters collection in Model class. The following snippet will select only the first fuelType from parameters and max enginePower.
var result = models
.Join(brands, mod => mod.brandId, b => b.ID,
(mod, b) => new
{
Brand = b.Name,
Model = mod.Name,
FuelType = mod.parameters.Select(p => p.fuelType).FirstOrDefault(),
EnginePower = mod.parameters.Max(p => p.enginePower)
})
.ToList();
However, there a situation when model can have multiple values for fuelType and enginePower.
If you need to choose all combinations, just look through the parameters collection and then flatten a result using SelectMany
var result = models
.Join(brands, mod => mod.brandId, b => b.ID,
(mod, b) => mod.parameters.Select(p => new
{
Brand = b.Name,
Model = mod.Name,
FuelType = p.fuelType,
EnginePower = p.enginePower
}))
.SelectMany(_ => _)
.ToList();

Related

C# select duplicate using group

I would like to select only from non-blank Names where any Group contains same ID multiple times:
Data Setup
var a1 = new { id = 3, Name = "", Group = "GroupA" };
var a2 = new { id = 2, Name = "", Group = "GroupA" };
var a3 = new { id = 3, Name = "", Group = "GroupA" };
var b1 = new { id = 4, Name = "B", Group = "GroupB" };
var b2 = new { id = 5, Name = "B", Group = "GroupB" };
var b3 = new { id = 5, Name = "B", Group = "GroupB" };
List<dynamic> group = new List<dynamic>();
group.Add(a1);
group.Add(a2);
group.Add(a3);
group.Add(b1);
group.Add(b2);
group.Add(b3);
Query:
var query1 = group.ToList()
.Where(s=>s.Name != "")
.GroupBy(x =>x.Group)
.Where(g => g.Count() > 1)
.SelectMany(y => y)
.ToList();
Console.WriteLine("output\n" + string.Join("\n", query1));
Returns
id = 4, Name = B, Group = GroupB
id = 5, Name = B, Group = GroupB
id = 5, Name = B, Group = GroupB
But what I wanted is the following:
id = 5, Name = B, Group = GroupB
id = 5, Name = B, Group = GroupB
What am I doing wrong?
Group by id and/or Name depending on your needs
.GroupBy(x => new {x.id, x.Name})
Given
var list = new[]
{
new {id = 3, Name = "", Group = "GroupA"},
new {id = 2, Name = "", Group = "GroupA"},
new {id = 3, Name = "", Group = "GroupA"},
new {id = 4, Name = "B", Group = "GroupB"},
new {id = 5, Name = "B", Group = "GroupB"},
new {id = 5, Name = "B", Group = "GroupB"}
};
Usage
var results = list
.Where(s => s.Name != "")
.GroupBy(x => new {x.id, x.Name})
// .GroupBy(x => x.id) <- depending on your needs
.Where(g => g.Count() > 1)
.SelectMany(y => y);
foreach (var result in results)
Console.WriteLine($"{result.id}, {result.Name}, {result.Group}");
Output
5, B, GroupB
5, B, GroupB
You are applying GroupBy to Group property. Instead of Grouping by Group property, use id property,
Input
var list = new[]
{
new {id = 3, Name = "", Group = "GroupA"},
new {id = 2, Name = "", Group = "GroupA"},
new {id = 3, Name = "", Group = "GroupA"},
new {id = 4, Name = "B", Group = "GroupB"},
new {id = 5, Name = "B", Group = "GroupB"},
new {id = 5, Name = "B", Group = "GroupB"}
};
Solution:
var query1 = list.ToList()
.Where(s=>s.Name != "")
.GroupBy(x =>x.id)
//^^^^^ This change you need to do
.Where(g => g.Count() > 1)
.SelectMany(y => y)
.ToList();
Output
5, B, GroupB
5, B, GroupB
Try it online

Data Set to Tree Structure

I have the below set of data
Where each City belongs to a specific Department, which belongs to a specific Region, which belongs to a specific Country (in this case there is only one country: France).
This data is contained in a CSV file which I can read from on a row-by-row basis, however my goal is to convert this data into a tree structure (with France being at the root).
Each of these nodes will be given a specific Id value, which is something I've already gone and done, but the tricky part is that each node here must also contain a ParentId (for instance Belley and Gex need the ParentId of Ain, but Moulins and Vichy need the ParentId of Aller).
Below is a snippet of code I've written that has assigned an Id value to each name in this data set, along with some other values:
int id = 0;
List<CoverageAreaLevel> coverageAreas = GetCoverageAreaDataFromCsv(path, true);
List<LevelList> levelLists = new List<LevelList>
{
new LevelList { Names = coverageAreas.Select(a => a.Level1).Distinct().ToList(), Level = "1" },
new LevelList { Names = coverageAreas.Select(a => a.Level2).Distinct().ToList(), Level = "2" },
new LevelList { Names = coverageAreas.Select(a => a.Level3).Distinct().ToList(), Level = "3" },
new LevelList { Names = coverageAreas.Select(a => a.Level4).Distinct().ToList(), Level = "4" }
};
List<CoverageArea> newCoverageAreas = new List<CoverageArea>();
foreach (LevelList levelList in levelLists)
{
foreach (string name in levelList.Names)
{
CoverageArea coverageArea = new CoverageArea
{
Id = id++.ToString(),
Description = name,
FullDescription = name,
Level = levelList.Level
};
newCoverageAreas.Add(coverageArea);
}
}
The levelLists variable contains a sort-of heirarchical structure of the data that I'm looking for, but none of the items in that list are linked together by anything.
Any idea of how this could be implemented? I can manually figure out each ParentId, but I'd like to automate this process, especially if this needs to be done in the future.
The solution from #Camilo is really good and pragmatic. I would also suggest the use of a tree.
A sample implementation:
var countries = models.GroupBy(xco => xco.Country)
.Select((xco, index) =>
{
var country = new Tree<String>();
country.Value = xco.Key;
country.Children = xco.GroupBy(xr => xr.Region)
.Select((xr, xrIndex) =>
{
var region = new Tree<String>();
region.Value = xr.Key;
region.Parent = country;
region.Children =
xr.GroupBy(xd => xd.Department)
.Select((xd, index) =>
{
var department = new Tree<String>();
department.Value = xd.Key;
department.Parent = region;
department.Children = xd
.Select(xc => new Tree<String> { Value = xc.City, Parent = department });
return department;
});
return region;
});
return country;
});
public class Tree<T>
{
public IEnumerable<Tree<T>> Children;
public T Value;
public Tree<T> Parent;
}
One way you could solve this is by building dictionaries with the names and IDs of each level.
Assuming you have data like this:
var models = new List<Model>
{
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept1", City = "FranceA" },
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept1", City = "FranceB" },
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept2", City = "FranceC" },
new Model { Country = "France", Region = "FranceRegionB", Department = "FranceDept3", City = "FranceD" },
new Model { Country = "Italy", Region = "ItalyRegionA", Department = "ItalyDept1", City = "ItalyA" },
new Model { Country = "Italy", Region = "ItalyRegionA", Department = "ItalyDept2", City = "ItalyB" },
};
You could do something like this, which can probably be improved further if needed:
var countries = models.GroupBy(x => x.Country)
.Select((x, index) => Tuple.Create(x.Key, new { Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var regions = models.GroupBy(x => x.Region)
.Select((x, index) => Tuple.Create(x.Key, new { ParentId = countries[x.First().Country].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var departments = models.GroupBy(x => x.Department)
.Select((x, index) => Tuple.Create(x.Key, new { ParentId = regions[x.First().Region].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var cities = models
.Select((x, index) => Tuple.Create(x.City, new { ParentId = departments[x.Department].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
The main idea is to leverage the index parameter of the Select method and the speed of dictionaries to find the parent ID.
Sample output from a fiddle:
countries:
[France, { Id = 1 }],
[Italy, { Id = 2 }]
regions:
[FranceRegionA, { ParentId = 1, Id = 1 }],
[FranceRegionB, { ParentId = 1, Id = 2 }],
[ItalyRegionA, { ParentId = 2, Id = 3 }]
departments:
[FranceDept1, { ParentId = 1, Id = 1 }],
[FranceDept2, { ParentId = 1, Id = 2 }],
[FranceDept3, { ParentId = 2, Id = 3 }],
[ItalyDept1, { ParentId = 3, Id = 4 }],
[ItalyDept2, { ParentId = 3, Id = 5 }]
cities:
[FranceA, { ParentId = 1, Id = 1 }],
[FranceB, { ParentId = 1, Id = 2 }],
[FranceC, { ParentId = 2, Id = 3 }],
[FranceD, { ParentId = 3, Id = 4 }],
[ItalyA, { ParentId = 4, Id = 5 }],
[ItalyB, { ParentId = 5, Id = 6 }]

Linq query and potential self join/group by

Suffering sadly from brain fade. I have the following scenario:
void Main()
{
List<CaseBase> caseList = new List<UserQuery.CaseBase>();
caseList.Add(new CaseBase() {CaseID = 1, CaseSequence = 1, CaseStatus = 1});
caseList.Add(new CaseBase() {CaseID = 1, CaseSequence = 2, CaseStatus = 2});
caseList.Add(new CaseBase() {CaseID = 2, CaseSequence = 1, CaseStatus = 1});
var cases = caseList.Where(x => new List<int> {2}.Contains(x.CaseStatus));
}
// Define other methods and classes here
public class CaseBase
{
public int CaseID {get;set;}
public int CaseSequence {get;set;}
public int CaseStatus {get;set;}
}
Which returns the expected
CaseID
CaseSequence
CaseStatus
1
2
2
What I want are all cases with the same ID where one of them has a status of 2.
CaseID
CaseSequence
CaseStatus
1
1
1
1
2
2
Which should be simple but I'm struggling for a simple solution.
There are a couple of ways to proceed:
You can combine the cases by CaseID and select the matching groups and then break them apart:
var cases = caseList
.GroupBy(c => c.CaseID)
.Where(cg => cg.Any(c => new List<int> { 2 }.Contains(c.CaseStatus)))
.SelectMany(cg => cg);
You can find the desired CaseIDs and then get all matching cases:
var wantedCaseIDs = caseList
.Where(c => new List<int> { 2 }.Contains(c.CaseStatus))
.Select(c => c.CaseID)
.ToHashSet();
var cases = caseList.Where(c => wantedCaseIDs.Contains(c.CaseID));
Or you might want to do it like this:
void Main()
{
List<CaseBase> caseList = new List<UserQuery.CaseBase>();
caseList.Add(new CaseBase() { CaseID = 1, CaseSequence = 1, CaseStatus = 1 });
caseList.Add(new CaseBase() { CaseID = 1, CaseSequence = 2, CaseStatus = 2 });
caseList.Add(new CaseBase() { CaseID = 2, CaseSequence = 1, CaseStatus = 1 });
var cases = caseList.Where(x => new List<int> { 2 }.Contains(x.CaseStatus))
.Join(caseList,x => x.CaseID,y => y.CaseID,(x,y) => new {x,y})
.Select(z => z.y)
.Dump();
}

Linq how to find max repeat items?

I have a list of object (ProductInfo).
ProductInfo contains an id, name, and an option.
Imagine this sample, i have this
ProductInfo Id => 1, Name => XXX, Option = A
ProductInfo Id => 1, Name => XXX, Option = B
ProductInfo Id => 2, Name => DEB, Option = A
ProductInfo Id => 2, Name => DEB, Option = B
ProductInfo Id => 2, Name => DEB, Option = C
ProductInfo Id => 3, Name => ZZZ, Option = D
....
....
We see we have 2 time the option A AND B for product 1 and 2.
My goal will be to obtain the max repeat item for each product in the list.
i would like to obtain as result this :
Id = 1, Name = XXX = A, count = 2
Id =2, Name = DEB, count = 2
How i can do that ?
thanks for your time
try to do this code:
var list = new List<ProductInfo> {
new ProductInfo { Id = 1, Name = "XXX", Option = "A"},
new ProductInfo { Id = 1, Name = "XXX", Option = "B" },
new ProductInfo { Id = 2, Name = "DEB", Option = "A" },
new ProductInfo { Id = 2, Name = "DEB", Option = "B"},
new ProductInfo { Id = 2, Name = "DEB", Option = "C" },
new ProductInfo { Id = 3, Name = "ZZZ", Option = "D" }
};
var x = from p in list
group p by new { p.Id, p.Name, p.Option } into g
select new
{
Id = g.Key.Id,
Name = g.Key.Name,
Count = list.Count(m => m.Name == g.Key.Name)
};
var t = x.Distinct();
You can use GroupBy on the Name and Id parameter. Sorry read that wrong at first.

Linq Grouping the result of group by query

I have the following exemple :
Model mod1 = new Model { Header = "A", canDelete = true,pax=1 };
Model mod2 = new Model { Header = "B", canDelete = true,pax=1 };
Model mod3 = new Model { Header = "A", canDelete = true,pax=2 };
Model mod4 = new Model { Header = "B", canDelete = false,pax=2 };
Model mod5 = new Model { Header = "A", canDelete = true,pax=3 };
Model mod6 = new Model { Header = "B", canDelete = false,pax=3 };
Model mod7 = new Model { Header = "A", canDelete = false,pax=4 };
Model mod8 = new Model { Header = "B", canDelete = true,pax=4 };
I added these models to a listMod
I want to group first by pax number, so I used :
var resultQuery = listMod.GroupBy(p=>p.pax);
How can I re-group the result of my resultQuery by Header and canDelete ?
The aim is to have 3 groups :
1st group : mod1 and mod2
2nd group : mod3 , mod4 , mod5 and mod6
3rd group : mod7 and mod8
Well, there may be a better way, but this one should work, assuming you have always two items for each pax number.
The "trick" is to concatenate the first and second canDelete / Header pair of items grouped by pax, and to group on that value.
Than the list inside the groups are flattened (using SelectMany)
listMod.GroupBy(m => m.pax)
.Select(m => new
{
valuePair = string.Format("{0}-{1}/{2}-{3}", m.First().canDelete, m.First().Header, m.Last().canDelete, m.Last().Header),
value = m.Select(x => x)
})
.GroupBy(m => m.valuePair)
.Select(g => g.SelectMany(x => x.value))
//.ToList();
if you wanna avoid this bad concatenation, you can also do
var result = listMod
.GroupBy(m => m.pax)
.Select(m => new
{
a1 = m.First().canDelete,
a2 = m.First().Header,
b1 = m.Last().canDelete,
b2 = m.Last().Header,
value = m.Select(x => x)
})
.GroupBy(m => new {m.a1, m.a2, m.b1, m.b2})
.Select(g => g.SelectMany(x => x.value))
//.ToList();

Categories