Comparar cadenas% de probabilidad de retorno de JavaScript

82

Estoy buscando una función de JavaScript que pueda comparar dos cadenas y devolver la probabilidad de que sean iguales. He mirado soundex pero eso no es realmente bueno para cadenas de varias palabras o no nombres. Estoy buscando una función como:

function compare(strA,strB){

}

compare("Apples","apple") = Some X Percentage.

La función funcionaría con todo tipo de cadenas, incluidos números, valores de varias palabras y nombres. ¿Quizás hay un algoritmo simple que podría usar?

Ultimately none of these served my purpose so I used this:

 function compare(c, u) {
        var incept = false;
        var ca = c.split(",");
        u = clean(u);
        //ca = correct answer array (Collection of all correct answer)
        //caa = a single correct answer word array (collection of words of a single correct answer)
        //u = array of user answer words cleaned using custom clean function
        for (var z = 0; z < ca.length; z++) {
            caa = $.trim(ca[z]).split(" ");
            var pc = 0;
            for (var x = 0; x < caa.length; x++) {
                for (var y = 0; y < u.length; y++) {
                    if (soundex(u[y]) != null && soundex(caa[x]) != null) {
                        if (soundex(u[y]) == soundex(caa[x])) {
                            pc = pc + 1;
                        }
                    }
                    else {
                        if (u[y].indexOf(caa[x]) > -1) {
                            pc = pc + 1;
                        }
                    }
                }
            }
            if ((pc / caa.length) > 0.5) {
                return true;
            }
        }
        return false;
    }

    // create object listing the SOUNDEX values for each letter
    // -1 indicates that the letter is not coded, but is used for coding
    //  0 indicates that the letter is omitted for modern census archives
    //                              but acts like -1 for older census archives
    //  1 is for BFPV
    //  2 is for CGJKQSXZ
    //  3 is for DT
    //  4 is for L
    //  5 is for MN my home state
    //  6 is for R
    function makesoundex() {
        this.a = -1
        this.b = 1
        this.c = 2
        this.d = 3
        this.e = -1
        this.f = 1
        this.g = 2
        this.h = 0
        this.i = -1
        this.j = 2
        this.k = 2
        this.l = 4
        this.m = 5
        this.n = 5
        this.o = -1
        this.p = 1
        this.q = 2
        this.r = 6
        this.s = 2
        this.t = 3
        this.u = -1
        this.v = 1
        this.w = 0
        this.x = 2
        this.y = -1
        this.z = 2
    }

    var sndx = new makesoundex()

    // check to see that the input is valid
    function isSurname(name) {
        if (name == "" || name == null) {
            return false
        } else {
            for (var i = 0; i < name.length; i++) {
                var letter = name.charAt(i)
                if (!(letter >= 'a' && letter <= 'z' || letter >= 'A' && letter <= 'Z')) {
                    return false
                }
            }
        }
        return true
    }

    // Collapse out directly adjacent sounds
    // 1. Assume that surname.length>=1
    // 2. Assume that surname contains only lowercase letters
    function collapse(surname) {
        if (surname.length == 1) {
            return surname
        }
        var right = collapse(surname.substring(1, surname.length))
        if (sndx[surname.charAt(0)] == sndx[right.charAt(0)]) {
            return surname.charAt(0) + right.substring(1, right.length)
        }
        return surname.charAt(0) + right
    }

    // Collapse out directly adjacent sounds using the new National Archives method
    // 1. Assume that surname.length>=1
    // 2. Assume that surname contains only lowercase letters
    // 3. H and W are completely ignored
    function omit(surname) {
        if (surname.length == 1) {
            return surname
        }
        var right = omit(surname.substring(1, surname.length))
        if (!sndx[right.charAt(0)]) {
            return surname.charAt(0) + right.substring(1, right.length)
        }
        return surname.charAt(0) + right
    }

    // Output the coded sequence
    function output_sequence(seq) {
        var output = seq.charAt(0).toUpperCase() // Retain first letter
        output += "-" // Separate letter with a dash
        var stage2 = seq.substring(1, seq.length)
        var count = 0
        for (var i = 0; i < stage2.length && count < 3; i++) {
            if (sndx[stage2.charAt(i)] > 0) {
                output += sndx[stage2.charAt(i)]
                count++
            }
        }
        for (; count < 3; count++) {
            output += "0"
        }
        return output
    }

    // Compute the SOUNDEX code for the surname
    function soundex(value) {
        if (!isSurname(value)) {
            return null
        }
        var stage1 = collapse(value.toLowerCase())
        //form.result.value=output_sequence(stage1);

        var stage1 = omit(value.toLowerCase())
        var stage2 = collapse(stage1)
        return output_sequence(stage2);

    }

    function clean(u) {
        var u = u.replace(/\,/g, "");
        u = u.toLowerCase().split(" ");
        var cw = ["ARRAY OF WORDS TO BE EXCLUDED FROM COMPARISON"];
        var n = [];
        for (var y = 0; y < u.length; y++) {
            var test = false;
            for (var z = 0; z < cw.length; z++) {
                if (u[y] != "" && u[y] != cw[z]) {
                    test = true;
                    break;
                }
            }
            if (test) {
    //Don't use & or $ in comparison
                var val = u[y].replace("$", "").replace("&", "");
                n.push(val);
            }
        }
        return n;
    }
