Last active
May 13, 2020 23:30
-
-
Save amenayach/2db33610fc24ddf1f280e9833113d033 to your computer and use it in GitHub Desktop.
A CSharp class that builds a nested tree out of a flat collection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public sealed class Nester<T, TSortKey> : IDisposable | |
{ | |
private readonly IEnumerable<T> flatList; | |
private Func<T, T, bool> parentChildPredicate; | |
private PropertyInfo nestingPropertyInfo; | |
private Func<T, TSortKey> sortingKeySelector; | |
private Func<T, bool> rootLevelPredicate; | |
private Nester(IEnumerable<T> flatList, | |
Func<T, bool> rootLevelPredicate, | |
Func<T, T, bool> parentChildPredicate, | |
Expression<Func<T, IEnumerable<T>>> nestingField, | |
Func<T, TSortKey> sortingKeySelector) | |
{ | |
this.flatList = flatList; | |
this.rootLevelPredicate = rootLevelPredicate; | |
this.parentChildPredicate = parentChildPredicate; | |
this.sortingKeySelector = sortingKeySelector; | |
if (!(nestingField.Body is MemberExpression memberSelectorExpression)) | |
{ | |
throw new ArgumentException(nameof(nestingField)); | |
} | |
nestingPropertyInfo = memberSelectorExpression.Member as PropertyInfo; | |
if (nestingPropertyInfo == null) | |
{ | |
throw new ArgumentException(nameof(nestingField)); | |
} | |
} | |
private IEnumerable<T> Nest() | |
{ | |
if (flatList == null || !flatList.Any()) | |
{ | |
return null; | |
} | |
var rootLevels = flatList | |
.Where(rootLevelPredicate) | |
.OrderBy(sortingKeySelector) | |
.Select(rootLevel => | |
{ | |
var nestedData = ConstructLevels(rootLevel, flatList); | |
nestingPropertyInfo.SetValue(rootLevel, nestedData, null); | |
return rootLevel; | |
}) | |
.ToArray(); | |
return rootLevels; | |
} | |
private IEnumerable<T> ConstructLevels(T detail, IEnumerable<T> remainCourseDetails) | |
{ | |
var result = remainCourseDetails?.Where(m => parentChildPredicate(detail, m)).OrderBy(sortingKeySelector).ToArray(); | |
var rest = remainCourseDetails?.Where(m => !parentChildPredicate(detail, m)).OrderBy(sortingKeySelector).ToArray(); | |
if ((rest?.Any() ?? false) && (result?.Any() ?? false)) | |
{ | |
result = result | |
.Select(innerDetail => | |
{ | |
var nestedData = ConstructLevels(innerDetail, rest); | |
nestingPropertyInfo.SetValue(innerDetail, nestedData, null); | |
return innerDetail; | |
}) | |
.ToArray(); | |
} | |
return result; | |
} | |
public void Dispose() | |
{ | |
this.rootLevelPredicate = null; | |
this.parentChildPredicate = null; | |
this.nestingPropertyInfo = null; | |
this.sortingKeySelector = null; | |
} | |
/// <summary> | |
/// Nests the specified flat list. | |
/// </summary> | |
/// <param name="flatList">The flat list.</param> | |
/// <param name="rootLevelPredicate">The root level predicate. ex: m => string.IsNullOrWhiteSpace(m.ParentId)</param> | |
/// <param name="parentChildPredicate">The parent child predicate. ex: (p, c) => c.ParentId == p.Id</param> | |
/// <param name="nestingField">The nesting field. ex: c => c.Details</param> | |
/// <param name="sortingKeySelector">The sorting key selector. ex: m => m.Order</param> | |
/// <returns></returns> | |
public static IEnumerable<T> Nest(IEnumerable<T> flatList, | |
Func<T, bool> rootLevelPredicate, | |
Func<T, T, bool> parentChildPredicate, | |
Expression<Func<T, IEnumerable<T>>> nestingField, | |
Func<T, TSortKey> sortingKeySelector) | |
{ | |
using var nester = new Nester<T, TSortKey>(flatList, rootLevelPredicate, parentChildPredicate, nestingField, sortingKeySelector); | |
return nester.Nest(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage, given a class
CourseDetail
for example:Given a collection of course details:
The tree can be generated as following: