简体   繁体   English

如何在Haskell的树中找到给定x的先前值?

[英]How to find a previous value to a given x in trees in Haskell?

The task is to find a previous value to the given x in a tree in Haskell. 任务是在Haskell的树中找到给定x的先前值。 IMPORTANT! 重要! Using Lists in any part is not allowed: 不允许在任何部分使用列表:

data SearchTree a = Leaf a | Node a (SearchTree a) (SearchTree a)

t1 = Node 5 ( Node 4 ( Leaf 1) 
                     ( Node 5 (Leaf 4) (Leaf 5))
            ) 
            ( Node 7 ( Node 6  ( Leaf 5 ) ( Leaf 7  )) 
                     ( Node 10 ( Leaf 9 ) ( Leaf 11 )) 
            )

previousVal :: Ord a => a -> (SearchTree a) -> a
previousVal x t = previousValHelp x t 

previousValHelp x (Leaf n) = error "Es gibt nichts kleineres"
previousValHelp x (Node n l r) = (max n (max (previousValHelp x l) (previousValHelp x r))) < x 

For example previousVal 9 t1 should give 7 out. 例如, previousVal 9 t1应该给出7 I managed to make this code, but it's wrong... Does anyone here have an idea? 我设法编写了这段代码,但这是错误的……这里有人有想法吗?

Thank you! 谢谢!

Haskell is about types. Haskell是关于类型的。 So it's best to start with the type signature of a function instead of the definition. 因此,最好从函数的类型签名而不是定义开始。 And it also makes sense to let the type checker tell you what's wrong. 让类型检查器告诉您什么地方也很有意义。 It often leads to the solution. 它通常导致解决方案。

So, I tried to compile your code. 因此,我尝试编译您的代码。 Compiler output can sometimes be a bit confusing, but that's something everyone has to go through. 编译器输出有时可能会有些混乱,但这是每个人都必须经历的事情。 I cheated a little to make the output more useful by changing previousVal xt = previousValHelp xt to previousVal = previousValHelp (that is called eta reduction , but you may just ignore it if it's confusing for you). 我作了一点欺骗,通过将previousVal xt = previousValHelp xt更改为previousVal = previousValHelp (这称为eta reduction ,但是如果您感到困惑,则可以忽略它)来使输出更有用。

Here is the output: 这是输出:

SearchTree.hs:11:15:
    Could not deduce (a ~ Bool)
    from the context (Ord a)
      bound by the type signature for
                 previousVal :: Ord a => a -> SearchTree a -> a
      at SearchTree.hs:10:16-48
      ‘a’ is a rigid type variable bound by
          the type signature for
            previousVal :: Ord a => a -> SearchTree a -> a
          at SearchTree.hs:10:16
    Expected type: a -> SearchTree a -> a
      Actual type: Bool -> SearchTree Bool -> Bool
    Relevant bindings include
      previousVal :: a -> SearchTree a -> a (bound at SearchTree.hs:11:1)
    In the expression: previousValHelp
    In an equation for ‘previousVal’: previousVal = previousValHelp

See how it tells you what type it expected of previousValHelp , but what it got instead: 看看它如何告诉您对previousValHelp期望的类型,但是却得到了什么:

Expected type: a -> SearchTree a -> a
  Actual type: Bool -> SearchTree Bool -> Bool

This type error shows also the logical error. 此类型错误也显示逻辑错误。 The definition of previousValHelp is basically a foo < bar (just ignore the brackets and the recursion for a second). previousValHelp的定义基本上是foo < bar (只需忽略方括号和递归一秒钟)。 Mind that the above two lines do not show up in your initial version of the code... so try to figure out the meaning of the very first few lines of the whole compiler output as well. 请注意,以上两行代码不会显示在您的代码的初始版本中……因此,请尝试弄清整个编译器输出的前几行的含义。

Anyway, let's check now what type (<) has in ghci ( < is the infix version of (<) ): 无论如何,让我们现在检查ghci中的类型(<)<(<)的中版本):

Prelude> :t (<)
(<) :: Ord a => a -> a -> Bool

