How to determine last string value in a text - c++

I'm given a text in std::string that i want to analyze using stringstream.
The text is a line from a csv file in the following format:
SPIN;5;WIN;10;STOPPOSITIONS;27;1;14
I must create a key value pair (in a map) with the key being a string value from the line (ex: "SPIN") and the value a vector populated with the next integer value from the line (ex: 5). (KVP: {"SPIN", {5}}).
The problem is that I dont know how to determine the last string value of the line (in this example "STOPPOSITIONS").
When i get the word "STOPPOSITIONS" at the next iteration the variable word is changed to "1" which is wrong because i should create the following kvp (KVP: {"STOPPOSITIONS", {27,1,14}}).
What should i fix in order to find the last string value of a line?
Here is the code I'm using:
std::map<std::string, std::vector<uint64_t>> CsvReader::readAllKvp()
{
if (!_ifs->is_open())
{
_ifs->open(_fileName);
}
std::map<std::string, std::vector<uint64_t>> result;
std::string line;
std::string word;
uint64_t val;
while(getline(*_ifs,line,'\n') >> std::ws)
{
/* do stuff with word */
std::istringstream ss(line);
while(getline(ss, word, ';') >> std::ws)
{
//no more strings found
if(word == "")
{
//read all integers at the end of the line and put them
//in the map at the last key added (in our case: STOPPOSITIONS)
while(ss >> val)
{
result[result.rbegin()->first].push_back(val);
}
break;
}
if (result.find(word) == result.end()) //word not found in map
{
std::vector<uint64_t> newV;
result.insert(
std::pair<std::string, std::vector<uint64_t>>(word, newV));
}
ss >> val;
result[word].push_back(val);
ss.ignore(std::numeric_limits<std::streamsize>::max(),';');
}
}
_ifs->close();
return result;
}

I made an example of my suggested method. It only read one line, but adding another outer loop and processing all the lines of the file is a simple task.
#include <iostream>
#include <sstream>
#include <fstream>
#include <map>
#include <vector>
using std::cout;
using std::endl;
std::map<std::string, std::vector<uint64_t>> readAllKvp()
{
std::string str = "SPIN;5;WIN;10;STOPPOSITIONS;27;1;14";
std::stringstream ss(str); // Emulating input from file
std::map<std::string, std::vector<uint64_t>> result;
std::string word;
std::string last_string;
uint64_t val;
while(getline(ss >> std::ws, word, ';') >> std::ws)
{
try {
val = std::stoi(word);
if(!last_string.empty())
result[last_string].push_back(val);
} catch (std::invalid_argument&) {
last_string = word;
}
}
return result;
}
int main() {
auto map = readAllKvp();
for (auto& m : map) {
cout << m.first << ": ";
for (auto v : m.second)
cout << v << ' ';
cout << endl;
}
}

Related

Problem reading file and storing it in unordered_map

I'm trying to read a file and store it's content into an unordered_map but I've got a little problem. This is my unordered_map:
std::unordered_map<std::string, std::vector<double>> _users;
And this is the content of the file that I'm trying to read:
Mike 4 NA 8 NA NA
Lena NA 8 4 NA 9
I want to store the content in _users in a way that the key is the name, and inside the vectors we have the numbers associated to the name. Moreover I want NA to be equal to 0.
So I managed to do this:
while ( std::getline(file, line))
{
std::istringstream iss(line);
std::string key;
double value;
iss >> key;
dict[key] = std::vector<double>();
while (iss >> value)
{
dict[key].push_back(value);
}
}
But since value is a double, when checking NA it just stops the while loop and I just get, for example with Mike: Mike 4. How can I do in order to get it to read NA and put it as 0 inside the vector ? Thank you for your help!
For your inner loop, you could do:
std::string stringval;
while (iss >> stringval)
{
double value;
try
{
value = std::stod (stringval);
}
catch (...)
{
value = 0.0;
}
dict[key].push_back(value);
}
I also tried something:
#include <iostream>
#include <unordered_map>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
int main(int argc, char*[])
{
//1. Read the file and load the map
std::unordered_map<std::string, std::vector<double>> users{};
std::ifstream file{"file.txt"};
std::string line{};
while(std::getline(file, line))
{
std::istringstream iss(line);
std::string key{};
iss >> key;
std::vector<double> values{};
while(iss)
{
double value{0};
if (iss.str() != "NA")
iss >> value;
values.push_back(value);
}
users.insert({key, values});
}
//2. Printing the map
for(auto const &[key, values]: users)
{
std::cout << "Key: " << key << std::endl;
for(auto const value: values)
{
std::cout << value << " ";
}
std::cout << std::endl;
}
return EXIT_SUCCESS;
}

