Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 library levenshtein; | 1 library levenshtein; |
| 2 | 2 |
| 3 import 'dart:math'; | 3 import 'dart:math' as math; |
| 4 | 4 |
| 5 /// Levenshtein algorithm implementation based on: | 5 /** |
| 6 /// http://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_ rows | 6 * The value returned by [levenshtein] if the distance is determined |
| 7 /// | 7 * to be over the specified threshold. |
| 8 /// Implementation: https://github.com/conradkleinespel/levenshtein-dart | 8 */ |
| 9 int getLevenshteinDistance(String s, String t, {bool caseSensitive: true}) { | 9 const int LEVENSHTEIN_MAX = 1 << 20; |
| 10 | |
| 11 const int _MAX_VALUE = 1 << 10; | |
| 12 | |
| 13 /** | |
| 14 * Find the Levenshtein distance between two [String]s if it's less than or | |
| 15 * equal to a given threshold. | |
| 16 * | |
| 17 * This is the number of changes needed to change one String into another, | |
| 18 * where each change is a single character modification (deletion, insertion or | |
| 19 * substitution). | |
| 20 * | |
| 21 * This implementation follows from Algorithms on Strings, Trees and Sequences | |
| 22 * by Dan Gusfield and Chas Emerick's implementation of the Levenshtein distance | |
| 23 * algorithm. | |
| 24 */ | |
| 25 int levenshtein(String s, String t, int threshold, {bool caseSensitive: true}) { | |
| 26 if (s == null || t == null) { | |
| 27 throw new ArgumentError('Strings must not be null'); | |
| 28 } | |
| 29 if (threshold < 0) { | |
| 30 throw new ArgumentError('Threshold must not be negative'); | |
| 31 } | |
| 32 | |
| 10 if (!caseSensitive) { | 33 if (!caseSensitive) { |
| 11 s = s.toLowerCase(); | 34 s = s.toLowerCase(); |
| 12 t = t.toLowerCase(); | 35 t = t.toLowerCase(); |
| 13 } | 36 } |
| 14 | 37 |
| 15 if (s == t) { | 38 int n = s.length; |
| 16 return 0; | 39 int m = t.length; |
|
Paul Berry
2014/07/25 19:53:26
Minor nit: can we swap the meanings of n and m? I
scheglov
2014/07/25 21:18:24
Done.
| |
| 17 } | 40 |
| 18 if (s.length == 0) { | 41 // if one string is empty, |
| 19 return t.length; | 42 // the edit distance is necessarily the length of the other |
| 20 } | 43 if (n == 0) { |
| 21 if (t.length == 0) { | 44 return m <= threshold ? m : LEVENSHTEIN_MAX; |
| 22 return s.length; | 45 } else if (m == 0) { |
| 46 return n <= threshold ? n : LEVENSHTEIN_MAX; | |
| 23 } | 47 } |
| 24 | 48 |
|
Paul Berry
2014/07/25 19:53:26
The Levenshtein distance can never be less than ab
scheglov
2014/07/25 21:18:24
Done.
| |
| 25 List<int> v0 = new List<int>.filled(t.length + 1, 0); | 49 // swap the two strings to consume less memory |
| 26 List<int> v1 = new List<int>.filled(t.length + 1, 0); | 50 if (n > m) { |
| 27 | 51 String tmp = s; |
| 28 for (int i = 0; i < t.length + 1; i < i++) { | 52 s = t; |
| 29 v0[i] = i; | 53 t = tmp; |
| 54 n = m; | |
| 55 m = t.length; | |
| 30 } | 56 } |
| 31 | 57 |
| 32 for (int i = 0; i < s.length; i++) { | 58 // 'previous' cost array, horizontally |
| 33 v1[0] = i + 1; | 59 List<int> p = new List<int>.filled(n + 1, 0); |
| 60 // cost array, horizontally | |
| 61 List<int> d = new List<int>.filled(n + 1, 0); | |
| 62 // placeholder to assist in swapping p and d | |
| 63 List<int> _d; | |
| 34 | 64 |
| 35 for (int j = 0; j < t.length; j++) { | 65 // fill in starting table values |
| 36 int cost = (s[i] == t[j]) ? 0 : 1; | 66 int boundary = math.min(n, threshold) + 1; |
| 37 v1[j + 1] = min(v1[j] + 1, min(v0[j + 1] + 1, v0[j] + cost)); | 67 for (int i = 0; i < boundary; i++) { |
| 68 p[i] = i; | |
| 69 } | |
| 70 | |
| 71 // these fills ensure that the value above the rightmost entry of our | |
| 72 // stripe will be ignored in following loop iterations | |
| 73 _setRange(p, boundary, p.length, _MAX_VALUE); | |
| 74 _setRange(d, 0, d.length, _MAX_VALUE); | |
| 75 | |
| 76 // iterates through t | |
| 77 for (int j = 1; j <= m; j++) { | |
| 78 // jth character of t | |
| 79 int t_j = t.codeUnitAt(j - 1); | |
| 80 d[0] = j; | |
| 81 | |
| 82 // compute stripe indices, constrain to array size | |
| 83 int min = math.max(1, j - threshold); | |
| 84 int max = math.min(n, j + threshold); | |
| 85 | |
| 86 // the stripe may lead off of the table if s and t are of different sizes | |
| 87 if (min > max) { | |
| 88 return LEVENSHTEIN_MAX; | |
| 38 } | 89 } |
| 39 | 90 |
| 40 for (int j = 0; j < t.length + 1; j++) { | 91 // ignore entry left of leftmost |
| 41 v0[j] = v1[j]; | 92 if (min > 1) { |
| 93 d[min - 1] = _MAX_VALUE; | |
| 42 } | 94 } |
| 95 | |
| 96 // iterates through [min, max] in s | |
| 97 for (int i = min; i <= max; i++) { | |
| 98 if (s.codeUnitAt(i - 1) == t_j) { | |
| 99 // diagonally left and up | |
| 100 d[i] = p[i - 1]; | |
| 101 } else { | |
| 102 // 1 + minimum of cell to the left, to the top, diagonally left and up | |
| 103 d[i] = 1 + math.min(math.min(d[i - 1], p[i]), p[i - 1]); | |
| 104 } | |
| 105 } | |
| 106 | |
| 107 // copy current distance counts to 'previous row' distance counts | |
| 108 _d = p; | |
| 109 p = d; | |
| 110 d = _d; | |
| 43 } | 111 } |
| 44 | 112 |
| 45 return v1[t.length]; | 113 // if p[n] is greater than the threshold, |
| 114 // there's no guarantee on it being the correct distance | |
| 115 if (p[n] <= threshold) { | |
| 116 return p[n]; | |
| 117 } | |
| 118 | |
| 119 return LEVENSHTEIN_MAX; | |
| 46 } | 120 } |
| 121 | |
| 122 void _setRange(List<int> a, int start, int end, int value) { | |
| 123 for (int i = start; i < end; i++) { | |
| 124 a[i] = value; | |
| 125 } | |
| 126 } | |
| OLD | NEW |