【问题标题】:F# Binary Search TreeF# 二叉搜索树
【发布时间】:2018-08-20 04:10:35
【问题描述】:

我正在尝试在 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 -> Tree -> 'a' 但给定一个 ''a -> Tree -> Tree' ''a' 和 'Tree' 类型不能统一。

【问题讨论】:

  • 尝试在insert 函数中切换参数顺序。 let rec insert targetTree newValue
  • @Szer - 我不建议在insert 中切换参数顺序:它实际上是 right 顺序已经用于 |&gt; 运算符,如所示我的回答。

标签: f# binary-search-tree


【解决方案1】:

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

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

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

您的第二个和第三个参数是正确的,但您的第一个参数与List.fold 所期望的签名不一致。 List.fold 想要'State -&gt; 'T -&gt; 'State 类型的东西,在你的情况下是Tree&lt;'a&gt; -&gt; 'a -&gt; Tree&lt;'a&gt;。也就是说,一个函数将树作为其 first 参数并将单个项目作为其 second 参数。但是您的 insert 函数采用相反的参数(项目作为第一个参数,树作为第二个参数)。

我会在这里暂停一下,根据惯用 F# 的样式规则,您的 insert 函数正确,您不应该更改其顺序参数。在编写处理集合的函数时,您总是希望将集合作为最后一个参数,以便您可以编写类似tree |&gt; 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 会使代码更多 混乱而不是更少 混乱,所以我不建议一直使用它。 (这也是为什么 F# 标准库中没有 flip 函数的原因:因为它并不总是最好的解决方案。而且因为自己编写很简单,所以标准库中缺少它并不是什么大问题)。但有时使用flip 会使代码更简单,我认为这是其中一种情况。

附: flip 函数也可以这样写:

let flip f a b = f b a

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

【讨论】:

  • 非常感谢您帮助理解所有这些!关于你的问题;我认为第一个翻转函数只是调用带有翻转参数的传递函数。在第二种情况下,我们正在替换函数。它让我想起了 javascript 代码测试,您可以轻松地替换函数而不是编写模拟。
  • 不错的猜测,但两个定义是相同的。在这两种情况下,当您调用flip insert 时,结果是一个接受两个参数ab 的函数,然后调用insert b a。这里发生的事情被称为“currying”,我认为你学习 F# 的下一步可能应该是阅读 fsharpforfunandprofit.com/posts/currying,它比我在 600 个字符的评论中解释得更好。 (然后阅读该站点的其余部分:它是 Internet 上学习 F# 的最佳站点)。
猜你喜欢
  • 1970-01-01
  • 2019-04-12
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 2010-10-26
相关资源
最近更新 更多