-1

I'm working on a code that reads a file containing the "course" of a racetrack and needs to determine how tall and wide the course is first. A sample course looks like such:

xxxxXxxxxXxxxxXxxxxXxxxxXxxxxX
xxxxxx      xxxxx      xxx  xx
xxxxx        xxx       xx    x
xx     xx     x        x     x
X      xx              x  x  x
x      xx                 x  x
x      xxxxxxxxxxxxxx     x  x
x     xxxxxxxxxxxxxxxxxxxxx  x
xFFFF x  xxxxxxxxxxxxxx      x
XFFFF x    xxxxxxxx          x
x     x      1               x
xxxxxxx      2        xxx    x
xxxxxx       3         x     x
x                            x
Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Now because arrays require constant values at compile-time, and I cannot provide that, I attempted to Dynamically allocate the array to memory once the height and width values were retrieved. In sum, my code looks as such:

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

using namespace std;

struct Level {
    int HEIGHT;
    int WIDTH;
};

vector<Level> getRowCol(istream& fin) {

    vector<Level> level;

    fin.seekg(0, fin.end);
    int fileSize = fin.tellg();
    fin.seekg(0, fin.beg);

    string s;
    if (getline(fin, s)) {
        Level l;

        l.WIDTH = s.size();
        l.HEIGHT = fileSize / (l.WIDTH + 1);

        level.push_back(l);
    }

    return level;
}

void readCourse(int& cols, int& rows, istream& fin) {

    char** level = new char*[rows];
    for (int i = 0; i < cols; i++) {
        level[i] = new char[cols];
    }

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            level[i][j] = 0;
        }
    }

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            fin.get(level[i][j]);
        }
    }

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            cout << level[i][j];
        }
    }
}


int main(int argc, char** argv) {
    vector<Level> course;
    ifstream fin(argv[1]);

    course = getRowCol(fin);

    cout << course[0].WIDTH << " columns" << endl;
    cout << course[0].HEIGHT << " rows" << endl;

    readCourse(course[0].WIDTH, course[0].HEIGHT, fin);

    return 0;
}

The output of this code when passed the file via command line looks like:

31 columns
14 rows
xxxxxx      xxxxx      xxx  xx
xxxxx        xxx       xx    x
xx     xx     x        x     x
X      xx              x  x  x
x      xx                 x  x
x      xxxxxxxxxxxxxx     x  x
x     xxxxxxxxxxxxxxxxxxxxx  x
xFFFF x  xxxxxxxxxxxxxx      x
XFFFF x    xxxxxxxx          x
x     x      1               x
xxxxxxx      2        xxx    x
xxxxxx       3         x     x
x                            x
Xxxxxxxxxxxxxxxxxx

There now appear to be gaps. These gaps do not appear if const int values are declared prior to compile time. However I cannot think of another way to have the array build with unknown values until the script is run short of using Dynamic allocation.

Is there another, or a better, way to accomplish this?

  • 1
    Why didn't you stick with using `std::vector` instead of introducing a gigantic memory leak in the `readCourse` function? A `std::vector> level(rows, std::vector(cols));` would have been the way to use vector. – PaulMcKenzie Apr 04 '17 at 00:17
  • @PaulMcKenzie Hmmm. Ill have to give that a further look. At first test, changing the dynamic allocation with your recommendation produces the same results. I'm not overly familiar with vectors, could you elaborate further on the correction? –  Apr 04 '17 at 00:23
  • The code I posted corrects a big mistake in your original in that there is no memory leak. You're already using `std::vector` in the `getRowCol` function, and all the declaration does is declare a vector of vectors, thus essentially a dynamic 2d array. – PaulMcKenzie Apr 04 '17 at 00:25
  • @PaulMcKenzie I am sure it does, but I am not sure as to how to access a `char` at a specific position in this 2D vector array. Normally I can `cout << array[x][y]` and get a value. –  Apr 04 '17 at 00:40
  • Note on `tellg()`: It doesn't tell you the size of the file! See http://stackoverflow.com/a/22986486/396551 – Sjoerd Apr 04 '17 at 00:40
  • @pasta_sauce -- *but I am not sure as to how to access a char at a specific position* -- You access it just like a 2D array, i.e. `cout << array[x][y]` – PaulMcKenzie Apr 04 '17 at 02:48
  • @PaulMcKenzie So I now see what you meant by your recommended 2D vector, however I cannot seem to populate it. The `fin.get(level[I][j])` method does not work. I imagine that I have to use `push_back()` but how does one push back a single `char` per row, per col? –  Apr 04 '17 at 12:12

1 Answers1

0

Your first order of business is to call getRowCol() which, in a rather roundabout way, attempts to determine in advance the size of the map in the file. It does this by determining the size of the file, then rewinding back to the beginning of the file, reading the first line, obtaining its length, and from that compute the number of rows.

However, if you paid very careful attention, you would've realized that when getRowCol() returned, it did that after reading the first line of the file. But the code that follows assumes that it's reading from the beginning of the file, and reads the entire map. Which, of course, is not going to happen any more, because the first line in the file has been read.

This explains the first part of your question, with the missing line and a gap.

As far as the second part of your question: all of this complicated logic is definitely, completely, and totally unnecessary.

Your code demonstrates that you have the basic knowledge and understanding of how vectors work.

So, why is this complicated logic needed in the first place?

Just open the file, and read each line of the file into a vector, push_back()ing each line after it's read, until the end of the file is reached.

When all is said and done, the vector's size is how tall the map is. because the vector contains one value for each row in the file. And by looking at the size of each line, you then know how wide the map is.

That's it. Maybe a dozen of lines of code, and no more than that. Much simpler, and easier to understand, than what you've shown here. Your existing logic (obtaining the file size, obtaining the size of the first row, then dividing one by the other) is very fragile. If, for some reason, the lines in the file have varying lengths (perhaps some of them have some trailing whitespace), your computed result will be completely off. With this, much simpler approach, it becomes much easier to handle this error situation, and still come up with some meaningful results.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • I made the logic change to my code. However when I try to output the vector it is all on a single line. Should I be adding them line by line as string or as char vectors? –  Apr 04 '17 at 01:04
  • And I am not sure how you get the length of an element of a vector. The `.size` gives me the "height" of the map but not the width. There is not `myvector[0].size()` method. –  Apr 04 '17 at 01:27
  • Of course there is. With a `std::vector myvector`, `myvector[0]` would be a `std::string`, with a perfectly functional `size()` method. – Sam Varshavchik Apr 04 '17 at 01:33
  • So now that I have taken my input track and turned it into a vector of 15 strings to determine height and width, do I continue to use that vector for my game or pass the int values of the `size()` methods to the array builder? I require a series of (x,y) points to which each `char` in the course would represent. –  Apr 04 '17 at 02:27