CS50-pset2: Readability. Incorrect outcome - arrays

When I run my code, the outcome has failed to show the exact result based on the texts given in the problem set. Although, it showed the grade, the result is incorrect. The text is : "Would you like them here or there? I would not like them here or there. I would not like them anywhere."(Grade 2)
enter image description here
Supposedly, the result for the text is "Grade 2". However, it shows all grades instead.
enter code here
int main(void)
{
string s = get_string("Text: ");
printf("%s\n",s);
int count_letters = 0; //To count letters (uppercase & lowercase)
int count_words = 1; //To count words
int count_sentences = 0; //To count sentences
for (int i = 0; i < strlen(s); i++)
if (isalpha(s[i]))
{
if ((s[i] >= 'a' && s[i] <= 'z' )||( s[i] >= 'A' && s[i] <= 'Z'))
{
count_letters++;
}
if (s[i] == ' ')
{
count_words++;
}
if (s[i] == '.' || s[i] =='!' || s[i] == '?')
{
count_sentences++;
}
//printf("%i count_letter(s)\n", count_letters);
//printf("%i count_words(s)\n", count_words);
//printf("%i sentence(s)\n", count_sentences);
//Coleman-Liau index
float L = (count_letters / (float) count_words) * 100;
float S = (count_sentences / (float) count_words) * 100;
int grade = round (0.0588 * L - 0.296 * S -15.8);
if (grade < 1)
{
printf("Before Grade 1\n");
}
else if (grade >= 16)
{
printf("Grade 16+\n");
}
else
{
printf("Grade %.d\n", grade);
}
}
}
Is there any problem with my code? How can I fix my code in order to receive the exact outcome. I've been doing this problem set for almost 2 days :'/. Thanks in advance

