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”]
  • f
    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)