-3

Write a C++ program that reads input from a text file and counts the number of characters read from the input. If the character read is a letter ('a'-'z'), counts the number of times that letter occurs [using an array] (both uppercase and lowercase should be counted as the same letter) in the input. Output the percentage of each letter in the input text, as well as the percentage of non-letter characters in the input.

Yes, this is a homework question, and I have most of it, but for some reason it isn't adding like I'd hoped.

#include <iostream>
#include <fstream>
#include <string>
#include <cctype>

using namespace std;

int main()
{
    // make array with size of 26
    // make array with all letter of alphabet
    const int size = 26;
    int narray[size];
    char larray[26] = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z' };

    // all variables
    int count = 0;
    char character;
    int length = 0;
    int amount = 0;
    int sum = 0;
    double percent = 0;


    // open file user inputs
    ifstream input;
    string file;
    cout << "Please enter the file to be read" << endl;
    cin >> file;
    input.open(file);

    if (input.fail())
    {
        cout << "Can't open file successfully." << endl;
            return 1;
    }

    // count amount of characters and spaces in while loop
    while (!input.eof())  //loop until the file ends
    {
        getline(input, file);   // read every character
        int c = file.length();  // count length
        length += c;
    }

    // make every variable in array equal to 0
    for (count = 0; count < size; count++)
    {
        narray[count] = amount;
    }


    // make for loop to read every character
    for (int i = 0; i < length; i++)
    {
        input.get(character);  // read characters

        if (character <= 'A' && character >= 'z')
        {
            narray[tolower(character)-'a']++; // find out which variable of the array it is and add 1 to the amount
            sum++;
        }
    }


    // make for loop to print out array percentages
    for (int j = 0; j < size; j++)
    {
        percent = (narray[j] / length) * 100;
        cout << larray[j] << " " << percent << "%" << endl;
    }

    int non = (((length - sum) / length) * 100);
    cout << "Non letter characters " << non << "%" << endl;

    input.close();

    return 0;
}
Cœur
  • 32,421
  • 21
  • 173
  • 232
Chrissy
  • 13
  • 1
  • 2
  • 3
    Welcome to Stack Overflow! It sounds like you may need to learn how to use a debugger to step through your code. With a good debugger, you can execute your program line by line and see where it is deviating from what you expect. This is an essential tool if you are going to do any programming. Further reading: [How to debug small programs](http://ericlippert.com/2014/03/05/how-to-debug-small-programs/) – NathanOliver Apr 13 '18 at 19:46
  • 2
    Check out an ASCII table and then ask yourself if `character <= 'A' && character >= 'z'` is correct – AndyG Apr 13 '18 at 19:50
  • Can you elaborate on "it isn't adding like I'd hoped."? What are you getting vs. what you are expecting? – Fred Larson Apr 13 '18 at 19:52
  • 2
    And please read [Why is iostream::eof inside a loop condition considered wrong?](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong) – Some programmer dude Apr 13 '18 at 19:52
  • Fred Larson I am getting 0% for all letters and 100% for non characters which is why I believe when the characters are read they aren't adding to the array – Chrissy Apr 13 '18 at 19:55
  • Consider that you are reading until you reach the end of the file, and then afterwards you try to get character by character from your filestream. What state do you expect `input` to be in before your first call to `input.get(character)` considering this? – AndyG Apr 13 '18 at 20:05

1 Answers1

1

Your code is a little more complicated than it needs to be, but worse it has several bugs in it.

  • You are using 2 separate loops to do the job that 1 loop can do.

  • You are calling input.eof() before you have performed a read operation. The stream's state is not updated until after a read is attempted, so calling eof() before the 1st read is undefined behavior.

  • After you have read through the stream one time to EOF just to count its characters, you are not seeking the stream back to the beginning so you can then read the characters all over again.

  • You are not counting line break characters in the 1st loop, but you are reading line break characters in the 2nd loop, so the 2nd loop (potentially) won't read as many characters as the 1st loop had counted.

  • You are not testing for Uppercase and Lowercase letters correctly, and you are not accounting for the fact that in ASCII, there are 6 non-letter characters between the set of Uppercase letters and set of Lowercase letters. Your indexing of the narray[] array is all wrong while you are counting characters.

  • You are not accounting for the possibility that the file might be completely empty, or have ONLY linebreak characters in it and no non-linebreak characters. If either of those condition happens, your length variable will be 0, and you would get errors when you calculate percentages when dividing by 0.

With that said, try something more like this instead:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    // make array with size of 26
    const int size = 26;
    int narray[size] = {}; // make every variable in array equal to 0

    // all variables
    char character;
    int count = 0;
    int sum_letters = 0;
    int sum_non = 0;
    double percent;
    string file, line;

    // prompt user for filename
    cout << "Please enter the file to be read" << endl;
    getline(cin, file);

    // open file
    ifstream input(file);
    if (!input.is_open())
    {
        cout << "Can't open file." << endl;
        return 1;
    }

    //loop until the file ends
    while (getline(input, line))
    {
        count += line.size(); // count every character

        for (int j = 0; j < line.size(); ++j)
        {
            character = line[j];

            // find out which variable of the array it is and add 1 to the amount
            if (character >= 'A' && character <= 'Z')
            {
                narray[character-'A']++;
                ++sum_letters;
            }
            else if (character >= 'a' && character <= 'z')
            {
                narray[character-'a']++;
                ++sum_letters;
            }
            else
                ++sum_non;
        }
    }

    input.close();

    if (count != 0)
    {
        // make for loop to print out array percentages
        for (int j = 0; j < size; ++j)
        {
            percent = (double(narray[j]) / count) * 100.0;
            cout << ('a'+j) << " " << percent << "%" << endl;
        }

        percent = (double(sum_non) / count) * 100.0;
        cout << "Non letter characters " << percent << "%" << endl;
    }
    else
    {
        cout << "File has no characters" << endl;
    }

    return 0;
}

