F# 漂亮的印花树

F# 漂亮的印花树,f#,pretty-print,F#,Pretty Print,假设我有一个二叉树数据结构,定义如下 type 'a tree = | Node of 'a tree * 'a * 'a tree | Nil 我有一个树的实例,如下所示: let x = Node (Node (Node (Nil,35,Node (Nil,40,Nil)),48,Node (Nil,52,Node (Nil,53,Nil))), 80,Node (Node (Nil,82,Node (Nil,83,Nil)),92,Node (Nil

假设我有一个二叉树数据结构,定义如下

type 'a tree =
    | Node of 'a tree * 'a * 'a tree
    | Nil
我有一个树的实例,如下所示:

let x =
  Node
    (Node (Node (Nil,35,Node (Nil,40,Nil)),48,Node (Nil,52,Node (Nil,53,Nil))),
     80,Node (Node (Nil,82,Node (Nil,83,Nil)),92,Node (Nil,98,Nil)))
我正试图把这棵树打印成易于理解的文字。最好是在控制台窗口中打印树,如下所示:

        _______ 80 _______
       /                  \
    _ 48 _              _ 92 _
   /      \            /      \
 35       52         82       98
   \       \        /
    40      53    83

让我的树以那种格式输出的简单方法是什么?

如果你不介意把头转向一边,你可以先打印树的深度,一个节点到一行,递归地向下传递树的深度,然后在节点之前的行上打印
depth*N
空格

以下是Lua代码:

tree={{{nil,35,{nil,40,nil}},48,{nil,52,{nil,53,nil}}},
      80,{{nil,82,{nil,83,nil}},92 {nil,98,nil}}}

function pptree (t,depth) 
  if t ~= nil
  then pptree(t[3], depth+1)
    print(string.format("%s%d",string.rep("  ",depth), t[2]))
    pptree(t[1], depth+1)
  end
end
测试:


虽然输出不完全正确,但我在以下位置找到了答案:


如果您希望它非常漂亮,可以从中窃取大约25行代码,然后使用WPF绘制它

但我很快也会编写一个ascii解决方案

编辑

好的,哇,那很难

我不确定它是否完全正确,我忍不住认为可能有更好的抽象。但无论如何。。。享受吧

(请参见代码末尾的一个相当漂亮的大型示例。)

