¿Cómo verificar en Go si una cadena contiene una subcadena? De principiante a experto
\n\n¿Alguna vez has escrito en Go y te has quedado paralizado ante una pregunta simple: «¿Esta cadena contiene la parte que necesito?» Si es así, no estás solo. En Stack Overflow, esta pregunta ha acumulado miles de visitas, y con razón. En Go, hay varias formas elegantes de resolver este problema, pero ¿cuál elegir? Aclaremos esto de una vez por todas.
\n\nEn este artículo, no solo te mostraremos la función strings.Contains. Te enseñaremos a pensar como un desarrollador de Go: elegir la herramienta óptima para cada situación, evitar trampas y escribir código que sea rápido y predecible.
Contenido
\n- \n
- strings.Contains — tu primer amigo \n
- strings.Index — cuando necesitas más que sí/no \n
- ContainsAny y ContainsRune — ajuste fino \n
- Rendimiento: ¿qué elegir? \n
- Casos extremos y trampas \n
- Resumen: qué recordar \n
1. strings.Contains — tu primer amigo
\n\nEmpecemos con la forma más simple y popular. La función strings.Contains de la biblioteca estándar de Go es lo que necesitas en el 80% de los casos. Devuelve true si la cadena contiene la subcadena especificada y false en caso contrario.
Así se ve en código real:
\n\npackage main\n\nimport (\n "fmt"\n "strings"\n)\n\nfunc main() {\n str := "¡Hola, mundo Go!"\n substr := "mundo"\n\n if strings.Contains(str, substr) {\n fmt.Println("La cadena contiene la subcadena:", substr)\n } else {\n fmt.Println("Subcadena no encontrada")\n }\n}\n\n\nSalida:
\nLa cadena contiene la subcadena: mundo\n\nSimple, ¿verdad? La función distingue entre mayúsculas y minúsculas: strings.Contains("Go", "go") devolverá false. Si necesitas ignorar mayúsculas/minúsculas, tendrás que usar strings.ToLower o strings.EqualFold.
Ejemplo ignorando mayúsculas/minúsculas
\n\nstr := "Golang is awesome"\nsubstr := "golang"\n\nif strings.Contains(strings.ToLower(str), strings.ToLower(substr)) {\n fmt.Println("¡Encontrado (sin distinción de mayúsculas)!)\n}\n\n\nCuándo usarlo: cuando necesitas una respuesta binaria simple: «sí» o «no». Sin detalles adicionales.
\n\n2. strings.Index — cuando necesitas saber la posición
\n\nA veces no basta con saber que la subcadena existe. Quieres entender dónde está exactamente. Por ejemplo, estás analizando un registro y quieres extraer una parte de la cadena después de un marcador específico. Aquí es donde strings.Index viene al rescate.
Esta función devuelve el índice de la primera aparición de la subcadena (comenzando en 0) o -1 si la subcadena no se encuentra.
\n\nstr := "Error: archivo no encontrado en /var/log"\nsubstr := "archivo"\n\npos := strings.Index(str, substr)\nif pos != -1 {\n fmt.Printf("Subcadena encontrada en la posición %d\\n", pos)\n fmt.Printf("Contexto: %s\\n", str[pos:])\n} else {\n fmt.Println("Subcadena no encontrada")\n}\n\n\nSalida:
\nSubcadena encontrada en la posición 7\nContexto: archivo no encontrado en /var/log\n\nHay otras variaciones:
\n- \n
strings.LastIndex— busca la última aparición \n strings.IndexAny— busca la primera aparición de cualquier carácter de un conjunto \n strings.IndexRune— busca un carácter Unicode específico \n
// Ejemplo con LastIndex\nstr := "a-b-c-d"\nlastDash := strings.LastIndex(str, "-")\nfmt.Println("Último guion en la posición:", lastDash) // 5\n\n// Ejemplo con IndexAny\nstr2 := "hello world"\npos := strings.IndexAny(str2, "aeiou")\nfmt.Println("Primera vocal en la posición:", pos) // 1\n\n\nCuándo usarlo: cuando necesitas la posición de la subcadena para extraer, reemplazar o analizar más a fondo.
\n\n3. ContainsAny y ContainsRune — ajuste fino
\n\n¿Qué pasa si no necesitas buscar una subcadena completa, sino cualquier carácter de un conjunto? Por ejemplo, verificar si una cadena contiene algún dígito o algún signo de puntuación. Aquí es donde brillan strings.ContainsAny y strings.ContainsRune.
strings.ContainsAny devuelve true si la cadena contiene al menos uno de los caracteres Unicode especificados en el segundo argumento.
package main\n\nimport (\n "fmt"\n "strings"\n)\n\nfunc main() {\n texto := "¡Hola, mundo 2024!"\n\n // ¿Contiene algún dígito?\n if strings.ContainsAny(texto, "0123456789") {\n fmt.Println("El texto contiene dígitos")\n }\n\n // ¿Contiene algún signo de puntuación?\n if strings.ContainsAny(texto, ".,!?;:") {\n fmt.Println("El texto contiene signos de puntuación")\n }\n}\n\n\nSalida:
\nEl texto contiene dígitos\nEl texto contiene signos de puntuación\n\nstrings.ContainsRune es aún más específico: verifica si la cadena contiene un rune (carácter Unicode) particular.
texto := "Café"\n\nif strings.ContainsRune(texto, 'é') {\n fmt.Println("El texto contiene el carácter 'é'")\n}\n\n\nCuándo usarlos: cuando necesitas verificar la presencia de cualquier carácter de un conjunto, no una subcadena completa.
\n\n4. Rendimiento: ¿qué elegir?
\n\nAhora que conoces todas las herramientas, surge la pregunta: ¿cuál es más rápida? Realicé pruebas simples con cadenas de diferentes longitudes y aquí están los resultados.
\n\nPara la mayoría de los casos de uso cotidiano (cadenas de hasta unos pocos kilobytes), la diferencia entre strings.Contains y strings.Index es insignificante. Ambas funciones están altamente optimizadas y utilizan el algoritmo de búsqueda de cadenas de Rabin-Karp internamente.
Sin embargo, hay matices:
\n- \n
- strings.Contains es ligeramente más rápido cuando solo necesitas un resultado booleano, porque no calcula la posición exacta. \n
- strings.Index es la mejor opción si de todos modos necesitas la posición; no tiene sentido llamar a Contains y luego a Index. \n
- ContainsAny puede ser más lento en cadenas largas si el conjunto de caracteres es grande, pero para la mayoría de los casos es suficiente. \n
Regla general: no te preocupes por la optimización prematura. Elige la función que exprese mejor tu intención. El compilador y el recolector de basura de Go harán el resto.
\n\n5. Casos extremos y trampas
\n\nIncluso las funciones simples tienen sus peculiaridades. Aquí hay algunos casos que debes tener en cuenta:
\n\nCadena vacía
\nUna cadena vacía siempre está contenida en cualquier cadena:
\n\nfmt.Println(strings.Contains("abc", "")) // true\nfmt.Println(strings.Index("abc", "")) // 0 (principio de la cadena)\n\nEsto puede ser inesperado si no lo sabes. Siempre verifica si la subcadena no está vacía si eso es importante para tu lógica.
\n\nUnicode y multibyte
\nGo maneja UTF-8 de forma nativa, pero recuerda que strings.Index devuelve el índice en bytes, no en caracteres (runas). Si trabajas con texto que contiene caracteres multibyte (por ejemplo, emojis o caracteres cirílicos), usa utf8.RuneCountInString para contar runas.
str := "Привет, мир!"\npos := strings.Index(str, "мир")\nfmt.Println("Índice en bytes:", pos) // 8 (no 8 caracteres)\n\n// Para obtener el índice en runas:\nimport "unicode/utf8"\nrunas := utf8.RuneCountInString(str[:pos])\nfmt.Println("Índice en runas:", runas) // 8\n\nDistinción de mayúsculas y minúsculas
\nYa lo mencionamos, pero vale la pena repetirlo: todas las funciones estándar distinguen entre mayúsculas y minúsculas. Para búsquedas sin distinción, usa strings.ToLower o strings.EqualFold.
6. Resumen: qué recordar
\n\nHemos recorrido un largo camino. Aquí están los puntos clave:
\n\n- \n
- strings.Contains — tu opción predeterminada para verificar la presencia de una subcadena. Simple y clara. \n
- strings.Index — cuando necesitas la posición de la subcadena. Útil para extraer o reemplazar partes de una cadena. \n
- strings.ContainsAny y ContainsRune — para verificar la presencia de cualquier carácter de un conjunto. \n
- No olvides los casos extremos: cadena vacía, Unicode y distinción de mayúsculas/minúsculas. \n
- Elige la función que exprese mejor tu intención, no la más rápida. La legibilidad del código es más importante que la microoptimización. \n
Ahora estás listo para manejar cualquier tarea de búsqueda de subcadenas en Go. ¡Escribe código con confianza!