linq query to create new objects - c#

assumed i have a class hierarchy where basically every class consists of a property item (like string) and a property list item of the nested class:
public class Master
{
public String myName;
public List<Detail> myDetails;
}
public class Detail
{
public String myDetailDescription;
public List<DetailParts> parts;
}
public class DetailParts
{
public int PartNumber;
public String PartDescription;
}
Now i would like to have a linq query that gives me
an list of objects that consists of these elements
public class Result
{
public String myName; // from Master
public String myDetailDescription; // from Detail
public int PartNumber; // from DetailParts
}
It is a silly question, I know, but i cant find useful links...
[Edit #1]: Better code sample:
public class Master
{
public String myName;
public List<Detail> myDetails = new List<Detail>();
}
public class Detail
{
public String myDetailDescription;
public List<DetailParts> parts = new List<DetailParts>();
}
public class DetailParts
{
public int PartNumber;
public String PartDescription;
}
// List of Masters
List<Master> master = new List<Master>();
// Two detail parts for the Details
DetailParts dp1 = new DetailParts { PartDescription = "dp1" };
DetailParts dp2 = new DetailParts { PartDescription = "dp2" };
// once again two details for the Master
Detail d1 = new Detail { myDetailDescription = "d1" };
Detail d2 = new Detail{ myDetailDescription = "d2"};
// Assign the Parts
d1.parts.Add(dp1);
d1.parts.Add(dp2);
d2.parts.Add(dp1);
d2.parts.Add(dp2);
Master m1 = new Master { myName = "m1" };
Master m2 = new Master { myName = "m2" };
m1.myDetails.Add(d1);
m1.myDetails.Add(d2);
m2.myDetails.Add(d1);
m2.myDetails.Add(d2);
master.Add(m1);
master.Add(m2);
// given a value for `myName` and `myDetailDescription` i
// would be able to get a list with the `DetailParts` objects:
var t = master.Where(a => a.myName == "m2")
.Select(a => a.myDetails).FirstOrDefault()
.Where(b => b.myDetailDescription == "d1")
.Select(c => c.parts).FirstOrDefault();

You are looking for SelectMany:
List<Result> results = masterList
.SelectMany(m => m.myDetails
.SelectMany(d => d.parts
.Select(dp => new Result
{
PartNumber = dp.PartNumber,
myDetailDescription = d.myDetailDescription,
myName = m.myName
})))
.ToList();
A more readable version with LINQ's query syntax:
var resultQuery = from m in masterList
from d in m.myDetails
from dp in d.parts
select new Result
{
PartNumber = dp.PartNumber,
myDetailDescription = d.myDetailDescription,
myName = m.myName
};
List<Result> results = resultQuery.ToList();

Related

Find two values in hierarchical object tree C# using Linq

I have the following structures:
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var tourn = new Tournament();
var player = new Player() { Type = PlayerType.User };
var seat1 = new Seat() { Number = 1, Player = player } ;
tourn.Tables = new List<Table>() { new Table(){ Seats = new List<Seat>(){seat1} } };
//Console.WriteLine(tourn.Tables.Where((k=> k.Tables.Any(m=> m.Seats.Any(j=> j.Player == Player.User)))).Count());
// Get Table and Seat numbers for PlayerType.User
}
public class Seat{
public Player? Player {get;set;}
public int Number {get;set;}
}
public enum PlayerType {
User,
Bot
}
public class Tournament{
public List<Table> Tables {get;set;}
}
public class Table
{
public List<Seat> Seats {get;set;}
public int Number {get;set;}
}
public class Player
{
public PlayerType Type { get; set; }
}
}
I have want the table and seat number where the playertype = user
Is it possible to build a Linq query to do this in one statement?
(I have looked at many examples and can't seem to get it right)
Tried this:
var playerLocation = tables
.Select(seat => new
{
TableNumber = seat.TableNumber,
TableSeat = seat.Seats
.Where(s => s.Player is not null &&
s.Player.Type == PlayerType.User)
.Select(st => new
{
SeatNumber = st.Number
})
});
Fiddle (.NET Core): https://dotnetfiddle.net/CMjZs6
I cheated...
PlayerLocation location = new PlayerLocation();
foreach (Table table in tables)
foreach (Seat seat in table.Seats)
if (seat.Player is not null && seat.Player.Type == PlayerType.User)
{
location.TableNumber = table.TableNumber;
location.SeatNumber = seat.Number;
break;
}
Assuming you want the answer as an object with the table number and a list of seat numbers, it is straightforward:
var ans = tourn.Tables.Select(t => new {
t.Number,
Seats = t.Seats.Where(s => s.Player?.Type == PlayerType.User)
.Select(s => s.Number)
.ToList()
});
Based On your Updated Fiddle
var anonyType = tourn.Tables.Where(k=> k.Seats.Any(j=> j.Player?.Type == PlayerType.User)).Select(k=> new { TableNumber = k.Number, SeatNumbers = k.Seats.Select(j=> j.Number) }).FirstOrDefault();
Console.WriteLine(anonyType.TableNumber);
Console.WriteLine(String.Join(" , ", anonyType.SeatNumbers));

Best approach to compare if one list is subset of another in C#

I have the below two classes:
public class FirstInner
{
public int Id { get; set; }
public string Type { get; set; }
public string RoleId { get; set; }
}
public class SecondInner
{
public int Id { get; set; }
public string Type { get; set; }
}
Again, there are lists of those types inside the below two classes:
public class FirstOuter
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<FirstInner> Inners { get; set; }
}
public class SecondOuter
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondInner> Inners { get; set; }
}
Now, I have list of FirstOuter and SecondOuter. I need to check if FirstOuter list is a subset of SecondOuter list.
Please note:
The names of the classes cannot be changed as they are from different systems.
Some additional properties are present in FirstOuter but not in SecondOuter. When comparing subset, we can ignore their presence in SecondOuter.
No.2 is true for FirstInner and SecondInner as well.
List items can be in any order---FirstOuterList[1] could be found in SecondOuterList[3], based on Id, but inside that again need to compare that FirstOuterList[1].FirstInner[3], could be found in SecondOuterList[3].SecondInner[2], based on Id.
I tried Intersect, but that is failing as the property names are mismatching. Another solution I have is doing the crude for each iteration, which I want to avoid.
Should I convert the SecondOuter list to FirstOuter list, ignoring the additional properties?
Basically, here is a test data:
var firstInnerList = new List<FirstInner>();
firstInnerList.Add(new FirstInner
{
Id = 1,
Type = "xx",
RoleId = "5"
});
var secondInnerList = new List<SecondInner>();
secondInner.Add(new SecondInner
{
Id = 1,
Type = "xx"
});
var firstOuter = new FirstOuter
{
Id = 1,
Name = "John",
Title = "Cena",
Inners = firstInnerList
}
var secondOuter = new SecondOuter
{
Id = 1,
Name = "John",
Inners = secondInnerList,
}
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
Need to check if firstOuterList is part of secondOuterList (ignoring the additional properties).
So the foreach way that I have is:
foreach (var item in firstOuterList)
{
var secondItem = secondOuterList.Find(so => so.Id == item.Id);
//if secondItem is null->throw exception
if (item.Name == secondItem.Name)
{
foreach (var firstInnerItem in item.Inners)
{
var secondInnerItem = secondItem.Inners.Find(sI => sI.Id == firstInnerItem.Id);
//if secondInnerItem is null,throw exception
if (firstInnerItem.Type != secondInnerItem.Type)
{
//throw exception
}
}
}
else
{
//throw exception
}
}
//move with normal flow
Please let me know if there is any better approach.
First, do the join of firstOuterList and secondOuterList
bool isSubset = false;
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
var jointOuterList = firstOuterList.Join(
secondOuterList,
p => new { p.Id, p.Name },
m => new { m.Id, m.Name },
(p, m) => new { FOuterList = p, SOuterList = m }
);
if(jointOuterList.Count != firstOuterList.Count)
{
isSubset = false;
return;
}
foreach(var item in jointOuterList)
{
var jointInnerList = item.firstInnerList.Join(
item.firstInnerList,
p => new { p.Id, p.Type },
m => new { m.Id, m.type },
(p, m) => p.Id
);
if(jointInnerList.Count != item.firstInnerList.Count)
{
isSubset = false;
return;
}
}
Note: I am assuming Id is unique in its outer lists. It means there will not be multiple entries with same id in a list. If no, then we need to use group by in above query
I think to break the question down..
We have two sets of Ids, the Inners and the Outers.
We have two instances of those sets, the Firsts and the Seconds.
We want Second's inner Ids to be a subset of First's inner Ids.
We want Second's outer Ids to be a subset of First's outer Ids.
If that's the case, these are a couple of working test cases:
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsTrue(isInnerSubset);
Assert.IsTrue(isOuterSubset);
}
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreNotSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
firstInnerIds.Clear();
firstInnerIds.Add(5);
firstOuterIds.Clear();
firstOuterIds.Add(5);
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsFalse(isInnerSubset);
Assert.IsFalse(isOuterSubset);
}
private List<FirstOuter> GetFirstOuterList() { ... }
private List<SecondOuter> GetSecondOuterList() { ... }

