C# 如何使用注入的功能实现结构的通用层次结构

C# 如何使用注入的功能实现结构的通用层次结构,c#,c++,haskell,d,C#,C++,Haskell,D,我想为树结构实现一个通用层次结构,以后可以用一种独立于实现的方式来描述树上的通用算法 我从这个层次结构开始: interface BinaryTree<Node> { Node left(Node); bool hasLeft(Node); Node right(Node); bool hasRight(Node); } interface BinaryTreeWithRoot<Node> : BinaryTree<Node>

我想为树结构实现一个通用层次结构,以后可以用一种独立于实现的方式来描述树上的通用算法

我从这个层次结构开始:

interface BinaryTree<Node> {
    Node left(Node);
    bool hasLeft(Node);

    Node right(Node);
    bool hasRight(Node);
}

interface BinaryTreeWithRoot<Node> : BinaryTree<Node> {
    Node root();
}

interface BinaryTreeWithParent<Node> : BinaryTree<Node> {
    Node parent(Node);
    bool hasParent(Node);
}
我感兴趣的是,这是如何做到的,以及如何在OOP语言(C++,C++,D,java)中完成,因为C++和D提供了混入框(我不确定D),并且出于Haskell类型系统的好奇。
public class Node {
    public Node Left {get; set:}
    public Node Right {get; set;}
    public Node Parent {get; set;}  // if you want to be able to go up the tree
    public Node Root {get; set;}    // only if you want a direct link to root
}
一个子树就是一棵树,每棵树都可以表示为该树的根节点,然后该节点可以具有允许在树中导航的属性

将其设为通用
节点
,并存储该值。如果您不喜欢公共setter,请将它们设置为私有,并仅在构造函数或某些安全的
AddLeft(…)
等方法中设置它们


您也可以去掉
Root
,只需遍历
Parent
链接,直到找到空的
Parent
值(或到达子树大小写的顶部节点)。

正如您所指出的,一种方法是为我创建的每个树类创建子树类, 这意味着代码重复,但它可以通过反射和T4以某种方式“避免”,或者更好地实现自动化。我在过去的一个项目中自己做的,效果很好

您可以从Oleg Synch博客开始了解T4的概述。这里有一个自动生成类的好例子:

在C#4中,我将使用动力学来实现这一目标。例如,您可以尝试将子树类定义为:

public class Subtree<T, Node> : DynamicObject, BinaryTreeWithRoot<Node> where T : BinaryTree<Node>
{
    private readonly T tree;

    public Subtree(T tree)
    {
        this.tree = tree;
    }
}
公共类子树:DynamicObject,BinaryTreeWithRoot其中T:BinaryTree
{
私有只读T树;
公共子树(T树)
{
this.tree=树;
}
}
并使用树的方法/属性重写DynamicObject的适当方法。更多信息(和示例代码)可以在这篇关于的博文中找到


值得一提的是,由于使用了动态功能和反射,将引入较小的性能开销并降低安全性(因为它可能涉及违反封装)。

在Haskell中创建这样的树接口是。。。不寻常。
节点
子树
都是多余的。这部分是由于代数类型,部分是因为Haskell数据是不可变的,因此需要不同的技术来完成某些事情(例如设置根节点)。如果可以这样做,界面将类似于:

class BinaryTree tree node where
    left :: tree -> node -> node
    right :: tree -> node -> node

class BinaryTreeWithRoot node where
    left :: tree -> node -> node
    right :: tree -> node -> node -- but this is a duplication of the code of BinaryTree
    root :: tree -> node

instance BinaryTree (BinaryTreeWithRoot node) where
    left = left
    right = right

data (BinaryTree tree node) => Subtree tree node = 
 ...

instance BinaryTreeWithRoot (Subtree tree node) where ...
class BinaryTree tree where
    left :: tree a -> Maybe (tree a)
    right :: tree a -> Maybe (tree a)

-- BinaryTreeWithRoot inherits the BinaryTree interface
class BinaryTree tree => BinaryTreeWithRoot tree where
    root :: tree a -> tree a
然后,使用一个非常标准的二叉树定义:

data Tree a =
  Leaf
  | Branch a (Tree a) (Tree a)

