左垫一个 Haskell 列表

Left pad a Haskell list

如果我想右填充一个 Haskell 整数列表,我可以执行如下操作:

rpad m xs = take m $ xs ++ repeat 0

不需要获取列表的长度。我认为它会非常高效。

有没有类似的方法可以定义 lpad,在左侧填充列表,而不会产生计算长度等的成本?

Right padding 只需检查输入列表(:[])中的 first 构造函数即可生成 first 输出列表的元素。这是一个流操作(可以用 foldr 完成)。

左填充需要检查 整个 输入列表,以便生成输出列表的 first 元素。也就是说,第一个元素是否为0取决于列表的尾部(假设它不以0开头)。这不能以流方式完成。 O(min(m,length)) 是你能得到的最好的,仅对于第一个元素。

此外,请注意,如果您的输入列表比那长,您的填充函数会丢弃第 m 之后的元素。这可能是不需要的——有时填充被定义为只能添加元素,而不能删除。

所以首先要说的是,不要太担心性能的细节。这段代码不太可能位于众所周知的 20% 的代码中,它占用了 80% 的 运行 时间。

话虽如此,在这里性能真正重要的地方在哪里?如果 m 很小而 length xs 很大或无穷大,这很重要。我的意思是,如果 m 很大(就像 rpad 一样,如果您只处理列表的第一个 k 项,那么获得良好的性能也会很好k 为某些 k << m 工作),但当然 问题描述 需要您可能做 m 工作才能看到最佳结果。 (如果为您提供了一个无限列表,您需要查看 m 项甚至知道是否要 return 0 第一个元素。)

在这种情况下,您确实需要零填充 take m xs 而不是 xs。这就是全部技巧:

lpad m xs = replicate (m - length ys) 0 ++ ys
    where ys = take m xs

这是一组(未经测试但正在编译)padding/trimming 函数 (Unlicenced):

padL :: a -> Int -> [a] -> [a]
padL p s l
    | length l >= s = l
    | otherwise     = replicate (s - length l) p ++ l
{-# INLINABLE padL #-}

padR :: a -> Int -> [a] -> [a]
padR p s l = take s $ l ++ repeat p
{-# INLINABLE padR #-}

trimL :: Int -> [a] -> [a]
trimL s l
    | length l <= s = l
    | otherwise     = drop (length l - s) l
{-# INLINABLE trimL #-}

trimR :: Int -> [a] -> [a]
trimR = take
{-# INLINE trimR #-}

resizeL :: a -> Int -> [a] -> [a]
resizeL p s l
    | length l == s = l
    | length l < s  = padL p s l
    | otherwise     = trimL s l
{-# INLINABLE resizeL #-}

resizeR :: a -> Int -> [a] -> [a]
resizeR p s l
    | length l == s = l
    | length l < s  = padR p s l
    | otherwise     = trimR s l
{-# INLINABLE resizeR #-}

@Solomon Ucko

为什么在 padL 中调用 length 两次(其他人也是如此)?怎么样

padL :: a -> Int -> [a] -> [a]
padL p s l
    | length' >= s = l
    | otherwise    = replicate (s - length') p ++ l
      where length' = length l