如何从列表末尾开始的列表中读取3个元素的块?

时间:2010-10-14 16:39:02

标签: haskell

这是我的功能签名:

foo :: Integer -> [String]

此功能应如何运作:

  1. foo 1234567应该返回["567", "234", "001"]
  2. foo 12345678应该返回["678", "345", "012"]
  3. f oo 123456789应该返回["789", "456", "123"]
  4. 我目前的解决方案:

    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)
    

    将'zeichenreihenBlock'替换为'foo'。你会如何实现这个?有更有效的解决方案吗?

    真的很感激一些新想法。

    干杯

6 个答案:

答案 0 :(得分:6)

使用数字来获取组更容易,而不是使用字符串。

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"]

答案 1 :(得分:3)

先填充,分割,反向:

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

测试:

ghci> foo 1234567
["567","234","001"]
ghci> foo 12345678
["678","345","012"]
ghci> foo 123456789
["789","456","123"]

答案 2 :(得分:2)

你可以经常检查长度方式(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

另一种方法是检查列表ONCE的长度并在第一个函数中添加填充。这避免了单个列表遍历的成本的额外逆转(注意节省不多)。在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

并不是说这里的表现有点重要(其他代码会占主导地位)但是我喜欢用Criterion打造好玩的东西:

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

此外,这是一个很好的机会,可以指出你对最快速度的最佳猜测通常不会消失(至少,不适合我)。如果您想要优化配置文件和基准测试,请不要猜测。

答案 3 :(得分:2)

使用整数运算的简单解决方案:

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)

答案 4 :(得分:1)

嗯,我认为如果你使用更多的模式匹配而不是那些守卫会更好看。您可以匹配特定的列表长度,如下所示:

partition :: String -> [String]
partition [] = ...
partition [x] = ...
partition [x,y] = ...
partition (x:y:z:rest) = ...

如果在拆分之前颠倒字符串也可能会更好,检查长度不是很优雅。这样你一次可以取三个数字,按正确顺序放回,然后重复。担心长度只会在最后发生,并且可以通过模式匹配完成(如上所述)。

答案 5 :(得分:0)

其他解决方案非常好,但真正的解决方案是使用无限列表进行填充。 ; - )

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)