Brad Ruderman
fuente
Estoy probando esto, todavía tengo problemas para encontrar el perfecto. El ejemplo clásico que rompe estos. Diga que la pregunta es "¿Cuáles son los dos primeros presidentes?" y alguien responde "adams y washington". Una comparación de cadenas con "george washington john adams" debería ser aproximadamente del 50%.
Brad Ruderman
oof, depende de jQuery?
Shawn Whinnery

Respuestas:

135

Aquí hay una respuesta basada en la distancia de Levenshtein https://en.wikipedia.org/wiki/Levenshtein_distance

function similarity(s1, s2) {
  var longer = s1;
  var shorter = s2;
  if (s1.length < s2.length) {
    longer = s2;
    shorter = s1;
  }
  var longerLength = longer.length;
  if (longerLength == 0) {
    return 1.0;
  }
  return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
}

Para calcular la distancia de edición

function editDistance(s1, s2) {
  s1 = s1.toLowerCase();
  s2 = s2.toLowerCase();

  var costs = new Array();
  for (var i = 0; i <= s1.length; i++) {
    var lastValue = i;
    for (var j = 0; j <= s2.length; j++) {
      if (i == 0)
        costs[j] = j;
      else {
        if (j > 0) {
          var newValue = costs[j - 1];
          if (s1.charAt(i - 1) != s2.charAt(j - 1))
            newValue = Math.min(Math.min(newValue, lastValue),
              costs[j]) + 1;
          costs[j - 1] = lastValue;
          lastValue = newValue;
        }
      }
    }
    if (i > 0)
      costs[s2.length] = lastValue;
  }
  return costs[s2.length];
}

Uso

similarity('Stack Overflow','Stack Ovrflw')

devuelve 0.8571428571428571


Puedes jugar con él a continuación:

overlord1234
fuente
Una mejora para varias palabras: var similarity2 = function (s1, s2) {var split1 = s1.split (''); var split2 = s2.split (''); var suma = 0; var max = 0; var temp = 0; para (var i = 0; i <split1.length; i ++) {max = 0; for (var j = 0; j <split2.length; j ++) {temp = similitud (split1 [i], split2 [j]); si (max <temp) max = temp; } console.log (máx.); suma + = max / split1.length; } devolución de suma; };
infinito84
@ overlord1234 ¿El método anterior funciona para cadenas como esta: 9e27dbb9ff6eea70821c02b4457cbc6b7eb8e12a64f46c192c3a05f1bc1519acd101193dac157c6233d9d773f9b364ca210d6287f965?
hyperfkcb
Funciona con cadenas sin una semántica adjunta. Intente ejecutar el fragmento de código en línea (gracias a David). Obtengo una similitud de 0.17857142857142858 cuando ingreso las cadenas mencionadas anteriormente.
overlord1234
@hyperfkcb está implementando el algoritmo de distancia de edición, que cuenta cuántos caracteres están en la posición incorrecta (más o menos), por lo que para calcular el porcentaje, está tomando el valor de distancia de edición más largo posible (longLength) y haciendo (longLength - editDistance) / longLength.
infinito84
Sin embargo, es demasiado lento para cadenas largas.
puesta en marcha el
16

Aquí hay una función muy simple que hace una comparación y devuelve un porcentaje basado en la equivalencia. Si bien no se ha probado en todos los escenarios posibles, puede ayudarlo a comenzar.

function similar(a,b) {
    var equivalency = 0;
    var minLength = (a.length > b.length) ? b.length : a.length;    
    var maxLength = (a.length < b.length) ? b.length : a.length;    
    for(var i = 0; i < minLength; i++) {
        if(a[i] == b[i]) {
            equivalency++;
        }
    }
    

    var weight = equivalency / maxLength;
    return (weight * 100) + "%";
}
alert(similar("test","tes"));   // 75%
alert(similar("test","test"));  // 100%
alert(similar("test","testt")); // 80%
alert(similar("test","tess"));  // 75%
jmort253
fuente
7
El problema con esto es que "atest" y "test" devuelven 0%, lo que sabemos que no es cierto.
peresisUser
8

¡Usar esta biblioteca para la similitud de cadenas funcionó como un encanto para mí!

Aquí está el ejemplo:

var similarity = stringSimilarity.compareTwoStrings("Apples","apple");    // => 0.88
Tushar Walzade
fuente
6
Eso es genial, excepto que stringSimilarity tiene una dependencia llamada lodash que contiene más de 1,000 archivos que se colocan en su proyecto solo para que pueda obtener similitud de cadenas.
GrumpyCrouton
2
Sí, sucede al agregar un paquete localmente. Pero en su lugar, podemos usar CDN para un tamaño de paquete menor. Aquí están los enlaces CDN - jsdelivr.com/package/npm/lodash - jsdelivr.com/package/npm/string-similarity
Tushar Walzade
2
Han eliminado la mayoría de las dependencias, incluido lodash
Lovenkrands
7

