Build hierarchy from strings C# - 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;
}
}
}

Related

Creating a list of an object which either does or doesn't exist in another different object list - C#

So I am trying to compare two different lists that both contain differently structured objects. One is easily accessible while the other is very nested in arrays, but sadly, these are responses from API calls so it's not a structure I can change to make them easier to compare. I want to have a list of the complete structures of items found:
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
The way the objects are structured are as follows:
public class ObjectsA
{
public Structure1[] Structure1 {get; set;}
}
public class Structure1
{
public int id {get; set;} //1
}
And the other object looks like:
public class ObjectsB
{
public Array1[] array1{get; set}
}
public class Array1
{
public Array2[] array2{get; set;}
}
public class Array2
{
public string id {get; set;} //"0001"
}
In a list, I added all the objects that came back from the API call, so ObjectAList contains technically just 1 deserialized object response, which contains an array of objects, while ObjectBList contains a list of objects added to it via AddRange.
At first I tried to putting an Array.Exists() inside of 2 foreach() statements.
foreach (var arr1 in ObjectsBList){
foreach (var arr2 in a.Array2){
if (Array.Exists(ObjectAList.Structure1, item => item.id == Convert.ToInt32(arr2.id)) == true){
foundList.AddRange(ObjectAList.Structure1);
}
else{
notFoundList.AddRange(ObjectAList.Structure1)
}}};
This code seemed to keep looping on the "item => item.id == Convert.ToInt32(arr2.id)" part of it, so consequently, it kept going till it found its match and so the answer was always 'true', therefore just putting everything in the foundList. I know I'm probably going at this wrong. I'm just starting out C# programming and I'm having trouble wrapping my mind around some of these things and knowing what all functions exists to help with what I need, etc. Any help would be great!
You can use linq for querying the in-memory objects.
pseudo code
public class Test {
public void T()
{
var ObjectsBList = new ObjectsB();
var ObjectAList = new ObjectsA();
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
var bList = ObjectsBList
.array1
.SelectMany(x => x.array2)
.Select(x => Convert.ToInt32(x.id))
.Distinct()
.ToList();
if (ObjectAList.Structure1.Any(x => bList.Contains(x.id)))
{
foundList.AddRange(ObjectAList.Structure1);
}
else
{
notFoundList.AddRange(ObjectAList.Structure1);
}
}
}
More simplified version:
Introduce an ID list property
using System;
using System.Text;
using System.Collections.Generic;
using System.Collections;
public class ObjectsA
{
public Structure1[] Structure1 { get; set; }
public List<int> IDs
{
get
{
return Structure1.Select(x => x.id).Distinct().ToList();
}
}
}
public class Structure1
{
public int id { get; set; } //1
}
public class ObjectsB
{
public Array1[] array1 { get; set; }
public List<int> IDs
{
get
{
return array1
.SelectMany(x => x.array2)
.Select(x => Convert.ToInt32(x.id))
.Distinct()
.ToList();
}
}
}
public class Array1
{
public Array2[] array2 { get; set; }
}
public class Array2
{
public string id { get; set; } //"0001"
}
public class Test
{
public void T()
{
var ObjectsBList = new ObjectsB();
var ObjectAList = new ObjectsA();
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
if (ObjectAList.IDs.Any(x => ObjectsBList.IDs.Contains(x)))
{
foundList.AddRange(ObjectAList.Structure1);
}
else
{
notFoundList.AddRange(ObjectAList.Structure1);
}
}
}
var objA = new ObjectsA();
var objB = new ObjectsB();
var objAIds = objA.Structure1.Select(x => x.Id).Distinct();
var objBIds = objB.Array1.SelectMany(x => x.Array2).Select(x => int.Parse(x.Id)).Distinct();
var foundInBothList = objAIds.Intersect(objBIds);
var notFoundinBList = objAIds.Except(objBIds);
var inBoth = objA.Structure1.Where(x => foundInBothList.Contains(x.Id));
var notInB = objA.Structure1.Where(x => notFoundinBList.Contains(x.Id));
Starting from .NET 6
var objBIds = objB.Array1.SelectMany(x => x.Array2).Select(x => int.Parse(x.Id)).Distinct();
var foundList = objA.Structure1.IntersectBy(objBIds, x => x.Id);
var notFoundList = objA.Structure1.ExceptBy(objBIds, x => x.Id);

Linq issues for a complex request

