Browse Source

ToTree函数非递归实现

懒得勤快 1 year ago
parent
commit
54ca14a00a

+ 1 - 1
Masuit.Tools.Abstractions/Masuit.Tools.Abstractions.csproj

@@ -3,7 +3,7 @@
         <TargetFrameworks>netstandard2.0;netstandard2.1;net461;net5;net6;net7;net8</TargetFrameworks>
         <LangVersion>latest</LangVersion>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-        <Version>2024.3</Version>
+        <Version>2024.3.2</Version>
         <Authors>懒得勤快</Authors>
         <Description>全龄段友好的C#万能工具库,码数吐司库,不管你是菜鸟新手还是骨灰级玩家都能轻松上手,Masuit.Tools基础公共库(适用于.NET4.6.1/.NET Standard2.0及以上项目),包含一些常用的操作类,大都是静态类,加密解密,反射操作,Excel简单导出,权重随机筛选算法,分布式短id,表达式树,linq扩展,文件压缩,多线程下载和FTP客户端,硬件信息,字符串扩展方法,日期时间扩展操作,中国农历,大文件拷贝,图像裁剪,验证码,断点续传,集合扩展等常用封装。
             官网教程:https://masuit.tools

+ 26 - 41
Masuit.Tools.Abstractions/Models/TreeExtensions.cs

@@ -283,7 +283,7 @@ namespace Masuit.Tools.Models
 
             var pidFunc = pidSelector.Compile();
             var idFunc = idSelector.Compile();
-            return TransData(source.Where(t => t != null), idFunc, pidFunc, topValue).ToList();
+            return BuildTree(source.Where(t => t != null), idFunc, pidFunc, topValue).ToList();
         }
 
         /// <summary>
@@ -304,7 +304,7 @@ namespace Masuit.Tools.Models
         /// <typeparam name="TKey"></typeparam>
         /// <param name="source"></param>
         /// <returns></returns>
-        public static List<T> ToTree<T, TKey>(this IEnumerable<T> source) where T : ITreeEntity<T, TKey> where TKey : struct, IComparable
+        public static List<T> ToTree<T, TKey>(this IEnumerable<T> source, TKey? topValue = default) where T : ITreeEntity<T, TKey> where TKey : struct, IComparable
         {
             if (source is IQueryable<T> queryable)
             {
@@ -312,13 +312,7 @@ namespace Masuit.Tools.Models
             }
 
             source = source.Where(t => t != null).ToList();
-            var temp = new List<T>();
-            foreach (var item in source.Where(item => item.ParentId is null || item.ParentId.Equals(default)))
-            {
-                temp.AddRange(TransData<T, TKey>(source, item));
-            }
-
-            return temp;
+            return BuildTree(source, topValue).ToList();
         }
 
         /// <summary>
@@ -341,16 +335,10 @@ namespace Masuit.Tools.Models
             var pidFunc = pidSelector.Compile();
             var idFunc = idSelector.Compile();
             source = source.Where(t => t != null).ToList();
-            var temp = new List<T>();
-            foreach (var item in source.Where(item => pidFunc(item) is null || pidFunc(item).Equals(topValue)))
-            {
-                temp.AddRange(TransData(source, item, idFunc, pidFunc));
-            }
-
-            return temp;
+            return BuildTree(source, idFunc, pidFunc, topValue).ToList();
         }
 
-        private static IEnumerable<T> TransData<T, TKey>(IEnumerable<T> source, Func<T, TKey> idSelector, Func<T, TKey> pidSelector, TKey topValue = default) where T : ITreeChildren<T> where TKey : IComparable
+        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
         {
             // 创建一个字典,用于快速查找节点的子节点
             var childrenLookup = new Dictionary<TKey, List<T>>();
@@ -360,7 +348,7 @@ namespace Masuit.Tools.Models
             }
 
             // 构建树结构
-            foreach (var item in source.Where(item => Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item))))
+            foreach (var item in source.Where(item => !Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item))))
             {
                 childrenLookup[pidSelector(item)].Add(item);
             }
@@ -395,27 +383,28 @@ namespace Masuit.Tools.Models
                         }
                     }
                 }
+
                 yield return root;
             }
         }
 
