【问题标题】:Haskell List Comprehension - Not all data getting transferredHaskell List Comprehension - 并非所有数据都被传输
【发布时间】:2021-09-01 13:52:16
【问题描述】:

我有这样的网络数据结构:

data Network = Empty | Node Double [Network] [Network]

其中 Double 表示节点标签,第一个列表是父节点,第二个列表是给定节点的子节点。假设所有边都从根指向叶,并且没有循环。

我有一个这样的边缘数据结构:

data Edge = Edge (Network, Network)

边缘从 a->b 指向 Edge (a,b)

我编写了一个函数来列出网络的所有树边缘。为了进一步定义这一点,具有多个父节点(网状节点)的节点的每个和所有父(传入)边都不是树边。我正在考虑将来自网状节点的出边以及网络中的所有其他边视为树边。

我尝试像这样对这个任务使用列表推导:

getTreeEdges :: Network -> [Edge]
getTreeEdges (Node label ps cs) = nub (getTreeEdgesHelp (Node label (Empty:[]) cs))
getTreeEdgesHelp :: Network -> [Edge]
--leaf node case, children is empty list
getTreeEdgesHelp n@(Node _ ps []) = [Edge (p, n) | p <- ps ]
--reticulation node and root case, there is more than one parent or parent is empty(root)
getTreeEdgesHelp (Node _ ps cs) | ((length ps) > 1||ps==(Empty:[])) = (concat (map getTreeEdgesHelp cs))
--interal node case
getTreeEdgesHelp n@(Node _ ps cs) = ([Edge (p, n) | p <- ps ])++( concat (map getTreeEdgesHelp cs))

如您所见,列表推导式用于存储叶和内部边的父边,而根节点和网状节点跳过将边添加到列表中而只是递归。叶结束递归。

我遇到的问题是存储的边缘不包含所有预期的信息。更具体地说,父节点的父信息消失了。其他信息仍然存在,标签和子项不受影响。

例如,假设我存储边缘(a,b)。网络b 存储正确。网络a 有自己(根节点)和所有后代节点都存在并正确标记,但它们都没有任何父节点的条目,每个节点的父节点都是[]

下面是一个给定输入、输出和预期输出的示例,具有 newick 格式 (a,b)c,用于具有子 a 和 b 的节点 c。我还在标签后的方括号中包含给定节点的父节点。为了全面披露,以下是我为NetworkEdge 定义Show 的方式。

instance Show Network where
  show Empty = "_"
  show (Node a ps []) = (show a)++"["++printlabels ps++"]"
  show (Node a ps [x,y]) = "("++show x++","++show y ++ ")"++(show a)++"["++printlabels ps++"]"
  show (Node a ps [x]) = "("++show x++")"++(show a)++"["++printlabels ps++"]"
printlabels :: [Network] -> [Char]
printlabels [] = " "
printlabels (Empty:_) = "root"
printlabels ((Node label _ _):ns) = (show label)++","++(printlabels ns)

这是我生成的示例树。因为标签比较长,所以我用字母来标记内部节点,用整数来标记叶子。

(((4.0[c],((0.0[f])f[e,h],(1.0[g],2.0[g])g[e])e[c])c[h],(0.0[f])f[e,h])h[d],3.0[d])d[root]

是下图的表示。

运行上述函数给出:

[(d,h),(h,c),(c,4.0),(c,e),(f,0.0),(e,g),(g,1.0),(g,2.0),(d,3.0)]

instance Show Edge where
  show ( Edge ((Node labelu _ _), (Node labelv _ _)) ) = "("++(show labelu)++","++(show labelv)++")"

当我查询单个边时,说出位置 5 的边 (e,g) 并将 Show 重写为 Edge

instance Show Edge where
  show ( Edge (u, v) ) = "("++(show u)++"=1, 2="++(show v)++")"

我得到(((1.0[ ],2.0[ ])g[ ],0.0[ ])e[ ]=1, 2=(1.0[g],2.0[g])g[e]),而预期的结果是(((1.0[g],2.0[g])g[e],(0.0[f])f[e,h])e[c]=1, 2=(1.0[g],2.0[g])g[e])

我注意到的另一个错误是0.0g 的兄弟,这是不正确的。正如评论者指出的那样,在尝试获取树边缘时,我没有更改任何节点,我只是将节点分配给适当的边缘对并将其存储在列表中。您还可以看到我输入的网络具有正确的出身和结构。

这里会发生什么?我是 Haskell 的新手,我是否使用了错误的列表理解?

【问题讨论】:

  • FWIW,在数据构造函数中存储一对是非常不寻常的。 data Edge = Edge Network Network 会更常见。它们并不完全相同,在几乎所有情况下,您都更喜欢没有额外配对的版本。获取您正在寻找的信息的指针少了一个。
  • 您能给出您提到的简单(a, b) 示例的输入、预期输出和实际输出吗?我不清楚结构是什么样的,我觉得问题出在构建结构的方式上,而不是在此处显示的代码中,因为您根本没有更改此代码中的节点。跨度>
  • Empty 的目的是什么?你没有将它与你的任何函数匹配,一个空列表在代表没有父母或没有孩子方面的表现与[Empty] 一样好。
  • nub 是如何定义的?
  • @KaariLandry 该更改不会解决您遇到的任何问题。这只是一般的 Haskell 风格建议。这就是为什么我把它放在评论中而不是回答 - 它对你的问题没有帮助。

标签: haskell list-comprehension


【解决方案1】:

您的数据结构对于纯语言来说是非常不寻常的,并且将被证明非常难以使用。尽管您要表示的图是非循环的,但您用来表示它的数据结构是循环的:父节点的子节点又是节点。也就是说,当你修改某个东西时,你需要循环修改它:如果我改变了一个节点,我也必须改变相应的孩子的父母,以及它的每个孩子的父母等等。这是非常复杂和微妙的。

这一挑战是纯语言所独有的:更改循环数据结构不是可变数据结构的问题——您只需在一个地方更改它,并且在任何地方引用它都可以“看到”更改。

最好是找到图表的非循环表示。例如:图是边的列表。如果您需要它,您还可以包含一组节点(因此可以表示 0 度节点)。仅通过标签引用节点,当您需要有关它们的信息时在列表中查找它们。

另一种方法是inductive graph (在fgl 库中实现)。

【讨论】:

  • 是的,我一直在通过复制整个网络的更改来更改网络。我真的不知道替代方案是什么,所以我很感激你指出我在如何表示图表的其他方向。我只是跟随我的直觉,并不是一个天生的程序员。
  • @KaariLandry 复制和修改是用像 haskell 这样的纯函数式语言“改变”任何东西的唯一方法。诀窍是设计一个使其变得容易的数据结构。 ;)
  • @KaariLandry 你可能会喜欢How do you represent a graph in Haskell?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-11
  • 1970-01-01
  • 2021-04-01
  • 2019-01-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多