Trying to push an unknown number of strings into a vector, but my cin loop doesn't terminate

I am trying to take strings as input from cin, and then push the string into a vector each time. However, my loop doesn't terminate even when I put a '\' at the end of all my input.
int main(void) {
string row;
vector<string> log;
while (cin >> row) {
if (row == "\n") {
break;
}
log.push_back(row);
}
return 0;
}
I've tried replacing the (cin >> row) with (getline(cin,row)), but it didn't make any difference. I've tried using stringstream, but I don't really know how it works. How do I go about resolving this?
As commented by #SidS, the whitespace is discarded. So you have to think about another strategy.
You could instead check if row is empty. But that will only work with std::getline:
#include <vector>
#include <string>
#include <iostream>
int main() {
std::string row;
std::vector<std::string> log;
while (std::getline(std::cin, row)) {
if (row.empty()) {
break;
}
log.push_back(row);
}
std::cout << "done\n";
}
OP, in case you want to save single words (rather than a whole line), you can use regex to single-handedly push each of them into log after input:
#include <vector>
#include <string>
#include <iostream>
#include <regex>
int main() {
const std::regex words_reg{ "[^\\s]+" };
std::string row;
std::vector<std::string> log;
while (std::getline(std::cin, row)) {
if (row.empty()) {
break;
}
for (auto it = std::sregex_iterator(row.begin(), row.end(), words_reg); it != std::sregex_iterator(); ++it){
log.push_back((*it)[0]);
}
}
for (unsigned i = 0u; i < log.size(); ++i) {
std::cout << "log[" << i << "] = " << log[i] << '\n';
}
}
Example run:
hello you
a b c d e f g
18939823
#_#_# /////
log[0] = hello
log[1] = you
log[2] = a
log[3] = b
log[4] = c
log[5] = d
log[6] = e
log[7] = f
log[8] = g
log[9] = 18939823
log[10] = #_#_#
log[11] = /////
If you want to store the tokens of one line from std::cin, separated by the standard mechanism as in the operator>> overloads from <iostream> (i.e., split by whitespace/newline), you can do it like this:
std::string line;
std::getline(std::cin, line);
std::stringstream ss{line};
const std::vector<std::string> tokens{std::istream_iterator<std::string>{ss},
std::istream_iterator<std::string>{}};
Note that this is not the most efficient solution, but it should work as expected: process only one line and use an existing mechanism to split this line into individual std::string objects.
You can't read newline by using the istream& operator >> of string. This operator ignores whitespaces and will never return the string "\n". Consider using getline instead.

C++ Read file line by line then split each line using the delimiter