So it returns a value of type Bool (if given those two a arguments). 因此,它返回一个类型为布尔值(如果给这两个a参数)。 Makes sense. 说得通。

Remember how I told you to ignore the recursion and brackets for a second. 记住我是如何告诉您忽略递归和方括号的。 Now let's figure out why not just the "return type" of your function is Bool, but the rest as well. 现在让我们弄清楚为什么不仅函数的“返回类型”是Bool,其余的也是。 Let's have a look: 我们来看一下:

previousValHelp x (Node n l r) =
  max n (max (previousValHelp x l) (previousValHelp x r)) < x

This might get a bit tricky. 这可能会有些棘手。 Since we already know that previousValHelp is a function that returns a Bool if given two parameters, we can deduce that the recursive calls (previousValHelp xl) and (previousValHelp xr) also return a Bool. 由于我们已经知道previousValHelp是一个在给定两个参数的情况下返回Bool的函数,因此我们可以推断出递归调用(previousValHelp xl)(previousValHelp xr)也会返回Bool。 That means we are running the inner function max (previousValHelp xl) (previousValHelp xr) with two Bool parameters and get back a Bool. 这意味着我们正在使用两个Bool参数运行内部函数max (previousValHelp xl) (previousValHelp xr) ,并返回一个Bool。 See the type signature: 请参阅类型签名:

max :: Ord a => a -> a -> a

Now we have the outer function max n (max (...) (...)) as well... we already know the type of it's second parameter which is Bool (see above). 现在我们还有外部函数max n (max (...) (...)) ...我们已经知道第二个参数的类型是Bool(请参见上文)。 Since the types must be consistent ( max True True works, but not max True 1 ) the first parameter n is assumed to be Bool as well. 由于类型必须一致( max True True有效,但max True 1 ),因此第一个参数n也假定为Bool。 Aha. 啊哈。 Check your definition of SearchTree... n is the type parameter of our SearchTree and as such we get SearchTree Bool ! 检查您对SearchTree的定义... n是我们的SearchTree的类型参数,因此我们得到SearchTree Bool Similarly, the parameters for (<) must be consistent as well. 同样, (<)的参数也必须一致。 True < False works, while True < 1 does not. True < False有效,而True < 1无效。 So the last step is to figure out the type of x. 因此,最后一步是找出x的类型。 We already know that the left-side parameter of < is Bool... so x must also be Bool which is our first function parameter. 我们已经知道<的左侧参数是Bool ...,因此x也必须是Bool,这是我们的第一个函数参数。

My guess is that you probably want to use (<) in a way that lets you choose what you want to do... instead of just returning the result. 我的猜测是,您可能希望以一种允许您选择要执行的操作的方式使用(<) ,而不是仅仅返回结果。 For that purpose, there are guards . 为此,有警卫

Again: for a language like haskell which has such a strong focus on types... it's really crucial to learn how to read type/compiler errors. 再次:对于像haskell这样的语言,它非常注重类型……学习如何读取类型/编译器错误真的很关键。

Note: in the below I assume that "previous value to x" means "the greatest value in the tree less than x" and that a SearchTree is a Binary Search Tree (ie. sorted). 注意:在下面,我假设“ x的先前值”表示“树中小于x的SearchTree ”,并且SearchTree是二进制搜索树( SearchTree排序)。

Let's first have a look at why your code isn't working how you want it to. 首先让我们看一下为什么您的代码无法按您希望的那样工作。

One of the great things that Haskell gives us is expressive types, along with type inference, so let's see what type it thinks your previousValHelp function has: Haskell给我们带来的伟大事情之一就是表达类型以及类型推断,因此让我们看看它认为您previousValHelp函数具有哪种类型:

Prelude> :t previousValHelp
previousValHelp :: Bool -> SearchTree Bool -> Bool

