Synchronize 2 lists - c#

What is the best fastest way to Synchronize 2 Lists?
public class UserGroup
{
public UserGroup(string group, string user)
{
this.Group = group;
this.User = user;
}
public string Group { get; set; }
public string User { get; set; }
}
IList<UserGroup> userGroup1 = new IList<UserGroup>();
IList<UserGroup> userGroup2 = new IList<UserGroup>();
Each group has different number of members.
How can i find out the different and merge both in one new list?
PS: I can change the type from IList to whatever if it would be more efficient.
Thanks

So first we need an effective way of comparing these objects. Since the default Equals and GetHashCode implementations won't be useful in your context you either need to override them, or create an IEqualityComparer. I did the latter, you can feel free to do the former if you want. Here's a simple comparer:
public class UserGroupComparer : IEqualityComparer<UserGroup>
{
public bool Equals(UserGroup x, UserGroup y)
{
return x.Group == y.Group && x.User == y.User;
}
public int GetHashCode(UserGroup obj)
{
return 37 * obj.Group.GetHashCode() + 19 * obj.User.GetHashCode();
}
}
Now that you have this comparer you can leverage LINQ to do the work for you:
var combinedList = userGroup1.Union(userGroup2, new UserGroupComparer())
.ToList();
That will have all of the user groups that are in either list, but without any duplicates.

You can try:
userGroup1.Concat(userGroup2).Distinct();
And don't forget to override Equals and GetHashCode for UserGroup class.

The following could be used if the items in collections are of two different types:
class CollectionSynchronizer<TSource, TDestination>
{
public Func<TSource, TDestination, bool> CompareFunc { get; set; }
public Action<TDestination> RemoveAction { get; set; }
public Action<TSource> AddAction { get; set; }
public Action<TSource, TDestination> UpdateAction { get; set; }
public void Synchronizer(ICollection<TSource> sourceItems, ICollection<TDestination> destinationItems)
{
// Remove items not in source from destination
RemoveItems(sourceItems, destinationItems);
// Add items in source to destination
AddOrUpdateItems(sourceItems, destinationItems);
}
private void RemoveItems(ICollection<TSource> sourceCollection, ICollection<TDestination> destinationCollection)
{
foreach (var destinationItem in destinationCollection.ToArray())
{
var sourceItem = sourceCollection.FirstOrDefault(item => CompareFunc(item, destinationItem));
if (sourceItem == null)
{
RemoveAction(destinationItem);
}
}
}
private void AddOrUpdateItems(ICollection<TSource> sourceCollection, ICollection<TDestination> destinationCollection)
{
var destinationList = destinationCollection.ToList();
foreach (var sourceItem in sourceCollection)
{
var destinationItem = destinationList.FirstOrDefault(item => CompareFunc(sourceItem, item));
if (destinationItem == null)
{
AddAction(sourceItem);
}
else
{
UpdateAction(sourceItem, destinationItem);
}
}
}
}
And the usage would be like this:
var collectionSynchronizer = new CollectionSynchronizer<string, ContentImageEntity>
{
CompareFunc = (communityImage, contentImage) => communityImage == contentImage.Name,
AddAction = sourceItem =>
{
var contentEntityImage = _contentImageProvider.Create(sourceItem);
contentEntityImages.Add(contentEntityImage);
},
UpdateAction = (communityImage, contentImage) =>
{
_contentImageProvider.Update(contentImage);
},
RemoveAction = contentImage =>
{
contentEntityImages.Remove(contentImage);
}
};
collectionSynchronizer.Synchronizer(externalContentImages, contentEntityImages);

See the answer to this question: Create a list from two object lists with linq
Basically you can use this in System.Linq:
userGroup1.Union(userGroup2).ToList();

You may use HashSet see following link class http://msdn.microsoft.com/en-us/library/bb383091.aspx

Related

Delete a record by matching Value