键入“a树”=
|“a tree*”a*”a tree的节点
|零
(*
对于任何给定的树
ddd
/ \
lll rrr
我们把它看作这三个部分,左|中|右(L | M | R):
d | d | d
/ |   | \
lll | | rrr
M总是正好是一个字符。
L将与(d的宽度/2)或L的宽度一样宽,以较大者为准(且始终至少一个)
R的宽度为((d的宽度-1)/2)或R的宽度,以较大者为准(且始终至少为一个)
(以上两行表示等长的“dddd”稍微偏离左中心)
我们希望“/”直接出现在直接左边子级最右边的字符上方。
我们希望“\”直接出现在右直子级最左边的字符上方。
如果“ddd”的宽度不够长,无法在斜杠的1个字符范围内达到,则使用
在该侧下划线字符,直到其足够宽。
*)
//PrettyAndWidthInfo:'a tree->string[]*int*int*int
//字符串的宽度都相同(如果需要,可以填充空格)
//第一个int是总宽度
//第二个int是根节点开始的列
//第三个int是根节点结束的列
//(假设d.ToString()从不返回空字符串)
让rec pretty和widthInfo t=
匹配
|零->
[], 0, 0, 0
|节点(零,d,零)->
设s=d.ToString()
[s] ,s.长度,0,s.长度-1
|节点(l、d、r)->
//计算此节点数据字符串的信息
设s=d.ToString()
设sw=s.长度
设swl=sw/2
设swr=(sw-1)/2
断言(swl+1+swr=sw)
//重现
设lp,lw,u,lc=pretty和widthInfo l
设rp,rw,rc,u=pretty和widthInfo r
//不存在子树的原因
设lw,lb=如果lw=0,则1“,否则lw,/”
设rw,rb=如果rw=0,则1“,否则rw,\\”
//计算这棵树的全宽
设totaleftwidth=(最大(最大lw swl)1)
设totalRightWidth=(最大(最大rw swr)1)
设w=totaleftwidth+1+totalRightWidth
(*
一个具有启发性的例子:
dddd | d | dddd__
/ |   |       \
lll | | rr
|   |      ...
|| rrrrrrrrrrr
--------swl,swr(任何填充之前的左/右字符串宽度(此节点的))
-----------lw,rw(任何填充之前(子树的)左/右宽度)
----总宽度
-----------总宽度
-------------w(总宽度)
*)
//获取占左侧的右列信息
设rc2=totaleftwidth+1+rc
//使左树和右树的高度相同
设lp=如果lp.Length“”)else lp
设rp=如果rp.Length“”)否则rp
//如有必要,加宽左树和右树(如果父节点较宽,还可以固定“添加的高度”)
设lp=lp |>List.map(fun s->if s.LengthList.map(乐趣s->如果s.Lengthline1.长度,则
测线1+(NBAR(rc2-测线1.长度))
其他的
第1行
//第1行右填充
设line1=line1+(n面(w-line1.长度))
//第2行的第一部分
设第2行=(nSpaces(总有效宽度-lw+lc))+lb
//垫上左半部的其余部分
设line2=line2+(n面(总宽度-line2.长度))
//添加正确的内容
设line2=line2++(nSpaces rc)+rb
//添加正确的填充
设line2=line2+(n面(w-line2.长度))
让resultLines=line1::line2:((lp,rp)| |>List.map2(funlr->l+“”+r))
对于结果行中的x,请执行以下操作:
断言(x.Length=w)
结果线,w,lw swl,总宽度+1+swr
和nSpaces n=
String.n“”
和nBars n=
String.n“\u0”
让我们预先打印t=
设sl,u,u,u=pretty和widthinfo t
对于sl中的s,请执行以下操作:
printfn“%s”s
设y=Node(Node(Node(Nil,35,Node)(Node(Nil,1,Nil),8888888,Nil)),48,Node(Nil,777,Node(Nil,53,Nil)),
80,节点(节点(Nil,82,节点(Nil,83,Nil)),1111111,节点(Nil,98,Nil)))
设z=Node(y,55555,y)
设x=Node(z,4444,y)
预印x
(*
__________________
> pptree(tree,4)
        98
      92
          83
        82
    80
          53
        52
      48
          40
        35
> 
(* A string representation of binary trees

Somebody represents binary trees as strings of the following type (see example opposite):

a(b(d,e),c(,f(g,)))

a) Write a Prolog predicate which generates this string representation, if the tree 
is given as usual (as nil or t(X,L,R) term). Then write a predicate which does this 
inverse; i.e. given the string representation, construct the tree in the usual form. 
Finally, combine the two predicates in a single predicate tree_string/2 which can be 
used in both directions.

b) Write the same predicate tree_string/2 using difference lists and a single 
predicate tree_dlist/2 which does the conversion between a tree and a difference 
list in both directions.

For simplicity, suppose the information in the nodes is a single letter and there are 
no spaces in the string. 
*)

type bin_tree = 
    Leaf of string
|   Node of string * bin_tree * bin_tree
;;

let rec tree_to_string t =
    match t with
            Leaf s -> s
    |       Node (s,tl,tr) -> 
                    String.concat "" 
                            [s;"(";tree_to_string tl;",";tree_to_string tr;")"]
;;
type 'a tree =    
    | Node of 'a tree * 'a * 'a tree
    | Nil

(*
For any given tree
     ddd
     / \
   lll rrr  
we think about it as these three sections, left|middle|right (L|M|R):
     d | d | d
     / |   | \
   lll |   | rrr  
M is always exactly one character.
L will be as wide as either (d's width / 2) or L's width, whichever is more (and always at least one)
R will be as wide as either ((d's width - 1) / 2) or R's width, whichever is more (and always at least one)
     (above two lines mean 'dddd' of even length is slightly off-center left)
We want the '/' to appear directly above the rightmost character of the direct left child.
We want the '\' to appear directly above the leftmost character of the direct right child.
If the width of 'ddd' is not long enough to reach within 1 character of the slashes, we widen 'ddd' with
    underscore characters on that side until it is wide enough.
*)

// PrettyAndWidthInfo : 'a tree -> string[] * int * int * int
// strings are all the same width (space padded if needed)
// first int is that total width
// second int is the column the root node starts in
// third int is the column the root node ends in
// (assumes d.ToString() never returns empty string)
let rec PrettyAndWidthInfo t =
    match t with
    | Nil -> 
        [], 0, 0, 0
    | Node(Nil,d,Nil) -> 
        let s = d.ToString()
        [s], s.Length, 0, s.Length-1
    | Node(l,d,r) ->
        // compute info for string of this node's data
        let s = d.ToString()
        let sw = s.Length
        let swl = sw/2
        let swr = (sw-1)/2
        assert(swl+1+swr = sw)  
        // recurse
        let lp,lw,_,lc = PrettyAndWidthInfo l
        let rp,rw,rc,_ = PrettyAndWidthInfo r
        // account for absent subtrees
        let lw,lb = if lw=0 then 1," " else lw,"/"
        let rw,rb = if rw=0 then 1," " else rw,"\\"
        // compute full width of this tree
        let totalLeftWidth = (max (max lw swl) 1)
        let totalRightWidth = (max (max rw swr) 1)
        let w = totalLeftWidth + 1 + totalRightWidth
(*
A suggestive example:
     dddd | d | dddd__
        / |   |       \
      lll |   |       rr
          |   |      ...
          |   | rrrrrrrrrrr
     ----       ----           swl, swr (left/right string width (of this node) before any padding)
      ---       -----------    lw, rw   (left/right width (of subtree) before any padding)
     ----                      totalLeftWidth
                -----------    totalRightWidth
     ----   -   -----------    w (total width)
*)
        // get right column info that accounts for left side
        let rc2 = totalLeftWidth + 1 + rc
        // make left and right tree same height        
        let lp = if lp.Length < rp.Length then lp @ List.init (rp.Length-lp.Length) (fun _ -> "") else lp
        let rp = if rp.Length < lp.Length then rp @ List.init (lp.Length-rp.Length) (fun _ -> "") else rp
        // widen left and right trees if necessary (in case parent node is wider, and also to fix the 'added height')
        let lp = lp |> List.map (fun s -> if s.Length < totalLeftWidth then (nSpaces (totalLeftWidth - s.Length)) + s else s)
        let rp = rp |> List.map (fun s -> if s.Length < totalRightWidth then s + (nSpaces (totalRightWidth - s.Length)) else s)
        // first part of line1
        let line1 =
            if swl < lw - lc - 1 then
                (nSpaces (lc + 1)) + (nBars (lw - lc - swl)) + s
            else
                (nSpaces (totalLeftWidth - swl)) + s
        // line1 right bars
        let line1 =
            if rc2 > line1.Length then
                line1 + (nBars (rc2 - line1.Length))
            else
                line1
        // line1 right padding
        let line1 = line1 + (nSpaces (w - line1.Length))
        // first part of line2
        let line2 = (nSpaces (totalLeftWidth - lw + lc)) + lb 
        // pad rest of left half
        let line2 = line2 + (nSpaces (totalLeftWidth - line2.Length))
        // add right content
        let line2 = line2 + " " + (nSpaces rc) + rb
        // add right padding
        let line2 = line2 + (nSpaces (w - line2.Length))
        let resultLines = line1 :: line2 :: ((lp,rp) ||> List.map2 (fun l r -> l + " " + r))
        for x in resultLines do
            assert(x.Length = w)
        resultLines, w, lw-swl, totalLeftWidth+1+swr
and nSpaces n = 
    String.replicate n " "
and nBars n = 
    String.replicate n "_"

let PrettyPrint t =
    let sl,_,_,_ = PrettyAndWidthInfo t
    for s in sl do
        printfn "%s" s

let y = Node(Node (Node (Nil,35,Node (Node(Nil,1,Nil),88888888,Nil)),48,Node (Nil,777777777,Node (Nil,53,Nil))),     
             80,Node (Node (Nil,82,Node (Nil,83,Nil)),1111111111,Node (Nil,98,Nil)))
let z = Node(y,55555,y)
let x = Node(z,4444,y)

PrettyPrint x
(*
                                   ___________________________4444_________________
                                  /                                                \
                      ________55555________________                         ________80
                     /                             \                       /         \
            ________80                      ________80             _______48         1111111111
           /         \                     /         \            /        \            /  \
   _______48         1111111111    _______48         1111111111 35         777777777  82   98
  /        \            /  \      /        \            /  \      \             \       \
35         777777777  82   98   35         777777777  82   98     88888888      53      83
  \             \       \         \             \       \            /
  88888888      53      83        88888888      53      83           1
     /                               /
     1                               1
*)     
                    _______ 80 _______
                   /                  \
                _ 48 _              _ 92 _
               /      \            /      \
             35       52         82       98
               \       \        /
                40      53    83
    
    
             35 40 48   52 53 80 83 82    92    98   
           0 1  2  3  4  5  6  7  8  9 10 11 12 13 14 
                              80
    
    then (L/2) - (L/4)  and  (L/2) + (L/4) 
    
                   48                    92
    then L/2-L/4-L/8, L/2-L/4+L/8, L/2+L/4-L/8 and L/2+L/4+L/8 
    
              35        52         82          98
    
    ...
N 35 40 48 N 52 53 80 83 82 N 92 N 98 N 
                   80
        48                    92
  35         52          82        98
     40         53    83