PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs//1.0.1/fuse.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 367 lines | 184 code | 49 blank | 134 comment | 45 complexity | 53d3e57e40eff86f4d56cfa0c4e3224e MD5 | raw file
  1. /**
  2. * @license
  3. * Fuse - Lightweight fuzzy-search
  4. *
  5. * Copyright (c) 2012 Kirollos Risk <kirollos@gmail.com>.
  6. * All Rights Reserved. Apache Software License 2.0
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. (function(global) {
  21. /**
  22. * Adapted from "Diff, Match and Patch", by Google
  23. *
  24. * http://code.google.com/p/google-diff-match-patch/
  25. *
  26. * Modified by: Kirollos Risk <kirollos@gmail.com>
  27. * -----------------------------------------------
  28. * Details: the algorithm and structure was modified to allow the creation of
  29. * <Searcher> instances with a <search> method inside which does the actual
  30. * bitap search. The <pattern> (the string that is searched for) is only defined
  31. * once per instance and thus it eliminates redundant re-creation when searching
  32. * over a list of strings.
  33. *
  34. * Licensed under the Apache License, Version 2.0 (the "License");
  35. * you may not use this file except in compliance with the License.
  36. */
  37. var defaultOptions = {
  38. // Approximately where in the text is the pattern expected to be found?
  39. location: 0,
  40. // Determines how close the match must be to the fuzzy location (specified above).
  41. // An exact letter match which is 'distance' characters away from the fuzzy location
  42. // would score as a complete mismatch. A distance of '0' requires the match be at
  43. // the exact location specified, a threshold of '1000' would require a perfect match
  44. // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
  45. distance: 100,
  46. // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match
  47. // (of both letters and location), a threshold of '1.0' would match anything.
  48. threshold: 0.6,
  49. // Machine word size
  50. maxPatternLength: 32
  51. };
  52. function Searcher(pattern, options) {
  53. options = options || {};
  54. var MATCH_LOCATION = options.location || defaultOptions.location,
  55. MATCH_DISTANCE = 'distance' in options ? options.distance : defaultOptions.distance,
  56. MATCH_THRESHOLD = 'threshold' in options ? options.threshold : defaultOptions.threshold,
  57. MAX_PATTERN_LEN = options.maxPatternLength || defaultOptions.maxPatternLength,
  58. pattern = options.caseSensitive ? pattern : pattern.toLowerCase(),
  59. patternLen = pattern.length;
  60. if (patternLen > MAX_PATTERN_LEN) {
  61. throw new Error('Pattern length is too long');
  62. }
  63. var matchmask = 1 << (patternLen - 1);
  64. /**
  65. * Initialize the alphabet for the Bitap algorithm.
  66. * @return {Object} Hash of character locations.
  67. * @private
  68. */
  69. var pattern_alphabet = (function() {
  70. var mask = {},
  71. i = 0;
  72. for (i = 0; i < patternLen; i++) {
  73. mask[pattern.charAt(i)] = 0;
  74. }
  75. for (i = 0; i < patternLen; i++) {
  76. mask[pattern.charAt(i)] |= 1 << (pattern.length - i - 1);
  77. }
  78. return mask;
  79. })();
  80. /**
  81. * Compute and return the score for a match with `e` errors and `x` location.
  82. * @param {number} e Number of errors in match.
  83. * @param {number} x Location of match.
  84. * @return {number} Overall score for match (0.0 = good, 1.0 = bad).
  85. * @private
  86. */
  87. function match_bitapScore(e, x) {
  88. var accuracy = e / patternLen,
  89. proximity = Math.abs(MATCH_LOCATION - x);
  90. if (!MATCH_DISTANCE) {
  91. // Dodge divide by zero error.
  92. return proximity ? 1.0 : accuracy;
  93. }
  94. return accuracy + (proximity / MATCH_DISTANCE);
  95. }
  96. /**
  97. * Compute and return the result of the search
  98. * @param {String} text The text to search in
  99. * @return
  100. * {Object} Literal containing:
  101. * {Boolean} isMatch Whether the text is a match or not
  102. * {Decimal} score Overall score for the match
  103. * @public
  104. */
  105. this.search = function(text) {
  106. text = options.caseSensitive ? text : text.toLowerCase();
  107. if (pattern === text) {
  108. // Exact match
  109. return {
  110. isMatch: true,
  111. score: 0
  112. };
  113. }
  114. var i, j,
  115. // Set starting location at beginning text and initialize the alphabet.
  116. textLen = text.length,
  117. // Highest score beyond which we give up.
  118. scoreThreshold = MATCH_THRESHOLD,
  119. // Is there a nearby exact match? (speedup)
  120. bestLoc = text.indexOf(pattern, MATCH_LOCATION),
  121. binMin, binMid,
  122. binMax = patternLen + textLen,
  123. lastRd, start, finish, rd, charMatch,
  124. score = 1,
  125. locations = [];
  126. if (bestLoc != -1) {
  127. scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
  128. // What about in the other direction? (speedup)
  129. bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen);
  130. if (bestLoc != -1) {
  131. scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
  132. }
  133. }
  134. bestLoc = -1;
  135. for (i = 0; i < patternLen; i++) {
  136. // Scan for the best match; each iteration allows for one more error.
  137. // Run a binary search to determine how far from 'MATCH_LOCATION' we can stray at this
  138. // error level.
  139. binMin = 0;
  140. binMid = binMax;
  141. while (binMin < binMid) {
  142. if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) {
  143. binMin = binMid;
  144. } else {
  145. binMax = binMid;
  146. }
  147. binMid = Math.floor((binMax - binMin) / 2 + binMin);
  148. }
  149. // Use the result from this iteration as the maximum for the next.
  150. binMax = binMid;
  151. start = Math.max(1, MATCH_LOCATION - binMid + 1);
  152. finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen;
  153. // Initialize the bit array
  154. rd = Array(finish + 2);
  155. rd[finish + 1] = (1 << i) - 1;
  156. for (j = finish; j >= start; j--) {
  157. // The alphabet <pattern_alphabet> is a sparse hash, so the following line generates warnings.
  158. charMatch = pattern_alphabet[text.charAt(j - 1)];
  159. if (i === 0) {
  160. // First pass: exact match.
  161. rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
  162. } else {
  163. // Subsequent passes: fuzzy match.
  164. rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
  165. }
  166. if (rd[j] & matchmask) {
  167. score = match_bitapScore(i, j - 1);
  168. // This match will almost certainly be better than any existing match.
  169. // But check anyway.
  170. if (score <= scoreThreshold) {
  171. // Told you so.
  172. scoreThreshold = score;
  173. bestLoc = j - 1;
  174. locations.push(bestLoc);
  175. if (bestLoc > MATCH_LOCATION) {
  176. // When passing loc, don't exceed our current distance from loc.
  177. start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
  178. } else {
  179. // Already passed loc, downhill from here on in.
  180. break;
  181. }
  182. }
  183. }
  184. }
  185. // No hope for a (better) match at greater error levels.
  186. if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
  187. break;
  188. }
  189. lastRd = rd;
  190. }
  191. return {
  192. isMatch: bestLoc >= 0,
  193. score: score
  194. };
  195. }
  196. }
  197. var Utils = {
  198. /**
  199. * Traverse an object
  200. * @param {Object} The object to traverse
  201. * @param {String} A . separated path to a key in the object. Example 'Data.Object.Somevalue'
  202. * @return {Mixed}
  203. */
  204. deepValue: function(obj, path) {
  205. for (var i = 0, path = path.split('.'), len = path.length; i < len; i++) {
  206. if (!obj) {
  207. return null;
  208. }
  209. obj = obj[path[i]];
  210. };
  211. return obj;
  212. }
  213. };
  214. /**
  215. * @param {Array} list
  216. * @param {Object} options
  217. * @public
  218. */
  219. function Fuse(list, options) {
  220. options = options || {};
  221. var searchKeys = options.keys || [];
  222. /**
  223. * Searches for all the items whose keys (fuzzy) match the pattern.
  224. * @param {String} pattern The pattern string to fuzzy search on.
  225. * @return {Array} A list of all serch matches.
  226. * @public
  227. */
  228. this.search = function(pattern) {
  229. var searcher = new Searcher(pattern, options),
  230. i, j, item, text,
  231. dataLen = list.length,
  232. searchKeysLen = searchKeys.length,
  233. bitapResult, rawResults = [],
  234. index = 0,
  235. resultMap = {},
  236. rawResultsLen, existingResult, results = [],
  237. compute = null;
  238. /**
  239. * Calls <Searcher::search> for bitap analysis. Builds the raw result list.
  240. * @param {String} text The pattern string to fuzzy search on.
  241. * @param {String|Int} entity If the <data> is an Array, then entity will be an index,
  242. * otherwise it's the item object.
  243. * @param {Int} index
  244. * @return {Object|Int}
  245. * @private
  246. */
  247. function analyzeText(text, entity, index) {
  248. // Check if the text can be searched
  249. if (text !== undefined && text !== null && typeof text === 'string') {
  250. // Get the result
  251. bitapResult = searcher.search(text);
  252. // If a match is found, add the item to <rawResults>, including its score
  253. if (bitapResult.isMatch) {
  254. // Check if the item already exists in our results
  255. existingResult = resultMap[index];
  256. if (existingResult) {
  257. // Use the lowest score
  258. existingResult.score = Math.min(existingResult.score, bitapResult.score);
  259. } else {
  260. // Add it to the raw result list
  261. resultMap[index] = {
  262. item: entity,
  263. score: bitapResult.score
  264. };
  265. rawResults.push(resultMap[index]);
  266. }
  267. }
  268. }
  269. }
  270. // Check the first item in the list, if it's a string, then we assume
  271. // that every item in the list is also a string, and thus it's a flattened array.
  272. if (typeof list[0] === 'string') {
  273. // Iterate over every item
  274. for (; index < dataLen; index++) {
  275. analyzeText(list[index], index, index);
  276. }
  277. } else {
  278. // Otherwise, the first item is an Object (hopefully), and thus the searching
  279. // is done on the values of the keys of each item.
  280. // Iterate over every item
  281. for (; index < dataLen; index++) {
  282. item = list[index];
  283. // Iterate over every key
  284. for (j = 0; j < searchKeysLen; j++) {
  285. analyzeText(Utils.deepValue(item, searchKeys[j]), item, index);
  286. }
  287. }
  288. }
  289. // Sort the results, form lowest to highest score
  290. rawResults.sort(function(a, b) {
  291. return a.score - b.score;
  292. });
  293. // From the results, push into a new array only the item identifier (if specified)
  294. // of the entire item. This is because we don't want to return the <rawResults>,
  295. // since it contains other metadata;
  296. rawResultsLen = rawResults.length;
  297. for (i = 0; i < rawResultsLen; i++) {
  298. results.push(options.id ? Utils.deepValue(rawResults[i].item, options.id) : rawResults[i].item);
  299. }
  300. return results;
  301. }
  302. }
  303. // Export to Common JS Loader
  304. if (typeof exports === 'object') {
  305. // Node. Does not work with strict CommonJS, but
  306. // only CommonJS-like environments that support module.exports,
  307. // like Node.
  308. module.exports = Fuse;
  309. } else if (typeof define === 'function' && define.amd) {
  310. // AMD. Register as an anonymous module.
  311. define(function() {
  312. return Fuse;
  313. });
  314. } else {
  315. // Browser globals (root is window)
  316. global.Fuse = Fuse;
  317. }
  318. })(this);