问题
如何实现运行长度编码模数n>=1
?因为n=4
,考虑到输入AAABBBBABCCCCBBBDAAA
,我们想要一个输出[('D', 1), ('A', 3)]
。请注意由于模运算而导致的长距离合并。见说明。
解释
第一次出现的编码BBBB
为who is ,从而抵消了自身。参见图表(忽略空格;它们仅用于说明目的):(B, 4)
modulus 4
(B, 0)
AAABBBBABCCCCBBBDAAA
A3 B4 ABCCCCBBBDAAA
A3 B0 ABCCCCBBBDAAA
A3 ABCCCCBBBDAAA
A4 BCCCCBBBDAAA
A0 BCCCCBBBDAAA
BCCCCBBBDAAA
...
DA3
一个更简单的例子,当没有合并发生时,因为没有被取消modulus 4
:输入AAABABBBC
产生输出[('A',3),('B',1),('A',1),('B',3),('C',1)]
。
要求
- Haskell 实现是首选,但也欢迎其他实现!
- 首选标准/通用库函数而不是 3rd 方库。
- 更喜欢使用高阶函数的可读和简洁的程序。
- 更喜欢效率(不必要时不要循环整个列表)
我的程序
我在 Haskell 中实现了这个,但它看起来太冗长且难以阅读。关键思想是一次检查三个元组,如果我们既不能取消0
元组,也不能合并手头的三个元组中的一对元组,就只能向前推进一个元组。
import Data.List (group)
test = [('A', 1), ('A', 2), ('B', 2), ('B', 2), ('A', 1), ('B', 1), ('C', 1), ('C', 3), ('B', 3), ('D', 1), ('A', 3)] :: [(Char, Int)]
expected = [('D', 1), ('A', 3)] :: [(Char, Int)]
reduce' :: [(Char, Int)] -> [(Char, Int)]
reduce' [ ] = [] -- exit
reduce' ( (_,0):xs) = reduce' xs
reduce' (x1:(_,0):xs) = reduce' (x1:xs)
reduce' ( (x,n):[]) = (x,n):[] -- exit
reduce' ( (x1,n1):(x2,n2):[]) -- [previous,current,NONE]
| x1 == x2 = reduce' ((x1, d4 (n1+n2)):[])
| otherwise = (x1,n1):( -- advance
reduce' ((x2, d4 n2 ):[]))
reduce' ((x1,n1):(x2,n2):(x3,n3):xs) -- [previous,current,next]
| n3 == 0 = reduce' ((x1, d4 n1 ):(x2, d4 n2 ):xs)
| n2 == 0 = reduce' ((x1, d4 n1 ):(x3, d4 n3 ):xs)
| x2 == x3 = reduce' ((x1, d4 n1 ):(x2, d4 (n2+n3)):xs)
| x1 == x2 = reduce' ((x2, d4 (n1+n2)):(x3, d4 n3 ):xs)
| otherwise = (x1,n1):( -- advance
reduce' ((x2, d4 n2 ):(x3, d4 n3 ):xs)
)
-- Helpers
flatten :: [(Char, Int)] -> String
flatten nested = concat $ (\(x, n) -> replicate n x) <$> nested
nest :: String -> [(Char, Int)]
nest flat = zip (head <$> xg) (d4 .length <$> xg)
where xg = group flat
reduce = reduce' . nest
d4 = (`rem` 4)
想法
我的输入就像test
上面截图中的变量。我们可以继续这样做flatten
,nest
直到它的结果没有改变,并且看起来肯定会更简单。但感觉它是多次扫描整个列表,而我的 3 指针实现只扫描整个列表一次。也许我们可以在合并相同的连续项目时从左侧弹出一个元素并将其添加到新堆栈中?或者也许使用应用函子?例如,这可行,但不确定其效率/性能:reduce = (until =<< ((==) =<<)) (nest . flatten)
。