In my program first I read a file into a vector of strings called rosterList:
100
95
0
-1
110
80
90
-1
120
80
75
-1
130
60
55
-1
This step is successful. My goal is to create a vector of Student objects using the data above. Constructor accepts 3 strings as parameters:
Student::Student(string id,string g1,string g2)
To do that, the program loops through this vector of strings line by line, if the line converted to integer is greater or equal to 100, it is an id, then dynamically create a new Student object by passing the id (current line) and the next 2 lines as parameters, and add the object to the vector studentRecords
for (vector<string>::iterator it = rosterList.begin(); it<rosterList.end();it++){
if(stoi(*it)>=100 || it == rosterList.begin()){ // error
studentRecords.push_back(
Student(*it,*(it+1),*(it+2)) // dynamically push
);
}
}
And there is a dynamic error:
libc++abi.dylib: terminating with uncaught exception of type
std::invalid_argument: stoi: no conversion
I looked it up online, the error comes from stoi not be able to convert. Where in the program goes wrong?
Either you have to read your file correctly....or
If you are not confident on file data, do a validation for numeric content.
Below is code sample.
Any junk that you read other than numeric content in your file will cause failure.
Below code compiles and for your idea. You can optimize it.
#include <iostream>
#include <cstring>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
std::vector<string> rosterList;
bool is_numeric(string strin)
{
char const *str = strin.c_str();
return all_of(str, str+strlen(str),
[](unsigned char c) { return ::isdigit(c); });
}
int main(int argc, char *argv[])
{
rosterList.push_back("100");
rosterList.push_back(" ");
rosterList.push_back("140");
rosterList.push_back("180");
for (vector<string>::iterator it = rosterList.begin(); it<rosterList.end();it++){
if(is_numeric(*it) && (stoi(*it)>=100 || it == rosterList.begin())){ // error
std::cout<<stoi(*it)<<std::endl;
}
}
}
One problem I see in your code is that you're accessing it+1 and it+2 also when it is the last 2 positions of the vector. This will try to access a pointer that doesn't exist as this points to after rosterList.end().
You will have to test if it+1 and it+2 are < than rosterList.end() before using them.
Related
This question already has answers here:
Comparing two char* for equality [duplicate]
(3 answers)
Closed 1 year ago.
I have some strange problem with reversed boolean value in C++. :')
I want to check if argument passed to main method is not "file.txt". When it's not I want to create new file with name given as main argument and write there lines of randomized data. Otherwise I want to read data from "file.txt".
Everything works just fine as I wished, but when I type
g++ sourcefile.cpp header.h in terminal, and then
./a.out file.txt
Program is entering this random data into "file.txt" instead of reading from it.
And on the other hand when I type:
./a.out someRandomData.txt it is reading from "file.txt". Any ideas?
int main(int argc, char* argv[]) {
if(argv[1] != "file.txt"){
fileWithRandomData(argv[1]);
}
else{
std::vector<std::string> row;
std::string line, word, temp;
std::ifstream MyReadFile(argv[1]);
(...)
And here is sample of my fileWithRandomData funtion:
void fileWithRandomData(string name){
srand(time(NULL));
std::ofstream MyFile(name);
int metalsNumber = rand() % 5000 + 1; //first line -> number of available metals.
(...)
You need to compare two C strings instead of comparing addresses where they are stored.
That is write
#include <cstring>
//...
if( strcmp( argv[1], "file.txt" ) != 0 ){
Otherwise in this if statement
if(argv[1] != "file.txt"){
there are compared two pointers: argv[1] and the pointer to the first character of the string literal "file.txt".
As the strings occupy different extents of memory the condition of the if statement will always evaluate to true.
I'm currently working on a lab that needs to keep inventory for a hardware store in a variety of ways. One of the ways is to put the information into an array. There is a list of tools that are given that each have a Record number, name, quantity, and cost. I figured that the best way to go about doing this is to put the information into a text file and add it into the array from there, but I am stuck on how to do so. So far I am able to manually add each item but that is very tedious and wont be easy to work with.
struct node {
int recordNum;
char toolName[20];
int quantity;
double toolCost;
node* next;
};
void unsortedArray() {
ifstream toolsFile("Tools.txt");
const int MAX = 100;
node unsortedArr[MAX];
unsortedArr[0].recordNum = 68;
strcpy_s(unsortedArr[0].toolName, "Screwdriver");
unsortedArr[0].quantity = 106;
unsortedArr[0].toolCost = 6.99;
}
I'm using a struct node as I later have to use linked lists. Here is the .txt file that has the information for each of the products.
68 Screwdriver 106 6.99
17 Hammer 76 11.99
56 Power saw 18 99.99
3 Electric Sander 7 57
83 Wrench 34 7.5
24 Jig Saw 21 11
39 Lawn mower 3 79.5
77 Sledge hammer 11 21.5
If there is a way to do this that doesn't involve the text file that works perfectly fine as well. I'm new to C++ and this is just what came to mind first.
Any help is much appreciated. Thanks!
I gather you would like to store values from a text file into an array. If so,
you would want to start by reading each line from the file. Next, split the line into each data field. Then append to the text file and repeat.
To read each line, I used a string to hold the line being read
Next, the line is split every time a character is seen. I used a ';' to separate values.
For example, the first line of your file would read:
68;Screwdriver;106;6.99
The splitting procedure then returns a vector of strings. This holds, in order:
record number, name, quantity and price.
The numbers that are integers and doubles need to be converted from strings, so
two functions cast them.
Finally, the values are stored in the index specified. For eg, after the program runs, index 68 of the array would hold 68, Screwdriver, 106, 6.99.
Here is the working solution
Note, to simplify the storage method a bit, I have changed tool name to a string. Feel free to change it back if need be
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
std::vector<std::string> split(std::string strToSplit, char delimeter) {
std::stringstream ss(strToSplit);
std::string item;
std::vector<std::string> splittedStrings;
while (std::getline(ss, item, delimeter)) {
splittedStrings.push_back(item);
}
return splittedStrings;
}
struct node {
int recordNum;
std::string toolName; // Changed this as string was already used elsewhere
int quantity;
double toolCost;
node* next;
};
int main() {
node node_array[100];
int index;
std::ifstream tool_file;
tool_file.open("Text.txt"); //The record file
std::string line;
std::vector<std::string> split_line;
while (std::getline(tool_file, line)) { //Repeat for each line of file
split_line = split(line, ';'); // Split each line into its components
index = stoi(split_line[0]); // Convert record num into an int
node_array[index].toolName = split_line[1]; // Save values into index
node_array[index].quantity = stoi(split_line[2]);
node_array[index].toolCost = stod(split_line[3]);
}
tool_file.close();
}
So I'm having this problem with substrings and converting them into integers. This will probably be an easy-fix but I'm not managing to find the answer.
So I receive this string "12-12-2012" and i want to split it, convert into integers and call the modifications methods like this:
string d = (data.substr(0,data.find("-")));
setDia(atoi(d.c_str()));
But it gives me the error mentioned in the title when I try to comvert into an integer.
EDIT:
Turns out that the string doesn't actually contain a '-' but this is really confusing since the string in the parameter results from this : to_char(s.diaInicio,'dd-mm-yyyy')
More information: I used the debugger and it's making the split correctly since the value that atoi receives is 12 (the first split). But I don't know why the VS can't convert into an integer even though the string passed is "12".
This code is not save in the sense that it fails when data does not contain a -.
Try this:
std::size_t p = data.find("-");
if(p == std::string::npos) {
// ERROR no - in string!
}
else {
std::string d = data.substr(0,p);
setDia(atoi(d.c_str()));
}
Please duplicate the problem with a very simple program. If what you say is correct, then the following program should also fail (taken from Danvil's example, and without calling the unknown (to us) setDia() function):
#include <string>
#include <cstdlib>
using namespace std;
int main()
{
string data = "12-12-2012";
std::size_t p = data.find("-");
if(p == std::string::npos) {
// ERROR no - in string!
}
else {
std::string d = data.substr(0,p);
atoi(d.c_str());
}
}
I picked up this bit of code a while back as a way to select a random line from a text file and output the result. Unfortunately, it only seems to output the first letter of the line that it selects and I can't figure out why its doing so or how to fix it. Any help would be appreciated.
#include "stdafx.h"
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <time.h>
using namespace std;
#define MAX_STRING_SIZE 1000
string firstName()
{
string firstName;
char str[MAX_STRING_SIZE], pick[MAX_STRING_SIZE];
FILE *fp;
int readCount = 0;
fp = fopen("firstnames.txt", "r");
if (fp)
{
if (fgets(pick, MAX_STRING_SIZE, fp) != NULL)
{
readCount = 1;
while (fgets (str, MAX_STRING_SIZE, fp) != NULL)
{
if ((rand() % ++readCount) == 0)
{
strcpy(pick, str);
}
}
}
}
fclose(fp);
firstName = *pick;
return firstName;
}
int main()
{
srand(time(NULL));
int n = 1;
while (n < 10)
{
string fn = firstName();
cout << fn << endl;
++n;
}
system("pause");
}
firstName = *pick;
I am guessing this is the problem.
pick here is essentially a pointer to the first element of the array, char*, so of course *pick is of type char.. or the first character of the array.
Another way to see it is that *pick == *(pick +0) == pick[0]
There are several ways to fix it. Simplest is to just do the below.
return pick;
The constructor will automatically make the conversion for you.
Since you didn't specify the format of your file, I'll cover both cases: fixed record length and variable record length; assuming each text line is a record.
Reading Random Names, Fixed Length Records
This one is straight forward.
Determine the index (random) of the record you want.
Calculate the file position = record length * index.
Set file to the position.
Read text from file, using std::getline.
Reading Random Names, Variable Length Records
This assumes that the length of the text lines vary. Since they vary, you can't use math to determine the file position.
To randomly pick a line from a file you will either have to put each line into a container, or put the file offset of the beginning of the line into a container.
After you have your container establish, determine the random name number and use that as an index into the container. If you stored the file offsets, position the file to the offset and read the line. Otherwise, pull the text from the container.
Which container should be used? It depends. Storing the text is faster but takes up memory (you are essentially storing the file into memory). Storing the file positions takes up less room but you will end up reading each line twice (once to find the position, second to fetch the data).
Augmentations to these algorithms is to memory-map the file, which is an exercise for the reader.
Edit 1: Example
include <iostream>
#include <fstream>
#include <vector>
#include <string>
using std::string;
using std::vector;
using std::fstream;
// Create a container for the file positions.
std::vector< std::streampos > file_positions;
// Create a container for the text lines
std::vector< std::string > text_lines;
// Load both containers.
// The number of lines is the size of either vector.
void
Load_Containers(std::ifstream& inp)
{
std::string text_line;
std::streampos file_pos;
file_pos = inp.tellg();
while (!std::getline(inp, text_line)
{
file_positions.push_back(file_pos);
file_pos = inp.tellg();
text_lines.push_back(text_line);
}
}
I'm having a bit of trouble with a school assignment for C++. The specific problem I'm having involves reading lines from a file that contain a series of between 5 and 6 grades. The grades for each student appear together in a single line following the student's name and id number. The challenge here is that the grades can have a variable number of spaces between them, and that if there are only 5 grades present, an error message needs to be generated to screen but the program is to continue running and average the 5 grades. Example of input: 23 46 68 85 98
I got the student name and id easily, but the string of digits is giving me problems. My plan was to getline and then tokenize the string and assign each token to a cell in an array. This works fine for 6 grades, but when only 5 grades are present it is assigning garbage to the sixth cell.
Here is the snippet of code that concerns this section:
fin.getline(gradeList, 200);
grade = strtok (gradeList, " ");
while (grade != '\0')
{
gradeArr[cycler] = atoi(grade);
grade = strtok(NULL, " ");
cycler++;
}
I tried doing an isdigit check on each token before converting it to an int, and writing a 0 in for any token that failed the isdigit check, but that didn't work at all. It seems like it is pulling the name from the next line when only 5 grades are present and then when it atoi's it, it changes it to a huge number.
I thought that when the program did getline, it would only grab the line until it saw the endline terminator. Is this not what is happening?
Scrap the C nonsense and use real C++:
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
// ...
std::vector<int> grades;
std::string line;
while (std::getline(fin, line))
{
std::istringstream iss(line);
int grade;
while (iss >> grade)
{
grades.push_back(grade);
}
}
Here's a somewhat more compact and elegant method, using istream-iterators and back-inserters:
#include <iterator>
#include <algorithm>
// other headers as before
std::vector<int> grades;
for (std::string line; std::getline(fin, line); )
{
std::istringstream iss(line);
std::copy(std::istream_iterator<int>(iss), std::istream_iterator<int>(),
std::back_inserter(grades));
}
The point is that formatted token extraction (>>) from streams does already precisely what you want to, and it subsumes both tokenizing and parsing into integers.
The istream_iterator encapsulates the token extraction and allows you to treat a stream as if it were already a sequence of parsed tokens. The copy algorithm then simply copies this sequence into a vector, inserting it at the end of the container.
If you use strtol or strtod, it gives you back a pointer to the end of what you just processed, so you can continue from there. No tokenization necessary :)
But it sounds like your problem is that you're reading the wrong line. Print out the gradeList variable before you start parsing.
You should end up with something like this:
fin.getline(grade_text, 200);
const char* grade = grade_text;
const char* next_grade;
double grade_sum = 0;
for( int grade_count = 0; grades[grade_count] = strtol(grade, &next_grade, 10), next_grade > grade; ++grade_count )
grade_sum += grades[grade_count];
double mean_grade = grade_sum / grade_count;