i have searched and got a good grasp of the solution from a previous post but I am still stuck a bit.
My problem is instead of "randomstring TAB number TAB number NL"
My data is "number (space colon space) number (space colon space) sentence"
I've edited the code below but still can't get it to work 100% because the parameters getline takes is (stream, string, delimiter).
For some reason, it only gets the first word of the sentence as well and not the rest.
Previous post
I want to read a txt file line by line and after reading each line, I want to split the line according to the tab "\t" and add each part to an element in a struct.
my struct is 1*char and 2*int
struct myStruct
{
char chr;
int v1;
int v2;
}
where chr can contain more than one character.
A line should be something like:
randomstring TAB number TAB number NL
SOLUTION
std::ifstream file("plop");
std::string line;
while(std::getline(file, line))
{
std::stringstream linestream(line);
std::string data;
int val1;
int val2;
// If you have truly tab delimited data use getline() with third parameter.
// If your data is just white space separated data
// then the operator >> will do (it reads a space separated word into a string).
std::getline(linestream, data, '\t'); // read up-to the first tab (discard tab).
// Read the integers using the operator >>
linestream >> val1 >> val2;
}
At the following code line, the data variable will hold the complete line. And linestream will be consumed, so further readings will not yield anything.
std::getline(linestream, data, '\t'); // read up-to the first tab (discard tab).
Instead, you can just work on the line like this
while (std::getline(file, line))
{
int token1 = std::stoi(line.substr(0, line.find(" : ")));
line.erase(0, line.find(" : ") + 3);
int token2 = std::stoi(line.substr(0, line.find(" : ")));
line.erase(0, line.find(" : ") + 3);
std::string token3 = line;
}
What exactly is your problem ?
//Title of this code
//clang 3.4
#include <iostream>
#include <string>
#include <iterator>
#include <algorithm>
#include <sstream>
#include <vector>
struct Data
{
int n1;
int n2;
std::string sequence;
};
std::ostream& operator<<(std::ostream& ostr, const Data& data)
{
ostr << "{" << data.n1 << "," << data.n2 << ",\"" << data.sequence << "\"}";
return ostr;
}
std::string& ltrim(std::string& s, const char* t = " \t")
{
s.erase(0, s.find_first_not_of(t));
return s;
}
std::string& rtrim(std::string& s, const char* t = " \t")
{
s.erase(s.find_last_not_of(t) + 1);
return s;
}
std::string& trim(std::string& s, const char* t = " \t")
{
return ltrim(rtrim(s, t), t);
}
int main()
{
std::string file_content{
"1\t1\t\n"
"2\t2\tsecond sequence\t\n"
"3\t3\tthird sequence\n"};
std::istringstream file_stream{file_content};
std::string line;
std::vector<Data> content;
while(std::getline(file_stream, line))
{
std::istringstream line_stream{line};
Data data{};
if(!(line_stream >> data.n1 >> data.n2))
{
std::cout << "Failed to parse line (numbers):\n" << line << "\n";
break;
}
auto numbers_end = line_stream.tellg();
if(numbers_end == -1)
{
std::cout << "Failed to parse line (sequence):\n" << line << "\n";
break;
}
data.sequence = line.substr(numbers_end);
trim(data.sequence);
content.push_back(std::move(data));
}
std::copy(content.cbegin(), content.cend(),
std::ostream_iterator<Data>(std::cout, "\n"));
}
Live
Live with colons

How to group values from the same line into a pair?

I am trying to write a program that reads a text file which has a data set in the form below and puts every 2 integers per line into a pair:
1 2
3 4
5 6
... and so on
Currently, this part of my main code reads the file line by line and converts the imported strings to integers one number at a time, but I am not sure how I would group 2 integers at a time in each line and put them in a pair. Ultimately, I want to add all the pairs to a single set.
while (getline(fs, line)) {
istringstream is(line);
while(is >> line) {
int j = atoi(line.c_str());
cout << "j = " << j << endl;}}
You could simply use std::pair, like this:
istringstream is(line);
pair<int, int> p;
is >> p.first;
is >> p.second;
cout << p.first << " " << p.second;
In next step you could use std::set<std::pair<int, int> > to acheive your goal of putting the pairs into single set.
Try this:
std::ifstream ifs{ "yourfile.txt" };
// read line by line
std::string line;
while(std::getline(ifs, line)) {
std::istringstream iss{ line }; // make a stream on the line
auto values = std::make_pair<int, int>(0, 0);
if(iss >> values.first >> values.second) { // check stream state
// after reading two ints
// (this skips spaces)
std::cout << "a=" << values.first << ", b=" << values.second << "\n";
} else {
throw std::runtime_error{ "bad data" };
}
// at this point, you have read a valid pair of ints into "values"
}
See the code comments for explanations.
Use pair available in 'utility' header, following is the sample code for the same.
while (getline(fs, line))
{
stringstream is;
is<<test;
int i, j;
is>>i;
is>>j;
pair<int, int> pr(i,j);
}
Following may help:
std::string line;
std::set<std::pair<int, int>> myset;
while (std::getline(fs, line)) {
std::istringstream is(line);
int a, b;
if (is >> a >> b) {
myset.insert({a, b});
} else {
throw std::runtime_error("invalid data");
}
}
Live example
To consider 1, 5 as identical as 5, 1, you may use:
struct unordered_pair_comp
{
bool operator () (const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) const
{
return std::minmax(lhs.first, lhs.second) < std::minmax(rhs.first, rhs.second);
}
};
and so your myset becomes std::set<std::pair<int, int>, unordered_pair_comp> myset;
Live example
Something like this should work for your purposes.
while (getline(fs, line))
{
istringstream is(line);
int i, j;
is >> i >> j;
std::pair<int,int> my_pair = std::make_pair (i, j);
}
Dont make the imported strings into integers. Read the two strings from one line and concatenate them. Then make them integers. Like this:
#include <sstream>
#include <fstream>
#include <string>
#include <iostream>
using namespace std;
int main()
{
string line;
ifstream fs("input.txt");
while (getline(fs, line))
{
istringstream is(line);
string num1, num2;
while (is >> num1 >> num2)
{
num1 += num2;
int j = stoi(num1);
cout << "j = " << j << endl;
}
cin.get();
}
}

