La gestione delle stringhe non è certo uno dei punti di forza del linguaggio C; questo non solo perchè si tratta di lavorare con dei puntatori ma anche perchè la libreria standard non offre molte funzionalità per il loro trattamento. Di conseguenza il programmatore deve rimboccarsi le maniche e costruirsi piano piano la propria libreria di funzioni. In questo articolo vediamo un’implementazione in linguaggio C della funzione String.Replace() di C#.

testo 540

PROTOTIPO

Il metodo Replace() della classe String di C# prevede due parametri: la stringa da ricercare e la stringa da inserire. Oltre a questi due parametri, nella nostra versione in C dobbiamo aggiungere la stringa su cui lavorare (in C# è implicita) e sulla quale andremo a fare direttamente le modifiche.

Il prototipo della funzione che vogliamo creare dovrebbe essere questo:

int StrReplace(char *str, int maxstr, char *pattern, char *replace);

dove str è la stringa originaria che verrà modificata, maxstr la sua dimensione allocata, pattern la stringa da ricercare e replace la stringa da usare in sostituzione.

Il valore di ritorno della nostra funzione sarà un intero che ci indicherà il numero di sostituzioni eseguite oppure -1 in caso di errore.

CONSIDERAZIONI

A questo punto sono necessari alcuni chiarimenti.

Innanzitutto il valore di ritorno della versione C# è una nuova stringa mentre nella nostra versione in C sarà un intero; la funzione modificherà direttamente la stringa passata. Si tratta di una scelta fatta per evitare di dover gestire l’allocazione di una nuova stringa, con i relativi possibili errori, e quindi rendere l’interfacciamento più semplice, soprattutto per il chiamante.

Un altro punto da considerare è che per modificare una stringa C dobbiamo controllare che questa sia allocata ad una dimensione sufficiente per contenere la sostituzione; per questo abbiamo aggiunto il parametro maxstr e abbiamo previsto di ritornare l'errore -1 nel caso la sostituzione non sia possibile.

Per ultimo, la versione C# sostituisce tutte le occorrenze mentre noi scriveremo prima una funzione che sostituisce solo la prima occorrenza e poi passeremo ad una funzione che le sostituisce tutte.

SOSTITUZIONE DELLA PRIMA OCCORRENZA

Di seguito l’implementazione della prima versione, ovvero quella che sostituisce solo la prima occorrenza:

int StrReplaceSingle(char *str, int maxstr, char *pattern, char *replace)
{
         int i, j;
         int lenstr = (int)strlen(str);
         int lenpattern = (int)strlen(pattern);
         int lenreplace = (int)strlen(replace);
         int diff;

         if ((lenstr - lenpattern + lenreplace) >= maxstr)
                   return -1;        // not enough space

         for (i = 0; i <= (lenstr - lenpattern); i++)
         {
                   if (strncmp(&str[i], pattern, lenpattern) == 0)    // if found
                   {
                            if (lenpattern == lenreplace)
                            {
                                      strncpy(&str[i], replace, lenreplace);
                                      return 1;
                            }
                            else if (lenpattern > lenreplace)
                            {
                                      diff = lenpattern - lenreplace;
                                      strncpy(&str[i], replace, lenreplace);
                                      // compact
                                      j = i + lenreplace;
                                      for (; j<lenstr - diff; j++)
                                               str[j] = str[j + diff];
                                      str[j] = '\0';
                                      return 1;
                            }
                            else // lensearch < lensubst
                            {
                                      diff = lenreplace - lenpattern;
                                      // expand
                                      j = lenstr - lenpattern + lenreplace;
                                      str[j] = '\0';
                                      for (; j > i + lenpattern; j--)
                                               str[j] = str[j - diff];
                                      strncpy(&str[i], replace, lenreplace);
                                      return 1;
                            }
                   }
         }
         return 0;
}

Da notare all’inizio il controllo che str sia allocato per poter contenere un eventuale sostituzione: nel caso non ci sia sufficiente spazio usciamo subito restituendo -1.

Successivamente inizia un ciclo per l’analisi della stringa str: la strncmp() ci permette di individuare l’eventuale inizio della stringa pattern all’interno di str.

