A veces hay la necesidad de comparar cadenas de texto para determinar si son similares o no. Por ejemplo, errores tipográficos al escribir, o ver si una abreviatura corresponde con una palabra, etc. Esto es útil cuando varios usuarios escriben la misma palabra, por ejemplo, el nombre de una ciudad y cada uno la escribe distinta bien porque la ciudad no es en su idioma nativo y comete errores tipográficos, bien porque tiene a abreviarla, etc. Sin embargo, medir la similitud entre palabras no es tan sencillo como verificar si son iguales. Diferentes algoritmos ofrecen distintas formas de evaluar similitudes, y elegir el correcto es clave para obtener buenos resultados.
¿Por qué comparar palabras no es trivial?
Imagina que tienes una base de datos de nombres de ciudades y los usuarios escriben sus nombres con variaciones como:
- «New York City» vs. «NYC»
- «San Francisco» vs. «San Fransisco»
- «Port of Antwerp» vs. «Antwerp»
- «Róterdam» vs. «Rotterdam»
Aquí hay varios desafíos:
- Errores tipográficos («Fransisco» en vez de «Francisco»).
- Abreviaciones («NYC» en vez de «New York City»).
- Diferencias en idiomas («Róterdam» vs. «Rotterdam»).
- Diferencias en formato («Port of Antwerp» vs. «Antwerp»).
Para abordar estos problemas, podemos usar la librería Apache Commons Text, que proporciona varias métricas de similitud entre cadenas.
Introducción a Apache Commons Text
Apache Commons Text es una biblioteca de Java especializada en manipulación de texto. Una de sus características más útiles es el conjunto de clases para medir la similitud entre cadenas. Algunas de las más importantes son:
LevenshteinDistance
JaroWinklerDistance
DamerauLevenshteinDistance
JaccardSimilarity
CosineSimilarity
FuzzyScore
Veamos cada una de estas clases y en qué casos es mejor utilizarlas.
1. Levenshtein Distance
📌 Cuándo usarla: Para detectar errores ortográficos o pequeños cambios en palabras.
Calcula el número mínimo de inserciones, eliminaciones o sustituciones necesarias para convertir una palabra en otra.
🔹 Ejemplo:
- «San Francisco» vs. «San Fransisco» → Distancia = 1, sólo hay que sustituir la s por c.
- «Rotterdam» vs. «Róterdam» → Distancia = 2, sólo hay que cambiar la ó por o e insertar una t
Desventaja: No tiene en cuenta la posición de los errores ni favorece coincidencias parciales.
2. Jaro-Winkler Distance
📌 Cuándo usarla: Para comparar nombres o direcciones con diferencias menores.
Se enfoca en la posición de los caracteres y favorece coincidencias al inicio de la palabra.
🔹 Ejemplo:
- «Robert» vs. «Roberto» → Similitud alta (~0.94)
- «Port of Antwerp» vs. «Antwerp» → Similitud alta
Desventaja: No maneja bien errores ortográficos importantes.
3. Damerau-Levenshtein Distance
📌 Cuándo usarla: Para errores comunes de transposición de letras.
Es una mejora sobre Levenshtein, permitiendo intercambios de letras adyacentes como un solo error.
🔹 Ejemplo:
- «shangai» vs. «sahngai» → Distancia = 1 (corrige un error de transposición)
Desventaja: Es más costoso computacionalmente que Jaro-Winkler.
4. Jaccard Similarity
📌 Cuándo usarla: Para comparar frases o textos largos.
Se basa en la cantidad de palabras en común entre dos textos.
🔹 Ejemplo:
- «El barco llega al puerto de Barcelona» vs. «El puerto de Barcelona recibe un barco» → Similaridad alta
Desventaja: No detecta errores ortográficos ni considera el orden de las palabras.
5. Cosine Similarity
📌 Cuándo usarla: Para medir similitud en documentos extensos.
Se basa en la frecuencia de palabras en dos textos, útil en análisis de documentos y búsqueda semántica.
🔹 Ejemplo:
- Comparar descripciones de productos en un ecommerce para detectar duplicados.
Desventaja: No es útil para palabras individuales.
6. FuzzyScore
📌 Cuándo usarla: Para búsquedas con errores tipográficos.
Evalúa qué tan probable es que una palabra haya sido mal escrita basándose en la disposición del teclado.
🔹 Ejemplo:
- «helo» vs. «hello» → Alta similitud porque «o» está cerca de «l» en el teclado.
Desventaja: Solo funciona bien en palabras cortas.
Implementación en Java
Para usar estos algoritmos en Java, primero debemos agregar la dependencia en Maven:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
Luego, podemos comparar nombres de puertos usando Jaro-Winkler y Levenshtein:
import org.apache.commons.text.similarity.JaroWinklerDistance;
import org.apache.commons.text.similarity.LevenshteinDistance;
public class ComparacionPuertos {
public static void main(String[] args) {
JaroWinklerDistance jaroWinkler = new JaroWinklerDistance();
LevenshteinDistance levenshtein = new LevenshteinDistance();
String puerto1 = "Port of Rotterdam";
String puerto2 = "Rotterdam";
double similitudJW = jaroWinkler.apply(puerto1, puerto2);
int distanciaLev = levenshtein.apply(puerto1, puerto2);
System.out.println("Jaro-Winkler Similarity: " + similitudJW);
System.out.println("Levenshtein Distance: " + distanciaLev);
}
}