Reading test data from file

Hi, Please help me with this.

I have to write a program that reads test data from a file.

Scenario:
Each test case starts with LOCATE and ends with Output. Between LOCATE and Output, there can be unlimited instructions. Instructions include move , left, right ... report then Output

Sample data (test.txt):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  LOCATE 1,2,EAST // put the robot at x=1, y=2 facing EAST
  MOVE  // take one step to towards the east
  REPORT
  Output: 2,2 EAST // after moving one step to the east
  LOCATE 0,0,WEST
  MOVE
  REPORT
  Output: 0,0,WEST
  LOCATE 0,0,NORTH
  MOVE
  RIGHT
  MOVE
  MOVE
  REPORT
  Output: 2,1,EAST


My idea is to create a struct like this
1
2
3
4
5
6
7
8
9
10
11
struct Start {
    std::string place{};
    uint16_t x{};
    uint16_t y{};
    std::string direction{};
};

struct TestCase {
    Start start; // holds the line LOCATE
    std::vector<std::string> commands; // holds everything other command between LOCATE to next LOCATE
};

Start should hold the value of the starting point of LOCATE 1,2,EAST and then the rest strings after LOCATE be put in a std::vector

My work thus far

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
std::vector<TestCase> readTestCases()
{
    std::vector<TestCase> testCases;
    std::ifstream ifs(test_file);
    if (!ifs)
    {
        std::cerr << "Could not read the test file\n";
        exit(1);
    } else
    {
        Start start;
        std::vector<std::string> commands;

        TestCase testCase;

        const auto digits{"0123456789"};
        std::string line{};

        while (std::getline(ifs, line))
        {
            if (!line.empty())
            {
                if (line.find("LOCATE") !=std::string::npos)
                {
                    start.place = line.substr(0, line.find_first_of(' '));
                    const auto x_position = line.find_first_of(digits);
                    const auto y_string = line.substr(x_position + 2, line.rfind(','));
               
                    start.x = std::stoi(line.substr(x_position));
                    start.y = std::stoi(y_string);
                    start.direction = line.substr(line.rfind(',')+1);
                    std::cout << start << std::endl;
                }
            }

        }
        testCase.start = start;
        testCase.commands = commands;
        testCases.push_back(testCase);
    }
   return testCases;
}


Any help with logic on how to proceed will be highly appreciated.

Thanks in advance

EDIT:
- Changed PLACE to LOCATE in the function body of readTestCases

- I need help with reading the rest data. i.e knowing when i have read every part of the test case; PLACE .... Output

Disclaimer:
This question is about reading the test data not about its validity or use
Last edited on
What is it that you want help for?
Do you want to read and store the data - or read and execute the data? If you just need to read/parse the data and execute it as it is parsed, then there's no need to store it.

Why is your code looking for PLACE when the sample data doesn't contain this?

Why not first parse into tokens (an enum?) and check/store tokens?

Do the instructions between locate and output always consist of just a single word on a line?
For a struct for each case possibly something like:

1
2
3
4
5
6
7
8
9
10
11
12
enum Dir {NORTH, SOUTH, EAST, WEST};
enum Instruct {MOVE, REPORT, LEFT, RIGHT, UP, DOWN};

struct Pos {
    std::pair<int, int> pos{};
    Dir dir {};
};

struct Case {
    Pos locPos, outPos;
    std::vector<Instruct> instructs;
};


then for all test cases you have:

 
std::vector<Case> cases;

@coder: I have updated the question to include what i need help with.

@seeplus:
Do you want to read and store the data - or read and execute the data? If you just need to read/parse the data and execute it as it is parsed, then there's no need to store it.

I need to read and execute the data. But i need to know when i read a test case
which starts with PLACE and ends with Output

Why is your code looking for PLACE when the sample data doesn't contain this?

Typo. Fixed it with recent edit

Do the instructions between locate and output always consist of just a single word on a line?

