├── NetDiff ├── DiffOrderType.cs ├── DiffOption.cs ├── NetDiff.nuspec ├── DiffStatus.cs ├── DiffResult.cs ├── Properties │ └── AssemblyInfo.cs ├── Extension.cs ├── NetDiff.csproj ├── EditGraph.cs └── DiffUtil.cs ├── NetDiff.Test ├── Properties │ └── AssemblyInfo.cs ├── NetDiff.Test.csproj └── Test.cs ├── NetDiff.sln ├── .gitattributes ├── README.md └── .gitignore /NetDiff/DiffOrderType.cs: -------------------------------------------------------------------------------- 1 | namespace NetDiff 2 | { 3 | public enum DiffOrderType 4 | { 5 | LazyInsertFirst, 6 | LazyDeleteFirst, 7 | GreedyInsertFirst, 8 | GreedyDeleteFirst, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /NetDiff/DiffOption.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetDiff 4 | { 5 | public class DiffOption 6 | { 7 | public IEqualityComparer EqualityComparer { get; set; } 8 | public int Limit { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /NetDiff/NetDiff.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Diff4Net 5 | 1.2.0 6 | Diff4Net 7 | skanmera 8 | skanmera 9 | false 10 | C# implementation of the Diff algorithm 11 | Improve performance 12 | Copyright 2017 13 | Diff diff 14 | 15 | 16 | -------------------------------------------------------------------------------- /NetDiff/DiffStatus.cs: -------------------------------------------------------------------------------- 1 | namespace NetDiff 2 | { 3 | public enum DiffStatus 4 | { 5 | Equal, 6 | Inserted, 7 | Deleted, 8 | Modified, 9 | } 10 | 11 | public static class DiffStatusExtension 12 | { 13 | public static char GetStatusChar(this DiffStatus self) 14 | { 15 | switch (self) 16 | { 17 | case DiffStatus.Equal: return '='; 18 | case DiffStatus.Inserted: return '+'; 19 | case DiffStatus.Deleted: return '-'; 20 | case DiffStatus.Modified: return 'M'; 21 | } 22 | 23 | throw new System.Exception(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /NetDiff/DiffResult.cs: -------------------------------------------------------------------------------- 1 | namespace NetDiff 2 | { 3 | public class DiffResult 4 | { 5 | public T Obj1 { get; } 6 | public T Obj2 { get; } 7 | public DiffStatus Status { get; } 8 | 9 | public DiffResult(T obj1, T obj2, DiffStatus status) 10 | { 11 | Obj1 = obj1; 12 | Obj2 = obj2; 13 | Status = status; 14 | } 15 | 16 | public override string ToString() 17 | { 18 | return string.Format("Obj1:{0} Obj2:{1} Status:{2}", Obj1.ToString() ?? string.Empty, Obj2.ToString() ?? string.Empty, Status) 19 | .Replace("\0", ""); 20 | } 21 | 22 | public string ToFormatString() 23 | { 24 | var obj = Status == DiffStatus.Deleted ? Obj1 : Obj2; 25 | 26 | return $"{Status.GetStatusChar()} {obj}"; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NetDiff/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 6 | // アセンブリに関連付けられている情報を変更するには、 7 | // これらの属性値を変更してください。 8 | [assembly: AssemblyTitle("NetDiff")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NetDiff")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから 18 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、 19 | // その型の ComVisible 属性を true に設定してください。 20 | [assembly: ComVisible(false)] 21 | 22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります 23 | [assembly: Guid("00a6267d-dde3-42cc-9c99-9166c334053e")] 24 | 25 | // アセンブリのバージョン情報は次の 4 つの値で構成されています: 26 | // 27 | // メジャー バージョン 28 | // マイナー バージョン 29 | // ビルド番号 30 | // Revision 31 | // 32 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を 33 | // 既定値にすることができます: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.2.0.0")] 36 | [assembly: AssemblyFileVersion("1.2.0.0")] 37 | -------------------------------------------------------------------------------- /NetDiff.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 6 | // アセンブリに関連付けられている情報を変更するには、 7 | // これらの属性値を変更してください。 8 | [assembly: AssemblyTitle("NetDiff.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("NetDiff.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから 18 | // 参照できなくなります。このアセンブリ内で COM から型にアクセスする必要がある場合は、 19 | // その型の ComVisible 属性を true に設定してください。 20 | [assembly: ComVisible(false)] 21 | 22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります 23 | [assembly: Guid("aafa1775-1f46-4aa5-97d3-de88d591828d")] 24 | 25 | // アセンブリのバージョン情報は次の 4 つの値で構成されています: 26 | // 27 | // メジャー バージョン 28 | // マイナー バージョン 29 | // ビルド番号 30 | // Revision 31 | // 32 | // すべての値を指定するか、以下のように '*' を使用してビルド番号とリビジョン番号を 33 | // 既定値にすることができます: 34 | //[アセンブリ: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.2.0.0")] 36 | [assembly: AssemblyFileVersion("1.2.0.0")] 37 | -------------------------------------------------------------------------------- /NetDiff.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDiff", "NetDiff\NetDiff.csproj", "{00A6267D-DDE3-42CC-9C99-9166C334053E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDiff.Test", "NetDiff.Test\NetDiff.Test.csproj", "{AAFA1775-1F46-4AA5-97D3-DE88D591828D}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {00A6267D-DDE3-42CC-9C99-9166C334053E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {00A6267D-DDE3-42CC-9C99-9166C334053E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {00A6267D-DDE3-42CC-9C99-9166C334053E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {00A6267D-DDE3-42CC-9C99-9166C334053E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AAFA1775-1F46-4AA5-97D3-DE88D591828D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AAFA1775-1F46-4AA5-97D3-DE88D591828D}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AAFA1775-1F46-4AA5-97D3-DE88D591828D}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AAFA1775-1F46-4AA5-97D3-DE88D591828D}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /NetDiff/Extension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | 5 | namespace NetDiff 6 | { 7 | internal static class Extension 8 | { 9 | public static IEnumerable> MakePairsWithNext(this IEnumerable source) 10 | { 11 | using (var enumerator = source.GetEnumerator()) 12 | { 13 | if (enumerator.MoveNext()) 14 | { 15 | var previous = enumerator.Current; 16 | while (enumerator.MoveNext()) 17 | { 18 | var current = enumerator.Current; 19 | 20 | yield return new Tuple(previous, current); 21 | 22 | previous = current; 23 | } 24 | } 25 | } 26 | } 27 | 28 | public static TSource FindMin(this IEnumerable source, Func selector) 29 | { 30 | return source.FirstOrDefault(c => selector(c).Equals(source.Min(selector))); 31 | } 32 | 33 | public static TSource FindMax(this IEnumerable source, Func selector) 34 | { 35 | return source.FirstOrDefault(c => selector(c).Equals(source.Max(selector))); 36 | } 37 | 38 | public static IEnumerable FindMins(this IEnumerable source, Func selector) 39 | { 40 | return source.ToLookup(selector).FindMin(n => n.Key); 41 | } 42 | 43 | public static IEnumerable FindMaxs(this IEnumerable source, Func selector) 44 | { 45 | return source.ToLookup(selector).FindMax(n => n.Key); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /NetDiff/NetDiff.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {00A6267D-DDE3-42CC-9C99-9166C334053E} 8 | Library 9 | Properties 10 | NetDiff 11 | NetDiff 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /NetDiff.Test/NetDiff.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {AAFA1775-1F46-4AA5-97D3-DE88D591828D} 7 | Library 8 | Properties 9 | NetDiff.Test 10 | NetDiff.Test 11 | v4.5.2 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {00a6267d-dde3-42cc-9c99-9166c334053e} 59 | NetDiff 60 | 61 | 62 | 63 | 64 | 65 | 66 | False 67 | 68 | 69 | False 70 | 71 | 72 | False 73 | 74 | 75 | False 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetDiff 2 | 3 | This is the C # implementation of the Diff algorithm. 4 | 5 |
6 | 7 | ## Usage 8 | 9 | ```cs 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using NetDiff; 14 | 15 | namespace NetDiffSample 16 | { 17 | class Program 18 | { 19 | static void Main(string[] args) 20 | { 21 | var str1 = "string"; 22 | var str2 = "strength"; 23 | 24 | IEnumerable> results = DiffUtil.Diff(str1, str2); 25 | 26 | results.ToList().ForEach(r => Console.WriteLine(r.ToFormatString())); 27 | } 28 | } 29 | } 30 | ``` 31 | ```cs 32 | = s 33 | = t 34 | = r 35 | - i 36 | + e 37 | = n 38 | = g 39 | + t 40 | + h 41 | ``` 42 | 43 |
44 | 45 | ## Option 46 | 47 | ### EqualityComparer 48 | 49 | Specify IEqualityComparer to be used for comparing equality. 50 | 51 | ```cs 52 | class Program 53 | { 54 | static void Main(string[] args) 55 | { 56 | var str1 = "string"; 57 | var str2 = "stRength"; 58 | 59 | var option = new DiffOption(); 60 | option.EqualityComparer = new CaseInsensitiveComparer(); 61 | 62 | IEnumerable> results = DiffUtil.Diff(str1, str2, option); 63 | 64 | results.ToList().ForEach(r => Console.WriteLine(r.ToFormatString())); 65 | Console.Read(); 66 | } 67 | 68 | } 69 | 70 | class CaseInsensitiveComparer : IEqualityComparer 71 | { 72 | public bool Equals(char x, char y) 73 | { 74 | return x.ToString().ToLower().Equals(y.ToString().ToLower()); 75 | } 76 | 77 | public int GetHashCode(char obj) 78 | { 79 | return obj.ToString().ToLower().GetHashCode(); 80 | } 81 | } 82 | ``` 83 | ```cs 84 | = s 85 | = t 86 | = R 87 | + e 88 | - i 89 | = n 90 | = g 91 | + t 92 | + h 93 | ``` 94 | 95 | ### Order 96 | 97 | Specify order of Insert and Delete from the shortest path in the edit graph. 98 | 99 | #### LazyInsertFirst 100 | ```cs 101 | var str1 = "aaa"; 102 | var str2 = "bbb"; 103 | 104 | var option = new DiffOption(); 105 | option.Order = DiffOrder.LazyInsertFirst; 106 | 107 | IEnumerable> results = DiffUtil.Diff(str1, str2, option); 108 | 109 | ``` 110 | ``` 111 | + b 112 | - a 113 | + b 114 | - a 115 | + b 116 | - a 117 | ``` 118 | 119 | 120 | #### LazyDeleteFirst 121 | ```cs 122 | var str1 = "aaa"; 123 | var str2 = "bbb"; 124 | 125 | var option = new DiffOption(); 126 | option.Order = DiffOrder.LazyDeleteFirst; 127 | 128 | IEnumerable> results = DiffUtil.Diff(str1, str2, option); 129 | ``` 130 | ``` 131 | - a 132 | + b 133 | - a 134 | + b 135 | - a 136 | + b 137 | ``` 138 | 139 | #### GreedyInsertFirst 140 | ```cs 141 | var str1 = "aaa"; 142 | var str2 = "bbb"; 143 | 144 | var option = new DiffOption(); 145 | option.Order = DiffOrder.GreedyInsertFirst; 146 | 147 | IEnumerable> results = DiffUtil.Diff(str1, str2, option); 148 | ``` 149 | ``` 150 | + b 151 | + b 152 | + b 153 | - a 154 | - a 155 | - a 156 | ``` 157 | 158 | #### GreedyDeleteFirst 159 | ```cs 160 | var str1 = "aaa"; 161 | var str2 = "bbb"; 162 | 163 | var option = new DiffOption(); 164 | option.Order = DiffOrder.GreedyDeleteFirst; 165 | 166 | IEnumerable> results = DiffUtil.Diff(str1, str2, option); 167 | ``` 168 | ``` 169 | - a 170 | - a 171 | - a 172 | + b 173 | + b 174 | + b 175 | ``` 176 | 177 | ### Performance 178 | 179 | Specify the maximum number of nodes that can exist at once at the edit graph. 180 | The lower the number, the better the performance, but the redundant differences increase. 181 | The default is 1000. 182 | 183 | ```cs 184 | var txt1 = Enumerable.Repeat("aaa", 10000); 185 | var txt2 = Enumerable.Repeat("bbb", 10000); 186 | 187 | var option = new DiffOption(); 188 | 189 | var stopwatch = new System.Diagnostics.Stopwatch(); 190 | 191 | option.Limit = 1000; 192 | stopwatch.Start(); 193 | DiffUtil.Diff(txt1, txt2, option); 194 | stopwatch.Stop(); 195 | 196 | Console.WriteLine(stopwatch.Elapsed); 197 | 198 | option.Limit = 100; 199 | stopwatch.Restart(); 200 | DiffUtil.Diff(txt1, txt2, option); 201 | stopwatch.Stop(); 202 | 203 | Console.WriteLine(stopwatch.Elapsed); 204 | ``` 205 | ```cs 206 | 00:00:08.3869959 207 | 00:00:00.6112575 208 | ``` 209 | 210 |
211 | 212 | ## Optimize 213 | 214 | Convert deleted/inserted to modified. 215 | 216 | ```cs 217 | /* 218 | src a a a a a a 219 | dst b b b -> b b b 220 | - + - + - M M M 221 | */ 222 | var results = DiffUtil.Diff("aaa", "bbb", option); 223 | var optimized = DiffUtil.OptimizeCaseDeletedFirst(results); 224 | ``` 225 | 226 | ## License 227 | 228 | [MIT License](http://opensource.org/licenses/MIT) 229 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ -------------------------------------------------------------------------------- /NetDiff/EditGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace NetDiff 6 | { 7 | internal enum Direction 8 | { 9 | Right, 10 | Bottom, 11 | Diagonal, 12 | } 13 | 14 | internal struct Point : IEquatable 15 | { 16 | public int X { get; } 17 | public int Y { get; } 18 | 19 | public Point(int x, int y) 20 | { 21 | X = x; 22 | Y = y; 23 | } 24 | 25 | public override bool Equals(object obj) 26 | { 27 | if (!(obj is Point)) 28 | return false; 29 | 30 | return Equals((Point)obj); 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | var hash = 17; 36 | hash = hash * 23 + X.GetHashCode(); 37 | hash = hash * 23 + Y.GetHashCode(); 38 | 39 | return hash; 40 | } 41 | 42 | public bool Equals(Point other) 43 | { 44 | return X == other.X && Y == other.Y; 45 | } 46 | 47 | public override string ToString() 48 | { 49 | return $"X:{X} Y:{Y}"; 50 | } 51 | } 52 | 53 | internal class Node 54 | { 55 | public Point Point { get; set; } 56 | public Node Parent { get; set; } 57 | 58 | public Node(Point point) 59 | { 60 | Point = point; 61 | } 62 | 63 | public override string ToString() 64 | { 65 | return $"X:{Point.X} Y:{Point.Y}"; 66 | } 67 | } 68 | 69 | internal class EditGraph 70 | { 71 | private T[] seq1; 72 | private T[] seq2; 73 | private DiffOption option; 74 | private List heads; 75 | private Point endpoint; 76 | private int[] farthestPoints; 77 | private int offset; 78 | private bool isEnd; 79 | 80 | public EditGraph( 81 | IEnumerable seq1, IEnumerable seq2) 82 | { 83 | this.seq1 = seq1.ToArray(); 84 | this.seq2 = seq2.ToArray(); 85 | endpoint = new Point(this.seq1.Length, this.seq2.Length); 86 | offset = this.seq2.Length; 87 | } 88 | 89 | public List CalculatePath(DiffOption option) 90 | { 91 | if (!seq1.Any()) 92 | return Enumerable.Range(0, seq2.Length + 1).Select(i => new Point(0, i)).ToList(); 93 | 94 | if (!seq2.Any()) 95 | return Enumerable.Range(0, seq1.Length + 1).Select(i => new Point(i, 0)).ToList(); 96 | 97 | this.option = option; 98 | 99 | BeginCalculatePath(); 100 | 101 | while (Next()) { } 102 | 103 | return EndCalculatePath(); 104 | } 105 | 106 | private void Initialize() 107 | { 108 | farthestPoints = new int[seq1.Length + seq2.Length + 1]; 109 | heads = new List(); 110 | } 111 | 112 | private void BeginCalculatePath() 113 | { 114 | Initialize(); 115 | 116 | heads.Add(new Node(new Point(0, 0))); 117 | 118 | Snake(); 119 | } 120 | 121 | private List EndCalculatePath() 122 | { 123 | var wayponit = new List(); 124 | 125 | var current = heads.Where(h => h.Point.Equals(endpoint)).FirstOrDefault(); 126 | while (current != null) 127 | { 128 | wayponit.Add(current.Point); 129 | 130 | current = current.Parent; 131 | } 132 | 133 | wayponit.Reverse(); 134 | 135 | return wayponit; 136 | } 137 | 138 | private bool Next() 139 | { 140 | if (isEnd) 141 | return false; 142 | 143 | UpdateHeads(); 144 | 145 | return true; 146 | } 147 | 148 | private void UpdateHeads() 149 | { 150 | if (option.Limit > 0 && heads.Count > option.Limit) 151 | { 152 | var tmp = heads.First(); 153 | heads.Clear(); 154 | 155 | heads.Add(tmp); 156 | } 157 | 158 | var updated = new List(); 159 | 160 | foreach (var head in heads) 161 | { 162 | Node rightHead; 163 | if (TryCreateHead(head, Direction.Right, out rightHead)) 164 | { 165 | updated.Add(rightHead); 166 | } 167 | 168 | Node bottomHead; 169 | if (TryCreateHead(head, Direction.Bottom, out bottomHead)) 170 | { 171 | updated.Add(bottomHead); 172 | } 173 | } 174 | 175 | heads = updated; 176 | 177 | Snake(); 178 | } 179 | 180 | private void Snake() 181 | { 182 | var tmp = new List(); 183 | foreach (var h in heads) 184 | { 185 | var newHead = Snake(h); 186 | 187 | if (newHead != null) 188 | tmp.Add(newHead); 189 | else 190 | tmp.Add(h); 191 | } 192 | 193 | heads = tmp; 194 | } 195 | 196 | private Node Snake(Node head) 197 | { 198 | Node newHead = null; 199 | while (true) 200 | { 201 | Node tmp; 202 | if (TryCreateHead(newHead ?? head, Direction.Diagonal, out tmp)) 203 | newHead = tmp; 204 | else 205 | break; 206 | } 207 | 208 | return newHead; 209 | } 210 | 211 | private bool TryCreateHead(Node head, Direction direction, out Node newHead) 212 | { 213 | newHead = null; 214 | var newPoint = GetPoint(head.Point, direction); 215 | 216 | if (!CanCreateHead(head.Point, direction, newPoint)) 217 | return false; 218 | 219 | newHead = new Node(newPoint); 220 | newHead.Parent = head; 221 | 222 | isEnd |= newHead.Point.Equals(endpoint); 223 | 224 | return true; 225 | } 226 | 227 | private bool CanCreateHead(Point currentPoint, Direction direction, Point nextPoint) 228 | { 229 | if (!InRange(nextPoint)) 230 | return false; 231 | 232 | if (direction == Direction.Diagonal) 233 | { 234 | var equal = option.EqualityComparer != null 235 | ? option.EqualityComparer.Equals(seq1[nextPoint.X - 1], (seq2[nextPoint.Y - 1])) 236 | : seq1[nextPoint.X - 1].Equals(seq2[nextPoint.Y - 1]); 237 | 238 | if (!equal) 239 | return false; 240 | } 241 | 242 | return UpdateFarthestPoint(nextPoint); 243 | } 244 | 245 | private Point GetPoint(Point currentPoint, Direction direction) 246 | { 247 | switch (direction) 248 | { 249 | case Direction.Right: 250 | return new Point(currentPoint.X + 1, currentPoint.Y); 251 | case Direction.Bottom: 252 | return new Point(currentPoint.X, currentPoint.Y + 1); 253 | case Direction.Diagonal: 254 | return new Point(currentPoint.X + 1, currentPoint.Y + 1); 255 | } 256 | 257 | throw new ArgumentException(); 258 | } 259 | 260 | private bool InRange(Point point) 261 | { 262 | return point.X >= 0 && point.Y >= 0 && point.X <= endpoint.X && point.Y <= endpoint.Y; 263 | } 264 | 265 | private bool UpdateFarthestPoint(Point point) 266 | { 267 | var k = point.X - point.Y; 268 | var y = farthestPoints[k + offset]; 269 | 270 | if (point.Y <= y) 271 | return false; 272 | 273 | farthestPoints[k + offset] = point.Y; 274 | 275 | return true; 276 | } 277 | } 278 | } 279 | 280 | -------------------------------------------------------------------------------- /NetDiff/DiffUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace NetDiff 6 | { 7 | public class DiffUtil 8 | { 9 | public static IEnumerable> Diff(IEnumerable seq1, IEnumerable seq2) 10 | { 11 | return Diff(seq1, seq2, new DiffOption()); 12 | } 13 | 14 | public static IEnumerable> Diff(IEnumerable seq1, IEnumerable seq2, DiffOption option) 15 | { 16 | if (seq1 == null || seq2 == null || (!seq1.Any() && !seq2.Any())) 17 | return Enumerable.Empty>(); 18 | 19 | var editGrap = new EditGraph(seq1, seq2); 20 | var waypoints = editGrap.CalculatePath(option); 21 | 22 | return MakeResults(waypoints, seq1, seq2); 23 | } 24 | 25 | public static IEnumerable CreateSrc(IEnumerable> diffResults) 26 | { 27 | return diffResults.Where(r => r.Status != DiffStatus.Inserted).Select(r => r.Obj1); 28 | } 29 | 30 | public static IEnumerable CreateDst(IEnumerable> diffResults) 31 | { 32 | return diffResults.Where(r => r.Status != DiffStatus.Deleted).Select(r => r.Obj2); 33 | } 34 | 35 | public static IEnumerable> OptimizeCaseDeletedFirst(IEnumerable> diffResults) 36 | { 37 | return Optimize(diffResults, true); 38 | } 39 | 40 | public static IEnumerable> OptimizeCaseInsertedFirst(IEnumerable> diffResults) 41 | { 42 | return Optimize(diffResults, false); 43 | } 44 | 45 | private static IEnumerable> Optimize(IEnumerable> diffResults, bool deleteFirst = true) 46 | { 47 | var currentStatus = deleteFirst ? DiffStatus.Deleted : DiffStatus.Inserted; 48 | var nextStatus = deleteFirst ? DiffStatus.Inserted : DiffStatus.Deleted; 49 | 50 | var queue = new Queue>(diffResults); 51 | while (queue.Any()) 52 | { 53 | var result = queue.Dequeue(); 54 | if (result.Status == currentStatus) 55 | { 56 | if (queue.Any() && queue.Peek().Status == nextStatus) 57 | { 58 | var obj1 = deleteFirst ? result.Obj1 : queue.Dequeue().Obj1; 59 | var obj2 = deleteFirst ? queue.Dequeue().Obj2 : result.Obj2; 60 | yield return new DiffResult(obj1, obj2, DiffStatus.Modified); 61 | } 62 | else 63 | yield return result; 64 | 65 | continue; 66 | } 67 | 68 | yield return result; 69 | } 70 | } 71 | 72 | private static IEnumerable> MakeResults(IEnumerable waypoints, IEnumerable seq1, IEnumerable seq2) 73 | { 74 | var array1 = seq1.ToArray(); 75 | var array2 = seq2.ToArray(); 76 | 77 | foreach (var pair in waypoints.MakePairsWithNext()) 78 | { 79 | var status = GetStatus(pair.Item1, pair.Item2); 80 | T obj1 = default(T); 81 | T obj2 = default(T); 82 | switch (status) 83 | { 84 | case DiffStatus.Equal: 85 | obj1 = array1[pair.Item2.X - 1]; 86 | obj2 = array2[pair.Item2.Y - 1]; 87 | break; 88 | case DiffStatus.Inserted: 89 | obj2 = array2[pair.Item2.Y - 1]; 90 | break; 91 | case DiffStatus.Deleted: 92 | obj1 = array1[pair.Item2.X - 1]; 93 | break; 94 | } 95 | 96 | yield return new DiffResult(obj1, obj2, status); 97 | } 98 | } 99 | 100 | private static DiffStatus GetStatus(Point current, Point prev) 101 | { 102 | if (current.X != prev.X && current.Y != prev.Y) 103 | return DiffStatus.Equal; 104 | else if (current.X != prev.X) 105 | return DiffStatus.Deleted; 106 | else if (current.Y != prev.Y) 107 | return DiffStatus.Inserted; 108 | else 109 | throw new Exception(); 110 | } 111 | 112 | public static IEnumerable> Order(IEnumerable> results, DiffOrderType orderType) 113 | { 114 | var resultArray = results.ToArray(); 115 | 116 | for (int i = 0; i < resultArray.Length; i++) 117 | { 118 | if (resultArray[i].Status == DiffStatus.Deleted) 119 | { 120 | while (i - 1 >= 0) 121 | { 122 | if (resultArray[i - 1].Status == DiffStatus.Equal && resultArray[i].Obj1.Equals(resultArray[i - 1].Obj1)) 123 | { 124 | var tmp = resultArray[i]; 125 | resultArray[i] = resultArray[i - 1]; 126 | resultArray[i - 1] = tmp; 127 | 128 | i--; 129 | } 130 | else 131 | { 132 | break; 133 | } 134 | } 135 | } 136 | } 137 | 138 | var resultQueue = new Queue>(resultArray); 139 | var additionQueue = new Queue>(); 140 | var deletionQueue = new Queue>(); 141 | 142 | while (resultQueue.Any()) 143 | { 144 | if (resultQueue.Peek().Status == DiffStatus.Equal) 145 | { 146 | yield return resultQueue.Dequeue(); 147 | continue; 148 | } 149 | 150 | while (resultQueue.Any() && resultQueue.Peek().Status != DiffStatus.Equal) 151 | { 152 | while (resultQueue.Any() && resultQueue.Peek().Status == DiffStatus.Inserted) 153 | { 154 | additionQueue.Enqueue(resultQueue.Dequeue()); 155 | } 156 | 157 | while (resultQueue.Any() && resultQueue.Peek().Status == DiffStatus.Deleted) 158 | { 159 | deletionQueue.Enqueue(resultQueue.Dequeue()); 160 | } 161 | } 162 | 163 | var latestReturenStatus = DiffStatus.Equal; 164 | while (true) 165 | { 166 | if (additionQueue.Any() && !deletionQueue.Any()) 167 | { 168 | yield return additionQueue.Dequeue(); 169 | } 170 | else if (!additionQueue.Any() && deletionQueue.Any()) 171 | { 172 | yield return deletionQueue.Dequeue(); 173 | } 174 | else if (additionQueue.Any() && deletionQueue.Any()) 175 | { 176 | switch (orderType) 177 | { 178 | case DiffOrderType.GreedyDeleteFirst: 179 | yield return deletionQueue.Dequeue(); 180 | latestReturenStatus = DiffStatus.Deleted; 181 | break; 182 | case DiffOrderType.GreedyInsertFirst: 183 | yield return additionQueue.Dequeue(); 184 | latestReturenStatus = DiffStatus.Inserted; 185 | break; 186 | case DiffOrderType.LazyDeleteFirst: 187 | if (latestReturenStatus != DiffStatus.Deleted) 188 | { 189 | yield return deletionQueue.Dequeue(); 190 | latestReturenStatus = DiffStatus.Deleted; 191 | } 192 | else 193 | { 194 | yield return additionQueue.Dequeue(); 195 | latestReturenStatus = DiffStatus.Inserted; 196 | } 197 | break; 198 | case DiffOrderType.LazyInsertFirst: 199 | if (latestReturenStatus != DiffStatus.Inserted) 200 | { 201 | yield return additionQueue.Dequeue(); 202 | latestReturenStatus = DiffStatus.Inserted; 203 | } 204 | else 205 | { 206 | yield return deletionQueue.Dequeue(); 207 | latestReturenStatus = DiffStatus.Deleted; 208 | } 209 | break; 210 | } 211 | } 212 | else 213 | { 214 | break; 215 | } 216 | } 217 | } 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /NetDiff.Test/Test.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace NetDiff.Test 6 | { 7 | [TestClass] 8 | public class Test 9 | { 10 | [TestMethod] 11 | public void Equal_GreedyDeleteFirst() 12 | { 13 | var str1 = "abcde"; 14 | var str2 = "abcde"; 15 | 16 | var results = DiffUtil.Diff(str1, str2); 17 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 18 | 19 | Assert.AreEqual(str1.Count(), results.Count()); 20 | Assert.IsTrue(results.All(r => r.Status == DiffStatus.Equal)); 21 | Assert.IsTrue(results.Select(r => r.Obj1).SequenceEqual(str1.Select(c => c))); 22 | Assert.IsTrue(results.Select(r => r.Obj2).SequenceEqual(str1.Select(c => c))); 23 | } 24 | 25 | [TestMethod] 26 | public void Equal_GreedyInsertFirst() 27 | { 28 | var str1 = "abcde"; 29 | var str2 = "abcde"; 30 | 31 | var results = DiffUtil.Diff(str1, str2); 32 | results = DiffUtil.Order(results, DiffOrderType.GreedyInsertFirst); 33 | 34 | Assert.AreEqual(str1.Count(), results.Count()); 35 | Assert.IsTrue(results.All(r => r.Status == DiffStatus.Equal)); 36 | Assert.IsTrue(results.Select(r => r.Obj1).SequenceEqual(str1.Select(c => c))); 37 | Assert.IsTrue(results.Select(r => r.Obj2).SequenceEqual(str1.Select(c => c))); 38 | } 39 | 40 | [TestMethod] 41 | public void Equal_LazyDeleteFirst() 42 | { 43 | var str1 = "abcde"; 44 | var str2 = "abcde"; 45 | 46 | var results = DiffUtil.Diff(str1, str2); 47 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst); 48 | 49 | Assert.AreEqual(str1.Count(), results.Count()); 50 | Assert.IsTrue(results.All(r => r.Status == DiffStatus.Equal)); 51 | Assert.IsTrue(results.Select(r => r.Obj1).SequenceEqual(str1.Select(c => c))); 52 | Assert.IsTrue(results.Select(r => r.Obj2).SequenceEqual(str1.Select(c => c))); 53 | } 54 | 55 | [TestMethod] 56 | public void Equal_LazyInsertFirst() 57 | { 58 | var str1 = "abcde"; 59 | var str2 = "abcde"; 60 | 61 | var results = DiffUtil.Diff(str1, str2); 62 | results = DiffUtil.Order(results, DiffOrderType.LazyInsertFirst); 63 | 64 | Assert.AreEqual(str1.Count(), results.Count()); 65 | Assert.IsTrue(results.All(r => r.Status == DiffStatus.Equal)); 66 | Assert.IsTrue(results.Select(r => r.Obj1).SequenceEqual(str1.Select(c => c))); 67 | Assert.IsTrue(results.Select(r => r.Obj2).SequenceEqual(str1.Select(c => c))); 68 | } 69 | 70 | [TestMethod] 71 | public void Restore_GreedyDeleteFirst() 72 | { 73 | var str1 = "q4DU8sbeD4JdhFA4hWShCv3bbtD7djX5SaNnQUHJHdCEJs6X2LJipbEEr7bZZbzcUrpuKpRDKNz92x5P"; 74 | var str2 = "3GKLWNDdCxip8kda2r2MUT45RrHUiESQhmhUZtMcpBGcSwJVS9uq4DWBAQk2zPUJCJabaeWuP5mxyPBz"; 75 | 76 | var results = DiffUtil.Diff(str1, str2 ); 77 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 78 | 79 | var src = new string(DiffUtil.CreateSrc(results).ToArray()); 80 | var dst = new string(DiffUtil.CreateDst(results).ToArray()); 81 | Assert.AreEqual(dst, str2); 82 | Assert.AreEqual(src, str1); 83 | } 84 | 85 | [TestMethod] 86 | public void Restore_GreedyInsertFirst() 87 | { 88 | var str1 = "q4DU8sbeD4JdhFA4hWShCv3bbtD7djX5SaNnQUHJHdCEJs6X2LJipbEEr7bZZbzcUrpuKpRDKNz92x5P"; 89 | var str2 = "3GKLWNDdCxip8kda2r2MUT45RrHUiESQhmhUZtMcpBGcSwJVS9uq4DWBAQk2zPUJCJabaeWuP5mxyPBz"; 90 | 91 | var results = DiffUtil.Diff(str1, str2); 92 | results = DiffUtil.Order(results, DiffOrderType.GreedyInsertFirst); 93 | 94 | var src = new string(DiffUtil.CreateSrc(results).ToArray()); 95 | var dst = new string(DiffUtil.CreateDst(results).ToArray()); 96 | Assert.AreEqual(dst, str2); 97 | Assert.AreEqual(src, str1); 98 | } 99 | 100 | [TestMethod] 101 | public void Restore_LazyInsertFirst() 102 | { 103 | var str1 = "q4DU8sbeD4JdhFA4hWShCv3bbtD7djX5SaNnQUHJHdCEJs6X2LJipbEEr7bZZbzcUrpuKpRDKNz92x5P"; 104 | var str2 = "3GKLWNDdCxip8kda2r2MUT45RrHUiESQhmhUZtMcpBGcSwJVS9uq4DWBAQk2zPUJCJabaeWuP5mxyPBz"; 105 | 106 | var results = DiffUtil.Diff(str1, str2); 107 | results = DiffUtil.Order(results, DiffOrderType.LazyInsertFirst); 108 | 109 | var src = new string(DiffUtil.CreateSrc(results).ToArray()); 110 | var dst = new string(DiffUtil.CreateDst(results).ToArray()); 111 | Assert.AreEqual(dst, str2); 112 | Assert.AreEqual(src, str1); 113 | } 114 | 115 | [TestMethod] 116 | public void Restore_LazyDeleteFirst() 117 | { 118 | var str1 = "q4DU8sbeD4JdhFA4hWShCv3bbtD7djX5SaNnQUHJHdCEJs6X2LJipbEEr7bZZbzcUrpuKpRDKNz92x5P"; 119 | var str2 = "3GKLWNDdCxip8kda2r2MUT45RrHUiESQhmhUZtMcpBGcSwJVS9uq4DWBAQk2zPUJCJabaeWuP5mxyPBz"; 120 | 121 | var results = DiffUtil.Diff(str1, str2); 122 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst); 123 | 124 | var src = new string(DiffUtil.CreateSrc(results).ToArray()); 125 | var dst = new string(DiffUtil.CreateDst(results).ToArray()); 126 | Assert.AreEqual(dst, str2); 127 | Assert.AreEqual(src, str1); 128 | } 129 | 130 | /* 131 | a a a b b b 132 | - - - + + + 133 | */ 134 | [TestMethod] 135 | public void DifferentAll_GreedyDeleteFirst() 136 | { 137 | var str1 = "aaa"; 138 | var str2 = "bbb"; 139 | 140 | var results = DiffUtil.Diff(str1, str2); 141 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 142 | 143 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(0).Status); 144 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 145 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(2).Status); 146 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 147 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(4).Status); 148 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(5).Status); 149 | } 150 | 151 | /* 152 | b b b a a a 153 | + + + - - - 154 | */ 155 | [TestMethod] 156 | public void DifferentAll_GreedyInsertFirst() 157 | { 158 | var str1 = "aaa"; 159 | var str2 = "bbb"; 160 | 161 | var results = DiffUtil.Diff(str1, str2); 162 | results = DiffUtil.Order(results, DiffOrderType.GreedyInsertFirst); 163 | 164 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 165 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 166 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(2).Status); 167 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(3).Status); 168 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(4).Status); 169 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(5).Status); 170 | } 171 | 172 | /* 173 | a b a b a b 174 | - + - + - + 175 | */ 176 | [TestMethod] 177 | public void DifferentAll_LazyDeleteFirst() 178 | { 179 | var str1 = "aaa"; 180 | var str2 = "bbb"; 181 | 182 | var results = DiffUtil.Diff(str1, str2 ); 183 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst).ToList(); 184 | 185 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(0).Status); 186 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 187 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(2).Status); 188 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 189 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(4).Status); 190 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(5).Status); 191 | } 192 | 193 | /* 194 | b a b a b a 195 | + - + - + - 196 | */ 197 | [TestMethod] 198 | public void DifferentAll_LazyInsertFirst() 199 | { 200 | var str1 = "aaa"; 201 | var str2 = "bbb"; 202 | 203 | var results = DiffUtil.Diff(str1, str2); 204 | results = DiffUtil.Order(results, DiffOrderType.LazyInsertFirst); 205 | 206 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 207 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 208 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(2).Status); 209 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(3).Status); 210 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(4).Status); 211 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(5).Status); 212 | } 213 | 214 | /* 215 | a b c d 216 | = = = + 217 | */ 218 | [TestMethod] 219 | public void Appended() 220 | { 221 | var str1 = "abc"; 222 | var str2 = "abcd"; 223 | 224 | var results = DiffUtil.Diff(str1, str2); 225 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 226 | 227 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 228 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(1).Status); 229 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 230 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 231 | 232 | results = DiffUtil.Diff(str1, str2); 233 | results = DiffUtil.Order(results, DiffOrderType.GreedyInsertFirst); 234 | 235 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 236 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(1).Status); 237 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 238 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 239 | 240 | results = DiffUtil.Diff(str1, str2); 241 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst); 242 | 243 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 244 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(1).Status); 245 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 246 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 247 | 248 | results = DiffUtil.Diff(str1, str2); 249 | results = DiffUtil.Order(results, DiffOrderType.LazyInsertFirst); 250 | 251 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 252 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(1).Status); 253 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 254 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 255 | } 256 | 257 | /* 258 | a b c d 259 | + = = = 260 | */ 261 | [TestMethod] 262 | public void Prepended() 263 | { 264 | var str1 = "bcd"; 265 | var str2 = "abcd"; 266 | 267 | var results = DiffUtil.Diff(str1, str2); 268 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 269 | 270 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 271 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(1).Status); 272 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 273 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 274 | 275 | results = DiffUtil.Diff(str1, str2); 276 | results = DiffUtil.Order(results, DiffOrderType.GreedyInsertFirst); 277 | 278 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 279 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(1).Status); 280 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 281 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 282 | 283 | results = DiffUtil.Diff(str1, str2); 284 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst); 285 | 286 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 287 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(1).Status); 288 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 289 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 290 | 291 | results = DiffUtil.Diff(str1, str2); 292 | results = DiffUtil.Order(results, DiffOrderType.LazyInsertFirst); 293 | 294 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 295 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(1).Status); 296 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 297 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 298 | } 299 | 300 | [TestMethod] 301 | public void CaseMultiSameScore_GreedyDeleteFirst() 302 | { 303 | var str1 = "cdhijkz"; 304 | var str2 = "ldxhnokz"; 305 | 306 | var results = DiffUtil.Diff(str1, str2); 307 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 308 | 309 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(0).Status); 310 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 311 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 312 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 313 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 314 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(5).Status); 315 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(6).Status); 316 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(7).Status); 317 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(8).Status); 318 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(9).Status); 319 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(10).Status); 320 | } 321 | 322 | [TestMethod] 323 | public void CaseMultiSameScore_GreedyInsertFirst() 324 | { 325 | var str1 = "cdhijkz"; 326 | var str2 = "ldxhnokz"; 327 | 328 | var results = DiffUtil.Diff(str1, str2); 329 | results = DiffUtil.Order(results, DiffOrderType.GreedyInsertFirst); 330 | 331 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 332 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 333 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 334 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 335 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 336 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(5).Status); 337 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(6).Status); 338 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(7).Status); 339 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(8).Status); 340 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(9).Status); 341 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(10).Status); 342 | } 343 | 344 | [TestMethod] 345 | public void CaseMultiSameScore_LazyDeleteFirst() 346 | { 347 | var str1 = "cdhijkz"; 348 | var str2 = "ldxhnokz"; 349 | 350 | var results = DiffUtil.Diff(str1, str2); 351 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst); 352 | 353 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(0).Status); 354 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 355 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 356 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 357 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 358 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(5).Status); 359 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(6).Status); 360 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(7).Status); 361 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(8).Status); 362 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(9).Status); 363 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(10).Status); 364 | } 365 | 366 | [TestMethod] 367 | public void CaseMultiSameScore_LazyInsertFirst() 368 | { 369 | var str1 = "cdhijkz"; 370 | var str2 = "ldxhnokz"; 371 | 372 | var results = DiffUtil.Diff(str1, str2); 373 | results = DiffUtil.Order(results, DiffOrderType.LazyInsertFirst); 374 | 375 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 376 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 377 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 378 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 379 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 380 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(5).Status); 381 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(6).Status); 382 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(7).Status); 383 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(8).Status); 384 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(9).Status); 385 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(10).Status); 386 | } 387 | 388 | [TestMethod] 389 | public void OptimizeCaseDeleteFirst() 390 | { 391 | var str1 = "aaa"; 392 | var str2 = "bbb"; 393 | 394 | /* 395 | obj1 a a a 396 | obj2 b b b 397 | - - M + + 398 | */ 399 | var results = DiffUtil.Diff(str1, str2); 400 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 401 | results = DiffUtil.OptimizeCaseDeletedFirst(results); 402 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(0).Status); 403 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 404 | Assert.AreEqual(DiffStatus.Modified, results.ElementAt(2).Status); 405 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 406 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(4).Status); 407 | 408 | 409 | /* 410 | obj1 a a a 411 | obj2 b b b 412 | + + M - - 413 | */ 414 | results = DiffUtil.Diff(str1, str2); 415 | results = DiffUtil.Order(results, DiffOrderType.GreedyInsertFirst); 416 | results = DiffUtil.OptimizeCaseInsertedFirst(results); 417 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 418 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 419 | Assert.AreEqual(DiffStatus.Modified, results.ElementAt(2).Status); 420 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(3).Status); 421 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(4).Status); 422 | 423 | 424 | /* 425 | obj1 a a a 426 | obj2 b b b 427 | M M M 428 | */ 429 | results = DiffUtil.Diff(str1, str2); 430 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst); 431 | results = DiffUtil.OptimizeCaseDeletedFirst(results); 432 | Assert.AreEqual(DiffStatus.Modified, results.ElementAt(0).Status); 433 | Assert.AreEqual(DiffStatus.Modified, results.ElementAt(1).Status); 434 | Assert.AreEqual(DiffStatus.Modified, results.ElementAt(2).Status); 435 | 436 | /* 437 | obj1 a a a 438 | obj2 b b b 439 | M M M 440 | */ 441 | results = DiffUtil.Diff(str1, str2); 442 | results = DiffUtil.Order(results, DiffOrderType.LazyInsertFirst); 443 | results = DiffUtil.OptimizeCaseInsertedFirst(results); 444 | Assert.AreEqual(DiffStatus.Modified, results.ElementAt(0).Status); 445 | Assert.AreEqual(DiffStatus.Modified, results.ElementAt(1).Status); 446 | Assert.AreEqual(DiffStatus.Modified, results.ElementAt(2).Status); 447 | } 448 | 449 | [TestMethod] 450 | public void CaseRepeat_GreedyInsertFirst() 451 | { 452 | string str1 = "abbbc"; 453 | string str2 = "adbbc"; 454 | 455 | var results = DiffUtil.Diff(str1, str2); 456 | results = DiffUtil.Order(results, DiffOrderType.GreedyInsertFirst); 457 | 458 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 459 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 460 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(2).Status); 461 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 462 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 463 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(5).Status); 464 | } 465 | 466 | [TestMethod] 467 | public void CaseRepeat_GreedyDeleteFirst() 468 | { 469 | string str1 = "abbbc"; 470 | string str2 = "adbbc"; 471 | 472 | var results = DiffUtil.Diff(str1, str2 ); 473 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 474 | 475 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 476 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 477 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(2).Status); 478 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 479 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 480 | } 481 | 482 | [TestMethod] 483 | public void CaseRepeat_LazyInsertFirst() 484 | { 485 | string str1 = "abbbc"; 486 | string str2 = "adbbc"; 487 | 488 | var results = DiffUtil.Diff(str1, str2); 489 | results = DiffUtil.Order(results, DiffOrderType.LazyInsertFirst); 490 | 491 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 492 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 493 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(2).Status); 494 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 495 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 496 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(5).Status); 497 | } 498 | 499 | [TestMethod] 500 | public void CaseRepeat_LazyDeleteFirst() 501 | { 502 | string str1 = "abbbc"; 503 | string str2 = "adbbc"; 504 | 505 | var results = DiffUtil.Diff(str1, str2); 506 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst); 507 | 508 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 509 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 510 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(2).Status); 511 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 512 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 513 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(5).Status); 514 | } 515 | 516 | 517 | [TestMethod] 518 | public void SpecifiedComparer() 519 | { 520 | var str1 = "abc"; 521 | var str2 = "dBf"; 522 | 523 | var option = new DiffOption(); 524 | option.EqualityComparer = new CaseInsensitiveComparer(); 525 | 526 | var results = DiffUtil.Diff(str1, str2, option); 527 | results = DiffUtil.Order(results, DiffOrderType.LazyDeleteFirst); 528 | 529 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(0).Status); 530 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 531 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(2).Status); 532 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(3).Status); 533 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(4).Status); 534 | } 535 | 536 | [TestMethod] 537 | public void CaseReplace() 538 | { 539 | string str1 = "abbbc"; 540 | string str2 = "adbbc"; 541 | 542 | var results = DiffUtil.Diff(str1, str2); 543 | results = DiffUtil.Order(results, DiffOrderType.GreedyDeleteFirst); 544 | 545 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(0).Status); 546 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 547 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(2).Status); 548 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(3).Status); 549 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(4).Status); 550 | Assert.AreEqual(DiffStatus.Equal, results.ElementAt(5).Status); 551 | } 552 | 553 | [TestMethod] 554 | public void GivenEmpty() 555 | { 556 | string str1 = ""; 557 | string str2 = ""; 558 | 559 | var results = DiffUtil.Diff(str1, str2); 560 | 561 | Assert.IsTrue(!results.Any()); 562 | } 563 | 564 | [TestMethod] 565 | public void GivenSeq1Empty() 566 | { 567 | string str1 = ""; 568 | string str2 = "abcde"; 569 | 570 | var results = DiffUtil.Diff(str1, str2); 571 | 572 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(0).Status); 573 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(1).Status); 574 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(2).Status); 575 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(3).Status); 576 | Assert.AreEqual(DiffStatus.Inserted, results.ElementAt(4).Status); 577 | } 578 | 579 | [TestMethod] 580 | public void GivenSeq2Empty() 581 | { 582 | string str1 = "abced"; 583 | string str2 = ""; 584 | 585 | var results = DiffUtil.Diff(str1, str2); 586 | 587 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(0).Status); 588 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(1).Status); 589 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(2).Status); 590 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(3).Status); 591 | Assert.AreEqual(DiffStatus.Deleted, results.ElementAt(4).Status); 592 | } 593 | 594 | [TestMethod] 595 | public void Performance() 596 | { 597 | var str1 = Enumerable.Repeat("Good dog", 1000).SelectMany(c => c); 598 | var str2 = Enumerable.Repeat("Bad dog", 1000).SelectMany(c => c); 599 | 600 | var option = new DiffOption(); 601 | option.Limit = 1000; 602 | 603 | var sw = new System.Diagnostics.Stopwatch(); 604 | sw.Start(); 605 | var result1 = DiffUtil.Diff(str1, str2); 606 | sw.Stop(); 607 | var time1 = sw.Elapsed; 608 | 609 | sw.Restart(); 610 | var result2 = DiffUtil.Diff(str1, str2, option); 611 | sw.Stop(); 612 | var time2 = sw.Elapsed; 613 | 614 | Assert.IsTrue(time2 < time1); 615 | } 616 | 617 | [TestMethod] 618 | public void CaseEmpty() 619 | { 620 | var str1 = string.Empty; 621 | var str2 = string.Empty; 622 | 623 | var results = DiffUtil.Diff(str1, str2); 624 | 625 | Assert.IsTrue(!results.Any()); 626 | } 627 | 628 | [TestMethod] 629 | public void CaseNull() 630 | { 631 | string str1 = null; 632 | var str2 = string.Empty; 633 | 634 | var results = DiffUtil.Diff(str1, str2); 635 | 636 | Assert.IsTrue(!results.Any()); 637 | } 638 | 639 | internal class CaseInsensitiveComparer : IEqualityComparer 640 | { 641 | public bool Equals(char x, char y) 642 | { 643 | return x.ToString().ToLower().Equals(y.ToString().ToLower()); 644 | } 645 | 646 | public int GetHashCode(char obj) 647 | { 648 | return obj.ToString().ToLower().GetHashCode(); 649 | } 650 | } 651 | } 652 | } 653 | --------------------------------------------------------------------------------