-        internal static IEnumerable<T> TransData<T, TKey>(IEnumerable<T> source, T parent) where T : ITreeEntity<T, TKey> where TKey : struct, IComparable
+        internal static IEnumerable<T> BuildTree<T, TKey>(IEnumerable<T> source, TKey? topValue = default) where T : ITreeEntity<T, TKey> where TKey : struct, IComparable
         {
             // 创建一个字典,用于快速查找节点的子节点
-            var childrenLookup = new Dictionary<TKey?, List<T>>();
+            var childrenLookup = new NullableDictionary<TKey, List<T>>();
             foreach (var item in source.Where(item => !childrenLookup.ContainsKey(item.Id)))
             {
                 childrenLookup[item.Id] = new List<T>();
             }
 
             // 构建树结构
-            foreach (var item in source.Where(item => Equals(item.ParentId, default(TKey)) && childrenLookup.ContainsKey(item.ParentId)))
+            foreach (var item in source.Where(item => (item.ParentId != null || !Equals(item.ParentId, default(TKey))) && childrenLookup.ContainsKey(item.ParentId ?? default)))
             {
-                childrenLookup[item.ParentId].Add(item);
+                childrenLookup[item.ParentId ?? default].Add(item);
             }
 
             // 找到根节点,即没有父节点的节点
-            foreach (var root in source.Where(x => Equals(x.ParentId, parent.Id)))
+            foreach (var root in source.Where(x => Equals(x.ParentId, topValue)))
             {
                 // 为根节点和所有子节点设置Children属性
                 // 使用队列来模拟递归过程
@@ -444,11 +433,12 @@ namespace Masuit.Tools.Models
                         }
                     }
                 }
+
                 yield return root;
             }
         }
 
-        internal static IEnumerable<T> TransData<T>(IEnumerable<T> source, T parent) where T : ITreeEntity<T>
+        internal static IEnumerable<T> BuildTree<T>(IEnumerable<T> source, string topValue = null) where T : ITreeEntity<T>
         {
             // 创建一个字典,用于快速查找节点的子节点
             var childrenLookup = new NullableDictionary<string, List<T>>();
@@ -458,13 +448,13 @@ namespace Masuit.Tools.Models
             }
 
             // 构建树结构
-            foreach (var item in source.Where(item => string.IsNullOrEmpty(item.ParentId) && childrenLookup.ContainsKey(item.ParentId)))
+            foreach (var item in source.Where(item => !string.IsNullOrEmpty(item.ParentId) && childrenLookup.ContainsKey(item.ParentId)))
             {
                 childrenLookup[item.ParentId].Add(item);
             }
 
             // 找到根节点,即没有父节点的节点
-            foreach (var root in source.Where(x => Equals(x.ParentId, parent.Id)))
+            foreach (var root in source.Where(x => Equals(x.ParentId, topValue)))
             {
                 // 为根节点和所有子节点设置Children属性
                 // 使用队列来模拟递归过程
@@ -497,7 +487,7 @@ namespace Masuit.Tools.Models
             }
         }
 
-        private static IEnumerable<T> TransData<T, TKey>(IEnumerable<T> source, T parent, Func<T, TKey> idSelector, Func<T, TKey?> pidSelector) where T : ITreeChildren<T> where TKey : struct
+        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 : struct
         {
             // 创建一个字典,用于快速查找节点的子节点
             var childrenLookup = new NullableDictionary<TKey, List<T>>();
@@ -507,13 +497,13 @@ namespace Masuit.Tools.Models
             }
 
             // 构建树结构
-            foreach (var item in source.Where(item => Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item).Value)))
+            foreach (var item in source.Where(item => !Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item) ?? default)))
             {
-                childrenLookup[pidSelector(item).Value].Add(item);
+                childrenLookup[pidSelector(item) ?? default].Add(item);
             }
 
             // 找到根节点,即没有父节点的节点
-            foreach (var root in source.Where(x => Equals(pidSelector(x), idSelector(parent))))
+            foreach (var root in source.Where(x => Equals(pidSelector(x), topValue)))
             {
                 // 为根节点和所有子节点设置Children属性
                 // 使用队列来模拟递归过程
@@ -570,20 +560,20 @@ namespace Masuit.Tools.Models
             foreach (var item in source.Where(item => pidFunc(item) is null || pidFunc(item).Equals(topValue)))
             {
                 var parent = new Tree<T>(item);
-                TransData(source, parent, idFunc, pidFunc);
+                BuildTree(source, parent, idFunc, pidFunc);
                 temp.Add(parent);
             }
 
             return temp;
         }
 
