0

I'm writing a program that takes in input and formats it into a score as "Name Score". Here is what needs to be done "Here's Moonglow's format.

  • The text file is composed of words. If a word is a number, then that is a student's score on a question, so you add it to the student's exam score.

  • If the word is not a number, but is the word "NAME", then the next word is the student's name (Moonglow only uses first names -- last names are corporate and impersonal).

  • If the word is "AVERAGE", then you start reading numbers until you read a word that is not a number (or is the end of the file). You average all of those numbers and add that to the score. Since Moonglow is a little scatterbrained, sometimes a number does not follow "AVERAGE." In that case, you ignore the "AVERAGE". "

My problem is I've somehow entered into a infinite loop, and have spent hours trying to fix this. This is a VERY simple program, and I can't seem to get it to work!

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <stdlib.h>
using namespace std;

int main()
{
    string s, name = "test";
    string buffer;
    double qScore, eScore, totalExam = 0, grade = 0, numExam = 0, finalGrade, avg = 0;
    while (!cin.eof())
    {
        if (cin >> qScore)
        {
            if (cin.fail())
            {
                cin.clear();
            }

            else
            {
                grade += qScore;
            }
        }
        else if (cin >> s)
        {
            if (s == "NAME")
            {
                cin >> s;
                s = name;
            }
            else if (s == "AVERAGE")
            {
                while (cin >> eScore)
                {
                    numExam++;
                    totalExam += eScore;

                }
                cin.clear();
            }
        }
    }

    if (numExam == 0)
    {
        avg = 0;
    }
    else
    {
        avg = (totalExam / numExam);
    }
    finalGrade = avg + grade;
    cout << name << " " << finalGrade << endl;
    return 0;

}
// end of main
Matt
  • 5,283
  • 9
  • 51
  • 93
  • 1
    [For starters, `while (!eof())` is incorrect.](http://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong) – 0x499602D2 Jan 29 '14 at 18:35
  • Should I replace it with while (cin >> qScore || cin >> s)? – user3250013 Jan 29 '14 at 18:39
  • Replace it with `while (cin >> qScore && cin >> s)`. There's also much more you can do to fix this program. That's just one. – 0x499602D2 Jan 29 '14 at 18:43
  • I see another grievous error with my code is nothing is ever read into "name". No matter what I execute no name is given in the output. – user3250013 Jan 29 '14 at 18:47
  • You're going to have to wait while I work on your code. In the meantime I want to know what you are supplying as input. – 0x499602D2 Jan 29 '14 at 18:50
  • I appreciate the help. Here are a few examples: 1: NAME Fred 2: Starrlite AVERAGE Starrlite!!! NAME Starrlite AVERAGE 55 Starrlite!!! Starrlite AVERAGE Starrlite 5 6 7 – user3250013 Jan 29 '14 at 18:58
  • It is supposed to ignore any words that aren't AVERAGE or NAME followed by a single name. Any number not after AVERAGE is to be added up. It's a weird lab, the goal is to convert some "byzantine" format into just " " – user3250013 Jan 29 '14 at 18:59
  • 1
    Is `1:` and `2:` part of the input? – 0x499602D2 Jan 29 '14 at 19:06
  • Part of the problem is you are always consuming the input in the pattern double, string. Your example shows that it can be more like string, string, string..., double, string, string... I would instead read everything as a `string` (separated by space) and then convert to `double` or `int`. – Matt Jan 29 '14 at 19:08
  • Why are you doing `cin >> s` then `s = name` afterwards? – 0x499602D2 Jan 29 '14 at 19:12
  • Also, the problem is pretty much a state machine, code it as such. – Matt Jan 29 '14 at 19:14
  • 1 and 2 are both separate inputs. How would I go about converting only the numbers to doubles? – user3250013 Jan 29 '14 at 19:23
  • @user3250013 See my answer. You can convert only numbers by looking at the characters to see if it is a number. Unless I'm forgetting and there is a way to try to convert and fail if it is not a number. – Matt Jan 29 '14 at 19:32

1 Answers1

1

I'm not going to write code for you (since this is clearly a homework problem), but I will offer some suggestions as to how I would do this completely differently. Hope this pseudo code helps.

EDIT: I missed the logic for AVERAGE and how the score should work, I'm making some corrections. (Note, with these changes, it may be better to add new states. I would consider Normal, Name, BeginAverage, Average, EndAverage. But it's up to you.)

1) Define these states (use an enum perhaps):

  • Normal (there's probably a better name for this one)
  • Name
  • Average

2) You want to read one "token" and transition with the following rules. Note that a token in this case will be a string, separated by spaces (e.g. "NAME", "AVERAGE", "Bob", "11", "23").

  • If state is Normal (this is also the initial state)
    • If token is "NAME", go to state Name
    • If token is "AVERAGE", go to state Average
    • If token starts with one of these characters '0', '1', '2', ... '9', convert it to a double and add it to your score
  • If state is Name
    • Copy the token into your "name" variable, transition back to Normal state
  • If state is Average
    • If you get a number, add it to a temporary count and increment a counter
    • If you get a string that is not "NAME", "AVERAGE", or a number, go back to Normal state
    • If you get "NAME", go to Name state
    • Before you enter this state, you need to reset your temporary count/counter to calculate the average
    • Before you leave this state, calculate the average and add it to the total score

3) For processing the input, read everything into a string variable first. Then you can use stringstreams to convert to double when needed. Just read all of the strings (tokens) as I've described and process them as above until you reach EOL.

"Code":

