【问题标题】:Haskell Tree constructionHaskell 树的构造
【发布时间】:2018-05-25 14:22:21
【问题描述】:

需要一些帮助来编写一个接受字符串并创建二叉树的 Haskell 函数。需要一些对 Haskell 有更好经验的人的帮助来为我填补一些漏洞并描述原因,因为这对我来说是一次学习经历。

我得到了一个用单个字符串编码的树,用于 Haskell 中的一个项目(例如 **B**DECA)。星号表示一个节点,任何其他字符表示一个叶子。我正在尝试使用从输入中读取的信息填充此数据结构。

data Trie = Leaf Char | Branch Trie Trie

我更喜欢数学和命令式编程,所以我观察到我可以通过从左到右解析来定义子树。正确的树将比* 多1 个字符。在数学上我会想到一个递归结构。

如果第一个字符不是*,则解决方案是第一个字符。否则解决方案是一个分支,其中子分支被反馈到函数中,其中左分支是第一组字符,其中字符输出编号为*,而右分支是其他所有字符。

constructTrie :: String -> Trie
constructTrie x = if x !! 1 == '*' then
                      let leftSubtree = (first time in drop 1 x where characters out number *'s)
                          rightSubtree = (rest of the characters in the drop 1 x)
                      in Branch constructTrie(leftSubtree) constructTrie(rightSubtree)
                  else Leaf x !! 1

大多数情况下,我需要帮助来定义左右子树,如果以这种方式定义它有什么问题。

【问题讨论】:

    标签: string haskell tree huffman-code


    【解决方案1】:

    !!(顺便说一下,0-indexed)通常是不行的。这是一件非常“当务之急”的事情,而且对于像这里这样的常量索引,它特别臭。这意味着你真的想要一个模式匹配。此外,在索引处拆分列表 (type String = [Char]) 并分别对这两个部分进行操作是一个坏主意,因为这些列表是链接的且不可变的,因此您最终会复制整个第一部分。

    你想这样做的方式如下:

    • 如果字符串为空,则失败。
    • 如果它以* 开头,则以某种方式解析左子树并一步获取列表的剩余部分,然后从剩余部分中解析右子树,生成Branch
    • 如果是其他角色,请发Leaf

    不需要弄清楚在哪里拆分字符串,实际拆分字符串,然后解析两半;只需解析列表,直到你不能再解析,然后剩下的(或者我应该说对吗?)可以再次解析。

    所以:定义一个函数constructTrie' :: String -> Maybe (Trie, String),它将String 的开头消耗为Trie,然后留下未解析的位(如果解析失败,则给出Nothing)。这个函数将是递归的,这就是它获得额外输出值的原因:它需要额外的管道来移动列表的其余部分。 constructTrie 本身可以被定义为一个包装器:

    -- Maybe Trie because it's perfectly possible that the String just won't parse
    constructTrie :: String -> Maybe Trie
    constructTrie s = do (t, "") <- constructTrie' s
                      -- patmat failure in a monad calls fail; fail @Maybe _ = Nothing
                         return t
    
    -- can make this local to constructTrie in a where clause
    -- or leave it exposed in case it's useful
    constructTrie' :: String -> Maybe (Trie, String)
    constructTrie' "" = Nothing -- can't parse something from nothing!
    constructTrie' ('*':leaves) = do (ln, rs) <- constructTrie' leaves
                                  -- Parse out left branch and leave the right part
                                  -- untouched. Doesn't copy the left half
                                     (rn, rest) <- constructTrie' rs
                                  -- Parse out the right branch. Since, when parsing
                                  -- "**ABC", the recursion will end up with
                                  -- constructTrie' "*ABC", we should allow the slop.
                                     return (Branch ln rn, rest)
    constructTrie' (x:xs) = return (Leaf x, xs)
    

    这是一种非常常见的模式:定义一个带有额外管道的递归函数来传递一些状态并将其包装在一个更好的状态中。我猜它对应于命令式循环通常如何改变变量以保持它们的状态。

    【讨论】:

    • 这是一个不错的方法。甚至可以更进一步,使用StateT String Maybe Trie 来避免传递字符串状态。
    猜你喜欢
    • 2014-04-18
    • 1970-01-01
    • 2011-05-29
    • 2019-11-09
    • 2019-05-28
    • 1970-01-01
    • 1970-01-01
    • 2011-02-04
    • 1970-01-01
    相关资源
    最近更新 更多