使用foldl实现Haskell的take函数

时间:2019-04-04 18:34:43

标签: list haskell fold foldleft

使用take实现Haskell的dropfoldl函数。

关于如何使用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

4 个答案:

答案 0 :(得分:2)

无法完成。

左折叠在无限列表上必定会有所不同,但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 ,直至归约化函数g的参数顺序,使它可以访问当前元素{{1} }和当前列表节点x(例如xs)以及递归结果xs == (x:t)同构的归约器只能访问rx

折叠通常会编码同构,但是这表明正确的折叠也可以用来编码同形。这样通用。我认为它很漂亮。

对于类型错误,要修复该错误,只需将参数切换到您的r

func

left 折叠中的累加器是reducer函数的 first 参数。


如果您真的希望用左折完成它,并且确实确保列表是有限的,则有两个选择:

       func y x | ..... = .......

第一个从左开始一直向上计数,可能会在整个列表遍历的中间停止组装前缀,但实际上会一直拖到最后,即为左折。

第二个也将列表完全遍历,将其变成右折,然后 then 从左开始递减计数,从而能够在组合前缀后立即停止工作。

以这种方式实施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 _ = [] 必然会变得更加笨拙(?)。可能是一个很好的练习。

答案 1 :(得分:2)

我注意到,您从未指定折叠必须在提供的列表上方。因此,可以满足您问题的字母的一种方法(虽然可能不是这种精神)是:

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或{{1给定drop n的}}函数。然后,此功能将应用于原始提供的列表n

这些版本在无限列表上都可以正常工作:

xs

答案 2 :(得分:0)

你不太远。这是一对修复程序。

首先,请注意,func首先传递给累加器(在您的情况下,就是a的列表),然后是list元素(a)。因此,您需要交换func的参数顺序。

然后,如果我们想模仿take,则当x小于{em> 而不大于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返回立即调用的函数(这也是一种后处理),您就不会轻易做到这一点。

答案 3 :(得分:0)

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)

承担效率损失,如果您别无选择,请重新构建整个列表!用螺丝刀拧入钉子比用锤子拧入螺丝更好。

这两种方法都很可怕。但这可以帮助您了解如何使用折叠来构造函数以及它们的极限是什么。

折叠不是实现drop的正确工具; paramorphism 是正确的工具。