缺乏理解无限列表和seq运算符

时间:2012-10-23 08:14:11

标签: haskell lazy-evaluation

对于给定的整数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

3 个答案:

答案 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工作;但你知道你将为它提供一系列交替的TrueFalse值。使用函数编程就像将管道插入漏斗 - 一个函数的输出作为下一个函数的输入 - 漏斗在这里太宽,因此接触松动。

沿着这些行重新编写代码可能是

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)。

-
(*) 两次,反映两个布尔值TrueFalse,以一种简单的方式交替一个接一个地。因此,这在代码的结构中被冻结,而不是作为能够容纳任意布尔值的参数而松散。

答案 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