I have a dictionary where values are stored in the following format -
userID, empDetails
For example,
1234, 'empName,jobDesc,CardNumber,Type'
I have to compare this information with another set of information such that -
If entered userId is present in the above dictionary, then remove this record from the dictionary.
If entered CardNumber is present (here userId is not known) in the above dictionary, then remove this record from the dictionary.
The first condition is simple and can be done by
dictionary.Remove(key)
But I am confused as to how would I implement the second condition. I want something like
if(CardNumber.PresentinAboveDictionary)
then
Remove that record
I know we can compare a partial string in a key like this, but I want to remove the record.
Check if any part of a hashtable value contains certain string c#
Assuming the employment details in your dictionary are a string in the specified format you would need to:
Search the values within the dictionary
Parse/Split the values to get the card numbers
Check the card numbers to see if they match the card number you are checking
Return the key value pair when a match occurs
Remove the entry for the key in the returned key value pair
Example code for the solution:
var dictionary = new Dictionary<int, string>() { { 1, "empName,jobDesc,124124134,Type" } };
var cardNumber = 124124134;
var entry = dictionary.FirstOrDefault(x => DoEmploymentDetailsContainCardNumber(x.Value, cardNumber));
if (!entry.Equals(default(KeyValuePair<int, string>)))
{
dictionary.Remove(entry.Key);
}
Method that checks if card number is present in employment details:
private static bool DoEmploymentDetailsContainCardNumber(string empDetails, int cardNumber)
{
var splitEmpDetails = empDetails.Split(',');
var empDetailsCardNumber = splitEmpDetails[2];
return empDetailsCardNumber == cardNumber.ToString();
}
Instead of Dictionary you can use a strongly typed List
Use the Linq builtin Remove method
Use Parallel.ForEach, iterate the list and remove the item (beware, takes more time)
pseudo code:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Collections;
namespace ConsoleApp4
{
public class Employee
{
public Employee(int userID, string empDetails)
{
string[] props = empDetails.Split(new char[] { ',' }, StringSplitOptions.None);
this.userID = userID;
this.empName = props[0];
this.jobDesc = props[1];
this.CardNumber = props[2];
this.Type = props[3];
}
public int userID { get; set; }
public string empName { get; set; }
public string jobDesc { get; set; }
public string CardNumber { get; set; }
public string Type { get; set; }
}
public class MyCustomList : List<Employee>
{
public void Add(int userID, string empDetails)
{
this.Add(new Employee(userID, empDetails));
}
public bool Remove(string CardNumber)
{
bool found = false ;
Parallel.ForEach(this,
(i, state) =>
{
if (i.CardNumber == CardNumber)
{
this.Remove(i);
state.Break();
}
});
return found;
}
public bool RemoveV2(string CardNumber)
{
bool found = false;
if (this.Any(x => x.CardNumber == CardNumber))
{
this.Remove(this.Where(x => x.CardNumber == CardNumber).First());
found = true;
}
return found;
}
}
class Program
{
static void Main(string[] args)
{
var dict = new MyCustomList();//userID, empDetails list
dict.Add(12341, "empName1,jobDesc,CardNumber1,Type");
dict.Add(12342, "empName2,jobDesc,CardNumber2,Type");
dict.Add(12343, "empName3,jobDesc,CardNumber3,Type");
dict.Add(12344, "empName4,jobDesc,CardNumber4,Type");
dict.Add(12345, "empName5,jobDesc,CardNumber5,Type");
dict.Add(12346, "empName6,jobDesc,CardNumber6,Type");
dict.Add(12347, "empName7,jobDesc,CardNumber7,Type");
dict.Add(12348, "empName8,jobDesc,CardNumber8,Type");
//remove CardNumber5
dict.Remove("CardNumber5");
Console.Write(dict);
}
}
}
you can follow the simple approach to remove the key by using a loop here.
Here I am assuming that there is no key with a value of -1 in the dictionary.
int keyToRemove = -1;
foreach (var entry in dictionary)
{
if (entry.Value.Contains(CardNumber))
{
keyToRemove = entry.Key;
break;
}
}
if (keyToRemove != -1)
{
dictionary.Remove(keyToRemove);
}
This is possibly overkill and is not optimised for reading the full dataset repeatedly but it is considerably faster than the accepted solution. I put together a test of the solution below which did the following:
Generated 1,000,000 data rows with unique IDs and card numbers (the solution would also work if the card numbers were not unique)
Randomly removed 100,000 data items by ID and 100,000 data items by card number
Generated a list of the remaining data items
The process took around 75 seconds.
I then tried to repeat steps 1) and 2) using the accepted answer - after around 10 minutes it's about 7% of the way through removing data items. Therefore I think the solution below is around 2 orders of magnitude faster for this type of operation.
There are probably better doubley linked list implementations out there but I am not too familiar with any of them.
namespace Question
{
public class EmployeeCollection
{
private readonly Dictionary<int, ListNode<EmployeeDetails>> _idDictionary = new();
private readonly Dictionary<string, Dictionary<int, EmployeeDetails>> _cardNumberDictionary = new();
private readonly LinkedList<EmployeeDetails> _list = new();
public void AddEmployee(EmployeeDetails details)
{
var node = new ListNode<EmployeeDetails>(details);
_list.AddToStart(node);
_idDictionary.Add(details.Id, node);
if(!_cardNumberDictionary.ContainsKey(details.CardNumber))
{
_cardNumberDictionary.Add(details.CardNumber, new Dictionary<int, EmployeeDetails>());
}
_cardNumberDictionary[details.CardNumber].Add(details.Id, details);
}
public void RemoveById(int id)
{
if (_idDictionary.TryGetValue(id, out var node))
{
_idDictionary.Remove(id);
_list.Remove(node);
var list = _cardNumberDictionary[node.Value.CardNumber];
list.Remove(id);
if(list.Count == 0)
{
_cardNumberDictionary.Remove(node.Value.CardNumber);
}
}
}
public void RemoveByCardNumber(string cardNumber)
{
if (_cardNumberDictionary.TryGetValue(cardNumber, out var employees))
{
_cardNumberDictionary.Remove(cardNumber);
foreach (var employee in employees)
{
if (_idDictionary.TryGetValue(employee.Key, out var node))
{
_list.Remove(node);
}
}
}
}
public IEnumerable<EmployeeDetails> Employees => _list.GetAllValues();
public EmployeeDetails? GetById(int id)
{
if(_idDictionary.ContainsKey(id))
{
return _idDictionary[id].Value;
}
return null;
}
}
public class EmployeeDetails
{
public int Id { get; init; }
public string Name { get; init; }
public string JobDescription { get; init; }
public string CardNumber { get; init; }
public string Type { get; init; }
public static EmployeeDetails FromData(int id, string details)
{
var parts = details.Split(',');
return new EmployeeDetails
{
Id = id,
Name = parts[0],
JobDescription = parts[1],
CardNumber = parts[2],
Type = parts[3],
};
}
}
public class LinkedList<T>
{
public int Count { get; private set; }
private ListNode<T>? Start { get; set; }
private ListNode<T>? End { get; set; }
public bool IsEmpty => Count == 0;
public void AddToStart(ListNode<T> node)
{
ArgumentNullException.ThrowIfNull(nameof(node));
node.Next = null;
node.Previous = null;
if (IsEmpty)
{
Start = End = node;
}
else
{
Start!.Previous = node;
node.Next = Start;
Start = node;
}
Count++;
}
public void Remove(ListNode<T> node)
{
if (node != Start)
{
node.Previous!.Next = node.Next;
}
else
{
Start = node.Next;
}
if (node != End)
{
node.Next!.Previous = node.Previous;
}
else
{
End = node.Previous;
}
Count--;
}
public IEnumerable<T> GetAllValues()
{
var counter = Start;
while (counter != null)
{
yield return counter.Value;
counter = counter.Next;
}
}
}
public class ListNode<T>
{
public T Value { get; }
public ListNode<T>? Previous { get; set; }
public ListNode<T>? Next { get; set; }
public ListNode(T value)
{
Value = value;
}
}
}
you can do something like this.
var recordsToRemove = dictionary.Where(x => x.Value.Contains("what you are looking for"))
.ToList();
if (recordsToRemove.Any())
{
foreach (var record in recordsToRemove)
{
dictionary.Remove(record.Key);
}
}