Solo uno que escribí rápidamente que podría ser lo suficientemente bueno para sus propósitos:

function Compare(strA,strB){
    for(var result = 0, i = strA.length; i--;){
        if(typeof strB[i] == 'undefined' || strA[i] == strB[i]);
        else if(strA[i].toLowerCase() == strB[i].toLowerCase())
            result++;
        else
            result += 4;
    }
    return 1 - (result + 4*Math.abs(strA.length - strB.length))/(2*(strA.length+strB.length));
}

Esto pesa los personajes que son iguales pero diferentes en caso de una cuarta parte de los personajes que son completamente diferentes o que faltan. Devuelve un número entre 0 y 1, 1 significa que las cadenas son idénticas. 0 significa que no tienen similitudes. Ejemplos:

Compare("Apple", "Apple")    // 1
Compare("Apples", "Apple")   // 0.8181818181818181
Compare("Apples", "apple")   // 0.7727272727272727
Compare("a", "A")            // 0.75
Compare("Apples", "appppp")  // 0.45833333333333337
Compare("a", "b")            // 0
Paul
fuente
6
No tan exacto: Compare ("Apple", "zApple") = 0.07, mientras Compare ("Apple", "Applez") = 0.84
Kousha
3
@Kousha, es posicional. "Apple" y "zApple" solo tienen una letra en común (la segunda p).
Paul
2
@Paulpro Apple y zApple tienen cinco letras en común lógicamente. Es culpa suya. Apple, zApple, Applez son similares.
Kousha
2
@Kousha, zApple no es similar según este algoritmo, ya que es posicional. Eso no hace que el algoritmo sea incorrecto.
Paul
8
@Paulpro: Eso no hace que su algoritmo sea incorrecto, pero lo convierte en una mala respuesta para esta pregunta ...
MarcoS
6

¿Qué tal la función similar_textde la biblioteca PHP.js ?

Se basa en una función PHP con el mismo nombre .

function similar_text (first, second) {
    // Calculates the similarity between two strings  
    // discuss at: http://phpjs.org/functions/similar_text

    if (first === null || second === null || typeof first === 'undefined' || typeof second === 'undefined') {
        return 0;
    }

    first += '';
    second += '';

    var pos1 = 0,
        pos2 = 0,
        max = 0,
        firstLength = first.length,
        secondLength = second.length,
        p, q, l, sum;

    max = 0;

    for (p = 0; p < firstLength; p++) {
        for (q = 0; q < secondLength; q++) {
            for (l = 0;
            (p + l < firstLength) && (q + l < secondLength) && (first.charAt(p + l) === second.charAt(q + l)); l++);
            if (l > max) {
                max = l;
                pos1 = p;
                pos2 = q;
            }
        }
    }

    sum = max;

    if (sum) {
        if (pos1 && pos2) {
            sum += this.similar_text(first.substr(0, pos2), second.substr(0, pos2));
        }

        if ((pos1 + max < firstLength) && (pos2 + max < secondLength)) {
            sum += this.similar_text(first.substr(pos1 + max, firstLength - pos1 - max), second.substr(pos2 + max, secondLength - pos2 - max));
        }
    }

    return sum;
}
Visión
fuente
1
¿Se devuelve la similitud según el carácter coincidente? ¿Cómo evalúa la similitud?
hyperfkcb
2

Encontrar el grado de similitud entre dos cadenas; podemos usar más de uno o dos métodos, pero me inclino principalmente por el uso del ' Coeficiente de dados ' . ¡cual es mejor! bien que yo sepa que usar ' distancia de Levenshtein '

Usando este paquete de ' string-similarity ' de npm, podrá trabajar en lo que dije anteriormente.

algunos ejemplos de uso sencillo son

var stringSimilarity = require('string-similarity');

var similarity = stringSimilarity.compareTwoStrings('healed', 'sealed'); 

var matches = stringSimilarity.findBestMatch('healed', ['edward', 'sealed', 'theatre']);

para obtener más información, visite el enlace que figura arriba. Gracias.

Leon Grin
fuente
1
Un enlace a una solución es bienvenido, pero asegúrese de que su respuesta sea útil sin él: agregue contexto alrededor del enlace para que sus compañeros usuarios tengan una idea de qué es y por qué está allí, luego cite la parte más relevante de la página. está enlazando a en caso de que la página de destino no esté disponible. Las respuestas que son poco más que un enlace pueden eliminarse .
David Buck
1

fuzzyset : un conjunto de cadenas difusas para javascript. fuzzyset es una estructura de datos que realiza algo parecido a la búsqueda de texto completo contra datos para determinar posibles errores ortográficos y la coincidencia aproximada de cadenas. Tenga en cuenta que este es un puerto javascript de una biblioteca de Python.

Octavio Díaz
fuente