F#二进制搜索树

时间:2018-08-19 19:44:20

标签: f# binary-search-tree

我正在尝试在F#中实现BST。自从我开始使用F#以来,我想寻求帮助。

我有一个简单的测试;

[<Fact>]
let ``Data is retained`` () =
    let treeData = create [4]
    treeData |> data |> should equal 4
    treeData |> left |> should equal None
    treeData |> right |> should equal None

使用区别联合的树类型

type Tree<'T> =
    | Leaf
    | Node of value: 'T * left: Tree<'T> * right: Tree<'T>

将数据节点插入树中的递归函数

let rec insert newValue (targetTree: Tree<'T>) =
    match targetTree with
    | Leaf -> Node(newValue, Leaf, Leaf)
    | Node (value, left, right) when newValue < value ->
        let left' = insert newValue left
        Node(value, left', right)
    | Node (value, left, right) when newValue > value -> 
        let right' = insert newValue right
        Node(value, left, right')
    | _ -> targetTree

现在我在创建功能时遇到问题。我有这个:

let create items = 
    List.fold insert Leaf items

和由此产生的错误:

  

FS0001类型不匹配。期待一个       ''a->树<'a>->'a'但给定一个       ''a-> Tree <'a>-> Tree <'a>'类型'a'和'Tree <'a>'无法统一。

1 个答案:

答案 0 :(得分:4)

List.fold documentation的类型签名显示为:

List.fold : ('State -> 'T -> 'State) -> 'State -> 'T list -> 'State

让我们打开包装。第一个参数是类型'State -> 'T -> 'State的函数。这意味着它需要一个状态和一个T类型的参数,并返回一个新状态。在这里,状态是您的Tree类型:从基本的Leaf开始,您正在逐步构建树。 List.fold的第二个参数是初始状态(在这种情况下为Leaf),第三个参数是要折叠的T类型的项目列表。

您的第二个和第三个参数是正确的,但是您的第一个参数与List.fold期望的签名不一致。 List.fold想要的类型为'State -> 'T -> 'State,在您的情况下为Tree<'a> -> 'a -> Tree<'a>。也就是说,该函数将树作为其 first 参数,并将单个项目作为其 second 参数。但是您的insert函数以相反的方式获取参数(项目作为第一个参数,树作为第二个参数)。

我将在此处暂停说明,根据惯用F#的样式规则,您的insert函数正确,并且您不应更改顺序其参数。在编写用于处理集合的函数时,您始终希望将集合作为最后一个参数,以便可以编写类似tree |> insert 5的内容。因此,我强烈建议您不要更改insert函数采用的参数的顺序。

因此,如果您不应该更改insert函数的参数顺序,但是它们与List.fold的使用顺序不正确,您会怎么做?简单:您可以创建一个匿名函数,其参数会翻转,以便可以将insertList.fold一起使用:

let create items = 
    List.fold (fun tree item -> insert item tree) Leaf items

现在,我们将进一步进行概括。实际上,在F#编程中很常见的是,发现两参数函数的参数在大多数情况下都是正确的方式,而在一个特定的用例中却是错误的方式。为了解决该问题,有时创建一个名为flip的通用函数很有用:

let flip f = fun a b -> f b a

然后,您可以像这样编写create函数:

let create items = 
    List.fold (flip insert) Leaf items

有时使用flip会使代码更多令人困惑,而不是使 less 令人困惑,因此,我不建议您一直使用它。 (这也是为什么F#标准库中没有flip函数的原因:因为它并不总是最好的解决方案。而且由于编写自己很琐碎,因此标准库中的不足并不重要) 。但是有时使用flip会使代码更简单,我认为这是其中一种情况。

P.S。 flip函数也可以这样写:

let flip f a b = f b a

此定义与我在主要示例中使用的let flip f = fun a b -> f b a定义相同。你知道为什么吗?