What is the difference between Mathematica Rules and the objects returned by GraphEdit?
This is actually a two-fold question. First: 开发者_如何学Cas someone coming from an OO programming background, I find Mathematica's use of lists as the basis of everything a bit annoying. So here is how a mathematica programmer (as far as I can tell) might define a graph:
graph={{1, 2, 3, 4, 5}, {1->2, 2->4, 4->4, 4->5}};
and then the programmer would just have to remember that
graph[[1]]
refers to the list of vertices and
graph[[2]]
refers to the list of edges (in this case defined as a set of rules.)
So, I was learning about the rules in Mathematica and I saw an opportunity to make my data structures a little more object-oriented feeling. I chose to define a graph something like:
graph={Verts->{1,2,3,4,5}, Edges->{1->2, 2->4, 4->4, 4->5}};
and then refer to vertices and edges (respectively) by
Verts/.graph
Edges/.graph
This can have weird side effects, however, if some other Mathematica file has defined Verts or Edges as a global variable somewhere, however, since the left hand side of the rule is not an identifier, but is itself an object.
So question 1 is this: is this a good practice, or a bad one for creating Mathematica data structures? One of the reasons I'm doing it this way is so that I can attach arbitrary properties, say colors:
AppendTo[graph, Colors->{Red, Red, Blue, Red, Red}]; (* Labels ea. vert with a color *)
and my functions don't have to know the exact order particular properties were added. For instance you might have a function GetColor defined like:
GetColor[graph_, vertIdx_]:=(Colors/.graph)[[vertIdx]];
and this is preferable, because I might not always want to have graph data structures that have Color info and so don't want to reserve a spot in the list (like graph[[[3]]]) for color information.
Second: I see that GraphEdit returns something that looks like the rules I've described above. For instance, if I execute (and draw a graph)
Needs["GraphUtilities`"];
g = GraphEdit[];
g[[2]]
I get output like:
Graph->{1->2,3->3,4->4,5->4}
which looks like a rule! So I try this:
Graph/.g[[2]]
expecting to have
{1->2,3->3,4->4,5->4}
returned. But instead the output is just
Graph
But if I instead execute
g[[2]][[1]] /. g[[2]]
I get the expected output,
{1->2,3->3,4->4,5->4}
which means that g[[2]] really is a rule, but for some reason g[[2]][[1]] (which if executed prints Graph) is not the same as typing Graph. So what the heck is g[[2]][[1]]?
It seems almost like it is a real identifier, which if so, I would like to use to solve the problems with question 1 above. Anyone know the difference, or how to type one vs. the other into Mathematica?
I cannot find anything in the documentation about any of this (or online). Thanks.
GraphEdit Rules
GraphEdit
returns a list whose first element is a Graphics
object and whose remaining elements are rules that describe the graph. The left-hand side of each rule is a string, not a symbol. You can determine this using g // FullForm
. To extract the graph rules, you must ignore the first element of the list, e.g.
"Graph" /. Drop[g, 1]
Simulating Record Types
It is a reasonable approach to implement record-like data types as you propose:
graph={Verts->{1,2,3,4,5}, Edges->{1->2, 2->4, 4->4, 4->5}};
It is true that if Verts
and Edges
were assigned values, then "weird side effects" would occur. However, there are a couple of ways to mitigate that problem.
First, there is an extremely widespread convention within Mathematica to avoid assigning values (specifically OwnValues
) to symbols with upper case initial letters. Wolfram prefixes all top-level variables with $
, e.g. $Context
. If you stick to these conventions, you get some measure of safety.
Second, there is provision for separate namespaces using Packages. Within the bounds of a package you define, you can have complete control over the bindings of symbols that you use as field names.
Third, you can use Protect to prevent field names from having values assigned to them.
When implementing these record types, one could follow a LISP idiom and define constructor and accessor functions. For the graph example, these functions could look something like this:
ClearAll[makeGraph, graphVertices, graphEdges]
makeGraph[vertices_, edges_] := {Verts -> vertices, Edges -> edges}
graphVertices[graph_] := Verts /. graph
graphEdges[graph_] := Edges /. graph
These functions would be used thus:
graph = makeGraph[{1,2,3,4,5}, {1->2,2->4,4->4,4->5}]
(* {Verts -> {1, 2, 3, 4, 5}, Edges -> {1 -> 2, 2 -> 4, 4 -> 4, 4 -> 5}} *)
graphVertices[graph]
(* {1, 2, 3, 4, 5} *)
graphEdges[graph]
(* {1 -> 2, 2 -> 4, 4 -> 4, 4 -> 5} *)
Using this scheme, the field keys Verts
and Edges
can be private to a package and protected, completely avoiding the prospect of an accidental value assignment ruining things.
In Mathematica, it is extremely common to use the Head
of an expression to identify its type. We can conform to this idiom and redefine our record functions thus:
ClearAll[makeGraph, graphVertices, graphEdges]
makeGraph[vertices_, edges_] := graphRecord[Verts -> vertices, Edges -> edges]
graphVertices[graphRecord[rules___]] := Verts /. {rules}
graphEdges[graphRecord[rules___]] := Edges /. {rules}
The only material difference between these and the preceding definitions is that the graph object is now represented by an expression of the form graphRecord[...]
instead of {...}
:
graph = makeGraph[{1,2,3,4,5}, {1->2,2->4,4->4,4->5}]
(* graphRecord[Verts -> {1, 2, 3, 4, 5}, Edges -> {1->2, 2->4, 4->4, 4->5}] *)
graphVertices[graph]
(* {1, 2, 3, 4, 5} *)
graphEdges[graph]
(* {1 -> 2, 2 -> 4, 4 -> 4, 4 -> 5} *)
Why the change? The first reason is that the head graphRecord
now positively identifies the type of data whereas before it was just a list. Second, we can define further functions (quasi-methods) that will only act on graphRecord
s and nothing else. For example:
graphEdgeCount[r_graphRecord] := graphEdges[r] // Length
graphEdgeCount[x_] := (Message[graphEdgeCount::invArg, x]; Abort[])
graphEdgeCount::invArg = "Invalid argument to graphEdgeCount: ``";
Use:
graphEdgeCount[graph]
(* 4 *)
graphEdgeCount["hi"]
During evaluation of graphEdgeCount::invArg: Invalid argument to graphEdgeCount: hi
$Aborted
As a final elaboration to all of this, it would be possible to define a macro function that automatically defined all the record functions given the type and field names. However, as this response is already TL;DR, that is probably best left as topic for another question some day.
Note: If these functions were all defined within the context of a package, their names would use initial capitals (e.g. MakeGraph
instead of makeGraph
). Beware, however, that Mathematica already has lots of built-in symbols that include the word Graph
.
I wonder what version of Mathematica you're using?
Graph theory is more tightly integrated into V8's core, and this is largely an improvement. You can interact with graphs in a way that an object oriented programmer might like. In V8, I'd perform your example like so:
g = Graph[Range[5], {1 -> 2, 2 -> 4, 4 -> 4, 4 -> 5}];
g = SetProperty[{g, 3}, VertexStyle -> Red]
I can then query the property like so:
PropertyValue[{g, 3}, VertexStyle]
Unfortunately, most of the older graph theory functionality doesn't play nicely in V8. Although, you can use it if you're careful with context specification. In V7, I might access the output of GraphEdit
like so:
Needs["GraphUtilities`"];
g = GraphEdit[];
And then,
{vertices, edges} = {"VertexLabels", "Graph"} /. Rest[g]
to get something worth passing to other functions, like GraphPlot
.
This partly answers your question as to whether this is a reasonable representation. This type of representation makes it easy to access the information via replacement rules. A good example of this is given by the way XML import works.
I found the answer to question 2 using InputForm[] (hadn't known about that function before today.)
InputForm[g[[2]][[1]]];
returns
"Graph"
So, it appears the way to avoid the problems in question 1 and the answer to how they define the GraphEdit stuff is to use strings in rules as "identifiers."
Question 1, then can be revised to: is this a good practice?
精彩评论