Javascript autosuggest service (levenshtein + word matching)

Date: 2018-09-26
function autoSuggestService() {
	var exports = {};
	var levenshtein = window.Levenshtein;

	function getDistance(str1, str2) {
		//levenshtein.get('mikailovitch', 'Mikhaïlovitch', { useCollator: true});
		return levenshtein.get(str1, str2, {
			useCollator: true
		});
	}

	function escapeRegExp(string) {
		return string.replace(/[.*+?^${}()|[\]\\]/g, '\\&'); // & means the whole matched string 
	}
	var getWordsRegEx = new RegExp('\\b([^.,? ]+?)\\b', 'gm');
	var getWordsRegEx = new RegExp('([^\\s.,;:]+)', 'gm');
	function getWords(str) { 
		var matches = []; 
		var match; 
		while (match = getWordsRegEx.exec(str)) { 
			if (match[1].length > 0) { matches.push(match[1]); } 
		} 
		return matches; 
	}
	function replaceWord(str, find, replace) { 
		return str.replace(new RegExp('\\b' + escapeRegExp(find) + '\\b', 'gmi'), replace); 
	}
	function multiFor(a, b, fn) { 
		for (var i = 0; i < a.length; i++) { 
			for (var j = 0; j < b.length; j++) { 
				var res = fn(a[i], b[j]); 
				if (res === false) { break; } 
			} 
		} 
	}
	function arrayCountMatch(a, b) { 
		var matchCount = 0; 
		multiFor(a, b, function (c, d) { if (c === d) { matchCount++; } }); 
		return matchCount; 
	}
	function getUniqueWords(suggestions) {
		var wordMap = {}; 
		suggestions.forEach(function (s) { var words = getWords((s.text || '')); 
		words.forEach(function (w) { wordMap[w] = true; }); }); 
		return Object.keys(wordMap);
	}
	function findWordSuggestions(text, uniqueWords) {
		if (!text || text === '' || text === ' ') { return []; }
		var words = getWords(text);
		var results = [];
		multiFor(words, uniqueWords, function (word, uniqueWord) {
			var lowerWord = word.toLowerCase(); var lowerUniqueWord = uniqueWord.toLowerCase();
			if (lowerUniqueWord === lowerWord) {
				return; // exact word no suggestions 
			}
			if (lowerUniqueWord.charAt(0) !== lowerWord.charAt(0)) { return; }
			var dist = getDistance(lowerWord, lowerUniqueWord.slice(0, lowerWord.length));
			var score = (lowerWord.length / (dist + 1 * lowerWord.length));
			if (score >= 0.7) { results.push({ src: word, target: uniqueWord, score: score }); }
		});
		results.sort(function (a, b) { return b.score - a.score; }); return results;
	}
	function findSuggestions(text, suggestions) {
		var results = [];
		if (typeof text === 'string' && text.length > 3) {
			text = text.toLowerCase(); var words = getWords(text); if (words.length < 1) { return results; }
			suggestions.forEach(function (s) {
				var str = (s.text || '').toLowerCase();
				if (str.indexOf(text) >= 0) { s.score = 1; results.push(s); return; }
				var sugWords = getWords(str);
				var matchCount = arrayCountMatch(words, sugWords);
				if (matchCount > 0) {
					s.score = matchCount / Math.min(words.length, sugWords.length);
					if (s.score > 0.2) { results.push(s); }
				}
			});
		} results.sort(function (a, b) { return b.score - a.score; }); return results;
	}
	exports = { findSuggestions: findSuggestions, findWordSuggestions: findWordSuggestions, getUniqueWords: getUniqueWords, replaceWord: replaceWord }; return exports;
}
function loadSuggestions() {
	var suggestions = [{ text: '', value: '' }];
	return suggestions;
}

var _suggestions = null;

function getSuggestions() {
	if (!_suggestions) {
		_suggestions = loadSuggestions();
	}
	return _suggestions;
}

function getSuggestionsForText(text) {
	return getSuggestions()
		.then(function (suggestions) {
			return autoSuggestService.findSuggestions(text, suggestions);
		});
}

function getWordSuggestionsForText(text) {
	return getSuggestions()
		.then(function (suggestions) {
			var uniqueWords = autoSuggestService.getUniqueWords(suggestions);
			return autoSuggestService.findWordSuggestions(text, uniqueWords);
		});
}
13130cookie-checkJavascript autosuggest service (levenshtein + word matching)