Patrick
Patrick

Reputation: 51

Use of stringstream in a loop

My problem is summarized at the bottom of this page if this wall of text is excessive. Anyway, I am trying to read lines from a file containing a list of atoms and their types, formatted like so:

   Li   O    Ti   La
 1    24     8     5

This example has four elements and 38 total atoms, but I'm writing my code to accommodate an arbitrary number of each. Regardless of the contents, the elements symbols are always on one line, and the atoms on the next. I figured the best way to do this would be to use getline to insert each line into a string, and then use stringstream to parse those strings appropriately. But the arbitrary size consideration has proved to be a problem for me. My attempt at using stringstream:

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <sstream>
using namespace std;

int main() {
  string line;
  int num_elements;
  struct Element {
    string symbol;
    int count;
  };
  Element* atoms_ptr = NULL;  //to be allocated
  ifstream myfile("filename");
  getline(myfile, line);
  num_elements = (int)(line.size()/5);  //symbols in file have field width 5
  atoms_ptr = new Element[num_elements];
  for (int i=0; i<num_elements; ++i) {
        stringstream(line) >> (*(atoms_ptr+i)).symbol;  //does not work
      }
      getline(myfile, line);
      for (int i=0; i<num_elements; ++i) {
        stringstream(line) >> (*(atoms_ptr+i)).count;  //does not work
      }
...
return 0;
}

You may have already realized the problem with my stringstream statements. Instead of reading in each of the four elements once, the first element is read four times. So the .symbol member for every entry of my array is initialized to Li. Similarly with the number of atoms, the .count member is initialized to 1.

I was able to write something that works as intended by reconstructing my loops in this way:

int j = 3;
for (int i=0; i<num_elements; ++i) {
  (*(atoms_ptr+i)).symbol = line.substr(j, 2);
  j += 5;
  cout << (*(atoms_ptr + i)).symbol << '\n';
}

But I don't like this solution because it depends on the exact file spacing, isn't especially readable, and I still don't know how to use stringstream properly.

Fundamentally, I think the problem results from my use of stringstream inside the loops. Maybe the position of the string file pointer gets reset with each iteration? If so I would need a workaround for this. I would greatly appreciate any help that can be offered. Thanks in advance!

Upvotes: 1

Views: 2927

Answers (2)

Jonathan Mee
Jonathan Mee

Reputation: 38929

OK, a couple suggestions here that will help you:

  1. Get rid of Element and use a map<string, int>
  2. Next use istream_iterator to populate

Given the hopefully validated ifstream myfile and the target output map<string, int> atoms, you can use an istream_iterator something like this:

string line;

getline(myfile, line);

istringstream myline{ line };

transform(istream_iterator<string>{ myline }, istream_iterator<string>{}, istream_iterator<int>{ myfile }, inserter(atoms, end(atoms)), [](const auto& key, const auto& value){ return make_pair(key, value); });

Live Example

Upvotes: 0

skeller
skeller

Reputation: 1161

this should do the trick

#include <iostream>
#include <fstream>
#include <string>
#include <iterator>
#include <vector>

struct Element
{
    std::string symbol;
    int count;
};

int main()
{
    std::ifstream myfile("test.txt");

    std::vector<std::string> tokens(
        (std::istream_iterator<std::string>(myfile)),
        (std::istream_iterator<std::string>()));

    myfile.close();

    std::vector<Element> elements;

    const size_t numEntries = tokens.size() / 2;

    for (size_t i = 0; i < numEntries; i++)
    {
        elements.push_back({ tokens[i], std::stoi(tokens[i+ numEntries]) });
    }

    return 0;
}

some explanation:

it first reads your file content into a vector of strings (first half are the element names, second part the count) then it runs over the vector and aggregates the information into a vector of Element (converting the count to a integer on the way)

Upvotes: 2

Related Questions