instance BinaryTree Tree where
  left Leaf = Nothing
  left (Branch _ l r) = Just l
  right Leaf = Nothing
  right (Branch _ l r) = Just r

data TreeWithRoot a =
  LeafR (TreeWithRoot a)
  | BranchR a (TreeWithRoot a) (TreeWithRoot a) (TreeWithRoot a)

instance BinaryTree TreeWithRoot where
-- BinaryTree definitions omitted

instance BinaryTreeWithRoot TreeWithRoot where
  root (LeafR rt) = rt
  root (BranchR _ rt l r) = rt
由于此接口返回一个
Maybe(树a)
,因此您还可以使用
left
right
来检查分支是否存在,而不是使用单独的方法

它并没有什么特别的问题,但我相信我从未见过任何人真正实现这种方法。更常用的技术是根据
Foldable
Traversable
定义遍历,或者创建一个。zipper很容易手动派生,但是有几种通用的zipper实现,例如,和。

由于D有“真实”的模板,而不是泛型,因此使模板类从其模板参数继承很简单:

class A {}
class B(T) : T {
    static assert(is(B!T : T));  // Passes.
}
假设您还有一个模板类
节点
,那么让
子树
在D中工作就可以了:

class Subtree(T) : T, BinaryTreeWithRoot!(Node!(T))
{
    T reference;
    Node root;

    void setRoot(Node root) {
        this.root = root;
    }

    override Node root() {
        return this.root;
    }
}
但是,IIUC(如果我错了请纠正我),
T
是树的有效负载,因此可以是原语。如果是这样的话,你最好能使用
子树!(T) 
作为一个
T
via,它允许在不继承的情况下进行子类型化,并与原语一起工作:

class Subtree(T) : BinaryTreeWithRoot!(Node!(T))
{
    T reference;
    alias reference this;  // Make this implicitly convertible to reference.
    Node root;

    void setRoot(Node root) {
        this.root = root;
    }

    override Node root() {
        return this.root;
    }
}

我认为通过“BinaryTree”的方法假设了太多的固定结构,并且不必要地以非通用的方式定义了接口。当树扩展为非二进制形式时,这样做会使重用算法变得困难。相反,如果没有必要或没有用处,则需要为多个样式的接口编写代码

此外,使用hasLeft/hasRight检查编码意味着每次访问都是一个两步过程。检查固定位置的存在不会提供有效的算法。相反,我认为您会发现,添加一个通用属性(可能是二进制左/右、二进制红/黑、字符索引或其他)将允许更多地重用您的算法,并检查数据是否只能由那些需要它的算法(特定的二进制算法)来完成

从语义的角度来看,您需要先对一些基本属性进行编码,然后再进行专门化。当您“在”算法中的节点时,您希望能够首先找到子边。这应该是允许您导航到子节点的边缘结构的容器范围。因为它可以是一个通用容器,所以它可以有0、2、5、1甚至100条边。许多算法并不在意。如果它有0,则在该范围内迭代将不起任何作用-无需检查hasX或hasY。对于每条边,您应该能够获得子节点的节点,并根据您希望的算法进行递归

这基本上是boost在其图形库中采用的方法,它允许将树算法扩展到适用的图形,以便更好地重用通用算法

所以你已经有了一个基本的界面

TreeNode:
  getChildEdges: () -> TreeEdgeRange

TreeEdge:
  getChildNode: () -> TreeNode
不管你最喜欢的语言喜欢什么范围。例如,D有一个特别有用的范围语法

您需要一些基本的树对象来为您提供节点。差不多

Tree:
  getTreeNodes: () -> TreeNodeRange
让你开始

现在,如果您想支持二进制树,请将其作为对该接口的限制。请注意,您实际上不需要任何新的接口方法,只需要强制执行更多的不变量,即每个树节点都有0、1或2个子边。只需创建一个
Tree:
  getTreeNodes: () -> TreeNodeRange
BinaryTree : Tree
RootedTree : Tree:
  getRoot: () -> TreeNode
PropertiedTreeNode<Property> : TreeNode:
  getProperty: () -> Property

PropertiedTreeEdge<Property> : TreeEdge:
  getProperty: () -> Property
PropertiedTreeNode<Property>:
  getSubTree: () -> PropertiedTree<Property>