├── .gitignore ├── BTFindTree.sln ├── LICENSE ├── README.md ├── samples └── SelfTest │ ├── Class1.cs │ ├── Program.cs │ └── SelfTest.csproj ├── src └── BTFindTree │ ├── BTFTemplate.cs │ ├── BTFindTree.csproj │ ├── Model │ └── StringExtension.cs │ └── Template │ ├── FuzzyTreeTemplate │ └── FuzzyPointTree.cs │ └── PrecisionTreeTemplate │ ├── Model │ ├── FrequencyModel.cs │ ├── PriorityTreeModel.cs │ └── RepeateModel.cs │ └── PrecisionMinPriorityTree.cs └── test ├── BTFUT ├── BTFUT.csproj └── FindTreeTest.cs └── BenchmarkTest ├── BenchmarkTest.csproj ├── FindTreeTest.cs └── Program.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /BTFindTree.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTFindTree", "src\BTFindTree\BTFindTree.csproj", "{7E28920B-A530-4E53-A939-10CA104FFCE6}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{11A28CBF-253A-4490-ADF0-FA775766BAB4}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8734A952-32DB-43BB-A02E-119CCE41E8ED}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F171984D-3F83-4FB3-912B-3D496B5EBAE5}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfTest", "samples\SelfTest\SelfTest.csproj", "{C627000B-AAD5-48CC-AFB0-8EA633BA2A6A}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkTest", "test\BenchmarkTest\BenchmarkTest.csproj", "{71518E70-2FE0-467E-9AD0-4491CCBB125E}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTFUT", "test\BTFUT\BTFUT.csproj", "{4D2707E5-2201-4809-A7AD-66142A45DDCE}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {7E28920B-A530-4E53-A939-10CA104FFCE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {7E28920B-A530-4E53-A939-10CA104FFCE6}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {7E28920B-A530-4E53-A939-10CA104FFCE6}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {7E28920B-A530-4E53-A939-10CA104FFCE6}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {C627000B-AAD5-48CC-AFB0-8EA633BA2A6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {C627000B-AAD5-48CC-AFB0-8EA633BA2A6A}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {C627000B-AAD5-48CC-AFB0-8EA633BA2A6A}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {C627000B-AAD5-48CC-AFB0-8EA633BA2A6A}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {71518E70-2FE0-467E-9AD0-4491CCBB125E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {71518E70-2FE0-467E-9AD0-4491CCBB125E}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {71518E70-2FE0-467E-9AD0-4491CCBB125E}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {71518E70-2FE0-467E-9AD0-4491CCBB125E}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {4D2707E5-2201-4809-A7AD-66142A45DDCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {4D2707E5-2201-4809-A7AD-66142A45DDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {4D2707E5-2201-4809-A7AD-66142A45DDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {4D2707E5-2201-4809-A7AD-66142A45DDCE}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(NestedProjects) = preSolution 47 | {7E28920B-A530-4E53-A939-10CA104FFCE6} = {11A28CBF-253A-4490-ADF0-FA775766BAB4} 48 | {C627000B-AAD5-48CC-AFB0-8EA633BA2A6A} = {F171984D-3F83-4FB3-912B-3D496B5EBAE5} 49 | {71518E70-2FE0-467E-9AD0-4491CCBB125E} = {8734A952-32DB-43BB-A02E-119CCE41E8ED} 50 | {4D2707E5-2201-4809-A7AD-66142A45DDCE} = {8734A952-32DB-43BB-A02E-119CCE41E8ED} 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {C1B88729-05E2-469E-B598-D7CBA5F9EBF1} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 .NET Labs. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BTFindTree 2 | 此项目为 Leo 项目中的高效查找数据结构,使用二分+桶的方式优化查找性能。 3 | 4 | # 使用场景 5 | 6 | 为了动态构建高性能查找‘字典’所提供的算法,可以结合 Natasha 创建出无锁高并发的键值对查找功能。 7 | 性能好于并发字典,在高频访问场景下比较有用。 8 | 9 | # 具体算法 10 | 11 | - Hash 二分查找树 12 | 13 | 该算法将使用字典中的 Key 作为查找依据,通过 roslyn Release模式优化成二三查找树,提供高性能的查找方法。 14 | 15 | - 模糊指针查找树 16 | 17 | 该算法针对 Key 类型为字符串的场景,使用者传入字符串后算法将自动寻找特征点,没用的字符会被跳过,因为仅匹配特征,所以性能超高。 18 | 但满足特征的字串都可执行 value, 该算法的使用场景通常是作者经过深思熟虑后的。 19 | 20 | - 归并最小权查找树 21 | 22 | 该算法也称为精确指针查找树,通过分治处理每个字符串和匹配次数,然后归并结果,计算最小权值,相比模糊指针查找树, 23 | 该算法是精确的,它虽然利用了特征,但是没有进行跳跃处理,该匹配的都要匹配到才能拿到结果。 24 | 25 | 26 | 27 | 28 | # 使用方法 29 | 30 | 使用前提: 31 | 32 | 了解并发字典的使用方法。 33 | 34 | - 使用自定义二分查找树 35 | 36 | ```C# 37 | 38 | var dict = Dictionary(); 39 | dict["a"] = "return 1;"; 40 | dict["abc"] = "return 2;"; 41 | 42 | var script = BTFTemplate.GetCustomerBTFScript(dict,"arg.GetHashCode()",item=>item.GetHashCode().ToString())+"return default;"; 43 | 以上便构建了一段完整的利用HashCode查找的方法体。 44 | //如switch(arg.GetHashCode()){ case 28273847: xxx } 45 | //其中case 28273847 是由 GetCustomerBTFScript 第三个参数,Func 委托得到的。 46 | 47 | ``` 48 | 49 | - 使用 Hash 二分查找树 50 | 51 | ```C# 52 | 53 | //Key : 可以为任意类型,因为真正用到T的是它的HashCode 54 | //value: 比如是字符串; return 1;/ Action(a); / a=1; 等正常代码字符串。 55 | // 作用是当用户传入 key 的时候执行 value. 56 | 57 | var dict = Dictionary(); 58 | dict["a"] = "return 1;"; 59 | dict["abc"] = "return 2;"; 60 | string result = BTFTemplate.GetHashBTFScript( dict ); 61 | 62 | //拿到 result 使用 natasha 构造。 63 | //例如:HashDelegate = NDomain.Random().UnsafeFunc(BTFTemplate.GetHashBTFScript(ScriptDict) + "return default;"); 64 | 65 | ``` 66 | 67 | - 使用模糊指针查找树 68 | 69 | 70 | ```C# 71 | 72 | //Key : 必须是字串,因为模糊树和最小权都是针对字串的一种算法结构 73 | //value: 比如是字符串; return 1;/ Action(a); / a=1; 等正常代码字符串。 74 | // 作用是当用户传入 key 的时候执行 value. 75 | 76 | var dict = Dictionary(); 77 | dict["a"] = "return 1;"; 78 | dict["abc"] = "return 2;"; 79 | string result = BTFTemplate.GetGroupFuzzyPointBTFScript( dict ); 80 | 81 | //拿到 result 使用 natasha 构造。 82 | //例如:HashDelegate = NDomain.Random().UnsafeFunc(BTFTemplate.GetFuzzyPointBTFScript(ScriptDict) + "return default;"); 83 | 84 | ``` 85 | 86 | - 使用归并最小权查找树 87 | 88 | 89 | ```C# 90 | 91 | //Key : 必须是字串,因为模糊树和最小权都是针对字串的一种算法结构 92 | //value: 比如是字符串; return 1;/ Action(a); / a=1; 等正常代码字符串。 93 | // 作用是当用户传入 key 的时候执行 value. 94 | 95 | var dict = Dictionary(); 96 | dict["a"] = "return 1;"; 97 | dict["abc"] = "return 2;"; 98 | string result = BTFTemplate.GetGroupPrecisionPointBTFScript( dict ); 99 | 100 | //拿到 result 使用 natasha 构造。 101 | //例如:HashDelegate = NDomain.Random().UnsafeFunc(BTFTemplate.GetPrecisionPointBTFScript(ScriptDict) + "return default;"); 102 | 103 | ``` 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /samples/SelfTest/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SelfTest 6 | { 7 | class Class1 8 | { 9 | public unsafe int A(string name) 10 | { 11 | fixed (char* c = name) 12 | { 13 | switch (*(short*)(c + 2)) 14 | { 15 | case 97: 16 | switch (*(short*)(c + 4)) 17 | { 18 | case 49: 19 | switch (*(short*)(c + 8)) 20 | { 21 | case 0: 22 | return 0; 23 | case 99: 24 | return 1; 25 | case 100: 26 | return 2; 27 | } 28 | break; 29 | case 50: 30 | switch (*(short*)(c + 9)) 31 | { 32 | case 0: 33 | return 3; 34 | case 114: 35 | var a = 1 + 1; 36 | break; 37 | } 38 | break; 39 | case 51: 40 | return 4; 41 | } 42 | break; 43 | case 99: 44 | switch (*(short*)(c + 6)) 45 | { 46 | case 102: 47 | return 5; 48 | case 103: 49 | return 6; 50 | case 105: 51 | return 7; 52 | case 104: 53 | return 8; 54 | case 106: 55 | return 9; 56 | } 57 | break; 58 | } 59 | } 60 | return default; 61 | 62 | } 63 | 64 | 65 | public int B(string name) 66 | { 67 | switch (name.GetHashCode()) 68 | { 69 | case -397903024: 70 | return 1; 71 | case 259107356: 72 | return 2; 73 | case -810005667: 74 | return 3; 75 | case 1043528272: 76 | var a = 1 + 1; 77 | break; 78 | case 1436709466: 79 | return 4; 80 | case 1834649015: 81 | return 5; 82 | case -1327692258: 83 | return 6; 84 | case -1655823507: 85 | return 7; 86 | case 1432890083: 87 | return 8; 88 | case -292139003: 89 | return 9; 90 | } 91 | return default; 92 | 93 | } 94 | 95 | 96 | 97 | public unsafe int C(string name) 98 | { 99 | fixed (char* c = name) 100 | { 101 | switch (*(ulong*)(c + 0)) 102 | { 103 | case 27584964335894625: 104 | if (*(ulong*)(c + 4) == 14355434268917810) 105 | { 106 | if (*(ushort*)(c + 8) == 101) 107 | { 108 | switch (*(ushort*)(c + 9)) 109 | { 110 | case 114: 111 | var a = 1 + 1; 112 | break; 113 | case 0: 114 | return 3; 115 | } 116 | } 117 | } 118 | break; 119 | case 28147922879250529: 120 | if (*(ulong*)(c + 4) == 442388316261) 121 | { 122 | return 6; 123 | 124 | } 125 | break; 126 | } 127 | } 128 | return default; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /samples/SelfTest/Program.cs: -------------------------------------------------------------------------------- 1 | using BTFindTree; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace SelfTest 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | 12 | Dictionary dict = new Dictionary(); 13 | dict["abcdefh"] = "return 9;"; 14 | dict["abcdefj"] = "return 10;"; 15 | dict["a"] = "return 11;"; 16 | 17 | //dict["abab1123"] = "return 0;"; 18 | //dict["abab1123c"] = "return 1;"; 19 | //dict["abab1123d"] = "return 2;"; 20 | 21 | //dict["abab2213e"] = "return 3;"; 22 | //dict["abab2213er"] = "var a = 1 + 1;"; 23 | 24 | //dict["abab3213f"] = "return 4;"; 25 | //dict["abcdeff"] = "return 5;"; 26 | //dict["abcdefg"] = "return 6;"; 27 | //dict["abcdefi"] = "return 7;"; 28 | //dict["abcdefh"] = "return 8;"; 29 | //dict["abcdefj"] = "return 9;"; 30 | //Console.WriteLine(BTFTemplate.GetFuzzyPointBTFScript(dict)); 31 | Console.WriteLine(BTFTemplate.GetPrecisionPointBTFScript(dict)); 32 | //Console.WriteLine(BTFTemplate.GetHashBTFScript(dict)); 33 | Class1 a = new Class1(); 34 | Console.WriteLine(a.C("abab2213e")); 35 | Console.ReadKey(); 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/SelfTest/SelfTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | true 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/BTFindTree/BTFTemplate.cs: -------------------------------------------------------------------------------- 1 | using BTFindTree.Template.PrecisionTreeTemplate.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace BTFindTree 8 | { 9 | public class BTFTemplate 10 | { 11 | 12 | public static string GetCustomerBTFScript(IDictionary pairs, string switchCode, Func caseCode = null) 13 | { 14 | 15 | if (pairs == default || pairs.Count == 0) 16 | { 17 | return default; 18 | } 19 | 20 | 21 | StringBuilder scriptBuilder = new StringBuilder(); 22 | scriptBuilder.Append($"switch({switchCode}){{"); 23 | 24 | 25 | foreach (var item in pairs) 26 | { 27 | 28 | scriptBuilder.AppendLine($"case {caseCode?.Invoke(item.Key)}:"); 29 | scriptBuilder.AppendLine(item.Value); 30 | //if (!item.Value.Contains("return ")) 31 | //{ 32 | // scriptBuilder.AppendLine("break;"); 33 | //} 34 | 35 | } 36 | 37 | 38 | scriptBuilder.Append("}"); 39 | return scriptBuilder.ToString(); 40 | 41 | } 42 | 43 | 44 | 45 | 46 | public static string GetHashBTFScript(IDictionary pairs, string parameterName = "arg") 47 | { 48 | 49 | if (pairs == default || pairs.Count == 0) 50 | { 51 | return default; 52 | } 53 | 54 | 55 | StringBuilder scriptBuilder = new StringBuilder(); 56 | scriptBuilder.Append($"switch({parameterName}.GetHashCode()){{"); 57 | 58 | 59 | foreach (var item in pairs) 60 | { 61 | 62 | scriptBuilder.AppendLine($"case {item.Key.GetHashCode()}:"); 63 | scriptBuilder.AppendLine(item.Value); 64 | if (!item.Value.Contains("return ")) 65 | { 66 | scriptBuilder.AppendLine("break;"); 67 | } 68 | 69 | } 70 | 71 | 72 | scriptBuilder.Append("}"); 73 | return scriptBuilder.ToString(); 74 | 75 | } 76 | 77 | 78 | 79 | public static string GetGroupFuzzyPointBTFScript(IDictionary pairs, string parameterName = "arg") 80 | { 81 | 82 | if (pairs == default || pairs.Count == 0) 83 | { 84 | return default; 85 | } 86 | 87 | Dictionary> groups = new Dictionary>(); 88 | foreach (var item in pairs) 89 | { 90 | int length = item.Key.Length; 91 | if (!groups.ContainsKey(length)) 92 | { 93 | groups[length] = new Dictionary(); 94 | } 95 | groups[length][item.Key] = item.Value; 96 | 97 | } 98 | 99 | StringBuilder scriptBuilder = new StringBuilder(); 100 | Dictionary result = new Dictionary(); 101 | foreach (var item in groups) 102 | { 103 | 104 | result[item.Key] = GetFuzzyPointBTFScript(item.Value, parameterName) + "break;"; 105 | 106 | } 107 | scriptBuilder.AppendLine(GetCustomerBTFScript(result, "btfParameterLength", item => item.ToString())); 108 | 109 | 110 | scriptBuilder.Insert(0, $"int btfParameterLength = {parameterName}.Length;"); 111 | return scriptBuilder.ToString(); 112 | 113 | } 114 | 115 | 116 | 117 | public static string GetFuzzyPointBTFScript(IDictionary pairs, string parameterName = "arg") 118 | { 119 | 120 | if (pairs == default || pairs.Count == 0) 121 | { 122 | return default; 123 | } 124 | 125 | StringBuilder scriptBuilder = new StringBuilder(); 126 | if (pairs.Count == 1) 127 | { 128 | foreach (var item in pairs) 129 | { 130 | scriptBuilder.AppendLine($"if({parameterName}[0] == '{item.Key[0]}'){{ {item.Value} }}"); 131 | } 132 | 133 | } 134 | else 135 | { 136 | scriptBuilder.AppendLine($"fixed (char* c = {parameterName}){{"); 137 | 138 | 139 | FuzzyPointTree tree = new FuzzyPointTree(pairs); 140 | scriptBuilder.Append(ForeachFuzzyTree(tree)); 141 | 142 | 143 | scriptBuilder.Append("}"); 144 | } 145 | 146 | 147 | 148 | return scriptBuilder.ToString(); 149 | 150 | } 151 | 152 | 153 | 154 | 155 | private static StringBuilder ForeachFuzzyTree(FuzzyPointTree tree) 156 | { 157 | 158 | StringBuilder scriptBuilder = new StringBuilder(); 159 | 160 | if (tree.Value != default) 161 | { 162 | 163 | //如果是叶节点 164 | scriptBuilder.AppendLine($"case {tree.PointCode}:"); 165 | scriptBuilder.AppendLine(tree.Value); 166 | if (!tree.Value.Contains("return ")) 167 | { 168 | scriptBuilder.AppendLine("break;"); 169 | } 170 | 171 | } 172 | else 173 | { 174 | 175 | //设置头节点 176 | var node = tree; 177 | 178 | 179 | //value==default 不为空则 Nodes 定有值 180 | //如果是单节点,则直接优化掉,节点层数+1 181 | while (node.Nodes != default && node.Nodes.Count == 1) 182 | { 183 | node = node.Nodes[0]; 184 | tree.Layer += 1; 185 | } 186 | 187 | 188 | //如果头节点移动了(允许优化) 189 | if (node != tree) 190 | { 191 | //则头节点重新赋值 192 | tree.Nodes = node.Nodes; 193 | } 194 | 195 | 196 | //一个集合必然已switch开头 197 | scriptBuilder.Append($"switch (*({FuzzyPointTree.OfferType}*)(c+{FuzzyPointTree.OfferSet * tree.Layer})){{"); 198 | if (tree.Nodes == default) 199 | { 200 | 201 | scriptBuilder.Append(ForeachFuzzyTree(node)); 202 | 203 | } 204 | else 205 | { 206 | 207 | //此时Nodes一定不是单节点,而是具有兄弟的节点 208 | foreach (var item in tree.Nodes) 209 | { 210 | 211 | //如果当前子节点不是叶节点 212 | if (item.Value == default) 213 | { 214 | 215 | //证明当前节点分支一定还需要再判断 216 | scriptBuilder.AppendLine($"case {item.PointCode}:"); 217 | scriptBuilder.Append(ForeachFuzzyTree(item)); 218 | scriptBuilder.Append("break;"); 219 | 220 | } 221 | else 222 | { 223 | 224 | //叶节点再次交给递归处理 225 | scriptBuilder.Append(ForeachFuzzyTree(item)); 226 | 227 | } 228 | 229 | } 230 | 231 | } 232 | 233 | scriptBuilder.Append('}'); 234 | 235 | } 236 | return scriptBuilder; 237 | 238 | } 239 | 240 | 241 | 242 | 243 | public static string GetGroupPrecisionPointBTFScript(IDictionary pairs, string parameterName = "arg") 244 | { 245 | 246 | if (pairs == default || pairs.Count == 0) 247 | { 248 | return default; 249 | } 250 | 251 | Dictionary> groups = new Dictionary>(); 252 | foreach (var item in pairs) 253 | { 254 | int temp = item.Key.Length; 255 | if (!groups.ContainsKey(temp)) 256 | { 257 | groups[temp] = new Dictionary(); 258 | } 259 | groups[temp][item.Key] = item.Value; 260 | 261 | } 262 | 263 | StringBuilder scriptBuilder = new StringBuilder(); 264 | Dictionary result = new Dictionary(); 265 | foreach (var item in groups) 266 | { 267 | 268 | result[item.Key] = GetPrecisionPointBTFScript(item.Value, parameterName) + "break;"; 269 | 270 | } 271 | scriptBuilder.AppendLine(GetCustomerBTFScript(result, "btfParameterLength", item => item.ToString())); 272 | 273 | 274 | scriptBuilder.Insert(0,$"int btfParameterLength = {parameterName}.Length;"); 275 | return scriptBuilder.ToString(); 276 | 277 | } 278 | 279 | 280 | 281 | 282 | public static string GetPrecisionPointBTFScript(IDictionary pairs, string parameterName = "arg") 283 | { 284 | 285 | if (pairs == default || pairs.Count == 0) 286 | { 287 | return default; 288 | } 289 | 290 | 291 | StringBuilder scriptBuilder = new StringBuilder(); 292 | if (pairs.Count == 1) 293 | { 294 | foreach (var item in pairs) 295 | { 296 | scriptBuilder.AppendLine($"if({parameterName} == \"{item.Key}\"){{ {item.Value} }}"); 297 | } 298 | 299 | } 300 | else 301 | { 302 | scriptBuilder.AppendLine($"fixed (char* c = {parameterName}){{"); 303 | 304 | 305 | PrecisionMinPriorityTree tree = new PrecisionMinPriorityTree(pairs.Keys.ToArray()); 306 | scriptBuilder.AppendLine(ForeachPrecisionTree(tree.GetPriorityTrees(), pairs)); 307 | 308 | 309 | scriptBuilder.AppendLine("}"); 310 | } 311 | 312 | 313 | return scriptBuilder.ToString(); 314 | 315 | } 316 | 317 | 318 | 319 | 320 | /* n1 n2 321 | * / \ \ / \ 322 | * / \ \ / \ 323 | * n3e n4 d4 n5 n6e 324 | * | / \ /\ 325 | * n7e n8e n9e n10e n11e 326 | * 327 | * n:节点 328 | * d: 无数据的空白节点 329 | * e: 末尾 330 | */ 331 | private static string ForeachPrecisionTree(List nodes, IDictionary parirs) 332 | { 333 | 334 | 335 | StringBuilder switchBuilder = new StringBuilder(); 336 | StringBuilder caseBuilder = new StringBuilder(); 337 | StringBuilder defaultBuilder = new StringBuilder(); 338 | 339 | for (int i = 0; i < nodes.Count; i += 1) 340 | { 341 | 342 | var node = nodes[i]; 343 | if (node.IsDefaultNode) 344 | { 345 | 346 | defaultBuilder.AppendLine($"default:"); 347 | if (node.Next.Count == 1 && node.Next[0].IsEndNode) 348 | { 349 | 350 | var returnStr = parirs[node.FullValue]; 351 | defaultBuilder.AppendLine($"{returnStr}"); 352 | if (!returnStr.Contains("return ")) 353 | { 354 | 355 | defaultBuilder.AppendLine("break;"); 356 | 357 | } 358 | 359 | } 360 | else 361 | { 362 | 363 | defaultBuilder.AppendLine(ForeachPrecisionTree(node.Next, parirs)); 364 | defaultBuilder.AppendLine("break;"); 365 | 366 | } 367 | 368 | } 369 | else if (node.IsEndNode) 370 | { 371 | 372 | if (node.IsZeroNode) 373 | { 374 | 375 | caseBuilder.AppendLine($"case 0:"); 376 | var returnStr = parirs[node.FullValue]; 377 | caseBuilder.AppendLine($"{returnStr}"); 378 | if (!returnStr.Contains("return ")) 379 | { 380 | caseBuilder.AppendLine("break;"); 381 | } 382 | 383 | } 384 | else 385 | { 386 | 387 | var (compareBuilder, code) = node.Value.GetCompareBuilder(node.Length, node.Offset); 388 | if (switchBuilder.Length == 0) 389 | { 390 | switchBuilder.AppendLine($"switch({compareBuilder}){{"); 391 | } 392 | 393 | caseBuilder.AppendLine($"case {code}:"); 394 | var returnStr = parirs[node.FullValue]; 395 | caseBuilder.AppendLine($"{returnStr}"); 396 | if (!returnStr.Contains("return ")) 397 | { 398 | caseBuilder.AppendLine("break;"); 399 | } 400 | 401 | } 402 | 403 | } 404 | else 405 | { 406 | 407 | var (compareBuilder, code) = node.Value.GetCompareBuilder(node.Length, node.Offset); 408 | if (switchBuilder.Length == 0) 409 | { 410 | switchBuilder.AppendLine($"switch({compareBuilder}){{"); 411 | } 412 | 413 | 414 | if (node.IsZeroNode) 415 | { 416 | 417 | caseBuilder.AppendLine($"case {code}:"); 418 | var returnStr = parirs[node.FullValue]; 419 | caseBuilder.AppendLine($"{returnStr}"); 420 | if (!returnStr.Contains("return ")) 421 | { 422 | caseBuilder.AppendLine("break;"); 423 | } 424 | 425 | } 426 | else if (node.Next.Count > 0) 427 | { 428 | 429 | caseBuilder.AppendLine($"case {code}:"); 430 | var tempNode = node.Next[0]; 431 | if (node.Next.Count == 1 && tempNode.IsEndNode && tempNode.IsZeroNode) 432 | { 433 | 434 | var returnStr = parirs[tempNode.FullValue]; 435 | caseBuilder.AppendLine($"{returnStr}"); 436 | if (!returnStr.Contains("return ")) 437 | { 438 | caseBuilder.AppendLine("break;"); 439 | } 440 | 441 | } 442 | else 443 | { 444 | 445 | caseBuilder.AppendLine(ForeachPrecisionTree(node.Next, parirs)); 446 | caseBuilder.AppendLine("break;"); 447 | 448 | } 449 | 450 | } 451 | 452 | } 453 | 454 | } 455 | 456 | var result = new StringBuilder(); 457 | result.Append(switchBuilder); 458 | result.Append(caseBuilder); 459 | result.Append(defaultBuilder); 460 | if (switchBuilder.Length > 0) 461 | { 462 | result.Append('}'); 463 | } 464 | return result.ToString(); 465 | 466 | } 467 | 468 | } 469 | 470 | } 471 | -------------------------------------------------------------------------------- /src/BTFindTree/BTFindTree.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netcoreapp3.0;netcoreapp3.1;net5.0; 5 | BTFindTree 6 | 快速查找算法,支持hash查找树,模糊trip查找树,归并最小权查找树。 7 | .NET Core Community and Contributors 8 | DotNetCore.BTFindTree 9 | False 10 | 支持自定义查找树,hash查找树,模糊查找树,归并最小权查找树, 分组最小权查找树。 11 | 未初始化的字典以及元素数量为0的字典默认返回空。 12 | 1.3.0 13 | https://github.com/dotnet-lab/BTFindTree/blob/master/LICENSE 14 | https://github.com/dotnet-lab/BTFindTree 15 | FastFind; DynamicBuild;Natasha; 16 | True 17 | 1.3.0.0 18 | .NET Core Community 19 | https://avatars2.githubusercontent.com/u/19404084 20 | 1.3.0.0 21 | NMSAzulX 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/BTFindTree/Model/StringExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace BTFindTree 4 | { 5 | public static class StringExtension 6 | { 7 | 8 | public unsafe static ulong GetULong(this string value,int index = 0) 9 | { 10 | fixed (char* c = value) 11 | { 12 | return *(ulong*)(c+index*4); 13 | } 14 | } 15 | 16 | 17 | 18 | 19 | public unsafe static uint GetUInt(this string value, int index = 0) 20 | { 21 | fixed (char* c = value) 22 | { 23 | return *(uint*)(c + index * 2); 24 | } 25 | } 26 | 27 | 28 | 29 | public unsafe static ushort GetUShort(this string value, int index = 0) 30 | { 31 | fixed (char* c = value) 32 | { 33 | return *(ushort*)(c + index); 34 | } 35 | } 36 | 37 | 38 | public unsafe static (StringBuilder compareBuilder, ulong code) GetCompareBuilder(this string value, int length, int index) 39 | { 40 | 41 | ulong code; 42 | string type; 43 | switch (length) 44 | { 45 | case 1: 46 | code = GetUShort(value); 47 | type = "ushort"; 48 | break; 49 | case 2: 50 | code = GetUInt(value); 51 | type = "uint"; 52 | break; 53 | default: 54 | code = GetULong(value); 55 | type = "ulong"; 56 | break; 57 | } 58 | 59 | 60 | StringBuilder builder = new StringBuilder(); 61 | builder.Append($"*({ type}*)(c + {index}) "); 62 | return (builder, code) ; 63 | 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/BTFindTree/Template/FuzzyTreeTemplate/FuzzyPointTree.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace BTFindTree 5 | { 6 | 7 | public class FuzzyPointTree 8 | { 9 | 10 | public List Nodes; 11 | public readonly string Value; 12 | public readonly ushort PointCode; 13 | public int Layer; 14 | 15 | 16 | public const int OfferSet = 1; 17 | public const string OfferType = "ushort"; 18 | 19 | 20 | public FuzzyPointTree(KeyValuePair value, int layer = 0) 21 | { 22 | 23 | //当前结果值 24 | Value = value.Value; 25 | //当前匹配值 26 | PointCode = value.Key.GetUShort(layer); 27 | //当前层数 28 | Layer = layer; 29 | 30 | } 31 | 32 | 33 | 34 | 35 | public FuzzyPointTree(IDictionary values, int layer = 0, ushort pCode = 0) 36 | { 37 | 38 | Layer = layer; 39 | PointCode = pCode; 40 | 41 | if (values.Count == 1 && layer != 0) 42 | { 43 | 44 | //如果递归集合中只剩一个元素,那么认为它的路径已经确认。 45 | foreach (var value in values) 46 | { 47 | 48 | //直接设置当前节点信息 49 | Value = value.Value; 50 | 51 | } 52 | 53 | } 54 | else 55 | { 56 | 57 | //初始化节点 58 | Nodes = new List(); 59 | var valuesDict = new Dictionary>(); 60 | 61 | 62 | foreach (var item in values) 63 | { 64 | 65 | //如果长度小于指针偏移量则视为叶子节点 66 | //如果 Key总长度 <= 当前指针偏移量 67 | if (item.Key.Length <= layer * OfferSet) 68 | { 69 | 70 | // 2 71 | // 1 - 72 | // 2 73 | // 3 74 | // 1 节点的 Nodes 集合里将存放 节点 2/2/3 75 | Nodes.Add(new FuzzyPointTree(item, layer)); 76 | 77 | } 78 | else 79 | { 80 | 81 | //获取当前元素Key值偏移量的指针数据 82 | ushort pcode = item.Key.GetUShort(layer); 83 | if (!valuesDict.ContainsKey(pcode)) 84 | { 85 | 86 | //如果缓存中不存在指针值则以该值创建字典缓存 87 | valuesDict[pcode] = new Dictionary(); 88 | 89 | } 90 | 91 | //将当前值以及元素添加到缓存 92 | valuesDict[pcode][item.Key] = item.Value; 93 | //code - key - value 94 | //code - key1 - value1 95 | //code - key2 - value2 96 | 97 | } 98 | 99 | } 100 | 101 | 102 | foreach (var item in valuesDict) 103 | { 104 | 105 | //将指针值以及改指针值下面的元素继续递归处理 106 | //以当前元素为节点,字典向下传递 107 | //pcode 即 item.Key 向下传递 108 | Nodes.Add(new FuzzyPointTree(item.Value, layer + 1, item.Key)); 109 | 110 | } 111 | 112 | } 113 | 114 | } 115 | 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/BTFindTree/Template/PrecisionTreeTemplate/Model/FrequencyModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BTFindTree.Template.PrecisionTreeTemplate.Model 4 | { 5 | 6 | public class FrequencyModel 7 | { 8 | 9 | // str: abcd 10 | public string Value; 11 | public int Length; 12 | /* 13 | * char : a - b - c - d 14 | * index : 0 - 1 - 2 - 3 15 | * Frequency : 1 - 1 - 3 - 4 16 | */ 17 | public int[] RepeateCache; 18 | 19 | 20 | public FrequencyModel(string value) 21 | { 22 | 23 | Value = value; 24 | Length = value.Length; 25 | //字符串的每个字符进行缓存 26 | RepeateCache = new int[value.Length]; 27 | 28 | } 29 | 30 | 31 | public int this[int index] 32 | { 33 | 34 | get { return RepeateCache[index]; } 35 | set { RepeateCache[index] = value; } 36 | 37 | } 38 | 39 | 40 | 41 | 42 | /// 43 | /// 获取匹配次数的连续字符串中间段 44 | /// 45 | /// 匹配频次 46 | /// 47 | public List GetByFrequency(int frequency) 48 | { 49 | 50 | var models = new List(); 51 | var node = new RepeateModel(); 52 | bool isFirst = true; 53 | for (int i = 0; i < RepeateCache.Length; i++) 54 | { 55 | 56 | //如果当前频次大于等于指定频次 57 | if (RepeateCache[i] >= frequency) 58 | { 59 | 60 | node.Length += 1; 61 | if (isFirst) 62 | { 63 | //如果第一次,则设置索引 64 | node.StartIndex = i; 65 | //标识重置 66 | isFirst = false; 67 | } 68 | 69 | } 70 | else 71 | { 72 | 73 | //如果出现了中断 74 | //如果之前正在连续计数 75 | if (!isFirst) 76 | { 77 | 78 | models.Add(node); 79 | node = new RepeateModel(); 80 | //标识设置为连续字符的开始 81 | isFirst = true; 82 | 83 | } 84 | 85 | } 86 | } 87 | 88 | return models; 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/BTFindTree/Template/PrecisionTreeTemplate/Model/PriorityTreeModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BTFindTree.Template.PrecisionTreeTemplate.Model 6 | { 7 | 8 | public class PriorityTreeModel 9 | { 10 | 11 | public List Next; 12 | public PriorityTreeModel() 13 | { 14 | Next = new List(); 15 | } 16 | 17 | 18 | public string Value; 19 | public string FullValue; 20 | public int Length; 21 | public int Offset; 22 | public bool IsDefaultNode; 23 | public bool IsEndNode; 24 | public bool IsZeroNode; 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/BTFindTree/Template/PrecisionTreeTemplate/Model/RepeateModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BTFindTree.Template.PrecisionTreeTemplate.Model 6 | { 7 | 8 | 9 | public struct RepeateModel 10 | { 11 | public RepeateModel(int startIndex = 0) 12 | { 13 | 14 | StartIndex = startIndex; 15 | Length = 1; 16 | MatchCount = 0; 17 | 18 | } 19 | public int StartIndex; 20 | public int Length; 21 | public int MatchCount; 22 | } 23 | 24 | 25 | 26 | 27 | public enum MatchOrder 28 | { 29 | None, 30 | LeftToRight, 31 | RightToLeft 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/BTFindTree/Template/PrecisionTreeTemplate/PrecisionMinPriorityTree.cs: -------------------------------------------------------------------------------- 1 | using BTFindTree.Template.PrecisionTreeTemplate.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace BTFindTree 7 | { 8 | 9 | public class PrecisionMinPriorityTree 10 | { 11 | 12 | private readonly int MaxLength; 13 | private int MaxMatchCount; 14 | private List PriorityCache; 15 | private readonly Dictionary TripCache; 16 | 17 | 18 | public PrecisionMinPriorityTree(params string[] strs) 19 | { 20 | 21 | TripCache = new Dictionary(); 22 | 23 | //寻找最大字符串长度 24 | for (int i = 0; i < strs.Length; i += 1) 25 | { 26 | 27 | if (MaxLength < strs[i].Length) 28 | { 29 | MaxLength = strs[i].Length; 30 | } 31 | //创建字符串的字符频次缓存 32 | TripCache[strs[i]] = new FrequencyModel(strs[i]); 33 | 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | 41 | private void AddFrequencyToTripCache() 42 | { 43 | 44 | //统计每个字符在当前位置出现的次数 45 | //遍历最大字符长度 46 | for (int i = 0; i < MaxLength; i += 1) 47 | { 48 | 49 | //遍历trip字符缓存 50 | Dictionary charsCache = new Dictionary(); 51 | foreach (var item in TripCache) 52 | { 53 | //如果当前字符串的长度大于索引 54 | if (item.Key.Length > i) 55 | { 56 | 57 | //获取字符 58 | var tempChar = item.Key[i]; 59 | if (charsCache.ContainsKey(tempChar)) 60 | { 61 | //如果缓存里已经存在了字符,那么缓存计数+1 62 | charsCache[tempChar] += 1; 63 | 64 | } 65 | else 66 | { 67 | 68 | //否则设置缓存字符,计数默认为1 69 | charsCache[tempChar] = 1; 70 | 71 | } 72 | 73 | } 74 | 75 | } 76 | 77 | //把之前位置对应的字符出现的频率,写入缓存中 78 | //再一次遍历trip字符缓存 79 | foreach (var item in TripCache) 80 | { 81 | 82 | //如果当前字符串的长度大于索引 83 | if (item.Key.Length > i) 84 | { 85 | 86 | //设置当前层字符的匹配频次 87 | int matchCount = charsCache[item.Key[i]]; 88 | item.Value.RepeateCache[i] = matchCount; 89 | //记录频次最大值 90 | if (MaxMatchCount < matchCount) 91 | { 92 | 93 | MaxMatchCount = matchCount; 94 | 95 | } 96 | 97 | } 98 | 99 | } 100 | 101 | } 102 | 103 | } 104 | 105 | 106 | public void GetAllWordsFrequency() 107 | { 108 | 109 | //将权值设置为最大 110 | int priority = int.MaxValue; 111 | 112 | //从1次开始匹配一直到最高频次 113 | for (int i = 1; i <= MaxMatchCount; i++) 114 | { 115 | 116 | //循环遍历trip缓存 117 | foreach (var item in TripCache) 118 | { 119 | 120 | //找到连续的相同位置的字符串, i 是能匹配到的频次 121 | var list = item.Value.GetByFrequency(i); 122 | 123 | 124 | //设置当前偏移量 125 | int offset = 0; 126 | List modelCache = new List(); 127 | foreach (var itemList in list) 128 | { 129 | 130 | //记录上一次偏移量,如果有间隔,则添加间隔 131 | //如果当前偏移量和连续节点的偏移量不相等 132 | //说明连续节点有了跳跃 133 | // start index 134 | // ↓ 135 | // - - - - - - x x x x x x x x x x - - - - - 136 | // | ____offset____|_GetFromSpace__|___length__| 137 | if (offset != itemList.StartIndex) 138 | { 139 | //搜集中间被跳过字符串的分割策略 140 | modelCache.AddRange(GetFromSpace(itemList.StartIndex, ref offset)); 141 | } 142 | 143 | 144 | //获取这部分字符 145 | // start index 146 | // ↓ 147 | // - - - - - - - - - - - - - - - x x x x x x 148 | // |____________str___________|___length__| 149 | // |____________________offset__________________| 150 | var str = item.Key.Substring(itemList.StartIndex, itemList.Length); 151 | 152 | 153 | //进行高频比对,先找到高频为4出现最多的,如果再中间,则两头比对 154 | //结果应该是当前比对字串的分割集合 155 | //针对上部分字符,进行高频分解 156 | var models = GetHighFrequency(str, itemList.StartIndex); 157 | for (int j = 0; j < models.Count; j++) 158 | { 159 | 160 | //因为是结构体,所以要单独拿出来操作 161 | var temp = models[j]; 162 | //增加偏移量,使用当前的偏移量 163 | temp.StartIndex += offset; 164 | //添加结构体 165 | modelCache.Add(temp); 166 | 167 | } 168 | 169 | 170 | //偏移量继续递增,跳到当前连续节点的后面 171 | // start index 172 | // ↓ 173 | // - - - - - - - - - - - - - - - x x x x x x 174 | // |_____________________________offset_________________ _________| 175 | // 176 | offset += itemList.Length; 177 | 178 | } 179 | 180 | 181 | //偏移量继续递增,跳到当前连续节点的后面 182 | // - - - - - - - - - - - - - - - - - - x x x x x x 183 | // |_____________________________offset____________________| 184 | // |_____________________________MaxLength___________________________| 185 | modelCache.AddRange(GetFromSpace(MaxLength, ref offset)); 186 | 187 | 188 | //获取最小权 189 | int result = GetPriority(modelCache); 190 | //选取最小权分配节点 191 | if (priority > result) 192 | { 193 | 194 | priority = result; 195 | PriorityCache = modelCache; 196 | 197 | } 198 | 199 | } 200 | 201 | } 202 | 203 | } 204 | 205 | 206 | 207 | 208 | public List GetPriorityTrees() 209 | { 210 | 211 | AddFrequencyToTripCache(); 212 | GetAllWordsFrequency(); 213 | var temp = from result in PriorityCache 214 | orderby result.StartIndex ascending 215 | select result; 216 | var list = new List(from result in temp 217 | orderby result.MatchCount descending 218 | select result); 219 | return GetTrees(TripCache.Keys, list); 220 | 221 | } 222 | 223 | 224 | /* n1 n2 225 | * / \ \ / \ 226 | * / \ \ / \ 227 | * n3e n4 d4 n5 n6e 228 | * | / \ /\ 229 | * n7e n8e n9e n10e n11e 230 | * 231 | * n: 节点 232 | * d: 无数据的空白节点 233 | * e: 末尾 234 | */ 235 | private List GetTrees(IEnumerable strs, List models, int deepth = 0) 236 | { 237 | 238 | //创建字符串集合缓存 239 | HashSet cache = new HashSet(strs); 240 | //创建叶节点集合 241 | List lists = new List(); 242 | 243 | 244 | //如果当前深度小于集合数量 245 | if (deepth < models.Count) 246 | { 247 | 248 | //创建字符映射叶子节点 249 | Dictionary StringToModelMapping = new Dictionary(); 250 | //创建叶节点映射全字符串 251 | Dictionary> ModelToStringsMapping = new Dictionary>(); 252 | 253 | 254 | //找到当前层的分割节点 255 | var model = models[deepth]; 256 | //遍历字符串,起初传入构造函数的集合 257 | foreach (var item in strs) 258 | { 259 | 260 | //如果分割起点 小于 字符串的最大值 261 | if (item.Length > model.StartIndex) 262 | { 263 | 264 | string node; 265 | //如果 拾取长度 大等于 字符串的总长度 266 | //则只截取字符串剩余的部分 267 | if (item.Length < model.StartIndex + model.Length) 268 | { 269 | 270 | //截取当前剩余字串 271 | node = item.Substring(model.StartIndex, item.Length - model.StartIndex); 272 | //如果字符串已经被耗尽,则停止对它的解析 273 | TripCache[item].Length -= item.Length - model.StartIndex; 274 | 275 | 276 | //创建一个方案节点 277 | PriorityTreeModel priority = new PriorityTreeModel 278 | { 279 | Value = node, 280 | FullValue = item, 281 | Offset = model.StartIndex, 282 | Length = model.Length 283 | }; 284 | 285 | 286 | //添加到叶节点集合中 287 | lists.Add(priority); 288 | //创建与该方案相关的字串集合 289 | ModelToStringsMapping[priority] = new List(); 290 | //相同的截取字符串和方案节点添加到缓存 291 | StringToModelMapping[node] = priority; 292 | 293 | 294 | if (TripCache[item].Length == 0) 295 | { 296 | 297 | priority.IsEndNode = true; 298 | 299 | } 300 | else 301 | { 302 | 303 | //如果字符串还未耗尽,则继续解析 304 | ModelToStringsMapping[priority].Add(item); 305 | 306 | } 307 | 308 | } 309 | else 310 | { 311 | 312 | //如果 拾取长度 小于 字符串的总长度 ,说明可以充分截取 313 | node = item.Substring(model.StartIndex, model.Length); 314 | TripCache[item].Length -= model.Length; 315 | 316 | 317 | //如果缓存中还没有这个截取方案 318 | //则生成截取方案节点 319 | if (!StringToModelMapping.ContainsKey(node)) 320 | { 321 | 322 | //仅仅是匹配类型的节点 323 | PriorityTreeModel priority = new PriorityTreeModel 324 | { 325 | Value = node, 326 | Offset = model.StartIndex, 327 | Length = model.Length 328 | }; 329 | 330 | 331 | //添加到叶节点集合中 332 | lists.Add(priority); 333 | //创建与该方案相关的字串集合 334 | ModelToStringsMapping[priority] = new List(); 335 | //相同的截取字符串和方案节点添加到缓存 336 | StringToModelMapping[node] = priority; 337 | 338 | } 339 | 340 | 341 | if (TripCache[item].Length == 0 && deepth == models.Count - 1) 342 | { 343 | 344 | StringToModelMapping[node].FullValue = item; 345 | StringToModelMapping[node].IsZeroNode = true; 346 | 347 | } 348 | else 349 | { 350 | 351 | //如果字符串还未耗尽,则继续解析 352 | ModelToStringsMapping[StringToModelMapping[node]].Add(item); 353 | 354 | } 355 | 356 | 357 | } 358 | 359 | 360 | cache.Remove(item); 361 | 362 | } 363 | else if (item.Length == model.StartIndex) 364 | { 365 | 366 | // abcdef 367 | // abcd 368 | if (TripCache[item].Length == 0) 369 | { 370 | 371 | //创建一个方案节点 372 | PriorityTreeModel priority = new PriorityTreeModel 373 | { 374 | 375 | FullValue = item, 376 | IsEndNode = true, 377 | IsZeroNode = true 378 | 379 | }; 380 | //添加到叶节点集合中 381 | lists.Add(priority); 382 | cache.Remove(item); 383 | 384 | } 385 | 386 | // 1abcdef 387 | // 3abcdefg 388 | // 4abcdegga 389 | // 2abcd 390 | // 5abev 391 | // 1 -- abcd 392 | // -- abev 393 | // 2 -- ef 394 | // -- eg 395 | 396 | } 397 | 398 | } 399 | 400 | 401 | int nextDeepth = deepth + 1; 402 | foreach (var item in ModelToStringsMapping) 403 | { 404 | 405 | item.Key.Next.AddRange(GetTrees(item.Value, models, nextDeepth)); 406 | 407 | } 408 | 409 | //如果此次有未参与处理的字串,那么在下一层时将进行处理 410 | if (cache.Count > 0) 411 | { 412 | 413 | PriorityTreeModel priority = new PriorityTreeModel 414 | { 415 | IsDefaultNode = true 416 | }; 417 | priority.Next.AddRange(GetTrees(cache, models, nextDeepth)); 418 | lists.Add(priority); 419 | 420 | } 421 | 422 | } 423 | 424 | return lists; 425 | } 426 | 427 | 428 | 429 | 430 | /// 431 | /// 搜集offset到index中间的分割策略 432 | /// 433 | /// 目标偏移量 434 | /// 当前偏移量 435 | /// 436 | private static List GetFromSpace(int target, ref int offset) 437 | { 438 | 439 | var list = new List(); 440 | 441 | //如果当前偏移量比前一偏移量多4个长度,那么以4个增长点上增 442 | while (target - offset > 3) 443 | { 444 | 445 | RepeateModel model = new RepeateModel 446 | { 447 | StartIndex = offset, 448 | Length = 4 449 | }; 450 | offset += 4; 451 | list.Add(model); 452 | 453 | } 454 | 455 | 456 | int remain = target - offset; 457 | //如果处理完之后偏移量仍然有剩余 458 | if (remain > 0) 459 | { 460 | 461 | if (remain == 3) 462 | { 463 | RepeateModel model2 = new RepeateModel(); 464 | model2.StartIndex = offset; 465 | //如果相差3个长度 466 | model2.Length = 2; 467 | offset += 2; 468 | //偏移量增加 469 | list.Add(model2); 470 | 471 | RepeateModel model3 = new RepeateModel(); 472 | model3.StartIndex = offset; 473 | //如果相差3个长度 474 | model3.Length = 1; 475 | //偏移量增加 476 | list.Add(model3); 477 | } 478 | else 479 | { 480 | RepeateModel model2 = new RepeateModel(); 481 | model2.StartIndex = offset; 482 | //如果相差3个长度 483 | model2.Length = remain; 484 | //偏移量增加 485 | list.Add(model2); 486 | } 487 | 488 | 489 | //最后处理完应该offset = target; 490 | offset = target; 491 | 492 | } 493 | 494 | return list; 495 | } 496 | 497 | 498 | 499 | 500 | private int GetPriority(List models) 501 | { 502 | 503 | //默认最小权为0 504 | int priority = 0; 505 | 506 | //遍历分割节点 507 | foreach (var model in models) 508 | { 509 | 510 | //匹配字符串缓存 511 | HashSet sets = new HashSet(); 512 | foreach (var item in TripCache) 513 | { 514 | 515 | string temp; 516 | //获取当前节点到当前字符串最后一个字符的距离 517 | int diff = item.Key.Length - model.StartIndex; 518 | if (diff < 0) 519 | { 520 | //如果分割偏移量已经超过当前字符串的长度 521 | //则进行下一个循环 522 | continue; 523 | 524 | } 525 | if (diff < model.Length) 526 | { 527 | 528 | //如果差值在分割长度的范围内 529 | //获取剩余的分割字符 530 | temp = item.Key.Substring(model.StartIndex, diff); 531 | 532 | } 533 | else 534 | { 535 | 536 | //获取当前的分割字符 537 | temp = item.Key.Substring(model.StartIndex, model.Length); 538 | 539 | } 540 | 541 | //如果分割字符不在缓存里 542 | if (!sets.Contains(temp)) 543 | { 544 | //权值+1 545 | priority += 1; 546 | sets.Add(temp); 547 | 548 | } 549 | 550 | } 551 | 552 | } 553 | 554 | return priority; 555 | 556 | } 557 | 558 | 559 | 560 | /// 561 | /// 对一段字符串进行高频解析 562 | /// 563 | /// 字符串 564 | /// 字符串之前的偏移量 565 | /// 当前偏移量 566 | /// 567 | private List GetHighFrequency(string str, int index, int offset = 0, MatchOrder order = MatchOrder.None, string other = default) 568 | { 569 | 570 | List result = new List(); 571 | RepeateModel model = default; 572 | 573 | if (str.Length < 3 || str.Length == 4) 574 | { 575 | 576 | model = GetFrequencyByOffsetAndIndex(str, index, str.Length); 577 | model.StartIndex += offset; 578 | result.Add(model); 579 | 580 | } 581 | else if (str.Length == 3) 582 | { 583 | 584 | // 3个字符分割成 2 + 1 分别求权值 585 | int tempPriority1 = 0; 586 | var model1 = GetFrequencyByOffsetAndIndex(str.Substring(0, 2), index, 2); 587 | tempPriority1 += model1.MatchCount; 588 | var model2 = GetFrequencyByOffsetAndIndex(str.Substring(2, 1), index + 2, 1); 589 | tempPriority1 += model2.MatchCount; 590 | 591 | 592 | //3个字符分割成 1 + 2 分别求权值 593 | int tempPriority2 = 0; 594 | var model3 = GetFrequencyByOffsetAndIndex(str.Substring(0, 1), index, 1); 595 | tempPriority2 += model3.MatchCount; 596 | var model4 = GetFrequencyByOffsetAndIndex(str.Substring(1, 2), index + 1, 2); 597 | tempPriority2 += model4.MatchCount; 598 | 599 | 600 | //3个字符借位求权 601 | int tempPriority3 = 0; 602 | if (order != MatchOrder.None) 603 | { 604 | 605 | model = GetFrequencyByOffsetAndIndex(other, index, 4); 606 | tempPriority3 = model.MatchCount; 607 | 608 | } 609 | 610 | //匹配权比较 611 | if (tempPriority1 >= tempPriority2) 612 | { 613 | 614 | if (tempPriority1 >= tempPriority3) 615 | { 616 | model1.StartIndex += offset; 617 | result.Add(model1); 618 | model2.StartIndex += offset + 2; 619 | result.Add(model2); 620 | } 621 | else if (order == MatchOrder.RightToLeft) 622 | { 623 | model.StartIndex += offset - 1; 624 | result.Add(model); 625 | } 626 | else if (order == MatchOrder.LeftToRight) 627 | { 628 | model.StartIndex += offset; 629 | result.Add(model); 630 | } 631 | 632 | } 633 | else 634 | { 635 | 636 | if (tempPriority2 >= tempPriority3) 637 | { 638 | model3.StartIndex += offset; 639 | result.Add(model3); 640 | model4.StartIndex += offset + 1; 641 | result.Add(model4); 642 | } 643 | else if (order == MatchOrder.RightToLeft) 644 | { 645 | model.StartIndex += offset - 1; 646 | result.Add(model); 647 | } 648 | else if (order == MatchOrder.LeftToRight) 649 | { 650 | model.StartIndex += offset; 651 | result.Add(model); 652 | } 653 | 654 | } 655 | } 656 | else 657 | { 658 | 659 | //如果是4个或者4个以上的, 那么找到4个字符为一组的,匹配最多的那组 660 | model = GetMaxFrequencyModel(str, index); 661 | 662 | 663 | //如果该组左侧有字符,那么递归处理左侧字符 664 | //如果获取的匹配节点的起点不在原点,证明左边是有剩余字符串的 665 | if (model.StartIndex > 0) 666 | { 667 | 668 | //获取左边的字符 669 | var source = str.Substring(0, model.StartIndex); 670 | if (source.Length == 3) 671 | { 672 | result.AddRange(GetHighFrequency(source, index, offset, MatchOrder.LeftToRight, str.Substring(0, 4))); 673 | } 674 | else 675 | { 676 | result.AddRange(GetHighFrequency(source, index, offset)); 677 | } 678 | 679 | } 680 | 681 | //如果该组右侧有字符,那么递归处理右侧字符 682 | if (model.StartIndex + 4 < str.Length) 683 | { 684 | 685 | int tempOffset = model.StartIndex + 4; 686 | var source = str.Substring(model.StartIndex, str.Length - tempOffset); 687 | if (source.Length == 3) 688 | { 689 | result.AddRange(GetHighFrequency(source, index + 4, tempOffset + offset, MatchOrder.RightToLeft, str.Substring(model.StartIndex + 3, 4))); 690 | } 691 | else 692 | { 693 | result.AddRange(GetHighFrequency(source, index + 4, tempOffset + offset)); 694 | } 695 | 696 | } 697 | 698 | 699 | model.StartIndex += offset; 700 | result.Add(model); 701 | } 702 | 703 | return result; 704 | } 705 | 706 | 707 | private RepeateModel GetFrequencyByOffsetAndIndex(string str, int index, int length) 708 | { 709 | //如果是1位,则直接取1位 710 | var model = new RepeateModel() 711 | { 712 | 713 | Length = length, 714 | StartIndex = 0 715 | 716 | }; 717 | 718 | foreach (var item in TripCache) 719 | { 720 | 721 | if (index + length <= item.Key.Length) 722 | { 723 | 724 | var matchStr = item.Key.Substring(index, length); 725 | if (str == matchStr) 726 | { 727 | 728 | model.MatchCount += 1; 729 | 730 | } 731 | 732 | } 733 | 734 | } 735 | return model; 736 | } 737 | 738 | 739 | private RepeateModel GetMaxFrequencyModel(string str, int index) 740 | { 741 | 742 | int frequency = 0; 743 | int result = 0; 744 | for (int i = 0; i < str.Length - 4; i += 1) 745 | { 746 | string temp = str.Substring(i, 4); 747 | int tempFrequency = 0; 748 | foreach (var item in TripCache) 749 | { 750 | 751 | if (item.Key.Length > index + i + 4) 752 | { 753 | 754 | string matchStr = item.Key.Substring(index + i, 4); 755 | if (temp == matchStr) 756 | { 757 | tempFrequency += 1; 758 | 759 | } 760 | 761 | } 762 | 763 | } 764 | if (frequency < tempFrequency) 765 | { 766 | frequency = tempFrequency; 767 | result = i; 768 | } 769 | 770 | } 771 | if (frequency == 0) 772 | { 773 | frequency = 1; 774 | } 775 | return new RepeateModel 776 | { 777 | MatchCount = frequency, 778 | StartIndex = result, 779 | Length = 4 780 | }; 781 | 782 | 783 | } 784 | 785 | 786 | } 787 | 788 | } 789 | -------------------------------------------------------------------------------- /test/BTFUT/BTFUT.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2;netcoreapp3.1;net5.0; 5 | true 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/BTFUT/FindTreeTest.cs: -------------------------------------------------------------------------------- 1 | using BTFindTree; 2 | using Natasha; 3 | using Natasha.CSharp; 4 | using Natasha.Log; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using Xunit; 9 | 10 | namespace BenchmarkTest 11 | { 12 | 13 | 14 | public class FindTreeTest 15 | { 16 | static FindTreeTest() 17 | { 18 | NatashaInitializer.InitializeAndPreheating(); 19 | } 20 | 21 | Func HashDelegate; 22 | Func FuzzyDelegate; 23 | Func PrecisionDelegate; 24 | readonly Dictionary Dict; 25 | readonly Dictionary ScriptDict; 26 | 27 | public FindTreeTest() 28 | { 29 | 30 | Dict = new Dictionary(); 31 | Dict["NullClass"] = 0; 32 | Dict["Test2"] = 1; 33 | Dict["abab1123"] = 0; 34 | Dict["abab1123c"] = 1; 35 | Dict["abab1123d"] = 2; 36 | Dict["abab1123de"] = 3; 37 | //Dict["abab2213e"] = 3; 38 | //Dict["abab2213er"] = 4; 39 | //Dict["abab3213f"] = 5; 40 | //Dict["abcdeff"] = 6; 41 | //Dict["abcdefg"] = 7; 42 | //Dict["abcdefi"] = 8; 43 | //Dict["abcdef"] = 90; 44 | //Dict["abcdefcidefg"] = 91; 45 | //Dict["abcdefh"] = 9; 46 | //Dict["abcdefj"] = 10; 47 | Dict["a"] = 11; 48 | Dict["b"] = 13; 49 | Dict["aa"] = 12; 50 | Dict["Age"] = 220; 51 | Dict["Name"] = 221; 52 | Dict["Temp"] = 122; 53 | Dict["Money"] = 121; 54 | Dict["acbc1"] = 21; 55 | Dict["afdc2"] = 22; 56 | Dict["a73c5"] = 23; 57 | Dict["a7"] = 24; 58 | Dict["a74d5"] = 25; 59 | Dict["^[peIO`"] = 1; 60 | Dict["A"] = 3342; 61 | Dict["`BLBD}Z"] = 3343; 62 | Dict["CTvO_uu"] = 3344; 63 | Dict["C"] = 3345; 64 | Dict["c"] = 3346; 65 | 66 | Dict["STTJRVACRM"] = 3346; 67 | Dict["GBLODSLPUW"] = 3347; 68 | Dict["GBLO"] = 3348; 69 | Dict["TIDTPPXDXGX"] = 3349; 70 | Dict["HQVAB"] = 3350; 71 | Dict["UXNFM"] = 3351; 72 | Dict["UXNFMJUDFYK"] = 3352; 73 | Dict["IFGKYFGPIID"] = 3353; 74 | // Dict["LFDJQN"] = 1; 75 | // Dict["YMUPCJ"] = 2; 76 | // Dict["YMUPCJRYBLPFH"] = 3; 77 | // Dict["MTNUNGD"] = 4; 78 | // Dict["ACFAADO"] = 5; 79 | // Dict["A"] = 6; 80 | ////Dict["GGVJAPIOOS"] = 3352; 81 | // Dict["NJXGLAA"] = 7; 82 | // Dict["B"] = 8; 83 | // Dict["O"] = 9; 84 | ScriptDict = new Dictionary(Dict.Select(item => KeyValuePair.Create(item.Key, "return " + item.Value.ToString() + ";"))); 85 | //HashDelegate = NFunc.UnsafeDelegate(BTFTemplate.GetHashBTFScript(ScriptDict)+"return default;"); 86 | //FuzzyDelegate = NFunc.UnsafeDelegate(BTFTemplate.GetFuzzyPointBTFScript(ScriptDict) + "return default;"); 87 | //PrecisionDelegate = NFunc.UnsafeDelegate(BTFTemplate.GetPrecisionPointBTFScript(ScriptDict) + "return default;"); 88 | } 89 | 90 | 91 | [Fact(DisplayName = "哈希查找树")] 92 | public void HashFindTree() 93 | { 94 | 95 | HashDelegate = NDelegate.RandomDomain().UnsafeFunc(BTFTemplate.GetHashBTFScript(ScriptDict) + "return default;"); 96 | foreach (var item in Dict) 97 | { 98 | Assert.Equal(item.Value, HashDelegate(item.Key)); 99 | } 100 | 101 | } 102 | [Fact(DisplayName = "自定义查找树1")] 103 | public void CustomerFindTree() 104 | { 105 | 106 | HashDelegate = NDelegate.RandomDomain().UnsafeFunc(BTFTemplate.GetCustomerBTFScript(ScriptDict,"arg.GetHashCode()",item=>item.GetHashCode().ToString()) + "return default;"); 107 | foreach (var item in Dict) 108 | { 109 | Assert.Equal(item.Value, HashDelegate(item.Key)); 110 | } 111 | 112 | } 113 | 114 | 115 | [Fact(DisplayName = "空-初始化")] 116 | public void RunNull() 117 | { 118 | 119 | Assert.Equal(1, 1); 120 | 121 | } 122 | 123 | [Fact(DisplayName = "模糊指针查找树")] 124 | public void FuzzyFindTree() 125 | { 126 | //NSucceedLog.Enabled = true; 127 | FuzzyDelegate = NDelegate.RandomDomain().UnsafeFunc(BTFTemplate.GetGroupFuzzyPointBTFScript(ScriptDict) + "return default;"); 128 | foreach (var item in Dict) 129 | { 130 | Assert.Equal(item.Value, FuzzyDelegate(item.Key)); 131 | } 132 | } 133 | 134 | 135 | 136 | [Fact(DisplayName = "归并最小权查找树")] 137 | public void PrecisionFindTree() 138 | { 139 | PrecisionDelegate = NDelegate.RandomDomain().UnsafeFunc(BTFTemplate.GetPrecisionPointBTFScript(ScriptDict) + "return default;"); 140 | foreach (var item in Dict) 141 | { 142 | Assert.Equal(item.Value, PrecisionDelegate(item.Key)); 143 | } 144 | } 145 | 146 | 147 | [Fact(DisplayName = "分组最小权查找树")] 148 | public void GroupsPrecisionFindTree() 149 | { 150 | NErrorLog.Enabled = true; 151 | PrecisionDelegate = NDelegate.RandomDomain().UnsafeFunc(BTFTemplate.GetGroupPrecisionPointBTFScript(ScriptDict) + "return default;"); 152 | //var temp = BTFTemplate.GetPrecisionPointBTFScript(ScriptDict) + "return default;"; 153 | foreach (var item in Dict) 154 | { 155 | Assert.Equal(item.Value, PrecisionDelegate(item.Key)); 156 | } 157 | } 158 | 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /test/BenchmarkTest/BenchmarkTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | true 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/BenchmarkTest/FindTreeTest.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace BenchmarkTest 9 | { 10 | 11 | 12 | public class FindTreeTest 13 | { 14 | 15 | public readonly Dictionary Dict; 16 | public readonly ConcurrentDictionary ConDict; 17 | 18 | public FindTreeTest() 19 | { 20 | Dict = new Dictionary(); 21 | Dict["abab1123"] = 0; 22 | Dict["abab1123c"] = 1; 23 | Dict["abab1123d"] = 2; 24 | 25 | Dict["abab2213e"] = 3; 26 | Dict["abab2213er"] = 2; 27 | 28 | Dict["abab3213f"] = 4; 29 | Dict["abcdeff"] = 5; 30 | Dict["abcdefg"] = 6; 31 | Dict["abcdefi"] = 7; 32 | Dict["abcdefh"] = 8; 33 | Dict["abcdefj"] = 9; 34 | ConDict = new ConcurrentDictionary(Dict); 35 | UseSpanPoint("abcdefj"); 36 | UsePoint("abcdefj"); 37 | UseMemoryMarshalPoint("abcdefj"); 38 | UseUnsafe("abcdefj"); 39 | } 40 | //[Benchmark] 41 | //public void HashFindTree() 42 | //{ 43 | // var result = UseHash("abcdefj"); 44 | //} 45 | 46 | //[Benchmark] 47 | //public void IntPtrSpanFindTree() 48 | //{ 49 | // var result = UseSpanPoint("abcdefj"); 50 | //} 51 | 52 | //[Benchmark] 53 | //public void IntPtrFindTree() 54 | //{ 55 | // var result = UsePoint("abcdefj"); 56 | //} 57 | //[Benchmark] 58 | //public void IntPtrMemoryMarshalFindTree() 59 | //{ 60 | // var result = UseMemoryMarshalPoint("abcdefj"); 61 | //} 62 | 63 | //[Benchmark] 64 | //public void UnsafeFindTree() 65 | //{ 66 | // var result = UseUnsafe2("abcdefj"); 67 | //} 68 | 69 | public unsafe int UseMemoryMarshalPoint(string arg) 70 | { 71 | 72 | fixed (char* c = arg) 73 | { 74 | switch (Unsafe.ReadUnaligned(c + 2)) 75 | { 76 | case 6422625: 77 | switch (Unsafe.ReadUnaligned(c + 4)) 78 | { 79 | case 3211313: 80 | switch (Unsafe.ReadUnaligned(c + 8)) 81 | { 82 | case 99: 83 | return 1; 84 | case 100: 85 | return 2; 86 | } 87 | break; 88 | case 3276850: 89 | switch (*(int*)(c + 8)) 90 | { 91 | case 101: 92 | return 3; 93 | case 7471205: 94 | var a = 1 + 1; 95 | break; 96 | } 97 | break; 98 | case 3276851: 99 | return 4; 100 | } 101 | break; 102 | case 6553699: 103 | switch (Unsafe.ReadUnaligned(c + 6)) 104 | { 105 | case 102: 106 | return 5; 107 | case 103: 108 | return 6; 109 | case 105: 110 | return 7; 111 | case 104: 112 | return 8; 113 | case 106: 114 | return 9; 115 | } 116 | break; 117 | } 118 | } 119 | return default; 120 | 121 | } 122 | 123 | 124 | public unsafe int UseUnsafe(string arg) 125 | { 126 | 127 | var bytes = MemoryMarshal.AsBytes(arg.AsSpan()); 128 | ref var addr = ref Unsafe.AsRef(in bytes.GetPinnableReference()); 129 | 130 | switch (Unsafe.ReadUnaligned(ref Unsafe.Add(ref addr, 4))) 131 | { 132 | case 6422625: 133 | switch (Unsafe.ReadUnaligned(ref Unsafe.Add(ref addr, 8))) 134 | { 135 | case 3211313: 136 | switch (Unsafe.ReadUnaligned(ref Unsafe.Add(ref addr, 16))) 137 | { 138 | case 99: 139 | return 1; 140 | case 100: 141 | return 2; 142 | } 143 | break; 144 | case 3276850: 145 | switch (Unsafe.ReadUnaligned(ref Unsafe.Add(ref addr, 16))) 146 | { 147 | case 101: 148 | return 3; 149 | case 7471205: 150 | var a = 1 + 1; 151 | break; 152 | } 153 | break; 154 | case 3276851: 155 | return 4; 156 | } 157 | break; 158 | case 6553699: 159 | switch (Unsafe.ReadUnaligned(ref Unsafe.Add(ref addr, 12))) 160 | { 161 | case 102: 162 | return 5; 163 | case 103: 164 | return 6; 165 | case 105: 166 | return 7; 167 | case 104: 168 | return 8; 169 | case 106: 170 | return 9; 171 | } 172 | break; 173 | } 174 | 175 | return default; 176 | 177 | } 178 | 179 | //[Benchmark] 180 | //public void ShortPtrFindTree() 181 | //{ 182 | // var result = UseShort("abcdefj"); 183 | //} 184 | public unsafe int UseUnsafe2(string arg) 185 | { 186 | 187 | //var bytes = MemoryMarshal.AsBytes(arg.AsSpan()); 188 | //ref var addr = ref Unsafe.AsRef(in bytes.GetPinnableReference()); 189 | //var span = arg.AsSpan(); 190 | //ref var addr = ref Unsafe.AvPointer(arg.AsSpan().GetPinnableReference()); 191 | ref char addr = ref Unsafe.AsRef(in arg.AsSpan().GetPinnableReference()); 192 | 193 | switch (Unsafe.As(ref Unsafe.Add(ref addr, 2))) 194 | { 195 | case 6422625: 196 | switch (Unsafe.As(ref Unsafe.Add(ref addr, 4))) 197 | { 198 | case 3211313: 199 | switch (Unsafe.As(ref Unsafe.Add(ref addr, 8))) 200 | { 201 | case 99: 202 | return 1; 203 | case 100: 204 | return 2; 205 | } 206 | break; 207 | case 3276850: 208 | switch (Unsafe.As(ref Unsafe.Add(ref addr, 8))) 209 | { 210 | case 101: 211 | return 3; 212 | case 7471205: 213 | var a = 1 + 1; 214 | break; 215 | } 216 | break; 217 | case 3276851: 218 | return 4; 219 | } 220 | break; 221 | case 6553699: 222 | switch (Unsafe.As(ref Unsafe.Add(ref addr, 6))) 223 | { 224 | case 102: 225 | return 5; 226 | case 103: 227 | return 6; 228 | case 105: 229 | return 7; 230 | case 104: 231 | return 8; 232 | case 106: 233 | return 9; 234 | } 235 | break; 236 | } 237 | 238 | return default; 239 | 240 | } 241 | 242 | 243 | 244 | public unsafe int UseShort(string arg) 245 | { 246 | fixed (char* c = arg) 247 | { 248 | switch (*(short*)(c + 2)) 249 | { 250 | case 97: 251 | switch (*(short*)(c + 4)) 252 | { 253 | case 49: 254 | switch (*(short*)(c + 8)) 255 | { 256 | case 99: 257 | return 1; 258 | case 100: 259 | return 2; 260 | } 261 | break; 262 | case 50: 263 | switch (*(short*)(c + 9)) 264 | { 265 | case 0: 266 | return 3; 267 | case 114: 268 | var a = 1 + 1; 269 | break; 270 | } 271 | break; 272 | case 51: 273 | return 4; 274 | } 275 | break; 276 | case 99: 277 | switch (*(short*)(c + 6)) 278 | { 279 | case 102: 280 | return 5; 281 | case 103: 282 | return 6; 283 | case 105: 284 | return 7; 285 | case 104: 286 | return 8; 287 | case 106: 288 | return 9; 289 | } 290 | break; 291 | } 292 | } 293 | return default; 294 | } 295 | 296 | 297 | //[Benchmark] 298 | //public void ConcurrentDict() 299 | //{ 300 | // var result = ConDict["abcdefj"]; 301 | //} 302 | 303 | 304 | //[Benchmark] 305 | public void NormalDict() 306 | { 307 | var result = Dict["abcdefj"]; 308 | } 309 | 310 | 311 | private static readonly byte[] array = new byte[240]; 312 | 313 | 314 | 315 | [Benchmark] 316 | public unsafe void ByteSpan() 317 | { 318 | 319 | //var array = new byte[1024]; 320 | var span = array.AsSpan(); 321 | for (var i = 0; i < span.Length; i += 1) 322 | { 323 | span[i] = byte.MinValue; 324 | } 325 | 326 | } 327 | 328 | 329 | [Benchmark] 330 | public unsafe void BytePointer() 331 | { 332 | 333 | //var array = new byte[1024]; 334 | fixed (byte* pointer = array) 335 | { 336 | for (var i = 0; i < array.Length; i += 1) 337 | { 338 | *(pointer + i) = byte.MinValue; 339 | } 340 | } 341 | 342 | } 343 | 344 | //[Benchmark] 345 | public unsafe void BytePointer2() 346 | { 347 | 348 | //byte[] array = new byte[1024]; 349 | fixed (byte* pointer = &array[0]) 350 | { 351 | for (var i = 0; i < array.Length; i += 1) 352 | { 353 | *(pointer + i) = *(pointer + i); 354 | } 355 | } 356 | 357 | } 358 | 359 | public int UseHash(string arg) 360 | { 361 | var a = arg.GetHashCode(); 362 | a = 10; 363 | switch (a) 364 | { 365 | case 1663536773: 366 | return 1; 367 | case -261059740: 368 | return 2; 369 | case -817518664: 370 | return 3; 371 | case -221347000: 372 | var b = 1 + 1; 373 | break; 374 | case 1485518367: 375 | return 4; 376 | case 1059151160: 377 | return 5; 378 | case -1970978525: 379 | return 6; 380 | case -595701590: 381 | return 7; 382 | case -538885107: 383 | return 8; 384 | case 10: 385 | return 9; 386 | } 387 | return default; 388 | } 389 | 390 | private unsafe int UsePoint(string arg) 391 | { 392 | fixed (char* c = arg) 393 | { 394 | switch (*(int*)(c + 2)) 395 | { 396 | case 6422625: 397 | switch (*(int*)(c + 4)) 398 | { 399 | case 3211313: 400 | switch (*(int*)(c + 8)) 401 | { 402 | case 99: 403 | return 1; 404 | case 100: 405 | return 2; 406 | } 407 | break; 408 | case 3276850: 409 | switch (*(int*)(c + 8)) 410 | { 411 | case 101: 412 | return 3; 413 | case 7471205: 414 | var a = 1 + 1; 415 | break; 416 | } 417 | break; 418 | case 3276851: 419 | return 4; 420 | } 421 | break; 422 | case 6553699: 423 | switch (*(short*)(c + 6)) 424 | { 425 | case 102: 426 | return 5; 427 | case 103: 428 | return 6; 429 | case 105: 430 | return 7; 431 | case 104: 432 | return 8; 433 | case 106: 434 | return 9; 435 | } 436 | break; 437 | } 438 | } 439 | return default; 440 | } 441 | 442 | private unsafe int UseSpanPoint(string arg) 443 | { 444 | fixed (char* c = arg) 445 | { 446 | switch (*(int*)(c + 2)) 447 | { 448 | case 6422625: 449 | switch (*(int*)(c + 4)) 450 | { 451 | case 3211313: 452 | switch (*(int*)(c + 8)) 453 | { 454 | case 99: 455 | return 1; 456 | case 100: 457 | return 2; 458 | } 459 | break; 460 | case 3276850: 461 | switch (*(int*)(c + 8)) 462 | { 463 | case 101: 464 | return 3; 465 | case 7471205: 466 | var a = 1 + 1; 467 | break; 468 | } 469 | break; 470 | case 3276851: 471 | return 4; 472 | } 473 | break; 474 | case 6553699: 475 | switch (*(short*)(c + 6)) 476 | { 477 | case 102: 478 | return 5; 479 | case 103: 480 | return 6; 481 | case 105: 482 | return 7; 483 | case 104: 484 | return 8; 485 | case 106: 486 | return 9; 487 | } 488 | break; 489 | } 490 | } 491 | return default; 492 | } 493 | } 494 | 495 | } 496 | -------------------------------------------------------------------------------- /test/BenchmarkTest/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using System; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace BenchmarkTest 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | FindTreeTest a = new FindTreeTest(); 12 | a.BytePointer(); 13 | a.ByteSpan(); 14 | //var b = a.UseUnsafe2("abcdefj"); 15 | //Console.WriteLine(b); 16 | //b = a.UseUnsafe2("abcdefj"); 17 | //Console.WriteLine(b); 18 | //b = a.UseUnsafe2("abcdefj"); 19 | //Console.WriteLine(b); 20 | //BenchmarkRunner.Run(); 21 | 22 | //Console.WriteLine(Unsafe.SizeOf()); 23 | //Test(); 24 | Console.ReadKey(); 25 | } 26 | public unsafe static void Test() 27 | { 28 | byte[] array = new byte[512]; 29 | fixed (byte* pointer = array) 30 | { 31 | for (var i = 0; i < array.Length; i++) 32 | { 33 | *(pointer + i) = (byte)i; 34 | Console.Write(array[i]); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | --------------------------------------------------------------------------------