Investigando un poco hace algunos años había encontrado esta entrada https://wiki.postgresql.org/wiki/SoundexESP, en la que me basé para crear sus equivalentes en distintos lenguages (VBA, T-SQL para Oracle). Por supuesto cuando se dió la necesidad le tocó a Genexus.
Implementación
La implementación la hice en un procedimiento llamado soundex, pero ustedes pueden llamarlo como mas les convengasoundex:
/* Implementación del algoritmo Soundex para el idioma español variables tipo ----------------------- ----------- caracter C(1) caracteres_buscar C(20) caracteres_reemplazar C(20) i N(2) primera_letra C(1) reemplazo C(1) resto C(20) soundex C(20) texto C(32) tmp C(32) parámetros ----------------------- in:&texto out:&soundex */ &soundex.SetEmpty() &tmp = &texto.Trim().ToUpper() // Si no hay texto a procesar retornamos un texto vacío if &tmp.IsEmpty() return endif // realizamos un preprocesado del texto do 'limpieza' // aplicamos el algoritmo do 'soundex' Sub 'limpieza' /* 1) limpieza */ // eliminamos la H inicial (incluso si hay mas de una) &tmp = &tmp.ReplaceRegEx('^(H+)(.*)', '$2') // retornar vacío si no nos queda texto para analizar if &tmp.IsEmpty() return endif // eliminamos los acentos y la Ñ &caracteres_buscar = 'ÑÁÉÍÓÚÀÈÌÒÙÜ' &caracteres_reemplazar = 'NAEIOUAEIOUU' for &i=1 to &caracteres_buscar.Length() &caracter = &caracteres_buscar.Substring(&i,1) if &caracteres_buscar.IndexOf(&caracter)>0 &tmp = &tmp.Replace(&caracter, &caracteres_reemplazar.Substring(&i, 1)) endif // &buscar.IndexOf(&caracter)>0 endfor // &i=1 to &buscar.Length() ... // eliminamos caracteres no alfabéticos (números, signos, símbolos, etc) &tmp = &tmp.ReplaceRegEx('[^A-Z]', '') /* 2) ajustar primera letra */ // fenómenos o casos especiales: GE y GI se convierten en JE y JI, CA en KA &primera_letra = &tmp.Substring(1,1) &resto = &tmp.Substring(2,&tmp.Length()-1) do case case &primera_letra = 'V' &reemplazo = 'B' // VACA -> BACA, VALOR -> BALOR case &primera_letra = 'Z' or &primera_letra = 'X' &reemplazo = 'S' // ZAPATO -> SAPATO, XILÓFONO -> SILÓFONO case &primera_letra = 'G' and (&tmp.Substring(2,1)='E' or &tmp.Substring(2,1)='I') &reemplazo = 'J' // GIMNASIO -> JIMNASIO, GERANIO -> JERANIO case &primera_letra = 'C' and &tmp.Substring(2,1)<>'H' and &tmp.Substring(2,1)<>'E' and &tmp.Substring(2,1)<>'I' &reemplazo = 'K' // CASA -> KASA, COLOR -> KOLOR, CULPA -> KULPA otherwise &reemplazo = &primera_letra endcase &tmp = &reemplazo + &resto /* 3) corregir letras compuestas, volverlas una sola */ &tmp = &tmp.ReplaceRegEx('CH', 'V') &tmp = &tmp.ReplaceRegEx('QU', 'K') &tmp = &tmp.ReplaceRegEx('LL', 'J') &tmp = &tmp.ReplaceRegEx('CE', 'S') &tmp = &tmp.ReplaceRegEx('CI', 'S') &tmp = &tmp.ReplaceRegEx('YA', 'J') &tmp = &tmp.ReplaceRegEx('YE', 'J') &tmp = &tmp.ReplaceRegEx('YI', 'J') &tmp = &tmp.ReplaceRegEx('YO', 'J') &tmp = &tmp.ReplaceRegEx('YU', 'J') //&tmp = &tmp.ReplaceRegEx('GE', 'J') //&tmp = &tmp.ReplaceRegEx('GI', 'J') &tmp = &tmp.ReplaceRegEx('NY', 'N') &tmp = &tmp.ReplaceRegEx('NH', 'N') // anho, banho, tamanho, inhalador EndSub // 'limpieza' ... Sub 'soundex' /* 4) obtener primera letra */ &primera_letra = &tmp.Substring(1, 1) /* 5) obtener el resto del texto * &resto = &tmp.Substring(2, &tmp.Length()-1) /* 6) en el resto, eliminar vocales y consonantes fonéticas */ &resto = &resto.ReplaceRegEx('[AEIOUHWY]', '') /* 7) convertir letras fonéticamente equivalentes a números. esto hace que B sea equivalente a V, C con S y Z, etc. */ &resto = &resto.ReplaceRegEx('[BPFV]', '1') &resto = &resto.ReplaceRegEx('[CGKSXZ]', '2') &resto = &resto.ReplaceRegEx('[DT]', '3') &resto = &resto.ReplaceRegEx('[L]', '4') &resto = &resto.ReplaceRegEx('[MN]', '5') &resto = &resto.ReplaceRegEx('[R]', '6') &resto = &resto.ReplaceRegEx('[QJ]', '7') // eliminamos números iguales adyacentes &resto = &resto.ReplaceRegEx('(\d)\1+', '$1') &soundex = &primera_letra + &resto.Trim() if &soundex.Length() < 4 &soundex = padr(&soundex, 4, '0') else &soundex = &soundex.Substring(1,4) endif EndSub // 'soundex' ...
Prueba
A continuación podemos hacer unas pruebas para ver el algoritmo en funcionamiento, creamos otro procedimiento llamado prueba_soundex:prueba_soundex:
/* Colocar los siguientes valores en las propiedades del procedimiento ------------------------------------------------------------------- Main program: true Call protocol: Command Line */ msg('hola => ' + soundex.Udp('hola'), status) msg('ola => ' + soundex.Udp('ola'), status) msg('zapato => ' + soundex.Udp('zapato'), status) msg('sapato => ' + soundex.Udp('sapato'), status) msg('Jimenez => ' + soundex.Udp('Jimenez'), status) msg('Jiménez => ' + soundex.Udp('Jiménez'), status) msg('Jimenes => ' + soundex.Udp('Jimenes'), status) msg('Jiménes => ' + soundex.Udp('Jiménes'), status) msg('Gimenez => ' + soundex.Udp('Gimenez'), status) msg('Giménez => ' + soundex.Udp('Giménez'), status) msg('Gimenes => ' + soundex.Udp('Gimenes'), status) msg('Giménes => ' + soundex.Udp('Giménes'), status) msg('Díaz => ' + soundex.Udp('Díaz'), status) msg('días => ' + soundex.Udp('días'), status) msg('dias => ' + soundex.Udp('dias'), status) msg('mejico => ' + soundex.Udp('mejico'), status) msg('mexico => ' + soundex.Udp('mexico'), status)
Luego ejecutamos prueba_soundex y deberíamos de obtener la siguiente salida:
"C:\Program Files\Java\jdk1.8.0_25\bin\java.exe" atest_soundex hola => O400 ola => O400 zapato => S130 sapato => S130 Jimenez => J520 Jiménez => J520 Jimenes => J520 Jiménes => J520 Gimenez => J520 Giménez => J520 Gimenes => J520 Giménes => J520 Díaz => D200 días => D200 dias => D200 mejico => M720 mexico => M200 Execution SuccessComo pueden ver el algoritmo es bastante preciso con palabras similares, aunque sigo trabajando en generalizar el caso de ji y xi.
Pensamientos finales
Los escenarios donde aplicar eso de forma práctica pueden variar mucho, de hecho no se si podría usarse efectivamente con nombres completos.En mi caso lo estuve utilizando para crear un diccionario de una tabla de nombres de calles. Primero se procesan las calles registradas (normalizadas), se separan en sus palabras componentes y se registran individualmente en un diccionario con su correspondiente código soundex. Se guarda la calle y su relación con sus palabras componentes.
Al introducir un nombre de calle para buscar, esta también se separa en sus palabras componentes, se calcula el código soundex para cada una y son estos códigos los que se buscan en el diccionario, por supuesto que el proceso es mas complejo y tiene mas validaciones, pero básicamente es así como funciona.
El código de ambos procedimientos pueden encontrarlos en https://github.com/padiazg/soundex-genexus
No hay comentarios.:
Publicar un comentario