There are several issues here.
The first is that a graph can be represented in all kinds of ways. A graph is an abstract concept. The trick, then, is to represent it using a structure that makes your manipulations of it convenient.
In the description you have provided, you list three characteristics of nodes:
• name (like “G23”)
• zero or two linked nodes
•
type (“INPUT”, “OUTPUT”, “NAND”, etc)
Your structure should also have these things. Naïvely, you might write:
1 2 3 4 5 6
|
struct node
{
std::string name;
??? linked[2];
??? type;
};
|
There is a difference between these things, though. Notice that a node is actually referenced by its name. For example, you NAND two INPUT nodes by referencing them by name:
nand(G1,G2)
It might help to do the same. (And, as it turns out, this is actually a very good way to do it.)
Your graph might then want to store nodes in a
std::map
:
1 2 3 4 5 6 7
|
typedef std::string NodeName;
struct Node
{
??? links[2];
??? type;
};
typedef std::map <NodeName, Node> Graph;
|
This makes our links very easy references as well — we just need the name of the node being linked:
1 2 3 4 5 6 7
|
typedef std::string NodeName;
struct Node
{
NodeName links[2];
??? type;
};
typedef std::map <NodeName, Node> Graph;
|
What this also does is make it very easy to process the graph:
for each OUTPUT node in the graph:
cout << evaluate( the node ) << "\n";
And
func evaluate( node ):
switch (graph[node].type)
case nand: return ! (evaluate(graph[node].links[0]) && evaluate(graph[node].links[1]));
case ...
The
type of a node might be any useful object, even a string. You could easily wow your prof by using an enum, which would work very well in that evaluate() switch.
The last trick is reading your input into the graph. (Which is what you are asking about...)
Remember, a graph is
however you want to represent it.
Put another way, you are not translating the input into C++; rather you are translating it into
however you represent it.
For a map of nodes, as I have suggested, this because really, really easy. Everything has a very specific format:
[NAME '='] TYPE '(' LINK0 [',' LINK1] ')'
Neither C++ nor C make any functions that make parsing this kind of stuff particularly easy straight from file. You will probably want to write a few functions that ask for a specific thing. Like:
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
|
std::string get_name_or_type( std::istream& f )
// Skips whitespace, returns all alphanumeric characters until encountering one of:
// space, equal sign, comma, or parenthesis (left or right). (Or EOF.)
{
int c;
std::string s;
f >> std::ws; // skip whitespace
// Read a character. While character is alphanumeric, append to s.
while (f.get( c ) && std::isalnum( c )) s += c;
// The last character read was not alphanumeric.
// We'll want to read it again, so put it back in the stream.
f.putback( c );
return s;
}
bool is_read_equal( std::istream& f )
// If the next character in f is an equal sign, read it and return true.
// Return false otherwise.
{
if (f.peek() != '=') return false;
f.get();
return true;
}
|
This kind of code is the basis of how a standard
Recursive Descent parser is designed.
As a side note, several modern compiler writers have rediscovered that Recursive Descent is an exceedingly powerful parsing method. Microsoft, for example.
Your reading code now becomes exceedingly simple.
while (true):
std::string name, type = get_name_or_type( myfile );
if (!myfile) break; // all done
if (is_read_equals( myfile )):
name = type;
type = get_name_or_type( myfile );
if (!is_read_open_paren( myfile )) complain_and_quit();
...
and so on.
The graph need not be represented this way. You can represent it as a list or vector of values. You can use the self-created node types. You can use an array. The associate array (or std::map) is just likely your easiest bet. If you must design something yourself, design something that approximates that.
1 2 3 4 5 6 7 8 9 10 11 12
|
struct Node
{
string links[2];
NodeType type;
};
struct NodeReference
{
string name;
Node* node;
};
NodeReference graph[ MAX_NODES ];
unsigned long num_nodes = 0;
|
I know this is a lot to take in. Take your time and things will become clear.
Hope this helps.
[edit]
Heh, I just wrote this at home...
My input file [
output = and(input1,or(input2,input3))]:
output(g0)
g0 = and(g1,g4)
g4 = or(g2,g3)
input(g1)
input(g2)
input(g3)
|
And my run...
C:\m\Programming\cpp\random\a>a t1.txt
0 0 0
0
C:\m\Programming\cpp\random\a>a t1.txt
0 0 1
0
C:\m\Programming\cpp\random\a>a t1.txt
0 1 0
0
C:\m\Programming\cpp\random\a>a t1.txt
0 1 1
0
C:\m\Programming\cpp\random\a>a t1.txt
1 0 0
0
C:\m\Programming\cpp\random\a>a t1.txt
1 0 1
1
C:\m\Programming\cpp\random\a>a t1.txt
1 1 0
1
C:\m\Programming\cpp\random\a>a t1.txt
1 1 1
1
C:\m\Programming\cpp\random\a>type t1.txt
|
Heh heh heh...
Did it in about 150 lines, which are a few more than strictly necessary, but I like things clean... Additional output for errors:
output(g0)
input(g1,g2)
g7=and(g3)
↓
ERROR: node U1:INPUT expected close parenthesis
|
output(g0)
input(g1)
g7=and(g3)
↓
ERROR: node G7:AND missing second link
|
Here’s how to do enums:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
#define NODE_TYPES(F) \
F(INPUT, 1) \
F(OUTPUT,1) \
F(AND, 2) \
F(NAND, 2) \
F(OR, 2) \
F(XOR, 2) \
F(NOT, 1)
#define F(a,b) a ## _NODE,
typedef enum { NODE_TYPES(F) NumNodeTypes } NodeType;
#undef F
#define F(a,b) # a,
const char* NodeNames[] = { NODE_TYPES(F) };
#undef F
#define F(a,b) b,
int NumNodeLinks[] = { NODE_TYPES(F) 0 };
#undef F
|
LOL, It was fun!