Se troviamo la stringa pattern all’interno di str dobbiamo comportarci diversamente a seconda di queste 3 situazioni:

  1. la lunghezza della stringa pattern e la lunghezza della stringa replace coincidono: in questo caso la sostituzione è semplice, basta copiare replace nella posizione trovata in str; utilizziamo strncpy() per copiare esattamente il numero di caratteri necessari, evitando così di inserire un terminatore di stringa;
  2. la lunghezza della stringa pattern è maggiore della lunghezza della stringa replace: in questo caso possiamo copiare la stringa replace e successivamente dobbiamo “compattare” il resto della stringa;
  3. la lunghezza della stringa pattern è minore della lunghezza della stringa replace: in questo caso prima di copiare la stringa replace dobbiamo espandere il resto della stringa.

L’utilizzo di è piuttosto semplice, ad esempio:

char miastringa[80];
strcpy(miastringa, "Quel ramo del lago di Como");
StrReplaceSingle(miastringa, 80, "Como", "Garda");
printf(miastringa);

stamperà:

Quel ramo del lago di Garda

Sfruttando il valore di ritorno, già con questa funzione è possibile sostituire tutte le occorrenze e non solo la prima; è sufficiente richiamare la routine fintantochè ritorna 1, ovvero fintantochè ha eseguito una sostituzione:

char miastringa[80];
strcpy(miastringa, "Quel ramo del lago di Como a Como");
while(StrReplaceSingle(miastringa, 80, "Como", "Garda") == 1);
printf(miastringa);

Stamperà

Quel ramo del lago di Garda a Garda

SOSTITUZIONE DI TUTTE LE OCCORRENZE

Di seguito l’implementazione della versione potenziata, ovvero quella che sostituisce tutte le occorrenze trovate:

int StrReplace(char *str, int maxstr, char *pattern, char *replace, int maxreplace)
{
         int i, j;        
int count = 0;
         int lenstr = (int)strlen(str);
         int lenpattern = (int)strlen(pattern);
         int lenreplace = (int)strlen(replace);
         int diff;

         for (i = 0; i <= (lenstr - lenpattern); i++)
         {
                   if (strncmp(&str[i], pattern, lenpattern) == 0)    // if found
                   {
                            if ((lenstr - lenpattern + lenreplace) >= maxstr)
                                      return -1;        // not enough space
                            if (lenpattern == lenreplace)
                            {
                                      strncpy(&str[i], replace, lenreplace);
                            }
                            else if (lenpattern > lenreplace)
                            {
                                      diff = lenpattern - lenreplace;
                                      strncpy(&str[i], replace, lenreplace);
                                      // compact
                                      j = i + lenreplace;
                                      for (; j<lenstr - diff; j++)
                                               str[j] = str[j + diff];
                                      str[j] = '\0';
                            }
                            else // lensearch < lensubst
                            {
                                      diff = lenreplace - lenpattern;
                                      // expand
                                      j = lenstr - lenpattern + lenreplace;
                                      str[j] = '\0';
                                      for (; j > i + lenpattern; j--)
                                               str[j] = str[j - diff];
                                      strncpy(&str[i], replace, lenreplace);
                            }
                            count++;
                            if (maxreplace > 0)
                            {
                                      if (count >= maxreplace)
                                               break;
                            }
                            // update string length and position
                            lenstr += lenreplace - lenpattern;
                            i += (lenreplace - 1);
                   }
         }
         return count;
}

In pratica, il controllo di spazio disponibile è stato spostato all’interno del ciclo e dopo la sostituzione non si esce ma si continua, aggiornando opportunamente la posizione all’interno della stringa str e lunghezza della stessa.

Da notare che è stato aggiunto il parametro maxreplace che se settato maggiore di 0 permette di indicare un numero massimo di sostituzioni accettabili.

CONCLUSIONI

E' vero che il linguaggio C fornisce poche funzioni di base ma è altrettanto vero che con un po' di tempo ci si può costruire una libreria per lo svolgimento dei vari compiti, secondo le proprie necessità. In questo articolo abbiamo visto come implementare una sostituzione di stringhe mentre in un altro articolo (Trim di stringhe in C) avevamo visto come implementare un trim, ovvero la rimozione degli spazi; magari in futuro vi proporrò qualche altra funzione, se avete qualche preferenza fatemelo sapere dalla pagina dei contatti e... buon divertimento!!!