Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Замена слова в строке на другое

17K
06 декабря 2009 года
kilowatt
27 / / 13.01.2007
Здравствуйте уважаемые форумчане. Пишу на си. И передо мной встала задача: написать функцию, которая бы заменяла слово в строке на другое слово. Целый день сегодня провозился, но ни чего не добился.
функция должна иметь вид:
char *modString(char *string);
то есть функция получает строку, и возвращает строку уже с измененным словом.
пример:
исходная строка: текст всякий ватный текст
обработанная строка: "текст" всякий ватный "текст"
12K
07 декабря 2009 года
Ghox
297 / / 26.07.2009
Подумал я, как это можно сделать на C, вот что получилось. Правда получилось весьма похоже на какое-то быдлокодерство, да и ошибки могут быть (код не отлаживал) - может кто предложит вариант получше.
Код:
#include <stdlib.h>
#include <string.h>

char *str1 = "текст";
char *str2 = "\"текст\"";

char* modString(char *string)
{
    int len, len1, len2, n1, n2, i, j, k;
    int *numpos;
    char *result;
    len = strlen(string);
    len1 = strlen(str1);
    len2 = strlen(str2);
    n1 = 0;
    numpos = (int*)malloc(sizeof(int) * (len / len1 + 1));
    for(i = 0; i <= len - len1; ++i)
    {
        j = 0;
        while(j < len1 && string[i + j] == str1[j])
            ++j;
        if(j == len1)
        {
            numpos[n1++] = i;
            i += len1 - 1;
        }
    }
    if(!n1)
    {
        result = (char*)malloc(sizeof(char) * (len + 1));
        strcpy(result, string);
    }
    else
    {
        result = (char*)malloc(sizeof(char) * (len + (len2 - len1) * n1 + 1));
        for(i = 0, j = 0, n2 = 0; i < len; ++i, ++j)
            if(n2 == n1 || numpos[n2] > i)
                result[j] = string;
            else
            {
                for(k = 0; k < len2; ++k, ++j)
                    result[j] = str2[k];
                i += len1 - 1;
                --j;
                ++n2;
            }
        result[j] = '\0';
    }
    free(numpos);
    return result;
}
87
07 декабря 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Правда получилось весьма похоже на какое-то быдлокодерство


Но ведь интерес представляет не только заставить программу работать, но и избавить её от таких недостатков. Выявление и исправление их - это даже более интересная задача.

С ходу вижу следующее.

1. Непонятно, почему str1 и str2 не являются параметрами modString.
2. Имена этих параметров не говорят, что является строкой для поиска, а что для замены.
3. len, len1, len2, n1, n2 - названия мне не нравятся, они не говорят о себе. Такие переменные годятся только для очень коротких функций, да и то редко.
4. Память на строку выделили, а кто удалять будет? Пользователь? Откуда он знает, что должен? Правда, элегантного решения для C я сразу не могу придумать.

12K
07 декабря 2009 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Но ведь интерес представляет не только заставить программу работать, но и избавить её от таких недостатков. Выявление и исправление их - это даже более интересная задача.

С ходу вижу следующее.


Высказанные (пока) замечания относятся в основном к стилистике оформления программы, либо представляются неустранимыми в силу формулировки задачи. :)

Цитата: Kogrom
1. Непонятно, почему str1 и str2 не являются параметрами modString.


Ну это задача так поставлена:

Цитата: kilowatt
функция должна иметь вид:
char *modString(char *string);
то есть функция получает строку, и возвращает строку уже с измененным словом.
пример:
исходная строка: текст всякий ватный текст
обработанная строка: "текст" всякий ватный "текст"


В представленном автором прототипе функции нет переменных, в которых передавалась бы подстрока, которую в тексте нужно заменить, и подстрока, на которую нужно заменить. Поэтому я и предположил что автор эти строки хочет задавать с помощью глобальных переменных, что и сделал в своем примере. По логике, согласен, правильнее было бы так:

 
Код:
char* modString(char *string, char *str1, char *str2);