while (cin >> s)
{
    if (state == state_normal)
    {
        if (s == "NAME") { state = state_name; }
        else if (s == "AVERAGE")
        {
            state = state_average;
            // You are starting a new average, initialize these variables to 0
            scoresToAverage = 0;
            numberOfScores = 0;
        }
        else if (isNumber(s))
        {
            number = convertToNumber(s);
            score += number;
        }
    }
    if (state == state_name)
    {
        name = s;
        state = state_normal;
    }
    if (state == state_average)
    {
        bool calculateAverage = false;
        if (s == "NAME")
        {
             state = state_name;
             calculateAverage = true;
        }
        else if (s == "AVERAGE")
        {
             state = state_average;
             calculateAverage = true;
        }
        else if (isNumber(s))
        {
            number = convertToNumber(s);
            scoresToAverage += number;
            numberOfScores++;
        }
        else
        {
            state = state_normal;
            calculateAverage = true;
        }

        // If you are DONE averaging, calculate the average and add it to the total
        if (calculateAverage)
        {
            if (numberOfScores > 0)
            {
                score += (scoresToAverage / numberOfScores);
            }
            scoresToAverage = 0;
            numberOfScores = 0;
        }
    }
}

// done reading input, just print!

isNumber takes a string and can just check that the first character is a number ('0', '1',...). It could be fancier and look at each character for numbers/decimals.

convertToNumber takes a string and converts to a double. Could use stringstreams to do the conversion or just the old atoi.

Matt
  • 5,283
  • 9
  • 51
  • 93
  • That's pretty helpful so far. Just one question. What do you mean by using an enum for the states? – user3250013 Jan 29 '14 at 20:34
  • Assuming you don't know what an `enum` is, http://www.cprogramming.com/tutorial/enum.html So you can make an new `enum` type where each possible value is a different state. Alternatively, you could use an `int` and define `const`ants, so like `0` is the first state, `1` is the second, `2` is the third. That's (somewhat) what an `enum` is doing for you. – Matt Jan 29 '14 at 22:00
  • I looked up enums and have used them successfully... I believe. However, now my output always shows the name but the grade is ALWAYS 0, no matter what the input is. Am I maybe converting wrong? atof(s.c_str()) is correct for converting to a string right? – user3250013 Jan 29 '14 at 22:05
  • I'm not really sure without seeing your new code. Perhaps it's time to dust off the old debugger and take a look at the value of `s`? Are you actually storing the converted value in the total? – Matt Jan 29 '14 at 22:09
  • I have it to test for the first char of s to the between 0 and 9 by if (s[0] == '0' || ..........so on...) – user3250013 Jan 29 '14 at 22:19
  • and then eScore = atof(s.c_str()) – user3250013 Jan 29 '14 at 22:21
  • so did you use the debugger to see if that `if` is ever true? Did you check the value of `s` in those cases? The value of `eScore`? Are you adding `eScore` to the total? So many variables, I can't really help. You could add your new code to your question, or post a http://pastebin.com/ link. Also, you can check whether the character is a number with just `(s[0] >= '0' && s[0] <= '9')`, but that's just an optimization. – Matt Jan 29 '14 at 22:31
  • forgive me but where do I find the decoder? I'm new here. And I'll give the (s[0] >= '0' && s[0] <= '9') a try just for optimization (I like the look of it much more) – user3250013 Jan 29 '14 at 23:04
  • You mixed up the "isNumber" cases for the "normal" and "average" states. For "normal", you need to add the score, for "average", you can just do nothing/ignore the number (lines 23 vs. 39-41). Side note, you are adding `grade`, which is always `0` at the end, you don't need this. And learning how to use a **debugger** is a whole different discussion, not really appropriate for the comments on my answer. Perhaps look into that on your own? http://www.bing.com/search?setmkt=en-US&q=introduction+to+debugger+c%2B%2B – Matt Jan 29 '14 at 23:25
  • The problem with that is after AVERAGE is encountered the numbers that appear AFTER that are added and averaged. If numbers are encountered before AVERAGE is read they are added to grade. Then grade is added to avg for the finalGrade. – user3250013 Jan 29 '14 at 23:29
  • I misread the "AVERAGE" part of the problem (it's kind of poorly worded), see corrections. – Matt Jan 30 '14 at 00:10
  • So now I've got this http://pastebin.com/wbck11j0 The name is not being read in. As far as I know I did what you said I should. Hmmm Also I agree, the problem description was hard for me to follow as well. – user3250013 Jan 30 '14 at 01:24
  • The `if`'s in the `while` loop need to be `else if`, or better yet, use `switch(...) {case...}`. You should really learn to use a debugger because it would be pretty clear. Also, you should learn to mentally step through code to figure out exactly what it should do. Also in the "AVERAGE" state, you are missing the case where your token is the word "AVERAGE" again, or if it is any other non-numerical word. – Matt Jan 30 '14 at 01:33
  • here is what I have now. Here is my input loop now. pastebin.com/H5QmMqWR Currently ALL output is " 0" no matter what I do as input. I understand what each part of the code does, but I just can't seem to get it to work properly. I've since started reading up how to use debuggers, but I'm afraid I'm not anywhere near experienced enough with them yet. – user3250013 Jan 30 '14 at 03:23
  • I figured out what I was doing wrong the entire time. I never initialized my enum to the Normal state, so nothing was being executed. I really appreciate all the help you gave me. And I learned a new C++ concept (enums) before we did in class so I'm very grateful! – user3250013 Jan 30 '14 at 03:42