Я пытаюсь заменить целое слово в массиве символов C и пропустить подстроки. Я провел исследование, и в итоге я пришел к действительно трудным решениям, хотя я думаю, что у меня есть идея получше, если кто-то может мне помочь. Скажем, у меня есть строка:
char sentence[100]= "apple tree house";
И я хотел бы заменить дерево на число 12:
"apple 12 house"
Я знаю, что слова разделены пробелом, поэтому моя идея состоит в том, чтобы:
1. Обозначьте строку пробелом-разделителем
2.В цикле while проверяется библиотечной функцией STRCMP, совпадает ли строка с токеном и подлежит ли она замене.
Проблема для меня возникает, когда я пытаюсь заменить строку, поскольку я не мог этого сделать.
void wordreplace(char string[], char search[], char replace[]) {
// Tokenize
char * token = strtok(string, " ");
while (token != NULL) {
if (strcmp(search, token) == 0) {
REPLACE SEARCH STRING WITH REPLACE STRING
}
token = strtok(NULL, " ");
}
printf("Sentence : %s", string);
}
Любые предложения, что я могу использовать? Я думаю, это может быть очень просто, но я новичок, и это очень ценится :)
[EDIT]: пробелы являются единственными разделителями, и обычно заменяемая строка не длиннее исходной.
Пробелы @BenZotto являются единственными разделителями, а заменяемая строка не длиннее исходной.
В своем редактировании вы говорите, что строка не длиннее оригинала. Но это в вашем примере с заменой «дерева» на «банан». Новая строка на два символа длиннее исходной. И если бы ввод был «дерево дерево дерево», это было бы на шесть символов длиннее. То есть непредсказуемое изменение размера результирующего вывода, который является полезной точкой данных для вашей работы. Он говорит вам, что эта функция сама по себе, без дополнительного контекста о выделенных размерах строк, не может работать.
Извините, это моя ошибка, я отредактировал это :) Спасибо, что упомянули об этом.
Ах хорошо. Это имеет существенное значение в том, насколько возможной и простой может быть функция, если вы только сокращаете длину ввода, а не расширяете ее.
Если вы должны выполнить замену на месте, то использование strtok, вероятно, будет плохим выбором для начала, поскольку оно изменит исходную строку с помощью символов новой строки.
strtok()всегда — плохой выбор.





