Haskell 如何从列表末尾开始读取由3个元素组成的块?
这是我的函数签名:Haskell 如何从列表末尾开始读取由3个元素组成的块?,haskell,Haskell,这是我的函数签名: foo :: Integer -> [String] 此功能应如何工作: zeichenreihenBlock :: Integer -> [String] zeichenreihenBlock x = reverse (partition (show x)) partition :: String -> [String] partition [] = [] partition x | length x `mod` 3 == 2 = take 3
foo :: Integer -> [String]
此功能应如何工作:
zeichenreihenBlock :: Integer -> [String]
zeichenreihenBlock x = reverse (partition (show x))
partition :: String -> [String]
partition [] = []
partition x
| length x `mod` 3 == 2 = take 3 ("0" ++ x) : partition (drop 3 ("0" ++ x))
| length x `mod` 3 == 1 = take 3 ("00" ++ x) : partition (drop 3 ("00" ++ x))
| length x `mod` 3 == 0 = take 3 x : partition (drop 3 x)
foo 1234567
应返回[“567”、“234”、“001”]
foo 12345678
应返回[“678”、“345”、“012”]
oo123456789
应返回[“789”、“456”、“123”]
zeichenreihenBlock :: Integer -> [String]
zeichenreihenBlock x = reverse (partition (show x))
partition :: String -> [String]
partition [] = []
partition x
| length x `mod` 3 == 2 = take 3 ("0" ++ x) : partition (drop 3 ("0" ++ x))
| length x `mod` 3 == 1 = take 3 ("00" ++ x) : partition (drop 3 ("00" ++ x))
| length x `mod` 3 == 0 = take 3 x : partition (drop 3 x)
将“Zeichenreichenblock”替换为“foo”。您将如何实现这一点?是否有更有效的解决方案
我真的很欣赏一些新的想法
干杯你检查长度的方式太频繁了(一个O(n)操作),而你可以在事后修复每个案例,因此,要花费一些成本,你可以进行两次反向操作:
import Data.List.Grouping(splitEvery)
foo :: Integer -> [String]
foo = map (reverse . fixIt) . splitEvery 3 . reverse . show
where
fixIt [a] = a:'0':'0':[]
fixIt [a,b] = a:b:'0':[]
fixIt lst = lst
另一种方法是检查列表的长度一次,并在第一个函数中添加填充。这避免了单个列表遍历的额外开销(请注意,这并没有多少节约)。在go
中,我假设列表是长度mod 3
(因为它是),并且总是取3
import Data.List.Grouping
foo :: Integer -> [String]
foo x | r == 2 = go ('0':s)
| r == 1 = go ('0':'0':s)
| otherwise = go s
where
r = l `rem` 3
l = length s
s = show x
go = reverse . splitEvery 3
这里的性能是什么并不重要(其他代码将占主导地位),但我喜欢用criteria来打趣:
benchmarking doubleRev -- my first one
mean: 14.98601 us, lb 14.97309 us, ub 15.00181 us, ci 0.950
benchmarking singleRev -- my second one
mean: 13.64535 us, lb 13.62470 us, ub 13.69482 us, ci 0.950
benchmarking simpleNumeric -- this is sepp2k
mean: 23.03267 us, lb 23.01467 us, ub 23.05799 us, ci 0.950
benchmarking jetxee -- jetxee beats all
mean: 10.55556 us, lb 10.54605 us, ub 10.56657 us, ci 0.950
benchmarking original
mean: 21.96451 us, lb 21.94825 us, ub 21.98329 us, ci 0.950
benchmarking luqui
mean: 17.21585 us, lb 17.19863 us, ub 17.25251 us, ci 0.950
-- benchmarked heinrich at a later time
-- His was ~ 20us
此外,这是一个很好的机会,可以指出你对什么是最快的最好猜测往往无法实现(至少,对我来说不是这样)。如果您想优化配置文件和基准测试,不要猜测。好吧,我认为如果您使用更多的模式匹配,而不是那些保护,它会看起来更好。您可以匹配特定的列表长度,如下所示:
partition :: String -> [String]
partition [] = ...
partition [x] = ...
partition [x,y] = ...
partition (x:y:z:rest) = ...
如果在拆分字符串之前反转字符串,检查长度可能会更好,这样做不是很优雅。这样你可以一次取三个数字,按正确的顺序放回去,然后重复。担心长度只会发生在末尾,可以通过模式匹配(如上所述)来完成。先焊盘、拆分、反转:
foo :: Integer -> [String]
foo = reverse . split3 . pad0
split3 [] = []
split3 xs = take 3 xs : split3 (drop 3 xs)
pad0 x = let s = show x in replicate ((- length s) `mod` 3) '0' ++ s
测试:
使用整数算术的简单解决方案:
padWithZeros n | n < 10 = "00" ++ show n
| n < 100 = '0' : show n
| otherwise = show n
foo 0 = []
foo n = padWithZeros (n `mod` 1000) : foo (n `div` 1000)
带零的焊盘n | n<10=“00”++显示n
|n<100='0':显示n
|否则=显示n
foo 0=[]
foo n=padWithZeros(n`mod`1000):foo(n`div`1000)
只需使用数字就可以更轻松地获取组,而不必使用字符串
digitGroups group 0 = []
digitGroups group n = r : digitGroups group q
where (q,r) = n `divMod` (10^group)
-- > digitGroups 3 1234567
-- [567,234,1]
然后,使用填充显示将成为一个单独的问题:
pad char len str = replicate (len - length str) char ++ str
-- > pad '0' 3 "12"
-- "012"
他们共同寻求最终解决方案:
foo = map (pad '0' 3 . show) . digitGroups 3
-- > foo 1234567
-- ["567","234","001"]
其他解决方案非常好,但真正的解决方案是使用无限列表填充
他无法匹配列表的长度。注意,他想从列表的后面向前分块,因此他必须反转列表,或者找出列表是否为非mod3长度,并添加一些填充。@TomMD:这就是为什么我建议反转列表,这更有意义。因为最终的分组列表顺序相反,如果从后面开始,分组与长度无关。也许应该先把这部分放在第一位。对,在仔细阅读之前,很抱歉下意识的评论。@TomMD:不用担心,即使是。+1是目前为止最简单的解决方案,可能也是最有效的。@identity我同意,代码很简单,但它不能清楚地立即告诉我,我将得到一个长度为3的字符串列表(这似乎是重要的特征)。使用简单递归和/或函数组合的其他解决方案(JETXES)更清晰。此外,我的基准测试表明,即使使用-O2,使用
rem
而不是mod
,这也是最慢的解决方案(我也完全感到惊讶)。@TomMD:很公平,尽管我必须说,我完全惊讶于它的速度如此之慢。但这基本上并不比原来的快(我假设计算时间就是海报的速度)“更高效”),它会给出不正确的结果。哎呀,修复了不正确的结果。关于性能,我认为这个函数不太可能成为瓶颈,所以我可以选择一个慢变量。
foo n = take count . map reverse . group 3 . (++ repeat '0') . reverse $ digits
where
digits = show n
count = (length digits - 1) `div` 3 + 1
group k xs = take k xs : group k (drop k xs)