yahoo911emil
yahoo911emil

Reputation: 49

I need help filtering a text file

Hello I was given a project that ask the user if they want to sort a text file in title,author or genre. My problem is if the user chooses to sort it using genre, he will input a genre (like fiction,fantasy) and the program would print out books associated by that genre in a alphabetical order. Kindly give me pointers or solution on how I should proceed. I am a beginner in c++ I'm sorry if my code doesn't make sense.

This is a summary of what inside the text file

Starting out with c++, Tony Gaddis, technical
Fundamentals of Database Systems, Elmarsi & Navathe, technical
One Hundred Years of Solitude, Gabriel Garcia Marquez, fiction
Ashes, Kenzo Kitakana, fiction

* The code

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

using namespace std;

struct Book
{
    string title;
    string author;
    string genre;
};

void readFile(ifstream&);
string GetFileName();

vector<Book> Books;
bool compareByTitle(Book, Book);
bool compareByAuthor(Book, Book);
bool compareByGenre(Book, Book);

void Option1();
void Option2();
void Option3();

void print(vector<Book>);
//void filtered_genre(vector<Book>, string);


int main()
{
    int UserChoice;
    string Newgenre;

    ifstream books("books.txt"); 
    while(!books.is_open())
    {
        books.open(GetFileName());
    }
    cout << "\nThe file is successfully open\n";
    readFile(books);

    cout << "\n 1. Sort by Title"
         << "\n 2. Sort by Author"
         << "\n 3. Sort by Genre"
         << "\n 4. EXIT\n"
         << "\n Pls enter your choice: ";
    do
    {
        cin >> UserChoice;
        if(UserChoice == 1)
        {
            Option1();
            break;
        }
        else if(UserChoice == 2)
        {
            Option2();
            break;
        }
        else if(UserChoice == 3)
        {
            cout << "Pls entere a genre : ";
            cin >> Newgenre;
            //Option3();
            break;
        }
        else if(UserChoice == 4)
        {
            cout << "\nExiting....\n";
        }
        else
        {
            cout << "\nInvalid Input\n"
                 << "\nPls enter your choice again: ";
        }
    }
    while (UserChoice != 4);

    books.close();
}

void print(const vector<Book> BookContents)
{
    for(int i=0; i<BookContents.size(); i++)
    {
        cout << "\n\nTitle: " << BookContents[i].title << endl;
        cout << "Author: " << BookContents[i].author << endl;
        cout << "Genre: " << BookContents[i].genre << endl;
    }
}

void readFile(ifstream& file)
{
    Book books;
    string line;
    while(!file.eof())
    {
        getline(file, line);
        stringstream ss(line);
        getline(ss, books.title,',');// Extracts title from the file
        getline(ss, books.author,',');// Extracts author name from the file
        getline(ss, books.genre);// Extracts genre from the file
        Books.push_back(books);// Adds books object to vector
    }
}

string GetFileName()
{
    string FileNameByUser;
    cout << "\nThe file you are trying to open doesn't exist\n"
         << "\nPls enter the file again : ";
    getline(cin, FileNameByUser);
    cout << "\nThe path you entered is : ";
    cout << FileNameByUser + "\n";
    return FileNameByUser;
}

bool compareByTitle(Book book1, Book book2)
{
    return book1.title < book2.title;
}

bool compareByAuthor(Book book1, Book book2)
{
    return book1.author < book2.author;
}

bool compareByGenre(Book book1, Book book2)
{
    return book1.genre < book2.genre;
}

void Option1()
{
    cout << "\nSorting the book contents by title\n";
    std::sort(Books.begin(), Books.end(), compareByTitle);
    print(Books);
}
void Option2()
{
    cout << "\nSorting the book contents by author\n";
    std::sort(Books.begin(), Books.end(), compareByAuthor);
    print(Books);
}
void Option3()
{
    cout << "\nSorting the book contents by genre\n";
    std::sort(Books.begin(), Books.end(), compareByGenre);
    print(Books);
}

Upvotes: 2

Views: 736

Answers (2)

A M
A M

Reputation: 15265

OK, John gave the answer, which is good and has already many upvotes.

Additionally I will show a full modern, object oriented C++ solution, using classes and algorithms.

I would like to especially emphasize that we should overwrite the extractor and inserter operator for classes. Because only the class should know, how to read and write its data. The outside world should not know and take care.

Initially there was only a book class, but I added a also a Books class, to reflect the concept, of having a collection of books.

For this "Books" class, we define the functions necessary needed for user commands.

Please note: In the shown sort functions, I use a pointer to class members, to select, what member shall be used in operations. This makes life easier.

Please have a look a the source code below:

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <regex>

// Remove leading and trailing spaces
std::string trim(const std::string& s) {
    return std::regex_replace(s, std::regex("^ +| +$"), "$1");
}

struct Book
{
    std::string title;
    std::string author;
    std::string genre;

