简体   繁体   English

我如何记住 Haskell 中的二叉搜索树的根

[英]How do I remember the root of a binary search tree in Haskell

I am new to Functional programming.我是函数式编程的新手。

The challenge I have is regarding the mental map of how a binary search tree works in Haskell.我面临的挑战是关于二叉搜索树如何在 Haskell 中工作的心理 map。

  1. In other programs (C,C++) we have something called root.在其他程序(C、C++)中,我们有一个叫做 root 的东西。 We store it in a variable.我们将它存储在一个变量中。 We insert elements into it and do balancing etc..我们将元素插入其中并进行平衡等。
  2. The program takes a break does other things (may be process user inputs, create threads) and then figures out it needs to insert a new element in the already created tree .程序暂停做其他事情(可能是处理用户输入,创建线程),然后确定它需要在已经创建的树中插入一个新元素。 It knows the root (stored as a variable) and invokes the insert function with the root and the new value.它知道根(存储为变量)并使用根和新值调用插入 function。

So far so good in other languages.到目前为止,其他语言都很好。 But how do I mimic such a thing in Haskell, ie但是我如何在 Haskell 中模仿这样的事情,即

  1. I see functions implementing converting a list to a Binary Tree, inserting a value etc.. That's all good我看到实现将列表转换为二叉树、插入值等的函数。这一切都很好
  2. I want this functionality to be part of a bigger program and so i need to know what the root is so that i can use it to insert it again.我希望此功能成为更大程序的一部分,因此我需要知道根是什么,以便我可以使用它再次插入它。 Is that possible?那可能吗? If so how?如果有怎么办?

Note: Is it not possible at all because data structures are immutable and so we cannot use the root at all to insert something.注意:这根本不可能,因为数据结构是不可变的,所以我们根本不能使用根来插入一些东西。 in such a case how is the above situation handled in Haskell?在这种情况下,Haskell 中如何处理上述情况?

It all happens in the same way, really, except that instead of mutating the existing tree variable we derive a new tree from it and remember that new tree instead of the old one.实际上,这一切都以相同的方式发生,除了我们没有改变现有的树变量,而是从中派生一棵新树,并记住那棵新树而不是旧树。

For example, a sketch in C++ of the process you describe might look like:例如,您描述的过程的 C++ 中的草图可能如下所示:

int main(void) {
  Tree<string> root;
  while (true) {
    string next;
    cin >> next;
    if (next == "quit") exit(0);
    root.insert(next);
    doSomethingWith(root);
  }
}

A variable, a read action, and loop with a mutate step.一个变量、一个读取操作和一个带有 mutate 步骤的循环。 In haskell, we do the same thing, but using recursion for looping and a recursion variable instead of mutating a local.在 haskell 中,我们做同样的事情,但使用递归循环和递归变量而不是改变局部变量。

main = loop Empty
  where loop t = do
    next <- getLine
    when (next /= "quit") $ do
      let t' = insert next t
      doSomethingWith t'
      loop t'

Writing an example with a BST would take too much time but I give you an analogous example using lists.用 BST 写一个例子会花费太多时间,但我给你一个使用列表的类似例子。

Let's invent a updateListN which updates the n-th element in a list.让我们发明一个updateListN来更新列表中的第 n 个元素。

updateListN :: Int -> a -> [a] -> [a]
updateListN i n l = take (i - 1) l ++ n : drop i l

Now for our program:现在我们的程序:

list = [1,2,3,4,5,6,7,8,9,10] -- The big data structure we might want to use multiple times

main = do
  -- only for shows
  print $ updateListN 3 30 list -- [1,2,30,4,5,6,7,8,9,10]
  print $ updateListN 8 80 list -- [1,2,3,4,5,6,7,80,9,10]
  -- now some illustrative complicated processing
  let list' = foldr (\i l -> updateListN i (i*10) l) list list
  -- list' = [10,20,30,40,50,60,70,80,90,100]
  -- Our crazily complicated illustrative algorithm still needs `list`
  print $ zipWith (-) list' list
  -- [9,18,27,36,45,54,63,72,81,90]

See how we "updated" list but it was still available?看看我们如何“更新”列表但它仍然可用? Most data structures in Haskell are persistent , so updates are non-destructive. Haskell 中的大多数数据结构都是持久的,因此更新是非破坏性的。 As long as we have a reference of the old data around we can use it.只要我们对周围的旧数据有参考,我们就可以使用它。

