levenshtein.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. (function() {
  2. 'use strict';
  3. /**
  4. * Extend an Object with another Object's properties.
  5. *
  6. * The source objects are specified as additional arguments.
  7. *
  8. * @param dst Object the object to extend.
  9. *
  10. * @return Object the final object.
  11. */
  12. var _extend = function(dst) {
  13. var sources = Array.prototype.slice.call(arguments, 1);
  14. for (var i=0; i<sources.length; ++i) {
  15. var src = sources[i];
  16. for (var p in src) {
  17. if (src.hasOwnProperty(p)) dst[p] = src[p];
  18. }
  19. }
  20. return dst;
  21. };
  22. /**
  23. * Defer execution of given function.
  24. * @param {Function} func
  25. */
  26. var _defer = function(func) {
  27. if (typeof setImmediate === 'function') {
  28. return setImmediate(func);
  29. } else {
  30. return setTimeout(func, 0);
  31. }
  32. };
  33. /**
  34. * Based on the algorithm at http://en.wikipedia.org/wiki/Levenshtein_distance.
  35. */
  36. var Levenshtein = {
  37. /**
  38. * Calculate levenshtein distance of the two strings.
  39. *
  40. * @param str1 String the first string.
  41. * @param str2 String the second string.
  42. * @return Integer the levenshtein distance (0 and above).
  43. */
  44. get: function(str1, str2) {
  45. // base cases
  46. if (str1 === str2) return 0;
  47. if (str1.length === 0) return str2.length;
  48. if (str2.length === 0) return str1.length;
  49. // two rows
  50. var prevRow = new Array(str2.length + 1),
  51. curCol, nextCol, i, j, tmp;
  52. // initialise previous row
  53. for (i=0; i<prevRow.length; ++i) {
  54. prevRow[i] = i;
  55. }
  56. // calculate current row distance from previous row
  57. for (i=0; i<str1.length; ++i) {
  58. nextCol = i + 1;
  59. for (j=0; j<str2.length; ++j) {
  60. curCol = nextCol;
  61. // substution
  62. nextCol = prevRow[j] + ( (str1.charAt(i) === str2.charAt(j)) ? 0 : 1 );
  63. // insertion
  64. tmp = curCol + 1;
  65. if (nextCol > tmp) {
  66. nextCol = tmp;
  67. }
  68. // deletion
  69. tmp = prevRow[j + 1] + 1;
  70. if (nextCol > tmp) {
  71. nextCol = tmp;
  72. }
  73. // copy current col value into previous (in preparation for next iteration)
  74. prevRow[j] = curCol;
  75. }
  76. // copy last col value into previous (in preparation for next iteration)
  77. prevRow[j] = nextCol;
  78. }
  79. return nextCol;
  80. },
  81. /**
  82. * Asynchronously calculate levenshtein distance of the two strings.
  83. *
  84. * @param str1 String the first string.
  85. * @param str2 String the second string.
  86. * @param cb Function callback function with signature: function(Error err, int distance)
  87. * @param [options] Object additional options.
  88. * @param [options.progress] Function progress callback with signature: function(percentComplete)
  89. */
  90. getAsync: function(str1, str2, cb, options) {
  91. options = _extend({}, {
  92. progress: null
  93. }, options);
  94. // base cases
  95. if (str1 === str2) return cb(null, 0);
  96. if (str1.length === 0) return cb(null, str2.length);
  97. if (str2.length === 0) return cb(null, str1.length);
  98. // two rows
  99. var prevRow = new Array(str2.length + 1),
  100. curCol, nextCol,
  101. i, j, tmp,
  102. startTime, currentTime;
  103. // initialise previous row
  104. for (i=0; i<prevRow.length; ++i) {
  105. prevRow[i] = i;
  106. }
  107. nextCol = 1;
  108. i = 0;
  109. j = -1;
  110. var __calculate = function() {
  111. // reset timer
  112. startTime = new Date().valueOf();
  113. currentTime = startTime;
  114. // keep going until one second has elapsed
  115. while (currentTime - startTime < 1000) {
  116. // reached end of current row?
  117. if (str2.length <= (++j)) {
  118. // copy current into previous (in preparation for next iteration)
  119. prevRow[j] = nextCol;
  120. // if already done all chars
  121. if (str1.length <= (++i)) {
  122. return cb(null, nextCol);
  123. }
  124. // else if we have more left to do
  125. else {
  126. nextCol = i + 1;
  127. j = 0;
  128. }
  129. }
  130. // calculation
  131. curCol = nextCol;
  132. // substution
  133. nextCol = prevRow[j] + ( (str1.charAt(i) === str2.charAt(j)) ? 0 : 1 );
  134. // insertion
  135. tmp = curCol + 1;
  136. if (nextCol > tmp) {
  137. nextCol = tmp;
  138. }
  139. // deletion
  140. tmp = prevRow[j + 1] + 1;
  141. if (nextCol > tmp) {
  142. nextCol = tmp;
  143. }
  144. // copy current into previous (in preparation for next iteration)
  145. prevRow[j] = curCol;
  146. // get current time
  147. currentTime = new Date().valueOf();
  148. }
  149. // send a progress update?
  150. if (null !== options.progress) {
  151. try {
  152. options.progress.call(null, (i * 100.0/ str1.length));
  153. } catch (err) {
  154. return cb('Progress callback: ' + err.toString());
  155. }
  156. }
  157. // next iteration
  158. _defer(__calculate);
  159. };
  160. __calculate();
  161. }
  162. };
  163. // amd
  164. if (typeof define !== "undefined" && define !== null && define.amd) {
  165. define(function() {
  166. return Levenshtein;
  167. });
  168. }
  169. // commonjs
  170. else if (typeof module !== "undefined" && module !== null && typeof exports !== "undefined" && module.exports === exports) {
  171. module.exports = Levenshtein;
  172. }
  173. // web worker
  174. else if (typeof self !== "undefined" && typeof self.postMessage === 'function' && typeof self.importScripts === 'function') {
  175. self.Levenshtein = Levenshtein;
  176. }
  177. // browser main thread
  178. else if (typeof window !== "undefined" && window !== null) {
  179. window.Levenshtein = Levenshtein;
  180. }
  181. }());