1

这实际上是一个双重问题。首先:作为一个来自 OO 编程背景的人,我发现 Mathematica 使用列表作为一切的基础有点烦人。所以这里是一个mathematica程序员(据我所知)如何定义一个图:

graph={{1, 2, 3, 4, 5}, {1->2, 2->4, 4->4, 4->5}};

然后程序员只需要记住

graph[[1]] 

指的是顶点列表和

graph[[2]]

指边缘列表(在这种情况下定义为一组规则。)

所以,我正在学习 Mathematica 中的规则,我看到了一个机会,可以让我的数据结构更具有面向对象的感觉。我选择定义一个类似的图形:

graph={Verts->{1,2,3,4,5}, Edges->{1->2, 2->4, 4->4, 4->5}};

然后通过(分别)引用顶点和边

Verts/.graph
Edges/.graph

但是,如果其他 Mathematica 文件在某处将 Verts 或 Edges 定义为全局变量,这可能会产生奇怪的副作用,因为规则的左侧不是标识符,而是本身就是一个对象。

所以问题 1 是这样的:对于创建 Mathematica 数据结构,这是一个好习惯还是坏习惯?我这样做的原因之一是我可以附加任意属性,比如颜色:

AppendTo[graph, Colors->{Red, Red, Blue, Red, Red}]; (* Labels ea. vert with a color *)

而且我的函数不必知道添加特定属性的确切顺序。例如,您可能定义了一个 GetColor 函数,如下所示:

GetColor[graph_, vertIdx_]:=(Colors/.graph)[[vertIdx]];

这是可取的,因为我可能并不总是希望拥有具有颜色信息的图形数据结构,因此不想在列表中保留一个位置(如图形[[[3]]])用于颜色信息。

第二:我看到 GraphEdit 返回的东西看起来像我上面描述的规则。例如,如果我执行(并绘制图表)

Needs["GraphUtilities`"];
g = GraphEdit[];
g[[2]]

我得到如下输出:

Graph->{1->2,3->3,4->4,5->4}

这看起来像一个规则!所以我试试这个:

Graph/.g[[2]]

期待拥有

{1->2,3->3,4->4,5->4}

回来。但相反,输出只是

Graph

但如果我改为执行

g[[2]][[1]] /. g[[2]]

我得到了预期的输出,

{1->2,3->3,4->4,5->4}

这意味着 g[[2]] 确实是一个规则,但由于某种原因 g[[2]][[1]] (如果执行打印 Graph)与键入 Graph 不同。那么 g[[2]][[1]] 到底是什么?

看起来它几乎是一个真正的标识符,如果是这样,我想用它来解决上面问题 1 的问题。任何人都知道其中的区别,或者如何在 Mathematica 中输入一个与另一个?

我在文档中找不到任何关于此(或在线)的任何内容。谢谢。

4

3 回答 3

4

我想知道您使用的是什么版本的 Mathematica?

图论更紧密地集成到 V8 的核心中,这在很大程度上是一种改进。您可以以面向对象的程序员可能喜欢的方式与图进行交互。在 V8 中,我会像这样执行您的示例:

g = Graph[Range[5], {1 -> 2, 2 -> 4, 4 -> 4, 4 -> 5}];
g = SetProperty[{g, 3}, VertexStyle -> Red]

然后我可以像这样查询属性:

PropertyValue[{g, 3}, VertexStyle]

不幸的是,大多数较旧的图论功能在 V8 中都不能很好地发挥作用。但是,如果您对上下文规范非常小心,则可以使用它。在 V7 中,我可能会GraphEdit像这样访问输出:

Needs["GraphUtilities`"];
g = GraphEdit[];

进而,

{vertices, edges} = {"VertexLabels", "Graph"} /. Rest[g]

得到一些值得传递给其他函数的东西,比如GraphPlot.

这部分回答了您关于这是否是合理的表示的问题。这种类型的表示可以通过替换规则轻松访问信息。XML 导入的工作方式就是一个很好的例子。

