[英]How to implement a generic hierarchy of structures with injected functionality
我想為樹結構實現一個通用層次結構,以后可以用一種與實現無關的方式來描述樹上的通用算法。
我從這個層次結構開始:
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);
}
現在,基本上我希望能夠以通用的方式實現子樹的概念:對於每個類T:BinaryTree,我想要一個'類'子樹(T),它提供相同的T功能(所以它必須來自它),並重寫root()功能。
像這樣的東西:
class Subtree<T, Node> : T, BinaryTreeWithRoot<Node>
where T : BinaryTree<Node>
{
T reference;
Node root;
void setRoot(Node root) {
this.root = root;
}
override Node BinaryTreeWithRoot<Node>::root() {
return this.root;
}
// Now, inherit all the functionality of T, so an instance of this class can be used anywhere where T can.
forall method(arguments) return reference.method(arguments);
}
現在有了這段代碼,我不知道如何創建一個類型子樹的對象,因為樹對象應該以某種方式注入。
一種方法是為我創建的每個樹類創建一個子樹類,但這意味着代碼重復,畢竟它是同一個東西。
因此,一種方法是mixins,它允許泛型類從其模板參數派生。
我也很感興趣如何在Haskell中實現這樣的層次結構,因為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 ...
我很感興趣是否以及如何在oop語言(c ++,c#,d,java)中完成這項工作,因為c ++和d提供了開箱即用的mixins(我不確定d),並且對Haskell類型系統的好奇心。
由於D具有“真實”模板,而不是泛型,因此使模板類從其模板參數繼承是微不足道的:
class A {}
class B(T) : T {
static assert(is(B!T : T)); // Passes.
}
至於使Subtree
在D中工作,這樣的事情應該這樣做,假設你還有一個模板類Node
:
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
是樹的有效載荷,因此可能是原始的。 如果是這種情況,你最好能夠使用子Subtree!(T)
作為通過alias this
的T
,這允許在沒有繼承的情況下進行子類型化並使用原語:
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;
}
}
在Haskell中創建這樣的樹接口是不尋常的。 Node
和Subtree
都是多余的。 這部分是由於代數類型,部分是因為Haskell數據是不可變的,因此需要不同的技術來完成某些事情(比如設置根節點)。 可以這樣做,界面看起來像:
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 (tree a)
,因此您還可以使用left
和right
來檢查分支是否存在而不是使用單獨的方法。
這沒什么特別的錯,但我不相信我見過有人真正實現過這種方法。 更常用的技術是根據Foldable
和可Traversable
定義遍歷或創建拉鏈 。 拉鏈很容易手動派生,但有幾種通用拉鏈實現,如zipper , pez和syz 。
在C#4中,我將使用動力學來實現這一目標。 例如,您可以嘗試將SubtTree類定義為:
public class Subtree<T, Node> : DynamicObject, BinaryTreeWithRoot<Node> where T : BinaryTree<Node>
{
private readonly T tree;
public Subtree(T tree)
{
this.tree = tree;
}
}
並使用樹的方法/屬性覆蓋DynamicObject的適當方法。 更多信息(和示例代碼)可以在這篇關於使用C#4.0動態的博客文章中找到, 以大大簡化您的私有反射代碼 。
值得一提的是,由於動態功能和反射的使用,將引入較小的性能開銷以及降低安全性(因為它可能涉及違反封裝)。
正如您所指出的,一種方法是為我創建的每個樹類創建一個子樹類,這意味着代碼重復,但它可以使用反射和T4以某種方式“避免”或更好地自動化。 我自己做了一個過去的項目,它運作得很好!
您可以從Oleg Synch博客開始,了解T4的概述。 這是自動生成的類的一個很好的例子: http : //www.olegsych.com/2007/12/how-to-use-t4-to-generate-decorator-classes/
我認為通過“BinaryTree”的方法假設過多的固定結構並且以非通用方式不必要地定義您的接口。 這樣做會使您在樹擴展為非二進制形式時難以重用算法。 相反,如果沒有必要或有用,您將需要為多種樣式編寫接口代碼。
此外,使用hasLeft / hasRight檢查進行編碼意味着每次訪問都是一個兩步過程。 檢查固定位置的存在將不提供有效的算法。 相反,我認為您會發現添加可能是二進制左/右或二進制紅/黑或字符索引或其他任何內容的通用屬性將允許更多地重用您的算法並檢查數據只能由這些算法完成需要它(特定的二進制算法)。
從語義視圖中,您希望首先編碼一些基本屬性,然后進行專門化。 當您“處於”算法內的節點時,您希望能夠首先找到子邊 。 這應該是容器范圍的邊緣結構,允許您導航到子節點。 由於它可以是通用容器,因此它可以包含0,2,5,1或甚至100個邊。 許多算法都不關心。 如果它為0,則迭代范圍將不執行任何操作 - 無需檢查hasX或hasY。 對於每個邊緣,您應該能夠獲得子節點,並遞歸所需的算法。
這基本上是它的圖形庫中的方法提升,它允許將樹算法擴展到適用的圖形,以實現更好的通用算法重用。
所以你已經有了一個基本的界面
TreeNode:
getChildEdges: () -> TreeEdgeRange
TreeEdge:
getChildNode: () -> TreeNode
以及您最喜歡的語言所享有的任何范圍到對象。 例如,D具有特別有用的范圍語法。
您將需要一些基本的Tree對象來為您提供節點。 就像是
Tree:
getTreeNodes: () -> TreeNodeRange
開始你。
現在,如果您想支持BinaryTrees,請將此作為對此接口的限制。 請注意,您實際上並不需要任何新的接口方法,只需要強制執行更多不變量 - 每個TreeNode都有0,1或2個childEdges。 只需創建一個指示此語義限制的接口類型:
BinaryTree : Tree
如果你想支持有根樹,添加一個接口層
RootedTree : Tree:
getRoot: () -> TreeNode
增加了這種能力。
基本思想是,如果要使類在層次結構中更具體,則不必添加接口方法來添加語義要求。 如果存在需要訪問的新語義行為,則僅添加接口方法。 否則 - 在通用接口上強制執行新的不變量。
最終,您需要使用包含節點或邊緣數據的結構來裝飾節點和邊緣,這樣您就可以構建Tries和Red-Black樹以及高級算法的所有優秀工具。 所以你會想擁有
PropertiedTreeNode<Property> : TreeNode:
getProperty: () -> Property
PropertiedTreeEdge<Property> : TreeEdge:
getProperty: () -> Property
由於這是您希望允許通用算法工作的內容,因此屬性是否是Tree的一部分的類型信息應該是通用的,並且算法可以忽略。 這將使您進入boost的設計軌道,這些問題已得到非常優雅的解決。 如果您想了解如何構建通用樹算法庫的想法,我建議您研究該庫。
如果你遵循上面的類型等同於語義描述的指導原則,那么SubTree應該是顯而易見的 - 它與它來自的樹完全相同! 實際上,你真的不應該有SubTree類型。 相反,您應該只有一個您正在處理的特定TreeNode類型的方法
PropertiedTreeNode<Property>:
getSubTree: () -> PropertiedTree<Property>
而且,在boost中,當您在其通用屬性中編碼Tree的更多功能信息時,您可以獲得具有更廣泛接口契約的新Tree類型。
你可以這樣做: -
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
}
子樹只是一棵樹,每棵樹都可以表示為該樹的根節點,然后該節點可以具有啟用樹導航的屬性。
使其成為通用Node<T>
並存儲該值。 如果您不喜歡公共setter,請將它們設為私有,並僅在構造函數或某些安全的AddLeft(...)
等方法中設置它們。
您也可以刪除Root
,只需遍歷Parent
鏈接,直到找到null Parent
值(或到達子樹案例的頂部節點)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.