Converting a list of options to a JavaScript arrary in C#

I need to get a JSON string that looks like the following:
{"1":[{"value":"1", "text":"Basketball"}, {"value":"2", "text":"Tennis"}, {"value":"3", "text":"Football"}],"3":[{"value":"4", "text":"futbol"}]}
The C# code responsible for building this looks like the following:
var sportsEntries = new Dictionary<byte, List<KeyValuePair<int, string>>>();
foreach (var department in Departments)
{
var deptOptions = SportList
.Where(x => x.DeptId == department.DeptId)
.ToDictionary(x => x.SportId, x => x.SportNameName).ToList();
sportsEntries .Add(department.DeptId, deptOptions);
}
var json = JsonConvert.SerializeObject(sportsEntries);
Unfortunately, this approach generates the wrong JSON. The JSON looks like this:
{"1":[{"Key":1,"Value":"Basketball"},{"Key":2,"Value":"Tennis"},{"Key":3,"Value":"Football"}],"3":[{"Key":4, "Value":"Futbol"}]}
I feel like I'm so close. Yet, I'm not sure how to update my C# code to make the resulting JSON look like the format I need. How do I update the C# to output the correct JSON?
Thank you!
You could use something like this:
var sportsEntries = new Dictionary<byte, List<object>();
foreach (var department in Departments)
{
var deptOptions = SportList
.Where(x => x.DeptId == department.DeptId)
.Select(x => new { value = x.SportId, text = x.SportNameName}).ToList();
sportsEntries .Add(department.DeptId, deptOptions);
}
var json = JsonConvert.SerializeObject(sportsEntries);
This solution replaces the initial KeyValuePair<int, string> with object and creates a list of anonymous objects, having the desired properties.
This works:
[TestFixture]
public class SoTest
{
[Test]
public void Test1()
{
var departments = new List<Department>
{
new Department
{
DeptId = 1
}
};
var sportList = new List<Sport>
{
new Sport
{
DeptId = 1,
SportId = 1,
SportName = "Basketball"
},
new Sport
{
DeptId = 1,
SportId = 2,
SportName = "Tennis"
}
};
var sportsEntries = new Dictionary<byte, List<Kvp>>();
foreach (var department in departments)
{
var deptOptions = sportList
.Where(x => x.DeptId == department.DeptId)
.Select(x => new Kvp { Value = x.SportId, Text = x.SportName }).ToList();
sportsEntries.Add(department.DeptId, deptOptions);
}
string json = JsonConvert.SerializeObject(sportsEntries);
Assert.IsNotNullOrEmpty(json);
Debug.Print(json);
}
}
public class Department
{
public byte DeptId { get; set; }
}
public class Sport
{
public byte DeptId { get; set; }
public int SportId { get; set; }
public string SportName { get; set; }
}
[DataContract]
public class Kvp
{
[DataMember(Name = "value")]
public int Value { get; set; }
[DataMember(Name = "text")]
public string Text { get; set; }
}