но автору по другому нужно...
Цитата: Kogrom
2. Имена этих параметров не говорят, что является строкой для поиска, а что для замены.
3. len, len1, len2, n1, n2 - названия мне не нравятся, они не говорят о себе. Такие переменные годятся только для очень коротких функций, да и то редко.


Это замечания по неймингу уже, я в нем не силен (не силен в том чтобы находить переменным подходящие имена, в смысле - чтобы имя переменной более-менее соответствовало назначению)... Могу попробовать, но не факт что у меня хорошо получится:

 
Код:
str1 - strReplaced
str2 - strReplacedBy
len - originalStringLength
len1 - strReplacedLength
len2 - strReplacedByLength
n1 - posReplacingCount
n2 - posReplacingNumber

И переписанный код тогда выглядит так (сделал также чтобы заменяемая и заменяющая строка передавались как аргументы функции):
Код:
#include <stdlib.h>
#include <string.h>

char* modString(char *string, char *strReplaced, char *strReplacedBy)
{
    int originalStringLength, strReplacedLength, strReplacedByLength;
    int posReplacingCount, posReplacingNumber;
    int i, j, k;
    int *numpos;
    char *result;
    originalStringLength = strlen(string);
    strReplacedLength = strlen(strReplaced);
    strReplacedByLength = strlen(strReplacedBy);
    posReplacingCount = 0;
    numpos = (int*)malloc(sizeof(int) * (originalStringLength / strReplacedLength + 1));
    for(i = 0; i <= originalStringLength - strReplacedLength; ++i)
    {
        j = 0;
        while(j < strReplacedLength && string[i + j] == strReplaced[j])
            ++j;
        if(j == strReplacedLength)
        {
            numpos[posReplacingCount++] = i;
            i += strReplacedLength - 1;
        }
    }
    if(!posReplacingCount)
    {
        result = (char*)malloc(sizeof(char) * (originalStringLength + 1));
        strcpy(result, string);
    }
    else
    {
        result = (char*)malloc(sizeof(char) * (originalStringLength + (strReplacedByLength - strReplacedLength) * posReplacingCount + 1));
        for(i = 0, j = 0, posReplacingNumber = 0; i < originalStringLength; ++i, ++j)
            if(posReplacingNumber == posReplacingCount || numpos[posReplacingNumber] > i)
                result[j] = string;
            else
            {
                for(k = 0; k < strReplacedByLength; ++k, ++j)
                    result[j] = strReplacedBy[k];
                i += strReplacedLength - 1;
                --j;
                ++posReplacingNumber;
            }
        result[j] = '\0';
    }
    free(numpos);
    return result;
}

По-моему, получилось что-то страшное. :D
Цитата: Kogrom
4. Память на строку выделили, а кто удалять будет? Пользователь? Откуда он знает, что должен? Правда, элегантного решения для C я сразу не могу придумать.


Да, пользователь. Если он вызвал эту функцию, присвоив результат выполнения указателю char*, то он в итоге получает строку, память под которую была выделена динамически. И если эта строка ему больше не нужна, то он в main (или в другой функции где использует modString) должен освободить с помощью free.
И варианта решения задачи, в котором соблюдался бы затребованный автором темы прототип функции и при этом не происходило бы выделения динамической памяти, я не вижу...

17K
07 декабря 2009 года
kilowatt
27 / / 13.01.2007
Большое всем спасибо за помощь. Свой вариант представлю в пятницу.
87
07 декабря 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Высказанные (пока) замечания относятся в основном к стилистике оформления программы, либо представляются неустранимыми в силу формулировки задачи.


Как говорит Мартин Фаулер, "любой дурак может написать программу, которую поймет компилятор. Хорошие программисты пишут программы, которые смогут понять другие программисты".

Цитата: Ghox
Ну это задача так поставлена


Задача поставлена заменить слова. Вид функции автор задал по неопытности. Поэтому можно пренебречь этим условием.

Цитата: Ghox
но автору по другому нужно...


Не особо важно, что нужно автору в данном разделе.

