作为一个学习项目,我正在努力用Chapel实现替换perl中有点慢的程序。我已经算法了,但我正在努力寻找在Chapel中引用数据的最佳方法。我可以直接翻译,但似乎我错过了一个更好的方法。
现有计划的详情:
在Perl中,程序使用具有常用整数ID的散列作为节点和边缘属性。我当然可以在Chapel中使用关联数组来复制它。
有没有更好的方法将这些捆绑在一起?我一直试图围绕着定义每个项目的不透明节点和边缘的方法,但是如何使用整数ID以简单的方式引用它们一直在努力。
如果某人能够提供理想的方式来完成以下任务,那么它将为我提供所需的推动力。
干杯,谢谢。
答案 0 :(得分:4)
正如您所料,在Chapel中有很多方法可以解决这个问题,但我认为考虑到您的历史方法和外部系统的界面,关联域和数组绝对是一种合适的方式。具体来说,鉴于您希望通过整数ID引用节点,因此关联域/数组是一种自然匹配。
对于Chapel新手:关联域本质上是任意值的集合,在这种情况下就像整数节点ID的集合。关联数组是从关联域的索引到给定类型的元素(变量)的映射。本质上,域表示键,数组表示键值存储或哈希表中的值。
为了表示节点和边缘本身,我将采用Chapel记录的方法。这是我对节点的记录:
record node {
var id: int;
var str: string,
i: int,
flag: bool;
var edges: [1..0] edge;
}
如您所见,它将id
存储为各种类型的整数,任意属性字段(字符串str
,整数i
和布尔{{1}你可以为你的程序提供更好的名字),以及我将在一秒钟内返回的一系列边缘。请注意,每个节点可能存在也可能不需要存储其ID ...也许在您拥有节点的任何环境中,您已经知道其ID,在这种情况下存储它可能是多余的。在这里我存储它只是为了表明你可以,而不是因为你必须。
回到边缘:在你的问题中,它听起来好像边缘可能有自己的整数ID并存储在与节点相同的池中,但在这里我采取了不同的方法:根据我的经验,给定一个节点,我通常希望从它的边缘引出一组边,所以我让每个节点存储一个它的传出边的数组。在这里,我使用了一个最初为空的密集一维边缘阵列(flag
是Chapel中的一个空范围,因为1..0
)。如果要为每个边提供唯一ID,也可以使用关联的边数组。或者您可以完全删除节点数据结构中的边缘并将它们全局存储。如果您不喜欢不同的方法,请随时提出后续问题。
这是我代表优势的记录:
1 > 0
前两个字段(record edge {
var from, to: int,
flag1, flag2: bool;
}
和from
)表示边连接的节点。与上面的节点ID一样,to
字段可能是冗余/不必要的,但为了完整性,我在此处将其包括在内。两个标志字段用于表示您与边缘关联的数据属性。
接下来,我将创建关联域和数组来表示节点ID和节点本身的集合:
from
这里,var NodeIDs: domain(int),
Nodes: [NodeIDs] node;
是表示节点的整数ID的关联域(集合)。 NodeIDs
是一个关联数组,它将这些整数映射到类型Nodes
的值(我们在上面定义的记录)。
现在,转向您的三个操作:
创建两个节点,其中xx属性由整数ID标识。
以下声明使用Chapel为不能定义自己的记录的默认记录构造函数/初始化程序创建一个名为node
的节点变量,其中包含一些任意属性:
n1
然后我可以将它插入节点数组中,如下所示:
var n1 = new node(id=1, "node 1", 42, flag=true);
此分配有效地将Nodes[n1.id] = n1;
添加到n1.id
域,并将NodeIDs
复制到n1
中的相应数组元素中。这是一个创建第二个匿名节点并将其添加到集合的赋值:
Nodes
请注意,在上面的代码中,我假设您要明确选择每个节点的ID(例如,您的数据文件可能是否建立了节点ID?)。另一种方法(这里没有显示)可能是自动确定它们,因为节点是使用全局计数器创建的(如果您并行创建它们,可能是原子计数器)。
填充完我们的节点之后,我们可以串行或并行地迭代它们(这里我并行执行;将Nodes[2] = new node(id=2, "node 2", i=133);
替换为forall
会使它们串行):
for
这些循环打印ID和节点的顺序是任意的,原因有两个:(1)它们是并行循环; (2)关联域和数组以任意顺序存储它们的元素。
使用xx attribues
在两者之间创建边缘
由于我将边缘与节点相关联,因此我采用了在writeln("Printing all node IDs (in an arbitrary order):");
forall nid in NodeIDs do
writeln("I have a node with ID ", nid);
writeln("Printing all nodes (in an arbitrary order):");
forall n in Nodes do
writeln(n);
类型上创建方法的方法,该方法将为其添加边缘:
node
此过程获取目标节点ID,并将属性作为其参数,使用该信息创建边缘(并将原始节点的ID作为proc node.addEdge(to: int, flag1: bool, flag2: bool) {
edges.push_back(new edge(id, to, flag1, flag2));
}
字段提供),并使用{{ 1}}矩形数组上的方法将其添加到边列表中。
然后我调用这个例程三次为节点2创建一些边(包括冗余和自边缘,因为到目前为止我只有两个节点):
from
此时,我可以循环遍历给定节点的所有边缘,如下所示:
push_back()
这里,任意打印顺序仅仅是由于使用了并行循环。如果我使用了序列Nodes[2].addEdge(n1.id, true, false);
Nodes[2].addEdge(n1.id, false, true);
Nodes[2].addEdge(2, false, false);
循环,我会按照添加顺序遍历边缘,因为使用了一维数组来表示它们。
回应请求"显示node(int)"
的xx属性
你现在可能已经得到了这个,但我可以通过索引到writeln("Printing all edges for node 2: (in an arbitrary order):");
forall e in Nodes[2].edges do
writeln(e);
数组来获得节点的任意属性。例如,表达式:
for
会给我节点2的字符串属性。这里是我编写的一个小帮助程序来获取(并打印)一些不同的属性):
Nodes
以下是对此的一些要求:
...Nodes[2].str...
我正在努力用Chapel实现替换perl中有点慢的程序
鉴于速度是查看Chapel的原因之一,一旦程序正确,请使用proc showAttributes(id: int) {
if (!NodeIDs.member(id)) {
writeln("No such node ID: ", id);
return;
}
writeln("Printing the complete attributes for node ", id);
writeln(Nodes[id]);
writeln("Printing its string field only:");
writeln(Nodes[id].str);
}
标志重新编译它以使其快速运行。