Ah! 啊! There's a problem! 有问题! Our implementation of previousValHelp only works for SearchTree Bool ! 我们previousValHelpSearchTree Bool实现仅适用于SearchTree Bool That's not right, though, we want it to work for all a which fit the constraint Ord a . 这是不对的,但是,我们希望它为所有工作的a ,其适合约束Ord a Try to work out why this is. 尝试找出原因。 (I'm not going to explain it here, but try working through the types to see why the SearchTree must be a SearchTree Bool .) (我在这里不做解释,而是尝试研究这些类型,以了解为什么SearchTree必须是SearchTree Bool 。)

Let's start re-writing our previousVal function. 让我们开始重新编写我们的previousVal函数。 We'll start with a type signature: 我们将从类型签名开始:

previousVal :: Ord a => a -> (SearchTree a) -> Maybe a

This says "assuming we can compare objects of some type a , we're going to define a function which takes an a , and a SearchTree containing a s, and returns to us either an a (wrapped in Just ), or Nothing ". 这是说“假设我们可以比较的一些类型的对象a ,我们要定义一个函数,它接受的a ,和SearchTree包含a S,并返回到我们的任何a (包裹在Just ),或Nothing ”。 (We're going to use a Maybe a instead of error , because then we can use the type system to make sure our program doesn't crash by forcing us to explicitly handle the Nothing result. It also makes the implementation of the recursive function much easier.) (我们将使用Maybe a代替error ,因为然后我们可以使用类型系统通过强制我们显式处理Nothing结果来确保程序不会崩溃。这也使递归函数得以实现容易得多。)

Now, we know a SearchTree has two possible "shapes": either a Node or a Leaf . 现在,我们知道SearchTree具有两个可能的“形状”: NodeLeaf We'll have to implement a case for both of them. 我们必须为它们两个都实现一个案例。

previousVal x (Node v l r) = _
previousVal x (Leaf v) = _

Let's start with the Node case. 让我们从Node案例开始。 There are two possible cases here: if the value at this Node is less than the value we're looking for, then we want to go down the right subtree ( r ), otherwise we want to go down the left subtree ( l ). 这里有两种可能的情况:如果此Node上的值小于我们要寻找的值,那么我们想沿着右边的子树( r )下来,否则我们想要沿着左边的子树( l )下来。

previousVal x (Node v l r) | v < x = previousVal x r
                           | otherwise = previousVal x l

For Leaf s the logic is even easier: if the value at the Leaf is less than the value we're looking for then we can return the value (because there's nothing else below us that could be "closer" to it than where we are currently). 对于Leaf ,逻辑甚至更容易:如果Leaf上的值小于我们正在寻找的值,那么我们可以返回该值(因为在我们下面没有比它更“接近”的东西了目前)。 Otherwise, we just return Nothing . 否则,我们只返回Nothing

previousVal x (Leaf v) | v < x = Just v
                       | otherwise = Nothing

Now, if you run the code we've written up until this point, you should find some cases where it works, and some where it fails. 现在,如果运行到此为止我们编写的代码,则应该找到一些可行的情况以及失败的情况。 For example: 例如:

Prelude> previousVal 5 t1
Just 4
Prelude> previousVal 9 t1
Nothing

Why does it seem to work some times, but not others? 为什么它有时似乎起作用,而另一些却不起作用? Well, it turns out we've actually got a bug in our Node case above. 好吧,事实证明,我们上面的Node案例实际上存在一个错误。 See, when we go down the right-hand branch there the possibility that there won't be any values in that subtree which are less than x . 看到,当我们沿着右手分支前进时,该子树中不会存在小于x任何值。 In that case, the right hand tree will return a Nothing back to us. 在这种情况下,右侧的树将向我们返回Nothing But if the right hand subtree returns a Nothing , then the Node we're currently at contains the largest value smaller than x , so we should return Just v instead. 但是,如果右侧子树返回Nothing ,那么我们当前所在的Node包含小于x最大值,因此我们应该返回Just v Instead of the Node case above we should have something like this: 代替上面的Node案例,我们应该有如下内容:

previousVal x (Node v l r) | v < x = case previousVal x r of
                                       Nothing -> Just v
                                       value -> value
                           | otherwise = previousVal x l

Now it should work for 9, too: 现在它也应该适用于9:

Prelude> previousVal 9 t1
Just 7

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM