[INC] FuzzyWords ( soundex / metaphone / levenshtein )
#1
Glosario
- Soundex
- Metaphone
- Levenshtein
- Damerau Levenshtein
- Fuzzy Matching

En estos días estuve estudiando diferentes métodos para crear un SpellChecker(corrector ortográfico).
Cuando empecé a buscar sobre el tema, lo primero que me encontré fue sobre Fuzzy-Matching / Fuzzy-Text, lo que me llevo al algoritmo de Levenshtein.
Con eso ya se podría crear un spellchecker básico, pero tiene un gran problema, no tiene escalabilidad. Si bien podría funcionar correctamente para un diccionario con cientos de palabras, funcionaria fatal al tener miles o decenas de miles de palabras.
Buscando me encontré con muchas soluciones, algunas muy complejas pero con mucha precisión, pero esos métodos suelen consumir mucha memoria RAM al crear muchas Tries/Hashtables. Al final opte por el método mas simple, pre-filtrar las palabras con Soundex / Metaphone.


Funciones:
Código PHP:
// Remueve los acentos y Ñ, pone todo en mayúscula
stock word_normalize(const in[], out[], len)

// https://es.wikipedia.org/wiki/Soundex
// https://es.wikipedia.org/wiki/Metaphone
stock metaphoneES(const word[], out[], lenstripVowel=true)

// https://es.wikipedia.org/wiki/Distancia_de_Damerau-Levenshtein
stock damerau_levenshtein(const string1[], const string2[], w=1s=1a=1d=1)

// https://es.wikipedia.org/wiki/Distancia_de_Levenshtein
stock levenshtein(const string1[], const string2[]) 

Ejemplo:
Código PHP:
#include <amxmodx>
#include <fuzzywords>

#define DEBUG_ENABLE
#include <debug>


#define PLUGIN    "Basic SpellChecker"
#define VERSION "1.0"
#define AUTHOR    "Destro"


new Trie:g_TrieDict

enum _
:_EntryStruct
{
    
Entry_Original[32],
    
Entry_Normalize[32],    // Second Search
    
Entry_Soundex[32],        // First Search
    
Entry_Freq                // Frequency
}

public 
plugin_init()
{
    
// Para encontrar el error de un crash, lo mejor es tener control sobre el inicio y la finalización de las funciones publicas
    // Una vez que detectas en cual public esta el problema, lo demás es mas fácil
    
debug_hidden("plugin_init() PRE")

    
register_plugin(PLUGINVERSIONAUTHOR)
    
    
register_srvcmd("sc""svcmd_spellcheck")

    
set_task(2.0"task_delay")

    
debug_hidden("plugin_init() POST")
}

public 
plugin_end()
{
    
debug_hidden("plugin_end() PRE")

    
TrieDestroy(g_TrieDict)

    
debug_hidden("plugin_end() POST")

    
// Depende del tipo de crash, puede o no llamarse a plugin_end()
    // Si no se llama, vas a tener que considerar en usar #define DEBUG_ONLY_LOGS
    // Pero trata de no loggear funciones que se llamen decenas de veces por segundo, sino matas al disco
    
debug_end()
}

public 
task_delay()
{
    
debug_hidden("task_delay() PRE")


    
// Veamos cuanto tiempo tarda en cargar el diccionario...
    
debug_performance_start(hola)
    
load_dictionary()
    
debug_performance_end(hola"load_dictionary()")


    
debug_hidden("task_delay() POST")
}

