javascript - node - sublime text packages manager



¿Cómo implementar texto sublime como la búsqueda difusa? (5)

¿Cómo puedo implementar una búsqueda difusa tipo sublime en select2?

Ejemplo, escribir "sta jav sub" coincidiría con "Stackoverflow javascript sublime like"

https://src-bin.com


Answer #1

Aquí hay una función de coincidencia alternativa. 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's not found, exclude this item
    }
    return true;
}

Esto es un poco más rápido (al menos cuando ejecuto esta prueba en Chrome en OS X), lo que puede ser importante si está filtrando muchos elementos.


Answer #2

Escribí algunas que funcionan mucho más largas coincidencias difusas de Sublime Text. Lograr esto requiere algunas cosas.

Primero, une todos los caracteres de un patrón en secuencia. En segundo lugar, marca las coincidencias de modo que ciertos personajes coincidentes valgan más puntos que otros.

Se me ocurrieron algunos factores para verificar. Las letras "CamelCase" o las letras que siguen a un separador (espacio o subrayado) valen muchos puntos. Los partidos consecutivos valen más. Los resultados encontrados cerca del comienzo valen más.

Un truco de importancia crítica es encontrar el mejor personaje coincidente. Lo que no es necesariamente el primero. Considere fuzzy_match ("tk", "The Black Knight"). Hay dos Ks que podrían emparejarse. El segundo vale más puntos porque sigue un espacio.

El código de JavaScript está debajo. Hay algunos matices que se describen con más detalle en una publicación de blog. También hay una demostración interactiva. Y fuente completa (incluye demo, además de implementación en C ++) en GitHub.

  • Entrada en el blog
  • Demostración interactiva
  • 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];
    }
    

Answer #3

Tuvo dificultades con el nuevo Select2, aquí lo que funcionó.

 $("#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's not found, exclude this item
    }
    return data; // return option
  }
}

Answer #4

select2 le permite implementar sus propias funciones de "comparador" ( como se ve en sus documentos ), utilizando eso y algunas expresiones regulares puede hacer algo como:

$("#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, '.+'));
    }
});

Se invoca una función de comparación contra cada elemento de la lista select2 cuando se filtra / busca en la lista, puede implementar cualquier tipo de búsqueda personalizada utilizando eso.


Answer #5
var fuzzysearch = function (querystrings, values) {
    return !querystrings.some(function (q) {
        return !values.some(function (v) {
            return v.toLocaleLowerCase().indexOf(q) !== -1;
        });
    });
}

Ejemplo de búsqueda de título y autor en la colección de libros http://jsfiddle.net/runjep/r887etnh/2/

Para una alternativa de 9kb que clasifica el resultado de la búsqueda: http://kiro.me/projects/fuse.html

Es posible que necesite un polyfill para la función '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>





jquery-select2