#include <iostream> #include <string> #include <fstream> #include <iomanip> #include <limits> #include <algorithm> using namespace std; const int MAX_TOPPINGS = 2; const int MAX_DONUTS = 50; struct donutType { string name; bool type; double price; string filling; string toppings[MAX_TOPPINGS]; }; ifstream getFileStream(string); int getDonuts(ifstream&, donutType[]); bool continueMenu(string); void sortByPrice(donutType[], int); int searchByName(const donutType[], int, string); void removeDonutFromList(donutType[], int&, int); int getCheapestDonut(const donutType[], int); void soldDonut(donutType, donutType[], int&); void outputSoldDonuts(ofstream&, const donutType[], int); void displayAvailableDonuts(const donutType[], int); string allCaps(string); /** * The main function to manage the donut ordering program. * * @return The exit status of the program. */ int main() { ifstream infile; ofstream outfile("sold.csv"); string filename; string request; donutType donuts[MAX_DONUTS]; donutType soldDonuts[MAX_DONUTS]; int amtDonuts = 0; int amtSold = 0; double total = 0; cout << fixed << setprecision(2); // gets step 1 the input stream infile = getFileStream("Enter a donut file: "); // step 2 get total donuts amtDonuts = getDonuts(infile, donuts); // step 3 print to console cout << "Welcome to Hank's Donut World!\n\n"; while (true) { // step 4 print the available donuts to console displayAvailableDonuts(donuts, amtDonuts); //step 5 ask for user input string nameEnteredByUser; cout << "Enter donut name or cheapest: "; getline(cin, nameEnteredByUser); // step 6 check the user input choice and call function accordingly int donutIndex; transform(nameEnteredByUser.begin(), nameEnteredByUser.end(), nameEnteredByUser.begin(), ::tolower); if (nameEnteredByUser.compare("cheapest") == 0) donutIndex = getCheapestDonut(donuts, amtDonuts); else donutIndex = searchByName(donuts, amtDonuts, nameEnteredByUser); // step 7 check if the search results was empty if (donutIndex == -1) { cout << "Donut not found!\n"; continue; } // step 8 print the selection to console cout << "You selected " << donuts[donutIndex].name << ".\nExcellent choice!\n"; // Step 9 inserting the sold donut into solddonuts array total += donuts[donutIndex].price; soldDonuts[amtSold] = donuts[donutIndex]; amtSold++; // Step 10 removing the donut from donuts array removeDonutFromList(donuts, amtDonuts, donutIndex); // Step 11 choice to continue with more purchase bool complete = continueMenu("Will this complete your order? "); if (!complete) { if (amtDonuts > 0) continue; // go to Step 4 else break; // End the program if there are no available donuts left } // Step 13 sort the donuts sold by price using bubble sort sortByPrice(soldDonuts, amtSold); // Step 14 if (outfile.is_open()) { outfile << "Sold," << fixed << setprecision(2) << total << "\n"; outputSoldDonuts(outfile, soldDonuts, amtSold); } else { cerr << "Error opening output file.\n"; } break; // End the program } return 0; } /** * Retrieves an input file stream for a given filename after prompting the user. * * @param msg The message to prompt the user for the filename. * @return An ifstream object for the specified filename. */ ifstream getFileStream(string msg) { ifstream fileStream; string fileName; while (true) { // Prompt user for a filename cout << msg; getline(cin, fileName); // Open the file stream fileStream.open(fileName); // Check if the file stream is open if (fileStream.is_open()) { break; } else { fileStream.clear(); // Clear any error flags cin.ignore(numeric_limits<streamsize>::max(), '\n'); // Clear input buffer } } return fileStream; } /** * Reads donut data from an input file stream into a struct array of donutType. * * @param infile An input file stream containing donut data. * @param donuts An array of donutType to store the read data. * @return The number of donuts read from the file. */ int getDonuts(ifstream &infile, donutType donuts[]) { const int MAX_RECORDS = 50; size_t MAX_CHARS = 100; char line[MAX_CHARS]; int count = 0; // Read and ignore the header line infile.getline(line, MAX_CHARS); // Read the CSV file line by line while (!infile.fail() && count < MAX_RECORDS) { // Read the entire line into 'line' infile.getline(line, MAX_CHARS); // Create a strings to parse the line string lineString(line); if(lineString.size() < 2) break; // Tokenize the line based on commas size_t start = 0; size_t end = lineString.find(','); // Read name donuts[count].name = lineString.substr(0, end); //add plus one to skip the comma lineString = lineString.substr(end+1); // Read type end = lineString.find(','); donuts[count].type = (lineString.substr(0, end) == "Cake"); lineString = lineString.substr(end+1); // Read filling end = lineString.find(','); donuts[count].filling = lineString.substr(0, end); lineString = lineString.substr(end+1); // Read toppings end = lineString.find(','); donuts[count].toppings[0] = lineString.substr(0, end); lineString = lineString.substr(end+1); end = lineString.find(',') ; donuts[count].toppings[1] = lineString.substr(0, end); lineString = lineString.substr(end+1); // Read price donuts[count].price = stod(lineString); count++; } return count; } /** * Displays a prompt and waits for user input to continue or exit. * * @param prompt The message to display as a prompt. * @return True if the user chooses to continue, false if the user chooses to exit. */ bool continueMenu(string prompt) { string input; while (true) { cout << prompt; getline(cin, input); // Convert the input to lowercase for case-insensitive comparison transform(input.begin(), input.end(), input.begin(), ::tolower); if (input == "no") return false; else if (input == "yes") return true; else cerr << "Invalid input. Please enter 'Yes' or 'No' (case insensitive).\n"; } } /** * Sorts an array of donuts based on their prices in ascending order using bubble sort. * * @param donuts An array of donuts to be sorted. * @param amtDonuts The number of donuts in the array. */ void sortByPrice(donutType donuts[], int amtDonuts) { for (int i = 0; i < amtDonuts - 1; ++i) { for (int j = 0; j < amtDonuts - i - 1; ++j) { // Compare prices and swap if needed if (donuts[j].price > donuts[j + 1].price) { // Swap donutType temp = donuts[j]; donuts[j] = donuts[j + 1]; donuts[j + 1] = temp; } } } } /** * Searches for a donut by name in an array of donuts. * * @param donuts An array of donuts to be searched. * @param amtDonuts The number of donuts in the array. * @param name The name of the donut to be searched. * @return The index of the found donut if present, otherwise -1. */ int searchByName(const donutType donuts[], int amtDonuts, string name) { transform(name.begin(), name.end(), name.begin(), ::tolower); for (int i = 0; i < amtDonuts; ++i) { string donutName = donuts[i].name; transform(donutName.begin(), donutName.end(), donutName.begin(), ::tolower); if (donutName.compare(name) == 0) return i; // Return the index if the name is found } return -1; // Return -1 if the name is not found } /** * Finds the index of the cheapest donut in an array of donuts. * * @param donuts An array of donuts to be searched. * @param amtDonuts The number of donuts in the array. * @return The index of the cheapest donut if the array is not empty, otherwise -1. */ int getCheapestDonut(const donutType donuts[], int amtDonuts) { if (amtDonuts <= 0) return -1; // Return -1 if the array is empty // Initialize to maximum possible value double minPrice = donuts[0].price; // Index of the cheapest donut int minIndex = 0; for (int i = 1; i < amtDonuts; ++i) { if (donuts[i].price < minPrice) { minPrice = donuts[i].price; minIndex = i; } } return minIndex; } /** * Converts a given string to uppercase. * * @param s The input string to be converted to uppercase. * @return The uppercase version of the input string. */ string allCaps(string s) { string upper = s; for (char &c : upper) c = toupper(static_cast<unsigned char>(c)); return upper; } /** * Removes a donut from the list based on the provided index. * * @param donuts The array of donuts. * @param amtDonuts The current number of donuts in the array. * @param removeIndex The index of the donut to be removed. */ void removeDonutFromList(donutType donuts[], int &amtDonuts, int removeIndex) { if (removeIndex < 0 || removeIndex >= amtDonuts) { cerr << "Invalid index to remove. Index out of range." << endl; return; } // Shift elements to fill the gap for (int i = removeIndex; i < amtDonuts - 1; ++i) { donuts[i] = donuts[i + 1]; } // Decrement the amount of donuts amtDonuts--; } /** * Outputs information about sold donuts to an output file. * * @param outfile The output file stream. * @param soldDonuts The array of sold donuts. * @param amtSold The current number of sold donuts in the array. */ void outputSoldDonuts(ofstream &outfile, const donutType soldDonuts[], int amtSold) { if (!outfile.is_open()) { cerr << "Error: Output file is not open." << endl; return; } outfile << fixed << setprecision(2); outfile << "Name,Type,Filling,Topping1,Topping2,Price" << endl; for (int i = 0; i < amtSold; ++i) { outfile << soldDonuts[i].name << ","; outfile << (soldDonuts[i].type ? "Cake" : "Dough") << ","; outfile << soldDonuts[i].filling << ","; outfile << soldDonuts[i].toppings[0] << ","; outfile << soldDonuts[i].toppings[1] << ","; outfile << soldDonuts[i].price << "\n"; } } /** * Displays the list of available donuts with their names and prices. * * @param donuts The array of available donuts. * @param amtDonuts The current number of available donuts in the array. */ void displayAvailableDonuts(const donutType donuts[], int amtDonuts) { cout << "List of donuts" << endl; cout << "---------------------------" << endl; int count = 0; for (int i = 0; i < amtDonuts; ++i) cout << donuts[i].name << " " << donuts[i].price << endl; cout << endl; }
"to the trailing", you can't read from the first character to the first comma."quotes", so you should extract the first field with the appropriate delimiter. It does not have to be same for each call tostrtok. But the second example does not have those quote marks. Post the exact lines copy/pasted into the question (and formatted as code).strtokis not very suitable to parse CSV, because it will skip empty fields.