Función para reemplazar string en C

VipeR_CS

Holaquetal,

Después de meses sin tocar nada de C (tampoco es que supiera mucho por aquel entonces) y en un momento de aburrimiento, me he puesto a mirar algún ejemplo que tenía por ahí y, particularmente, una función para reemplazar strings en una cadena de texto. Recuerdo que había tenido algún problema y acabé cogiendo parte de código de algún sitio en internet, pero ahora al probarlo veo que sigue sin funcionar como debería y no sé qué coño pasa entre otras cosas porque no entiendo un par de lineas del código. Así que tras esta introducción que seguro que a nadie le interesaba, el Quid de la cuestión es:

¿Qué coño le falla a este código...

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *replace_str(char *str, char *orig, char *rep)
{
  static char buffer[4096];
  char *p;

  if(!(p = strstr(str, orig)))  // Is 'orig' even in 'str'?
    return str;

  strncpy(buffer, str, p-str); // Copy characters from 'str' start to 'orig' st$
  buffer[p-str] = '\0';

  sprintf(buffer+(p-str), "%s%s", rep, p+strlen(orig));

  return replace_str(buffer, orig, rep);
}

int main(void)
{
  puts(replace_str("Hello, world world!", "world", "holaquetal"));

  system("pause");
  return 0;
}

... para que el resultado sea: "Hello, holaquetal holaquetaluetal"?

Y por otro lado, si alguien es tan amable de explicarme qué coño hacen las líneas 14 y 16, Dios se lo pagará con fertilidad eterna. Gracias, besitos.

B

Así sin probar el código.. strstr devuelve un puntero al substring que buscas y luego se sustituye. Eso solo se está realizando una vez. Si no hay nada que esté mal el resultado que tendrías sería "Hello, holaquetal world!". Si quisieses que sustituyese varias veces sería tan simple como hacer un bucle con strstr como condición y si devuelve null terminas.

En la linea 13 se copian como máximo p-str caracteres al buffer. Si no se ha llegado a copiar el caracter nulo que termina el string, para asegurarse, se copia en la linea 14. En la linea 16 lo que hace es imprimir a la posición en que se había quedado en el buffer un string, el primer %s con el valor del substring que estabas sustituyendo y a continuación, el segundo %s, el resto del contenido del string original donde se había realizado la búsqueda. Si no te aclaras con las operaciones de buffer, p y str echale un ojo a la aritmética de punteros.

1 respuesta
VipeR_CS

#2 Gracias por la explicación del par de lineas esas. La verdad que la aritmética de punteros voy a tener que refrescarla pero más o menos me he enterado con lo que has comentado xD

Respecto a tu primer párrafo, si te fijas la función replace_str tiene una llamada recursiva y cuando no encuentre la string a reemplazar retorna la cadena tal cual rompiendo la recursividad.

El problema es que la primera vez lo hace bien, pero la segunda ocurrencia (y supongo que las sucesivas irá poniéndose aún peor) ya no sé qué mierda hace (seguro que algún problema con las operaciones en los punteros) que el resultado es "Hello, holaquetal holaquetaluetal". La parte en negrita sobraría xD

1 respuesta
B

#3 No lo sé seguro pero supongo que tiene algo que ver el buffer esté declarado como static. De esta forma no se destruye cuando cambia el marco de pila y se puede mantener un puntero, lo que ocurre es que buffer en todas las llamadas a la función, es la misma variable, es como si estuviese declarada global. Si te fijas e imprimes lo que hay en el buffer cada vez que se ejecuta la función verás que el uetal empieza en la misma posición del ! que hay después del world. La solución sería hacerlo con memoria dinámica para que no se machacase y fuesen buffers completamente diferentes en cada ejecución. El que hizo eso creo que no entendía muy bien para que se usa el static.

Yo que tú no me fiaría de lo que encuentre por muchos sitios de internet xd

Edit: El fallo básicamente es reutilizar el buffer como origen y como destino. En la primera ejecución lo coge de orig y por eso va, en la segunda se queda en la posición que le correspondería empezar a copiar, hace el sprintf pero luego la parte que tiene que copiar la acaba de sobreescribir.

LOc0