If you want to include line breaks in the percentage of non-letter characters, then use this instead:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    // make array with size of 26
    const int size = 26;
    int narray[size] = {}; // make every variable in array equal to 0

    // all variables
    char character;
    int count = 0;
    int sum_letters = 0;
    int sum_non = 0;
    double percent;
    string file, line;

    // prompt user for filename
    cout << "Please enter the file to be read" << endl;
    getline(cin, file);

    // open file
    ifstream input(file);
    if (!input.is_open())
    {
        cout << "Can't open file." << endl;
        return 1;
    }

    //loop until the file ends
    while (input.get(character))
    {
        ++count; // count every character

        // find out which variable of the array it is and add 1 to the amount
        if (character >= 'A' && character <= 'Z')
        {
            narray[character-'A']++;
            ++sum_letters;
        }
        else if (character >= 'a' && character <= 'z')
        {
            narray[character-'a']++;
            ++sum_letters;
        }
        else
            ++sum_non;
    }

    input.close();

    if (count != 0)
    {
        // make for loop to print out array percentages
        for (int j = 0; j < size; ++j)
        {
            percent = (double(narray[j]) / count) * 100.0;
            cout << ('a'+j) << " " << percent << "%" << endl;
        }

        percent = (double(sum_non) / count) * 100.0;
        cout << "Non letter characters " << percent << "%" << endl;
    }
    else
    {
        cout << "File is empty" << endl;
    }

    return 0;
}
Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
  • What do you mean by "non-linebreak characters"? Thank you for your help! – Chrissy Apr 13 '18 at 22:11
  • Line breaks are characters, too - `\r` (13, 0x0D) and `\n` (10, 0x0A). `std::getline()` does not output those characters, but `std::istream::get()` does. In the code you showed, you aren't counting characters used for line breaks, so there is *potential* for your `length` variable to be 0 even though the file is not empty, if ALL the file has are line breaks in it and nothing else. If you are going to use separate loops for counting and reading, make sure they act on the same data. Either 1) count/read the line breaks in both loops, or 2) ignore line breaks in both loops. – Remy Lebeau Apr 13 '18 at 22:24