#if NETFRAMEWORK
using System;
using System.Collections.Generic;
using System.Linq;
#endif
using System.Linq.Expressions;
using Masuit.Tools.Systems;
namespace Masuit.Tools.Models;
///
/// 树形数据扩展
///
public static class TreeExtensions
{
///
/// 过滤
///
///
///
///
///
public static IEnumerable Filter(this IEnumerable items, Func func) where T : class, ITreeChildren => Flatten(items).Where(func);
///
/// 过滤
///
///
///
///
///
public static IEnumerable> Filter(this IEnumerable> items, Func, bool> func) where T : class => Flatten(items).Where(func);
///
/// 过滤
///
///
///
///
///
public static IEnumerable Filter(this T item, Func func) where T : class, ITreeChildren => Flatten(item).Where(func);
///
/// 过滤
///
///
///
///
///
public static IEnumerable> Filter(this Tree item, Func, bool> func) where T : class => Flatten(item).Where(func);
///
/// 平铺开
///
///
///
/// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象
///
public static IEnumerable Flatten(this IEnumerable items, Action optionAction = null) where T : class, ITreeChildren => items.Flatten(i => i.Children ?? [], optionAction);
///
/// 平铺开
///
///
///
/// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象
///
public static IEnumerable Flatten(this T p, Action optionAction = null) where T : class, ITreeChildren => p != null ? Flatten([p], t => t.Children, optionAction) : [];
///
/// 平铺开任意树形结构数据
///
///
///
/// 子节点获取方法
/// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象
///
public static IEnumerable Flatten(this IEnumerable items, Func> selector, Action optionAction = null)
{
if (items == null)
yield break;
// 使用一个队列来存储待处理的节点
var queue = new Queue();
// 首先将所有项加入队列
foreach (var item in items)
queue.Enqueue(item);
// 循环直到队列为空
while (queue.Count > 0)
{
// 从队列中取出当前节点
var currentItem = queue.Dequeue();
// 将当前节点返回
yield return currentItem;
// 获取当前节点的所有子节点
var children = selector(currentItem) ?? [];
// 将所有子节点加入队列
foreach (var child in children)
{
// 执行平铺时的操作(如果有的话)
optionAction?.Invoke(child, currentItem);
queue.Enqueue(child);
}
}
}
///
/// 平铺开
///
///
///
/// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象
///
public static IEnumerable> Flatten(this IEnumerable> items, Action, Tree> optionAction = null) where T : class => items != null ? Flatten(items, t => t.Children, optionAction) : [];
///
/// 平铺开
///
///
///
/// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象
///
public static IEnumerable> Flatten(this Tree p, Action, Tree> optionAction = null) where T : class
{
if (p == null)
{
return [];
}
return Flatten([p], t => t.Children, optionAction);
}
///
/// 平行集合转换成树形结构
///
///
///
///
///
/// 根对象parentId的值
///
public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, string topValue = null) where T : ITreeParent, ITreeChildren => ToTree(source, idSelector, pidSelector, topValue);
///
/// 平行集合转换成树形结构
///
///
///
///
///
/// 根对象parentId的值
///
public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, int topValue = 0) where T : ITreeParent, ITreeChildren => ToTree(source, idSelector, pidSelector, topValue);
///
/// 平行集合转换成树形结构
///
///
///
///
///
/// 根对象parentId的值
///
public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, long topValue = 0) where T : ITreeParent, ITreeChildren => ToTree(source, idSelector, pidSelector, topValue);
///
/// 平行集合转换成树形结构
///
///
///
///
///
/// 根对象parentId的值
///
public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, Guid topValue = default) where T : ITreeParent, ITreeChildren => ToTree(source, idSelector, pidSelector, topValue);
///
/// 平行集合转换成树形结构
///
///
///
///
///
///
/// 根对象parentId的值
///
public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, TKey topValue = default) where T : ITreeParent, ITreeChildren where TKey : IComparable => idSelector.Body.ToString() != pidSelector.Body.ToString() ? BuildTree(source.Enumerable2NonNullList(), idSelector.Compile(), pidSelector.Compile(), topValue).ToList() : throw new ArgumentException("idSelector和pidSelector不应该为同一字段!");
///
/// 平行集合转换成树形结构
///
///
///
///
public static List ToTree(this IEnumerable source) where T : class, ITreeEntity => ToTree(source);
///
/// 平行集合转换成树形结构
///
///
///
///
///
///
public static List ToTree(this IEnumerable source, TKey? topValue = null) where T : class, ITreeEntity where TKey : struct, IComparable => BuildTree(source.Enumerable2NonNullList(), topValue).ToList();
///
/// 平行集合转换成树形结构
///
///
///
///
///
///
/// 根对象parentId的值
///
public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, TKey? topValue = null) where T : ITreeChildren where TKey : struct => idSelector.Body.ToString() != pidSelector.Body.ToString() ? BuildTree(source.Enumerable2NonNullList(), idSelector.Compile(), pidSelector.Compile(), topValue).ToList() : throw new ArgumentException("idSelector和pidSelector不应该为同一字段!");
private static IEnumerable BuildTree(IEnumerable source, Func idSelector, Func pidSelector, TKey topValue = default) where T : ITreeChildren where TKey : IComparable
{
// 创建一个字典,用于快速查找节点的子节点
var childrenLookup = new Dictionary>();
var list = source as ICollection ?? source.ToList();
foreach (var item in list.Where(item => !childrenLookup.ContainsKey(idSelector(item))))
{
childrenLookup[idSelector(item)] = [];
}
// 构建树结构
foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item))))
{
childrenLookup[pidSelector(item)].Add(item);
}
// 找到根节点,即没有父节点的节点
foreach (var root in list.Where(x => Equals(pidSelector(x), topValue)))
{
// 为根节点和所有子节点设置Children属性
// 使用队列来模拟递归过程
var queue = new Queue();
queue.Enqueue(root);
while (queue.Count > 0)
{
// 出队当前节点
var current = queue.Dequeue();
// 为当前节点设置子节点
if (childrenLookup.TryGetValue(idSelector(current), out var children))
{
current.Children = children;
foreach (var child in children)
{
// 如果子节点实现了ITreeParent接口,则设置其Parent属性
if (child is ITreeParent tree)
{
tree.Parent = current;
}
// 将子节点入队以继续处理
queue.Enqueue(child);
}
}
}
yield return root;
}
}
internal static IEnumerable BuildTree(IEnumerable source, TKey? topValue = null) where T : ITreeEntity where TKey : struct, IComparable => BuildTree(source, t => t.Id, t => t.ParentId, topValue);
internal static IEnumerable BuildTree(IEnumerable source, string topValue = null) where T : ITreeEntity => BuildTree(source, t => t.Id, t => t.ParentId, topValue);
internal static IEnumerable BuildTree(IEnumerable source, T parent) where T : ITreeEntity
{
// 创建一个字典,用于快速查找节点的子节点
var childrenLookup = new NullableDictionary>();
var list = source as ICollection ?? source.ToList();
foreach (var item in list.Where(item => !childrenLookup.ContainsKey(item.Id)))
childrenLookup[item.Id] = [];
// 构建树结构
foreach (var item in list.Where(item => !string.IsNullOrEmpty(item.ParentId) && childrenLookup.ContainsKey(item.ParentId)))
childrenLookup[item.ParentId].Add(item);
// 找到根节点,即没有父节点的节点
foreach (var root in list.Where(x => x.Id == parent.Id))
{
// 为根节点和所有子节点设置Children属性
// 使用队列来模拟递归过程
var queue = new Queue();
queue.Enqueue(root);
while (queue.Count > 0)
{
// 出队当前节点
var current = queue.Dequeue();
// 为当前节点设置子节点
if (childrenLookup.TryGetValue(current.Id, out var children))
{
current.Children = children;
foreach (var child in children)
{
// 如果子节点实现了ITreeParent接口,则设置其Parent属性
if (child is ITreeParent tree)
tree.Parent = current;
// 将子节点入队以继续处理
queue.Enqueue(child);
}
}
}
yield return root;
}
}
private static IEnumerable BuildTree(IEnumerable source, Func idSelector, Func pidSelector, TKey? topValue = null) where T : ITreeChildren where TKey : struct
{
// 创建一个字典,用于快速查找节点的子节点
var childrenLookup = new NullableDictionary>();
var list = source as ICollection ?? source.ToList();
foreach (var item in list.Where(item => !childrenLookup.ContainsKey(idSelector(item))))
{
childrenLookup[idSelector(item)] = [];
}
// 构建树结构
foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item) ?? default)))
{
childrenLookup[pidSelector(item) ?? default].Add(item);
}
// 找到根节点,即没有父节点的节点
foreach (var root in list.Where(x => Equals(pidSelector(x), topValue)))
{
// 为根节点和所有子节点设置Children属性
// 使用队列来模拟递归过程
var queue = new Queue();
queue.Enqueue(root);
while (queue.Count > 0)
{
// 出队当前节点
var current = queue.Dequeue();
// 为当前节点设置子节点
if (childrenLookup.TryGetValue(idSelector(current), out var children))
{
current.Children = children;
foreach (var child in children)
{
// 如果子节点实现了ITreeParent接口,则设置其Parent属性
if (child is ITreeParent tree)
{
tree.Parent = current;
}
// 将子节点入队以继续处理
queue.Enqueue(child);
}
}
}
yield return root;
}
}
///
/// 平行集合转换成树形结构
///
///
///
///
///
///
/// 根对象parentId的值
///
public static List> ToTreeGeneral(this IEnumerable source, Expression> idSelector, Expression> pidSelector, TKey topValue = default) where TKey : IComparable => idSelector.Body.ToString() != pidSelector.Body.ToString() ? BuildTreeGeneral(source.Where(t => t != null).ToList(), idSelector.Compile(), pidSelector.Compile(), topValue).ToList() : throw new ArgumentException("idSelector和pidSelector不应该为同一字段!");
private static IEnumerable> BuildTreeGeneral(List list, Func idSelector, Func pidSelector, TKey parent) where TKey : IComparable
{
// 创建一个字典,用于快速查找节点的子节点
var lookup = new Dictionary>>();
foreach (var item in list.Where(item => !lookup.ContainsKey(idSelector(item))))
{
lookup[idSelector(item)] = [];
}
// 构建树结构
foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && lookup.ContainsKey(pidSelector(item))))
{
lookup[pidSelector(item)].Add(new Tree(item));
}
// 找到根节点,即没有父节点的节点
foreach (var root in list.Where(x => Equals(pidSelector(x), parent)))
{
// 为根节点和所有子节点设置Children属性
var queue = new Queue>();
var item = new Tree(root);
queue.Enqueue(item);
while (queue.Count > 0)
{
// 出队当前节点
var current = queue.Dequeue();
// 为当前节点设置子节点
if (lookup.TryGetValue(idSelector(current.Value), out var children))
{
current.Children = children;
foreach (var child in children)
{
child.Parent = current.Parent;
// 将子节点入队以继续处理
queue.Enqueue(child);
}
}
}
yield return item;
}
}
///
/// 所有子级
///
public static ICollection AllChildren(this T tree) where T : ITreeChildren => GetChildren(tree, c => c.Children);
///
/// 所有子级
///
public static ICollection AllChildren(this T tree, Func> selector) where T : ITreeChildren => GetChildren(tree, selector);
///
/// 所有子级
///
public static ICollection> AllChildren(this Tree tree) => GetChildren(tree, c => c.Children);
///
/// 所有子级
///
public static ICollection> AllChildren(this Tree tree, Func, IEnumerable>> selector) => GetChildren(tree, selector);
///
/// 所有父级
///
public static List AllParent(this T tree) where T : class, ITreeParent => GetParents(tree, c => c.Parent);
///
/// 所有父级
///
public static List AllParent(this T tree, Func selector) where T : class, ITreeParent => GetParents(tree, selector);
///
/// 所有父级
///
public static List> AllParent(this Tree tree, Func, Tree> selector) => GetParents(tree, selector);
///
/// 是否是根节点
///
public static bool IsRoot(this ITreeParent tree) where T : ITreeParent => tree.Parent == null;
///
/// 是否是叶子节点
///
public static bool IsLeaf(this ITreeChildren tree) where T : ITreeChildren => tree.Children?.Count == 0;
///
/// 是否是根节点
///
public static bool IsRoot(this Tree tree) => tree.Parent == null;
///
/// 是否是叶子节点
///
public static bool IsLeaf(this Tree tree) => tree.Children?.Count == 0;
///
/// 深度层级
///
public static int Level(this T tree) where T : class, ITreeParent
{
if (tree == null)
throw new ArgumentNullException(nameof(tree), "当前节点不能为null");
// 使用一个队列来存储待处理的节点
var queue = new Queue();
queue.Enqueue(tree);
int level = 1;
// 循环直到队列为空
while (queue.Count > 0)
{
int nodeCount = queue.Count; // 当前层级的节点数
for (int i = 0; i < nodeCount; i++)
{
// 从队列中出队一个节点
T currentNode = queue.Dequeue();
// 如果当前节点是根节点,则返回当前层级
if (currentNode.Parent == null)
return level;
// 将当前节点的父节点入队
if (currentNode.Parent != null)
queue.Enqueue(currentNode.Parent);
}
// 完成当前层级的遍历,准备进入下一层级
level++;
}
// 如果执行到这里,说明没有找到根节点
throw new InvalidOperationException("未找到根节点");
}
///
/// 节点路径(UNIX路径格式,以“/”分隔)
///
public static string Path(this T tree) where T : class, ITree => GetFullPath(tree, t => t.Name);
///
/// 节点路径(UNIX路径格式,以“/”分隔)
///
public static string Path(this T tree, Func selector) where T : class, ITreeParent => GetFullPath(tree, selector);
///
/// 节点路径
///
///
/// 分隔符
public static string Path(this T tree, string separator) where T : class, ITree => GetFullPath(tree, t => t.Name, separator);
///
/// 节点路径
///
///
///
/// 选择字段
/// 分隔符
///
public static string Path(this T tree, Func selector, string separator) where T : class, ITreeParent => GetFullPath(tree, selector, separator);
///
/// 根节点
///
public static T Root(this T tree) where T : class, ITreeParent => GetRoot(tree, t => t.Parent);
private static string GetFullPath(T c, Func selector, string separator = "/") where T : class, ITreeParent
{
if (c == null || selector == null)
{
throw new ArgumentNullException(c == null ? nameof(c) : nameof(selector), "当前节点或选择器不能为null");
}
// 使用一个栈来存储节点,栈将逆序存储路径中的节点
var stack = new Stack();
var currentNode = c;
while (currentNode != null)
{
stack.Push(currentNode);
currentNode = currentNode.Parent;
}
// 构建路径字符串
return stack.Select(selector).Join(separator);
}
///
/// 根节点
///
public static T GetRoot(T c, Func selector) where T : class, ITreeParent
{
if (c == null || selector == null)
{
throw new ArgumentNullException(c == null ? nameof(c) : nameof(selector), "当前节点或父级选择器不能为null");
}
// 使用一个集合来存储已访问的节点,以避免无限循环
var visited = new HashSet();
var currentNode = c;
// 向上遍历直到找到根节点
while (true)
{
// 如果当前节点已被访问,说明存在循环引用,抛出异常
if (!visited.Add(currentNode))
throw new InvalidOperationException("节点存在循环引用");
var parent = selector(currentNode);
if (parent == null)
{
return currentNode; // 找到了根节点
}
currentNode = parent;
}
}
///
/// 递归取出所有下级
///
///
///
///
private static List GetChildren(T t, Func> selector)
{
if (t == null || selector == null)
return []; // 如果t或selector为null,则返回空列表
var list = new List();
var queue = new Queue();
queue.Enqueue(t);
while (queue.Count > 0)
{
var current = queue.Dequeue();
list.Add(current); // 将当前节点添加到结果列表
var children = selector(current) ?? new List();
foreach (var child in children.Where(child => selector(child).Any()))
{
queue.Enqueue(child);
}
}
return list;
}
///
/// 递归取出所有上级
///
///
///
///
private static List GetParents(T t, Func selector) where T : class
{
if (t == null || selector == null)
return []; // 如果t或selector为null,则返回空列表
var parents = new List();
var current = t;
while (current != null)
{
// 添加当前节点到父节点列表
parents.Add(current);
// 通过selector函数获取当前节点的父节点
current = selector(current);
// 如果selector返回了已经遍历过的节点,则停止遍历以避免无限循环
if (parents.Contains(current))
{
break;
}
}
// 由于是逆序添加的,所以需要反转列表
parents.Reverse();
return parents;
}
}
///
/// 树形数据扩展 long
///
public static class TreeExtensionLong
{
///
/// 平行集合转换成树形结构
///
///
///
///
public static List ToTree(this IEnumerable source) where T : class, ITreeEntity => source.ToTree();
}
///
/// 树形数据扩展 guid
///
public static class TreeExtensionGuid
{
///
/// 平行集合转换成树形结构
///
///
///
///
public static List ToTree(this IEnumerable source) where T : class, ITreeEntity => source.ToTree();
}
///
/// 树形数据扩展 string
///
public static class TreeExtensionString
{
///
/// 平行集合转换成树形结构
///
///
///
///
///
public static List ToTree(this IEnumerable source, string topValue) where T : class, ITreeEntity => TreeExtensions.BuildTree(source.Enumerable2NonNullList(), topValue).ToList();
///
/// 平行集合转换成树形结构
///
///
///
///
public static List ToTree(this IEnumerable source) where T : class, ITreeEntity => source.Enumerable2NonNullList().Where(t => string.IsNullOrEmpty(t.ParentId)).SelectMany(parent => TreeExtensions.BuildTree(source, parent)).ToList();
}
internal static class TreeExtensionCommon
{
///
/// 将IEnumerable转换成不含null的List,若T为IQueryable,则将数据读取到内存
///
///
///
///
public static List Enumerable2NonNullList(this IEnumerable source)
{
source = source is IQueryable queryable ? queryable.AsEnumerable() : source;
return (typeof(T).IsValueType ? source : source.Where(t => t != null)).ToList();
}
}