-        private static void TransData<T, TKey>(IEnumerable<T> source, Tree<T> parent, Func<T, TKey> idSelector, Func<T, TKey> pidSelector) where TKey : IComparable
+        private static void BuildTree<T, TKey>(IEnumerable<T> source, Tree<T> parent, Func<T, TKey> idSelector, Func<T, TKey> pidSelector) where TKey : IComparable
         {
             var temp = new List<Tree<T>>();
             foreach (var item in source.Where(item => pidSelector(item)?.Equals(idSelector(parent.Value)) == true))
             {
                 var p = new Tree<T>(item);
-                TransData(source, p, idSelector, pidSelector);
+                BuildTree(source, p, idSelector, pidSelector);
                 p.Parent = parent.Value;
                 temp.Add(p);
             }
@@ -754,8 +744,9 @@ namespace Masuit.Tools.Models
         /// </summary>
         /// <typeparam name="T"></typeparam>
         /// <param name="source"></param>
+        /// <param name="topValue"></param>
         /// <returns></returns>
-        public static List<T> ToTree<T>(this IEnumerable<T> source) where T : ITreeEntity<T>
+        public static List<T> ToTree<T>(this IEnumerable<T> source, string topValue = null) where T : ITreeEntity<T>
         {
             if (source is IQueryable<T> queryable)
             {
@@ -763,13 +754,7 @@ namespace Masuit.Tools.Models
             }
 
             source = source.Where(t => t != null).ToList();
-            var temp = new List<T>();
-            foreach (var item in source.Where(item => item.ParentId is null || item.ParentId.Equals(default)))
-            {
-                temp.AddRange(TreeExtensions.TransData(source, item));
-            }
-
-            return temp;
+            return TreeExtensions.BuildTree(source, topValue).ToList();
         }
     }
 }

+ 1 - 1
Masuit.Tools.AspNetCore/Masuit.Tools.AspNetCore.csproj

@@ -18,7 +18,7 @@
         <Product>Masuit.Tools.AspNetCore</Product>
         <PackageId>Masuit.Tools.AspNetCore</PackageId>
         <LangVersion>latest</LangVersion>
-        <Version>2024.3</Version>
+        <Version>2024.3.2</Version>
         <RepositoryType></RepositoryType>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
         <FileVersion>1.1.9</FileVersion>

+ 1 - 1
Masuit.Tools.Core/Masuit.Tools.Core.csproj

@@ -6,7 +6,7 @@
 官网教程:https://tools.masuit.org
 github:https://github.com/ldqk/Masuit.Tools
         </Description>
-        <Version>2024.3</Version>
+        <Version>2024.3.2</Version>
         <Copyright>Copyright © 懒得勤快</Copyright>
         <PackageProjectUrl>https://github.com/ldqk/Masuit.Tools</PackageProjectUrl>
         <PackageTags>Masuit.Tools,工具库,Utility,Crypt,Extensions</PackageTags>

+ 1 - 1
Masuit.Tools.Excel/Masuit.Tools.Excel.csproj

@@ -3,7 +3,7 @@
         <TargetFramework>netstandard2.0</TargetFramework>
         <LangVersion>latest</LangVersion>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-        <Version>2024.3</Version>
+        <Version>2024.3.2</Version>
         <Authors>懒得勤快</Authors>
         <Description>Masuit.Tools.Excel导出库,支持一些简单数据的导出,支持图片列</Description>
         <Copyright>懒得勤快</Copyright>

+ 1 - 1
Masuit.Tools.Net45/package.nuspec

@@ -2,7 +2,7 @@
 <package>
     <metadata>
         <id>Masuit.Tools.Net45</id>
-        <version>2024.3</version>
+        <version>2024.3.2</version>
         <title>Masuit.Tools</title>
         <authors>懒得勤快</authors>
         <owners>masuit.com</owners>

+ 1 - 1
Masuit.Tools/package.nuspec

@@ -2,7 +2,7 @@
 <package>
     <metadata>
         <id>Masuit.Tools.Net</id>
