zoids3
zoids3

Reputation: 65

Switching letters (no strings or strings functions)

I want to switch the word "cat" into "dog" each time it appears in the text. I can't use string or strings functions.

my code:

#include <stdio.h>

int main()
{   
    int i; // loop counter 
    int size; // size of arry  
    int input[20];
    printf("enter text here\n");  

    while((input[i] = getchar()) != '\n') // input text to the arry
    {
        if(input[i]=='c' && input[i+1]=='a' && input[i+2]=='t') // switching characters
        {
            input[i]='d'; input[i+1]='o'; input[i+2]='g'; 
        }

        i++; 
        size++;  
    }

    i=0; // reset for next loop

    while(i <= size) // printing the text out ofthe arry 
    {
         putchar(input[i]); 
         i++;
    }

    printf("\n"); 
    return 0;
}

output:

enter text here                                                                                                                                  
cat                                                                                                                                              
cat                                                                                                                                              
ȵm�� $$ŵ��p��$���Zտ ��$��M��v���������������������      ������������!��d@8                                                                    $  

�                                                                                                                                                
�����������5_Segmentation fault

Upvotes: 0

Views: 158

Answers (4)

Nominal Animal
Nominal Animal

Reputation: 39406

This problem is perfect match for a finite-state machine.

Here's a flowchart of how a finite-state machine could do the job: Example finite state machine flowchart

It might be a surprise that the non-matching cases go back to checking if the latest character matches 'C', rather than just printing it and getting a new one. The reason for this is that if we read say ccat or cacat, we want to check if we still match a (shorter) prefix, in this case c.

As a different example, consider if we were trying to match cocoa, and our input was cococoa. At the fifth character, we read c instead of a (already having matched coco, but not output anything yet), so we'd need to output co, and go back to matching for the second c.

We humans do not normally build such state machines by hand. We already have one for strings, either as a library or built-in to POSIX-compatible C libraries (Linux, Mac, BSDs): regular expression matching. If we use the basic POSIX ones, then regcomp() builds an efficient state machine based on our specifications, and regexec() processes input data on it.

This particular case is simple enough to implement by hand, though. What we'll want to do, is to put the first state ("Get next char") outside a loop, do a loop that continues as long as "char = EOF" is not true, and do the rest inside the loop. The final "Done" state is then after the loop. In pseudocode:

ch = Next char
While ch != EOF:

    If ch != 'c':
        Output ch
        ch = Next char
        Continue
    End If

    # The second "Get next char" in the flowchart:
    ch = Next char

    If ch == EOF:
        Output 'c'
        Break
    Else
    If ch != 'a':
        Output 'c'
        Continue
    End If

    # The third "Get next char" in the flowchart
    ch = Next char

    If ch == EOF:
        Output 'c'
        Output 'a'
        Break
    Else
    If ch != 't':
        Output 'c'
        Output 'a'
        Continue
    End If

    # 'c' 'a' 't' matched (with ch == 't').
    Output 'd'
    Output 'o'
    Output 'g'

    ch = Next char
End While
Done

A C program that reads standard input, converts each occurrence of cat to dog, case sensitive, can therefore be written as

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

void cat_to_dog(FILE *in, FILE *out)
{
    int ch;

    ch = fgetc(in);
    while (ch != EOF) {
        if (ch != 'c') {
            fputc(ch, out);
            ch = fgetc(in);
            continue;
        }

        ch = fgetc(in);
        if (ch == EOF) {
            fputc('c', out);
            break;
        } else
        if (ch != 'a') {
            fputc('c', out);
            continue;
        }

        ch = fgetc(in);
        if (ch == EOF) {
            fputc('c', out);
            fputc('a', out);
            break;
        } else
        if (ch != 't') {
            fputc('c', out);
            fputc('a', out);
            continue;
        }

        fputc('d', out);
        fputc('o', out);
        fputc('g', out);

        ch = fgetc(in);
    }
}

int main(void)
{
    cat_to_dog(stdin, stdout);
    return EXIT_SUCCESS;
}

The issue with finite state machines is that the code tends to be write-only. To understand the code, or to verify or maintain it in any time scales, you really need the definition of the finite-state machine, so one can compare the implemented code to the finite-state machine.

And here we finally get to the point of this "answer": Proper Documentation.

