suggestionList.mjs 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. /**
  2. * Given an invalid input string and a list of valid options, returns a filtered
  3. * list of valid options sorted based on their similarity with the input.
  4. */
  5. export default function suggestionList(input, options) {
  6. var optionsByDistance = Object.create(null);
  7. var inputThreshold = input.length / 2;
  8. for (var _i2 = 0; _i2 < options.length; _i2++) {
  9. var option = options[_i2];
  10. var distance = lexicalDistance(input, option);
  11. var threshold = Math.max(inputThreshold, option.length / 2, 1);
  12. if (distance <= threshold) {
  13. optionsByDistance[option] = distance;
  14. }
  15. }
  16. return Object.keys(optionsByDistance).sort(function (a, b) {
  17. return optionsByDistance[a] - optionsByDistance[b];
  18. });
  19. }
  20. /**
  21. * Computes the lexical distance between strings A and B.
  22. *
  23. * The "distance" between two strings is given by counting the minimum number
  24. * of edits needed to transform string A into string B. An edit can be an
  25. * insertion, deletion, or substitution of a single character, or a swap of two
  26. * adjacent characters.
  27. *
  28. * Includes a custom alteration from Damerau-Levenshtein to treat case changes
  29. * as a single edit which helps identify mis-cased values with an edit distance
  30. * of 1.
  31. *
  32. * This distance can be useful for detecting typos in input or sorting
  33. *
  34. * @param {string} a
  35. * @param {string} b
  36. * @return {int} distance in number of edits
  37. */
  38. function lexicalDistance(aStr, bStr) {
  39. if (aStr === bStr) {
  40. return 0;
  41. }
  42. var d = [];
  43. var a = aStr.toLowerCase();
  44. var b = bStr.toLowerCase();
  45. var aLength = a.length;
  46. var bLength = b.length; // Any case change counts as a single edit
  47. if (a === b) {
  48. return 1;
  49. }
  50. for (var i = 0; i <= aLength; i++) {
  51. d[i] = [i];
  52. }
  53. for (var j = 1; j <= bLength; j++) {
  54. d[0][j] = j;
  55. }
  56. for (var _i3 = 1; _i3 <= aLength; _i3++) {
  57. for (var _j = 1; _j <= bLength; _j++) {
  58. var cost = a[_i3 - 1] === b[_j - 1] ? 0 : 1;
  59. d[_i3][_j] = Math.min(d[_i3 - 1][_j] + 1, d[_i3][_j - 1] + 1, d[_i3 - 1][_j - 1] + cost);
  60. if (_i3 > 1 && _j > 1 && a[_i3 - 1] === b[_j - 2] && a[_i3 - 2] === b[_j - 1]) {
  61. d[_i3][_j] = Math.min(d[_i3][_j], d[_i3 - 2][_j - 2] + cost);
  62. }
  63. }
  64. }
  65. return d[aLength][bLength];
  66. }