How do I count the number of sentences in C using ".", "?", "!"? - c

I'm fairly new to coding. I'm having trouble with my "CountSentences" function. I compare the string to "." , "?" , and ! to count a sentence. It only adds one to the sentence counter no matter how many of the punctuation marks I have in the string. Am I using strcmp wrong to get my desired result and is there another way I can approach this?
#include<cs50.h>
#include<ctype.h>
#include<string.h>
#include<math.h>
//function for letter count
int count_letters(string s)
{
int numberofLetters = 0; // counter
//loop as long as string length
for(int i = 0, n = strlen(s); i < n; i++)
{
//if character is alphanumeric
if(isalnum(s[i]) != 0)
{
numberofLetters++; //increase counter
};
};
return numberofLetters; //return new counter number
};
//function for word count
int count_Words(string w)
{
int numberofWords = 0;//counter for words declared
int i = 0; // counter for character in string
if(w == NULL) // if nothing
{
return numberofWords; // return Wordcount of 0
};
bool spaces = true; //truth value for space
//if character is not null terminating character
while(w[i] != '\0')
{
if(isblank(w[i]) != 0) //if character is blank
{
spaces = true; //its a space
}
else if(spaces) //if no more space and a letter is present add to words
{
numberofWords++; //add to number of words counter
spaces = false;
};
i++;// increase chracter count in string w
};
return numberofWords; //return total word counter
};
//function to count sentences
int count_Sentences(string l)
{
//variable counter for marks
int countMarks = 0;
//loop iteration using the number of characters in string
for(int i = 0, n = strlen(l); i < n; i++)
{
//check if character is ?, . , or !
if(strcmp(&l[i], "!") == 0 || strcmp(&l[i], ".") == 0 || strcmp(l, "?") == 0)
{
countMarks++;// sentence counted
};
};
// return the total number of marks
return countMarks;
};
int main (void)
{
string text = get_string ("Text: ");
//to check the functions bug checker
printf("Number of letters: %i\n", count_letters(text));
printf("Number of words: %i\n", count_Words(text));
printf("Number of sentences: %i\n", count_Sentences(text));
//Coleman Liau Index
int grade = round(0.0588 * (100 * (count_letters(text)) / (count_Words(text))) - 0.296 * (100 *(count_Sentences(text)) / (count_Words(text))) - 15.8 );
if(grade <= 1)
{
printf("Before Grade 1\n");
}
else if(grade < 16)
{
printf("Grade %i\n", grade);
}
else
{
printf("Grade 16+\n");
};
};

if(strcmp(&l[i], "!") == 0 || strcmp(&l[i], ".") == 0 || strcmp(l, "?") == 0)
strcmp compares two strings. In C, our "string" is essentially "the char-sized data starting at the place this pointer points to, and continuing until a null terminator". The cs50 library does not change this and does not give you a real string type; it only provides a typedef and some helper functions for reading input. (It also, sadly, does not and realistically cannot give you a real text character type, which char also is not; but that is beyond the scope of this answer.)
&l[i] is a pointer into the middle of the l string, starting at offset i. When that pointer is used by strcmp, it will treat the "string" as everything from that character to the end of the original string - because that's where the null terminator is. It will, in particular, not treat the single l[i] char as a separate string, in general, because the next char is in general not a null terminator. So,
It only adds one to the sentence counter no matter how many of the punctuation marks I have in the string.
In fact, it only even adds one because your string ends with one of those marks.
To compare individual chars, don't use strcmp. It is not intended nor fit for the purpose. A char is a single entity, so it can be compared just fine with ==. You just need to have something appropriate on both sides of the comparison.
Recall, in C single quotes are used for char literals, and indexing into the char array (equivalently, "indexing" into a char pointer, which performs the equivalent pointer arithmetic) gives you a char. Thus:
if (l[i] == '!' || l[i] == '.' || l[i] == '?')

You indeed need to see if the one character l[i] is any of ., ? or !. To do that, you can test if that is equal to any of these character constants, i.e. l[i] == '!' || l[i] == '.' || l[i] == '?'
Or you can use the function strchr that will look for a given character in a given string and return a pointer to that character, or a null pointer if the character is not found. A null pointer will be considered as falsy in if, and and a non-null pointer truthy. So we can look for l[i] in the string ".?!":
if (strchr(".?!", l[i])) {
...
}

Related