As for your comment:至于你的评论:

My program is trying the following a) Convert a list to a Binary Search Tree b) do some I/O operation c) Ask for a user input to insert a new value in the created Binary Search Tree d) Insert it into the already created list.我的程序正在尝试以下操作 a) 将列表转换为二叉搜索树 b) 执行一些 I/O 操作 c) 要求用户输入以在创建的二叉搜索树中插入一个新值 d) 将其插入已经创建的列表。 This is what the program intends to do.这就是该程序打算做的事情。 Not sure how to get this done in Haskell (or) is am i stuck in the old mindset.不知道如何在 Haskell (或)中完成这项工作,我是否陷入了旧的心态。 Any ideas/hints welcome.欢迎任何想法/提示。

We can sketch a program:我们可以画一个程序:

data BST
readInt :: IO Int; readInt = undefined
toBST :: [Int] -> BST; toBST = undefined
printBST :: BST -> IO (); printBST = undefined

loop :: [Int] -> IO ()
loop list = do
  int <- readInt
  let newList = int : list
  let bst = toBST newList
  printBST bst
  loop newList

main = loop []

"do balancing"... "It knows the root" nope. “做平衡”......“它知道根本”不。 After re-balancing the root is new.重新平衡后根是新的。 The function balance_bst must return the new root. function balance_bst必须返回新的根。

Same in Haskell, but also with insert_bst . Haskell 中相同,但也与insert_bst It too will return the new root, and you will use that new root from that point forward.它也将返回新的根,从那时起您将使用新的根。

Even if the new root's value is the same, in Haskell it's a new root, since one of its children has changed.即使新根的值相同,在 Haskell 中它也是一个新根,因为它的一个孩子已经改变。

See '' How to "think functional" '' here .请参阅此处的“如何“思考功能性” ”。

Even in C++ (or other imperative languages), it would usually be considered a poor idea to have a single global variable holding the root of the binary search tree.即使在 C++(或其他命令式语言)中,使用单个全局变量来保存二叉搜索树的根通常也被认为是一个糟糕的主意。

Instead code that needs access to a tree should normally be parameterised on the particular tree it operates on.相反,需要访问树的代码通常应该在它操作的特定树上参数化 That's a fancy way of saying: it should be a function/method/procedure that takes the tree as an argument.这是一种花哨的说法:它应该是一个将树作为参数的函数/方法/过程。

So if you're doing that, then it doesn't take much imagination to figure out how several different sections of code (or one section, on several occasions) could get access to different versions of an immutable tree.因此,如果您这样做,那么不需要太多想象力就可以弄清楚几个不同的代码部分(或一个部分,在多个场合)如何访问不可变树的不同版本。 Instead of passing the same tree to each of these functions (with modifications in between), you just pass a different tree each time.而不是将相同的树传递给这些函数中的每一个(在两者之间进行修改),您只需每次传递不同的树。

It's only a little more work to imagine what your code needs to do to "modify" an immutable tree.想象你的代码需要做些什么来“修改”不可变的树,只需要多做一点工作。 Obviously you won't produce a new version of the tree by directly mutating it, you'll instead produce a new value (probably by calling methods on the class implementing the tree for you, but if necessary by manually assembling new nodes yourself), and then you'll return it so your caller can pass it on - by returning it to its own caller, by giving it to another function, or even calling you again.显然,您不会通过直接对其进行变异来生成树的新版本,而是会生成一个新值(可能通过调用 class 上的方法为您实现树,但如有必要,您可以自己手动组装新节点),然后您将其返回,以便您的调用者可以将其传递 - 通过将其返回给自己的调用者,将其提供给另一个 function,甚至再次调用您。

Putting that all together, you can have your whole program manipulate (successive versions of) this binary tree without ever having it stored in a global variable that is "the" tree.综上所述,您可以让您的整个程序操纵(连续版本)这棵二叉树,而无需将其存储在“the”树的全局变量中。 An early function (possibly even main ) creates the first version of the tree, passes it to the first thing that uses it, gets back a new version of the tree and passes it to the next user, and so on.早期的 function(甚至可能是main )创建树的第一个版本,将其传递给第一个使用它的对象,取回树的新版本并将其传递给下一个用户,依此类推。 And each user of the tree can call other subfunctions as needed, with possibly many of new versions of the tree produced internally before it gets returned to the top level.树的每个用户都可以根据需要调用其他子函数,在返回到顶层之前,可能会在内部生成许多新版本的树。

