pipe istream_view and views::transform. How? - c++

Sorry, I was searching a lot, but I still do not understand.
Let's assume that I want to read line by line with an istream_view using a line proxy and convert the result to lowercase.
It would be nice, if I could write
#include <iostream>
#include <sstream>
#include <string>
#include <iterator>
#include <ranges>
#include <algorithm>
namespace rng = std::ranges;
struct CompleteLine { // Line Proxy for the input Iterator
friend std::istream& operator>>(std::istream& is, CompleteLine& cl) { std::getline(is, cl.completeLine); return is; }
operator std::string() const { return completeLine; } // cast operator
std::string completeLine{};
};
std::istringstream iss{ R"(1 Line AAA
2 Line BBB
3 Line CCC)" };
int main() {
for (const std::string& line : rng::istream_view<CompleteLine>(iss) | std::views::transform(std::tolower)) {
std::cout << line << '\n';
}
}
But obviously I cannot. I does not compile.
How would I use ranges, views and pipes to get a correct solution?

How would I use ranges, views and pipes to get a correct solution?
Your attempt is correct. However, it should be noted that rng::istream_view<CompleteLine>(iss) will generate a range whose elements are CompleteLines, while tolower acts on a single character.
This should work
for (const std::string& line : std::views::istream<CompleteLine>(iss)
| std::views::transform([](std::string line) {
std::ranges::transform(line, line.begin(),
[](auto c) { return std::tolower(c); });
return line;
})) {
std::cout << line << '\n';
}
Demo
Prefer views::istream over ranges::istream_view.
Prefer using lambdas instead of taking the address of toupper directly, the standard does not allow to do this.

Related

Simplification of appender function using std::accumulate

I wanted a simple function that takes a collection of strings and appends them and returns one string which is each string item appended together. I thought std::accumulate was the way to go but interested to hear if this code could be improved.
Is there a simpler append type function that can be used here instead of the lambda?
Is this overly complicated and best achieved by some other code?
#include <string>
#include <vector>
#include <iostream>
#include <numeric>
std::string concatenate(std::vector<std::string> strings)
{
return std::accumulate(strings.begin(), strings.end(), std::string(""), [](std::string s1, std::string s2) { return s1 + s2; });
}
int main() {
std::vector<std::string> vec2{ "aaa","bbb","ccc","ddd","eee","fff" };
std::cout << concatenate(vec2) << std::endl;
}
Yes, you can omit the lambda entirely (or use std::plus<>{}).
Also the "" can be removed from std::string(""), or the whole third argument can be removed if you switch to std::reduce:
std::reduce(strings.begin(), strings.end());
Also concatenate should take the vector by a const reference, or even better a std::span<const std::string> (by value).
Also libfmt can be used for this:
fmt::format("{}", fmt::join(strings, ""));
It can be simplified with C++17 fold expression, there is no an intermediate or a temporary collection needed.
#include <string>
#include <iostream>
template<typename ...S>
std::string concatenate(S&&... strings) {
using namespace std::string_literals;
return (""s + ... + strings);
}
int main() {
std::cout << concatenate("aaa","bbb","ccc","ddd","eee","fff") << std::endl;
}

constructor does not work correctly while reading variables from txt file