-        <version>2024.3</version>
+        <version>2024.3.2</version>
         <title>Masuit.Tools</title>
         <authors>懒得勤快</authors>
         <owners>masuit.com</owners>

+ 4 - 0
Test/Masuit.Tools.Abstractions.Test/Masuit.Tools.Abstractions.Test.csproj

@@ -29,4 +29,8 @@
     <ProjectReference Include="..\..\Masuit.Tools.Abstractions\Masuit.Tools.Abstractions.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="Tree\" />
+  </ItemGroup>
+
 </Project>

+ 157 - 0
Test/Masuit.Tools.Abstractions.Test/Tree/TreeTest.cs

@@ -0,0 +1,157 @@
+using System.Collections.Generic;
+using System.Linq;
+using Masuit.Tools.Models;
+using Xunit;
+
+namespace Masuit.Tools.Abstractions.Test.Tree;
+
+public class TreeTest
+{
+    [Fact]
+    public void Can_BuildTree()
+    {
+        // arrange
+        var list = new List<MyClass>()
+        {
+            new MyClass
+            {
+                Name = "Root",
+                Id = 1
+            },
+            new MyClass
+            {
+                Name = "Root",
+                Id = 20000
+            }
+        };
+        for (int i = 2; i < 1500; i++)
+        {
+            list.Add(new MyClass
+            {
+                Name = $"这是第{i}个子节点",
+                Id = i,
+                ParentId = (i - 1)
+            });
+        }
+
+        for (int i = 20001; i < 40000; i++)
+        {
+            list.Add(new MyClass
+            {
+                Name = $"这是第{i}个子节点",
+                Id = i,
+                ParentId = (i - 1)
+            });
+        }
+
+        // act
+        var tree = list.ToTree();
+
+        // assert
+        Assert.Equal(tree[0].Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Id, 8);
+        Assert.Equal(tree.Count, 2);
+        Assert.Equal(tree[0].AllChildren().Count, 1498);
+    }
+
+    [Fact]
+    public void Can_BuildTree2()
+    {
+        // arrange
+        var list = new List<MyClass2>()
+        {
+            new MyClass2
+            {
+                Name = "Root",
+                Id = "1"
+            },
+            new MyClass2
+            {
+                Name = "Root",
+                Id = "2000"
+            }
+        };
+        for (int i = 2; i < 1500; i++)
+        {
+            list.Add(new MyClass2
+            {
+                Name = $"这是第{i}个子节点",
+                Id = i.ToString(),
+                ParentId = (i - 1).ToString()
+            });
+        }
+
+        for (int i = 2001; i < 4000; i++)
+        {
+            list.Add(new MyClass2
+            {
+                Name = $"这是第{i}个子节点",
+                Id = i.ToString(),
+                ParentId = (i - 1).ToString()
+            });
+        }
+
+        // act
+        var tree = list.ToTree();
+
+        // assert
+        Assert.Equal(tree[0].Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Children.FirstOrDefault().Id, "8");
+        Assert.Equal(tree.Count, 2);
+        Assert.Equal(tree[0].AllChildren().Count, 1498);
+    }
+}
+
+internal class MyClass : ITree<MyClass>, ITreeEntity<MyClass, int>
+{
+    /// <summary>
+    /// 父节点
+    /// </summary>
+    public MyClass Parent { get; set; }
+
+    /// <summary>
+    /// 子级
+    /// </summary>
+    public ICollection<MyClass> Children { get; set; }
+
+    /// <summary>
+    /// 名字
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 主键id
+    /// </summary>
+    public int Id { get; set; }
+
+    /// <summary>
+    /// 父级id
+    /// </summary>
+    public int? ParentId { get; set; }
+}
+
+internal class MyClass2 : ITree<MyClass2>, ITreeEntity<MyClass2>
+{
+    /// <summary>
+    /// 父节点
+    /// </summary>
+    public MyClass2 Parent { get; set; }
+
+    /// <summary>
+    /// 子级
+    /// </summary>
+    public ICollection<MyClass2> Children { get; set; }
+
+    /// <summary>
+    /// 名字
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 主键id
+    /// </summary>
+    public string Id { get; set; }
+
+    /// <summary>
+    /// 父级id
+    /// </summary>
+    public string ParentId { get; set; }
+}