0

In each line, There are a string and a number. I have done the search function to search for a name and it works

For example in a file with the following contents:

Batman 290
Joker 100
Spiderman 300

But the problem that I'm facing right now is how do I modify the number next to a string. For example, when I search for the name 'Batman' and I only want to change Batman's number.

Here's the code for adding:

void Teacher::addScore(string name, double score) {
    outStream.open("StudentRecord.txt", ios_base::app);
    outStream << name << " || " << score << endl;
    outStream.close();
}

For searching

string Teacher::search(string searchKeyword) {
    int count = 0;
    string name;
    readStream.open("StudentRecord.txt");

    while (readStream.is_open()) {
        getline(cin, name);

        while (readStream >> name) {
            if(name == searchKeyword) {
                count++;
                return name;

            }
        }
        if(name != searchKeyword) {
            return "That student doesnt exist";
        }
    }
    return "";
}

For modification, Im having trouble at this stage

void Teacher::modifyScore() {
    string name;
    string searchResult;
    cout << "You want to modify the a student score?" << endl;
    cout << "Which student's you want to change?" << endl;
    cin >> name;
    searchResult = search(name);
    outStream.open("StudentRecord.txt", ios::trunc);
    // What should I do here to modify a student number?  
}
martijnn2008
  • 3,236
  • 3
  • 27
  • 38
