20

I want to read graph adjacency information from a text file and store it into a vector.

  • the file has arbitrary number of lines

  • each line has arbitrary number of integers ended with '\n'

for example,

First line:
0 1 4
Second line:
1 0 4 3 2
Thrid line:
2 1 3
Fourth line:
3 1 2 4
Fifth line:
4 0 1 3

If I use getline() to read one line at a time, how do I parse the line (as each line has variable number of integers)?

Any suggestions?

gsamaras
  • 71,951
  • 46
  • 188
  • 305
itnovice
  • 503
  • 1
  • 4
  • 20

2 Answers2

20

The standard line reading idiom:

#include <fstream>
#include <sstream>
#include <string>
#include <vector>


std::ifstream infile("thefile.txt");
std::string line;

while (std::getline(infile, line))
{
  std::istringstream iss(line);
  int n;
  std::vector<int> v;

  while (iss >> n)
  {
    v.push_back(n);
  }

  // do something useful with v
}

Here's a one-line version using a for loop. We need an auxiliary construction (credits to @Luc Danton!) that does the opposite of std::move:

namespace std
{
  template <typename T> T & stay(T && t) { return t; }
}

int main()
{
  std::vector<std::vector<int>> vv;

  for (std::string line;
       std::getline(std::cin, line);
       vv.push_back(std::vector<int>(std::istream_iterator<int>(std::stay(std::istringstream(line))),
                                     std::istream_iterator<int>())
                    )
       ) { }

  std::cout << vv << std::endl;
}
Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • That means, this is using C++11 feature, as `&&` is available only in C++11. – Nawaz Nov 14 '11 at 03:37
  • @Nawaz: My earlier version involves a more elaborate struct and a const-cast, which is C++98. Talk to me in private if you're interested, it's not suitable for polite conversation :-) – Kerrek SB Nov 14 '11 at 03:38
  • @Kerrek, Thanks for your solution. When I have nested STL container, say, vector >, how do I iterate through it? I was trying the following: std::vector > iter1; std::vector::iterator iter2; for (iter1 = vv.begin(); iter1 != vv.end(); ++iter1) { for (iter2 = (*vv).begin(); iter2 != (*vv).end(); ++iter2) cout << *iter2 << " "; cout << endl; } It doesn't compile. What is the proper way of iterating through a nested STL container. Thanks. – itnovice Nov 15 '11 at 03:35
  • @itnovice: It's something like that. `for (std::vector>::const_iterator it1 = v.begin(), end1 = v.end(); it1 != end1; ++it1) { for ( std::vector::const_iterator it2 = it1->begin(), end2 = it1->end(); it2 != end2; ++it2) { ... } }` etc. – Kerrek SB Nov 15 '11 at 03:38
  • @Kerrek: Thanks for your reply. It does the job. – itnovice Nov 16 '11 at 01:00
  • @itnovice: Cheers, I'm glad. Use the first version. The second one was purely for "I'll be damned if that can't be done in one line." – Kerrek SB Nov 16 '11 at 01:01
  • 6
    "`std::stay`" Since when is it allowed to add names to `std`? – curiousguy Nov 29 '11 at 03:54
9

First read a line using std::getline function, then use std::stringstream to read the integers from the line as:

std::ifstream file("input.txt");

std::vector<std::vector<int>> vv;
std::string line;
while(std::getline(file, line))
{
    std::stringstream ss(line);
    int i;
    std::vector<int> v;
    while( ss >> i ) 
       v.push_back(i);
    vv.push_back(v);
}

You can also write the loop-body as:

while(std::getline(file, line))
{
    std::stringstream ss(line);
    std::istream_iterator<int> begin(ss), end;
    std::vector<int> v(begin, end);
    vv.push_back(v);
}

This looks shorter, and better. Or merge-the last two lines:

while(std::getline(file, line))
{
    std::stringstream ss(line);
    std::istream_iterator<int> begin(ss), end;
    vv.push_back(std::vector<int>(begin, end));
}

