count the total no. of keywords in the file - c

I want to count the total no. of keywords in the file but the code counts those keywords that are used to declare the variable.
void main()
{
//2d array used to store the keywords but few of them are used.
char key[32][12]={"int","char","while","for","if","else"};
//cnt is used to count the occurrence of the keyword in the file.
int cnt=0,i;
//used to store the string that is read line by line.
char ch[100];
FILE *fp=fopen("key.c","r");
//to check whether file exists or not
if(fp=='\0')
{
printf("file not found..\n");
exit(0);
}
//to extract the word till it don't reach the end of file
while((fscanf(fp,"%s",ch))!=EOF)
{
//compare the keyword with the word present in the file.
for(i=0;i<32;i++)
{
// compare the keyword with the string in ch.
if(strcmp(key[i],ch)==0) {
//just to check which keyword is printed.
printf("\nkeyword is : %s",ch);
cnt++;
}
}
}
printf("\n Total no. of keywords are : %d", cnt);
fclose(fp);
}
Expected output should be:
Total no. of keywords are : 7
Actual output is coming :
Total no. of keywords are : 3

fscanf(fp,"%s",ch) will match a sequence of non-whitespace characters (see cpp reference), so in your case for, while and if won't be matched as single words - because there's no space after them.

In my opinion, but diverting a little from your intention, you had better to use flex(1) for that purpose, as it will scan the file more efficiently than comparing each sequence with the set of words you may have. This approach will require more processing, as several keywords can be in the same line, and it only filters which lines have keywords on them.
Also, using flex(1) will give you a more efficient C source code, a sample input for flex(1) would be:
%{
unsigned long count = 0;
%}
%%
int |
char |
unsigned |
signed |
static |
auto |
do |
while |
if |
else |
/* ... add more keywords as you want here */
return |
break |
continue |
volatile { printf("keyword is = %s\n", yytext);
count++;
}
\n |
. ;
%%
int yywrap()
{
return 1;
}
int main()
{
yylex();
printf("count = %lu\n", count);
}
The efficiency comes basically from the fact that flex(1) uses a special algorithm that gets the right match with only scanning once the source file (one decision per char, all the patterns are scanned in parallel). The problem in your code comes from the fact that %s format has a special interpretation of what it considers is a word, different as the one defined by the C language (for scanf() a word si something surrounded by spaces, where spaces means \n, \t or only --- it will match as a word something like while(a==b) if you don't put spaces around your keywords). Also, If you need to compare each input pattern with each of the words your algorithm will end doing N passes through each input file character (with each letter meaning N = nw * awl (being N the number of times you compare each character and nw the number of words, awl the average of the list of word lengths in your set) By the way, keywords should not be recognised inside comments, or string literals, It is easy to adapt the code you see above to reject those and do a right scanning. For example, the next flex file will do this:
%{
unsigned long count = 0;
%}
%x COMM1
%x COMM2
%x STRLIT
%x CHRLIT
%%
int |
char |
unsigned |
signed |
static |
auto |
do |
while |
if |
else |
/* ... */
return |
break |
continue |
volatile { printf("kw is %s\n", yytext);
count++;
}
[a-zA-Z_][a-zA-Z0-9_]* |
^[\ \t]*#.* |
"/*"([^*]|\*[^/])*"*/" |
"//".* |
\"([^"\n]|\\")*\" |
\'([^'\n]|\\')*\' |
. |
\n ;
%%
int yywrap()
{
return 1;
}
int main()
{
yylex();
printf("count = %lu\n", count);
}
It allows different regular expressions to be recognised as language tokens, so provision is given to match also C language constructs like identifiers ([a-zA-Z_][a-zA-Z0-9_]*), preprocessor directives (^[\ \t]*#.*), old style C comments ("/*"([^*]|\*[^/])*"*/"), new C++ style comments ("//".*), string literals (\"([^"\n]|\\")*\"), character literals (\'([^'\n]|\\')*\'), where keywords cannot be identified as such.
Flex(1) is worth learning, as it simplifies a lot the input of structured data into a program. I suggest you to study it.
note
you had better to write if (fp == NULL), or even if (!fp)... (You are not doing anything incorrect in your statement if (fp == '\0'), but as \0 is the char representation of the nul character, it's somewhat inconvenient, strange or imprecise to compare a pointer value with a character literal, and suggests you are interpreting the FILE * not as a pointer, but more as an integer (or char) value.) But I repeat, it's something perfectly legal in C language.
note 2
The flex sample code posted above doesn't consider the possibility of running out of buffer space due to input very long tokens (like several line comments overflowing internal buffer space) This is done on purpose, to simplify description and to make the code simpler. Of course, in a professional scanner, all of these must be acquainted for.

Related

How do I read data from a text file and store it in my C program to be used in functions?

I'm trying to read different data types on the same line of a text file, and currently trying to store them in their own arrays via a structure. I'm not sure if this is the best course of action to begin with, but the point is to read data from a file and manipulate it using different functions. I thought that if I could extract the data from the file and store it in arrays, I could send the arrays into functions with the arrays as their parameters. Here's what I have, and the problem explained within the main function:
Driver File:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "student_struct.c"
struct Student{
char name[50];
int id;
float gpa;
int age;
};
int main(){
FILE *fptr;
fptr = fopen("student_records.txt", "r");
if (fptr == NULL){
printf("Error opening file!\n");
exit(1);
}
struct Student students[100];
int i = 0;
while(!feof(fptr)){
//PROBLEM HERE. Data for what is expected to be in the "gpa" array is always 0.
fscanf(fptr, "%c %d %f %d", &students[i].name[i], &students[i].id, &students[i].gpa, &students[i].age);
i++;
}
fclose(fptr);
//Always prints "0.0000"
printf("GPA of student #2: %f\n", students[1].gpa);
//avgGPA(students.gpa);
return 0;
}
Function:
#include <stdio.h>
float avgGPA(float gpa[]){
int i;
float avgGPA = 0;
for(i = 0; i < sizeof(*gpa); i++){
avgGPA += gpa[i];
}
avgGPA = avgGPA / sizeof(*gpa);
printf("Average GPA: %f", avgGPA);
}
Text file:
David 1234 4.0 44
Sally 4321 3.6 21
Bob 1111 2.5 20
Greg 9999 1.8 28
Heather 0000 3.2 22
Keith 3434 2.7 40
Pat 1122 1.0 31
Ann 6565 3.0 15
Mike 9898 2.0 29
Steve 1010 2.2 24
Kristie 2222 3.9 46
My question is, how do I properly pull the data from the file and use it in different functions? Thank you for your help.
The %c in fscanf needs to be changed to %s. Refer to the fscanf man page for what each of the conversion specifiers mean. Specifically:
s
Matches a sequence of non-white-space characters; the next pointer must be a pointer to character array that is long enough to hold the input sequence and the terminating null byte ('\0'), which is added automatically. The input string stops at white space or at the maximum field width, whichever occurs first.
c
Matches a sequence of characters whose length is specified by the maximum field width (default 1); the next pointer must be a pointer to char, and there must be enough room for all the characters (no terminating null byte is added). The usual skip of leading white space is suppressed. To skip white space first, use an explicit space in the format.
In other words, %c by default only matches a single character. %s matches multiple non-white space characters (ie, colloquially a "word").
Other follow on questions you had:
but if the array in the structure "Student" is made of characters, why does it properly function with a string?
In C a string is defined as an array of characters terminated by a NUL (0).
At that end, why does that influence the rest of the operation?
%c will consume just one character. Which means the next modifier (%d in this case) will try to match with the remaining part of the first word and fail.
Other best practices that are relevant should be applied. Specifically:
Always check the return value of function calls. fscanf in particular for this case. If that were done you would be able to see that fscanf had failed to match most of the modifiers.
while !feof is always wrong. A full explanation of that is not provided here but please refer to other SO answers such as this.
Use a debugger to step through your code to help you examine the state of variables to better understand what the program is doing and where things go wrong.

How to split string (character) and variable in 1 line on C?

How can I split character and variable in 1 line?
Example
INPUT
car1900food2900ram800
OUTPUT
car 1900
food 2900
ram 800
Code
char namax[25];
int hargax;
scanf ("%s%s",&namax,&hargax);
printf ("%s %s",namax,hargax);
If I use code like that, I need double enter or space for make output. How can I split without that?
You should be able to use code like this to read one name and number:
if (scanf("%24[a-zA-Z]%d", namax, &hargax) == 2)
…got name and number OK…
else
…some sort of problem to be reported and handled…
You would need to wrap that in a loop of some sort in order to get three pairs of values. Note that using &namax as an argument to scanf() is technically wrong. The %s, %c and %[…] (scan set) notations all expect a char * argument, but you are passing a char (*)[25] which is quite different. A fortuitous coincidence means you usually get away with the abuse, but it is still not correct and omitting the & is easy (and correct).
You can find details about scan sets etc in the POSIX specification of scanf().
You should consider reading a whole line of input with fgets() or POSIX
getline(), and then processing the resulting string with sscanf(). This makes error reporting and error recovery easier. See also How to use sscanf() in loops.
Since you are asking this question which is actually easy, I presume you are somewhat a beginner in C programming. So instead of trying to split the input itself during the input which seems to be a bit too complicated for someone who's new to C programming, I would suggest something simpler(not efficient when you take memory into account).
Just accept the entire input as a String. Then check the string internally to check for digits and alphabets. I have used ASCII values of them to check. If you find an alphabet followed by a digit, print out the part of string from the last such occurrence till the current point. And while printing this do the same with just a slight tweak with the extracted sub-part, i.e, instead of checking for number followed by letter, check for letter followed by digit, and at that point print as many number of spaces as needed.
just so that you know:
ASCII value of digits (0-9) => 48 to 57
ASCII value of uppercase alphabet (A-Z) => 65 to 90
ASCII value of lowercase alphabets (a-z)
=> 97 to 122
Here is the code:
#include<stdio.h>
#include<string.h>
int main() {
char s[100];
int i, len, j, k = 0, x;
printf("\nenter the string:");
scanf("%s",s);
len = strlen(s);
for(i = 0; i < len; i++){
if(((int)s[i]>=48)&&((int)s[i]<=57)) {
if((((int)s[i+1]>=65)&&((int)s[i+1]<=90))||(((int)s[i+1]>=97)&&((int)s[i+1]<=122))||(i==len-1)) {
for(j = k; j < i+1; j++) {
if(((int)s[j]>=48)&&((int)s[j]<=57)) {
if((((int)s[j-1]>=65)&&((int)s[j-1]<=90))||(((int)s[j-1]>=97)&&((int)s[j-1]<=122))) {
printf("\t");
}
}
printf("%c",s[j]);
}
printf("\n");
k = i + 1;
}
}
}
return(0);
}
the output:
enter the string: car1900food2900ram800
car 1900
food 2900
ram 800
In addition to using a character class to include the characters to read as a string, you can also use the character class to exclude digits which would allow you to scan forward in the string until the next digit is found, taking all characters as your name and then reading the digits as an integer. You can then determine the number of characters consumed so far using the "%n" format specifier and use the resulting number of characters to offset your next read within the line, e.g.
char namax[MAXNM],
*p = buf;
int hargax,
off = 0;
while (sscanf (p, "%24[^0-9]%d%n", namax, &hargax, &off) == 2) {
printf ("%-24s %d\n", namax, hargax);
p += off;
}
Note how the sscanf format string will read up to 24 character that are not digits as namax and then the integer that follows as hargax storing the number of characters consumed in off which is then applied to the pointer p to advance within the buffer in preparation for your next parse with sscanf.
Putting it altogether in a short example, you could do:
#include <stdio.h>
#define MAXNM 25
#define MAXC 1024
int main (void) {
char buf[MAXC] = "";
while (fgets (buf, MAXC, stdin)) {
char namax[MAXNM],
*p = buf;
int hargax,
off = 0;
while (sscanf (p, "%24[^0-9]%d%n", namax, &hargax, &off) == 2) {
printf ("%-24s %d\n", namax, hargax);
p += off;
}
}
}
Example Use/Output
$ echo "car1900food2900ram800" | ./bin/fgetssscanf
car 1900
food 2900
ram 800

Lex/flex program to count ids, statements, keywords, operators etc

%{
#undef yywrap
#define yywrap() 1
#include<stdio.h>
int statements = 0;
int ids = 0;
int assign = 0;
int rel = 0;
int keywords = 0;
int integers = 0;
%}
DIGIT [0-9]
LETTER [A-Za-z]
TYPE int|char|bool|float|void|for|do|while|if|else|return|void
%option yylineno
%option noyywrap
%%
\n {statements++;}
{TYPE} {/*printf("%s\n",yytext);*/keywords++;}
(<|>|<=|>=|==) {rel++;}
'#'/[a-zA-Z0-9]* {;}
[a-zA-Z]+[a-zA-Z0-9]* {printf("%s\n",yytext);ids++;}
= {assign++;}
[0-9]+ {integers++;}
. {;}
%%
void main(int argc, char **argv)
{
FILE *fh;
if (argc == 2 && (fh = fopen(argv[1], "r"))) {
yyin = fh;
}
yylex();
printf("statements = %d ids = %d assign = %d rel = %d keywords = %d integers = %d \n",statements,ids,assign,rel,keywords,integers);
}
//Input file.c
#include<stdio.h>
void main(){
float a123;
char a;
char b123;
char c;
int ab[5];
int bc[2];
int ca[7];
int ds[4];
for( a = 0; a < 5 ;a++)
printf("%d ", a);
return 0;
}
output:
include
stdio
h
main
a123
a
b123
c
ab
bc
ca
ds
a
a
a
printf
d
a
statements = 14 ids = 18 assign = 1 rel = 3 keywords = 11 integers = 7
I am printing the identifiers on the go. #include<stdio.h> is being counted as identifier. How do I avoid this?
I have tried '#'/[a-zA-Z0-9]* {;} rule:action pair but it is still being counted as identifier. How is the file being tokenized?
Also the %d string in printf is being counted as an identifier. I have explicitly written that identifiers should only begin with letters, then why is %d being inferred as identifier?
I have tried '#'/[a-zA-Z0-9]* {;} rule:action pair but it [include] is still being counted as identifier. How is the file being tokenized?
Tokens are recognized one at a time. Each token starts where the previous token finished.
'#'/[a-zA-Z0-9]* matches '#' provided it is followed by [a-zA-Z0-9]*. You probably meant "#"/[a-zA-Z0-9]* (with double quotes) which would match a #, again provided it is followed by a letter or digit. Note that only the # is matched; the pattern after the / is "trailing context", which is basically a lookahead assertion. In this case, the lookahead is pointless because [a-zA-Z0-9]* can match the empty string, so any # would be matched. In any event, after the # is matched as a token, the scan continues at the next character. So the next token would be include.
Because of the typo, that pattern does not match. (There are no apostrophes in the source.) So what actually matches is your "fallback" rule: the rule whose pattern is .. (We call this a fallback rule because it matches anything. Really, it should be .|\n, since . matches anything but a newline, but as long as you have some rule which matches a newline character, it's acceptable to use .. If you don't supply a fallback rule, one will be inserted automatically by flex with the action ECHO.)
Thus, the # is ignored (just as it would have been if you'd written the rule as intended) and again the scan continues with the token include.
If you wanted to ignore the entire preprocessor directive, you could do something like
^[[:blank:]]#.* { ; }
(from a comment) I am getting stdio and h as keywords, how does that fit the definition that I have given? What happened to the . in between?
After the < is ignored by the fallback rule, stdio is matched. Since [a-zA-Z]+[a-zA-Z0-9]* doesn't match anything other than letters and digits, the . is not considered part of the token. Then the . is matched and ignored by the fallback rule, and then h is matched.
Also the %d string in printf is being counted as an identifier.
Not really. The % is explicitly ignored by the fallback rule (as was the ") and then the d is marched as an identifier. If you want to ignore words in string literals, you will have to recognise and ignore string literals.
The #include directive is a preprocessor directive and is thus preprocessed by the preprocessor. The preprocessor includes the header file and removes the #include directive And thus after preprocessing when the program is given to compiler as input it doesn't have any preprocessor directives like #include.
So you don't need to write code to detect #include because neither compiler ever sees it nor it is designed to tokenize #include directive.
References: Is #include a token of type keyword?
adding the following line in the rules section works for me:
#.* ;
Here rule is #.* and action is ;. The #.* will catch the line starting with # and ; will just do nothing so basically this would ignore the line starting with #.

Appending an output file in C

I am solving a problem on USACO. In the problem, I have to take two strings as inputs and calculate the numerical values modulo 47. If the values are same, then GO is to be printed otherwise STAY has to be printed. The initial numerical value will be calculated by taking the product of the numerical values of the alphabets ( 1 for A and similarily 26 for Z ) and then the final number will be calculated by using modulo.
My program is being compiled withour any error and the first case is also a success. But the problem is in the second case and the way my file is being appended. The program is as follows:-
#include<stdio.h>
#include<malloc.h>
#include<string.h>
#define MAX 6
main()
{
int cal(char *ptr);
int a,b;
char *comet,*group;
FILE *fptr;
comet=malloc(6*sizeof(char));
group=malloc(6*sizeof(char));
scanf("%s",comet);
a=cal(comet);
scanf("%s",group);
b=cal(group);
fptr=fopen("ride.out","a+"); (1)
//fptr=fopen("ride.txt","a+"); (2)
if(a==b)
fprintf(fptr,"GO\n"); (3)
//printf("GO\n"); (4)
else
fprintf(fptr,"STAY\n"); (5)
//printf("STAY\n"); (6)
fclose(fptr);
return 0;
}
int cal(char *ptr)
{
int c,prod=1,mod;
while(*ptr)
{
c=(*ptr++)-'A'+1;
prod=prod*c;
}
mod=prod%47;
return mod;
}
OUTPUT:-
The first case is the set two strings:-
COMETQ
HVNGAT
and the second case is given in the error notification itself.
If I remove the comment symbols from (2) and put it on (1), then the program is working fine because I can see the contents of the file and they appear just as the grader system wants. It isn't happening for the actual statement of (1). The comments of line (4) and (6) are also fine but not the line (1). I am not able figure this out. Any help?
First a few notes:
main(): a decent main is either:
int main(void)
or
int main(int argc, char *argv[])
Using malloc() you should always check if it returns NULL, aka fail, or not.
Always free() malloc'ed objects.
Everyone has his/hers/its own coding style. I have found this to be invaluable when it comes to C coding. Using it as a base for many other's to. Point being structured code is so much easier to read, debug, decode, etc.
More in detail on your code:
Signature of cal()
First line in main you declare the signature for cal(). Though this works you would probably put that above main, or put the cal() function in entirety above main.
Max length
You have a define #define MAX 6 that you never use. If it is maximum six characters and you read a string, you also have to account for the trailing zero.
E.g. from cplusplus.com scanf:
specifier 's': Any number of non-whitespace characters, stopping at the first whitespace character found. A terminating null character is automatically added at the end of the stored sequence.
Thus:
#define MAX_LEN_NAME 7
...
comet = malloc(sizeof(char) * MAX_LEN_NAME);
As it is good to learn to use malloc() there is nothing wrong about doing it like this here. But as it is as simple as it is you'd probably want to use:
char comet[MAX_LEN_NAME] = {0};
char group[MAX_LEN_NAME] = {0};
instead. At least: if using malloc then check for success and free when done, else use static array.
Safer scanf()
scanf() given "%s" does not stop reading at size of target buffer - it continues reading and writing the data to consecutive addresses in memory until it reads a white space.
E.g.:
/* data stream = "USACOeRRORbLAHbLAH NOP" */
comet = malloc(szieof(char) * 7);
scanf("%s", buf);
In memory we would have:
Address (example)
0x00000f comet[0]
0x000010 comet[1]
0x000011 comet[2]
0x000012 comet[3]
0x000013 comet[4]
0x000014 comet[5]
0x000015 comet[6]
0x000016 comet[7]
0x000017 /* Anything; Here e.g. group starts, or perhaps fptr */
0x000018 /* Anything; */
0x000019 /* Anything; */
...
And when reading the proposed stream/string above we would not read USACOe in to comet but we would continue reading beyond the range of comet. In other words (might) overwriting other variables etc. This might sound stupid but as C is a low level language this is one of the things you have to know. And as you learn more you'll most probably also learn to love the power of it :)
To prevent this you could limit the read by e.g. using maximum length + [what to read]. E.g:
scanf("%6[A-Z]", comet);
| | |
| | +------- Write to `comet`
| +-------------- Read only A to Z
+---------------- Read maximum 6 entities
Input data
Reading your expected result, your errors, your (N) comments etc. it sound like you should have a input file as well as an output file.
As your code is now it relies on reading data from standard input, aka stdin. Thus you also use scanf(). I suspect you should read from file with fscanf() instead.
So: something like:
FILE *fptr_in;
char *file_data = "ride.in";
int res;
...
if ((fptr_in = fopen(file_data, "r")) == NULL) {
fprintf(stderr, "Unable to open %s for reading.\n", file_data);
return 1; /* error code returned by program */
}
if ((res = fscanf(fptr_in, "%6[A-Z]%*[ \n]", comet)) != 1) {
fprintf(stderr, "Read comet failed. Got %d.\n", res);
return 2;
}
b = cal(comet);
if ((res = fscanf(fptr_in, "%6[A-Z]%*[ \n]", group)) != 1) {
fprintf(stderr, "Read group failed. Got %d.\n", res);
return 2;
}
...
The cal() function
First of, the naming. Say this was the beginning of a project that eventually would result in multiple files and thousand of lines of code. You would probably not have a function named cal(). Learn to give functions good names. The above link about coding style gives some points. IMHO do this in small projects as well. It is a good exercise that makes it easier when you write on bigger to huge ones. Name it e.g. cprod_mod_47().
Then the mod variable (and might c) is superfluous. An alternative could be:
int cprod_mod_47(char *str)
{
int prod = 1;
while (*str)
prod *= *(str++) - 'A' + 1;
return prod % 47;
}
Some more general suggestions
When compiling use many warning and error options. E.g. if using gcc say:
$ gcc -Wall -Wextra -pedantic -std=c89 -o my_prog my_prog.c
This is tremendous amount of help. Further is the use of tools like valgrind and gdb invaluable.

String replace in C?

Write a program that takes nouns and
forms their plurals on the basis of
these rules: a. If noun ends in “y”
remove the “y” and add “ies” b. If
noun ends in “s” , “ch”, or “sh”, add
“es” c. In all other cases, just add
“s” Print each noun and its plural.
Try the following data: chair
dairy boss circus fly dog
church clue dish
This is what I've got so far but it just isn't quite functioning like it's supposed to:
#include<stdlib.h>
#include <Windows.h>
#include <stdio.h>
#include <string.h>
#define SIZE 8
char *replace_str(char *str, char *orig, char *rep)
{
static char buffer[4096];
char *p;
if(!(p = strstr(str, orig)))
return str;
strncpy(buffer, str, p-str);
buffer[p-str] = '\0';
sprintf(buffer+(p-str), "%s%s", rep, p+strlen(orig));
return buffer;
}
int main(void)
{
char plural[SIZE];
printf("Enter a noun: ");
scanf("%c",&plural);
bool noreplace = false;
puts(replace_str(plural, "s","es"));
puts(replace_str(plural, "sh","es"));
puts(replace_str(plural, "ch","es"));
puts(replace_str(plural, "y", "ies"));
if(noreplace) {
puts(replace_str(plural, "","s"));
}
system("pause");
return 0;
}
I haven't taken a C class in a while can anyone help me out?
Thanks.
For a start, scanf("%c") gets a single character, not a string. You should use fgets for that, along the lines of:
fgets (buffer, SIZE, stdin);
// Remove newline if there.
size_t sz = strlen(buffer);
if (sz > 0 && buffer[sz-1] == '\n') buffer[sz-1] = '\0';
Once you've fixed that, we can turn to the function which pluralises the words along with a decent test harness. Make sure you keep your own main (with a fixed input method) since there's a couple of things in this harness which will probably make your educator suspect it's not your code. I'm just including it for our testing purposes here.
Start with something like:
#include <stdio.h>
#include <string.h>
char *pluralise(char *str) {
static char buffer[4096];
strcpy (buffer, str);
return buffer;
}
int main(void) {
char *test[] = {
"chair", "dairy", "boss", "circus", "fly",
"dog", "church", "clue", "dish"
};
for (size_t i = 0; i < sizeof(test)/sizeof(*test); i++)
printf ("%-8s -> %s\n", test[i], pluralise(test[i]));
return 0;
}
This basically just gives you back exactly what you passed in but it's a good start:
chair -> chair
dairy -> dairy
boss -> boss
circus -> circus
fly -> fly
dog -> dog
church -> church
clue -> clue
dish -> dish
The next step is to understand how to detect a specific ending and how to copy and modify the string to suit. The string is an array of characters of the form:
0 1 2 3 4 5
+---+---+---+---+---+---+
| c | h | a | i | r | $ |
+---+---+---+---+---+---+
where $ represents the null terminator \0. The numbers above give the offset from the start or the index that you can use to get a character from a particular position in that array. So str[3] will give you i.
Using that and the length of the string (strlen(str) will give you 5), you can check specific characters. You can also copy the characters to your target buffer and use a similar method to modify the end.
Like any good drug pusher, I'm going to give you the first hit for free :-)
char *pluralise(char *str) {
static char buffer[4096]; // Risky, see below.
size_t sz = strlen(str); // Get length.
if (sz >= 1 && str[sz-1] == 'y') { // Ends with 'y'?
strcpy(buffer, str); // Yes, copy whole buffer,
strcpy(&(buffer[sz-1]), "ies"); // overwrite final bit,
return buffer; // and return it.
}
strcpy(buffer, str); // If no rules matched,
strcat(buffer, "s"); // just add "s",
return buffer; // and return it.
}
Of particular interest there is the sequence:
strcpy(buffer, str);
strcpy(&(buffer[sz-1]), "ies");
The first line makes an exact copy of the string like:
0 1 2 3 4 5
+---+---+---+---+---+---+
| d | a | i | r | y | $ |
+---+---+---+---+---+---+
The second line copies the "ies" string into the memory location of buffer[sz-1]. Since sz is 5, that would be offset 4, resulting in the following change:
0 1 2 3 4 5
+---+---+---+---+---+---+
| d | a | i | r | y | $ |
+---+---+---+---+---+---+---+---+
| i | e | s | $ |
+---+---+---+---+
so that you end up with dairies.
From that, you should be able to use the same methods to detect the other string endings, and do similar copy/modify operations to correctly pluralise the strings.
Keep in mind that this is basic code meant to illustrate the concept, not necessarily hardened code that I would use in a production environment. For example, the declaration static char buffer[4096] has at least two problems that will occur under certain circumstances:
If your words are longer than about 4K in length, you'll get buffer overflow. However, even German, with its penchant for stringing basic words together in long sequences(a), doesn't have this problem :-) Still, it's something that should be catered for, if only to handle "invalid" input data.
Being static, the buffer will be shared amongst all threads calling this function if used in a multi-threaded environment. That's unlikely to end well as threads may corrupt the data of each other.
A relatively easy fix would be for the caller to also provide a buffer for the result, at least long enough to handle the largest possible expansion of a word to its plural form. But I've left that as a separate exercise since it's not really relevant to the question.
(a) Such as Donaudampfschiffahrtselektrizitätenhauptbetriebswerkbauunterbeamtengesellschaft :-)
You are reading a word as:
scanf("%c",&plural);
which is incorrect as it only reads one character.
Change it to:
scanf("%s",plural);
or even better use fgets as:
fgets (plural,SIZE, stdin);
But note that fgets might add a newline at the end of the string. If it does you need to remove it before you do the replacement as your replacement depends on the last character in the word.
Also your replacement part is incorrect. You are replacing any s with es (same with other replacements). You need to replace only the last s.
puts(replace_str(plural, "ch","es"));
Consider the input: church
strstr(3) will find the first ch, not the last ch. Ooops.
Furthermore, once you modify replace_str() to find the the last ch, you're still ripping it off and not putting it back on: chures. (Assuming your replace_str() functions as I think it does; that's some hairy code. :) So add the ch back on:
puts(replace_str(plural, "ch","ches"));
first of all u need to find last position of occurance and then call replace_str() function
and secondly scanf("%s",&plural);or use fgets()
Maybe this might help you:
str_replace
it's nicely done!

Resources