I'm having looping issues with my code. I have a method that takes in two char arrays (phrase, characters). The characters array holds characters that must be read individually and compared to the phrase. If it matches, every occurrence of the character will be removed from the phrase.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//This method has two parameters: (str, c)
//It will remove all occurences of var 'c'
//inside of 'str'
char * rmstr(char * c, char * str) {
//Declare counters and pointers
int stemp = 0;
int ctemp = 0;
char *p = str;
char *d = c;
//Retrieve str count
while(str[stemp] != '\0') {
stemp++;
}
//Retrieve c count
while(c[ctemp] != '\0') {
ctemp++;
}
//Output information
printf("String Count: %d\n",stemp);
printf("Character Count: %d\n",ctemp);
//Iterate through arrays
for (int i = 0; i != stemp; i++) {
for (int j = 0; j != ctemp; j++) {
if (c[j] != str[i]){
*p++ = str[i];
}
break;
}
printf("%s\n",str);
}
*p = 0;
return str;
}
int main()
{
char c[256] = "ema";
char input[256] = "Great message!";
char *result = rmstr(c, input);
printf("%s", result);
return 0;
}
In this case, the input would be "Great Message!" and the character I'd like to remove all occurrences of the characters: e, m, a (As specified in main).
Using the code as it is above, the output is as follows:
Grat mssag!
It is only looping through 1 iteration and removing 'e'. I would like it to loop through 'm' and 'a' as well.
After you fix your break; that was causing your inner loop to exit, it may make sense to reorder your loops and loop over the chars to remove while checking against the characters in str. This is more of a convenience allowing you to shuffle each character down by one in str if it matches a character is c. If you are using the functions in string.h like memmove to move characters down, it doesn't really matter.
A simple implementation using only pointers to manually work through str removing all chars in c could look something like the following:
#include <stdio.h>
char *rmstr (char *str, const char *chars)
{
const char *c = chars; /* set pointer to beginning of chars */
while (*c) { /* loop over all chars with c */
char *p = str; /* set pointer to str */
while (*p) { /* loop over each char in str */
if (*p == *c) { /* if char in str should be removed */
char *sp = p, /* set start pointer at p */
*ep = p + 1; /* set end pointer at p + 1 */
do
*sp++ = *ep; /* copy end to start to end of str */
while (*ep++); /* (nul-char copied on last iteration) */
}
p++; /* advance to next char in str */
}
c++; /* advance to next char in chars */
}
return str; /* return modified str */
}
int main (void) {
char c[] = "ema";
char input[] = "Great message!";
printf ("original: %s\n", input);
printf ("modified: %s\n", rmstr (input, c));
return 0;
}
(there are many ways to do this -- how is largely up to you. whether you use pointers as above, or get the lengths and use string-indexes is also a matter of choice)
Example Use/Output
$ ./bin/rmcharsinstr
original: Great message!
modified: Grt ssg!
If you did want to use memmove (to address the overlapping nature of the source and destination) to move the remaining characters in str down by one each time the character in str matches a character in c, you could leave the loops in your original order, e.g.
#include <string.h>
char *rmstr (char *str, const char *chars)
{
char *p = str; /* set pointer to str */
while (*p) { /* loop over each char in str */
const char *c = chars; /* set pointer to beginning of chars */
while (*c) { /* loop over all chars with c */
while (*c == *p) { /* while the character matches */
memmove (p, p + 1, strlen (p)); /* shuffle down by 1 */
c = chars; /* reset c = chars to check next */
}
c++; /* advance to next char in chars */
}
p++; /* advance to next char in str */
}
return str; /* return modified str */
}
(make sure you understand why you must reset c = chars; in this case)
Finally, if you really wanted the shorthand way of doing it, you could use strpbrk and memmove and reduce your function to:
#include <string.h>
char *rmstr (char *str, const char *chars)
{
/* simply loop using strpbrk removing the character found */
for (char *p = strpbrk (str, chars); p; p = strpbrk (str, chars))
memmove (p, p+1, strlen(p));
return str; /* return modified str */
}
(there is always more than one way to skin-the-cat in C)
The output is the same. Look things over here and let me know if you have further questions.
Related
I am trying to remove duplicated letters in each word from string.(I haven't specified it for upper and lower case letters yet)
Input:
Ii feel good todday!!
thhis iss fixed
Output:
I fel god today!
this is fixed
I am calling this function in the main and i have to use the result in another function. That's why I call it by reference.
int main(){
char string[100];
printf("Enter a string:");
gets(string);
dup_letters_rule(&string);
return 0;
}
void dup_letters_rule(char *str_[]){
char new_str_[100];
int i=0, j=0;
printf("Fixed duplicates:\n");
while(*str_[i]!='\0'){
if(*str_[i]== *str_[i+1] && *str_[i+1]!='\0'){
while(*str_[i]==*str_[i+1] && *str_[i+1]!='\0'){
i++;
}
*str_[i]=new_str_[j];
j++;
i++;
}
else{
*str_[i]=new_str_[j];
j++;
i++;
}
}
new_str_[j]='\0';
puts(new_str_);
}
It works like:
void dup_letters_rule(char *str_[]){
char *new_str_=*str_, *temp=*str_;
temp++;
printf("Fixed duplicates:\n");
while(*new_str_!='\0'){
if(*new_str_== *temp && *temp!='\0'){
while(*new_str_==*temp && *temp!='\0'){
new_str_++;
temp++;
}
putchar(*new_str_);
new_str_++;
temp++;
}
else{
putchar(*new_str_);
new_str_++;
temp++;
}
}
}
But then, I can't use *str_ string in another function.
The code can be simplified.
We can keep an int value that is the previous char seen and compare it against the current char and only "copy it out" if they are different. (i.e. we only need two pointers).
We also have to use tolower because Ii goes to I.
Although a second/output buffer could be used, the function can do the cleanup "in-place". Then, the caller can use the cleaned up buffer. This is what we'd normally want to do.
If the caller needs to keep the original string, it can save the original to a temp buffer and call the function with the temp
I had to refactor your code. I tested it against your sample input. It is annotated:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
void
dup_letters_rule(char *src)
{
char *dst = src;
int prev = -1;
// rchr -- the "raw" char
// lchr -- the result of tolower(rchr)
// prev -- the previous value of lchr (starts with -1 to force output of
// first char)
for (int rchr = *src++; rchr != 0; rchr = *src++) {
// get lowercase char
int lchr = tolower((unsigned char) rchr);
// output if _not_ a dup
if (lchr != prev)
*dst++ = rchr;
// remember this char for the next iteration
prev = lchr;
}
*dst = 0;
}
int
main(void)
{
char *cp;
char buf[1000];
while (1) {
cp = fgets(buf,sizeof(buf),stdin);
if (cp == NULL)
break;
// get rid of newline
buf[strcspn(buf,"\n")] = 0;
// eliminate dups
dup_letters_rule(buf);
// output the clean string
printf("%s\n",buf);
}
return 0;
}
UPDATE:
can i print the clean string in the dup_letters_rule function? – hamster
Sure, of course. We're the programmers, so we can do whatever we want ;-)
There is a maxim for functions: Do one thing well
In many actual (re)use cases, we don't want the simple/low level function to do printing. That is the usual.
But, we could certainly add printing to the function. We'd move the printf from main into the function itself.
To get the best of both worlds, we can use two functions. One that just does the transformation. And, a second that calls the simple function and then prints the result.
Here's a slight change that illustrates that. I renamed my function and created dup_letters_rule with the printf embedded in it:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
void
dup_letters_rule_basic(char *src)
{
char *dst = src;
int prev = -1;
// rchr -- the "raw" char
// lchr -- the result of tolower(rchr)
// prev -- the previous value of lchr (starts with -1 to force output of
// first char)
for (int rchr = *src++; rchr != 0; rchr = *src++) {
// get lowercase char
int lchr = tolower((unsigned char) rchr);
// output if _not_ a dup
if (lchr != prev)
*dst++ = rchr;
// remember this char for the next iteration
prev = lchr;
}
*dst = 0;
}
void
dup_letters_rule(char *buf)
{
dup_letters_rule_basic(buf);
// output the clean string
printf("%s\n",buf);
}
int
main(void)
{
char *cp;
char buf[1000];
while (1) {
cp = fgets(buf,sizeof(buf),stdin);
if (cp == NULL)
break;
// get rid of newline
buf[strcspn(buf,"\n")] = 0;
dup_letters_rule(buf);
}
return 0;
}
UPDATE #2:
and why it's not char *dst = *src; but char *dst = src; – hamster
This is basic C. We want dst to have the same value/contents that src does. Just as if we did:
int x = 23;
int y = x;
If we do what you're suggesting, the compiler flags the statement:
bad.c: In function ‘dup_letters_rule_basic’:
bad.c:8:14: warning: initialization of ‘char *’ from ‘char’ makes pointer from integer without a cast [-Wint-conversion]
char *dst = *src;
^
Doing char *dst = *src [as you suggest] is using * in two different ways.
Doing char *dst says that dst is defined as a pointer to a char.
Doing *src here [which is the initializer for dst and is an expression], the * is the dereference operator. It says "fetch the value (a char) pointed to by src". Not what we want.
Perhaps this would be more clear if we didn't use an initializer. We use a definition (without an initializer) and set the initial value of dst with an assignment statement:
char *dst; // define a char pointer (has _no_ initial value)
dst = src; // assign the value of dst from the value of src
The assignment [statement] can occur anywhere after the definition and before the for loop/statement. Here's the first few lines of the function body:
char *dst;
int prev = -1;
dst = src;
To remove the duplicate consecutive characters from a string in-place, keep track of position in string where the next character, which is not same as its previous character, to be write and check current processing character with previous character (ignore the difference in their case) except when the character is the first character of string because the first character does not have any character previous to it. If current processing character is same as previous character then move to next character in the string and if they are not same then overwrite the character at tracked position with current processing character and increment tracked position pointer by 1.
Its implementation:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
void remove_consecutive_dup_chars (char * pstr) {
if (pstr == NULL) {
printf ("Invalid input..\n");
return;
}
/* Pointer to keep track of position where next character
* to be write in order to remove consecutive duplicate character.
*/
char * p = pstr;
for (unsigned int i = 0; pstr[i] ; ++i) {
if ((i) && (tolower (pstr[i]) == tolower (pstr[i - 1]))) {
continue;
}
*p++ = pstr[i];
}
/* Add the null terminating character.
*/
*p = '\0';
}
int main (void) {
char buf[256] = {'\0'};
strcpy (buf, "Ii feel good todday!!");
remove_consecutive_dup_chars (buf);
printf ("%s\n", buf);
strcpy (buf, "thhis iss fixed");
remove_consecutive_dup_chars (buf);
printf ("%s\n", buf);
strcpy (buf, "");
remove_consecutive_dup_chars (buf);
printf ("%s\n", buf);
strcpy (buf, "aaaaaa zzzzzz");
remove_consecutive_dup_chars (buf);
printf ("%s\n", buf);
return 0;
}
Output:
I fel god today!
this is fixed
a z
So what i have is a string(str) that i get from fgets(str, x, stdin);.
If i write for example "Hello World" i want to be able to add a character infront of each word in the string.
To get this "Hello? World?" as an example. I think i've made it alot harder for myself by trying to solve it this way:
add(char *s, char o, char c){
int i, j = 0;
for (i = 0; s[i] != '\0'; i++) {
if (s[i] != o) {
s[j] = s[i];
}
else {
s[j] = c;
}
j++;
}
}
add(str, ' ','?');
printf("\n%s", str);
This will read out "Hello?World" without the spaces. Now the only way i see this working is if i move everything after the first "?" one to the right while also making the positon of the "W" to a space and a "?" at the end. But for much longer strings i can't see myself doing that.
You can't safely extend a string with more characters without insuring the buffer that holds the string is big enough. So let's devise a solution that counts how many additional characters are needed, allocate a buffer big enough to hold a string of that length, then do the copy loop. Then return the new string back to the caller.
char* add(const char* s, char o, char c)
{
size_t len = strlen(s);
const char* str = s;
char* result = NULL;
char* newstring = NULL;
// count how many characters are needed for the new string
while (*str)
{
len += (*str== o) ? 2 : 1;
str++;
}
// allocate a result buffer big enough to hold the new string
result = malloc(len + 1); // +1 for null char
// now copy the string and insert the "c" parameter whenever "o" is seen
newstring = result;
str = s;
while (*str)
{
*newstring++ = *str;
if (*str == o)
{
*newstring++ = c;
}
str++;
}
*newString = '\0';
return result;
}
Then your code to invoke is as follows:
char* newstring g= add(str, ' ','?');
printf("\n%s", newstring);
free(newstring);
#include <stdio.h>
#include <string.h>
int main(void) {
char text[] = "Hello World";
for(char* word = strtok(text, " .,?!"); word; word = strtok(NULL, " .,?!"))
printf("%s? ", word);
return 0;
}
Example Output
Success #stdin #stdout 0s 4228KB
Hello? World?
IDEOne Link
Knowing the amount of storage available when you reach a position where the new character will be inserted, you can check whether the new character will fit in the available storage, move from the current character through end-of-string to the right by one and insert the new character, e.g.
#include <stdio.h>
#include <string.h>
#define MAXC 1024
char *add (char *s, const char find, const char replace)
{
char *p = s; /* pointer to string */
while (*p) { /* for each char */
if (*p == find) {
size_t remain = strlen (p); /* get remaining length */
if ((p - s + remain < MAXC - 1)) { /* if space remains for char */
memmove (p + 1, p, remain + 1); /* move chars to right by 1 */
*p++ = replace; /* replace char, advance ptr */
}
else { /* warn if string full */
fputs ("error: replacement will exceed storage.\n", stderr);
break;
}
}
p++; /* advance to next char */
}
return s; /* return pointer to beginning of string */
}
...
(note: the string must be mutable, not a string-literal, and have additional storage for the inserted character. If you need to pass a string-literal or you have no additional storage in the current string, make a copy as shown by #Selbie in his answer)
Putting together a short example with a 1024-char buffer for storage, you can do something like:
#include <stdio.h>
#include <string.h>
#define MAXC 1024
char *add (char *s, const char find, const char replace)
{
char *p = s; /* pointer to string */
while (*p) { /* for each char */
if (*p == find) {
size_t remain = strlen (p); /* get remaining length */
if ((p - s + remain < MAXC - 1)) { /* if space remains for char */
memmove (p + 1, p, remain + 1); /* move chars to right by 1 */
*p++ = replace; /* replace char, advance ptr */
}
else { /* warn if string full */
fputs ("error: replacement will exceed storage.\n", stderr);
break;
}
}
p++; /* advance to next char */
}
return s; /* return pointer to beginning of string */
}
int main (void) {
char buf[MAXC];
if (!fgets (buf, MAXC, stdin))
return 1;
buf[strcspn(buf, "\n")] = 0;
puts (add (buf, ' ', '?'));
}
Example Use/Output
$ ./bin/str_replace_c
Hello World?
Hello? World?
Look things over and let me know if you have questions.
Just for fun, here's my implementation. It modifies the string in-place and in O(n) time. It assumes that the char-buffer is large enough to hold the additional characters, so it's up to the calling code to ensure that.
#include <stdio.h>
void add(char *s, char o, char c)
{
int num_words = 0;
char * p = s;
while(*p) if (*p++ == o) num_words++;
char * readFrom = p;
char * writeTo = p+num_words;
char * nulByte = writeTo;
// Insert c-chars, iterating backwards to avoid overwriting chars we have yet to read
while(readFrom >= s)
{
*writeTo = *readFrom;
if (*writeTo == o)
{
--writeTo;
*writeTo = c;
}
writeTo--;
readFrom--;
}
// If our string doesn't end in a 'c' char, append one
if ((nulByte > s)&&(*(nulByte-1) != c))
{
*nulByte++ = c;
*nulByte = '\0';
}
}
int main(int argc, char ** argv)
{
char test_string[1000] = "Hello World";
add(test_string, ' ','?');
printf("%s\n", test_string);
return 0;
}
The program's output is:
$ ./a.out
Hello? World?
I have been running into issues with the strcpy() function in C. In this function I take a string in buffer and the string contains a something along the lines of '(213);' and I am trying to remove the brackets so the output would be something like 200;.
for (i = 0; i < bufferlen; i++) {
// check for '(' followed by a naked number followed by ')'
// remove ')' by shifting the tail end of the expression
// remove '(' by shifting the beginning of the expression
if((buffer[i] == '(') && (isdigit(buffer[i+1]))){
int numberLen = 0;
int test =0;
i++;
while((isdigit(buffer[i]))){
i++;
numberLen++;
}
if(buffer[i] == ')'){
int numberStart = i - numberLen-1;
strcpy(&buffer[i], &buffer[i+1]);
strcpy(&buffer[numberStart], &buffer[numberStart+1]);
printf("buffer = %s\n", buffer);
}
}
}
However, the output is as follows
buffer before strcpy(&buffer[i], &buffer[i+1]); = (213);
buffer after strcpy(&buffer[i], &buffer[i+1]); = (213;
buffer after strcpy(&buffer[numberStart], &buffer[numberStart+1]); = 23;;
for some reason the second strcpy function removes the second value of the string. I have also tried
strcpy(&buffer[0], &buffer[1]); and still end up with the same results. Any insight as to why this is occurring would be greatly appreciated.
Continuing from the comment, strcpy(&buffer[i], &buffer[i+1]); where source and dest overlap results in Undefined Behavior, use memmove, or simply use a couple of pointers instead.
The prohibition on using strings that overlap (i.e. are the same string) is found in C11 Standard - 7.24.2.3 The strcpy function
If I understand your question and you simply want to turn "'(213)'" into "213", you don't need any of the string.h functions at all. You can simply use a couple of pointers and walk down the source-string until you find a digit. Start copying digits to dest at that point by simple assignment. When the first non-digit is encountered, break your copy loop. Keeping a flag to indicate when you are "in" a number copying digits will allow you to break on the 1st non-digit to limit your copy to the first sequence of digits found (e.g. so from the string "'(213)' (423)", only 213 is returned instead of 213423). You could do somehting like:
char *extractdigits (char *dest, const char *src)
{
/* you can check src != NULL here */
char *p = dest; /* pointer to dest (to preserve dest for return) */
int in = 0; /* simple flag to break loop when non-digit found */
while (*src) { /* loop over each char in src */
if (isdigit(*src)) { /* if it is a digit */
*p++ = *src; /* copy to dest */
in = 1; /* set in-number flag */
}
else if (in) /* if in-number, break on non-digit */
break;
src++; /* increment src pointer */
}
*p = 0; /* nul-terminate dest */
return dest; /* return pointer to dest (for convenience) */
}
A short example would be:
#include <stdio.h>
#include <ctype.h>
#define MAXC 32
char *extractdigits (char *dest, const char *src)
{
/* you can check src != NULL here */
char *p = dest; /* pointer to dest (to preserve dest for return) */
int in = 0; /* simple flag to break loop when non-digit found */
while (*src) { /* loop over each char in src */
if (isdigit(*src)) { /* if it is a digit */
*p++ = *src; /* copy to dest */
in = 1; /* set in-number flag */
}
else if (in) /* if in-number, break on non-digit */
break;
src++; /* increment src pointer */
}
*p = 0; /* nul-terminate dest */
return dest; /* return pointer to dest (for convenience) */
}
int main (void) {
char digits[MAXC] = "";
const char *string = "'(213}'";
printf ("in : %s\nout: %s\n", string, extractdigits (digits, string));
}
Example Use/Output
$ ./bin/extractdigits
in : '(213}'
out: 213
Look things over and let me know if you have further questions.
I've a program which takes any number of words from the command-line arguments and replaces them with the word 'CENSORED'. I finally have the program working for the first argument passed in, and I am having trouble getting the program to censor all arguments, outputted in just a single string. The program rather functions individually on a given argument and does not take them all into account. How would I modify this?
How does one use/manipulate multiple command-line arguments collectively ?
My code follows.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *replace_str(char *str, char *orig, char *rep, int j, int argc)
{
static char buffer[4096];
char *p;
for ( j = 1; j <= argc; j++ )
{
if(!(p = strstr(str, orig))) // Check if 'orig' is not in 'str'
{
if ( j == argc ) { return str; } // return str once final argument is reached
else { continue; } // restart loop with next argument
}
strncpy(buffer, str, p-str); // Copy characters from 'str' start to 'orig' str
buffer[p-str] = '\0';
if ( j == argc ) { return buffer; }
else { continue; }
}
sprintf(buffer+(p-str), "%s%s", rep, p+strlen(orig));
}
int main( int argc, char* argv[] ) //argv: list of arguments; array of char pointers //argc: # of arguments.
{
long unsigned int c, i = 0, j = 1;
char str[4096];
while ( (c = getchar()) != EOF )
{
str[i] = c; // save input string to variable 'str'
i++;
}
puts(replace_str( str, argv[j], "CENSORED", j, argc ) );
return 0;
}
i.e.
$ cat Hello.txt
Hello, I am me.
$ ./replace Hello me < Hello.txt
CENSORED, I am CENSORED.
Two issues, you are not guaranteeing a null-terminated str and second, you are not iterating over the words on the command line to censor each. Try the following in main after your getchar() loop:
/* null-terminate str */
str[i] = 0;
/* you must check each command line word (i.e. argv[j]) */
for (j = 1; j < argc; j++)
{
puts(replace_str( str, argv[j], "CENSORED", j, argc ) );
}
Note: that will place each of the CENSORED words on a separate line. As noted in the comments, move puts (or preferably printf) outside the loop to keep on a single line.
Edit
I apologize. You have more issues than stated above. Attempting to check the fix, it became apparent that you would continue to have difficulty parsing the words depending on the order the bad words were entered on the command line.
While it is possible to do the pointer arithmetic to copy/expand/contract the original string regardless of the order the words appear on the command line, it is far easier to simply separate the words provided into an array, and then compare each of the bad words against each word in the original string.
This can be accomplished relatively easily with strtok or strsep. I put together a quick example showing this approach. (note: make a copy of the string before passing to strtok, as it will alter the original). I believe this is what you were attempting to do, but you were stumbling on not having the ability to compare each word (thus your use of strstr to test for a match).
Look over the example and let me know if you have further questions. Note: I replaced your hardcoded 4096 with a SMAX define and provided a word max WMAX for words entered on the command line. Also always initialize your strings/buffers. It will enable you to always be able to easily find the last char in the buffer and ensure the buffer is always null-terminated.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define SMAX 4096
#define WMAX 50
char *replace_str (char *str, char **bad, char *rep)
{
static char buffer[SMAX] = {0};
char *p = buffer;
char *wp = NULL;
unsigned i = 0;
unsigned char censored = 0;
char *str2 = strdup (str); /* make copy of string for strtok */
char *savp = str2; /* and save start address to free */
if (!(wp = strtok (str2, " "))) /* get first word in string or bail */
{
if (savp) free (savp);
return str;
}
while (bad[i]) /* test against each bad word */
{
if (strcmp (wp, bad[i++]) == 0) /* if matched, copy rep to buffer */
{
memcpy (buffer, rep, strlen (rep));
censored = 1;
}
}
if (!censored) /* if no match, copy original word */
memcpy (buffer, wp, strlen (wp));
while ((wp = strtok (NULL, " "))) /* repeat for each word in str */
{
i = 0;
censored = 0;
memcpy (strchr (buffer, 0), " ", 1);
p = strchr (buffer, 0); /* (get address of null-term char) */
while (bad[i])
{
if (strcmp (wp, bad[i++]) == 0)
{
memcpy (p, rep, strlen (rep));
censored = 1;
}
}
if (!censored)
memcpy (p, wp, strlen (wp));
}
if (savp) free (savp); /* free copy of strtok string */
return buffer;
}
int main ( int argc, char** argv)
{
unsigned int i = 0;
char str[SMAX] = {0};
char *badwords[WMAX] = {0}; /* array to hold command line words */
for (i = 1; i < argc; i++) /* save command line in array */
badwords[i-1] = strdup (argv[i]);
i = 0; /* print out the censored words */
printf ("\nCensor words:");
while (badwords[i])
printf (" %s", badwords[i++]);
printf ("\n\n");
printf ("Enter string: "); /* promt to enter string to censor */
if (fgets (str, SMAX-1, stdin) == NULL)
{
fprintf (stderr, "error: failed to read str from stdin\n");
return 1;
}
str[strlen (str) - 1] = 0; /* strip linefeed from input str */
/* print out censored string */
printf ("\ncensored str: %s\n\n", replace_str (str, badwords, "CENSORED"));
i = 0; /* free all allocated memory */
while (badwords[i])
free (badwords[i++]);
return 0;
}
use/output
./bin/censorw bad realbad
Censor words: bad realbad
Enter string: It is not nice to say bad or realbad words.
censored str: It is not nice to say CENSORED or CENSORED words.
I've seen many solutions for getting substring of a string with usage of strndup or memcpy or strncpy and etc,.
I was wondering if there's a way to get substring without using those functions; even if it's unnecessary.
EDIT: I tried making function myself; I don't remember what the problem was but something went wrong and I ended up not using it.
char *substring(char *str, int start, int length) {
char *s = malloc(sizeof(char)*(length+1));
for(int i=start; i<start+length; i++) {
s[i-start] = str[i];
}
s[length] = '\0';
return s;
}
There are a number of ways to recreate strstr. The following is a quick implementation using the inch-worm method, where you simply use pointers to search for the beginning of the substring in string, then if found, compare every character in substring with the corresponding character in string. If all characters match, the substring is found, return a pointer to the beginning of substring in string.
If a character fails the test, look for another character in string that matches the first character in substring, until string is exhausted.
There are probably several more checks that can be inplemented, but this example should get you started:
#include <stdio.h>
#include <stdlib.h>
char *strstr2 (char *str, char *sub)
{
if (!str || !sub) return NULL; /* validate both strings */
char *p = NULL; /* general pointer */
char *sp = NULL; /* substring pointer */
char *rp = NULL; /* return pointer */
char matched = 0; /* matched flag */
size_t szstr = 0; /* string length */
size_t szsub = 0; /* substring length */
p = sub;
while (*p++) szsub++; /* strlen of substr */
p = str;
while (*p++) szstr++; /* strlen of str */
if (szsub > szstr) return NULL; /* szstr < szsub - no match */
p = str;
while (p < (p + szstr - szsub + 1))
{
while (*p && *p != *sub) p++; /* find start of sub in str */
if ((str + szstr) == p) return NULL; /* if end reached - no sub */
rp = p; /* save return pointer */
sp = sub; /* set sp to sub */
matched = 1; /* presume will match */
while (*sp) /* for each in substring */
if (*p++ != *sp++) { /* check if match fails */
matched = 0; /* if failed, no match */
break; /* break & find new start */
}
if (matched) /* if matched, return ptr */
return rp; /* to start of sub in str */
}
return NULL; /* no match, return NULL */
}
int main() {
char *string = NULL;
char *substr = NULL;
char *begin = NULL;
printf ("\nEnter string : ");
scanf ("%m[^\n]%*c", &string);
printf ("\nEnter substr : ");
scanf ("%m[^\n]%*c", &substr);
if ((begin = strstr2 (string, substr)) != NULL)
printf ("\nSubstring found beginning at : %s\n\n", begin);
else
printf ("\nSubstring NOT in string.\n\n");
if (string) free (string);
if (substr) free (substr);
return 0;
}
output:
$ ./bin/strstr
Enter string : This is the full string or "haystack".
Enter substr : g or "
Substring found beginning at : g or "haystack".
$ ./bin/strstr
Enter string : This is the full string or "haystack".
Enter substr : g or '
Substring NOT in string.
Wow!!! So many variables and tests and lots of indentation.
In the 1970's, some considered it poor style to not have all of the return
statements at the bottom of the routine, but that thinking has mostly disappeared.
For some reason, many programmers write their conditionals to test
if one variable is equal, not equal, greater, or less than something else.
They believe that conditionals should be boolean values and nothing else.
But C allows tests of int, char or others equal or not equal to zero.
Zero can be NULL or NUL or any other zero value. This is legal and appropriate.
if (variable) return NULL;
Some consider conditionals with side effects, such as,
if (*h++ == *n++) continue;
where variables h and n are modified, to not be great style.
To avoid that, I suppose you can rewrite it as
if (*h == *n) { h++; n++; continue;}
Here is my version. It is not worse than the version you supplied on this page. But I want to believe it is shorter, simpler, and easier to understand.
My style is not perfect. Nobody has perfect style. I supply this only
for contrast.
char * strstr( const char *haystack, const char *needle) {
const char *h = haystack, *n = needle;
for (;;) {
if (!*n) return (char *)h;
if (!*h) return NULL;
if (*n++ == *h++) continue;
h = ++haystack;
n = needle;
}
}