Can I put a conditional check on a string to see if it ends in "00" inside a LINQ expression?

I am trying to populate an Objective and ObjectiveDetail objects. Here are the classes I have:
partial class Objective
{
public Objective() {
this.ObjectiveDetails = new List<ObjectiveDetail>();
}
public int ObjectiveId { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
}
public partial class ObjectiveDetail
{
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public string Text { get; set; }
public virtual Objective Objective { get; set; }
}
I'm currently populating the only the Objective object from this call:
var objectiveData = GetContent.GetType5();
var objectives = objectiveData.Select(o => new Objective {
Name = o.Name,
Text = o.Text}
);
The data looks like this:
Name Text
0600 header 1
0601 detail abc
0602 detail def
0603 detail ghi
0700 header 2
0701 detail xyz
Is there a way I could modify my LINQ so that only the data where the name field contents end in "00" goes into the Objective object (as it does now) and when the data where the name field contents end in "01" then it creates a new ObjectiveDetail object with "detail abc" etc going into the text field.
This is a picture of what the end result should look like:
A collection of Objectives
new Objective { name = "header 1",
ObjectiveDetails = A collection of ObjectiveDetails
name = "detail abc"
name = "detail def" etc.
Sure you can do that, using [string.EndsWith] method like:1
.Where(r=> r.Name.EndsWith("00"))
Modify your query as:
var objectives = objectiveData
.Where(r => r.Name.EndsWith("00"))
.Select(o => new Objective {
Name = o.Name,
Text = o.Text}
);
It's somewhat unclear what you are asking, but you can put complex logic inside the Select() if you need to:
var objectives = objectiveData.Select(o =>
{
var result = new Objective
{
Name = o.Name,
Text = o.Text
};
if (o.Name != null && o.Name.EndsWith("01"))
{
result.ObjectiveDetails.Add
(
new ObjectiveDetail
{
ObjectiveDetailId = o.ObjectiveId,
Name = o.Name,
Text = o.Text,
Objective = result
}
);
}
return result;
});
(Note that I'm guessing at what you need; you will need to correct the logic to do what you really want.)
Looks like you want to do some sort of conditional mapping. I like Matthew Watson's answer, but it's a bit unclear why he's always creating an Objective instance every time. Here's some LINQ-less code which I believe is more readable, and maps the way I think you'd want:
public class Mapper
{
public List<Objective> Objectives = new List<Objective>();
public class Objective
{
public int ObjectiveId { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
public Objective()
{
ObjectiveDetails = new List<ObjectiveDetail>();
}
}
public class ObjectiveDetail
{
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public string Text { get; set; }
public virtual Objective Objective { get; set; }
}
public void Assign()
{
var objectiveData = new[] // Hard-coded test data. We don't know what the type of each item in this list is, so I use an anonymous type
{
new {Name = "0600", Text = "Header 06"},
new {Name = "0601", Text = "06 Detail 01"},
new {Name = "0602", Text = "06 Detail 02"},
new {Name = "0603", Text = "06 Detail 03"},
new {Name = "0700", Text = "Header 07"},
new {Name = "0701", Text = "07 Detail 01"},
new {Name = "0702", Text = "07 Detail 02"}
};
// Create Objectives first
var id = 1;
foreach (var item in objectiveData.Where(i => i.Name.EndsWith("00")))
{
Objectives.Add(new Objective { ObjectiveId = id, Name = item.Name, Text = item.Text });
id++;
}
// Create ObjectiveDetails
id = 1;
foreach (var item in objectiveData.Where(i => !i.Name.EndsWith("00")))
{
var itemLocal = item;
var matchingObjective = Objectives.FirstOrDefault(o => o.Name.StartsWith(itemLocal.Name.Substring(0, 2)));
var objectiveDetail = new ObjectiveDetail
{
ObjectiveDetailId = id,
Text = item.Text,
ObjectiveId = matchingObjective != null ? matchingObjective.ObjectiveId : 0,
Objective = matchingObjective
};
if (matchingObjective != null)
{
matchingObjective.ObjectiveDetails.Add(objectiveDetail);
}
id++;
}
// At the end of this method you should have a list of Objectives, each with their ObjectiveDetails children
}
}
Output:
Hope this helps.

Build hierarchy from strings C#

I have a collection of strings:
"Alberton;Johannesburg"
"Allendale;Phoenix"
"Brackenhurst;Alberton"
"Cape Town;"
"Durban;"
"Johannesburg;"
"Mayville;Durban"
"Phoenix;Durban"
"Sandton;Johannesburg"
that I want to structure into a hierarchical structure in the fastest possible manner, like:
Johannesburg
Alberton
Brackenhurst
Sandton
Cape Town
Durban
Phoenix
Allandale
Mayville
Currently I have nested for loops and checks, but was hoping I could achieve this with a single LAMBDA query?
The above mentioned strings are in a List.
I prepared lambda-like solution, but you should really think if it's more readable/efficient then your current one:
Helper Extension Method:
public static class ChildrenGroupExtensions
{
public static List<CityInfo> GetChildren(this IEnumerable<IGrouping<string, City>> source, string parentName)
{
var cities = source.SingleOrDefault(g => g.Key == parentName);
if (cities == null)
return new List<CityInfo>();
return cities.Select(c => new CityInfo { Name = c.Name, Children = source.GetChildren(c.Name) }).ToList();
}
}
Helper Classes:
public class City
{
public string Name { get; set; }
public string Parent { get; set; }
}
public class CityInfo
{
public string Name { get; set; }
public List<CityInfo> Children { get; set; }
}
Usage:
var groups = (from i in items
let s = i.Split(new[] { ';' })
select new City { Name = s[0], Parent = s[1] }).GroupBy(e => e.Parent);
var root = groups.GetChildren(string.Empty);
Where items is your List<string>
You can look the results with simple helper method like that one:
private static void PrintTree(List<CityInfo> source, int level)
{
if (source != null)
{
source.ForEach(c =>
{
Enumerable.Range(1, level).ToList().ForEach(i => Console.Write("\t"));
Console.WriteLine(c.Name);
PrintTree(c.Children, level + 1);
});
}
}
And the results are:
Cape Town
Durban
Mayville
Phoenix
Allendale
Johannesburg
Alberton
Brackenhurst
Sandton
You haven't specified any specific data structure so I just used a class called Area with a list of children of itself. Also, it's in 2 lines of linq. There is also no check to see if an area is a child of 2 separate parents as the code is. Here's the code for the test I used(Relevant lines in-between the equals comments):
[TestFixture]
public class CitiesTest
{
[Test]
public void Test()
{
var strings = new List<string>
{
"Alberton;Johannesburg",
"Allendale;Phoenix",
"Brackenhurst;Alberton",
"Cape Town;",
"Durban;",
"Johannesburg;",
"Mayville;Durban",
"Phoenix;Durban",
"Sandton;Johannesburg"
};
//===================================================
var allAreas = strings.SelectMany(x=>x.Split(';')).Where(x=>!string.IsNullOrWhiteSpace(x)).Distinct().ToDictionary(x=>x, x=>new Area{Name = x});
strings.ForEach(area =>
{
var areas = area.Split(';');
if (string.IsNullOrWhiteSpace(areas[1]))
return;
var childArea = allAreas[areas[0]];
if (!allAreas[areas[1]].Children.Contains(childArea))
allAreas[areas[1]].Children.Add(childArea);
childArea.IsParent = false;
});
var result = allAreas.Select(x=>x.Value).Where(x => x.IsParent);
//===================================================
}
public class Area
{
public string Name;
public bool IsParent;
public List<Area> Children { get; set; }
public Area()
{
Children = new List<Area>();
IsParent = true;
}
}
}

Categories