【问题标题】:Run Length Encoding in HaskellHaskell 中的运行长度编码
【发布时间】:2018-02-08 03:03:19
【问题描述】:
import Data.List

data Encoding = Multiple Int Char | Single Char deriving (Eq,Show,Ord)

行程编码

encode :: String -> [Encoding]
encode inputString =encoding (group inputString) []


encoding :: [String] -> [Encoding] -> [Encoding]
encoding groupString xs=
if (length groupString == 0)
    then xs
else
    case (head groupString) of
            ([c]) ->encoding (tail groupString)  (xs ++ [Single c])
            (x) -> encoding (tail groupString)  (xs ++ [Multiple (length x) (head x)])

运行长度解码

decode :: [Encoding] -> String
decode listString = decoding listString []              

decoding :: [Encoding] -> String -> String
decoding inputString xs=
if (length inputString == 0)
    then xs
else
    case (head inputString) of
        (Single x) ->decoding (tail inputString) (xs++ [x])
        (Multiple num x) ->decoding (tail inputString) (xs ++ (replicate num x) )

这是我的运行长度编码解决方案,谁能建议我编写编码和解码函数的更好方法

【问题讨论】:

    标签: haskell


    【解决方案1】:

    您的许多代码专门用于更新累加器。您将元素添加到该累加器的尾部,这将对性能产生巨大影响。

    这通常不是很有效的原因是因为 Haskell [a] 中的列表被实现 - 至少在概念上 - 作为 链表。因此,将两个列表l1l2l1 ++ l2 连接起来将花费O(|l1|) 时间(即l1 中的元素数)。这意味着如果列表已经包含 100 个元素,那么在最后添加一个额外的元素将需要大量工作。

    另一个反模式是使用headtail。是的,可以使用这些函数,但不幸的是,由于您不使用模式匹配,因此可能会传递一个空列表,然后headtail 会出错。

    这里的另一个问题是您在列表中使用length。因为有时 Haskell 中的列表可能有无限长,所以 length 调用将 - 如果我们需要它 - 永远不会结束。

    如果您必须在累加器的末尾追加,通常我们可以将递归写在我们正在构建的列表“cons”的尾部。所以我们可以重写我们的程序:

    encode :: String -> [Encoding]
    encode [] = []
    encode (h:t)  = ...
    

    现在的问题是我们如何计算这些值。我们可以使用span :: (a -> Bool) -> [a] -> ([a],[a]),该函数将 - 对于给定的谓词和列表 - 构造一个 2 元组,其中第一项包含列表的前缀,其中所有元素都满足谓词,第二项包含其余的列表,所以我们可以这样使用:

    encode :: String -> [Encoding]
    encode [] = []
    encode (h:t)  | nh > 1 = Multiple nh h : tl
                  | otherwise = Single h : tl
        where (s1, s2) = span (h ==) t
              nh = 1 + length s1
              tl = encode s2
    

    例如:

    Prelude Data.List> encode "Foobaaar   qqquuux"
    [Single 'F',Multiple 2 'o',Single 'b',Multiple 3 'a',Single 'r',Multiple 3 ' ',Multiple 3 'q',Multiple 3 'u',Single 'x']
    

    对于解码,我们再次不需要累加器,可以使用replicate :: Int -> a -> [a]

    decode :: [Encoding] -> String
    decode [] = []
    decode (Single h:t) = h : decode t
    decode (Multiple nh h) = replicate nh h ++ decode t
    

    或者我们可以使用列表单子代替:

    decode :: [Encoding] -> String
    decode = (>>= f)
        where f (Single h) = [h]
              f (Multiple nh h) = replicate nh h
    

    例如:

    Prelude Data.List> decode [Single 'F',Multiple 2 'o',Single 'b',Multiple 3 'a',Single 'r',Multiple 3 ' ',Multiple 3 'q',Multiple 3 'u',Single 'x']
    "Foobaaar   qqquuux"
    

    【讨论】:

    • 您知道这一点,但是将项目添加到列表末尾的另一种方法是将每个新元素添加到严格累加器的开头,然后反转列表以以正确的顺序获取它。这比追加更快,因为当您添加到不可变列表的末尾时,需要将前一个最后一个节点替换为链接到新最后一个节点的节点,然后需要将之前的节点替换为链接到的节点新的倒数第二个,依此类推,直到您每次插入都复制了整个列表并且您的算法现在需要二次时间。
    • @Davislor:是的,我知道我们可以使用“反向累加器”,但我不相信这会更快,因为反向也需要时间,而且它可以创建一个巨大的内存负载(因为没有说我们无论如何都想要整个编码)。
    • 并不是说它是或不是解决此问题的更好方法。主要是我记得试图弄清楚为什么我的accum ++ [x] 函数如此缓慢,我认为同一阶段的其他人,比如 OP,会从解释中受益。
    • @Davislor:公平点,我更新了答案并解释了为什么附加到右边通常不是一个好主意:)。
    【解决方案2】:

    作为Willem Van Onsem's优秀answer的扩展,考虑单独创建运行长度,然后将它们与zipWith的字母组合在一起。

    Data.List 具有函数group(它本身是泛型groupBy 的特化;group = groupby (==))将字符串分解为同质子字符串。即:

    group "aaabbccccd"
    = ["aaa", "bb", "cccc", "d"]
    

    计算每个的长度将为您提供运行长度。

    注意group 的实现方式与 Willem 的 span 解决方案完全相同。

    import Data.List (group)
    
    data Encoding = Multiple Int Char
                  | Single       Char
      deriving (Eq, Show, Ord)
    
    encode :: String -> [Encoding]
    encode xs = zipWith op lengths letters
      where
      groups  = group xs
      lengths = map length groups
      letters = map head   groups
    
      op :: Int -> Char -> Encoding
      op 1 = Single
      op n = Multiple n
    

    这也可以作为一种非常丑陋的列表理解来完成。

    encode xs = [ let (n, c) = (length g, head g)
                  in  if   n == 1
                      then Single c
                      else Multiple n c
                | g <- group xs ]
    

    【讨论】:

      【解决方案3】:
      1. 您的 encoding 函数是一个函数映射。只需使用map,而不是制作自己的原始递归函数。

      2. 您的编码正在反转输出(xs ++ [Single c] 等),这既违反直觉又计算成本很高。别了。

      3. 括号太多,例如case (..) ofif (...) then 和案例的手臂(...) -&gt;。所有这些都是不需要的,并且会使代码混乱。

      4. 如果您键入 head,您可能应该在某处匹配模式。

      考虑:

      encoding :: String -> [Encoding]
      encoding = map enc . group       -- Point 1, use map which also takes
                                       -- care of point 2 and 3.
       where
       enc [x]      = Single x
       enc xs@(x:_) = Multiple (length xs) x -- Point 4, patterns not `head`
       -- Here consider make pattern matches total either via an error call or Maybe type
      

      【讨论】:

        猜你喜欢
        • 2012-08-17
        • 2013-09-27
        • 1970-01-01
        • 2013-02-01
        • 2012-07-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多