Цитата: Ghox
Это замечания по неймингу уже, я в нем не силен (не силен в том чтобы находить переменным подходящие имена, в смысле - чтобы имя переменной более-менее соответствовало назначению)... Могу попробовать, но не факт что у меня хорошо получится:
 
Код:
str1 - strReplaced
str2 - strReplacedBy
len - originalStringLength
len1 - strReplacedLength
len2 - strReplacedByLength
n1 - posReplacingCount
n2 - posReplacingNumber

...
По-моему, получилось что-то страшное. :D


да. Слишком длинно получилось. Ты уместил почти целый комментарий в имя. Надо нечто среднее. У меня тоже это не всегда получается (слаб в английском и т.п.), но попробую:

 
Код:
str1 - sought
str2 - replacer
len - oldLen
len1 - soughtLen
len2 - replacerLen
n1 - findCount
n2 - не нужен


Цитата: Ghox
И варианта решения задачи, в котором соблюдался бы затребованный автором темы прототип функции и при этом не происходило бы выделения динамической памяти, я не вижу...



Варианты есть, но не скажу, что они намного лучше этого. Например, была какая-то функция Win32 API, которая делала подобную работу в 2 хода. В первом ходе она вычисляла размер требуемой строки и возвращала пользователю, во втором уже размещала данные в строку, которую пользователь предоставил.

Ходы различались тем, что в первый раз функции передавался нулевой указатель.

12K
07 декабря 2009 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Как говорит Мартин Фаулер, "любой дурак может написать программу, которую поймет компилятор. Хорошие программисты пишут программы, которые смогут понять другие программисты".


ИМХО если не переименовывать переменные, но добавить комментарии - описание что каждая из них обозначает, то первый вариант тоже был бы понятен и другим программистам. Функция не такая уж и большая... Хотя безусловно в правильном нейминге много пользы, но как я уже сказал - с ним у меня плохо.

Цитата: Kogrom
Варианты есть, но не скажу, что они намного лучше этого. Например, была какая-то функция Win32 API, которая делала подобную работу в 2 хода. В первом ходе она вычисляла размер требуемой строки и возвращала пользователю, во втором уже размещала данные в строку, которую пользователь предоставил.

Ходы различались тем, что в первый раз функции передавался нулевой указатель.


Если сделать по такому варианту, и использовать твои названия переменных (соглашусь что они гораздо лучше моих), то у меня вот что получилось. Функция принимает на вход 4 указателя:

  • 1 - для записи результата (если NULL то писать ничего не надо, надо только посчитать сколько понадобится места)
  • 2 - исходная строка
  • 3 - заменяемая строка
  • 4 - заменяющая строка
Если первый указатель NULL, то вычисляется сколько байт нужно выделить для записи результата, без учета места под терминальный символ строки '\0', и возвращается функцией.
Если первый указатель не NULL, то происходит запись, но при этом ответственность за то что указатель валиден и под него выделено достаточно места, возлагается на пользователя фукнции.
Код:
#include <stdlib.h>
#include <string.h>

int modString(char *result, char *source, char *sought, char *replacer)
{
    int oldLen, soughtLen, replacerLen, findCount;
    int i, j, k;
    oldLen = strlen(source);
    soughtLen = strlen(sought);
    replacerLen = strlen(replacer);
    if(result == NULL)
    {
        for(i = 0, findCount = 0; i <= oldLen - soughtLen; ++i)
        {
            j = 0;
            while(j < soughtLen && source[i + j] == sought[j])
                ++j;
            if(j == soughtLen)
            {
                ++findCount;
                i += soughtLen - 1;
            }
        }
        return oldLen + (replacerLen - soughtLen) * findCount;
    }
    for(i = 0, j = 0; i <= oldLen - soughtLen; ++i, ++j)
    {
        k = 0;
        while(k < soughtLen && source[i + k] == sought[k])
            ++k;
        if(k == soughtLen)
        {
            for(k = 0; k < replacerLen; ++k, ++j)
                result[j] = replacer[k];
            i += soughtLen - 1;
            --j;
        }
        else
            result[j] = source;
    }
    result[j] = '\0';
    return 0;
}
87
07 декабря 2009 года
Kogrom
2.7K / / 02.02.2008
Я не особо силен в C, не помню, есть ли там поиск строки, но помню, что там есть strncmp. То есть можно сравнивать слово с участком строки.