Iterator variable doesn't exist in IEnumerable from which it came?

I have a method which looks like
private Component[] AssociateComponentsWithParametersAndValues(
IEnumerable<Component> components,
IEnumerable<ComponentParameter> parameters,
IEnumerable<ComponentParameterValue> values
)
{
var componentsDictionary = new Dictionary<string, Component>();
var parametersDictionary = new Dictionary<string, ComponentParameter>();
var valuesDictionary = new Dictionary<string, ComponentParameterValue>();
foreach (Component c in components)
{
bool componentMatch = components.Any(co => co == c);
bool identifierMatch = components.Any(co => co.Identifier == c.Identifier);
if (!componentsDictionary.ContainsKey(c.Identifier))
componentsDictionary.Add(c.Identifier, c);
}
// Do a bunch of stuff to mutate the components
return components.ToArray();
}
You would think that componentMatch and identifierMatch would both be true each time right? Instead, componentMatch is always false and identifierMatch is always true. Also, the identifier is (nearly, occasionally there's some bad data) always unique, so it's not like it can be finding another component with the same identifier.
So, there must be something weird with the Component class. Well, here's what it looks like
public class Component : ConfigurationObject
{
public string Parent { get; set; }
public string Type { get; set; }
public string Module { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string TypeName { get; set; }
public bool? Enabled { get; set; }
public string DBIdentifier { get; set; }
public Dictionary<string, ComponentParameterAndValues> ParametersAndValues { get; set; }
public override string Identifier => DBIdentifier;
}
And here's the class it implements
public abstract class ConfigurationObject
{
public abstract string Identifier { get; }
}
Why is this happening?
The only way I could see this break, is if IEnumerable<Component> components is a lazily evaluated enumerable, returning new iterator objects every time. This works:
var list = new List<Component>
{
new Component { Identifier = "Foo" },
new Component { Identifier = "Bar" },
new Component { Identifier = "Baz" },
};
foreach (Component c in list)
{
bool componentMatch = list.Any(co => co == c);
Console.WriteLine($"Component {c.Identifier} match: {componentMatch}");
}
Because == checks reference equality (unless Component overrides it, but it doesn't look like it does). However, if it's not a list, but a new result each iteration:
IEnumerable<Component> list = GetList();
foreach (Component c in list)
{
bool componentMatch = list.Any(co => co == c);
Console.WriteLine($"Component {c.Identifier} match: {componentMatch}");
}
private static IEnumerable<Component> GetList()
{
yield return new Component { Identifier = "Foo" };
yield return new Component { Identifier = "Bar" };
yield return new Component { Identifier = "Baz" };
}
Then it prints false, because foreach() and Any() each get a new collection of new objects, so their references don't match.
The solution would be to enumerate once, and store the components once, materialized in a list, and use that:
var localComponents = components.ToList();
foreach (Component c in localComponents)
{
// ...
}

Ordering a self-referencing hierarchy in LINQ

I have a class called NavigationElement that looks like this
public class NavigationElement
{
public int Id { get; set; }
public string Title { get; set; }
public string Link { get; set; }
public int SortOrder { get; set; }
public bool Visible { get; set; }
public int? ParentId { get; set; }
public virtual ICollection<NavigationElement> Children { get; set; }
public virtual NavigationElement Parent { get; set; }
public NavigationElement()
{
Children = new List<NavigationElement>();
}
}
As you can see, the class is self referencing. From that, I am creating a site navigation menu with drop downs (hierarchy in play).
I am struggling in the ordering of the items. I want the top-level items to be ordered by the SortOrder property, but everything underneath, I would like ordered alphabetically by the Title property.
Here is why I have done so far.
var orderedModel = unorderedModel.OrderBy(x => x.SortOrder).ThenBy(x => x.Children.OrderBy(y => y.Title).ThenBy(z => z.Children.OrderBy(a => a.Title))).ToList();
unorderedModel is of type List<NavigationElementModel>.
This is compiling, but I get an error when I run the code. The error says:
At least one object must implement IComparable.
You should just go recursive through all children elements and sort it.
Somehting like:
var ordered = unorderedModel.OrderBy(x=>x.SortOrder).ToList();
ordered.ForEach(OrderChildren);
public void OrderChildren(NavigationElement el)
{
el.Children = el.Children.OrderBy(x => x.Title).ToList();
if (el.Children != null)
{
foreach (var c in el.Children)
{
OrderChildren(c);
}
}
}
What about something like this?
public static class LinqExtension {
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector) {
if (source == null) throw new ArgumentNullException("source");
foreach (var i in source) {
yield return i;
var children = childrenSelector(i);
if (children == null) continue;
foreach (var child in SelectManyRecursive(children, childrenSelector)) {
yield return child;
}
}
}
}
var orderedModel = unorderedModel
.OrderBy(x => x.SortOrder)
.SelectMany(x => new[] { x }.Union(
x.Children.SelectManyRecursive(y => y.Children)
.OrderBy(y => y.Parent.Title) // considering hierarchy
.ThenBy(y => y.Title)
))
.ToList();
I will utilize the approach from Sort hierarchy with path and depth fields using Linq for your case.
First, the general tree traversal helper from my answer to How to flatten tree via LINQ?:
public static partial class TreeUtils
{
public static IEnumerable<T> Expand<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
{
var stack = new Stack<IEnumerator<T>>();
var e = source.GetEnumerator();
try
{
while (true)
{
while (e.MoveNext())
{
var item = e.Current;
yield return item;
var elements = elementSelector(item);
if (elements == null) continue;
stack.Push(e);
e = elements.GetEnumerator();
}
if (stack.Count == 0) break;
e.Dispose();
e = stack.Pop();
}
}
finally
{
e.Dispose();
while (stack.Count != 0) stack.Pop().Dispose();
}
}
}
And the solution of your particular problem:
var orderedModel = unorderedModel.Where(item => item.Parent == null).OrderBy(item => item.SortOrder)
.Expand(item => item.Children != null && item.Children.Any() ? item.Children.OrderBy(child => child.Title) : null)
.ToList();

How to compare all values in an object without repeating if statements?

I am trying to compare all possible values in a list of objects like this:
public class Object21
{
int Id,
bool firstbool,
bool secondbool
}
I would loop through the objects and compare them like this:
List<Object1> objects;
foreach(var o in objects)
{
if(firstbool && secondbool)
....
if(firstbool && !secondbool)
....
if(!firstbool && secondbool)
....
if(!firstbool && !secondbool)
....
}
This seems ok, but what if the object had several values that you were running through if statements.
public class Object2
{
int Id;
int firstbool;
....
int twentiethbool;
}
Then you would have to write out all of the possible conditional statements and your code would be terribly written and hard to read.
List<Object2> objects2;
foreach(var o in objects2)
{
if(firstbool && secondbool && ... && twentiethbool)
....
if(....)
....
....
....
if(!firstbool && !secondbool && ... && !twentiethbool)
....
}
Is there a simpler way to write the second scenario so that you are not writing every combination of if statements?
In the end I would like to calculate the percentage occurrence of each condition in the list.
To answer the first part of the question (about comparing every combination):
There isn't really a good way to do that, other than write a bunch of if statements. Of course; you probably shouldn't be doing that anyways :)
You could probably use reflection and recursion, but thats going to get messy really fast.
Luckily, to just get the percentage occurrence of each flag, you can just do:
list.Count(i => i.firstbool) / (double)list.Count();
...
first, create a dictionary to save all conditions
var dict = new Dictionary<string, int>{{"001",0},{"010",0} ...}
then, create key use bool values
var key=string.Empty;
key+=firstbool ?"0":"1"
key+=secondbool ?"0":"1"
......
after all, you can know which condition occurred
dict[key]++;
Given a class structure like this:
public class YourClass
{
public int Id { get; set; }
public bool firstbool { get; set; }
public bool secondbool { get; set; }
public bool thirdbool { get; set; }
}
You can use reflection to get all the boolean values (and only bool values) inside the class:
public IEnumerable<bool> GetBools(YourClass obj)
{
return obj.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof (bool))
.Select(x => (bool)x.GetValue(obj, null));
}
Then use LINQ to iterate through the collection, and create a dictionary of combinations and totals:
List<YourClass> objects = new List<YourClass>();
var totals = objects.GroupBy(x => String.Join(",", GetBools(x)))
.ToDictionary(x => x.Key, x => x.Count() / (double)objects.Count);
This will give you a dictionary with each unique combination and the percentage it occurs.
Given this input:
var o = new List<YourClass>
{
new YourClass {firstbool = true, secondbool = true, thirdbool = false},
new YourClass {firstbool = false, secondbool = false, thirdbool = false},
new YourClass {firstbool = true, secondbool = true, thirdbool = false}
};
The result in the dictionary will be:
{["True,True,False", 0.666666666666667]}
{["False,False,False", 0.333333333333333]}
it's probably easier to rewrite your class, storing each condition in an array like follows:
public class MyObject
{
public static int numFields = 20;
public enum Conditions
{
C1, C2, C3, .... C20 //name for each condition, so can set values using descriptive names
};
public Boolean[] BinaryFields = new Boolean[numFields];
public void setCondition(Conditions condition, Boolean value)
{
BinaryFields[(int)condition] = value;
}
public override string ToString()
{
return string.Join(",", BinaryFields);
}
}
then you can calculate the stat by counting what is actually there, instead of numerating through all of the 2^20 possibilities. something like follows:
static void Main(string[] args)
{
//simulation: creat 10 MyObjects
List<MyObject> lst = new List<MyObject>();
for (int i = 0; i < 10; i++)
{
MyObject m = new MyObject();
//example of setting condition
m.setCondition(MyObject.Conditions.C1, true);
lst.Add(m);
}
//calculate stat
var resultCount = new Dictionary<string, int>(); //conditionResult, count
foreach (MyObject m in lst)
{
if (resultCount.ContainsKey(m.ToString()))
{
resultCount[m.ToString()] += 1;
}
else
{
resultCount.Add(m.ToString(), 1);
}
}
//print stat
foreach(KeyValuePair<string, int> entry in resultCount){
Debug.WriteLine("probability for conditoin={0} is {1}", entry.Key, (double)entry.Value / lst.Count);
}
}
If you have some unique action for each boolean properties combination I suggest you to use some kind of string key for your object, generated on those values. Something like "001001", "000000" etc. Then use Dictionary<string, Func<int>> to hold all your unique actions, get and perform the right one by it's key. For example:
public class Object21
{
public int Id { get; set; }
public bool FirstBool { get; set; }
public bool SecondBool { get; set; }
public bool ThirdBool { get; set; }
public bool FourthBool { get; set; }
public bool FifthBool { get; set; }
public bool SixthBool { get; set; }
public void Process()
{
// Perform the action
Actions[Key]();
}
// Returns "001001" like representation of your object
public string Key
{
get
{
return string.Join(string.Empty, GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(bool))
.Select(x => (bool)x.GetValue(this, null) ? "1" : "0" ));
}
}
private static Dictionary<string, Func<int>> Actions
{
get
{
return new Dictionary<string, Func<int>>
{
{"000000", new Func<int>(delegate
{
Console.WriteLine("000000 action performed.");
return 0;
})},
{"000001", new Func<int>(delegate
{
Console.WriteLine("000001 action performed.");
return 1;
})},
{"000010", new Func<int>(delegate
{
Console.WriteLine("000010 action performed.");
return 2;
})},
// More actions
{"111111", new Func<int>(delegate
{
Console.WriteLine("111111 action performed.");
return 63;
})}
};
}
}
}
And then use this in your program like this:
static void Main(string[] args)
{
var list = new List<Object21>
{
// initialize your list
};
foreach (var object21 in list)
{
object21.Process();
}
// Calculate your occurrences (basically what #Grant Winney suggested)
var occurrences = list.GroupBy(o => o.Key).ToDictionary(g => g.Key, g => (g.Count() / (double)list.Count)*100);
foreach (var occurrence in occurrences)
{
Console.WriteLine("{0}: {1}%", occurrence.Key, occurrence.Value);
}
}

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