Я бы избегал strtok в этом случае (потому что это изменит строку как побочный эффект ее токенизации) и подошел бы к этому, просматривая строку посимвольно и поддерживая индекс «чтения» и «записи». Поскольку вывод никогда не может быть длиннее ввода, индекс записи никогда не будет опережать индекс чтения, и вы можете «обратно записать» и внести изменения в той же строке.
Чтобы визуализировать это, я считаю полезным записывать входные данные в поля и рисовать стрелки к текущим индексам чтения и записи и отслеживать процесс, чтобы вы могли убедиться, что у вас есть система, которая будет делать то, что вы хотите, и что ваши циклы и индексы работают так, как вы ожидаете.
Вот одна реализация, которая соответствует тому, как мой собственный разум склонен подходить к такому алгоритму. Он просматривает строку и смотрит вперед, чтобы попытаться найти совпадение с текущим символом. Если он находит совпадение, он копирует замену в текущее место и соответственно увеличивает оба индекса.
void wordreplace(char * string, const char * search, const char * replace) {
// This is required to be true since we're going to do the replace
// in-place:
assert(strlen(replace) <= strlen(search));
// Get ourselves set up
int r = 0, w = 0;
int str_len = strlen(string);
int search_len = strlen(search);
int replace_len = strlen(replace);
// Walk through the input character by character.
while (r < str_len) {
// Is this character the start of a matching token? It is
// if we see the search string followed by a space or end of
// string.
if (strncmp(&string[r], search, search_len) == 0 &&
(string[r+search_len] == ' ' || string[r+search_len] == '\0')) {
// We matched the search token. Copy the replace token.
memcpy(&string[w], replace, replace_len);
// Update our indexes.
w += replace_len;
r += search_len;
} else {
// Otherwise just copy this character.
string[w++] = string[r++];
}
}
// Be sure to terminate the final version of the string.
string[w] = '\0';
}
(Обратите внимание, что я настроил сигнатуру вашей функции, чтобы использовать более идиоматическую нотацию указателя, а не массивы символов, и в соответствии с комментарием гриппа ниже я пометил токены поиска и замены как «const», что является способом объявления функции, что она не будет изменять эти струны.)
search и replace должны быть const char *еще одно незначительное предложение: замените strcpy на memcpy, чтобы избежать ненужной вставки нулевого символа.
Кажется, у него проблемы с заменой термина равной или большей длины, чем поисковый запрос.
@flu: Спасибо, не только ненужное, но и неправильное в некоторых случаях. Фиксированный.
@DavidC.Rankin Спасибо, к вашему сведению, за разъяснение OP в комментариях (и редактировании), случай большей длины на самом деле не является (я думаю?) частью проблемного пространства здесь. Согласитесь, что это открывает совершенно другую алгоритмическую банку червей, если бы это было так, и этот подход недостаточен, по крайней мере, без создания дополнительного временного буфера ... какой-то непостижимой длины.
Не беспокойтесь, я не совсем понял, каковы были все его ограничения. Я просто возился с вашим кодом (мне он нравится) и попробовал его с несколькими вариантами замены.
Делать то, что вы хотите, становится немного сложнее, потому что вам нужно обрабатывать сценарии, в которых:
strtok имеет некоторые недостатки из-за внесения изменений в исходную строку в процессе токенизации. (можно просто сделать копию, но если вы хотите замену на месте, вам нужно искать дальше). Комбинация strstr и strcspn позволяет более эффективно работать с исходной строкой при поиске определенной строки поиска в оригинале.
strcspn можно использовать как strtok с набором разделителей, чтобы указать длину текущего найденного токена (чтобы strstr не соответствовал вашему поисковому запросу как меньшая включенная подстрока более длинного слова, например tree в trees). Тогда это становится простым делом зацикливания с помощью strstr и проверки длины токена с помощью strcspn, а затем просто применения одного из трех вышеперечисленных случаев.
Краткий пример реализации с комментариями, включенными в строку, чтобы помочь вам следовать, может быть таким:
#include <stdio.h>
#include <string.h>
#define MAXLIN 100
void wordreplace (char *str, const char *srch,
const char *repl, const char *delim)
{
char *p = str; /* pointer to str */
size_t lenword, /* length of word found */
lenstr = strlen (str), /* length of total string */
lensrch = strlen (srch), /* length of search word */
lenrepl = strlen (repl); /* length of replace word */
while ((p = strstr (p, srch))) { /* srch exist in rest of string? */
lenword = strcspn (p, delim); /* get length of word found */
if (lenword == lensrch) { /* word len match search len */
if (lenrepl == lensrch) /* if replace is same len */
memcpy (p, repl, lenrepl); /* just copy over */
else if (lenrepl > lensrch) { /* if replace is longer */
/* check that additional lenght will fit in str */
if (lenstr + lenrepl - lensrch > MAXLIN - 1) {
fputs ("error: replaced length would exeed size.\n",
stderr);
return;
}
if (!p[lenword]) { /* if no following char */
memcpy (p, repl, lenrepl); /* just copy replace */
p[lenrepl] = 0; /* and nul-terminate */
}
else { /* store rest of line in buffer, replace, add end */
char endbuf[MAXLIN]; /* temp buffer for end */
size_t lenend = strlen (p + lensrch); /* end length */
memcpy (endbuf, p + lensrch, lenend + 1); /* copy end */
memcpy (p, repl, lenrepl); /* make replacement */
memcpy (p + lenrepl, endbuf, lenend); /* add end after */
}
}
else { /* otherwise replace is shorter than search */
size_t lenend = strlen (p + lenword); /* get end length */
memcpy (p, repl, lenrepl); /* copy replace */
/* move end to after replace */
memmove (p + lenrepl, p + lenword, lenend + 1);
}
}
}
}
int main (int argc, char **argv) {
char str[MAXLIN] = "apple tree house in the elm tree";
const char *search = argc > 1 ? argv[1] : "tree",
*replace = argc > 2 ? argv[2] : "12",
*delim = " \t\n";
wordreplace (str, search, replace, delim);
printf ("str: %s\n", str);
}
Пример использования/вывода
Ваш пример замены "tree" на "12" в "apple tree house in the elm tree":
$ ./bin/wordrepl_strstr_strcspn
str: apple 12 house in the elm 12
Простая замена "tree" на "core" такой же длины, например.
$ ./bin/wordrepl_strstr_strcspn tree core
str: apple core house in the elm core
Замена «длиннее чем» "tree" на "bobbing":
$ ./bin/wordrepl_strstr_strcspn tree bobbing
str: apple bobbing house in the elm bobbing
Есть много разных способов решить эту проблему, поэтому ни один из них не является правильным. Главное сделать его понятным и достаточно эффективным. Просмотрите все и дайте мне знать, если у вас есть дополнительные вопросы.
Одной из проблем, которая неразрывно связана с алгоритмическим аспектом этой проблемы, является проблема управления памятью. C заставляет вас много беспокоиться об этом, например, что, если замена слов сделает строку длиннее, чем она уже была? Будете ли вы переполнять выделенную память, предоставленную вам вызывающим абонентом? Вы знаете, сколько у вас есть? Если вам необходимо отредактировать ту же самую строку, которую вам дали, вы, вероятно, можете сделать это во всех случаях только путем выделения временной строки. Вы не указали никаких конкретных параметров ввода - например. пробелы единственные пробелы, которые имеют значение?