0

I'm attempting to search a csv file in order to print the data found with a certain name. I have tried multiple suggestions found on this website but nothing seems to be working as I get constant errors. Possibly because I'm not quite understanding why my code is not working, so I would very much appreciate an explanation. My code can be found below:

code removed
kingk
  • 11
  • 5
  • Aside: writing a `std::istream& operator>>(std::istream&, Records&)` means you can check whether the whole record is valid, rather than assume the stream has more if `fail` isn't set. At the moment you add an invalid `Record` at the end. The loop could become `for (Records awardIn; inFS >> awardsIn;) { input.push_back(awardIn); }` (or you could populate `inputs` in it's declaration, with `std::istream_iterator`) – Caleth Sep 18 '19 at 16:00

3 Answers3

0

First off your while loop condition is not wrong, but could be simplified by using operator bool instead:

while(inFS)
{
    ...
}

Second, your usage of std::find_if is incorrect. The third argument must be a callable of signature bool(Record) (possibly const or ref qualified) and returns an iterator to the first match.

From your variable names and strings I guess you want to search over the Records::recip members in your vector. std::find_if is a good way to achieve this:

auto comp = [&search](Record const& r) { return r.receip == search; };

for (auto it = std::find_if(input.begin(), input.end(), comp);
     it != input.end();
     it = std::find_if(++it, input.end(), comp))
{
    // this loop iterates over all matches for search in Record::receip
    Record& record = *it;
    std::cout << record << '\n';
}
Timo
  • 9,269
  • 2
  • 28
  • 58
  • Is there anyway you could give me a bit more on how this works? My professor hasn't gone over this and I'm a little lost on auto comp – kingk Sep 18 '19 at 16:55
  • `comp` is a [lambda](https://stackoverflow.com/questions/7627098/what-is-a-lambda-expression-in-c11). The `auto` keyword means that the compiler should choose an appropriate data type. `comp` is basically a function here. What `find_if` does, is iterate over each element in your container (here `input`) and call `comp(element)`. If that call returns `true`, `find_if` will return with an iterator to that element. – Timo Sep 18 '19 at 17:09
  • @kingk Note that even though the `while` condition isn't wrong, it _is_ wrong to use that condition before reading 4 lines and adding a `Records` to the vector. – Ted Lyngmo Sep 18 '19 at 17:54
  • @Timo Thank you! Now if I want to print matching data members would I use at() at it? – kingk Sep 18 '19 at 21:22
  • I want to add what my goal is it might help. I need to search a file for reference to a name and print the award the name is associated with. If the name shows up more than once I need to print it more than once. – kingk Sep 18 '19 at 21:47
  • @kingk that's exactly what the loop does. – Timo Sep 18 '19 at 22:03
  • I get the compiler error " no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream'} and 'Records') " cout << record << endl; – kingk Sep 18 '19 at 23:06
  • Whoever reads this I ended up creating a custom ostream operator in order to resolve the error – kingk Sep 19 '19 at 01:39
0

The way you use find_if is incorrect, you need to use lambda to make custom search in your vector of structs. Here is more straight way:

for(const Record& record : input) // Iterate all records
{
    if(record.recip == search) // Check if 'recip' of the record matches the requested one
    {
        cout << "found";
        return 0;
    }
}
Artem Suprunov
  • 323
  • 1
  • 7
0

!= string::npos is not the correct condition. find_if returns an iterator to the first element satisfying the condition or last if no such element is found. last is in your case input.end().

You read the fields by getline, but when you enter the search string, you use formatted input (breaking on space). If the field contained a space in the file, you'd never be able to find it.

To make input/output easier, consider adding operator << and >> to your class. Your current while(!inFS.fail()) condition does not work. If you read the last Records struct from the file, fail() will not be true and you'll therefor try to read one complete Records (but failing on every line). You'll still add the erroneous Records struct to the vector.

As an alternative to the other suggestions using a lambda (which is nice) you could also add operator== to your Records - if you only want to be able to search for the same thing.

Also, read Why is “using namespace std;” considered bad practice?

Example:

#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

struct Records {
    std::string year{};
    std::string category{};
    std::string won{};
    std::string recip{};

    // added operator to be able to compare a Records with a string
    bool operator==(const std::string& Recip) const { return recip == Recip; }
};

// custom streaming operator for reading one `Records` from a stream (like a file)
std::istream& operator>>(std::istream& is, Records& r) {
    std::getline(is, r.year, ',') && std::getline(is, r.category, ',') &&
        std::getline(is, r.won, ',') && std::getline(is, r.recip);
    return is;
}

// custom streaming operator to write a `Records` to a stream
std::ostream& operator<<(std::ostream& os, const Records& r) {
    return os << r.year << '\n' << r.category << '\n' << r.won << '\n' << r.recip << '\n';
}

int main() {
    std::ifstream inFS("oscars.csv");

    if(!inFS) { // in boolean context, inFS will be false if not opened
        std::cout << "Failed to open file.\n";
        return 1;
    }

    std::vector<Records> input;
    Records awardIn;

    // use the custom operator>> to extract a "Records" from the file and push it back
    // inFS will be false in boolean context if the extraction failed
    while(inFS >> awardIn)
        input.push_back(awardIn);

    std::string search;
    std::cout << "Enter recipient: ";

    if(std::getline(std::cin, search)) { // check that reading succeed

        // with the added operator== you can use a normal std::find
        for(auto it = std::find(input.begin(), input.end(), search);
            it != input.end(); 
            it = std::find(std::next(it), input.end(), search)) 
        {
            // use the custom operator<< to print the Records
            std::cout << *it << "\n";
        }
    }
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108