从Haskell中的{preorder)bitstring重构Huffman树

时间:2016-12-24 00:14:08

标签: haskell recursion encoding tree huffman-code

我有以下Haskell多态数据类型:

data Tree a = Leaf Int a | Node Int (Tree a) (Tree a)

树将以0s和1s的位串压缩。 A' 0' 0表示一个节点,然后是左子树的编码,然后是右子树的编码。 A' 1'表示Leaf,后跟7位信息(例如,它可能是char)。每个节点/叶子都应该包含存储信息的频率,但这对于这个问题并不重要(所以我们可以把任何东西放在那里)。

例如,从此编码树开始

[0,0,0,1,1,1,0,1,0,1,1,1,1,1,1,0,1,0,0,0,0,1,1,1,1,0,0,0,1,1,1,
 1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,1]

它应该回馈这样的东西

Node 0 (Node 0 (Node 0 (Leaf 0 'k') (Leaf 0 't')) 
       (Node 0 (Node 0 (Leaf 0 'q') (Leaf 0 'g')) (Leaf 0 'r'))) 
(Node 0 (Leaf 0 'w') (Leaf 0 'a'))

(间距不重要,但不适合一行)。

我没有使用树的经验,特别是在实现代码时。我对如何在纸上解决这个问题有一个模糊的想法(使用类似于堆栈的东西来处理深度/水平)但我仍然有点迷失。

感谢任何帮助或想法!

3 个答案:

答案 0 :(得分:2)

好吧,你正试图从比特流中解析一个字节树。解析其中一个建立一些结构的案例:我们将以How to Replace Failure by a List of Successes的方式编写一个微型解析器组合库,这将允许我们以惯用的函数样式编写代码并委托机器的很多工作。

the old rhyme翻译成monad变换器的语言,并将“string”读成“bit-string”,我们有

newtype Parser a = Parser (StateT [Bool] [] a)
    deriving (Functor, Applicative, Monad, Alternative)

runParser :: Parser a -> [Bool] -> [(a, [Bool])]
runParser (Parser m) = runStateT m

解析器是一个monadic计算,它在布尔流上有状态地运行,产生一组成功解析的a。 GHC的GeneralizedNewtypeDeriving超级大国允许我忽略Monad等人的样板实例。

然后,目标是编写一个Parser (Tree SevenBits) - 一个解析器,它返回一个布尔的三元组树。 (您可以Word8Tree并使用fmap将7位变为Tree。)我将使用以下{{1}的定义1}}因为它更简单 - 我相信你可以弄清楚如何使这个代码适应你自己的目的。

data Tree a = Leaf a | Node (Tree a) (Tree a) deriving Show

type SevenBits = (Bool, Bool, Bool, Bool, Bool, Bool, Bool)

这是一个尝试从输入流中消耗一个位的解析器,如果它是空的则失败:

one :: Parser Bool
one = Parser $ do
    stream <- get
    case stream of
        [] -> empty
        (x:xs) -> put xs *> return x

这是尝试从输入流中使用特定位的一个,如果它不匹配则会失败:

bit :: Bool -> Parser ()
bit b = do
    i <- one
    guard (i == b)

在这里,我使用deriving a Functor instance从输入流中提取七个布尔值的序列,并将它们打包成一个元组。我们将使用它来填充Leaf个节点的内容。

sevenBits :: Parser SevenBits
sevenBits = pack7 <$> replicateM 7 one
    where pack7 [a,b,c,d,e,f,g] = (a, b, c, d, e, f, g)

现在我们终于可以编写解析树结构本身的代码了。我们将使用replicateMNodeLeaf替代品之间进行选择。

tree :: Parser (Tree SevenBits)
tree = node <|> leaf
    where node = bit False *> liftA2 Node tree tree
          leaf = bit True *> fmap Leaf sevenBits

如果node成功解析了流的头部的低位,它继续递归地解析左子树的编码,然后是右子树,用<|>对应用操作进行排序。诀窍是如果node没有遇到输入流头部的低位,这会导致<|>放弃node并尝试leaf代替。

请注意tree的结构如何反映Tree类型本身的结构。这是在工作中应用解析。我们可以替代地单独构造此解析器,首先使用one解析任意位,然后对该位使用case分析来确定是否应该继续解析一对树或叶子。在我看来,这个版本更简单,更具说明性,更简洁。

还要将此代码的清晰度与@ behzad.nouri基于foldr的解决方案的低级样式进行比较。我的设计不是构建一个在解析节点和离开之间切换的显式有限状态机 - 一种命令性的想法 - 我的设计允许您使用liftA2<|>之类的标准函数以声明方式向机器描述语法。并相信抽象会做正确的事。

无论如何,这里我正在解析一个简单的树,其中包含一对包含(二进制编码)数字Leaf0的{​​{1}} s。如您所见,它返回单个成功的解析和剩余位的空流。

1

答案 1 :(得分:1)

做正确的折叠:

{{1}}

然后:

{{1}}

答案 2 :(得分:1)

好的,这是一种简单的(临时的,但更容易理解)的方式。

我们需要使用以下类型来建立函数parse

parse  :: [Int] -> Tree Char

你提到的堆栈方法是必不可少的。在这里,我们只是在递归调用。堆栈将由编译器构建,它将只存储每个递归调用(至少你可以想象它,如果你想,或者只是忽略所有这段)。

所以,这个想法如下:每当你找到0时,你需要对算法进行两次递归调用。第一个递归调用将读取树的一个分支(左侧)。需要使用列表的其余部分作为参数调用第二个。剩下的就是第一次递归调用。所以,我们需要一个具有以下类型的辅助函数parse'(现在我们返回一对,作为其余列表的第二个值):

parse' :: [Int] -> (Tree Char, [Int])

接下来,您可以看到一段代码0案例如前所述 对于1情况,我们只需要接下来的7个数字并以某种方式将它们变成char(我为你留下toChar的定义),然后,只返回Leaf以及列表的其余部分。

parse' (0:xs) = let (l, xs')    = parse' xs
                    (r, xs'')   = parse' xs' in (Node 0 l r, xs'') --xs'' should be []
parse' (1:xs) = let w = toChar (take 7 xs) in (Leaf 0 w , drop 7 xs)

最后,我们的parse函数只调用辅助解析一并返回该对的第一个元素。

parse xs = fst $ parse' xs