LINQ - GroupBy and project to a new type? - c#

I have a list of items, i.e, List<SearchFilter>, and this is the SearchFilter object:
public class SearchFilter
{
public int ItemID { get { return ValueInt("ItemID"); } }
public string ItemName { get { return ValueString("ItemName"); } }
public string Type { get { return ValueString("Type"); } }
}
How do I group by the Type, and project the grouped item into a new list of GroupedFilter, i.e:
public class Filter
{
public int ItemID { get; set; }
public string ItemName { get; set; }
}
public class GroupedFilter
{
public int Type { get; set; }
public List<Filter> Filters { get; set; }
}
Thanks.

var result = items.GroupBy(
sf => sf.Type,
sf => new Filter() { ItemID = sf.ItemID, ItemName = sf.ItemName },
(t, f) => new GroupedFilter() { Type = t, Filters = new List<Filter>(f) });
But you need to make sure your GroupedFilter.Type property is a string to match your SearchFilter.Type property.

With Linq query syntax it is longer and more complex but just for reference:
var grpFilters = (from itm in list group itm by itm.Type into grp select
new GroupedFilter
{
Type = grp.Key,
Filters = grp.Select(g => new Filter
{
ItemID = g.ItemID,
ItemName = g.ItemName
}).ToList()
}).ToList();
Somebody may find it more readable because they don't know all the possible parameters to GroupBy().

Related

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() { ... }

How can i fill a class prperty form another class?

I have class where i have the collection of FreeDressingItems, FreeToppingItems, FreeInstructionItems
that is like this each of which fill selectedCustomization i have another property in this class public string Items { get { return GetAllItems(); } }
that i want to fill so that it keeps the all catetoryname for the same category type so that i can bind it to grid easily and display all its value in comma separated form.
i have following code could somebody help me how can i acineve this.
public class selectedCustomization
{
public CategoryType TypeName { get; set; }
public string CategoryName { get; set; }
public string ItemName { get; set; }
public int SourceID { get; set; }
public string Items { get { return GetAllItems(); } }
private string GetAllItems()
{
switch (TypeName)
{
case CategoryType.Dressing:
{
cFreeCustomization cfreeCust = new cFreeCustomization();
break;
}
case CategoryType.Topping:
break;
case CategoryType.SpecialInstruction:
break;
}
}
}
this is another class cFreeCustomization
public List<selectedCustomization> SelectedItems
{
get
{
libDBDataContext cn = new libDBDataContext();
List<selectedCustomization> lst = new List<selectedCustomization>();
lst.AddRange(
(from xx in this.FreeDressingItems
select new selectedCustomization() { TypeName = CategoryType.Dressing, CategoryName = xx.DressingInfo.CatName, ItemName = xx.DressingInfo.Description }
).ToList()
);
lst.AddRange(
(from xx in this.FreeToppingItems
select new selectedCustomization() { TypeName = CategoryType.Topping, CategoryName = xx.ToppingInfo.CatName, ItemName = xx.ToppingInfo.Description }
).ToList()
);
lst.AddRange(
(from xx in this.FreeInstructionItems
select new selectedCustomization() { TypeName = CategoryType.SpecialInstruction, CategoryName = xx.InstructionInfo.CatName, ItemName = xx.InstructionInfo.Description }
).ToList()
);
return lst;
}
}
How can i make tiems of selectedCustomization in comma separated form?
I believe the method GetAllItems should be like below:
private string GetAllItems()
{
cFreeCustomization cfreeCust = new cFreeCustomization();
var ls = cfreeCust.SelectedItems.FindAll(I => I.TypeName == this.TypeName);
return string.Join(",", ls.Select(I => I.CategoryName).ToArray());
}
This will fix your issue.

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;
}
}
}

Grouping / Multiple grouping with LINQ