Solving a problem once using carefully-crafted, extremely tight and efficient code, is worth nothing if there is no way to modify or fix bugs in the code. (Even the very best programmer in the world makes mistakes and bugs at various levels of complexity. If someone claims they do not, they're lying.)

We could document the finite state machine by explaining it in comments interspersed with the above code. That would be fine; comments should always explain the intent of the programmer, the purpose or the task a piece of code is intended to accomplish. We often instead write comments that tells what the code does, which is less than useful, because we can easily read the code to see what it does. What we do not know, is whether the code matches the programmers intent, or whether the programmers intent is a valid solution to the underlying problem!

Another possibility would be to include the diagram, perhaps numbering the actions (ovals) and tests (parallelograms), and add comments in the code referring to the diagram. This is easier, but not as easy to follow (because you need to constantly cross-reference the code and the diagram).

It is, sadly, very common to omit the documentation part ("I'll do it later when I have more time"), and simply verify that the code works for correct input. It is often impossible to fully test the code for all possible inputs (although this one is so simple it can be), so a lot of bugs are left uncovered. Without documentation, so that a human can check the code and try and assess if it is logically correct (i.e., that the "flowchart", or finite state machine that it implements, is correct), and whether the code matches the working model or not, bugs are only found by being bitten by them in practice. Which is nasty.

Finite-state machines are a prime example of how important comments (and documentation) are, but it really applies to all code you write. Learn to try to describe your reasoning (model of the solution) and intent (what you want the code to accomplish) in your comments, and write a lot of comments, from the get go. It is very hard to get into the habit later on; I'm personally still struggling with this, after decades of programming. If a comment is later found to be unnecessary, it takes less than a fraction of a second to remove it; but if it explains a crucial quirk or complex detail that us humans don't normally notice, it may save hours, days, or even weeks of developer time later on.

This also means that the practice of commenting out unused code is not very useful, because the actual comments very quickly diverge from the code the compiler (or interpreter) sees. Instead, learn to use a version control system for your sources; I recommend git. It is available for just about all operating systems (see here), and you can use it both locally on your computer, as well as for distributed projects, in case you want to put your code on GitHub or similar services (or even set up your own git server). That way you can keep your code and its comments in sync; and when modifying your code, you can separately describe the reasons for those changes (changesets). After you get the hang of it, you'll find it is not a burden, but actually speeds up your code development!

Upvotes: 2

Andy J
Andy J

Reputation: 1545

Kiran mentions in his answer that "You are checking a and t chars which are not read yet". You can get around this by exploiting the use of argc and argv.

This is my version of your program using argc and argv. You'll notice that it also prevents you from limiting your input buffer (i.e. no input[20]).

#include <stdio.h>

int main(int argc, char **argv)
{
    int i = 0, j = 0;

    for(i=1; i<argc; i++)
    {
      while(argv[i][j] != '\0')
      {
        if(argv[i][j] == 'c')
        {
          if(((argv[i][j+1]) == 'a') && (argv[i][j+2] == 't'))
          {
            argv[i][j] = 'd';
            argv[i][j+1] = 'o';
            argv[i][j+2] = 'g';
          }
        }
        printf("%c", argv[i][j]);
        j++;
      }
      j=0;
      printf(" ");
    }
    printf("\n");
    return 0;
}

Sample input and output:

$ ./test my favourite cartoon when i was a kid was catdog
my favourite cartoon when i was a kid was dogdog 

$ ./test i used to love cats but now i love dogs
i used to love dogs but now i love dogs 

PS: Just in case you were born a little too late or didn't watch many cartoons on TV; here's the reference: https://en.wikipedia.org/wiki/CatDog.

Upvotes: 0

kiran Biradar
kiran Biradar

Reputation: 12742

Few problems here.

  1. Uninitialized local variables.

    int i = 0; // loop counter int size = 0; // size of array

  2. You are checking a and t chars which are not read yet.

    Hence check for the t in current inputted char if it is matching then check for a and c in previous entered chars as below.

    if(i>=2 && input[i-2]=='c' && input[i-1]=='a' && input[i]=='t') // switching characters
    {
        input[i]='g'; input[i-1]='o'; input[i-2]='d';
    }
    

Upvotes: 5

Jean-Marc Zimmer
Jean-Marc Zimmer

Reputation: 555

You're trying to access input[i], input[i + 1] and input[i + 2].
Use :

while (input[i + 2] && input[i + 2] != '\n')


In your case :

#include <stdio.h>

int main()
{
    int i = 0; // loop counter
    int size = 0; // size of array  
    int input[20];
    printf("enter text here\n");  

    while((input[i] = getchar()) != '\n' && i < 19) // input text to the array
    {
/*        if(input[i]=='c' && input[i+1]=='a' && input[i+2]=='t') // switching characters
        {
            input[i]='d'; input[i+1]='o'; input[i+2]='g'; 
        }
*/

        i++; 
        size++;  
    }
    input[i] = 0;//ALWAYS null-terminate arrays.
    if (i >= 2);
        while (input[i + 2]) {
            if (input[i] == 'c' && input[i + 1] == 'a' && input[i + 2] == 't') {
                input[i] = 'd';
                input[i + 1] = 'o';
                input[i + 2] = 'g';
            }
        }
    }

    i=0; // reset for next loop

    while(i < size) // printing the text out ofthe arry 
    {
        putchar(input[i]); 
        i++;
    }

    printf("\n"); 
    return 0;
}

Upvotes: -2

Related Questions