Haskell中的Folds实现

www*_*www 1 refactoring haskell functional-programming function fold

我在尝试了解Haskell上的折叠实现时遇到很多问题。我需要使用具有此输出的fold两个函数

> runLengthEncode "aaaaaaabbb"
[(7,'a'),(3,'b')]
> runLengthDecode [(1,'h'), (5,'i')]
"hiiiii"
Run Code Online (Sandbox Code Playgroud)

因此,我要做的就是像编写模式匹配一​​样(工作)先编写函数,但是现在我不知道如何使用向左折叠或向右折叠来“翻译”该函数。

runLengthEncode :: String -> [(Int,Char)]
runLengthEncode [] = []
runLengthEncode (x:xs) = runLengthEncode 1 x xs
    where
        runLengthEncode n x [] = [(n,x)]
        runLengthEncode n x (y:ys) | x == y = runLengthEncode (n + 1) y ys
                                   | otherwise = (n,x) : runLengthEncode 1 y ys

runLengthDecode :: [(Int,Char)] -> String
runLengthDecode [] = []
runLengthDecode ((a,b):xs) = replicate a b ++ (runLengthDecode xs)
Run Code Online (Sandbox Code Playgroud)

K. *_*uhr 8

可以将折叠视为一系列术语:

[a,b,c,d]
Run Code Online (Sandbox Code Playgroud)

并在条款之间添加初始值zz和二进制运算符<+>

foldl (<+>) zz [a,b,c,d] = (((zz <+> a) <+> b) <+> c) <+> d
foldr (<+>) zz [a,b,c,d] = a <+> (b <+> (c <+> (d <+> zz)))
Run Code Online (Sandbox Code Playgroud)

请注意,初始值也是折叠应用于空列表时将具有的值,因此通常很容易弄清楚。困难的部分是定义适当的二进制运算符。

因此,要表达runLengthEncode正确的权利,您需要:

'a' <+> ('a' <+> ('a' <+> ('b' <+> zz))) = [(3,'a'),(1,'b')]
Run Code Online (Sandbox Code Playgroud)

给算子<+>和一些初始值zz

我们可以很容易地“解决”了zz,因为我们知道runLengthEncode [] = [],所以zz = []。我们需要进行定义,<+>以便满足从上例(从右到左)得出的方程式:

'b' <+> [] = [(1, 'b')]
'a' <+> [(1, 'b')] = [(1, 'a'), (1, 'b')]
'a' <+> [(1, 'a'), (1, 'b')] = [(2, 'a'), (1, 'b')]
'a' <+> [(2, 'a'), (1, 'b')] = [(3, 'a'), (1, 'b')]
Run Code Online (Sandbox Code Playgroud)

定义这样的运算符实际上非常简单:

(<+>) :: Char -> [(Int, Char)] -> [(Int, Char)]
x <+> ((n, y) : rest) | x == y = ((n+1), y) : rest
x <+> rest                     = (1, x) : rest
Run Code Online (Sandbox Code Playgroud)

这样我们得到:

runLengthEncode' :: String -> [(Int,Char)]
runLengthEncode' = foldr (<+>) []
Run Code Online (Sandbox Code Playgroud)

也尝试左折操作:

(((zz + 'a') <+> 'a') <+> 'a') <+> 'b' =>  [(3,'a'),(1,'b')]
Run Code Online (Sandbox Code Playgroud)

您会发现,当需要定义时<+>,您必须检查当前RLE 的最后一个元素,而不是第一个。当然可以做到这一点,但是自然程度要低一些,这就是为什么我在上面使用了正确的折痕的原因。

对于runLengthDecode,这是相同的业务:

(1, 'h') <+> ((5, 'i') <+> zz) = "hiiiii"
Run Code Online (Sandbox Code Playgroud)

再次,确定zz应该是什么。然后,弄清楚如何编写二进制运算符来解决:

(5, 'i') <+> zz = "iiiii"
(1, 'h') <+> "iiiii" = "hiiiii"
Run Code Online (Sandbox Code Playgroud)

剧透跟随...

当然,您实际上不需要使用运算符语法,因此完整的解决方案如下所示:

runLengthEncode'' :: String -> [(Int,Char)]
runLengthEncode'' = foldr step []
  where step x ((n, y) : rest) | x == y = ((n+1), y) : rest
        step x rest                     = (1, x) : rest

runLengthDecode'' :: [(Int,Char)] -> String
runLengthDecode'' = foldr step ""
  where step (n, x) str = replicate n x ++ str
Run Code Online (Sandbox Code Playgroud)