I have a issue about my constructor is not correctly working. Whenever i run the program, my overloaded operator might not be perform correctly because i always get the default constructor values when i get the output with cout.
I believe that i made my constructor declarations well but all of my objects getting filled with 0 and Unknown
here is my txt file:
1 Prince Heins 25
2 Lady Bridgette 29
3 Tony Ann 223
4 Lucy Phoenix 35
Here is my code;
#include <iostream>
#include <string>
#include <conio.h>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <sstream>
#include <istream>
#include <iomanip>
#include <cstring>
#include <ostream>
using namespace std;
class contact{
private:
int listno;
string name;
string surname;
string phonenumber;
public:
contact(){
this->name="Unknown";
this->surname="Unknown";
this->phonenumber="Unknown";
this->listno=0;
}
contact (string name,string surname,string phonenumber){
this->name=name;
this->surname=surname;
this->phonenumber=phonenumber;
}
contact(int listno,string name,string surname,string phonenumber){
this->name=name;
this->surname=surname;
this->listno=listno;
this->phonenumber=phonenumber;
}
friend ostream & operator<< (ostream &out, const contact &con){
out << con.listno << con.name << con.surname << con.phonenumber;
return out;
}
friend istream & operator>> (istream &in, contact &con){
in >> con.listno >> con.name >> con.surname >> con.phonenumber;
return in;
}
};
int main(){
ifstream pbin("phoneData2.txt");
string line;
long linecount;
for(linecount=0;getline(pbin,line);linecount++);
contact* myArray = new contact[linecount];
pbin.seekg(0);
if(pbin.is_open()){
int i;
for(i=0;i<linecount;i++){
if(pbin!=NULL){
while(pbin>>myArray[i]);
}
}
pbin.close();
cout << myArray[2]; // try attempt
return 0;
}
}
and here is my output for cout << Array[2];
OutputArray2
The problem results from the wrong used algorithm and wrongly placed statements.
So, let's look what is going on in the below:
long linecount;
for(linecount=0;getline(pbin,line);linecount++)
;
contact* myArray = new contact[linecount];
pbin.seekg(0);
if(pbin.is_open()){
int i;
for(i=0;i<linecount;i++){
if(pbin!=NULL) {
while(pbin>>myArray[i]);
}
}
pbin.close();
You want to count the lines. So you read all lines until the eofstate is set. But, additionally, also the fail bit will be set. See also here.
If you use your debugger, you will find a 3 in _Mystate.
Then you perform a seekg. This will reset the eof bit but keep the fail bit. The dubugger shows then
You can see that the fail bit is still set.
So, and this will now lead to the main problem. If your write if(pbin!=NULL) which is definitely wrong (on my machine is does not even compile), or if you better write if(pbin) the fail bit will still be set. And because the bool and the ! operator for streams is overwritten (please see here) the result of the if and while will be false and your pbin>>myArray[i] will never be executed.
So, a pbin.clear() would help.
But, although your class definition is already very good, with inserter and extractor overwritten, you do not use the full C++ power for reading the data.
One basic recommendation would be to never use raw pointers for owned memory. And best also not new. Use dedicated containers for your purpose. E.g. a std::vector. The you can use the std::vectors constructor no 5 together with a std::istream_iterator. Please read here. The range based constructor for the std::vector will copy data from a given range, denoted by the begin and end iterator. And if you use the std::istream_iterator, it will call your overwritten extractor operator, until all data are read.
So your main shrinks to:
int main() {
// Open source file and check, if it could be opened
if (ifstream pbin("r:\\phoneData2.txt");pbin) {
// Read complete source file
std::vector data(std::istream_iterator<contact>(pbin), {});
// Show data on console
std::copy(data.begin(), data.end(), std::ostream_iterator<contact>(std::cout, "\n"));
}
return 0;
}
This looks by far compacter and is easier to read. We start with an if-statement with initializer. The initializer parts defines the variable and the constructor will open the file for us. In the condition part, we simple write pbin. And, as explained above, its bool operator will be called, to check if everything was ok.
Please note:
We do not need a close statement, because the destructor of the
std::ifstream will close the file for us.
The outer namespace will not be polluted with the variable name pbin. That is one of the reasons, why ifstatement with initializer should be used.
We alread descibed the std::vector with its range constructor. SO reading the complete file is simple done by the very simple statement
std::vector data(std::istream_iterator<contact>(pbin), {});
Please note:
We do not define the type of the std::vector. This will be automatically deduced by the compiler through CTAD
We use the default initialzer {} for the end iterator, as can be seen here in constructor number 1.
The whole program could then be rewritten to:
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
class contact {
private:
int listno;
string name;
string surname;
string phonenumber;
public:
contact() {
this->name = "Unknown";
this->surname = "Unknown";
this->phonenumber = "Unknown";
this->listno = 0;
}
contact(string name, string surname, string phonenumber) {
this->name = name;
this->surname = surname;
this->phonenumber = phonenumber;
}
contact(int listno, string name, string surname, string phonenumber) {
this->name = name;
this->surname = surname;
this->listno = listno;
this->phonenumber = phonenumber;
}
friend ostream& operator<< (ostream& out, const contact& con) {
out << con.listno << '\t' << con.name << '\t' << con.surname << '\t' << con.phonenumber;
return out;
}
friend istream& operator>> (istream& in, contact& con) {
in >> con.listno >> con.name >> con.surname >> con.phonenumber;
return in;
}
};
int main() {
// Open source file and check, if it could be opened
if (ifstream pbin("r:\\phoneData2.txt");pbin) {
// Read complete source file
std::vector data(std::istream_iterator<contact>(pbin), {});
// Show data on console
std::copy(data.begin(), data.end(), std::ostream_iterator<contact>(std::cout, "\n"));
}
return 0;
}

How do I display the contents of a std::vector of my own datatype and display the name on the console using std::copy?

I would like to display the contents of the std::vector<User> using std::copy in a similar way to how I've achieved it through std::for_each in the code below.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
class User {
private:
std::string m_sName;
public:
User(std::string sName):m_sName(sName) {}
std::string GetName()const {
return m_sName;
}
};
int main()
{
std::vector<User> vectNames;
vectNames.emplace_back("Jack");
vectNames.emplace_back("George");
vectNames.emplace_back("Jose");
//How can I get std::copy to do what the std::for_each is doing?
std::copy(vectNames.begin(), vectNames.end(), std::ostream_iterator<User>(std::cout, "\n"));
//The following line is what I really would like std::copy to do.
std::for_each(vectNames.begin(), vectNames.end(), [](const User &user) {std::cout << user.GetName() << "\n"; });
}
You can simply overload the ostream operator:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
class User {
private:
std::string m_sName;
public:
User(std::string sName):m_sName(sName) {}
std::string GetName()const {
return m_sName;
}
void print(std::ostream& where) const
{ where << m_sName; }
};
/// Overloaded ostream operator
std::ostream& operator<< (std::ostream& out, const User& user)
{
user.print(out);
return out;
}
int main()
{
std::vector<User> vectNames;
vectNames.emplace_back("Jack");
vectNames.emplace_back("George");
vectNames.emplace_back("Jose");
std::copy(vectNames.begin(), vectNames.end(), std::ostream_iterator<User>(std::cout, "\n"));
}
In general I always have an overloaded ostream as part of classes that store and output information.
Because of your comment I am a new programmer and am experimenting with different ideas, I'm posting this answer as an alternative approach which also shows some algorithm that you might enthusiastic about.
I think that std::copy isn't the right tool if what you want to do is both
calling a member function on the objects in a collection, and only after that
copying/printing them to screen
One, and maybe the best, approach is the one in the atru's answer, which basically solves only point 2 with std::copy (and this is something you already knew how to do it, based on your question), and point 1 by overloading <<, which makes the trick.
The alternative I propose is based on the idea that "calling a member function on every object of a collection" is actually a transform operation of that collection. std::transform, however, just like std::copy acts on iterators, not on ranges, so they cannot be easily composed with one another.
Here comes Boost, with boost::copy and boost::adaptors::transformed which allow you to do this:
boost::copy(
vectNames | transformed([](auto const& x){ return x.GetName(); }),
std::ostream_iterator<std::string>(std::cout, "\n")
);
where vectNames is "piped into" transformed, which applies the lambda on every element of the collection, resulting in a new temporary collection, which is the argument to boost::copy. If you were to use std::copy, you would have to store the temporary somewhere, e.g. in temp, before passing to std::copy its iterators temp.begin() and temp.end().
The full example is below. I hope it will give you some insight in different, more functional, approaches.
#include <functional>
#include <iostream>
#include <vector>
#include <string>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/copy.hpp>
class User {
private:
std::string m_sName;
public:
User(std::string sName):m_sName(sName) {}
std::string GetName()const {
return m_sName;
}
};
int main()
{
std::vector<User> vectNames;
vectNames.emplace_back("Jack");
vectNames.emplace_back("George");
vectNames.emplace_back("Jose");
using boost::adaptors::transformed;
boost::copy(
vectNames | transformed([](auto const& x){ return x.GetName(); }),
std::ostream_iterator<std::string>(std::cout, "\n")
);
}

What is the best way to slurp a text file into an array/collection of std::string in C++

In the style of What is the best way to read an entire file into a std::string in C++? I want to ask a similar question, except that I want my output to be an array/STL container containing the lines in a text file.
C#/.net has the rather useful File.ReadAllLines() utility function, what would a good C++ (STL) version look like?
In C++, you could do this.
Define a struct as:
struct line : std::string
{
friend std::istream & operator >> (std::istream & in, line & ln)
{
return std::getline(in, ln);
}
};
then do this:
std::ifstream file("file.txt");
std::istream_iterator<line> begin(file), end;
std::vector<std::string> allLines(begin, end);
Done!
With this approach, you can directly work with iterator-pair begin and end. No need to use std::vector<std::string>. Note that line can implicitly convert into std::string. So you can use begin and end with the Standard algorithms.
For example,
Find the longest line:
auto cmp = [](line const &a, line const& b) { return a.size() < b.size(); };
std::string longestLine = *std::max_element(begin, end, cmp);
Count the lines whose length is greater than 10:
auto cmp = [](line const &a) { return a.size() > 10 ; };
size_t count = std::count_if(begin, end, cmp);
So on. In this way, you can work directly work with begin and end. No need to use std::vector. Save memory.
Hope that helps.
The standard idiom:
#include <fstream>
#include <string>
#include <vector>
#include <utility>
std::ifstream infile("file.txt");
std::vector<std::string> v;
for (std::string line; std::getline(infile, line); )
{
v.push_back(std::move(line));
}
std::ifstream ifs(name);
std::vector<std::string> lines;
std::string line;
while (std::getline(ifs, line))
lines.push_back(line);
This is my attempt at #Nawaz's idea. I've avoided inheritance from std::string, as I felt a little queasy about it:
#include <iostream>
#include <iterator>
#include <vector>
struct Line
{
operator const std::string&() const {return string;}
std::string string;
};
std::istream& operator>>(std::istream& in, Line& line)
{
return std::getline(in, line.string);
}
int main()
{
std::istream_iterator<Line> begin(std::cin), end;
std::vector<std::string> allLines(begin, end);
std::cout << allLines.size() << " lines read from file:" << std::endl;
std::copy(allLines.begin(), allLines.end(),
std::ostream_iterator<std::string>(std::cout, "|"));
return 0;
}

Is there a C++ iterator that can iterate over a file line by line?

I would like to get an istream_iterator-style iterator that returns each line of the file as a string rather than each word. Is this possible?
EDIT: This same trick was already posted by someone else in a previous thread.
It is easy to have std::istream_iterator do what you want:
namespace detail
{
class Line : std::string
{
friend std::istream & operator>>(std::istream & is, Line & line)
{
return std::getline(is, line);
}
};
}
template<class OutIt>
void read_lines(std::istream& is, OutIt dest)
{
typedef std::istream_iterator<detail::Line> InIt;
std::copy(InIt(is), InIt(), dest);
}
int main()
{
std::vector<std::string> v;
read_lines(std::cin, std::back_inserter(v));
return 0;
}
The standard library does not provide iterators to do this (although you can implement something like that on your own), but you can simply use the getline function (not the istream method) to read a whole line from an input stream to a C++ string.
Example:
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
ifstream is("test.txt");
string str;
while(getline(is, str))
{
cout<<str<<endl;
}
return 0;
}
Here is a solution. The exemple print the input file with ## at the end of each line.
#include <iostream>
#include <iterator>
#include <fstream>
#include <string>
using namespace std;
class line : public string {};
std::istream &operator>>(std::istream &is, line &l)
{
std::getline(is, l);
return is;
}
int main()
{
std::ifstream inputFile("input.txt");
istream_iterator<line> begin(inputFile);
istream_iterator<line> end;
for(istream_iterator<line> it = begin; it != end; ++it)
{
cout << *it << "##\n";
}
getchar();
}
Edit : Manuel has been faster.
You could write your own iterator. It's not that hard.
An iterator is just a class on which (simply speaking) the increment and * operators are defined.
Look at http://www.drdobbs.com/cpp/184401417 to get started writing your own iterators.
It is also possible to use range-based for loop:
// Read from file.
std::ifstream f("test.txt");
for (auto& line : lines(f))
std::cout << "=> " << line << std::endl;
// Read from string.
std::stringstream s("line1\nline2\nline3\n\n\nline4\n\n\n");
for (auto& line : lines(s))
std::cout << "=> " << line << std::endl;
where lines is defined in the following way:
#include <string>
#include <iterator>
#include <istream>
struct line_iterator {
using iterator_category = std::input_iterator_tag;
using value_type = std::string;
using difference_type = std::ptrdiff_t;
using reference = const value_type&;
using pointer = const value_type*;
line_iterator(): input_(nullptr) {}
line_iterator(std::istream& input): input_(&input) { ++*this; }
reference operator*() const { return s_; }
pointer operator->() const { return &**this; }
line_iterator& operator++() {
if (!std::getline(*input_, s_)) input_ = nullptr;
return *this;
}
line_iterator operator++(int) {
auto copy(*this);
++*this;
return copy;
}
friend bool operator==(const line_iterator& x, const line_iterator& y) {
return x.input_ == y.input_;
}
friend bool operator!=(const line_iterator& x, const line_iterator& y) {
return !(x == y);
}
private:
std::istream* input_;
std::string s_;
};
struct lines {
lines(std::istream& input): input_(input) {}
line_iterator begin() const { return line_iterator(input_); }
line_iterator end() const { return line_iterator(); }
private:
std::istream& input_;
};
You can use istreambuf_iterator instead of istream_iterator. It doesn't ignore control characters like istream_iterator.
code.cpp:
#include <iterator>
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream file("input.txt");
istreambuf_iterator<char> i_file(file);
istreambuf_iterator<char> eof;
std::string buffer;
while(i_file != eof)
{
buffer += *i_file;
if(*i_file == '\n')
{
std::cout << buffer;
buffer.clear();
}
++i_file;
}
return 0;
}
input.txt:
ahhhh test *<-- There is a line feed here*
bhhhh second test *<-- There is a line feed here*
output:
ahhhh test
bhhhh second test
In a related thread iterate-over-cin-line-by-line quoted above, Jerry Coffin described "another possibility (which) uses a part of the standard library most people barely even know exists." The following applies that method (which was what I was looking for) to solve the iterate-over-file-line-by-line problem as requested in the current thread.
First a snippet copied directly from Jerry's answer in the related thread:
struct line_reader: std::ctype<char> {
line_reader(): std::ctype<char>(get_table()) {}
static std::ctype_base::mask const* get_table() {
static std::vector<std::ctype_base::mask> rc(table_size, std::ctype_base::mask());
rc['\n'] = std::ctype_base::space;
return &rc[0];
}};
And now, imbue the ifstream with the custom locale as described by Jerry, and copy from infstream to ofstream.
ifstream is {"fox.txt"};
is.imbue(locale(locale(), new line_reader()));
istream_iterator<string> ii {is};
istream_iterator<string> eos {};
ofstream os {"out.txt"};
ostream_iterator<string> oi {os,"\n"};
vector<string> lines {ii,eos};
copy(lines.begin(), lines.end(), oi);
The output file ("out.txt") will be exactly the same as the input file ("fox.txt").
Here is a pretty clean approach that uses boost::tokenizer. This returns an object providing begin() and end() member functions; for a complete interface, see the documentation of the tokenizer class.
#include <boost/tokenizer.hpp>
#include <iostream>
#include <iterator>
using istream_tokenizer = boost::tokenizer<boost::char_separator<char>,
std::istreambuf_iterator<char>>;
istream_tokenizer line_range(std::istream& is);
{
using separator = boost::char_separator<char>;
return istream_tokenizer{std::istreambuf_iterator<char>{is},
std::istreambuf_iterator<char>{},
separator{"\n", "", boost::keep_empty_tokens}};
}
This hardcodes char as the stream's character type, but this could be templatized.
The function can be used as follows:
#include <sstream>
std::istringstream is{"A\nBB\n\nCCC"};
auto lines = line_range(is);
std::vector<std::string> line_vec{lines.begin(), lines.end()};
assert(line_vec == (std::vector<std::string>{{"A", "BB", "", "CCC"}}));
Naturally, it can also be used with an std::ifstream created by opening a file:
std::ifstream ifs{"filename.txt"};
auto lines = line_range(ifs);