El problema está en el sprintf cuando el trozo de reemplazo es mayor que el trozo original.

Una posible solución:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *replace_str(char *str, char *orig, char *rep)
{
  static char buffer[4096];
  char *p;

  if(!(p = strstr(str, orig)))  // Is 'orig' even in 'str'?
    return str;

  strncpy(buffer, str, p-str); // Copy characters from 'str' start to 'orig' st$
  //buffer[p-str] = '\0'; NO HACE FALTA
  
char aux[strlen(p+strlen(orig))+1]; strcpy(aux, p+strlen(orig)); //Lo guardamos para que sprintf no lo machaque si rep es más largo que orig sprintf(buffer+(p-str), "%s%s", rep, aux);
return replace_str(buffer, orig, rep); } int main(void) { puts(replace_str("Hello, world world!", "world", "holaquetal")); return 0; }

La explicación es que sprintf va haciendo las sustituciones de izquierda a derecha según los parámetros que le llegan. Por tanto, si el primer parámetro REP es de longitud mayor que ORIG, estará machacando parte de la cadena original por detrás de orig.

De todos modos, como dice rapsioux lo suyo en este caso es usar malloc() calculando previamente el tamaño de la cadena final con todas las sustituciones.

Salu2 ;)

VipeR_CS

Perfecto, gracias a ambos. Voy a probarlo y a debuggearlo para ver con más claridad como funciona el tema e intentaré modificarlo para hacerlo funcionar con malloc ^^

Edit: sus muertos, era más fácil de lo que parecía xD lo he solucionado así, que seguramente sea una chapucilla de malloc y se pueda optimizar, pero funciona xD

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *replace_str(char *str, char *orig, char *rep)
{
  char *buffer;
  char *p;

  if(!(p = strstr(str, orig)))  // Is 'orig' even in 'str'?
    return str;

  buffer = (char*)malloc(strlen(str)+strlen(rep)-strlen(orig)+1);

  strncpy(buffer, str, p-str); // Copy characters from 'str' start to 'orig' st$
  //buffer[p-str] = '\0'; NO HACE FALTA

  sprintf(buffer+(p-str), "%s%s", rep, p+strlen(orig));

  return replace_str(buffer, orig, rep);
}

int main(void)
{
  puts(replace_str("Hello, world world!", "world", "holaquetal"));

  system("pause");
  return 0;
}
1 respuesta
LOc0

#6
Tienes un memory-leak en cada llamada recursiva. Posible corrección:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *replace_str(char *str, char *orig, char *rep)
{
  char *buffer;
  char *p;

  if(!(p = strstr(str, orig)))  // Is 'orig' even in 'str'?
    return str;

  buffer = (char*)malloc(strlen(str)+strlen(rep)-strlen(orig)+1);

  strncpy(buffer, str, p-str); // Copy characters from 'str' start to 'orig' st$
  //buffer[p-str] = '\0'; NO HACE FALTA

  sprintf(buffer+(p-str), "%s%s", rep, p+strlen(orig));

 if( buffer != (p=replace_str(buffer, orig, rep)) )  
      free(buffer);
  
     return p;
}

int main(void)
{
  puts(replace_str("Hello, world world!", "world", "holaquetal"));

  system("pause");
  return 0;
}

Lo que hace:

if( buffer != (p=replace_str(buffer, orig, rep)) )
free(buffer);

es liberar los buffers de las llamadas recursivas menos el último que contiene la cadena buena con todos los reemplazos.

Salu2 ;)

VipeR_CS

Fixed, gracias de nuevo.

VipeR_CS

Perdón por el doble post pero aprovecho este tema por no abrir otro.

Tengo lo siguiente

char cod_letra;
...
printf("Letra: %c %d \n", cod_letra);
...
while ((cod_letra < 65) || (cod_letra > 82) || (cod_letra != 123) || (cod_letra != 125));

Básicamente, tengo que repetir un DO hasta que la letra esté comprendida entre A-R (mayúsculas) o sean los símbolos de apertura y cierre de corchetes. El printf me muestra bien la letra con %c (la saco con una función casera que me devuelve el último caracter del string que se le pasa, en teoría funciona xD), pero sin embargo el %d me muestra un 0 patatero y el bucle se repite hasta el infinito y más allá.

