Strings en profundidad
Inmutabilidad, concatenación eficiente, slicing y diferencias clave entre Python y JavaScript.
5 min de lectura
Los strings parecen arreglos de caracteres y en muchos lenguajes se comportan parecido. Pero hay una diferencia crítica que cambia cómo escribes código sobre ellos: en Python, JavaScript, Java y C# los strings son inmutables.
Qué significa inmutable
Una vez que creas un string, no puedes modificarlo. Las operaciones que parecen modificarlo en realidad crean un string nuevo.
s = "hola"
# s[0] = "H" → TypeError
nuevo = "H" + s[1:] # crea string nuevo "Hola"
let s = "hola";
// s[0] = "H"; → no falla pero no hace nada
const nuevo = "H" + s.slice(1); // crea string nuevo "Hola"
Cualquier operación de "modificar" implica copiar todo el contenido a una zona de memoria nueva.
Por qué importa: el bug del loop cuadrático
El error más común con strings: concatenar dentro de un loop.
# MAL: O(n²)
resultado = ""
for palabra in lista_de_palabras:
resultado += palabra
Cada += no extiende el string viejo. Crea uno nuevo de tamaño len(resultado) + len(palabra) y copia todos los caracteres. Si tienes n palabras de longitud constante, el costo total es 1 + 2 + 3 + ... + n ≈ n²/2.
Para 10.000 palabras, son 50 millones de copias de caracteres. Para 100.000, son 5 mil millones. Tu programa empieza a tardar minutos cuando debería tardar segundos.
La forma correcta en Python
resultado = "".join(lista_de_palabras)
join calcula la longitud total una vez, reserva ese espacio, y copia todo en una pasada. O(n).
La forma correcta en JavaScript
const resultado = lista.join("");
Mismo principio. O(n).
O usar un buffer/array temporal
En ambos lenguajes puedes acumular en una lista/array y unir al final:
partes = []
for palabra in lista_de_palabras:
partes.append(palabra)
resultado = "".join(partes)
const partes = [];
for (const palabra of lista) {
partes.push(palabra);
}
const resultado = partes.join("");
Append a lista es O(1) amortizado. Join al final es O(n). Total: O(n).
Slicing en Python
Python tiene slicing nativo poderoso:
s = "hola mundo"
sub = s[0:4] # "hola"
sub = s[5:] # "mundo"
sub = s[::-1] # "odnum aloh" (revertido)
sub = s[::2] # "hl ud" (cada 2)
Costo: O(j - i) donde i y j son los índices del slice. No es O(1).
s[::-1] para revertir es O(n) y crea un string nuevo. Está bien para casos chicos. Para casos grandes, considerar revertir un arreglo de caracteres.
Slicing en JavaScript
const s = "hola mundo";
const sub1 = s.slice(0, 4); // "hola"
const sub2 = s.slice(5); // "mundo"
const sub3 = s.substring(0, 4); // "hola" (similar pero trata negativos distinto)
slice acepta índices negativos (cuentan desde el final). substring no.
Para revertir un string en JS no hay sintaxis nativa. La forma idiomática:
const reverso = s.split("").reverse().join("");
Tres operaciones, todas O(n). Es feo pero funciona.
Comparación de strings
En Python y JS, == compara contenido, no referencia:
"hola" == "hola" # True
"hola" === "hola" // true
La comparación es O(n) en el peor caso (recorrer ambos strings hasta encontrar diferencia).
Comparación lexicográfica
Tanto Python como JS comparan strings por orden Unicode de caracteres:
"abc" < "abd" # True
"abc" < "abcd" # True (más corto si es prefijo)
"abc" < "abC" # False, "C" (67) < "c" (99) en Unicode
Para ordenamiento case-insensitive en JS, usar localeCompare:
"Abc".localeCompare("abd", undefined, { sensitivity: "base" });
Operaciones costosas a recordar
| Operación | Python | JS | Costo |
|---|---|---|---|
| Largo | len(s) | s.length | O(1) |
| Acceder un carácter | s[i] | s[i] o s.charAt(i) | O(1) |
| Concatenar | s1 + s2 | s1 + s2 | O(n + m) |
| Subcadena | s[i:j] | s.slice(i, j) | O(j - i) |
| Buscar carácter | s.index(c) | s.indexOf(c) | O(n) |
| Buscar substring | s.find(sub) | s.includes(sub) | O(n × m) ingenuo |
| Reemplazar | s.replace(a, b) | s.replace(a, b) | O(n) |
| Lower/Upper | s.lower() | s.toLowerCase() | O(n) |
| Split | s.split(sep) | s.split(sep) | O(n) |
Trucos específicos del lenguaje
Python: f-strings y comparación rápida
nombre = "Martin"
saludo = f"Hola {nombre}" # más rápido que "Hola " + nombre
in para buscar substring:
"mundo" in "hola mundo" # True, O(n × m) ingenuo
JavaScript: template literals
const nombre = "Martin";
const saludo = `Hola ${nombre}`;
includes para buscar substring:
"hola mundo".includes("mundo"); // true
La regla para strings en problemas DSA
- Si necesitas modificar el string repetidamente, convertilo a arreglo de caracteres (
list(s)en Python,s.split("")en JS). - Si solo necesitas leer, trabajá con índices y slicing solo cuando importa.
- Nunca concatenes en un loop. Usa
joino un buffer. - Asume que cualquier operación de búsqueda o split es O(n) salvo que el lenguaje garantice lo contrario.
Inicia sesión para guardar el progreso de esta lección.