[英]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。
So far so good in other languages.到目前为止,其他语言都很好。 But how do I mimic such a thing in Haskell, ie但是我如何在 Haskell 中模仿这样的事情,即
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还有多种工具可以解决这个问题,这些工具在命令式语言中并不常见,例如(但不限于):
State
monad to automate and hide much of the boilerplate of passing around successive versions of the tree.使用State
monad 来自动化和隐藏传递树的连续版本的大部分样板。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.