Calculate the number of letters, sentences, and words inside of the loop and calculate Coleman-Liau's index outside of the loop.
Don't calculate something in a loop and try to get specific output from within it as well, it never ends well. So in conclusion, calculate your values in the loop and do everything else outside of it.
int count_letters = 0; //To count letters (uppercase & lowercase)
int count_words = 1; //To count words
int count_sentences = 0; //To count sentences
for (int i = 0; i < strlen(s); i++){
// get the amounts in the loop
if (isalpha(s[i]))
{
if ((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z'))
{
count_letters++;
}
if (s[i] == ' ')
{
count_words++;
}
if (s[i] == '.' || s[i] == '!' || s[i] == '?')
{
count_sentences++;
}
}
}
//Calculate Coleman-Liau outside of it and get the correct grade from your if statements

Since you have three distinct categories to count, I would create a function for each of these.
For example, based on your code, you could create a function to count characters (you don't need isdigit function here, non-digit characters are already filtered out by the algorithm itself):
int get_letters_count(char *text_str)
{
int count_letters = 0;
int text_len = strlen(text_str);
for (int i = 0; i < text_len; i++) {
if ( (text_str[i] >= 'a' && text_str[i] <= 'z')
|| (text_str[i] >= 'A' && text_str[i] <= 'Z')
|| (text_str[i] >= '0' && text_str[i] <= '9')) {
count_letters++;
}
}
return count_letters;
}
This approach of breaking down your program will make it much easier to develop.
Here is a very crude implementation based on the Coleman–Liau index Wikipedia page:
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
int get_letters_count(char *text_str)
{
int count_letters = 0;
int text_len = strlen(text_str);
for (int i = 0; i < text_len; i++) {
if ( (text_str[i] >= 'a' && text_str[i] <= 'z')
|| (text_str[i] >= 'A' && text_str[i] <= 'Z')
|| (text_str[i] >= '0' && text_str[i] <= '9')) {
count_letters++;
}
}
return count_letters;
}
int get_words_count(char *text_str)
{
int count_words = 0;
int text_len = strlen(text_str);
for (int i = 0; i < text_len; i++) {
if (text_str[i] == ' ') {
count_words++;
}
}
if (count_words)
count_words++;
return count_words;
}
bool word_is_acronym(char *word)
{
bool ret = true;
for (; *word && *word != ' '; word++) {
if ( *word != '.'
&& *word < 'A' || *word > 'Z') {
ret = false;
}
}
return ret;
}
int get_sentences_count(char *text_str)
{
int count_sentences = 0;
int text_len = strlen(text_str);
char *last_word = &text_str[0];
for (int i = 0; i < text_len; i++) {
if ( text_str[i] == ' '
&& i < (text_len - 1)) {
last_word = &text_str[i + 1];
}
bool end_mark = text_str[i] == '.'
|| text_str[i] == '!'
|| text_str[i] == '?';
if ( end_mark
&& word_is_acronym(last_word) == false) {
count_sentences++;
}
}
return count_sentences;
}
int main(void)
{
char text_str[] = "Existing computer programs that measure readability are based "
"largely upon subroutines which estimate number of syllables, "
"usually by counting vowels. The shortcoming in estimating syllables "
"is that it necessitates keypunching the prose into the computer. "
"There is no need to estimate syllables since word length in letters "
"is a better predictor of readability than word length in syllables. "
"Therefore, a new readability formula was computed that has for its "
"predictors letters per 100 words and sentences per 100 words. "
"Both predictors can be counted by an optical scanning device, and "
"thus the formula makes it economically feasible for an organization "
"such as the U.S. Office of Education to calibrate the readability of "
"all textbooks for the public school system.";
int count_letters = get_letters_count(text_str);
int count_words = get_words_count(text_str);
int count_sentences = get_sentences_count(text_str);;
if ( count_letters > 0
&& count_words > 0
&& count_sentences > 0) {
float L = ((count_letters * 100) / count_words);
float S = ((count_sentences * 100) / count_words);
float grade = 0.0588 * L - 0.296 * S - 15.8;
printf("grade = %.01f\n", grade);
} else {
printf("bad input\n");
}
}
Ouput:
$ gcc main.c && ./a.out
grade = 14.5
Parsing text can be very trick, though.
Once you get a first version working with a known input such as this, try to expand your data set and keep improving your program.
This program is also far from being computationally efficient. If that becomes a bottleneck, you could optimize the functions or maybe reduce the number of loops grouping the functions in a single loop.
Certainly most times it's better to start with a crude working solution and improve from there instead of attempting a more sophisticated/complete solution right from the beginning.

Related

How do it get character frequency and highest character frequency?

so this is my function. My main focus is to get the character frequencies and the highest character frequency.
The function below (get_letter_frequencies) is supposed to get a string example ("I am a big boy") and return the character frequencies and the highest character frequency.
The Function should return
i - 2
a - 2
m - 1
b - 2
g - 1
o - 1
y - 1
Highest character frequency would be " iab "
My problem is with the get_letter_frequencies function. What should I arrange from the function in order to return the above output?
void get_letter_frequencies(const char *text, size_t len, int freq[26], int *max_freq)
{
for(int i = 0; i<len; i++)
{
if(text[i] != ' ' || !(is_sentence_terminator(text[i]))) //this condition is set in order to ignore the spaces and the sentence terminators (! ? .)
{
if(text[i] >= 'a' && text[i] <= 'z')
{
freq[text[i] - 'a']++;
}
}
}
for(int j = 0; j < 26; j++)
{
if(freq[j] >= 1)
{
*max_freq = freq[j];
}
}
This function below(is_sentence_terminator). Here the function checks whether the sentence finishes with a " ! ? or . " if it does not finish with one of the terminators then it is not a sentence and ignores it.
int is_sentence_terminator(char ch)
{
if(ch == 33 || ch == 46 || ch == 63)
{
return 1;
}else
{
return 0;
}
}
There are some issues in your code:
there is no need to test for special characters, comparing text[i] to 'a' and 'z' is sufficient for ASCII systems.
in the second loop, you should update *max_freq only if freq[j] is greater than the current value, not 1. *max_freq should be initialized to 0 before the loop.
In the calling code, you would also
print the letters whose frequency is non 0.
print all letters with the maximum frequency using one final loop.
Here is a modified version:
void get_letter_frequencies(const char *text, size_t len, int freq[26], int *max_freq) {
for (int i = 0; i < 26; i++)
freq[i] = 0;
for (int i = 0; i < len; i++) {
if (text[i] >= 'a' && text[i] <= 'z') {
freq[text[i] - 'a']++; // assuming ASCII
}
}
*max_freq = 0;
for (int i = 0; i < 26; i++) {
if (*max_freq < freq[i]) {
*max_freq = freq[i];
}
}
}

Mistake in CS50 Readability project with only 1 input, all others work

I seem to have made a mistake in my code but I can't find it.
All reading grades give me the correct grade, except for grade 7 which results in grade 8.
I assume it is a rounding error of some sort?
I tested the following piece of code with and without the round() in the last function.
Without it most of the grade levels are off, with the round() in there I only get an mistake a the grade7 level.
Where is my mistake?
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
//Prototypes
int count_letters(string text);
int count_words(string text);
int count_sentences(string text);
int get_score (string text);
//Main function
int main(void)
{
//Get user input
string text = get_string("Text: ");
//Grade user text
int i = get_score(text);
if(i < 1)
{
printf("Before Grade 1\n");
}
else if (i > 1 && i < 16)
{
printf("Grade %i\n", i);
}
else
{
printf("Grade 16+\n");
}
}
// Extra functions
int count_letters(string text)
{
// variables
int letters = strlen(text);
int total_letters = 0;
int characters = 0;
// Loop through text and count all non-letters
for(int i = 0; i < letters; i++)
{
if((text[i] < 65 || text[i] > 95) && (text[i] < 97 || text[i] > 122))
{
characters++;
}
}
// substract all non-letters from total chars and return.
total_letters = letters - characters;
return total_letters;
}
int count_words(string text)
{
// variables
int letters = strlen(text);
int spaces = 1;
// Loop through text and count all spaces
for(int i = 0; i < letters; i++)
{
if(text[i] == ' ')
{
spaces++;
}
}
return spaces;
}
int count_sentences(string text)
{
// variables
int letters = strlen(text);
int sentence = 0;
// Loop through text and count all sentences
for(int i = 0; i < letters; i++)
{
if(text[i] == 46 || text[i] == 33 || text[i] == 63)
{
sentence++;
}
}
return sentence;
}
int get_score (string text)
{
//variables
int letters = count_letters(text);
int words = count_words(text);
int sentence = count_sentences(text);
float index = 0;
// letters divided by words * 100
float L = 100 * letters / words;
// sentences divided by words *100
float S = 100 * sentence / words;
index = round(0.0588 * L - 0.296 * S - 15.8);
return index;
}
if((text[i] < 65 || text[i] > 95) && (text[i] < 97 || text[i] > 122)) is almost certainly a bug. You probably meant to be implementing isalpha, but you did it incorrectly. You meant to write:
if((text[i] < 'A' || text[i] > 'Z') && (text[i] < 'a' || text[i] > 'z')), which would have avoided the typo in which 95 was used instead of 90. Instead of this, though, you should just use the standard library and write:
if( ! isalpha(text[i]) ) ...
Using literals like 'A' instead of the magic number 65 makes the code more readable and helps avoid trivial mistakes like this.
There are quite a few issues with your code:
As #IrAM has mentioned in a comment, your if does not handle a score of 1. Moreover, you can simplify your if checks if you start from the other end, i.e. first check for greater than 16:
int main(void)
{
//Get user input
string text = get_string("Text: ");
//Grade user text
int i = get_score(text);
if(i > 16)
{
printf("Grade 16+\n");
}
else if (i > 0)
{
printf("Grade %i\n", i);
}
else
{
printf("Before Grade 1\n");
}
}
As #Gerhardh mentions, you are dividing two integers which forces the result to be an integer too. If at least one of the operands is casted to a float, the result is type-promoted to a float:
// letters divided by words * 100
float L = 100 * (float) letters / words;
// sentences divided by words *100
float S = 100 * (float) sentence / words;
Optimizations
You have three different functions for counting words, sentences and letters. Why three loops when you can do it in one loop? Plus an additional iteration for strlen() in each function. Write a Count struct like this:
struct Count
{
int letters;
int words;
int sentences;
int length;
};
Then have one function that returns this struct. Like #WilliamPursell mentions, using character literals instead of ASCII values makes code much more readable:
Count get_count(string text)
{
Count result = {0, 1, 0, 0};
result.length = strlen(text);
int characters = 0;
// Loop through text and count all non-letters
for(int i = 0; i < count.length; i++)
{
if((text[i] < 'A' || text[i] > 'Z') && (text[i] < 'a' || text[i] > 'z'))
{
characters++;
}
if(text[i] == ' ')
{
result.words++;
}
if(text[i] == '.' || text[i] == '!' || text[i] == '?')
{
result.sentences++;
}
}
// subtract all non-letters from total chars and return.
count.letters = count.length - characters;
return result;
}
This is what get_score() will change to:
int get_score (string text)
{
//variables
Count result = get_count(text);
float index = 0;
// letters divided by words * 100
float L = 100 * (float) result.letters / result.words;
// sentences divided by words *100
float S = 100 * (float) result.sentences / result.words;
index = round(0.0588 * L - 0.296 * S - 15.8);
return index;
}
Side Note: A '.' may always not necessarily mean the end of a sentence. It has other meanings like in an acronym or as ellipsis.

Password checker in c language

I want to take input as a string and evaluate if it is a valid password. The password is valid if it has numbers, capital and small alphabets, and a length of at least 7 characters. Also, when all these requirements are satisfied, the output should be "Strong", else "Weak". Code:
#include<stdio.h>
#include<string.h>
#define N 20
int main()
{
char pass[N];
int i, x=0, p;
printf("Enter a password: ");
scanf("%s", &pass);
if(strlen(pass)>=7)
{
p=strlen(pass);
for (i = 0; i < p; i++)
{
if ((pass[i] >= 'a' && pass[i] <= 'z') || (pass[i] >= 'A' && pass[i <= 'Z') || (pass[i] >= '0' && pass[i] <= '9'))
printf("Sucess! ");
}
}
else
printf("Try Again..");
return 0;
}
Somethings are not working like: 1. After execution, it prints "Sucess!!" multiple times.. 2. In the inner if block, when i replace || with &&, it didn't print anything.
Please help.
You could change this section of code:
p=strlen(pass);
for (i = 0; i < p; i++)
{
if ((pass[i] >= 'a' && pass[i] <= 'z') || (pass[i] >= 'A' && pass[i <= 'Z') || (pass[i] >= '0' && pass[i] <= '9'))
printf("Sucess! ");
}
... to something like this:
int lower_case_count = 0;
int upper_case_count = 0;
int digit_count = 0;
p=strlen(pass);
for (i = 0; i < p; i++) {
if (pass[i] >= 'a' && pass[i] <= 'z') ++lower_case_count;
if (pass[i] >= 'A' && pass[i] <= 'Z') ++upper_case_count;
if (pass[i] >= '0' && pass[i] <= '9') ++digit_count;
}
if (lower_case_count > 0 && upper_case_count > 0 && digit_count > 0) {
printf("Strong\n");
}
In the loop you just want to count how many of each type of characters is present.
Once the loop is done, you can check whether all requirements are satisfied and take appropriate action.
You made me thinking and I came up with this rather simple solution - two series of ifs, one wrapped within a while loop. It checks for lower and uppercase, numbers and special characters, reporting the exact error a user had eventually made.
//A program for password validation
#include <stdio.h>
#include <ctype.h>//for islower(), isupper(), isdigit() and ispunct()
#include <string.h>//for strlen()
int main(void)
{
char pass[30];
printf("Enter your password: ");
scanf("%[^\n]s", pass);//so that password may contain space char
int i = 0,
lower_count = 0,
upper_count = 0,
digit_count = 0,
punct_count = 0;
int length = strlen(pass);
//loop goes through the password as array
while (pass[i]) {
if(islower(pass[i]))//if there is lowercase
lower_count++;
if(isupper(pass[i]))//if there is uppercase
upper_count++;
if (isdigit(pass[i]))//if there is a number
digit_count++;
if (ispunct(pass[i]))//if there is a special chars
punct_count++;
i++;
}
if(strlen(pass) < 8)
{
printf("The password must have at least 8 chars\n");
return 1;
}
if(lower_count == 0)
{
printf("You need lowercases\n");
}
if(upper_count == 0)
{
printf("You need an uppercase\n");
}
if(digit_count == 0)
{
printf("You need digits\n");
}
if(punct_count == 0)
{
printf("You need a special character\n");
}
if(upper_count != 0 && digit_count != 0 && punct_count != 0 &&
length > 12)
{
printf("Your password is strong!\n");
}
if(upper_count != 0 && digit_count != 0 && punct_count != 0 &&
length <= 12)
{
printf("Your password is OK\n");
}
return 0;
}

Creating an atoi function in C

I try to create atoi function, and think I made right code, but when I run it, it shows wrong one. I'm trying to figure it out, but don't know what I made it wrong please check the code and give some help
My code is
#include <stdio.h>
int my_atoi(char *str)
{
int i;
int res;
int sign;
i = 0;
res = 0;
sign = 1;//sign of '-' or '+'
while(str[i] == ' ' || (str[i] >= 9 && str[i] <= 13))
{
i++;
}
if(str[i] == '-')
{
sign = -1;
i++;
}
else if(str[i] == '+')
{
sign = 1;
i++;
}
while(str[i] >= '0' && str[i] <= '9')
{
res = res * 10 + str[i] + '0';
i++;
}
return(res * sign);// to make integer which has value of '-' or '+'
}
int main(void)
{
char str[] = "-2018shiba";
printf("%d\n", my_atoi(str));
return(0);
}
When I run it, it shows -108674
I am seeing multiple mistakes here.
If you want to convert a ASCII character into the corresponding integer you need to subtract '0'. Take a look at the ASCII table: for instance '7' is mapped by decimal value 55. Hence if you want to get 7 then you need to subtract the ASCII of '0' which is 48 (55 - 48 = 7):
int foo = str[i] - '0';
In the very last while loop of my_atoi. The value of an indexed numeral string representation is calculated by multiplying the value of str[i] with the numerical base to the power of the index starting from behind.
For example lets take a look at "1337":
7*10^0 + 3*10^1 + 3*10^2 + 1*10^3 = 7 + 30 + 300 + 1000 = 1337
As you can see, the 7 has the numerical index 0 and so on. Assuming you want to just ignore shiba your code be looking something like this:
#include <stdio.h>
#include <string.h>
// Return base^(exponent)
int my_pow(int base, unsigned int exponent)
{
if (exponent == 0) {
return 1;
} else {
int result = base;
for (int i = 1; i < exponent; i++) {
result *= base;
}
return result;
}
}
int my_atoi(char *str, size_t len)
{
int i;
int res;
int sign;
i = 0;
res = 0;
sign = 1;//sign of '-' or '+'
while(str[i] == ' ' || (str[i] >= 9 && str[i] <= 13))
{
i++;
}
if(str[i] == '-')
{
sign = -1;
i++;
}
else if(str[i] == '+')
{
sign = 1;
i++;
}
// Store the index where the number string starts
int j = i-1;
// Find the ending index of the number string
i = len;
while (str[i] < '0' || str[i] > '9') {
i--;
}
int num_end = i;
// Now start at the ending
while(i > j)
{
if (str[i] >= '0' && str[i] <= '9') {
res += my_pow(10, num_end-i) * (str[i] - '0');
} else {
// If a character unequal to a digit is found then skip it
num_end--;
}
i--;
}
return(res * sign);// to make integer which has value of '-' or '+'
}
int main(void)
{
char str[] = "-2018shiba";
printf("%d\n", my_atoi(str, strlen(str)));
char str2[] = "-20X18shiba";
printf("%d\n", my_atoi(str2, strlen(str2)));
return(0);
}

there is a test that stuck at infinity loop can you find it?

int main() {
int s, b;
int hist[26] = { 0 };
int hist2[26] = { 0 };
char char1, char2;
printf("Hello Netta, enter the letters you would like us to repeat ending with $:\n");
scanf("%c", &char2);
while (char2 != '$') {
if (char2 >= 'A' && char2 <= 'Z')
char2 = char2 + 32;
int char3 = char2 - 'a';
hist2[char3]++;
scanf("%c", &char2);
if (char2 < 0)
break;
}
printf("How many times would you like to loop?\n");
if (!scanf("%d", &s))
return 0;
printf("Enter the string you would like to be checked ending with $:\n");
scanf("%c", &char1);
if (char1 >= 'A' && char1 <= 'Z')
char1 = char1 + 32;
while (char1 != '$' && char1 > 0) {
int char3 = char1 - 'a';
hist[char3]++;
scanf("%c", &char1);
}
for (int i = 0; i < 26; i++) {
if (hist[i] > s * hist2[i]) {
printf("Not enough letters\n");
b = 0;
break;
} else {
b = 1;
}
}
if (b)
printf("Congratulations! you have enough letters to create your song and win the Eurovision!\n");
return 0;
}
//so basically this a homewrok in my university they asked us to do the program input is char and a loop and it compare it with one more input how many times you can loop each letter (no need to check that the input is true but the loop number int
Your program has many problems:
scanf() returns the number of successful conversions. Compare the return value to 1 in your program instead of testing 0, which would never happen for "%c" anyway. Furthermore, the char2 would not be modified if the stream is at end of file.
You must check of char2 is a letter before indexing into the array, otherwise you may access beyond the array boundaries and have undefined behavior.
presentation is important: use proper indentation and spacing for the program to be readable.
include the necessary headers such as <stdio.h>
Here is an improved version:
#include <stdio.h>
int main() {
int hist[26] = { 0 };
int hist2[26] = { 0 };
int s;
char c;
printf("Hello Netta, enter the letters you would like us to repeat ending with $:\n");
while ((scanf("%c", &c) == 1 && c != '$') {
if (c >= 'A' && c <= 'Z')
hist2[c - 'A']++;
else if (c >= 'a' && c <= 'z')
hist2[c - 'a']++;
}
printf("How many times would you like to loop?\n");
if (scanf("%d", &s) != 1)
return 1;
printf("Enter the string you would like to be checked ending with $:\n");
while (scanf("%c", &c) == 1 && c != '$') {
if (c >= 'A' && c <= 'Z')
hist[c - 'A']++;
else if (c >= 'a' && c <= 'z')
hist[c - 'a']++;
}
for (int i = 0; i < 26; i++) {
if (hist[i] > s * hist2[i]) {
printf("Not enough letters\n");
break;
}
}
if (i == 26)
printf("Congratulations! you have enough letters to create your song and win the Eurovision!\n");
return 0;
}

Resources