load_dictionary()
{
    new 
entry[_EntryStruct], Array:af
    g_TrieDict 
TrieCreate()

    
fopen("es-AR.dic""r")
    if(!
f)
    {
        
debug_log1("Error al cargar el diccionario")
        return
    }

    new 
maxrepeatmaxrepeatKey[32

    while(!
feof(f))
    {
        
fgets(fentry[Entry_Original], 31)

        
trim(entry[Entry_Original])

        
word_normalize(entry[Entry_Original], entry[Entry_Normalize], 31)
        
metaphoneES(entry[Entry_Original], entry[Entry_Soundex], 31)


        if(
TrieKeyExists(g_TrieDictentry[Entry_Soundex]))
        {
            
TrieGetCell(g_TrieDictentry[Entry_Soundex], a)
            
ArrayPushArray(aentry)

            if(
ArraySize(a) > maxrepeat)
            {
                
copy(maxrepeatKey31entry[Entry_Soundex])
                
maxrepeat ArraySize(a)
            }
        }
        else {
            
ArrayCreate(_EntryStruct1)

            
ArrayPushArray(aentry)
            
TrieSetCell(g_TrieDictentry[Entry_Soundex], a)
        }

    }

    
fclose(f)

    
debug_log1("dictionary max repeat metaphone: %d - %d"maxrepeatmaxrepeatKey)
}

public 
svcmd_spellcheck()
{
    
debug_hidden("svcmd_spellcheck() PRE")

    new 
input[32], soundkey[32], normalize[32]

    
read_argv(1input31)

    
word_normalize(inputnormalize41)
    
metaphoneES(normalizesoundkey31)


    if(
TrieKeyExists(g_TrieDictsoundkey))
    {
        
server_print("Sugerencias para: ^"%s^" (%s) -> [%s] :"inputnormalizesoundkey)

        new 
entry[_EntryStruct], Array:a

        TrieGetCell
(g_TrieDictsoundkeya)

        new 
size ArraySize(a)

        
debug_performance_start(sort)
        
ArraySort(a"ArraySortResult"normalize31)
        
debug_performance_end(sort,"ArraySortResult")

        for(new 
isizei++)
        {
            
ArrayGetArray(aientry)

            new 
soundes[36]
            
metaphoneES(entry[Entry_Original], soundes35false)

            
server_print("- %s ^t%s  ^t->  [%s]"entry[Entry_Original], entry[Entry_Normalize], entry[Entry_Soundex])
        }

        
server_print("^n")
        
debug_hidden("svcmd_spellcheck() POST")
        return
    }

    
server_print("No se encontro  [%s]^n"soundkey)
    
debug_hidden("svcmd_spellcheck() POST")
}

public 
ArraySortResult(Array:aitem1item2, const normalize[])
{
    static 
data1[_EntryStruct], data2[_EntryStruct]

    
ArrayGetArray(aitem1data1)
    
ArrayGetArray(aitem2data2)

    new 
distance1 damerau_levenshtein(data1[Entry_Normalize], normalize)
    new 
distance2 damerau_levenshtein(data2[Entry_Normalize], normalize)

    if(
distance1 distance2)
        return -
1
    
if(distance1 distance2)
        return 
1

    
if(data1[Entry_Freq] < data2[Entry_Freq])
        return -
1
    
if(data1[Entry_Freq] > data2[Entry_Freq])
        return 
1

    
return 0


[Imagen: 7d658d1fd1.png]
[Imagen: 1ebcbb260b.png]




.inc   fuzzywords.inc (Tamaño: 6.36 KB / Descargas: 16)
Responder
#2
Excelente aporte Destro; un capo. Crab
Responder
#3
Interesante aporte Thinking
Responder
#4
Buen aporte !, deberías dejar una información para que sirve para algunos usuarios novatos en eso me incluyo.
Responder
#5
Good Job!
Steam
Responder
#6
Esta interesante el aporte, cuando esté en la PC lo voy a ver con más comodidad porque en el celu se deforma todo XD.

Por lo rápido que ví, estaría bueno que le des soporte para amx 1.9 y uses algunas de las funciones nuevas que tiene que están mejor optimizadas. O mejor aún, quizás puedas aportarlo para que sea parte de amx Thats what she said
Believe, be yourself and don't hold on to just one dream ❤

https://github.com/FEDERICOMB96
Responder
#7
Buen aporte destro Approved

Estaba revisando el inc, y vi esto:
Código PHP:
replace_all(buffcharsmax(buff), "MN""N"

Que pasa con las palabras que llevan "mn"?
columna, amnesia, insomnio, alumno, somnolencia?

What

PD: No lo he probado todavia
[Imagen: b_350_20_323957_202743_f19a15_111111.png]

Estudia siempre; el tiempo es oro, lo material se puede recuperar pero el tiempo no se puede recuperar.
(02/10/2016, 05:05 PM)meTaLiCroSS escribió: Siempre me gusta ayudar cuando alguien esta interesado realmente en ver que esta programando.
(08/08/2019, 05:32 PM)meTaLiCroSS escribió: grax x el dato cr4ck


Mis aportes

PLUGINS
MAPAS
Menú LANG [SF] Sistema de Frags
Say System (Admin Prefix)
Responder
#8
(22/09/2018, 08:34 PM)Federicomb escribió: Esta interesante el aporte, cuando esté en la PC lo voy a ver con más comodidad porque en el celu se deforma todo XD.

Por lo rápido que ví, estaría bueno que le des soporte para amx 1.9 y uses algunas de las funciones nuevas que tiene que están mejor optimizadas. O mejor aún, quizás puedas aportarlo para que sea parte de amx Thats what she said
ya me tendría que estar actualizandome, todavía tengo amxx 1.8.1 y 1.8.2 xd

(22/09/2018, 09:46 PM)totopizza escribió: Buen aporte destro Approved

Estaba revisando el inc, y vi esto:
Código PHP:
replace_all(buffcharsmax(buff), "MN""N"

Que pasa con las palabras que llevan "mn"?
columna, amnesia, insomnio, alumno, somnolencia?

What

PD: No lo he probado todavia
en esos casos la M suena algo similar a la N, es como CE CI S Z X que tienen casos en los que suenan muy similares

"amnesia" queda en "anesia", el metaphone es "ANS"
Responder
#9
Buen aporte!
[Imagen: 76561198350936449.png]

Cita:Los precios en la moneda venezolana se fijarán a partir de la reconversión monetaria y valdrá mucho menos de lo que cuesta una Cachapa con queso.
Responder
#10
(22/09/2018, 10:11 PM)Destro escribió:
(22/09/2018, 08:34 PM)Federicomb escribió: Esta interesante el aporte, cuando esté en la PC lo voy a ver con más comodidad porque en el celu se deforma todo XD.

Por lo rápido que ví, estaría bueno que le des soporte para amx 1.9 y uses algunas de las funciones nuevas que tiene que están mejor optimizadas. O mejor aún, quizás puedas aportarlo para que sea parte de amx Thats what she said
ya me tendría que estar actualizandome, todavía tengo amxx 1.8.1 y 1.8.2 xd

(22/09/2018, 09:46 PM)totopizza escribió: Buen aporte destro Approved

Estaba revisando el inc, y vi esto:
Código PHP:
replace_all(buffcharsmax(buff), "MN""N"

Que pasa con las palabras que llevan "mn"?
columna, amnesia, insomnio, alumno, somnolencia?

What

PD: No lo he probado todavia
en esos casos la M suena algo similar a la N, es como CE CI S Z X que tienen casos en los que suenan muy similares

"amnesia" queda en "anesia", el metaphone es "ANS"

Ok Insecure
[Imagen: b_350_20_323957_202743_f19a15_111111.png]

Estudia siempre; el tiempo es oro, lo material se puede recuperar pero el tiempo no se puede recuperar.
(02/10/2016, 05:05 PM)meTaLiCroSS escribió: Siempre me gusta ayudar cuando alguien esta interesado realmente en ver que esta programando.
(08/08/2019, 05:32 PM)meTaLiCroSS escribió: grax x el dato cr4ck


Mis aportes

PLUGINS
MAPAS
Menú LANG [SF] Sistema de Frags
Say System (Admin Prefix)
Responder
#11
recién me doy cuenta de que esta bug el compiler, convierte mal los caracteres UTF cuando están en un include :S
voy a tener que escribir los bits manualmente, shit

_________
@Edit
Listo, corregido y agregado un plugin de ejemplo
Responder
#12
(23/09/2018, 01:22 AM)Destro escribió: recién me doy cuenta de que esta bug el compiler, convierte mal los caracteres UTF cuando están en un include :S
voy a tener que escribir los bits manualmente, shit

_________
@Edit
Listo, corregido y agregado un plugin de ejemplo

creo que revirtieron algo que habían hecho.

https://github.com/alliedmodders/amxmodx...982102f138
[Imagen: b_350_20_323957_202743_f19a15_111111.png]

(18/11/2014, 05:47 PM)Neeeeeeeeeel.- escribió: Por qué necesitan una guía para todo? Meté mano y que salga lo que salga... es la mejor forma de aprender.

(16/05/2016, 11:08 PM)kikizon2 escribió: No cabe duda que tienen mierda en vez de cerebro, par de pendejos v:
Responder
#13
Yo hace unas semanas estaba intentando lo mismo x.x, si me acuerdo dejare mi base por aquí.

Pd: Muy buen aporte.

Saludos Mario
Mis proyectos.

[ Venta o aporte ] CTF + COD ❣❣

[ Venta o aporte ] BaseBuilder Levels. ❣❣

[ Venta o aporte ] ZombiePlague Levels v1 ❣❣

Sí necesitas ayuda y tengo tiempo y puedo ayudarte, no dudes en contactarme.

Frase diaria:
[ Si el plan falla cambia el plan pero nunca cambies la meta. ]
_______________________________________________________________________________________
Responder
#14
(22/09/2018, 04:25 PM)Chema escribió: Excelente aporte Destro; un capo. Crab

Responder


Salto de foro:


Usuarios navegando en este tema: 1 invitado(s)