    // Extractor and Inserter ------------------------------------------------------------------------------
    // Overwrite extractor for easier input
    friend std::istream& operator >> (std::istream& is, Book& b) {
        return std::getline(std::getline(std::getline(is, b.title, ','), b.author, ','), b.genre);
    }
    // Overwrite inserter for easier output
    friend std::ostream& operator << (std::ostream& os, const Book& b) {
        return os << "\nTitle:\t " << b.title << "\nAuthor:\t" << b.author << "\nGenre:\t" << b.genre << '\n';
    }
};

struct Books {
    // All the books
    std::vector<Book> data{};

    // Extractor and Inserter ------------------------------------------------------------------------------
    // Overwrite extractor for easier input
    friend std::istream& operator >> (std::istream& is, Books& b) {
        b.data.clear();
        std::copy(std::istream_iterator<Book>(is), {}, std::back_inserter(b.data));
        return is;
    }
    // Overwrite inserter for easier output
    friend std::ostream& operator << (std::ostream& os, const Books& b) {
        std::copy(b.data.begin(), b.data.end(), std::ostream_iterator<Book>(os, "\n"));
        return os;
    }

    // -----------------------------------------------------------------------------------------------------
    // Sort the books according to given member
    void sortBooksAndPrint(std::string Book::*member, std::ostream& os) {
        std::sort(data.begin(), data.end(), [&](const Book& b1, const Book& b2) { return b1.*member < b2.*member; });
        os << *this;
    }
    // Filter the books. Filter can also be a part of the string
    void filterAndPrintBooks(std::string Book::* member, const std::string filter, std::ostream& os) {
        std::sort(data.begin(), data.end(), [&](const Book& b1, const Book& b2) { return b1.title < b2.title; });
        std::copy_if(data.begin(), data.end(), std::ostream_iterator<Book>(os), [&](const Book& b) {
            return trim(b.*member).find(trim(filter)) != std::string::npos; });
    }
};

// Sow menu and get user selection
unsigned int menuOption() {
    std::cout << "\n\n 1. Sort by Title\n 2. Sort by Author\n 3. Sort by Genre\n 4. Filter by Title\n"
        " 5. Filter by Author\n 6. Filter by Genre\n 7. Show list\n\nPress 0 for Exit.\n\nPlease select:  ";
    unsigned int option{};
    std::cin >> option;
    return option;
}

int main() {

    // Open file with books and properties
    if (std::ifstream booksFileStream{ "r:\\books.txt" }; booksFileStream) {

        // Here we will store our books data
        Books books{};

        // Read complete file
        booksFileStream >> books;

        // Show menu and get user commands
        for (bool doLoop{ true }; doLoop; )
        switch(menuOption()) {
            case 1:
                books.sortBooksAndPrint(&Book::title, std::cout);
                break;
            case 2:
                books.sortBooksAndPrint(&Book::author, std::cout);
                break;
            case 3:
                books.sortBooksAndPrint(&Book::genre, std::cout);
                break;
            case 4:
                std::cout << "\nFilter by title: Please enter title: ";
                if (std::string filter{}; std::cin >> filter)
                    books.filterAndPrintBooks(&Book::title, filter, std::cout);
                break;
            case 5:
                std::cout << "\nFilter by author: Please enter author: ";
                if (std::string filter{}; std::cin >> filter)
                    books.filterAndPrintBooks(&Book::author, filter, std::cout);
                break;
            case 6:
                std::cout << "\nFilter by Genre: Please enter Genre: ";
                if (std::string filter{}; std::cin >> filter)
                    books.filterAndPrintBooks(&Book::genre, filter, std::cout);
                break;
            case 7:
                std::cout << books;
                break;
            case 0:
                doLoop = false;
                break;
            default:
                std::cout << "\nWrong Selection. Please select valid menu option\n";
        }
    }
    else {
        std::cerr << "\n*** Error: Cannot open source file with books\n";
    }
    return 0;
}

If you have questions, then I am happy to answer.

Upvotes: 1

john
john

Reputation: 88017

So I think you have all the skill you need to solve this problem. You know how to add items to a vector and you know how to sort a vector using different criteria. The only piece left, as you say in the title, is how to filter the data.

There's lots of different ways to do this, I'm going to suggest a very straightforward one. First add a genre parameter to your Option3 function, so that you can pass the genre that you've read in main to that function.

void Option3(string genre);

int main()
{
    ...
        else if(UserChoice == 3)
        {
            cout << "Pls entere a genre : ";
            cin >> Newgenre;
            Option3(Newgenre);
            break;
        }
    ...
}

void Option3(string genre)
{
    ...
}

Now in the function Option3 we're going to do the filtering. There are four simple steps

  1. Declare a new vector to hold the filtered data, call it (say) FilteredBooks

  2. Now write a loop that goes through all the books and compares the book's genre to the genre that the user is interested in. If the genres are the same then add the book to the FilteredBooks vector.

  3. Now sort the FilteredBooks vector alphabetically (that's what you said, I guess you mean alphabetically by title).

  4. Now print the FilteredBooks vector. You already have a function to do this.

That's it. Hope this helps.

Upvotes: 4

Related Questions