如何在Haskell中表示两棵树之间的映射?

时间:2019-04-14 21:18:56

标签: algorithm haskell tree mapping abstract-syntax-tree

我正在尝试在Haskell中实现树处理算法,并且(由于这是我的第一个Haskell程序!)正在努力设计数据结构。那里的任何FP专家都可以伸出援手吗?

首先,我将描述算法的重要特征,勾勒出如何使用命令式语言来实现这一点,最后完成我到目前为止在Haskell中遇到的绊脚石。

问题

我不会详细描述完整的算法,但是要点如下:

  • 该算法在X和Y两棵玫瑰树上运行。
  • 该算法的第一阶段根据其节点和后代的标签和属性为每个节点计算一些派生属性。
  • 这些导出的属性用于计算两棵树之间的部分映射,以使X中的节点可以与Y中的节点关联,反之亦然。由于映射是部分映射,因此X或Y中的任何节点都可以被映射(即,另一棵树有一个伙伴),或者可以不映射。
  • 算法的最后阶段通过检查被映射节点的父/子/兄弟姐妹的一系列操作来优化这些映射。

因此,数据结构必须具有以下特征:

  • 给出对节点的引用,提供对该节点的父级,该节点的同级以及该节点的子级的访问。
  • 给出输入树中的一个节点,允许对该节点进行注释并带有附加信息(派生的属性以及对另一棵树中一个节点的可选引用)。

势在必行的解决方案

如果我要使用命令式语言实现此算法,则解决方案应类似于以下内容。

让我们假设起点是输入树的以下定义:

struct node {
    // Identifier for this node, unique within the containing tree
    size_t id;

    // Label of this node
    enum label label;

    // Attributes of this node
    // An attribute can be assumed to be a key-value pair
    // Details of the attributes themselves aren't material to this
    // discussion, so the "attribute" type is left opaque
    struct attribute **attributes;
    size_t n_attributes;

    // Pointer to parent of this node
    // NULL iff this node is root
    struct node *parent;

    // Pointer to first child of this node
    // NULL iff this node is leaf
    struct node *child;

    // Double-linked list of siblings of this node
    struct node *prev;
    struct node *next;
};

每个节点中嵌入的指针显然支持算法所需的向上/向下/向左/向右遍历。

可以通过定义以下结构来实现注释:

struct algo_node {
    // Pointer to input node which has been wrapped
    struct node *node;

    // Derived properties computed by first phase of the algorithm
    // Details of the properties themselves aren't material to this
    // discussion, so the "derived" type is left opaque
    struct derived props;

    // Pointer to corresponding node in the other tree
    // NULL iff this node is unmatched
    struct node *match;
};

算法的第一阶段为每个输入树中的每个algo_node构造一个node

algo_node映射到node很简单:遵循嵌入的*node指针。通过将algo_node存储在由输入节点的id索引的数组中,可以支持另一个方向的映射。

这当然只是一种可能的实现。可能有多种变化,包括

  • listqueue接口后面提取子链表,而不是存储三个原始指针
  • 与其直接通过索引将输入树与算法树相关联,不如直接在struct algo_node中编码父/子/同级关系

移至Haskell

让我们从输入树的以下定义开始:

data Tree = Leaf Label Attributes
          | Node Label Attributes [Tree]

每个具有id的节点的增强可以通过以下方式实现:

data AnnotatedTree = Tree Int

addIndex :: Int -> AnnotatedTree -> (AnnotatedTree, Int)

indexedTree = addIndex 0 tree

类似地,我们可以编写一个计算派生属性的函数:

data AnnotatedTree = Tree DerivedProperties

computeDerived :: DerivedProperties -> AnnotatedTree -> (AnnotatedTree, DerivedProperties)

derivedTree = computeDerived DefaultDerived tree

上面的代码片段几乎不需要任何调整,因此AnnotatedTree既包含索引又包含派生属性。

但是,我不知道从哪里开始代表两棵树之间的映射。根据一些阅读,我有一些半生半熟的想法...

  • 定义AnnotatedTree以包含从另一棵树的根到映射节点的路径-编码为每个后续子列表[Integer]的索引列表
    • 使用一个拉链(我目前对此还不太了解)通过路径访问映射的节点(或其父/子/兄弟姐妹)
    • 或者使用镜头(...我对它的理解还不够清楚!)进行同样的操作
  • 定义AnnotatedTree以直接包含对映射节点的引用,例如为Maybe Tree
    • 但是然后我看不到步行到映射节点的父级/同级的一种方法

...但是我真的可以就其中哪些(如果有的话)值得追求的问题做一些指导。

任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:0)

您可以使用Int id标记树节点,并用拉链四处走动(使用Data.TreeData.Tree.Zipper是个好主意,因为无需重新发明轮子。)然后,您可以使用Data.IntMap将辅助属性附加到节点,以将节点ID映射到所需的任何内容。特别是,您可以创建一个IntMap来从该节点的节点ID映射到TreePos Full Int,以便您可以探索该节点的父级,同级和子级。