C++ reading from data from text file

I have the following data:
$GPVTG,,T,,M,0.00,N,0.0,K,A*13
I need to read the data, however there are blanks in between the commas, therefore I am not sure how I should read the data.
Also, how do I select GPVTG only for a group of data? For example:
GPVTG,,T,,M
GPGGA,184945.00
GPRMC,18494
GPVTG,,T,,M,0
GPGGA,184946.000,3409
I have tried using:
/* read data line */
fgets(gpsString,100,gpsHandle);
char type[10] = "GPVTG";
sscanf(gpsString," %GPVTG", &type);
if (strcmp(gpsString, "GPTVG") == 0){
printf("%s\n",gpsString);
}
Thats what i'd do
#include <iostream>
#include <vector>
#include <sstream>
#include <fstream>
#include <string>
using namespace std;
vector<string> &split(const string &s, char delim, vector<string> &elems) {
stringstream ss(s);
string item;
while (getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
vector<string> split(const string &s, char delim) {
vector<string> elems;
split(s, delim, elems);
return elems;
}
int main()
{
ifstream ifs("file.txt");
string data_string;
while ( getline( ifs, data_string ) )
{
//i think you'd want to erase first $ charachter
if ( !data_string.empty() ) data_string.erase( data_string.begin() );
//now all data put into array:
vector<string> data_array = split ( data_string, ',' );
if ( data_array[0] == "GPVTG" )
{
//do whatever you want with that data entry
cout << data_string;
}
}
return 0;
}
Should handle your task. All empty elements will be empty "" strings in array. Ask if you need anything else.
Credits for split functions belong to Split a string in C++? answer.
How about this
#include <istream>
#include <sstream>
class CSVInputStream {
public:
CSVInputStream(std::istream& ist) : input(ist) {}
std::istream& input;
};
CSVInputStream& operator>>(CSVInputStream& in, std::string& target) {
if (!in.input) return in;
std::getline(in.input, target , ',');
return in;
}
template <typename T>
CSVInputStream& operator>>(CSVInputStream& in, T& target) {
if (!in.input) return in;
std::string line;
std::getline(in.input, line , ',');
std::stringstream translator;
translator << line;
translator >> target;
return in;
}
//--------------------------------------------------------------------
// Usage follow, perhaps in another file
//--------------------------------------------------------------------
#include <fstream>
#include <iostream>
int main() {
std::ifstream file;
file.open("testcsv.csv");
CSVInputStream input(file);
std::string sentence_type;
double track_made_good;
char code;
double unused;
double speed_kph;
char speed_unit_kph;
double speed_kmh;
char speed_unit_kmh;
input >> sentence_type >> track_made_good >> code;
input >> unused >> unused;
input >> speed_kph >> speed_unit_kph;
input >> speed_kmh >> speed_unit_kmh;
std::cout << sentence_type << " - " << track_made_good << " - ";
std::cout << speed_kmh << " " << speed_unit_kmh << " - ";
std::cout << speed_kph << " " << speed_unit_kph << std::endl;;
}
This separates the comma separation from the reading of the values, and can be reused on
most other comma separated stuff.
If you want use C++ style code based on fstream:
fin.open(input);
cout << "readed";
string str;
getline(fin, str); // read one line per time