Yes the instructions of locate and output always consists of one line as shown in the sample data.
1. What's the different between OUTPUT and REPORT?
2. Can locations be negative?
If no, what happens if I'm at 0,0,WEST and execute a MOVE?

I'm not sure I understand the reason for storing the instructions.
If you execute one line at a time, you're always at the current location.

Hi AbstractionAnon,
Thanks for taking time to look at my question.

REPORT tells the object to report its current state.
Output: xxxxx gives the expected result when REPORT is called.


The values x,x,WEST|EAST etc cannot be negative (uinr16_t). But the readTestCases() function is not expected to check for validity. It concerns itself with the format

Last edited on
I'd recommend using stringstream:

https://cplusplus.com/reference/sstream/stringstream/?kw=stringstream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
std::vector<TestCase> readTestCases()
{
    std::vector<TestCase> testCases;
    std::ifstream ifs(test_file);
    if (!ifs)
    {
        std::cerr << "Could not read the test file\n";
        exit(1);
    } else
    {
        Start start;
        std::vector<std::string> commands;

        TestCase testCase;

        const auto digits{"0123456789"};
        std::string line{};
        bool started = false;
        while (std::getline(ifs, line))
        {
          if (!line.empty())
          {
            std::replace(line.begin(), line.end(), ',', ' '); // Remove ,
            std::replace(line.begin(), line.end(), ':', ' '); // Remove :
            std::transform(line.begin(), line.end(), line.begin(), toupper); // Guaranties uppercase
            std::istringstream iss{line};
            if(started)
            {
              std::string command;
              if(iss >> command)
              {
                if(command == "OUTPUT")
                {
                  started = false;
                  uint16_t x{};
                  uint16_t y{};
                  std::string dir;
                  if((iss >> x) && (iss >> y) && (iss >> dir))
                  {
...
                    testCases.push_back(testCase);
                  }
                  else
                    std::cout << "Error\n";
                }
                else
                  testCase.commands.push_back(command);
              }
              else
                std::cout << "Error\n";
            }
            else
            {
              testCase = {};
              if((iss >> start.place) && (iss >> start.x) && (iss >> start.y) && (iss >> start.direction))
              {
                if (start.place == "LOCATE") // Ok
                {
                  started = true;
                  testCase.start = start;
                }
                else
                  std::cout << "Error\n";
              }
              else
                std::cout << "Error\n";
            }
          }
        }
    }
   return testCases;
}
Not tested and not complete! Just to provide an idea
Last edited on
Here's my take on your program:

pos.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#pragma once

enum class DIR { NORTH, EAST, SOUTH, WEST };
constexpr char COMMA = ',';

class POS
{
	uint16_t	x{};
	uint16_t	y{};
	DIR			dir{ DIR::EAST };

public:
	int get_x() const;
	int get_y() const;
	DIR get_dir() const;

	int parse_int(stringstream& ss);
	bool parse_direction(stringstream& ss, DIR & dir);
	bool parse_locate(stringstream& ss);
	bool move_xy(int incr_x, int incr_y);
	bool set_xy(int new_x, int new_y);

	friend ostream& operator << (ostream& os, const POS& pos);
};

extern int linenum;
extern void rtrim(string& str);


pos.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
using namespace std;
#include "pos.h"

const string dir_name[4] = { "NORTH", "EAST", "SOUTH", "WEST" };

int POS::get_x() const
{
	return x;
}

int POS::get_y() const
{
	return y;
}

DIR POS::get_dir() const
{
	return dir;
}

bool POS::set_xy(int new_x, int new_y)
{
	if (new_x < 0 || new_y < 0)
	{
		cout << "Attempt to move to illegal position at line " << linenum << endl;
		return true;		//	Does not stop the program
	}
	x = new_x;
	y = new_y;
	return true;
}

//	Parse an integer from the stream
int POS::parse_int(stringstream& ss)
{
	string	temp;
	getline(ss, temp, COMMA);
	return stoi(temp);
}