Кстати, я ещё не сильно надоел?
12K
07 декабря 2009 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Я не особо силен в C, не помню, есть ли там поиск строки, но помню, что там есть strncmp. То есть можно сравнивать слово с участком строки.


Фукнция int strcnmp(const char *str1, const char *str2, size_t count) выполняет лексикографическое сравнение первых count символов строк. Думаю можно создать еще один указатель и смещать его, и по нему строки сравнивать. А вместо посимвольного копирование replacer можно memcpy использовать.
Тогда так выходит:

Код:
#include <stdlib.h>
#include <string.h>

int modString(char *result, char *source, char *sought, char *replacer)
{
    int oldLen, soughtLen, replacerLen, findCount;
    char *ptrSource;
    char *ptrResult;
    oldLen = strlen(source);
    soughtLen = strlen(sought);
    replacerLen = strlen(replacer);
    if(result == NULL)
    {
        for(ptrSource = source, findCount = 0; ptrSource <= source + (oldLen - soughtLen); ++ptrSource)
            if(!strncmp(ptrSource, sought, soughtLen))
            {
                ++findCount;
                ptrSource += soughtLen - 1;
            }
        return oldLen + (replacerLen - soughtLen) * findCount;
    }
    for(ptrSource = source, ptrResult = result; ptrSource <= source + (oldLen - soughtLen); ++ptrSource, ++ptrResult)
        if(!strncmp(ptrSource, sought, soughtLen))
        {
            memcpy((void*)ptrResult, (void*)replacer, sizeof(char) * replacerLen);
            ptrResult += replacerLen - 1;
            ptrSource += soughtLen - 1;
        }
        else
            *ptrResult = *ptrSource;
    *ptrResult = '\0';
    return 0;
}

Цитата: Kogrom
Кстати, я ещё не сильно надоел?


Мне - нет. :) Другим - не знаю. :rolleyes:

Update. В последних двух своих вариантах заметил ошибку, из-за которой в случае, если в конце исходной строки имеются символы в количестве до (soughtLen - 1), которые не должны быть заменены при подстановке, то эти символы не будут переписаны в строку - результат замены, хотя должны.
В предыдущем варианте:

 
Код:
for(i = 0, j = 0; i <= oldLen [COLOR="Red"]- soughtLen[/COLOR]; ++i, ++j)

В последнем варианте (из этого поста):
 
Код:
for(ptrSource = source, ptrResult = result; ptrSource <= source + (oldLen [COLOR="Red"]- soughtLen[/COLOR]); ++ptrSource, ++ptrResult)

Как вариант исправления - добавить сразу после цикла for еще один цикл (прямо перед присвоением терминального символа '\0'), чтобы последние символы были дописаны.
Для предыдущего варианта:
 
Код:
for( ; i < oldLen; ++i, ++j)
        result[j] = source;

Для последнего варианта:
 
Код:
for( ; ptrSource < source + oldLen; ++ptrSource, ++ptrResult)
        *ptrResult = *ptrSource;
87
07 декабря 2009 года
Kogrom
2.7K / / 02.02.2008
Есть ещё пара замечаний, но наверное они из области C++ (или в нем они понятнее). Например, неинициализированные локальные переменные в функции. Ну и то, что скобки убрал - сомнительный момент.

Но это мелочи. Возможно, тут есть более крупные промахи, но их пусть разглядывают люди более знакомые с C. А я почти доволен - могу прочитать функцию, да и короче она немного стала.
17K
11 декабря 2009 года
kilowatt
27 / / 13.01.2007
Ghox спасибо вам за помощь. Ваш вариант меня полностью устраивает. К сожалению свой написать так и не удосужился.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог