Как я могу реализовать возвышенный подобный нечеткий поиск на select2?
Например, ввод "sta jav sub" будет соответствовать "Stackoverflow javascript sublime like"
Как я могу реализовать возвышенный подобный нечеткий поиск на select2?
Например, ввод "sta jav sub" будет соответствовать "Stackoverflow javascript sublime like"
select2 позволяет вам реализовать свои собственные функции "matcher" (как видно из их документов), используя это и некоторое регулярное выражение, вы можете что-то сделать как:
$("#element").select2({
matcher: function(term, text, opt) {
//We call to uppercase to do a case insensitive match
//We replace every group of whitespace characters with a .+
//matching any number of characters
return text.toUpperCase().match(term.toUpperCase().replace(/\s+/g, '.+'));
}
});
Функция сопоставления вызывается для каждого элемента списка select2 при фильтрации/поиске в списке, вы можете реализовать любой вид пользовательского поиска, используя это.
Здесь альтернативная функция соответствия. http://jsfiddle.net/trevordixon/pXzj3/4/
function match(search, text) {
search = search.toUpperCase();
text = text.toUpperCase();
var j = -1; // remembers position of last found character
// consider each search character one at a time
for (var i = 0; i < search.length; i++) {
var l = search[i];
if (l == ' ') continue; // ignore spaces
j = text.indexOf(l, j+1); // search for character & update position
if (j == -1) return false; // if it not found, exclude this item
}
return true;
}
Это немного быстрее (по крайней мере, когда я запускаю этот тест в Chrome на OS X), что может быть важно, если вы фильтруете множество предметов.
Я написал несколько, которые работают очень долго, Sublime Text нечеткое совпадение. Для этого требуется несколько вещей.
Во-первых, последовательно соберите все символы из шаблона. Во-вторых, сопоставляйте совпадения так, что некоторые совпадающие символы стоят больше очков, чем другие.
Я придумал несколько факторов для проверки. Буквы или буквы "CamelCase" после разделителя (пробел или символ подчеркивания) стоят много очков. Последовательные матчи стоят больше. Результаты, найденные рядом с началом, стоят больше.
Критически важным трюком является поиск наилучшего совпадающего символа. Это не обязательно первое. Рассмотрим fuzzy_match ( "tk", "Черный рыцарь" ). Есть два Ks, которые можно сопоставить. Во-вторых, стоит больше очков, потому что это следует за пространством.
Ниже приведен код JavaScript. Существует несколько нюансов, которые более подробно описаны в сообщении в блоге. Там также есть интерактивная демонстрация. И полный источник (включая демонстрационную версию, плюс реализацию С++) на GitHub.
// Returns [bool, score, formattedStr]
// bool: true if each character in pattern is found sequentially within str
// score: integer; higher is better match. Value has no intrinsic meaning. Range varies with pattern.
// Can only compare scores with same search pattern.
// formattedStr: input str with matched characters marked in <b> tags. Delete if unwanted.
function fuzzy_match(pattern, str) {
// Score consts
var adjacency_bonus = 5; // bonus for adjacent matches
var separator_bonus = 10; // bonus if match occurs after a separator
var camel_bonus = 10; // bonus if match is uppercase and prev is lower
var leading_letter_penalty = -3; // penalty applied for every letter in str before the first match
var max_leading_letter_penalty = -9; // maximum penalty for leading letters
var unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter
// Loop variables
var score = 0;
var patternIdx = 0;
var patternLength = pattern.length;
var strIdx = 0;
var strLength = str.length;
var prevMatched = false;
var prevLower = false;
var prevSeparator = true; // true so if first letter match gets separator bonus
// Use "best" matched letter if multiple string letters match the pattern
var bestLetter = null;
var bestLower = null;
var bestLetterIdx = null;
var bestLetterScore = 0;
var matchedIndices = [];
// Loop over strings
while (strIdx != strLength) {
var patternChar = patternIdx != patternLength ? pattern.charAt(patternIdx) : null;
var strChar = str.charAt(strIdx);
var patternLower = patternChar != null ? patternChar.toLowerCase() : null;
var strLower = strChar.toLowerCase();
var strUpper = strChar.toUpperCase();
var nextMatch = patternChar && patternLower == strLower;
var rematch = bestLetter && bestLower == strLower;
var advanced = nextMatch && bestLetter;
var patternRepeat = bestLetter && patternChar && bestLower == patternLower;
if (advanced || patternRepeat) {
score += bestLetterScore;
matchedIndices.push(bestLetterIdx);
bestLetter = null;
bestLower = null;
bestLetterIdx = null;
bestLetterScore = 0;
}
if (nextMatch || rematch) {
var newScore = 0;
// Apply penalty for each letter before the first pattern match
// Note: std::max because penalties are negative values. So max is smallest penalty.
if (patternIdx == 0) {
var penalty = Math.max(strIdx * leading_letter_penalty, max_leading_letter_penalty);
score += penalty;
}
// Apply bonus for consecutive bonuses
if (prevMatched)
newScore += adjacency_bonus;
// Apply bonus for matches after a separator
if (prevSeparator)
newScore += separator_bonus;
// Apply bonus across camel case boundaries. Includes "clever" isLetter check.
if (prevLower && strChar == strUpper && strLower != strUpper)
newScore += camel_bonus;
// Update patter index IFF the next pattern letter was matched
if (nextMatch)
++patternIdx;
// Update best letter in str which may be for a "next" letter or a "rematch"
if (newScore >= bestLetterScore) {
// Apply penalty for now skipped letter
if (bestLetter != null)
score += unmatched_letter_penalty;
bestLetter = strChar;
bestLower = bestLetter.toLowerCase();
bestLetterIdx = strIdx;
bestLetterScore = newScore;
}
prevMatched = true;
}
else {
// Append unmatch characters
formattedStr += strChar;
score += unmatched_letter_penalty;
prevMatched = false;
}
// Includes "clever" isLetter check.
prevLower = strChar == strLower && strLower != strUpper;
prevSeparator = strChar == '_' || strChar == ' ';
++strIdx;
}
// Apply score for last match
if (bestLetter) {
score += bestLetterScore;
matchedIndices.push(bestLetterIdx);
}
// Finish out formatted string after last pattern matched
// Build formated string based on matched letters
var formattedStr = "";
var lastIdx = 0;
for (var i = 0; i < matchedIndices.length; ++i) {
var idx = matchedIndices[i];
formattedStr += str.substr(lastIdx, idx - lastIdx) + "<b>" + str.charAt(idx) + "</b>";
lastIdx = idx + 1;
}
formattedStr += str.substr(lastIdx, str.length - lastIdx);
var matched = patternIdx == patternLength;
return [matched, score, formattedStr];
}
Ответ на albertein не соответствует версии Trevor, поскольку исходная функция выполняет сопоставление по характеру, а не на основе слова. Здесь более простое совпадение на основе символов:
$("#element").select2({
matcher: function(term, text, opts) {
var pattern = term.replace(/\s+/g, '').split('').join('.*');
text.match(new RegExp(pattern, 'i'))
}
})
var fuzzysearch = function (querystrings, values) {
return !querystrings.some(function (q) {
return !values.some(function (v) {
return v.toLocaleLowerCase().indexOf(q) !== -1;
});
});
}
Пример поиска заголовка и автора в коллекции книг http://jsfiddle.net/runjep/r887etnh/2/
Для альтернативы 9kb, которая оценивает результат поиска: http://kiro.me/projects/fuse.html
Вам может понадобиться polyfill для функции "some" https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
var books = [{
id: 1,
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald'
}, {
id: 2,
title: 'The DaVinci Code',
author: 'Dan Brown'
}, {
id: 3,
title: 'Angels & Demons',
author: 'Dan Brown'
}];
search = function () {
var queryarray = document.getElementById('inp').value.trim().toLowerCase().split(' ');
var res = books.filter(function (b) {
return fs(queryarray, [b.title, b.author]);
});
document.getElementById('res').innerHTML = res.map(function (b) {
return b.title + ' <i> ' + b.author + '</i>';
}).join('<br/> ');
}
fs = function (qs, vals) {
return !qs.some(function (q) {
return !vals.some(function (v) {
return v.toLocaleLowerCase().indexOf(q) !== -1;
});
});
}
<input id="inp" />
<button id="but" onclick="search()">Search</button>
<div id="res"></div>
function fuzzyMe(term, query) {
var score = 0;
var termLength = term.length;
var queryLength = query.length;
var highlighting = '';
var ti = 0;
// -1 would not work as this would break the calculations of bonus
// points for subsequent character matches. Something like
// Number.MIN_VALUE would be more appropriate, but unfortunately
// Number.MIN_VALUE + 1 equals 1...
var previousMatchingCharacter = -2;
for (var qi = 0; qi < queryLength && ti < termLength; qi++) {
var qc = query.charAt(qi);
var lowerQc = qc.toLowerCase();
for (; ti < termLength; ti++) {
var tc = term.charAt(ti);
if (lowerQc === tc.toLowerCase()) {
score++;
if ((previousMatchingCharacter + 1) === ti) {
score += 2;
}
highlighting += "<em>" + tc + "</em>";
previousMatchingCharacter = ti;
ti++;
break;
} else {
highlighting += tc;
}
}
}
highlighting += term.substring(ti, term.length);
return {
score: score,
term: term,
query: query,
highlightedTerm: highlighting
};
}
Вышеизложенное заботится о нечеткости. Затем вы можете просто перебирать все выбранные 2 элемента
$("#element").select2({
matcher: function(term, text, opt) {
return fuzzyMe(term, text).highlightedTerm;
}
});
Кредит для нечеткого кода -: https://github.com/bripkens/fuzzy.js
возникли трудности с новым select2, здесь что работало
$("#foo").select2({
matcher: matcher
});
function matcher(params, data) {
// return all opts if seachbox is empty
if(!params.term) {
return data;
} else if(data) {
var term = params.term.toUpperCase();
var option = data.text.toUpperCase();
var j = -1; // remembers position of last found character
// consider each search character one at a time
for (var i = 0; i < term.length; i++) {
var l = term[i];
if (l == ' ') continue; // ignore spaces
j = option.indexOf(l, j+1); // search for character & update position
if (j == -1) return false; // if it not found, exclude this item
}
return data; // return option
}
}