I'm trying to initialize a vector using iterators and I'm getting a compiler error basically saying that there's no matching function to call.
The code reads from a file with an istream_iterator and ends with an input sentinel. Then I try to initialize the vector with those two iterators.
#include "std_lib_facilities.h"
#include<iterator>
int main()
{
string from, to; // get source and target file names
cin >> from >> to;
ifstream is(from.c_str()); // open input stream
ofstream os(to.c_str()); // open output stream
istream_iterator<string> ii(is); // make input iterator for stream
istream_iterator<string> eos; // input sentinel
ostream_iterator<string> oo(os,"\n");
vector<string> words(ii, eos); // vector initialized from input
sort(words.begin(), words.end()); // sort the buffer
copy(words.begin(), words.end(), oo); // copy buffer to output
}
I know I could use the copy function to copy the input stream into the vector, but I read that it can be done this way as well. Can anyone explain why this is not compiling? Thanks.
Compiler error:
C:\Users\Alex\C++\stream_iterators.cpp|16|error: no matching function for call to `Vector<String>::Vector(std::istream_iterator<String, char, std::char_traits<char>, ptrdiff_t>&, std::istream_iterator<String, char, std::char_traits<char>, ptrdiff_t>&)'|
Edit: It is not a header problem. Std_lib_facilities has all of the needed headers.
vector<string> words(ii, eos);
is an analogue to
vector<string> words;
copy( ii, eos, back_inserter(words) );
vector class has the following constructor:
// initialize with range [First, Last)
template<class InputIterator>
vector(
InputIterator First,
InputIterator Last
);
To make your sample compiling you need to include the following:
#include <sstream>
#include <iostream>
#include <vector>
#include <fstream>
#include <algorithm> // for std::copy
Since your identifiers not fully qualified you should add the following:
using namespace std;
Or fully qualify all STL identifiers.
And to change, I guess,
copy(words.begin(), words.end(), out)
to
copy(words.begin(), words.end(), oo)
Please copy and paste the compiler error. Also you are missing a number of headers such as algorithm and vector. You need a use namespace std declaration or use std:: to access the STL classes.
Once you provide more information we can give you more advice.
Update: why is your error message referring to "Vector" (with capital), not vector (lower case)?
The book header had some kind of compliance issues, so I just included the appropriate headers and it worked.
The Vector class in std_lib_facilities.h defines three constructors, but not one which accepts a pair of iterators. You can go ahead and add that to the header:
template <class Iter>
Vector(Iter from, Iter to): std::vector<T>(from, to) {}
With this header you'll need to take into account that this is for hand-holding. operator[] in a real std::vector is not supposed to do range-checking. (Why not just teach beginners to use vector::at instead, until they get the idea that it might be better to stay in bounds...?)
Related
I would like to know how I can extract / skip certain columns such as age and weight from a CSV file in C++.
Does it make more sense to extract the desired information after I loaded the entire csv file (if memory is not a problem)?
EDIT: If possible, I would like to have a reading, printing and modification part.
If possible, I want to use only the STL. The content of my test csv file looks as follows:
*test.csv*
name;age;weight;height;test
Bla;32;1.2;4.3;True
Foo;43;2.2;5.3;False
Bar;None;3.8;2.4;True
Ufo;32;1.5;5.4;True
I load the test.csv file with the following C++ program that prints the file's content on the screen:
#include <iostream>
#include <vector>
#include <string>
#include <iomanip>
#include <fstream>
#include <sstream>
void readCSV(std::vector<std::vector<std::string> > &data, std::string filename);
void printCSV(const std::vector<std::vector<std::string>> &data);
int main(int argc, char** argv) {
std::string file_path = "./test.csv";
std::vector<std::vector<std::string> > data;
readCSV(data, file_path);
printCSV(data);
return 0;
}
void readCSV(std::vector<std::vector<std::string> > &data, std::string filename) {
char delimiter = ';';
std::string line;
std::string item;
std::ifstream file(filename);
while (std::getline(file, line)) {
std::vector<std::string> row;
std::stringstream string_stream(line);
while (std::getline(string_stream, item, delimiter)) {
row.push_back(item);
}
data.push_back(row);
}
file.close();
}
void printCSV(const std::vector<std::vector<std::string> > &data) {
for (std::vector<std::string> row: data) {
for (std::string item: row) {
std::cout << item << ' ';
}
std::cout << std::endl;
}
}
Any assistance you can provide would be greatly appreciated.
Basically I answered this question already in a similar thread. But anyway, I will show a ready to use solution with a different approach and some explanation here.
One hint: You should make yourself more familiar with object oriented programming. And think over your design. In your read and write function you create a unneccessary dependency to a file or to std::cout- So, you should not handover a file name and then open the file in the function, but, use streams. Because, in the function that I created, using the C++ IO facilities, it doesn't matter, if we read from a file or a std::istringstream or write to std::cout or a file stream.
All will be handled via the (overloaded) extractor and inserter operators.
So, and because I wanted the code a little bit more flexible, I made my struct a template, to be able to put in the selected Columns and reuse the same struct for other column combinations.
If you want to have fixed selected columns then you can delete the line with template and can replace std::vector<size_t> selectedFields{ {Colums...} }; with std::vector<size_t> selectedFields{ {1,2} };
Later we use a using for the template to allow easier handling and understanding:
// Define Dataype for selected columns age and weight
using AgeAndWeight = SelectedColumns<1, 2>;
OK, let's first see the source code and then try to understand.
#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <fstream>
#include <initializer_list>
#include <iterator>
#include <algorithm>
std::regex re{ ";" };
// Proxy for reading an splitting a line and extracting certain fields and some simple output
template<size_t ... Colums>
struct SelectedColumns {
std::vector<std::string> data{};
std::vector<size_t> selectedFields{ {Colums...} };
// Overwrite extractor operator
friend std::istream& operator >> (std::istream& is, SelectedColumns& sl) {
// Read a complete line and check, if it could be read
if (std::string line{}; std::getline(is, line)) {
// Now split the line into tokens
std::vector tokens(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
// Clear old data
sl.data.clear();
// So, and now copy the selected columns into our data vector
for (const size_t& column : sl.selectedFields)
if (column < tokens.size()) sl.data.push_back(tokens[column]);
}
return is;
}
// Simple extractor
friend std::ostream& operator << (std::ostream & os, const SelectedColumns & sl) {
std::copy(sl.data.begin(), sl.data.end(), std::ostream_iterator<std::string>(os, "\t"));
return os;
}
};
// Define Dataype for selected columns age and weight
using AgeAndWeight = SelectedColumns<1U, 2U>;
const std::string fileName{ "./test.csv" };
int main() {
// Open the csv file and check, if it is open
if (std::ifstream csvFileStream{ fileName }; csvFileStream) {
// Read complete csv file and extract age and weight columns
std::vector sc(std::istream_iterator<AgeAndWeight>(csvFileStream), {});
// Now all data is available in this vector sc Do something
sc[3].data[0] = "77";
// Show some debug out put
std::copy(sc.begin(), sc.end(), std::ostream_iterator<AgeAndWeight>(std::cout, "\n"));
// By the way, you could also write the 2 lines above in one line.
//std::copy(std::istream_iterator<AgeAndWeight>(csvFileStream), {}, std::ostream_iterator<AgeAndWeight>(std::cout, "\n"));
}
else std::cerr << "\n*** Error: Could not open source file\n\n";
return 0;
}
One major task here is to split a line with CSV Data into its tokens. Let us have a look at this.
Splitting a string into tokens:
What do people expect from the function, when they read
getline ?
Most people would say, Hm, I guess it will read a complete line from somewhere. And guess what, that was the basic intention for this function. Read a line from a stream and put it into a string.
But, as you can see here std::getline has some additional functionality.
And this lead to a major misuse of this function for splitting up std::strings into tokens.
Splitting strings into tokens is a very old task. In very early C there was the function strtok, which still exists, even in C++. Here std::strtok. Please see the std::strtok-example
std::vector<std::string> data{};
for (char* token = std::strtok(const_cast<char *>(line.data()), ","); token != nullptr; token = std::strtok(nullptr, ","))
data.push_back(token);
Simple, right?
But because of the additional functionality of std::getline is has been heavily misused for tokenizing strings. If you look on the top question/answer regarding how to parse a CSV file (please see here), then you will see what I mean.
People are using std::getline to read a text line, a string, from the original stream, then stuffing it into an std::istringstream and use std::getline with delimiter again to parse the string into tokens. Weird.
But, since many many years, we have a dedicated, special function for tokenizing strings, especially and explicitly designed for that purpose. It is the
std::sregex_token_iterator
And since we have such a dedicated function, we should simply use it.
This thing is an iterator. For iterating over a string, hence the function name is starting with an s. The begin part defines, on what range of input we shall operate, the end part is default constructed, and then there is a std::regex for what should be matched / or what should not be matched in the input string. The type of matching strategy is given with last parameter.
0 --> give me the stuff that I defined in the regex and (optional)
-1 --> give me that what is NOT matched based on the regex.
We can use this iterator for storing the tokens in a std::vector. The std::vector has a range constructor, which takes 2 iterators as parameter, and copies the data between the first iterator and 2nd iterator to the std::vector. The statement
std::vector tokens(std::sregex_token_iterator(s.begin(), s.end(), re, -1), {});
defines a variable “tokens” as a std::vector and uses the so called range-constructor of the std::vector. Please note: I am using C++17 and can define the std::vector without template argument. The compiler can deduce the argument from the given function parameters. This feature is called CTAD ("class template argument deduction").
Additionally, you can see that I do not use the "end()"-iterator explicitly.
This iterator will be constructed from the empty brace-enclosed default initializer with the correct type, because it will be deduced to be the same as the type of the first argument due to the std::vector constructor requiring that.
You can read any number of tokens in a line and put it into the std::vector
But you can do even more. You can validate your input. If you use 0 as last parameter, you define a std::regex that even validates your input. And you get only valid tokens.
Overall, the usage of a dedicated functionality is superior over the misused std::getline and people should simple use it.
Some people complain about the function overhead, and, they are right, but how many of them are using big data. And even then, the approach would be probably then to use string.findand string.substring or std::stringviews or whatever.
So, now to further topics.
In the extractor, we first read a complete line from the source stream and check, if that worked. Or, if we have and end of file or any other error.
Then we tokenize that just read string as described above.
And then, we will copy only selected columns from the tokens into our resulting data. This is done in a simple for loop. Here we also check the boundaries, because somebody could specify invalid selected columns, or, a line could have less tokens than expected.
So the body of the extractor is vey simple. Just 5 line of code . . .
Then, again,
You should start using object-oriented features in C++. In C++ you can put data and methods that operate on these data into one object. The reason is that the outside world should not care about objects internals. For example, your readCSV and printCSV function should be part of a struct (or class).
And as next step, we will not use your “read” and “print” functions. We will use the dedicated function for Stream-IO, the extractor operator >> and the inserter operator <<. And we will overwrite the standard IO-functions in our struct.
In function main we will open the the source file and check, if the open was successful. BTW. All input output functions shall be checked, if they were successful.
Then, we use the next iterator, the std::istream_iterator. And this together with our “AgeAndWeight”-type and the input file stream. Also here we use CTAD and the default constructed end-iterator. The std::istream_iterator will repeatedly call the AgeAndWeight extractor operator, until all lines of the source file are read.
For output, we will use the std::ostream_iterator. This will call the inserter operator for "AgeAndWeight" until all data are written.
Is there is any similar solution to this command:
using namespace std;
copy(istream_iterator<string>(cin), istream_iterator<string>(),ostream_iterator<string>(cout, "\n"));
-- this command copies everything into cout but I would like to change it to copy the string in reverse order so I have used this:
using namespace std;
reverse_copy(istream_iterator<string>(cin), istream_iterator<string>(),ostream_iterator<string>(cout, "\n"));
-- but this did not even compile. Are there any solutions to this? Thank you
The first two arguments to std::reverse_copy must be Bidirectional Iterator whereas std::istream_iterator is Input Iterator which cannot behave as Bidirectional Iterator. That explains why it doesn't work — it wouldn't even compile.
You've to write your own iterator — or do it manually in a loop — to solve this problem (which is not clear as to what mean by reverse : given foo bar as input, do you want bar foo or oof rab, or rab oof? as many of the commenters say).
You can write a recursive function. For example
#include <iostream>
#include <string>
#include <sstream>
std::ostream & reverse_output( std::istream &is = std::cin,
std::ostream &os = std::cout )
{
std::string s;
if ( is >> s ) reverse_output( is, os ) << s << '\n';
return os;
}
int main()
{
std::istringstream is( "Hello Bobul Mentol" );
reverse_output( is );
}
The program output is
Mentol
Bobul
Hello
Of course instead of the string stream I used for the demonstrative purpose you can use std::cin. In this case the call of the function will look just like
reverse_output();
Otherwise you need to store the input in some container and use it to reverse the inputted data for outputing.
For example
std::vector<std::string> v( std::istream_iterator<std::string>( std::cin ),
std::istream_iterator<std::string>() );
std::reverse_copy( v.begin(), v.end(),
std::ostream_iterator<std::string>( std::cout, "\n" ) );
I have never heard of some standard algorithm which can copy a reversed collection by an input_iterator or even forward_iterator - probably if this exist, it requires at least bidirectional_iterator.
So, you can use the temporary collection to store the values read, like this:
vector<string> tmp;
copy(istream_iterator<string>(cin), istream_iterator<string>(), back_inserter(tmp));
copy(tmp.rbegin(), tmp.rend(), ostream_iterator<string>(cout, "\n"));
There is a general problem here: std::cin is a stream. When it is attached to a file, you could imagine a way to initially find the size and so how to know where the reverse iterator should start. But when it is attached to a terminal, with an imprevisible human being able to type input data at will, at what position should the reverse iterator start? I have no idea of it, and it looks like cin designer had no more - more seriously, cin does not propose a reverse iterator, and it is by design.
If you want to present what has been inputted in cin but in reverse order, you must first specify:
what is the piece of input to reverse: anything until stream is closed, or anything until first end of line, or [put here your own definition]. Once it's done you have the start place for your reverse iterator
what is the unit to be reversed: one character or one word at a time. Once this is done, you know what your reverse iterator should return.
The implementation could use a vector of strings. You consistently accumulate words or single characters in it until what you have defined as the end of the stream. Once you hit the end, you hold a container with bidirectional iterators so copying it in reverse order should be easy.
The signature of transform is:
OutputIterator transform (InputIterator first1, InputIterator last1,
OutputIterator result, UnaryOperation op);
And I want to create a generic token replacing functor, msg_parser, below, so I can use any container (string used in example below) and pass begin and end of container to transform. Thats the idea.
But I can't get this to compile.
Here is my code. Any help would be much appreciated.
#include <iostream>
#include <iterator>
#include <string>
#include <map>
#include <algorithm>
class msg_parser {
public:
msg_parser(const std::map<std::string, std::string>& mapping, const char token = '$')
: map_(mapping), token_(token) {}
// I can use a generic istream type interface to handle the parsing.
std::ostream_iterator operator() (std::istream_iterator in) {
//body will go through input and when get to end of input return output
}
private:
const char token_;
const std::map<std::string, std::string>& map_;
};
int main(int argc, char* argv[]) {
std::map<std::string, std::string> mapping;
mapping["author"] = "Winston Churchill";
std::string str_test("I am $(author)");
std::string str_out;
std::transform(str_test.begin(), str_test.end(), str_out.begin(), msg_parser(mapping));
return 0;
}
Since std::string is a collection of chars, std::transform will iterate over chars exactly distance(first1, last1) times, so in your case it's not possible to change the size of the string. You may be able to transform "$(author)" into another string exactly the same size, though, but I guess it's not what you want.
You probably want to iterate over stream iterators instead of chars:
std::stringstream istrstr(str_test);
std::stringstream ostrstr;
std::transform(std::istream_iterator<std::string>(istrstr),
std::istream_iterator<std::string>(),
std::ostream_iterator<std::string>(ostrstr, " "), // note the delimiter
msg_parser(mapping));
std::cout << ostrstr.str() << std::endl;
By the way, your UnaryOperation works on the iterated type, not on iterators, so operator() should be:
std::string operator() (std::string in) { // ...
You should read the documentations and examples for std::transform in a reference like this.
You'll notice that the operation shall take an element of the input container and generate an element for the output container. Since your containers are strings and the elements are chars, the signature should be char operator()(char). Container-iterators would be wrong in this case. Anyways, the iterators of std::string are char*s, so your std::ostream_iterator are completely senseless.
Having said that, you will notice that transform works on single characters, if you apply it to your string, not on the whole "author" substring. What you are trying to do is best achieved with C++11's std::regex regular expression library, not with std::transform
can you use Insert Iterators while reading from a file to put the data into STL container?
for example:
FILE *stream;
fread(back_inserter(std::list), sizeof(int), 1, stream);
C++ streams are not compatible with C stdio streams. In other words, you can't use C++ iterators with FILE* or fread. However, if you use the C++ std::fstream facilities along with istream_iterator, you can use an insertion iterator to insert into a C++ container.
Assuming you have an input file "input.txt" which contains ASCII text numbers separated by whitespace, you can do:
#include <iostream>
#include <fstream>
#include <vector>
#include <iterator>
int main()
{
std::ifstream ifs("input.txt");
std::vector<int> vec;
// read in the numbers from disk
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(), std::back_inserter(vec));
// now output the integers
std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, "\n"));
}
no you can't. And it's fundamentally not-portable to store int like that. Your code will break if you write your file with a big-endian machine and try to read it with a little-endian machine.
But nobody prevents you to. Just define your own forward iterator that reads binary from a istream. You will likely want to stop using FILE and fread/fopen/fclose function as they are from C era.
then you will be able to write :
std::copy_n(your_custom_forward_iterator, count, back_inserter<std::list<....> >);
I'm working this source code:
#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>
int main()
{
std::string str = "The quick brown fox";
// construct a stream from the string
std::stringstream strstr(str);
// use stream iterators to copy the stream to the vector as whitespace separated strings
std::istream_iterator<std::string> it(strstr);
std::istream_iterator<std::string> end;
std::vector<std::string> results(it, end);
// send the vector to stdout.
std::ostream_iterator<std::string> oit(std::cout);
std::copy(results.begin(), results.end(), oit);
}
To, instead of tokenizing a single line and putting it into the vector results, it tokenizes a group of lines taken from this text file and puts the resulting words into a single vector .
Text File:
Munroe states there is no particular meaning to the name and it is simply a four-letter word without a phonetic pronunciation, something he describes as "a treasured and carefully-guarded point in the space of four-character strings." The subjects of the comics themselves vary. Some are statements on life and love (some love strips are simply art with poetry), and some are mathematical or scientific in-jokes.
So far, I'm only clear that I need to use a
while (getline(streamOfText, readTextLine)){}
to get the loop running.
But I don't think this would work:
while (getline(streamOfText, readTextLine)) {
cout << readTextLine << endl;
// construct a stream from the string
std::stringstream strstr(readTextLine);
// use stream iterators to copy the stream to the vector as whitespace separated strings
std::istream_iterator it(strstr);
std::istream_iterator end;
std::vector results(it, end);
/*HOw CAN I MAKE THIS INSIDE THE LOOP WITHOUT RE-DECLARING AND USING THE CONSTRUCTORS FOR THE ITERATORS AND VECTOR? */
// send the vector to stdout.
std::ostream_iterator<std::string> oit(std::cout);
std::copy(results.begin(), results.end(), oit);
}
Yes, then you have one whole line in readTextLine. Is it that what you wanted in that loop? Then instead of constructing the vector from the istream iterators, copy into the vector, and define the vector outside the loop:
std::vector<std::string> results;
while (getline(streamOfText, readTextLine)){
std::istringstream strstr(readTextLine);
std::istream_iterator<std::string> it(strstr), end;
std::copy(it, end, std::back_inserter(results));
}
You actually don't need to read a line into the string first, if all you need is all words from a stream, and no per-line processing. Just read from the other stream directly like you did in your code. It will not only read words from one line, but from the whole stream, until the end-of-file:
std::istream_iterator<std::string> it(streamOfText), end;
std::vector<std::string> results(it, end);
To do all that manually, like you ask for in the comments, do
std::istream_iterator<std::string> it(streamOfText), end;
while(it != end) results.push_back(*it++);
I recommend you to read a good book on this. It will show you much more useful techniques i think. C++ Standard library by Josuttis is a good book.