I'm trying to output some data, grouped, then group it again.
Here's some example data (from db)
I'm trying to output this, grouped like so:
Best Type Name
- Discipline name
-- Result
-- Result
-- Result
CompetitorBest class looks like:
public class CompetitorBest
{
public int ResultId { get; set; }
public string BestTypeName { get; set; }
public int BestTypeOrder { get; set; }
public string DisciplineName { get; set; }
public string ResultValue { get; set; }
public string Venue { get; set; }
public DateTime ResultDate { get; set; }
}
Currently, I've got the following
var bestsGroups = from b in Model.CompetitorBests
group b by new { b.BestTypeName, b.BestTypeOrder }
into grp
orderby grp.Key.BestTypeOrder
select new
{
BestType = grp.Key.BestTypeName,
Results = grp.ToList()
};
But this obviously doesn't take into account the grouping by DisciplineName.
My output code is something like:
foreach (var bestsGroup in bestsGroups)
{
<h2>#bestsGroup.BestType</h2>
foreach (var result in bestsGroup.Results)
{
//i am guessing here i'll need another foreach on the discipline group....
<p>#result.DisciplineName</p>
<p>#result.ResultId </p>
}
}
I think this is what you are looking for:
from b in Model.CompetitorBests
group b by new { b.BestTypeName, b.BestTypeOrder } into grp
orderby grp.Key.BestTypeOrder
select new
{
BestType = grp.Key.BestTypeName,
Results = from d in grp
group d by d.DisciplineName into grp2
select new
{
DisciplineName = grp2.Key,
Results = grp2
}
};
Edit:
Iterate over it like this:
foreach (var bestsGroup in bestsGroups)
{
<h2>#bestsGroup.BestType</h2>
foreach (var discipline in bestsGroup.Results)
{
<p>#discipline.DisciplineName</p>
foreach (var result in discipline.Results)
{
<p>#result.ResultId</p>
}
}
}

Linq to Objects where clause and typecasting

I have the following:
class BaseType {
public Int32 Id { get; set; }
}
class Option : BaseType {
public String DisplayName { get; set; }
public String StoredValue { get; set; }
}
class Container {
public Collection<BaseType> Options;
}
Container c = new Container();
c.Options.add(new Option() { Id=1, DisplayName="Bob", StoredValue="aaaa"});
c.Options.add(new Option() { Id=2, DisplayName="Dora", StoredValue="bbbb"});
c.Options.add(new Option() { Id=3, DisplayName="Sara", StoredValue="cccc"});
Now, what I want to do is pull out the DisplayName of the specific option that matches StoredValue.
Previously, I'd iterate over the entire collection until I found a match. But, I'd rather have something that looked a bit better...
I got started with
var found = (from c in c.Options
where ...
And that's where I'm stuck.
I think this is what you want: (Single will error if 0 or more than 1 match is found)
string searchValue = "aaaa";
string displayName = c.Options.OfType<Option>.Single(o => o.StoredValue == searchValue).DisplayName;
Or to allow for multiple values: (this will give you all the display names that match, 0 to many)
IEnumerable<string> displayNames = from o in c.Options.OfType<Option>
where o.StoredValue == searchValue
select o.DisplayName;
This should do it:
c.Options.OfType<Option>()
.Where(o => o.StoredValue == "aaaa")
.Select(o => o.DisplayName)
.SingleOrDefault(); //or .ToList()
var found = (from c in c.Options.OfType<Option>()
where c.StoredValue == yourValue
select c.DisplayName).FirstOrDefault();
This is using Linqpad. You need to cast as the Option type first, then you can use it. I put in a check to find all of that type then check for the value.
void Main()
{
Container c = new Container();
c.Options.Add(new Option() { Id=1, DisplayName="Bob", StoredValue="aaaa"});
c.Options.Add(new Option() { Id=2, DisplayName="Dora", StoredValue="bbbb"});
c.Options.Add(new Option() { Id=3, DisplayName="Sara", StoredValue="cccc"});
var t = from x in c.Options.OfType<Option>()
where x.DisplayName == "Bob"
select x.StoredValue;
t.Dump();
}
class BaseType {
public Int32 Id { get; set; }
}
class Option : BaseType {
public String DisplayName { get; set; }
public String StoredValue { get; set; }
}
class Container {
public List<BaseType> Options;
public Container() { Options = new List<BaseType>(); }
}

Categories