Home > Blockchain >  c String from file to vector - more elegant way
c String from file to vector - more elegant way

Time:04-07

I write a code in which I want to pass several strings from text file to string vector. Currently I do this that way:

using namespace std;
int main()
{
string list_name="LIST";
ifstream REF;
REF.open(list_name.c_str());
vector<string> titles;
for(auto i=0;;i  )
{
    REF>>list_name;
    if(list_name=="-1"){break;}
    titles.push_back(list_name);
}
REF.close();
cout<<titles.size();
for(unsigned int i=0; i<titles.size(); i  )
{
    cout<<endl<<titles[i];
}

It works fine, I get the output as expected. My concern is is there more elegant way to pass string from text file to vector directly, avoiding this fragment, when passing string from filestream to string object and assigning it to the vector with push_back as separate step:

REF>>list_name;
if(list_name=="-1"){break;}
titles.push_back(list_name);

CodePudding user response:

More elegant way with algorithms

std::copy_if(std::istream_iterator<std::string>(REF),
    std::istream_iterator<std::string>(),
    std::back_inserter(titles),
    [](const std::string& t) { return t != "-1"; });

CodePudding user response:

If you knew the number of strings to read beforehand, you could

using StringVector = std::vector<std::string>;
int main(int argc, const char* argv) {
  constexpr size_t N = 4; // or however many strings you want...
  StringVector data(N);
  std::ifstream stream("foo.txt");
  for (size_t i =0; (i < N) && stream; i  ) {
    stream >> data[i];
  }
}

But this would be less flexible and it would be trickier to implement your "-1" "terminator" convention.

If that "-1" thing is a true requirement (in contrast to an arbitrary choice), and if you use this more than once, it might pay off to "abstract", how you read those strings. Abstraction is usually done in form of a function.

// compile with:
// clang  -13 -std=c  20 -g -O3 -o words words.cpp

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

using StringVector = std::vector<std::string>;

std::istream& operator>> (std::istream& stream, StringVector& sv)
{
  std::string word;
  while (stream) {
    stream >> word;
    if (word == "-1")
      return stream;
    sv.push_back(word);
  }
  return stream;
}

std::ostream& operator<< (std::ostream& stream,
              const StringVector& sv) {
  for (const auto& s : sv) {
    stream << s << std::endl;
  }
  return stream;
}

int main(int argc, const char* argv[]) {
  std::string file_data{R"(word1 word2 
word3
word4 -1)"};
  std::istringstream stream(file_data);

  StringVector data;
  data.reserve(10);
  stream >> data;
  std::cout
    << "Number of strings loaded: "
    << data.size() << std::endl;
  std::cout << data;
  
  return 0;
}

The above operator>>() works for streams in general, so it also works for file streams.

As an aside: One reason, why people would not like the "-1" terminator approach is performance. If you keep pushing into a vector an arbitrary amount of times, the storage of the vector needs to be re-allocated as the vector grows, which is avoidable overhead. So, usually people would use another file format, e.g. giving the number of strings first, then the strings, which would allow for:

size_t n;
stream >> n;
StringVector data;
data.reserve(n); // avoids "spurious reallocs as we load the strings"
for (size_t i = 0; i < n; i  ) { ... }
  • Related