于 2011-07-27T18:26:16.057 回答
4

图表编辑规则

GraphEdit返回一个列表,其第一个元素是Graphics对象,其余元素是描述图的规则。每个规则的左侧是一个字符串,而不是一个符号。您可以使用g // FullForm. 要提取图形规则,您必须忽略列表的第一个元素,例如

"Graph" /. Drop[g, 1]

模拟记录类型

按照您的建议,这是一种实现类似记录的数据类型的合理方法:

graph={Verts->{1,2,3,4,5}, Edges->{1->2, 2->4, 4->4, 4->5}};

确实,如果VertsEdges被赋值,那么“奇怪的副作用”就会发生。但是,有几种方法可以缓解该问题。

首先,在 Mathematica 中有一个非常普遍的约定,以避免将值(特别是OwnValues)分配给具有大写首字母的符号。Wolfram 为所有顶级变量添加前缀$,例如$Context. 如果您遵守这些约定,您将获得一定程度的安全性。

其次,使用Packages提供了单独的命名空间。在您定义的包的范围内,您可以完全控制用作字段名称的符号绑定。

第三,您可以使用Protect来防止为字段名称分配值。

在实现这些记录类型时,可以遵循 LISP 习惯用法并定义构造函数和访问函数。对于图形示例,这些函数可能如下所示:

ClearAll[makeGraph, graphVertices, graphEdges]
makeGraph[vertices_, edges_] := {Verts -> vertices, Edges -> edges}
graphVertices[graph_] := Verts /. graph
graphEdges[graph_] := Edges /. graph

这些函数将因此被使用:

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} *)

使用这种方案,字段键VertsEdges可以对包私有并受到保护,完全避免了意外赋值破坏事物的可能性。

Head在 Mathematica 中,使用表达式的 来识别其类型是极为常见的。我们可以遵循这个习惯用法并重新定义我们的记录函数:

ClearAll[makeGraph, graphVertices, graphEdges]
makeGraph[vertices_, edges_] := graphRecord[Verts -> vertices, Edges -> edges]
graphVertices[graphRecord[rules___]] := Verts /. {rules}
graphEdges[graphRecord[rules___]] := Edges /. {rules}

这些和前面的定义之间的唯一实质性区别是,图形对象现在由以下形式的表达式表示,graphRecord[...]而不是{...}

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} *)

为什么改变?第一个原因是头部graphRecord现在可以肯定地识别数据类型,而之前它只是一个列表。其次,我们可以定义更多的函数(准方法),它们只会作用于graphRecords 而不会作用于其他任何东西。例如:

graphEdgeCount[r_graphRecord] := graphEdges[r] // Length
graphEdgeCount[x_] := (Message[graphEdgeCount::invArg, x]; Abort[])
graphEdgeCount::invArg = "Invalid argument to graphEdgeCount: ``";

采用:

graphEdgeCount[graph]
(* 4 *)

graphEdgeCount["hi"]
在计算 graphEdgeCount::invArg 期间:graphEdgeCount 的参数无效:hi
$Aborted

作为对所有这些的最后阐述,可以定义一个宏函数,该函数自动定义给定类型和字段名称的所有记录函数。但是,由于此响应已经是 TL;DR,因此最好将其留作某天另一个问题的主题。

注意:如果这些函数都在包的上下文中定义,则它们的名称将使用首字母大写(例如MakeGraph,而不是makeGraph)。但是请注意,Mathematica 已经有很多包含单词的内置符号Graph

于 2011-07-29T03:12:53.573 回答
0

我使用 InputForm[] 找到了问题 2 的答案(在今天之前不知道该函数。)

InputForm[g[[2]][[1]]];

返回

"Graph"

因此,似乎避免问题 1 中的问题的方法以及他们如何定义 GraphEdit 东西的答案是在规则中使用字符串作为“标识符”。

问题1,然后可以修改为:这是一个好习惯吗?

于 2011-07-27T17:36:25.000 回答