//	Move x and y by some increment
//	Moves to negative positions are ignored
bool POS::move_xy(int incr_x, int incr_y)
{
	return set_xy(x + incr_x, y + incr_y);	
}

//	Parse a direction from the stream
bool POS::parse_direction(stringstream& ss, DIR & dir)
{
	string	temp;

	getline(ss, temp);
	rtrim(temp);
	for (int i = 0; i < 4; i++)
		if (temp == dir_name[i])
		{
			dir = (DIR)i; 
			return true;
		}
	cout << "Invalid direction: " << temp << endl;
	return false;	
}
//	LOCATE x , y , dir
bool POS::parse_locate(stringstream& ss)
{
	int temp_x, temp_y;
	temp_x = parse_int(ss);
	temp_y = parse_int(ss);
	set_xy(temp_x, temp_y);
	return parse_direction(ss, dir);	
}

//	Format a POS to the output stream
std::ostream& operator << (std::ostream& os, const POS& pos)
{
	os << pos.x << "," << pos.y << "," << dir_name[(int)pos.dir];
	return os;
}


main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
using namespace std;
#include "pos.h"

//	Globals
POS		curr_pos;
int		linenum = 0;

//	Remove a single trailing space from string
void rtrim(string& str)
{
	if (str[str.size() - 1] == ' ')
		str.erase(str.size() - 1);	//  Erase trailing sopace
}

void upshift(string& str)
{
	for (size_t i = 0; i < str.size(); i++)
		if (islower(str[i]))
			str[i] = toupper(str[i]);
}
//	Move in some direction
bool do_move(DIR dir)
{	
	const int delta_x[4] = { 0, 1, 0, -1 };
	const int delta_y[4] = { 1, 0, -1, 0 };

	return curr_pos.move_xy(delta_x[(int)dir], delta_y[(int)dir]); 
}

//	Report current position
bool do_report()
{
	cout << curr_pos << endl;
	return true;
}

//	Echo the OUTPUT command
bool do_output(const string & line)
{
	cout << line << endl;
	return true;
}

bool parse_line(const string& line)
{
	string cmd;
	stringstream	ss(line);

	ss >> cmd;
	if (cmd == "LOCATE")
		return curr_pos.parse_locate(ss);
	if (cmd == "MOVE")
		return do_move(curr_pos.get_dir());	
	if (cmd == "REPORT")
		return do_report();
	if (cmd == "OUTPUT:")
		return do_output(line);
	if (cmd == "RIGHT")
		return do_move (DIR::EAST);
	if (cmd == "LEFT")
		return do_move(DIR::WEST);
	if (cmd == "UP")
		return do_move(DIR::NORTH);
	if (cmd == "DOWN")
		return do_move(DIR::NORTH);
	cout << "Command not recognized: " << cmd << endl;
	return false;
}

bool parse_file(const string& fname)
{
	ifstream	ifs(fname);
	string		line;

	if (!ifs.is_open())
	{
		cout << "Unable to open file: " << fname << endl;
		return false;
	}
	while (getline(ifs, line))
	{
		linenum++;
		cout << linenum << ".  " << line << endl;
		upshift(line);
		if (!parse_line(line))
		{
			cout << "Could not parse line: " << line << endl;
			return false;
		}
	}
	return true;
}

int main()
{
	if (!parse_file("test.txt"))
		return 1;
	return 0;
}


note that the code does not handle comments in your test file.

1.  LOCATE 1,2,EAST
2.    MOVE
3.    REPORT
2,2,EAST
4.    Output: 2,2,EAST
  OUTPUT: 2,2,EAST
5.    LOCATE 0,0,WEST
6.    MOVE
Attempt to move to illegal position at line 6
7.    REPORT
0,0,WEST
8.    Output: 0,0,WEST
  OUTPUT: 0,0,WEST