Note that I haven't actually described any special features of Haskell here.请注意,我实际上并没有在这里描述 Haskell 的任何特殊功能。 You can do all of this in just about any programming language, including C++.您几乎可以使用任何编程语言完成所有这些操作,包括 C++。 This is what people mean when they say that learning other types of programming makes them better programmers even in imperative languages they already knew.这就是人们所说的学习其他类型的编程使他们成为更好的程序员的意思,即使他们已经知道了命令式语言。 You can see that your habits of thought are drastically more limited than they need to be;你可以看到你的思维习惯比他们需要的要有限得多。 you could not imagine how you could deal with a structure "changing" over the course of your program without having a single variable holding a structure that is mutated, when in fact that is just a small part of the tools that even C++ gives you for approaching the problem.您无法想象如何在程序过程中处理结构“变化”,而没有一个变量保存一个已变异的结构,而实际上这只是 C++ 为您提供的工具的一小部分接近问题。 If you can only imagine this one way of dealing with it then you'll never notice when other ways would be more helpful.如果您只能想象这种处理方式,那么您将永远不会注意到其他方式何时会更有帮助。

Haskell also has a variety of tools it can bring to this problem that are less common in imperative languages, such as (but not limited to): Haskell还有多种工具可以解决这个问题,这些工具在命令式语言中并不常见,例如(但不限于):

  1. Using the State monad to automate and hide much of the boilerplate of passing around successive versions of the tree.使用State monad 来自动化和隐藏传递树的连续版本的大部分样板。
  2. Function arguments allow a function to be given an unknown "tree-consumer" function, to which it can give a tree, without any one place both having the tree and knowing which function it's passing it to. Function arguments allow a function to be given an unknown "tree-consumer" function, to which it can give a tree, without any one place both having the tree and knowing which function it's passing it to.
  3. Lazy evaluation sometimes negates the need to even have successive versions of the tree;懒惰的评估有时甚至否定了树的连续版本的需要; if the modifications are expanding branches of the tree as you discover they are needed (like a move-tree for a game, say), then you could alternatively generate "the whole tree" up front even if it's infinite, and rely on lazy evaluation to limit how much work is done generating the tree to exactly the amount you need to look at.如果修改是在您发现需要它们时扩展树的分支(例如游戏的移动树),那么即使它是无限的,您也可以预先生成“整棵树”,并依赖惰性评估将生成树的工作量限制为您需要查看的数量。
  4. Haskell does in fact have mutable variables, it just doesn't have functions that can access mutable variables without exposing in their type that they might have side effects. Haskell 实际上确实有可变变量,它只是没有可以访问可变变量的函数,而不会暴露它们可能有副作用的类型。 So if you really want to structure your program exactly as you would in C++ you can;因此,如果您真的想像在 C++ 中那样构建程序,您可以; it just won't really "feel like" you're writing Haskell, won't help you learn Haskell properly, and won't allow you to benefit from many of the useful features of Haskell's type system.它只是不会真正“感觉”你正在编写 Haskell,不会帮助你正确学习 Haskell,也不会让你从 Haskell 类型系统的许多有用特性中受益。

暂无
暂无

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

相关问题 如何根据成员变量的值将对象添加到二叉树? - How do I add an object to a binary tree based on the value of a member variable? 如何解决 Haskell 中的这种模棱两可的类型变量错误? - How do I solve this ambiguous type variable error in Haskell? 如何更新这些Int ref(Haskell)的值? - How do I update the values of these Int refs (Haskell)? 如何在编辑器中搜索变量名? - How do I search for variable names in the editor? 如何记住网址? - How to remember url? 使用会话(或其他方式),当我返回页面时,该如何做才能使页面记住变量? - Using Sessions (or otherwise) what can I do to make my page remember the variables when I go back to it? 如果我来自命令式编程背景,该如何围绕没有动态变量的想法来跟踪Haskell中的情况? - If I come from an imperative programming background, how do I wrap my head around the idea of no dynamic variables to keep track of things in Haskell? 如何以二进制形式在变量中存储最大可能的双精度值(1.8e + 308)? - How do I store largest double value possible (1.8e+308) in a variable, in binary form? 如何让变量记住结果 - how to make a variable remember the result 在 JavaScript 中,我将如何通过数组为 go 创建一个变量,不仅要记住最大值,还要记住该值的 position - In JavaScript how would I create a variable to go through an array and remember not only the highest value but also the position of that value
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM