Reputation: 65
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
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:
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
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
Reputation: 12742
Few problems here.
Uninitialized local variables.
int i = 0; // loop counter
int size = 0; // size of array
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
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