Now don't make it shorter, as it would look ugly.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 3
    Or even `for (std::string line; std::getline(file, line); vv.push_back(std::vector(std::istream_iterator(std::istringstream(line)), std::istream_iterator()))) {}` – Kerrek SB Nov 14 '11 at 03:08
  • @KerrekSB: Hehe. Good one. I like the idea of minimizing the scope of `line` variable. – Nawaz Nov 14 '11 at 03:11
  • Just testing it, but it might be that you can't get the iterator from a temporary string stream :-( – Kerrek SB Nov 14 '11 at 03:12
  • The iterator can only be constructed from a non-constant reference. So you can't construct it from an rvalue, unfortunately. – Kerrek SB Nov 14 '11 at 03:36
  • @KerrekSB: Oh I see. In that case, if you replace `std::istringstream(line‌​)` with `static_cast(std::stringstream().flush() << line)`, then it will work. See this : http://www.ideone.com/r0T0X ... but it is better to avoid such code, in my opinion! – Nawaz Nov 14 '11 at 08:24
  • @Nawaz: Thanks Nawaz. Your solution's awesome. – itnovice Nov 15 '11 at 03:20
  • @Nawaz: Thanks Nawaz. Your solution's awesome. I am not quite sure about std::istream_iterator begin(ss), end; std::vector v(begin, end); . Is that an alternate way of doing copy(std::istream_iterator(ss), std::istream_iterator(), back_inserter(v); ? – itnovice Nov 15 '11 at 03:29
  • @itnovice: Yes. It is same as `std::copy` version, but it is better than that as it is using the constructor of vector directly. – Nawaz Nov 15 '11 at 04:04
  • @KerrekSB: Or you can write `++(std::istream_iterator(static_cast(std::stringstream() << 100 <<" " << line)))`. Any integer can be used in place of `100`. I hope you understand the rationale here. Code : http://www.ideone.com/9tSV5 – Nawaz Nov 15 '11 at 05:15
  • 1
    @Nawaz: impressive -- no, I'm not sure I understand. Why is the return value not an rvalue anymore? Can the same be achieved more simply with a function that returns a reference to its argument? – Kerrek SB Nov 15 '11 at 09:37
  • @KerrekSB: There are many things to consider : 1) you cannot make copy of a stream, 2) which means you've to pass it by reference to a free function, 3) since it is originally a rvalue, it cannot be bound to a non-const reference, but if you bind it to a const reference, the function cannot return non-const reference. 4) all these imply that this cannot be achieved by using a free function. [Contd.]. – Nawaz Nov 15 '11 at 10:06
  • [Contd.] 5) but since a member function can be invoked on a rvalue and the function can return non-const reference by use of `return *this`, the code works as expected. Now there are many member functions which returns `std::istream&`, for example `std::istream::flush` and `operator<<(int)`. That is why I gave these two examples. – Nawaz Nov 15 '11 at 10:06
  • @Nawaz: The question is, why can a `*this` returned from a member function of an rvalue suddenly be an lvalue? – Kerrek SB Nov 15 '11 at 10:10
  • @KerrekSB: A more intriguing question, I think, is : how can it remain a rvalue if the return type of the member function is `std::istream&`? I think, it is by design, a member function can transform a rvalue into lvalue. – Nawaz Nov 15 '11 at 10:16
  • 1
    So there should be a mixin template `can_use_as_temporary`, from which you derive, and which exposes `T & lvalue() { return *this; }`! – Kerrek SB Nov 15 '11 at 10:21
  • @KerrekSB: I think that is a good feature. I'm going to start a topic on this, with few more questions I've in my mind. Lets see others opinion. – Nawaz Nov 15 '11 at 10:24
  • @Nawaz: I look forward to that! – Kerrek SB Nov 15 '11 at 11:33
  • @Nawaz: Thanks for your reply. Can you explain the usage 'std::istream_iterator begin(ss), end;' a bit more? What happens when the copy constructor of istream_iterator is invoked? Why end is not initialized? How does that work? Thanks! – itnovice Nov 16 '11 at 01:06