airsoftFreak
  • 1,272
  • 2
  • 26
  • 52
  • 1
    Have a look at this article: [Why is iostream::eof inside a loop condition considered wrong?](http://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong) – πάντα ῥεῖ May 23 '15 at 13:24
  • Are you trying to modify the number and return it to an output file, or modify the number, once read into memory? – octopusgrabbus May 23 '15 at 13:26
  • You cannot safely change 'a single number' in a plain text file, or even an entire line. What if you change "290" to "0"? What if you change it to "10000"? – Jongware May 23 '15 at 13:26
  • @octopusgrabbus Im trying to modify a number and return it to an output file, but it has to be on the same line as the name – airsoftFreak May 23 '15 at 14:03
  • @Jongware So its not possible to modify a number next to a string? – airsoftFreak May 23 '15 at 14:13
  • Well ... it *is* possible. But only if (1) you are **absolutely sure** the number occupies the same number of characters, or (2) rewrite the output file – which is why @Sneftel suggests this is a duplicate (not your question; your *intention* is). – Jongware May 23 '15 at 14:30
  • I'm trying to use @Sneftel solution, which is solution 2 but ifstream doesnt have a member named replace. – airsoftFreak May 23 '15 at 14:34
  • @Sneftnel, I tried to use your suggestion, but seems like ifstream doesnt have a member named replace. – airsoftFreak May 23 '15 at 14:36
  • To change values in a text file, usually you need read the whole file and write them back to the same file or a new one. – Tim3880 May 23 '15 at 14:43
  • @Tim3880 if you have an answer, please provide it, so I could accept it – airsoftFreak May 23 '15 at 14:44
  • @airsoftFreak: Look at the upvoted answer on the duplicate (from Joachim). The accepted answer there is a bunch of baloney (and I just downvoted it) – Ben Voigt May 24 '15 at 00:28

3 Answers3

2

Second edit: With code for reading/writing

Note that the code is not optimal and does not have any error checks. The data is assumed to be in "data.txt".

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

int main()
{
    // Holds file data
    std::map<std::string, int> data;

    // Read file and fill data map
    std::ifstream ifs("data.txt");
    std::string line;
    while (std::getline(ifs, line))
    {
        std::string name;
        int number;
        std::stringstream ss(line);
        ss >> name >> number;
        data[name] = number;
    }
    ifs.close();

    // Print data
    for (auto& entry : data)
    {
        std::cout << entry.first << " " << entry.second << std::endl;
    }
    // Modify data
    data["Batman"] += 100;

    // Open same file for output, overwrite existing data
    std::ofstream ofs("data.txt");
    for (auto& entry : data)
    {
        ofs << entry.first << " " << entry.second << std::endl;
    }
    ofs.close();

    return 0;
}

Edit: The original answer assumed that the file structure is known (string and number per line)

To find a number in a file, you need parse the file contents into so called "token". Example: The string "foo 10 0.1" would be broken down into "foo", "10", "0.1" using the blank character as a separator. This process is called tokenization. Next you need to check each token if it represents a valid number, to do so, just check, if each character is a valid integer.

Original amswer:

You basically need to do the following

  1. Load the file into a data structure which represents the logical structue of the file (for example std::map for storing name and number). This involves parsing the file contents. You propably can use stringstream for that.
  2. Modify the data in memory (for example data["Batman"] += 100; for adding 100 to the number associated with "Batman")
  3. Store the data structure in the output file in the rquested format

If the input file MUST be used as the output file, just reopen with overwrite flags (which should be default afaik)

RedAgito
  • 405
  • 2
  • 8
1

I changed the code to handle files properly, you should be able to test it:

#include "stdafx.h" // for VC++
#include <iostream>
#include <fstream>
#include <string>

#define score_file "d:\\StudentRecord.txt"
#define score_file_bak score_file".bak"
#define score_file_new score_file".new"


using namespace std;

void addScore(string name, double score) 
{
    ofstream outStream;
    outStream.open(score_file, ios_base::app);
    outStream << name << "||" << score << endl;
    outStream.close();
}


void modifyScore(string name, double score) {
    string line;
    int fd =0;
    ifstream scores(score_file);
    if(!scores.is_open()){
        std::cout << "Unable to read score file" << std::endl;
        return ;
    }
    ofstream outStream(score_file_new);
    if(!outStream.is_open()){ 
        std::cout << "Unable to open temp file to write" << std::endl;
        return ;
    }
    name += "||";
    while(getline(scores, line)){
        if(line.find(name) == 0){
            std::cout << "Found: " << line << std::endl;
            outStream << name << score << std::endl;
            fd++;
        }
        else{
            outStream << line << std::endl;
        }
    }
    scores.close();
    outStream.close();
    if(fd<1){ 
        remove(score_file_new);
        std::cout << "Not found: " << name << std::endl;
    }
    else{
        remove(score_file_bak);// delete previous backup
        if(0 != rename(score_file,score_file_bak)){
            std::cout << "Unable to backup" << std::endl;
            remove(score_file_new);  // delete the new file since update failed
            return;
        }
        if(0 == rename(score_file_new,score_file)) 
            std::cout << "Updated succeeded." << std::endl;
        else
            std::cout << "Unable to update\n" << std::endl;
    }
}

void showScore()
{
    string line;
    ifstream scores(score_file);
    while(getline(scores, line)){
        cout << line << std::endl;
    }
    scores.close();
}

int main()
{
    bool reset_scores = true; //do you want to reset all scores?

    if(reset_scores) // clean all old records?  
    {
        ofstream outStream(score_file, ios_base::trunc);outStream.close();
        addScore("Tom",120); addScore("Jerry", 130); addScore("Tim",100);
    }

    std::cout << "Here are the scores" << std::endl;
    showScore();

    std::cout << "Try to change Timmy's score:" << std::endl;
    modifyScore("Timmy",200);
    showScore();

    std::cout << "Try to change Jerry's score:" << std::endl;
    modifyScore("Jerry",1190);
    showScore();

    if(1)
    {
        string aaa; 
        std::cout << "Press enter to quit:\n";
        getline(cin, aaa);
    }
    return 0;
}

I have this output:

 Here are the scores
 Tom||120
 Jerry||130
 Tim||100
 Try to change Timmy's score:
 Not found: Timmy||
 Tom||120
 Jerry||130
 Tim||100
 Try to change Jerry's score:
 Found: Jerry||130
 Updated succeeded.
 Tom||120
 Jerry||1190
 Tim||100
 Press enter to quit:

You need find out how to handle input by yourself.

Tim3880
  • 2,493
  • 1
  • 8
  • 13
0

You cannot modify files "in their middle"; that is, you can only truncate or append to a file, or overwrite a subsequence of bytes by another sequence (of the same length) of bytes.

So you could, as suggested by RedAgito's anser, read the entire file in memory, modify the in-memory representation, then rewrite in full the entire file.

Of course, this approach is not very scalable (but is practically worthwhile: laptop computers have gigabytes of RAM, and a single-gigabyte textual files would contain millions of name - number lines, which is often practically enough) : the complexity of a single modification is proportional to the number of the lines (unless you know that all numbers are the same width -eg some telephone number; then you can overwrite the line, but you still need to find that matching line).

A practical approach might be to use some database (perhaps as simple as the sqlite library; or full featured DBMS like PostGreSQL or MongoDB), or some indexed file library (like GDBM or kyotocabinet).

Another approach might be to use some conventional textual structured format like JSON or YAML (or perhaps XML, which is less suited for your task). There exist many C++ libraries able to easily deal with such formats (e.g. jsoncpp and many others). You'll then parse the full file (in a few C++ statements), perhaps as a single (but large) JSON object, then modify the data in memory, then write it again.

If you cannot use any of these (e.g. if it is some homework, and your teacher is constraining you) you might code your own routines managing fixed-size records (and perhaps providing some higher-level abstraction above them) which might be some tagged union perhaps containing offsets to other records (this is basically what database systems and index files are doing). If you are constrained to use textual files (which is a good idea; you'll better have data that you could modify with any good editor, à la emacs or vim), then you have to parse the entire file, store a representation of it in memory (e.g. perhaps some std::map<std::string,long> in your case, the key being the name), then rewrite the entire file.

Community
  • 1
  • 1
Basile Starynkevitch
  • 1
  • 16
  • 251
  • 479