I have a list of users and each users have list of hashtags.
In my app I provide a way on searching results by hashtags.
In my api I split the search string to get each hashtags separately and what I try to have is:
The user entity, the number of hashtags in common and the hashtags related.
For example:
My classes are as below:
public class UserDto
{
public List<HashtagDto> Hashtags { get; set; }
}
public class HashtagDto
{
string name { get; set; }
}
public class UserHashtagSearchResultDto
{
public UserDto UserFk { get; set; }
public int CountResults { get; set; }
public List<HashtagDto> HashtagsFk { get; set; }
}
My query:
if (searchText.IsNullOrEmpty())
return null;
var splits = searchText.Split(new string[] { " " }, System.StringSplitOptions.RemoveEmptyEntries);
List<string> hashes = HashtagHelper.ToHashkeys(splits);
if (hashes != null && hashes.Count() > 0)
{
User.GetUsers().Include(h => h.Hashtags).Where(s => hashes.Contains(....).Select((hs) =>
{
return new UserHashtagSearchResultDto
{
}
});
}
Input will be: running swimming
Output will be:
List
Example:
Result1
{
UserFk=User1
CountResults=2 (the two hashtags of this user exists in the hashtag repository)
HashtagsFk=List<Hashtag>{hashtag1, hashtag2}; // (swimming and running}
}
Result1
{
UserFk=User2
CountResults=1 (only the hashtag swimming existsin the hashtag repository)
HashtagsFk=List<Hashtag>{hashtag1}; // (swimming)
}
I don't see how to do it by linq.
Ok this is an answer to my issue.
var userCommon = _userRepository.GetAll().Include(h => h.HashtagUsers).ThenInclude(h => h.HashtagFk)
.Where(s => hashes.Intersect(s.HashtagUsers.Select(h => h.HashtagFk.Code).AsEnumerable()).Count() > 0);
userCommon.ForEach(res =>
{
var result = new HashtagResultSearchDto();
result.UserFk = ObjectMapper.Map<UserDto>(res);
var d = res.HashtagUsers.AsQueryable().Select(h => h.HashtagFk);
result.CountResult = result.Hashtags.Count();
result.UserId = res.Id;
hashtagResultSearchDtos.Add(result);
});

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

Nested List Order In C#

I have a custom list called ServiceFormFields and it has a List property named ChildrenTables.
I want to order descending by ChildrenTables' names
var fields = activeForm.ServiceFormFields.OrderBy (o =>
o.ChildrenTables.OrderBy(c=>c)).ToList();
but it does not work.
I want to order ServiceFormFields list according to its children. Maybe I should do with GroupBy..
So, for example...
ServiceFormFields has FieldName property.. and Children is a List of String
FieldName = Matter, Children = Version
FieldName = Client, Children = Matter
FieldName = Status, Children = Null
FieldName = Version, Children = Null (but has parents, it is Matter)
so and I want to order like:
2,1,3,4
because Client is the on highest level, second is Matter, third one is Version because Matter is its parent, and final is Status, because it does not have any dependency.
Edit: This is structure of the class
public class ServiceForm
{
public List<ServiceFormField> ServiceFormFields { get; set; }
public string Id { get; set; }
public bool IsDefaultPrimary { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Type { get; set; }
}
public class ServiceFormField
{
public FormProperty FormField {get;set;}
public bool IsVisible { get;set;}
public List<string> ParentTables { get; set; }
public bool HasChildren { get; set; }
public List<string> ChildrenTables { get; set; }
}
public partial class FormProperty
{
private string NameField;
private string SQLInfoField;
...
}
NameField contains Client, Matter, Version..
I don't know if this is what you want, but here's a solution with the least bits of code needed.
I just transformed all the Childrens into a single string, and then the order by compares against them.
This is certainly not the most performance-wise solution, if this matters.
here's a sample:
static void Main(string[] args)
{
ServiceForm sf = new ServiceForm();
sf.ServiceFormFields = new List<ServiceFormField>
{
new ServiceFormField { ChildrenTables = new List<string> { "a", "b", "c"}},
new ServiceFormField { ChildrenTables = new List<string> { "tra la la", "xxx"}},
new ServiceFormField { ChildrenTables = new List<string> { "TTTTT" }},
new ServiceFormField { ChildrenTables = new List<string> { "123455", "8157125", "1763123"}},
new ServiceFormField { ChildrenTables = new List<string> { " ", " ", " ", " "}}
};
var ordered= sf.ServiceFormFields.OrderByDescending(f => string.Join(",", f.ChildrenTables)).ToList();
foreach(ServiceFormField sff in ordered)
{
foreach(string s in sff.ChildrenTables)
{
Console.WriteLine(s);
}
}
}
Output:
There are two ways, first you can use the List.Sort function with the signature Comparison like:
activeForm.ServiceFormFields.Sort(new Comparison<ServiceFormField>((e1,e2)=>Compare(e1, e2)));
private static int Compare(ServiceFormField e1, ServiceFormField e2)
{
//Do your logic here
}
in this case you will have an inplace sort.
or use the linq orderby function with the signature to KeySelector, an IComparer and implement an IComparer like that
var result = activeForm.ServiceFormFields.OrderBy(e => e, new ServiceFormFieldComparer());
public class ServiceFormFieldComparer : IComparer<ServiceFormField>
{
private int Compare(ServiceFormField e1, ServiceFormField e2)
{
//Your logic here
}
}
in this case you will have an ordered list returned to you

LINQ - GroupBy and project to a new type?

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

Categories