Normaliza una cadena in situ en C con dos punteros, colapsando espacios y contando palabras: ejercicio de examen resuelto y explicado paso a paso.
Actualizado el
Código de Examen C Fundamentos de programación Nivel examen Código probado

Escribe un programa en C que lea una línea de la entrada estándar (como máximo 200 caracteres, sin contar el salto de línea) y la normalice in situ, es decir, modificando el propio vector de caracteres sin usar ningún vector auxiliar.
Normalizar significa que, en el resultado, las palabras (secuencias de caracteres distintos del espacio) quedan separadas por exactamente un espacio y no hay espacios ni al principio ni al final. En la línea solo se considera separador el carácter espacio.
Debes implementar la función int normaliza(char *s), que modifica la cadena s y devuelve el número de palabras que contiene. Dentro de la función está prohibido indexar con corchetes: hay que recorrer la cadena con punteros, uno de lectura y otro de escritura.
El programa principal lee la línea con fgets, elimina el salto de línea final si existe, llama a normaliza e imprime dos líneas: primero la cadena resultante entre corchetes, con el formato [texto], y después el número de palabras. Si la línea no contiene palabras, se imprime [] y 0. Si la entrada está vacía (ni siquiera hay una línea), se trata como una línea vacía.
Entrada de ejemplo
hola mundo Salida esperada
[hola mundo]
2- Firma correcta de la función y uso de punteros (sin índices de vector para recorrer la cadena)
- 0,5
- Lógica de lector y escritor: colapsa espacios internos y elimina los de los extremos
- 1,0
- Terminador nulo escrito en la posición correcta y retorno del número de palabras
- 0,5
- Programa principal: lectura segura de la línea, eliminación del salto de línea y formato de salida
- 0,5
Pistas
Pista 1 · ¿Cuántos punteros necesitas y qué hace cada uno?
La cadena se puede reescribir sobre sí misma porque el resultado nunca es más largo que el original. Usa un puntero r que lee todos los caracteres y un puntero w que marca dónde se escribe el siguiente carácter válido. Siempre se cumple w <= r, así que nunca pisas un carácter que aún no has leído.
Pista 2 · ¿Cómo decides cuándo escribir un espacio separador?
No copies los espacios del original. Guarda una bandera que indique si el carácter anterior pertenecía a una palabra. Cuando encuentres el primer carácter no espacio de una palabra nueva, escribe un espacio antes de la palabra solo si ya has escrito algo (es decir, si w no está al principio de la cadena). Así no aparecen espacios al principio ni al final.
Pista 3 · ¿Qué ocurre con el final de la cadena?
Al terminar el bucle el texto normalizado puede ser más corto que el original, pero el vector conserva los caracteres antiguos a partir de la nueva longitud. Escribe '\0' en la posición a la que apunta w. Comprueba mentalmente el caso de una línea de solo espacios: w no se ha movido y la cadena debe quedar vacía.
Solución
Solución explicada
La solución usa la técnica de los dos punteros: un lector que avanza por todo el vector y un escritor que solo avanza cuando hay un carácter que conservar. Es un patrón muy habitual en exámenes de cadenas porque obliga a razonar sobre el terminador nulo y sobre aritmética de punteros.
El número de palabras se cuenta de paso: cada vez que empieza una palabra nueva (primer carácter no espacio tras un espacio o tras el inicio) se incrementa un contador.
#include <stdio.h>
#include <string.h>
#define MAX 201
/* Normaliza s in situ y devuelve el numero de palabras. */
int normaliza(char *s) {
char *r = s; /* puntero de lectura */
char *w = s; /* puntero de escritura */
int enPalabra = 0;
int palabras = 0;
while (*r != '\0') {
if (*r != ' ') {
if (!enPalabra) {
if (w != s) {
*w = ' ';
w++;
}
palabras++;
enPalabra = 1;
}
*w = *r;
w++;
} else {
enPalabra = 0;
}
r++;
}
*w = '\0';
return palabras;
}
int main(void) {
char linea[MAX];
if (fgets(linea, sizeof linea, stdin) == NULL) {
linea[0] = '\0';
}
linea[strcspn(linea, "\n")] = '\0';
int n = normaliza(linea);
printf("[%s]\n%d\n", linea, n);
return 0;
}En normaliza, r y w empiezan ambos en s. El bucle while (*r != '\0') recorre el original completo. Cuando *r no es un espacio y la bandera enPalabra vale 0, estamos ante el primer carácter de una palabra nueva: incrementamos palabras, activamos la bandera y, si w != s, escribimos antes un espacio separador. La condición w != s es la que evita el espacio inicial: mientras no se haya escrito nada, w sigue apuntando al principio.
Después se copia el carácter con *w = *r; w++;. Si *r es un espacio, solo se baja la bandera y no se escribe nada, de modo que cualquier racha de espacios, larga o corta, desaparece. Los espacios finales tampoco dejan rastro, porque el separador se escribe siempre antes de la palabra siguiente y nunca después de la última.
El invariante clave es w <= r: por cada carácter leído se escribe como máximo uno, y el escritor puede adelantarse al lector solo si se escribiera más de lo leído, algo imposible. Por eso podemos reescribir sobre el mismo vector sin perder datos. Tras el bucle, *w = '\0' cierra la cadena; sin esa línea quedarían restos del texto original.
En main, fgets limita la lectura a MAX bytes y strcspn(linea, "\n") devuelve la posición del salto de línea (o la longitud si no hay), con lo que se elimina sin riesgo. Si fgets devuelve NULL (entrada completamente vacía) se inicializa la cadena vacía, lo que cubre el caso del test de entrada vacía.
Casos de prueba: el ejemplo del enunciado ( hola mundo ) comprueba espacios iniciales, internos y finales a la vez y da dos palabras. El test de una palabra sin espacios comprueba que no se altera nada. La línea de solo espacios y la línea vacía devuelven [] y 0: en ambos w nunca avanza y el terminador queda en la posición 0. Los demás tests mezclan rachas de espacios de distinta longitud entre cuatro y tres palabras.
Traza paso a paso · generada ejecutando el código
| Paso | r | w | enPalabra | palabras |
|---|---|---|---|---|
| 1 | 0 | 0 | 0 | 0 |
| 2 | 1 | 0 | 0 | 0 |
| 3 | 2 | 1 | 1 | 1 |
| 4 | 3 | 2 | 1 | 1 |
| 5 | 4 | 3 | 1 | 1 |
| 6 | 5 | 4 | 1 | 1 |
| 7 | 6 | 4 | 0 | 1 |
| 8 | 7 | 4 | 0 | 1 |
| 9 | 8 | 4 | 0 | 1 |
| 10 | 9 | 6 | 1 | 2 |
| 11 | 10 | 7 | 1 | 2 |
| 12 | 11 | 8 | 1 | 2 |
| 13 | 12 | 9 | 1 | 2 |
| 14 | 13 | 10 | 1 | 2 |
| 15 | 14 | 10 | 0 | 2 |
| 16 | 15 | 10 | 0 | 2 |
Casos de prueba
| Caso | Entrada | Salida esperada | Salida real | Resultado |
|---|---|---|---|---|
| Ejemplo: espacios al inicio, dentro y al final | hola mundo | [hola mundo]
2 | [hola mundo]
2 | OK |
| Una sola palabra sin espacios | uno | [uno]
1 | [uno]
1 | OK |
| Línea de solo espacios | | []
0 | []
0 | OK |
| Entrada vacía | (vacía) | []
0 | []
0 | OK |
| Rachas de espacios de distinta longitud | a b c d | [a b c d]
4 | [a b c d]
4 | OK |
| Tres palabras con espacios iniciales | un solo tabulador | [un solo tabulador]
3 | [un solo tabulador]
3 | OK |
Salidas reales: código compilado con gcc 14.4.0 (C17) y ejecutado en un contenedor aislado el 22 de octubre de 2026.
Complejidad
El tiempo es O(n), con n la longitud de la línea: el puntero r recorre cada carácter exactamente una vez y cada iteración hace un número constante de operaciones. No hay bucles anidados ni llamadas a strlen dentro del bucle.
La memoria adicional es O(1): solo se usan dos punteros y dos enteros, además del propio vector. Eso es lo que significa trabajar in situ, y es la razón de que el enunciado prohíba un vector auxiliar.
Errores típicos
- Olvidar escribir
'\0'al final (*w = '\0'). La cadena conserva los caracteres antiguos a partir de la nueva longitud. Por ejemplo, conhola mundo(12 caracteres) el resultado ocupa 10 posiciones y las posiciones 10 y 11 siguen valiendo'd'y'o', de modo que se imprimiríahola mundodo. - Escribir el espacio separador después de cada palabra en lugar de antes de la siguiente: deja un espacio sobrante al final o exige un parche posterior que casi siempre falla en el caso de la línea vacía.
- Usar un único puntero y desplazar el resto de la cadena con
memmoveostrcpycada vez que se encuentra un espacio repetido: funciona, pero cuesta O(n²) ystrcpycon regiones solapadas es comportamiento indefinido. - No quitar el
'\n'que dejafgets: como el salto de línea no es un espacio, se trata como parte de una palabra y se imprime]en la línea siguiente. - Declarar el vector con tamaño exacto (200) sin dejar sitio para el
'\0', o leer congets/scanf("%s"), que además se detiene en el primer espacio y no lee la línea completa.
Variantes
Contar palabras sin modificar la cadena
Si basta con contar, no hace falta escritor: se recorre con un único puntero const char * y se incrementa el contador cada vez que un carácter no espacio sigue a un espacio o al inicio. Es la misma bandera enPalabra sin las escrituras. Se puede declarar el parámetro como const char *s, lo que el compilador comprueba.
Invertir el orden de las palabras
Tras normalizar, invierte la cadena completa con dos punteros que se cruzan (uno al inicio y otro al final, intercambiando caracteres) y después invierte cada palabra por separado. Como la cadena ya está normalizada, cada palabra queda delimitada por un espacio o por el terminador, lo que simplifica la búsqueda de sus extremos.
Considerar también tabuladores como separadores
Basta con sustituir la comparación *r != ' ' por *r != ' ' && *r != '\t', o mejor por !isspace((unsigned char)*r) con <ctype.h>. El separador que se escribe sigue siendo un único espacio.
En los temarios
Los punteros y su relación con los vectores forman parte del temario de Algoritmos y Programación I (95.11) de la Universidad de Buenos Aires, cuya Unidad III trata arrays, arrays y punteros, inicialización de arrays y operadores de punteros. El ejercicio de esta página practica justamente esa frontera: recorrer un vector de caracteres con aritmética de punteros.
En CC3301 Programación de Software de Sistemas de la Universidad de Chile el programa incluye la escritura de programas en C de sistemas, donde el manejo de memoria y de punteros es la base de todo lo demás.
Historial de cambios
- Reescrito con la plantilla de Código de Examen: enunciado de examen, pistas, solución explicada, casos de prueba y código compilado y ejecutado; versión en inglés enlazada.
- Publicación original.
Artículo generado mediante AI.larebelion · Revisado por Kernel


