对于给定的整数n,下面的代码保留列表中的前n个项目,删除以下n个项目,保留以下n项,依此类推。它适用于任何有限列表。
为了使其可用于无限列表,我使用'seq'运算符在递归步骤之前强制累加器评估,如foldl'中所示。
我通过跟踪累加器的值进行了测试,看起来它是按照有限列表的需要进行有效计算的。
然而,当应用于无限列表时,它不起作用。主函数中的“take”仅在内部计算终止时执行,当然,无限列表中不会发生这种情况。
拜托,有人能告诉我我的错误在哪里吗?
main :: IO ()
main = print (take 2 (foo 2 [1..100]))
foo :: Show a => Int -> [a] -> [a]
foo l lst = inFoo keepOrNot 1 l lst []
inFoo :: Show a => (Bool -> Int -> [a] -> [a] -> [a]) -> Int -> Int -> [a] -> [a] -> [a]
inFoo keepOrNot i l [] lstOut = lstOut
inFoo keepOrNot i l lstIn lstOut = let lstOut2 = (keepOrNot (odd i) l lstIn lstOut) in
stOut2 `seq` (inFoo keepOrNot (i+1) l (drop l lstIn) lstOut2)
keepOrNot :: Bool -> Int -> [a] -> [a] -> [a]
keepOrNot b n lst1 lst2 = case b of
True -> lst2 ++ (take n lst1)
False -> lst2
答案 0 :(得分:6)
以下是列表并置的实现方式:
(++) :: [a] -> [a] -> [a]
(++) [] ys = ys
(++) (x:xs) ys = x : xs ++ ys
请注意
这意味着如果您使用++
来构建列表,则希望累加器位于右侧。 (对于有限列表,仅出于效率原因 - 如果累加器在左侧,它将被重复复制,这是低效的。对于无限列表,调用者不能查看结果的第一个元素,直到它最后一次被复制,并且不会有最后一次,因为总有一些东西可以连接到累加器的右边。)
True
的{{1}}案例在keepOrNot
左侧有累加器。您需要使用不同的数据结构。
在这种情况下通常的习惯用法是使用差异列表。不要使用++
类型作为累加器,而是使用[a]
。您的累加器现在是一个函数,将列表添加到作为输入的列表中。这样可以避免重复复制,并且可以懒惰地构建列表。
[a] -> [a]
累加器的初始值应为keepOrNot :: Bool -> Int -> [a] -> ([a] -> [a]) -> ([a] -> [a])
keepOrNot b n lst1 acc = case b of
True -> acc . (take n lst1 ++)
False -> acc
。如果要将其转换为常规列表,请使用id
(即[]
)进行调用。
acc []
在这里是一个红鲱鱼。 seq
不强制整个列表。 seq
仅确定其格式是seq
还是[]
。
你正在学习Haskell,是吗?因此,修改代码以使用差异列表累加器是一个好主意。可能使用无限列表会使您陷入代码的不同部分;我不知道。
但是有更好的方法来编写x : xs
。
foo
答案 1 :(得分:3)
因此,您希望将列表分组为n
元素组,并删除其他所有组。我们可以直接写下来:
import Data.List (unfoldr)
groups n xs = takeWhile (not.null) $ unfoldr (Just . splitAt n) xs
foo c xs = concatMap head . groups 2 . groups c $ xs
dave4420已经解释了你的代码有什么错误,但我想评论你如何到那里,IMO。您的keepOrNot :: Bool -> Int -> [a] -> [a] -> [a]
功能过于笼统。它根据收到的Bool
,任何 Bool
工作;但你知道你将为它提供一系列交替的True
和False
值。使用函数编程就像将管道插入漏斗 - 一个函数的输出作为下一个函数的输入 - 漏斗在这里太宽,因此接触松动。
沿着这些行重新编写代码可能是
foo n lst = go lst
where
go lst = let (a,b) = splitAt n lst
(c,d) = splitAt n b
in
a ++ go d
联系人“紧”,这里没有“信息泄露”。我们自己只做两次 (*)的工作,并在此代码中明确地“连接管道”,获取一个结果(a
)并删除其他(c
)。
-
(*) 两次,反映两个布尔值True
和False
,以一种简单的方式交替一个接一个地。因此,这在代码的结构中被冻结,而不是作为能够容纳任意布尔值的参数而松散。
答案 2 :(得分:1)
就像dava4420所说,你不应该使用(++)
从左边积累。但也许你根本不应该积累!在Haskell中,懒惰使得直接的“头部构造”通常比在例如你需要使用的尾部递归更有效。 Lisp的。例如:
foo :: Int -> [a] -> [a] -- why would you give this a Show constraint?
foo ℓ = foo' True
where foo' _ [] = []
foo' keep lst
| keep = firstℓ ++ foo' False other
| otherwise = foo' True other
where (firstℓ, other) = splitAt ℓ lst