How can I fix my program to not crash if I run any character that is not alphabetical?

So I am trying to write code that will allow me to count the number of letters a user has entered.
My code runs well if I simply type one word.
The moment that I include any character that is not a letter, my terminal stops working.
What can I do to fix it?
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
int count_letters(string text);
int main(void)
{
string text = get_string("text? ");
int letters = count_letters(text);
printf("%i letters\n", letters);
}
int count_letters(string text)
{
int i = 0;
do
{
if (isalpha(text[i]))
{
i++;
}
else if (isspace(text[i]))
{
i = + 0;
}
else
{
i = +0;
}
} while (text[i] != 0);
return i;
}
i is the do ... while counter, do not use it to count the number of letters, use another variable, something like:
int count_letters(string text)
{
int i = 0, letters = 0;
do
{
if (isalpha((unsigned char)text[i]))
{
letters++;
}
i++;
}
while (text[i] != 0);
return letters;
}
Notice that with your approach the loop is also testing the NUL terminator in an empty string, use a while loop instead to exit the loop as soon as the NUL is found:
int count_letters(string text)
{
int letters = 0;
while (*text)
{
if (isalpha((unsigned char)*text))
{
letters++;
}
text++;
}
return letters;
}
Consider what happens in your loop: i only increments when the character at text[i] is an alphabetic character. Otherwise it resets to zero.
Thus, the loop will never complete for a string that is not entirely alphabetic characters because the loop will "reset" to the beginning with ever non-alphabetic character.
We can increment an index from zero until the character at that index in the string is the null-terminator of zero (which is false in C).
int count_letters(string text) {
int count = 0;
for (int i = 0; text[i]; i++) {
if (isalpha(text[i])) count++;
}
return count;
}
We can however, use pointers to iterate over your string, taking advantage of the detection of the null terminator to terminate the loop. If each character is an alphabetic character, increment a counter. A for loop can handle giving you a temp pointer and initializing it, testing for termination of the loop and incrementing the pointer.
int count_letters(string text) {
int count = 0;
for (char *ch = text; *ch; ch++) {
if (isalpha(*ch)) count++;
}
return count;
}
As text is a pointer passed by value (being hidden by CS50's string typedef), it can be modified without affecting the original string as long as we don't dereference and modify the individual characters, so we can avoid the extra char pointer.
int count_letters(string text) {
int count = 0;
for (; *text; text++) {
if (isalpha(*text)) count++;
}
return count;
}
As pointed out in the comment, i cannot serve two purposes (index and counter) except for the special case of a string comprised of only letters.
Below is a compact function that counts 'hits' when isalpha() has returned a non-zero value (indicating the current letter is in the range "A-Za-z").
The unusual "!!" transforms the non-zero positive value into C's true (or false) having values of 1 (or 0) respectively. Thusly, the value of 1 or 0 is appropriately added to the accumulator to be returned to the caller.
int count_letters( string text ) {
int i = 0, cnt = 0;
if( text != NULL ) // trust is good, testing is better
while( text[i] )
cnt += !!isalpha( (unsigned char)text[i++] );
return cnt;
}
EDIT and Credit: #Aconcagua for pointing out the need for casting each char to unsigned char to avoid UB.

how to fix this code so that it can test the integers present next to the character?

Given a string containing alphanumeric characters, calculate the sum of all numbers present in the string.
The problem with my code is that it displays the integers present before the characters, but it is not summing up the integers after the characters.
The execution is easy in python and C++ but I cant get it done using C! Can anyone please verify where I have done wrong? << thank you !
enter code here
#include<stdio.h>
#include<string.h>
int convert(char[]);
int main()
{
char ch[100],temp[100]={0};
int i=0,s=0,j=0,n;
scanf("%s",ch);
for(i=0;i<strlen(ch);i++)
{
if((ch[i]>='0') && (ch[i]<='9'))
{
temp[j]=ch[i];
j++;
}
else
{
if(temp[0]== '\0')
{
continue;
}
else
{
n=convert(temp);
s+=n;
temp[0]= '\0';
j=0;
}
}
}
printf("%d",s);
return 0;
}
int convert(char s[]) //converting string to integer
{
int n=0;
for(int i=0;i<strlen(s);i++)
{
n= n * 10 + s[i] - '0';
}
return n;
}
Input : 12abcd4
Expected output : 16
But the output is 12 for my code.
There are two problems in your code. The first was mentioned in the comments : if the last character is a digit, the last "number section" will not be taken into account. But I don't think that the solution given in the comments is good because if the last character is not a digit, you will have a wrong value. To correct this, I added an if statement that check if the last character is a digit, if so call convert().
The second problem is that strlen return the number of characters in you string from the beginning until it finds an '\0'. The way you used your string lead to the follow problem :
ch = "12abcd4".
At first you have temp = '1' + '2' + '\0'...
After calling convert() you set temp[0] to '\0', thus temp = '\0' + '2' + '\0'... .
And when you start reading digit again, you set '4' in temp[0]. Your string is now : '4' + '2' + '\0'... .
The n returned will be 42 and your result 54 (12+42). There are several solution to have the expected behavior, I chose to use your variable j to indicate how many characters should be read instead of using strlen() :
#include<stdio.h>
#include<string.h>
int convert(char[], int size);
int main() {
char ch[100],temp[100]={0};
int i=0,s=0,j=0,n;
scanf("%s",ch);
for(i=0;i<strlen(ch);i++) {
if((ch[i]>='0') && (ch[i]<='9')) {
temp[j]=ch[i];
j++;
// change here
if(i == strlen(ch) - 1) {
n=convert(temp, j);
s+=n;
}
}
else {
// change here
n=convert(temp, j);
s+=n;
if(temp[0]== '\0') {
continue;
}
temp[0]= '\0';
j=0;
}
}
printf("%d\n",s);
return 0;
}
//change here
int convert(char s[], int size) {
int n=0;
for(int i=0;i<size;i++) {
n= n * 10 + s[i] - '0';
}
return n;
}
You could use a combination of strtoul() and strpbrk() to do this.
Declare two character pointers start_ptr and end_ptr and make start_ptr point to the beginning of the string under consideration.
char *start_ptr=s, *end_ptr;
where s is the character array of size 100 holding the string.
Since your string has only alphanumeric characters, there is no - sign and hence there are no negative numbers. So we can get away with using unsigned integers.
We are using strtoul() from stdlib.h to perform the string to integer conversion. So let's declare two variables: rv for holding the value returned by strtoul() and sum to hold the sum of numbers.
unsigned long rv, sum_val=0;
Now use a loop:
for(; start_ptr!=NULL; )
{
rv = strtoul(start_ptr, &end_ptr, 10);
if(rv==ULONG_MAX && errno==ERANGE)
{
//out of range!
printf("\nOut of range.");
break;
}
else
{
printf("\n%lu", rv);
sum_val += rv;
start_ptr=strpbrk(end_ptr, "0123456789");
}
}
strtoul() will convert as much part of the string as possible and then make end_ptr point to the first character of the part of the string that could not be converted.
It will return ULONG_MAX if the number is too big and errno would be set to ERANGE.
Otherwise the converted number is returned.
strpbrk() would search for a set of characters (in this case the characters 0-9) and return a pointer to the first match. Otherwise NULL is returned.
Don't forget to include the following header files:
stdlib.h ---> strtoul
string.h ---> strpbrk
limits.h ---> ULONG_MAX
errno.h ---> errno
In short, we could make the program to something like
for(; start_ptr!=NULL; sum_val += rv, start_ptr=strpbrk(end_ptr, "0123456789"))
{
rv = strtoul(start_ptr, &end_ptr, 10);
if(rv==ULONG_MAX && errno==ERANGE)
{
//out of range!
break;
}
}
printf("\n\n%lu", sum_val);
So the value of sum_val for the string "12abcd4" would be 16.
scanf() is usually not the best way to accept input that is not well-formatted. Maybe you can use fgets()-sscanf() combo instead.
If you must use scanf(), make sure that you check the value returned by it, which in your case must be 1 (the number of successful assignments that scanf() made).
And to prevent overflow, use a width specifier as in
scanf("%99s",ch);
instead of
scanf("%s",ch);
as 100 is the size of the ch character array and we need one extra byte to store the string delimiter (the \0 character).

C: Comparing hash value seems to disappear

For the love of holy code, I am trying to compare hashes to find the correct password. I am given a hash as a command line argument, and I then hash words from "a" to "ZZZZ" until one of the hash pairs match.
void decipher(string hash)
{
//Set the password, and the salt.
char pass[4] = "a";
char salt[] ="50";
//Compare the crypted pass againts the hash until found.
while (strcmp(hash,crypt(pass, salt)) != 0)
{
//Use int i to hold position, and return next char
int i = 0;
pass[i] = get_next(pass[i]);
tick_over (pass, i);
//Hardcode in a fail safe max length: exit.
if (strlen(pass) > 4)
{
break;
}
}
printf("%s\n", pass);
}
The problem is that it will not 'catch' the correct password / comparison, when that password is 4 letters long. It works for 1,2 and 3 letter long words.
//Tick over casino style
string tick_over (string pass, int i)
{
//Once a char reaches 'Z', move the next char in line up one value.
char a[] = "a";
if (pass[i] == 'Z')
{
if (strlen(pass) < i+2)
{
strncat (pass, &a[0], 1);
return pass;
}
pass[i+1] = get_next(pass[i+1]);
//Recursively run again, moving along the string as necessary
tick_over (pass, i+1);
}
return pass;
}
//Give the next character in the sequence of available characters
char get_next (char y)
{
if (y == 'z')
{
return 'A';
}
else if (y == 'Z')
{
return 'a';
}
else
{
return y + 1;
}
}
It does iterate through the correct word, as I have found in debugging. I have tried moving the
strcmp(hash, crypt(pass, salt)) == 0
into a nested if statement among other things, but it doesn't seem to be the problem. Is c somehow 'forgetting' the command line value? When debugging the hash value seemed to have disappeared :/ Please help!
char pass[4] = "a"; you're defining a char array which can contain at most 3 chars + null terminator.
that's not coherent with your "safety" test: if (strlen(pass) > 4)
When strlen is 4 the array is already overwriting something in memory because of the null termination char: undefined behaviour.
Quickfix: char pass[5] ...
Here is the explanation of the function strncat:
Append characters from string
Appends the first num characters of source to destination, plus a terminating null-character.
with a size of 4 you are not considering the terminating null character of your four chars array.

2D Pointer to 2D Pointer

I forgot most of my C, so please forgive me if this is a stupid question. Because I need to separate a string of words into individual words.
#include "argsInfo.h"
#include <stdlib.h>
/* Parses string argument which contains words
* separated by whitespace. It returns an
* argsInfo data structure which contains an
* array of the parsed words and the number
* of words in the array.
*/
argsInfo getArgsInfo(char * string) {
argsInfo info;
char ** temp;
int nWords=1;
int i=0;
int j,k;
//Test if the the input string is empty
if (string[0] == '\0'){
nWords=0;
}else{
//First I need to check how long the input String is, as-well as cout how many words are in the string.
while (string[i] != '\0'){
if (string[i] == ' '){
nWords++;
}
i++;
}
}
//This allocates enough memory for each word.
temp = (char**) malloc(nWords*sizeof(char*));
for (j=0;j<nWords;j++){
temp[j] = (char*) malloc(i*sizeof(char));
}
j=0;
k=0;
// If I encounter a white space, it signifies a new word, and I need to move it to the next element
while (j < i){
if (string[j] == ' '){
k++;
}
temp[k][j] = string[j];
j++;
}
info.argc = nWords;
info.argv = temp;
return info;
}
That 3rd last LINE. THAT'S where I think the problem is. info.argv = temp;
This is what the struct looks like:
typedef struct {
int argc;
char ** argv;
} argsInfo;
Example Input and Output:
Input: "ax bcd efghij"
Output: ax
If I remove the k++ line, the output becomes: ax bcd efghij
Likewise, if I input a b c. Only 'a' will show up when I run through the array.
First, this part is inefficient but works:
for (j=0;j<nWords;j++){
temp[j] = (char*) malloc(i*sizeof(char));
}
You are using the value of i which will be equal to the total number of characters in your original input string. This means that for each separate word you are allocation enough room to store the original sentence which is a waste of space.
You could, for example, while you are counting words, also remember the longest word seen thus far and use that as your allocation factor which will probably be much less than the whole sentence. We start the length at 1 to include the terminating character '\0'
int longest = 1;
int tempLength = 1;
//Test if the the input string is empty
if (string[0] == '\0'){
nWords=0;
}else{
//First I need to check how long the input String is,
//as-well as count how many words are in the string.
while (string[i] != '\0'){
if (string[i] == ' '){
if(tempLength > longest) {
longest = tempLength;
}
nWords++;
} else {
tempLength++; // count characters of current word
}
i++;
}
}
for (j=0;j<nWords;j++){
temp[j] = (char*) malloc(longest*sizeof(char));
}
Finally, the last part of your code needs a fix. It doesn't work because you are using j as an index in the overall sentence and as an index in a single word. You never reset j.
Let's say the first word is
apple
Once you encounter a space, you will have:
j = 5
temp[0] = "apple"
Now you increment k to 1 but j stays the same so you will start storing characters of the next word from position 5 instead of 0:
temp[1][5] = string[5];
Instead of:
temp[1][0] = string[5];
Therefore, you have 3 indexes to worry about:
Index a that iterates over the input string.
Index b that iterates over a single word of the string.
Index c that iterates over the array of words.
The code:
int a, b, c;
for(a = 0, b = 0, c = 0; a < i; a++) { // index i holds the total number of chars in input string
if(string[a] != ' ') {
temp[c][b] = string[a];
b++;
} else {
temp[c][b] = '/0'; // add terminating character to current word
b = 0;
c++;
}
}
info.argc = nWords;
info.argv = temp;
return info;
Pretty sure this is what you were after. This should only require scanning the string once. Your index math has several issues:
Your calculation of i is inefficient.
The hoops nWords seems to go through questionable
You don't seem to be interested in terminating each word, which is very bad.
That said, walk through the following very carefully in a debugger to see how it works.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
argsInfo getArgsInfo(const char * s)
{
argsInfo info = {0,NULL};
while (*s)
{
// find start of next word
while (*s && isspace((unsigned char)*s))
++s;
// find end of next word
const char *beg = s;
while (*s && !isspace((unsigned char)*s))
++s;
if ((s - beg) > 0)
{
char **tmp = realloc(info.argv, (info.argc+1)*sizeof(*tmp));
if (tmp)
{
info.argv = tmp;
tmp[info.argc] = malloc((s - beg + 1) * sizeof(char));
if (tmp[info.argc] != NULL)
{
memcpy(tmp[info.argc], beg, s-beg);
tmp[info.argc++][s-beg] = 0; // <<= TERMINATE
}
else
{
perror("Failed to allocate string");
exit(EXIT_FAILURE);
}
}
else
{
perror("Failed to expand string pointer array");
exit(EXIT_FAILURE);
}
}
}
return info;
}

Checking two character arrays for equality in C

I am trying to write a program that checks to see if a word inputed to a program matches one of the predefined keywords. Input is going to be coming from a text file and the text file will have a single word in it. So far the text file I have just has the word 'crackerjack' which means the program should clearly print 'Match Found' but it is not currently doing that. Here is my code, does anything stand out to you guys? Thanks
#define NUM 4
#define SIZE 11
int isAlpha(char);
//Returns 1 if it is an Alphabetical character, 0 if it is not
int isAlpha(char c) {
return (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z');
}
int main() {
char message[141];
int charCount = 0, c = 0, matchCheck = 0;
char keywords[NUM][SIZE] = {
"crackerjack",
"Hey",
"dog",
"fish"
};
//Removes non alphabetical characters
while((c = getchar()) != EOF && charCount <= 140) {
if(isAlpha(c)){
message[charCount] = c;
charCount++;
}
printf("%d", isAlpha(c));
}
//checks if message matches keyword
for (int i = 0; i < NUM; i++) {
for (int j = 0; j < SIZE; j++) {
//Check if current two characters match
if (message[j] == keywords[i][j]) {
//Check if the two matched characters are the null terminator character
if (message[j] == '\0' && keywords[i][j] == '\0')
matchCheck = 1;
break;
}
//if characters are not the same, break from loop
else {
break;
}
}
}
//prints "Match Found!" if there was a match
if (matchCheck == 1) {
printf("Match Found!\n");
}
}
There are 3 problems in your code. Two of them have already been addressed:
Ensure that SIZE is big enough to include a '\0' at the end of the longest keyword
Ensure that the text file includes a '\0' at the end of the word. If it is not the case or it is out of your control, you can always manually end the string with a '\0' after reading it.
You are missing brackets on your second if statement. This causes the break statement to be executed every time the first if statement is entered.
SIZE is too small. Make room for '\0'.
#define SIZE 12
char keywords[NUM][SIZE] = {
"crackerjack",
...
};
I see now this is effectively what #user3121023 said. Credit to #user3121023.

Resources