WTF is going on? :\

PD: Maldita la hora en que me dio por hacer la puta mierda de programa este en C xD Con lo bien que me habría ido en VB.net.

Edit: /facepalm con el parámetro del printf xD Y el while ya funciona correctamente con los &&. Thx #10 <3

LOc0

Te falta un parámetro en el printf y la condición lógica del do-while está en modo "infinito" xD. Mira a ver así:

printf("Letra: %c %d \n", cod_letra, cod_letra);

while ( cod_letra < 65 || ( cod_letra > 82 &&  cod_letra != 123 && cod_letra != 125) );

Salu2 ;)

1 respuesta
VipeR_CS

Esto es de otro programa distinto al de #1, que teóricamente ya está terminado si no llega a ser porque no funciona -.-

Resumiendo el caso es el siguiente: debería coger un código de entrada por teclado y cambiarle un par de cosas según unas reglas que están comentadas al principio del código. Sin embargo el programa peta y no muestra bien el resultado. Tengo la sensación de que el pete es por los malloc/free, pero no sé si también puede ser el motivo de que la salida no sea correcta. Pongo el código completo y a ver si algún iluminado ve algo aparte de los malloc/free xD

spoiler

Este es el error en cuestión y la salida obtenida y la esperada:

Lecherito

Realmente no sé si te he entendido bien, y es un código realmente largo y a mi parecer, bastante ofuscado xD

#include <stdio.h>

int getPosicion(char abc[], char letra);

int main() {
  char abc[] = {'{','a','b','c','d','e','f','g','h','i','}','j','k','l','m','n','o','p','q','r'};
  char str[] = "90}";
  char letra = str[2];
  int posicion = getPosicion(abc,letra);
  int signo = posicion/9; //0=positivo 1=negativo
  int valor = posicion%9;
  printf("Letra: %c\n",letra);
  printf("Posicion: %d\n",posicion);
  printf("Signo: %d\n",signo);
  printf("Valor: %d\n",valor);
  int total = 90*10+valor-signo;
  total = ((signo)?-1:1)*total;
  printf("Resultado: %d\n",total);
}

int getPosicion(char abc[], char letra) {
	int i=0;	
	for (i=0;i<20;i++)
		if (abc[i] == letra) 
			return i;
	return -1;
}

He hecho este, en el que con una matriz de caracteres únicamente, puedes saber el valor, el signo, y todo, lo de la conversión de strings y yo que se que, la verdad es que no tengo mucho tiempo, pero espero haberte dado la pista que te lleve a resolver eso de una manera más sencilla.

PD: int total = 90*10+valor; <---- Si, el 90 me lo he sacado de la manga del string, como ya dije, no tenía mucho tiempo xD

1 respuesta
VipeR_CS

#12 Sí, sé que el código es largo, mi especialidad es complicarme la vida xD. He probado tu código y si bien se acerca no va del todo bien. En algunos casos acierta, en otros el valor es superior en 1 al que debería (el '90}' por ejemplo da como salida -901 y debería ser -900; sin embargo con 10{ muestra correctamente un 100). Seguramente podría apañarlo, pero ya es por cabezonería saber qué coño le pasa al mio xD.

Además que pese a ser más largo, tiene la ventaja (para mí al menos) de estar más modularizado, lo que en el futuro le da mayor facilidad en las modificaciones o expansiones (si cambian las equivalencias simplemente tendría que modificar el array de valores, por ejemplo, frente a prácticamente rehacer el código de la otra forma xD). Se agradece el código igualmente, puedo sacar buenas ideas para el futuro.

1 respuesta
Lecherito

#13 también tienes que tener en cuenta de que no tengo ni puñetera idea de qué es lo que necesitas, y bueno, eso suele afectar a la "modularidad" que tu quieres para tu código.

PD: Se arregla modificando: int total = 90*10+valor-signo;

PD2: He intentado bajarme tu código y entenderlo, pero me es imposible xD

Edit2: Con atoi, puedes meter una string en un entero, así que con eso quedaría todo hecho xD

Usuarios habituales