左垫一个 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
如果我想右填充一个 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