Parsing a string in C ++
I am trying to create a constructor for a graph class that takes a string like and uses it to plot a graph.
The string is formatted like this: |vertex list|Edges list|
for example|1,2,3,4,15|(1->2),(3->2),(4->15)|
The idea is that the constructor will take values from a string and then know to do the following (insert vertices into the list of vertices and then insert edges into the list of edges):
addVertex(1) addVertex(2) addVertex(3) addVertex(4) addVertex(15) addEdge(1,2) addEdge(3,2) addEdge(4,15)
I would just do a couple of "for" loops to scan a string, but I don't know what to do with double (or more) digits. I'm starting to imagine all kinds of seriously complicated for loops, and I'm wondering if someone here can share smarter ways to extract and use this data with me.
a source to share
You seem to be overwhelmed looking at all this. Break it to pieces ... tasks. What you are trying to do seems to be a separate functionality.
- Tokenizing
- Parsing Summits
- Collapsible edges
- Vertex execution
- Execution at the edges
These are 5 functions more or less.
You want a pipe based tokenize (|), so take a pipe based substring and pass each side to the appropriate parser, parse commas, etc.
Not going to do it for you, but hopefully I can make you think in the right direction. Learning to program isn't so much about any particular language as it's about changing the way you think.
a source to share
Without your homework for you, it will give you a good start. I gave you a basic workflow for parsing a vertex list, you have to make the edge list yourself. I also leave the error checking for you, for example in parseVertex () you can give an error if you encounter invalid characters.
void skipWhiteSpace(const char*& first , const char* last) {
// do whatever need to be done to skip white space
}
// parse integer only, no error checking is performed
bool parseVertex(const char*& first , const char* last) {
skipWhiteSpace(first, last);
const char* numBegin = first;
for (; first != last && ::isdigit(static_cast<unsigned char>(*first));
++first) {}
if (numBegin != first) {
std::cout << "addVertex(" << std::string(numBegin, first) << ")" << std::endl;
return true;
}
return false;
}
bool parseComma(const char*& first , const char* last) {
skipWhiteSpace(first, last);
if (first != last && ',' == *first) {
++first;
return true;
}
return false;
}
// VL := V (, VL)
// a vertex list (VL) is a vertex (V) followed by a comma than another vertex list
bool parseVertexList(const char*& first, const char* last) {
if (parseVertex(first, last)) {
parseComma(first, last) && parseVertexList(first, last);
return true;
}
return false;
}
}
void test() {
const char* str = "1,2,3,4,15";
parseVertexList(str, str + sizeof("1,2,3,4,15"));
}
a source to share
Analyzing this sort of thing is pretty easy (albeit tedious) with recursive descent techniques. The idea is to split the language that is parsed into logical units, and then write a function to parse each of those units.
If we indicate in the example "| 1,2,3,4,15 | (1-> 2), (3-> 2), (4-> 15) |" that the whole string is a "polygon", we will write parsePolygon (), which will look something like this:
void parsePolygon (Buffer& b)
{
parseVertices (b);
parseEdges (b);
}
Suppose Buffer is a class that goes through your string. You will need two basic operations: look into the next symbol without consuming it, and use the next symbol.
parseVertices might look like this:
void parseVertices (Buffer& b)
{
if (b.peek() != '|') { /* error */ }
b.consume (); // burn the '|'
parseVertexList (b);
if (b.peek() != '|') { /* error */ }
b.consume (); // burn the '|'
}
You want to handle errors much better, of course. If the thread encounters any error, it needs to pass the error code onto the call stack or throw an exception.
Two more examples ... parseVertexList and parseNumber might look like this:
void parseVertexList (Buffer& b)
{
addVertex (parseNumber (b));
while (b.peek() == ',')
{
b.consume (); // eat the comma
addVertex (parseNumber (b));
}
}
int parseNumber (Buffer& b)
{
char accum[80] = { '0' }; // sensible default in case of failure
int accumPos = 0;
while (isDigit (b.peek())
{
accum[accumPos++] = b.consume();
}
return atoi(accum);
}
It's very quick and messy, but hopefully it gives you an idea of how this technique works. You can mix the processing with your parsing as shown above, where the parseVertexList function actually makes the addVertex calls for you.
I think this is really one of the simplest manual parsing methods. Ideally, we can always use created parsers like boost spirit or pyparsing or lex / yacc, but life isn't always good, especially for homework.
Also, I suppose it's worth noting that the above technique may be a lot overkill for some parsing situations.
a source to share
Use stringstream
. Check out the example on this page for reading in numbers using istringstream
.
a source to share
I would definitely use this issue as an excuse to play with the boost spirit ! Writing a little grammar for this tiny language should be a lot of fun.
a source to share