• Source
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5.  
    6. namespace SEA2016.FuzzySearch
    7. {
    8. /// <summary>
    9. /// Calculate Levenshtein distance between two strings using Dynamic Programming approach
    10. /// </summary>
    11. public static class LevenshteinDynamicProgramming
    12. {
    13. /// <summary>
    14. /// Wagner–Fischer algorithm with full matrix
    15. /// Time complexity: O(|a|*|b|)
    16. /// Memory complexity: O(|a|*|b|)
    17. /// https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
    18. /// </summary>
    19. public static int Calculate(string a, string b)
    20. {
    21. return CreateMatrix(a, b)[a.Length, b.Length];
    22. }
    23.  
    24. /// <summary>
    25. /// Wagner–Fischer algorithm that uses memory for two rows
    26. /// Time complexity: O(|a|*|b|)
    27. /// Memory complexity: O(max(|a|,|b|))
    28. /// https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
    29. /// </summary>
    30. public static int CalculateMemoryOptimized(string a, string b)
    31. {
    32. if (a.Length < b.Length) {
    33. var t = a;
    34. a = b;
    35. b = t;
    36. }
    37.  
    38. var d = new int[b.Length + 1];
    39. var c = new int[b.Length + 1];
    40.  
    41. // Initialization
    42. for (int i = 0; i <= b.Length; i++)
    43. d[i] = i;
    44.  
    45. // Calculation
    46. for (int i = 0; i < a.Length; i++) {
    47. c[0] = i + 1;
    48.  
    49. for (int j = 0; j < b.Length; j++) {
    50. if (a[i] == b[j]) {
    51. c[j + 1] = d[j];
    52. } else {
    53. c[j + 1] = 1 + Math.Min(d[j], Math.Min(d[j + 1], c[j]));
    54. }
    55. }
    56.  
    57. var t = c;
    58. c = d;
    59. d = t;
    60. }
    61.  
    62. return d[b.Length];
    63. }
    64.  
    65. /// <summary>
    66. /// Print steps to transform a to b
    67. /// </summary>
    68. public static TraceLog Trace(string a, string b)
    69. {
    70. return new TraceLog(a, b, CreateMatrix(a, b));
    71. }
    72.  
    73. private static int[,] CreateMatrix(string a, string b)
    74. {
    75. var d = new int[a.Length + 1, b.Length + 1];
    76.  
    77. // Initialization
    78. for (int i = 0; i <= a.Length; i++)
    79. d[i, 0] = i;
    80. for (int i = 0; i <= b.Length; i++)
    81. d[0, i] = i;
    82.  
    83. // Calculation
    84. for (int i = 0; i < a.Length; i++) {
    85. for (int j = 0; j < b.Length; j++) {
    86. if (a[i] == b[j]) {
    87. d[i + 1, j + 1] = d[i, j];
    88. } else {
    89. d[i + 1, j + 1] = 1 + Math.Min(d[i, j], Math.Min(d[i, j + 1], d[i + 1, j]));
    90. }
    91. }
    92. }
    93.  
    94. return d;
    95. }
    96.  
    97. public class TraceLog
    98. {
    99. private readonly int[,] d;
    100. private readonly string a;
    101. private readonly string b;
    102.  
    103. internal TraceLog(string a, string b, int[,] d)
    104. {
    105. this.a = a;
    106. this.b = b;
    107. this.d = d;
    108. }
    109.  
    110. public void PrintMatrix()
    111. {
    112. for (int i = 0; i <= a.Length; i++) {
    113. for (int j = 0; j <= b.Length; j++) {
    114. Console.Write(d[i, j] + " ");
    115. }
    116. Console.WriteLine();
    117. }
    118. }
    119.  
    120. public void PrintTransformations()
    121. {
    122. // Trace back
    123. int k = a.Length;
    124. int m = b.Length;
    125. int p = b.Length;
    126. var path = new Stack<Action>();
    127. while ((k != 0) || (m != 0)) {
    128. int c = d[k, m];
    129. if ((k > 0) && (m > 0) && (d[k - 1, m - 1] < c)) {
    130. k = k - 1;
    131. m = m - 1;
    132. --p;
    133. // $"Sub {a[k]} with {b[m]} at position {p}"
    134. path.Push(new Action(1, p, b[m]));
    135. } else if ((k > 0) && (d[k - 1, m] < c)) {
    136. k = k - 1;
    137. path.Push(new Action(2, p, '\0'));
    138. // $"Del {a[k]} at position {p}"
    139. } else if ((m > 0) && (d[k, m - 1] < c)) {
    140. m = m - 1;
    141. --p;
    142. // $"Ins {b[m]} at position {p}"
    143. path.Push(new Action(3, p, b[m]));
    144. } else if ((k > 0) && (m > 0) && (d[k - 1, m - 1] == c)) {
    145. k = k - 1;
    146. m = m - 1;
    147. --p;
    148. } else if ((k > 0) && (d[k - 1, m] == c)) {
    149. k = k - 1;
    150. } else if ((m > 0) && (d[k, m - 1] == c)) {
    151. m = m - 1;
    152. }
    153. }
    154.  
    155. var tr = new List<char>(a);
    156. int counter = 0;
    157. Console.WriteLine(string.Format("{0}: {1}", counter, a));
    158. while (path.Count > 0) {
    159. var action = path.Pop();
    160. switch (action.type) {
    161. case 1:
    162. tr[action.position] = action.c;
    163. break;
    164. case 2:
    165. tr.RemoveAt(action.position);
    166. break;
    167. case 3:
    168. tr.Insert(action.position, action.c);
    169. break;
    170. }
    171. ++counter;
    172. Console.Write(string.Format("{0}: ", counter));
    173. Console.WriteLine(new string(tr.ToArray()));
    174. }
    175. }
    176. }
    177.  
    178. private struct Action
    179. {
    180. public readonly int type;
    181. public readonly int position;
    182. public readonly char c;
    183.  
    184. public Action(int type, int position, char c)
    185. {
    186. this.type = type;
    187. this.position = position;
    188. this.c = c;
    189. }
    190. }
    191.  
    192. }
    193.  
    194. public static class Program
    195. {
    196. public static void Main(string[] args)
    197. {
    198. var words = Console.ReadLine().Split(' ');
    199.  
    200. Console.WriteLine($"Distance between words {words[0]} and {words[1]} using dynamic programming algorithm:");
    201. Console.WriteLine(LevenshteinDynamicProgramming.Calculate(words[0], words[1]));
    202.  
    203. Console.WriteLine("Transformations:");
    204. var trace = LevenshteinDynamicProgramming.Trace(words[0], words[1]);
    205. trace.PrintTransformations();
    206. }
    207. }
    208. }
    209.