TreeExtensions.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. #if NETFRAMEWORK
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. #endif
  6. using System.Linq.Expressions;
  7. using Masuit.Tools.Systems;
  8. namespace Masuit.Tools.Models;
  9. /// <summary>
  10. /// 树形数据扩展
  11. /// </summary>
  12. public static class TreeExtensions
  13. {
  14. /// <summary>
  15. /// 过滤
  16. /// </summary>
  17. /// <typeparam name="T"></typeparam>
  18. /// <param name="items"></param>
  19. /// <param name="func"></param>
  20. /// <returns></returns>
  21. public static IEnumerable<T> Filter<T>(this IEnumerable<T> items, Func<T, bool> func) where T : class, ITreeChildren<T> => Flatten(items).Where(func);
  22. /// <summary>
  23. /// 过滤
  24. /// </summary>
  25. /// <typeparam name="T"></typeparam>
  26. /// <param name="items"></param>
  27. /// <param name="func"></param>
  28. /// <returns></returns>
  29. public static IEnumerable<Tree<T>> Filter<T>(this IEnumerable<Tree<T>> items, Func<Tree<T>, bool> func) where T : class => Flatten(items).Where(func);
  30. /// <summary>
  31. /// 过滤
  32. /// </summary>
  33. /// <typeparam name="T"></typeparam>
  34. /// <param name="item"></param>
  35. /// <param name="func"></param>
  36. /// <returns></returns>
  37. public static IEnumerable<T> Filter<T>(this T item, Func<T, bool> func) where T : class, ITreeChildren<T> => Flatten(item).Where(func);
  38. /// <summary>
  39. /// 过滤
  40. /// </summary>
  41. /// <typeparam name="T"></typeparam>
  42. /// <param name="item"></param>
  43. /// <param name="func"></param>
  44. /// <returns></returns>
  45. public static IEnumerable<Tree<T>> Filter<T>(this Tree<T> item, Func<Tree<T>, bool> func) where T : class => Flatten(item).Where(func);
  46. /// <summary>
  47. /// 平铺开
  48. /// </summary>
  49. /// <typeparam name="T"></typeparam>
  50. /// <param name="items"></param>
  51. /// <param name="optionAction">平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象</param>
  52. /// <returns></returns>
  53. public static IEnumerable<T> Flatten<T>(this IEnumerable<T> items, Action<T, T> optionAction = null) where T : class, ITreeChildren<T> => items.Flatten(i => i.Children ?? [], optionAction);
  54. /// <summary>
  55. /// 平铺开
  56. /// </summary>
  57. /// <typeparam name="T"></typeparam>
  58. /// <param name="p"></param>
  59. /// <param name="optionAction">平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象</param>
  60. /// <returns></returns>
  61. public static IEnumerable<T> Flatten<T>(this T p, Action<T, T> optionAction = null) where T : class, ITreeChildren<T> => p != null ? Flatten([p], t => t.Children, optionAction) : [];
  62. /// <summary>
  63. /// 平铺开任意树形结构数据
  64. /// </summary>
  65. /// <typeparam name="T"></typeparam>
  66. /// <param name="items"></param>
  67. /// <param name="selector">子节点获取方法</param>
  68. /// <param name="optionAction">平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象</param>
  69. /// <returns></returns>
  70. public static IEnumerable<T> Flatten<T>(this IEnumerable<T> items, Func<T, IEnumerable<T>> selector, Action<T, T> optionAction = null)
  71. {
  72. if (items == null)
  73. yield break;
  74. // 使用一个队列来存储待处理的节点
  75. var queue = new Queue<T>();
  76. // 首先将所有项加入队列
  77. foreach (var item in items)
  78. queue.Enqueue(item);
  79. // 循环直到队列为空
  80. while (queue.Count > 0)
  81. {
  82. // 从队列中取出当前节点
  83. var currentItem = queue.Dequeue();
  84. // 将当前节点返回
  85. yield return currentItem;
  86. // 获取当前节点的所有子节点
  87. var children = selector(currentItem) ?? [];
  88. // 将所有子节点加入队列
  89. foreach (var child in children)
  90. {
  91. // 执行平铺时的操作(如果有的话)
  92. optionAction?.Invoke(child, currentItem);
  93. queue.Enqueue(child);
  94. }
  95. }
  96. }
  97. /// <summary>
  98. /// 平铺开
  99. /// </summary>
  100. /// <typeparam name="T"></typeparam>
  101. /// <param name="items"></param>
  102. /// <param name="optionAction">平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象</param>
  103. /// <returns></returns>
  104. public static IEnumerable<Tree<T>> Flatten<T>(this IEnumerable<Tree<T>> items, Action<Tree<T>, Tree<T>> optionAction = null) where T : class => items != null ? Flatten(items, t => t.Children, optionAction) : [];
  105. /// <summary>
  106. /// 平铺开
  107. /// </summary>
  108. /// <typeparam name="T"></typeparam>
  109. /// <param name="p"></param>
  110. /// <param name="optionAction">平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象</param>
  111. /// <returns></returns>
  112. public static IEnumerable<Tree<T>> Flatten<T>(this Tree<T> p, Action<Tree<T>, Tree<T>> optionAction = null) where T : class
  113. {
  114. if (p == null)
  115. {
  116. return [];
  117. }
  118. return Flatten([p], t => t.Children, optionAction);
  119. }
  120. /// <summary>
  121. /// 平行集合转换成树形结构
  122. /// </summary>
  123. /// <typeparam name="T"></typeparam>
  124. /// <param name="source"></param>
  125. /// <param name="idSelector"></param>
  126. /// <param name="pidSelector"></param>
  127. /// <param name="topValue">根对象parentId的值</param>
  128. /// <returns></returns>
  129. public static List<T> ToTree<T>(this IEnumerable<T> source, Expression<Func<T, string>> idSelector, Expression<Func<T, string>> pidSelector, string topValue = null) where T : ITreeParent<T>, ITreeChildren<T> => ToTree<T, string>(source, idSelector, pidSelector, topValue);
  130. /// <summary>
  131. /// 平行集合转换成树形结构
  132. /// </summary>
  133. /// <typeparam name="T"></typeparam>
  134. /// <param name="source"></param>
  135. /// <param name="idSelector"></param>
  136. /// <param name="pidSelector"></param>
  137. /// <param name="topValue">根对象parentId的值</param>
  138. /// <returns></returns>
  139. public static List<T> ToTree<T>(this IEnumerable<T> source, Expression<Func<T, int>> idSelector, Expression<Func<T, int>> pidSelector, int topValue = 0) where T : ITreeParent<T>, ITreeChildren<T> => ToTree<T, int>(source, idSelector, pidSelector, topValue);
  140. /// <summary>
  141. /// 平行集合转换成树形结构
  142. /// </summary>
  143. /// <typeparam name="T"></typeparam>
  144. /// <param name="source"></param>
  145. /// <param name="idSelector"></param>
  146. /// <param name="pidSelector"></param>
  147. /// <param name="topValue">根对象parentId的值</param>
  148. /// <returns></returns>
  149. public static List<T> ToTree<T>(this IEnumerable<T> source, Expression<Func<T, long>> idSelector, Expression<Func<T, long>> pidSelector, long topValue = 0) where T : ITreeParent<T>, ITreeChildren<T> => ToTree<T, long>(source, idSelector, pidSelector, topValue);
  150. /// <summary>
  151. /// 平行集合转换成树形结构
  152. /// </summary>
  153. /// <typeparam name="T"></typeparam>
  154. /// <param name="source"></param>
  155. /// <param name="idSelector"></param>
  156. /// <param name="pidSelector"></param>
  157. /// <param name="topValue">根对象parentId的值</param>
  158. /// <returns></returns>
  159. public static List<T> ToTree<T>(this IEnumerable<T> source, Expression<Func<T, Guid>> idSelector, Expression<Func<T, Guid>> pidSelector, Guid topValue = default) where T : ITreeParent<T>, ITreeChildren<T> => ToTree<T, Guid>(source, idSelector, pidSelector, topValue);
  160. /// <summary>
  161. /// 平行集合转换成树形结构
  162. /// </summary>
  163. /// <typeparam name="T"></typeparam>
  164. /// <typeparam name="TKey"></typeparam>
  165. /// <param name="source"></param>
  166. /// <param name="idSelector"></param>
  167. /// <param name="pidSelector"></param>
  168. /// <param name="topValue">根对象parentId的值</param>
  169. /// <returns></returns>
  170. public static List<T> ToTree<T, TKey>(this IEnumerable<T> source, Expression<Func<T, TKey>> idSelector, Expression<Func<T, TKey>> pidSelector, TKey topValue = default) where T : ITreeParent<T>, ITreeChildren<T> where TKey : IComparable => idSelector.Body.ToString() != pidSelector.Body.ToString() ? BuildTree(source.Enumerable2NonNullList(), idSelector.Compile(), pidSelector.Compile(), topValue).ToList() : throw new ArgumentException("idSelector和pidSelector不应该为同一字段!");
  171. /// <summary>
  172. /// 平行集合转换成树形结构
  173. /// </summary>
  174. /// <typeparam name="T"></typeparam>
  175. /// <param name="source"></param>
  176. /// <returns></returns>
  177. public static List<T> ToTree<T>(this IEnumerable<T> source) where T : class, ITreeEntity<T, int> => ToTree<T, int>(source);
  178. /// <summary>
  179. /// 平行集合转换成树形结构
  180. /// </summary>
  181. /// <typeparam name="T"></typeparam>
  182. /// <typeparam name="TKey"></typeparam>
  183. /// <param name="source"></param>
  184. /// <param name="topValue"></param>
  185. /// <returns></returns>
  186. public static List<T> ToTree<T, TKey>(this IEnumerable<T> source, TKey? topValue = null) where T : class, ITreeEntity<T, TKey> where TKey : struct, IComparable => BuildTree(source.Enumerable2NonNullList(), topValue).ToList();
  187. /// <summary>
  188. /// 平行集合转换成树形结构
  189. /// </summary>
  190. /// <typeparam name="T"></typeparam>
  191. /// <typeparam name="TKey"></typeparam>
  192. /// <param name="source"></param>
  193. /// <param name="idSelector"></param>
  194. /// <param name="pidSelector"></param>
  195. /// <param name="topValue">根对象parentId的值</param>
  196. /// <returns></returns>
  197. public static List<T> ToTree<T, TKey>(this IEnumerable<T> source, Expression<Func<T, TKey>> idSelector, Expression<Func<T, TKey?>> pidSelector, TKey? topValue = null) where T : ITreeChildren<T> where TKey : struct => idSelector.Body.ToString() != pidSelector.Body.ToString() ? BuildTree(source.Enumerable2NonNullList(), idSelector.Compile(), pidSelector.Compile(), topValue).ToList() : throw new ArgumentException("idSelector和pidSelector不应该为同一字段!");
  198. private static IEnumerable<T> BuildTree<T, TKey>(IEnumerable<T> source, Func<T, TKey> idSelector, Func<T, TKey> pidSelector, TKey topValue = default) where T : ITreeChildren<T> where TKey : IComparable
  199. {
  200. // 创建一个字典,用于快速查找节点的子节点
  201. var childrenLookup = new Dictionary<TKey, List<T>>();
  202. var list = source as ICollection<T> ?? source.ToList();
  203. foreach (var item in list.Where(item => !childrenLookup.ContainsKey(idSelector(item))))
  204. {
  205. childrenLookup[idSelector(item)] = [];
  206. }
  207. // 构建树结构
  208. foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item))))
  209. {
  210. childrenLookup[pidSelector(item)].Add(item);
  211. }
  212. // 找到根节点,即没有父节点的节点
  213. foreach (var root in list.Where(x => Equals(pidSelector(x), topValue)))
  214. {
  215. // 为根节点和所有子节点设置Children属性
  216. // 使用队列来模拟递归过程
  217. var queue = new Queue<T>();
  218. queue.Enqueue(root);
  219. while (queue.Count > 0)
  220. {
  221. // 出队当前节点
  222. var current = queue.Dequeue();
  223. // 为当前节点设置子节点
  224. if (childrenLookup.TryGetValue(idSelector(current), out var children))
  225. {
  226. current.Children = children;
  227. foreach (var child in children)
  228. {
  229. // 如果子节点实现了ITreeParent接口,则设置其Parent属性
  230. if (child is ITreeParent<T> tree)
  231. {
  232. tree.Parent = current;
  233. }
  234. // 将子节点入队以继续处理
  235. queue.Enqueue(child);
  236. }
  237. }
  238. }
  239. yield return root;
  240. }
  241. }
  242. internal static IEnumerable<T> BuildTree<T, TKey>(IEnumerable<T> source, TKey? topValue = null) where T : ITreeEntity<T, TKey> where TKey : struct, IComparable => BuildTree(source, t => t.Id, t => t.ParentId, topValue);
  243. internal static IEnumerable<T> BuildTree<T>(IEnumerable<T> source, string topValue = null) where T : ITreeEntity<T> => BuildTree(source, t => t.Id, t => t.ParentId, topValue);
  244. internal static IEnumerable<T> BuildTree<T>(IEnumerable<T> source, T parent) where T : ITreeEntity<T>
  245. {
  246. // 创建一个字典,用于快速查找节点的子节点
  247. var childrenLookup = new NullableDictionary<string, List<T>>();
  248. var list = source as ICollection<T> ?? source.ToList();
  249. foreach (var item in list.Where(item => !childrenLookup.ContainsKey(item.Id)))
  250. childrenLookup[item.Id] = [];
  251. // 构建树结构
  252. foreach (var item in list.Where(item => !string.IsNullOrEmpty(item.ParentId) && childrenLookup.ContainsKey(item.ParentId)))
  253. childrenLookup[item.ParentId].Add(item);
  254. // 找到根节点,即没有父节点的节点
  255. foreach (var root in list.Where(x => x.Id == parent.Id))
  256. {
  257. // 为根节点和所有子节点设置Children属性
  258. // 使用队列来模拟递归过程
  259. var queue = new Queue<T>();
  260. queue.Enqueue(root);
  261. while (queue.Count > 0)
  262. {
  263. // 出队当前节点
  264. var current = queue.Dequeue();
  265. // 为当前节点设置子节点
  266. if (childrenLookup.TryGetValue(current.Id, out var children))
  267. {
  268. current.Children = children;
  269. foreach (var child in children)
  270. {
  271. // 如果子节点实现了ITreeParent接口,则设置其Parent属性
  272. if (child is ITreeParent<T> tree)
  273. tree.Parent = current;
  274. // 将子节点入队以继续处理
  275. queue.Enqueue(child);
  276. }
  277. }
  278. }
  279. yield return root;
  280. }
  281. }
  282. private static IEnumerable<T> BuildTree<T, TKey>(IEnumerable<T> source, Func<T, TKey> idSelector, Func<T, TKey?> pidSelector, TKey? topValue = null) where T : ITreeChildren<T> where TKey : struct
  283. {
  284. // 创建一个字典,用于快速查找节点的子节点
  285. var childrenLookup = new NullableDictionary<TKey, List<T>>();
  286. var list = source as ICollection<T> ?? source.ToList();
  287. foreach (var item in list.Where(item => !childrenLookup.ContainsKey(idSelector(item))))
  288. {
  289. childrenLookup[idSelector(item)] = [];
  290. }
  291. // 构建树结构
  292. foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item) ?? default)))
  293. {
  294. childrenLookup[pidSelector(item) ?? default].Add(item);
  295. }
  296. // 找到根节点,即没有父节点的节点
  297. foreach (var root in list.Where(x => Equals(pidSelector(x), topValue)))
  298. {
  299. // 为根节点和所有子节点设置Children属性
  300. // 使用队列来模拟递归过程
  301. var queue = new Queue<T>();
  302. queue.Enqueue(root);
  303. while (queue.Count > 0)
  304. {
  305. // 出队当前节点
  306. var current = queue.Dequeue();
  307. // 为当前节点设置子节点
  308. if (childrenLookup.TryGetValue(idSelector(current), out var children))
  309. {
  310. current.Children = children;
  311. foreach (var child in children)
  312. {
  313. // 如果子节点实现了ITreeParent接口,则设置其Parent属性
  314. if (child is ITreeParent<T> tree)
  315. {
  316. tree.Parent = current;
  317. }
  318. // 将子节点入队以继续处理
  319. queue.Enqueue(child);
  320. }
  321. }
  322. }
  323. yield return root;
  324. }
  325. }
  326. /// <summary>
  327. /// 平行集合转换成树形结构
  328. /// </summary>
  329. /// <typeparam name="T"></typeparam>
  330. /// <typeparam name="TKey"></typeparam>
  331. /// <param name="source"></param>
  332. /// <param name="idSelector"></param>
  333. /// <param name="pidSelector"></param>
  334. /// <param name="topValue">根对象parentId的值</param>
  335. /// <returns></returns>
  336. public static List<Tree<T>> ToTreeGeneral<T, TKey>(this IEnumerable<T> source, Expression<Func<T, TKey>> idSelector, Expression<Func<T, TKey>> 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不应该为同一字段!");
  337. private static IEnumerable<Tree<T>> BuildTreeGeneral<T, TKey>(List<T> list, Func<T, TKey> idSelector, Func<T, TKey> pidSelector, TKey parent) where TKey : IComparable
  338. {
  339. // 创建一个字典,用于快速查找节点的子节点
  340. var lookup = new Dictionary<TKey, List<Tree<T>>>();
  341. foreach (var item in list.Where(item => !lookup.ContainsKey(idSelector(item))))
  342. {
  343. lookup[idSelector(item)] = [];
  344. }
  345. // 构建树结构
  346. foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && lookup.ContainsKey(pidSelector(item))))
  347. {
  348. lookup[pidSelector(item)].Add(new Tree<T>(item));
  349. }
  350. // 找到根节点,即没有父节点的节点
  351. foreach (var root in list.Where(x => Equals(pidSelector(x), parent)))
  352. {
  353. // 为根节点和所有子节点设置Children属性
  354. var queue = new Queue<Tree<T>>();
  355. var item = new Tree<T>(root);
  356. queue.Enqueue(item);
  357. while (queue.Count > 0)
  358. {
  359. // 出队当前节点
  360. var current = queue.Dequeue();
  361. // 为当前节点设置子节点
  362. if (lookup.TryGetValue(idSelector(current.Value), out var children))
  363. {
  364. current.Children = children;
  365. foreach (var child in children)
  366. {
  367. child.Parent = current.Parent;
  368. // 将子节点入队以继续处理
  369. queue.Enqueue(child);
  370. }
  371. }
  372. }
  373. yield return item;
  374. }
  375. }
  376. /// <summary>
  377. /// 所有子级
  378. /// </summary>
  379. public static ICollection<T> AllChildren<T>(this T tree) where T : ITreeChildren<T> => GetChildren(tree, c => c.Children);
  380. /// <summary>
  381. /// 所有子级
  382. /// </summary>
  383. public static ICollection<T> AllChildren<T>(this T tree, Func<T, IEnumerable<T>> selector) where T : ITreeChildren<T> => GetChildren(tree, selector);
  384. /// <summary>
  385. /// 所有子级
  386. /// </summary>
  387. public static ICollection<Tree<T>> AllChildren<T>(this Tree<T> tree) => GetChildren(tree, c => c.Children);
  388. /// <summary>
  389. /// 所有子级
  390. /// </summary>
  391. public static ICollection<Tree<T>> AllChildren<T>(this Tree<T> tree, Func<Tree<T>, IEnumerable<Tree<T>>> selector) => GetChildren(tree, selector);
  392. /// <summary>
  393. /// 所有父级
  394. /// </summary>
  395. public static List<T> AllParent<T>(this T tree) where T : class, ITreeParent<T> => GetParents(tree, c => c.Parent);
  396. /// <summary>
  397. /// 所有父级
  398. /// </summary>
  399. public static List<T> AllParent<T>(this T tree, Func<T, T> selector) where T : class, ITreeParent<T> => GetParents(tree, selector);
  400. /// <summary>
  401. /// 所有父级
  402. /// </summary>
  403. public static List<Tree<T>> AllParent<T>(this Tree<T> tree, Func<Tree<T>, Tree<T>> selector) => GetParents(tree, selector);
  404. /// <summary>
  405. /// 是否是根节点
  406. /// </summary>
  407. public static bool IsRoot<T>(this ITreeParent<T> tree) where T : ITreeParent<T> => tree.Parent == null;
  408. /// <summary>
  409. /// 是否是叶子节点
  410. /// </summary>
  411. public static bool IsLeaf<T>(this ITreeChildren<T> tree) where T : ITreeChildren<T> => tree.Children?.Count == 0;
  412. /// <summary>
  413. /// 是否是根节点
  414. /// </summary>
  415. public static bool IsRoot<T>(this Tree<T> tree) => tree.Parent == null;
  416. /// <summary>
  417. /// 是否是叶子节点
  418. /// </summary>
  419. public static bool IsLeaf<T>(this Tree<T> tree) => tree.Children?.Count == 0;
  420. /// <summary>
  421. /// 深度层级
  422. /// </summary>
  423. public static int Level<T>(this T tree) where T : class, ITreeParent<T>
  424. {
  425. if (tree == null)
  426. throw new ArgumentNullException(nameof(tree), "当前节点不能为null");
  427. // 使用一个队列来存储待处理的节点
  428. var queue = new Queue<T>();
  429. queue.Enqueue(tree);
  430. int level = 1;
  431. // 循环直到队列为空
  432. while (queue.Count > 0)
  433. {
  434. int nodeCount = queue.Count; // 当前层级的节点数
  435. for (int i = 0; i < nodeCount; i++)
  436. {
  437. // 从队列中出队一个节点
  438. T currentNode = queue.Dequeue();
  439. // 如果当前节点是根节点,则返回当前层级
  440. if (currentNode.Parent == null)
  441. return level;
  442. // 将当前节点的父节点入队
  443. if (currentNode.Parent != null)
  444. queue.Enqueue(currentNode.Parent);
  445. }
  446. // 完成当前层级的遍历,准备进入下一层级
  447. level++;
  448. }
  449. // 如果执行到这里,说明没有找到根节点
  450. throw new InvalidOperationException("未找到根节点");
  451. }
  452. /// <summary>
  453. /// 节点路径(UNIX路径格式,以“/”分隔)
  454. /// </summary>
  455. public static string Path<T>(this T tree) where T : class, ITree<T> => GetFullPath(tree, t => t.Name);
  456. /// <summary>
  457. /// 节点路径(UNIX路径格式,以“/”分隔)
  458. /// </summary>
  459. public static string Path<T>(this T tree, Func<T, string> selector) where T : class, ITreeParent<T> => GetFullPath(tree, selector);
  460. /// <summary>
  461. /// 节点路径
  462. /// </summary>
  463. /// <param name="tree"></param>
  464. /// <param name="separator">分隔符</param>
  465. public static string Path<T>(this T tree, string separator) where T : class, ITree<T> => GetFullPath(tree, t => t.Name, separator);
  466. /// <summary>
  467. /// 节点路径
  468. /// </summary>
  469. /// <typeparam name="T"></typeparam>
  470. /// <param name="tree"></param>
  471. /// <param name="selector">选择字段</param>
  472. /// <param name="separator">分隔符</param>
  473. /// <returns></returns>
  474. public static string Path<T>(this T tree, Func<T, string> selector, string separator) where T : class, ITreeParent<T> => GetFullPath(tree, selector, separator);
  475. /// <summary>
  476. /// 根节点
  477. /// </summary>
  478. public static T Root<T>(this T tree) where T : class, ITreeParent<T> => GetRoot(tree, t => t.Parent);
  479. private static string GetFullPath<T>(T c, Func<T, string> selector, string separator = "/") where T : class, ITreeParent<T>
  480. {
  481. if (c == null || selector == null)
  482. {
  483. throw new ArgumentNullException(c == null ? nameof(c) : nameof(selector), "当前节点或选择器不能为null");
  484. }
  485. // 使用一个栈来存储节点,栈将逆序存储路径中的节点
  486. var stack = new Stack<T>();
  487. var currentNode = c;
  488. while (currentNode != null)
  489. {
  490. stack.Push(currentNode);
  491. currentNode = currentNode.Parent;
  492. }
  493. // 构建路径字符串
  494. return stack.Select(selector).Join(separator);
  495. }
  496. /// <summary>
  497. /// 根节点
  498. /// </summary>
  499. public static T GetRoot<T>(T c, Func<T, T> selector) where T : class, ITreeParent<T>
  500. {
  501. if (c == null || selector == null)
  502. {
  503. throw new ArgumentNullException(c == null ? nameof(c) : nameof(selector), "当前节点或父级选择器不能为null");
  504. }
  505. // 使用一个集合来存储已访问的节点,以避免无限循环
  506. var visited = new HashSet<T>();
  507. var currentNode = c;
  508. // 向上遍历直到找到根节点
  509. while (true)
  510. {
  511. // 如果当前节点已被访问,说明存在循环引用,抛出异常
  512. if (!visited.Add(currentNode))
  513. throw new InvalidOperationException("节点存在循环引用");
  514. var parent = selector(currentNode);
  515. if (parent == null)
  516. {
  517. return currentNode; // 找到了根节点
  518. }
  519. currentNode = parent;
  520. }
  521. }
  522. /// <summary>
  523. /// 递归取出所有下级
  524. /// </summary>
  525. /// <param name="t"></param>
  526. /// <param name="selector"></param>
  527. /// <returns></returns>
  528. private static List<T> GetChildren<T>(T t, Func<T, IEnumerable<T>> selector)
  529. {
  530. if (t == null || selector == null)
  531. return []; // 如果t或selector为null,则返回空列表
  532. var list = new List<T>();
  533. var queue = new Queue<T>();
  534. queue.Enqueue(t);
  535. while (queue.Count > 0)
  536. {
  537. var current = queue.Dequeue();
  538. list.Add(current); // 将当前节点添加到结果列表
  539. var children = selector(current) ?? new List<T>();
  540. foreach (var child in children.Where(child => selector(child).Any()))
  541. {
  542. queue.Enqueue(child);
  543. }
  544. }
  545. return list;
  546. }
  547. /// <summary>
  548. /// 递归取出所有上级
  549. /// </summary>
  550. /// <param name="t"></param>
  551. /// <param name="selector"></param>
  552. /// <returns></returns>
  553. private static List<T> GetParents<T>(T t, Func<T, T> selector) where T : class
  554. {
  555. if (t == null || selector == null)
  556. return []; // 如果t或selector为null,则返回空列表
  557. var parents = new List<T>();
  558. var current = t;
  559. while (current != null)
  560. {
  561. // 添加当前节点到父节点列表
  562. parents.Add(current);
  563. // 通过selector函数获取当前节点的父节点
  564. current = selector(current);
  565. // 如果selector返回了已经遍历过的节点,则停止遍历以避免无限循环
  566. if (parents.Contains(current))
  567. {
  568. break;
  569. }
  570. }
  571. // 由于是逆序添加的,所以需要反转列表
  572. parents.Reverse();
  573. return parents;
  574. }
  575. }
  576. /// <summary>
  577. /// 树形数据扩展 long
  578. /// </summary>
  579. public static class TreeExtensionLong
  580. {
  581. /// <summary>
  582. /// 平行集合转换成树形结构
  583. /// </summary>
  584. /// <typeparam name="T"></typeparam>
  585. /// <param name="source"></param>
  586. /// <returns></returns>
  587. public static List<T> ToTree<T>(this IEnumerable<T> source) where T : class, ITreeEntity<T, long> => source.ToTree<T, long>();
  588. }
  589. /// <summary>
  590. /// 树形数据扩展 guid
  591. /// </summary>
  592. public static class TreeExtensionGuid
  593. {
  594. /// <summary>
  595. /// 平行集合转换成树形结构
  596. /// </summary>
  597. /// <typeparam name="T"></typeparam>
  598. /// <param name="source"></param>
  599. /// <returns></returns>
  600. public static List<T> ToTree<T>(this IEnumerable<T> source) where T : class, ITreeEntity<T, Guid> => source.ToTree<T, Guid>();
  601. }
  602. /// <summary>
  603. /// 树形数据扩展 string
  604. /// </summary>
  605. public static class TreeExtensionString
  606. {
  607. /// <summary>
  608. /// 平行集合转换成树形结构
  609. /// </summary>
  610. /// <typeparam name="T"></typeparam>
  611. /// <param name="source"></param>
  612. /// <param name="topValue"></param>
  613. /// <returns></returns>
  614. public static List<T> ToTree<T>(this IEnumerable<T> source, string topValue) where T : class, ITreeEntity<T> => TreeExtensions.BuildTree(source.Enumerable2NonNullList(), topValue).ToList();
  615. /// <summary>
  616. /// 平行集合转换成树形结构
  617. /// </summary>
  618. /// <typeparam name="T"></typeparam>
  619. /// <param name="source"></param>
  620. /// <returns></returns>
  621. public static List<T> ToTree<T>(this IEnumerable<T> source) where T : class, ITreeEntity<T> => source.Enumerable2NonNullList().Where(t => string.IsNullOrEmpty(t.ParentId)).SelectMany(parent => TreeExtensions.BuildTree(source, parent)).ToList();
  622. }
  623. internal static class TreeExtensionCommon
  624. {
  625. /// <summary>
  626. /// 将IEnumerable转换成不含null的List,若T为IQueryable,则将数据读取到内存
  627. /// </summary>
  628. /// <typeparam name="T"></typeparam>
  629. /// <param name="source"></param>
  630. /// <returns></returns>
  631. public static List<T> Enumerable2NonNullList<T>(this IEnumerable<T> source)
  632. {
  633. source = source is IQueryable<T> queryable ? queryable.AsEnumerable() : source;
  634. return (typeof(T).IsValueType ? source : source.Where(t => t != null)).ToList();
  635. }
  636. }