使用`foldl`实现Haskell的`take`函数
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用`foldl`实现Haskell的`take`函数相关的知识,希望对你有一定的参考价值。
使用take
实现Haskell的drop
和foldl
函数。
有关如何使用foldl
实现删除和删除功能的任何建议?
take x ls = foldl ???
drop x ls = foldl ???
我试过这些,但它显示错误:
myFunc :: Int -> [a] -> [a]
myFunc n list = foldl func [] list
where
func x y | (length y) > n = x : y
| otherwise = y
错误产生:
*** Expression : foldl func [] list
*** Term : func
*** Type : a -> [a] -> [a]
*** Does not match : [a] -> [a] -> [a]
*** Because : unification would give infinite type
无法做到。
左侧折叠必然在无限列表上发散,但take n
不会。这是因为左侧折叠是尾递归,因此它必须扫描整个输入列表才能开始处理。
正确的折叠,它是
ntake :: Int -> [a] -> [a]
ntake 0 _ = []
ntake n xs = foldr g z xs 0
where
g x r i | i>=n = []
| otherwise = x : r (i+1)
z _ = []
ndrop :: Int -> [a] -> [a]
ndrop 0 xs = xs
ndrop n xs = foldr g z xs 0 xs
where
g x r i xs@(_:t) | i>=n = xs
| otherwise = r (i+1) t
z _ _ = []
ndrop
很好地和忠实地实现了一个paramorphism,直到reducer函数g
的参数顺序,使它可以访问当前元素x
和当前列表节点xs
(如xs == (x:t)
)以及递归结果r
。一个catamorphism的reducer只能访问x
和r
。
折叠通常编码catamorphisms,但这表明右折叠也可以用来编码一个paramorphism。这种方式很普遍。我觉得它很漂亮。
至于类型错误,要修复它只需将参数切换到func
:
func y x | ..... = .......
左侧折叠中的累加器作为reducer函数的第一个参数。
如果你真的希望用左折叠完成,如果你真的确定列表是有限的,有两个选择:
ltake n xs = post $ foldl' g (0,id) xs
where
g (i,f) x | i < n = (i+1, f . (x:))
| otherwise = (i,f)
post (_,f) = f []
rltake n xs = foldl' g id xs r n
where
g acc x = acc . f x
f x r i | i > 0 = x : r (i-1)
| otherwise = []
r _ = []
第一个从左上方向上计数,可能会停止在完整列表遍历的中间组装前缀,但它仍然会进行到最后,即左侧折叠。
第二个也完全遍历列表,将其转换为右侧折叠,然后再次从左侧向下计数,一旦前缀组合就能够实际停止工作。
以这种方式实现drop
必然会(?)甚至更笨拙。可能是一个很好的运动。
我注意到你从未指定折叠必须在提供的列表上。因此,满足您的问题的一个方法,虽然可能不是精神,但是:
sillytake :: Int -> [a] -> [a]
sillytake n xs = foldl go (const []) [1..n] xs
where go f _ (x:xs) = x : f xs
go _ _ [] = []
sillydrop :: Int -> [a] -> [a]
sillydrop n xs = foldl go id [1..n] xs
where go f _ (_:xs) = f xs
go _ _ [] = []
这些都使用左侧折叠,但是在数字列表[1..n]
上 - 数字本身被忽略,并且列表仅用于其长度来为给定的take n
构建自定义drop n
或n
函数。然后将此函数应用于原始提供的列表xs
。
这些版本在无限列表上运行良好:
> sillytake 5 $ sillydrop 5 $ [1..]
[6,7,8,9,10]
你不是太远了。这是一对修复。
首先,请注意func
首先传递累加器(即a
列表,在您的情况下),然后是list元素(a
)。所以,你需要交换func
参数的顺序。
然后,如果我们想模仿take
,我们需要在x
小于length y
时添加n
,而不是更大!
所以我们得到了
myFunc :: Int -> [a] -> [a]
myFunc n list = foldl func [] list
where
func y x | (length y) < n = x : y
| otherwise = y
测试:
> myFunc 5 [1..10]
[5,4,3,2,1]
如您所见,这正在扭转字符串。这是因为我们在前面(x
)而不是在后面(x:y
)添加y++[x]
。或者,也可以使用reverse (foldl ....)
来修复最后的订单。
此外,由于foldl
总是扫描整个输入列表,myFunc 3 [1..1000000000]
将花费大量时间,而myFunc 3 [1..]
将无法终止。使用foldr
会好得多。
drop
更难处理。如果没有像myFunc n xs = fst (foldl ...)
这样的后处理或者让foldl
返回一个你立即调用的函数(这也是一种后处理),我认为你不能轻易做到这一点。
Ness是否会用take
实现foldr
。用drop
实现foldr
最不令人厌恶的方式是这样的:
drop n0 xs0 = foldr go stop xs0 n0
where
stop _ = []
go x r n
| n <= 0 = x : r 0
| otherwise = r (n - 1)
如果您别无选择,请考虑效率损失并重建整个列表!最好用螺丝刀钉入钉子,而不是用锤子拧入螺钉。
两种方式都很可怕。但是这个可以帮助您了解折叠如何用于构造函数以及它们的限制。
Folds只是不适合实施drop
的工具;一个paramorphism是正确的工具。
以上是关于使用`foldl`实现Haskell的`take`函数的主要内容,如果未能解决你的问题,请参考以下文章