9.    LOCATE 0,0,NORTH
10.    MOVE
11.    RIGHT
12.    MOVE
13.    MOVE
14.    REPORT
1,3,NORTH
15.    Output: 2,1,EAST
  OUTPUT: 2,1,EAST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <iostream>
#include <fstream>
#include <string>
#include <string_view>
#include <array>
#include <utility>
#include <algorithm>
#include <vector>
#include <cctype>
#include <limits>

enum Dir {
	NORTH, SOUTH, EAST, WEST
};

enum Instruct {
	MOVE, REPORT, LEFT, RIGHT, UP, DOWN
};

struct Pos {
	std::pair<int, int> pos {};
	Dir dir {};
};

struct Case {
	Pos locPos, outPos;
	std::vector<Instruct> instructs;
};

std::string& toUpper(std::string& s) {
	for (auto& c : s)
		c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));

	return s;
}

constexpr std::array<std::string_view, 4> dir { "NORTH", "SOUTH", "EAST", "WEST" };
constexpr std::array<std::string_view, 6> instruct { "MOVE", "REPORT", "LEFT", "RIGHT", "UP", "DOWN" };

std::istream& operator>>(std::istream& is, Pos& pos) {
	std::string d;

	if (char del {}; is >> pos.pos.first >> del >> pos.pos.second >> del >> d) {
		if (const auto p { std::ranges::find(dir, toUpper(d)) }; p != dir.end())
			pos.dir = Dir(p - dir.begin());
		else {
			std::cout << "Incorrect direction\n";
			is.setstate(std::ios_base::badbit);
		}

		is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
	}

	return is;
}

std::ostream& operator<<(std::ostream& os, const Pos& pos) {
	return os << pos.pos.first << ',' << pos.pos.second << ',' << dir[pos.dir];
}

std::istream& operator>>(std::istream& is, Case& cse) {
	std::string str;
	cse.instructs.clear();

	if (is >> str; is && (str == "LOCATE"))
		if (is >> cse.locPos) {
			do {
				if (is >> str; is && (toUpper(str) != "OUTPUT:")) {
					is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
					if (const auto i { std::ranges::find(instruct, str) }; i != instruct.end())
						cse.instructs.push_back(Instruct(i - instruct.begin()));
					else {
						std::cout << "Invalid instruction\n";
						is.setstate(std::ios_base::badbit);
					}
				}
			} while (is && (str != "OUTPUT:"));

			is >> cse.outPos;
		}
	else if (is) {
		std::cout << "LOCATE expected\n";
		is.setstate(std::ios_base::badbit);
	}

	return is;
}

std::ostream& operator<<(std::ostream& os, const Case& cse) {
	os << "LOCATE " << cse.locPos << '\n';

	for (const auto& i : cse.instructs)
		os << instruct[i] << '\n';

	return os << "Output: " << cse.outPos;
}

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

	if (!ifs)
		return (std::cout << "Can not open input file\n"), 1;

	std::vector<Case> cases;

	for (Case c; ifs >> c; cases.push_back(c));

	for (const auto& c : cases)
		std::cout << c << '\n';
}


Which given an input file of:


LOCATE 1,2,EAST // put the robot at x=1, y=2 facing EAST
  MOVE  // take one step to towards the east
  REPORT
  Output: 2,2,EAST // after moving one step to the east
  LOCATE 0,0,WEST
  MOVE
  REPORT
  Output: 0,0,WEST
  LOCATE 0,0,NORTH
  MOVE
  RIGHT
  MOVE
  MOVE
  REPORT
  Output: 2,1,EAST


parses the file and displays:


LOCATE 1,2,EAST
MOVE
REPORT
Output: 2,2,EAST
LOCATE 0,0,WEST
MOVE
REPORT
Output: 0,0,WEST
LOCATE 0,0,NORTH
MOVE
RIGHT
MOVE
MOVE
REPORT
Output: 2,1,EAST

Last edited on
Topic archived. No new replies allowed.