How to get the last integer of sequence of integer

2019-04-15 15:25发布

问题:

What I'm trying to do is to get each integer from a file of integers separated by commas and add them. For example, if the file contains 2,3,4,1 my program has to display that the sum is 10. For that, I wrote the following code.

int number_sum(const char* filename) {

std::ifstream file(filename); 

if (file.is_open()) {

    int sum = 0; //sum
    char c; //Store each comma
    int number = 0; //Store each number

    while ( (file >> number >> c) && (c == ',') ) {

        std::cout << "Adding up: " << number << " + " << sum << std::endl;
        sum = sum + number;


    }

    std::cout << "Result is: " << sum << std::endl;

    return sum;
}
else {
    std::cout << "ERROR" << std::endl;
    return -1;
}

This works well with all the digits except the last one. The problem comes because the last one is not followed by a comma, so the program does not get it. I've tried to discriminate "c" value by checking whether it is ',' or EOF, but this didn't work. Is there any solution so that my program can get the last number and add it up to the rest? (Sorry for my english, not native).

Thank you very much in advance.

回答1:

It is easier to parse a string than arbitrary data from a file. It is easier to grab either a single line of information from a file and store that into an std::string or to read all of the file and save the contents into either a large buffer or a std::vector<std::string>>. Then after you have all the information you need, close the file handle and then it is time to do your parsing.

With the help of a few of the std libraries out there you can do this fairly easy. We will also use a helper function to breakdown our line of text from the file.

#include <numeric>
#include <string>
#include <sstream>    
#include <fstream>
#include <iostream>

std::vector<std::string> split( const std::string& s, char delimiter ) {
    std::vector<std::string> tokens = {};
    std::string token = {};
    std::istringstream tokenStream( s ); // std::istringstream found in <sstream>
    while( std::getline( tokenStream, token, delimiter ) ) {
        tokens.push_back( token );
    }    
    return tokens;
} 

int number_sum( const char* filename ) {
    // First try to open the file; if fails return -1
    std::ifstream file;
    file.open( filename );
    if ( !file.is_open() ) {
        std::cout << "failed to open file " << filename << '\n';
        return -1;
    }

    // read a single line from the file and save it to a local string
    std::string line = {};
    std::getline( file, line );

    // close the file
    file.close();

    // now parse the local string into string tokens and convert them to ints
    std::vector<int> values = {};
    std::vector<std::string> tokens = split( line, ',' );
    for ( auto s : tokens ) {
        values.push_back( std::stoi( s ) ); // std::stoi() found in <string>
    }

    // now that we have our vector of ints parsed from the strings we can add them together
    // std::accumulate() found in <numeric>
    return std::accumulate( values.begin(), values.end(), 0 );
}   

int main() {
    std::cout << number_sum( "test.txt" );

    return 0;
}

test.txt

2,3,4,1

output

10

With this approach you don't have to worry about accounting for delimiters either being there or not being there, this will accommodate for both odd and even type cases. This is the power of the stl library when you know what functions to use.

Now this will only do a single line from your input file, but this can be expanded upon to incorporate multiple lines from a file with a simple while loop and an extra vector to store each line. You may have to change what this function returns though. I'll leave that part as an exercise for you.


In a previous iteration of this answer I had mentioned there was a bug and that I knew what it was; I was in the process of writing a helper function when I originally posted it. The program worked fine for each character as long as the value between commas was a single digit character and it would fail or break if there was more than one value between commas. With the aid of this helper function to split a string into multiple strings via a delimiter we don't have to worry about manually parsing each character in the string as the stl library and functions will do this for us. This function is now working correctly and is accommodating for when the values between commas are more than one digit!

test.txt - 2nd trial

23,32,46,11

output

112

After some consideration I cleaned this up a bit. I didn't like the fact that the function that does the accumulation was handling the responsibility of opening and reading the contents from the file so I moved that to its own separate function. I also like the ability to catch errors during runtime and to display them to the console. Finally I renamed a function to be a little more descriptive for readability. Here is the refactored program:

#include <numeric>
#include <string>
#include <sstream>
#include <fstream>
#include <iostream>
#include <exception>

std::string readLineFromFile( const char* filename ) {
    std::ifstream file( filename );
    if ( !file ) {
        std::stringstream stream;
        stream << "failed to open file " << filename << '\n';
        throw std::runtime_error( stream.str() );
    }

    std::string line;
    std::getline( file, line );

    file.close();

    return line;
}

std::vector<std::string> splitString( const std::string& s, char delimiter ) {
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream( s );
    while( std::getline( tokenStream, token, delimiter ) ) {
        tokens.push_back( token );
    }    
    return tokens;
}

int number_sum( const char* filename ) {
    // Get contents from file
    std::string line = readLineFromFile( filename );

    // parse string
    std::vector<int> values;    
    std::vector<std::string> tokens = splitString( line, ',' );
    for( auto s : tokens ) {
        values.push_back( std::stoi( s ) );
    }

    // return the accumulated value
    return std::accumulate( values.begin(), values.end(), 0 );
}

int main() {
    try {
        std::cout << number_sum( "test.txt" ) << '\n';
        // assuming there is no "test2.txt"
        // this will throw a runtime error 
        // and display the appropriate message to the console
        std::cout << number_sum( "test2.txt" ) << '\n';
    } catch( const std::runtime_error& e ) {
        std::cerr << e.what() << '\n';
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}


回答2:

Adding the following line to this code works, but may take some consideration for number

while ( (file >> number >> c) && (c == ',') ) {

    std::cout << "Adding up: " << number << " + " << sum << std::endl;
    sum = sum + number;

}
std::cout << "Adding up: " << number << " + " << sum << std::endl;
sum = sum + number;

It's like brute forcing the last one, hope it helps!