为什么这个列表理解失败了?

时间:2013-09-10 20:45:59

标签: haskell list-comprehension

我正在尝试从这样的列表中选择唯一元素:

x = [1,1,2,3,4]
s = [e | e <- x, not (e `elem` s)]

它不会产生错误,但当我尝试从s读取时,程序似乎挂起了。为什么呢?

另外,这样做的正确方法是什么?

3 个答案:

答案 0 :(得分:17)

我不是一个Haskell-er,但这似乎你刚刚编写了类似 1 Russell's paradox的东西。您是否要求列出s的元素,其中的元素是x中的元素,而不是s中的元素?

s = [e | e <- [1,1,2,3,4], not (e `elem` s)]

因此,请考虑当您尝试询问s的第一个元素时会发生什么。好吧,e中的第一个元素是1,因此1将是s 的第一个元素,如果 not (1 `elem` s)。嗯,是(1 `elem` s)?我们可以通过迭代s的元素并查看1是否出现来进行检查。好吧,让我们从s ...

的第一个元素开始

通常假设某些ns的元素。那一定是真的吗? n必须是x(易于检查)的元素,并且不是 s的元素。但我们认为 s的一个元素。这是一个矛盾。因此,n可以不是s的元素,因此s必须是空列表。不幸的是,Haskell编译器没有做我们刚刚做的证明,它试图以编程方式计算s的元素。

要从列表中删除重复的项目,您需要Neil Brown在评论中推荐的功能nub来自Data.List

  

nub :: Eq a => [a] -> [a] Source

     

为O(n ^ 2)。 nub函数从列表中删除重复的元素。在   特别是,它只保留每个元素的第一次出现。 (该   名称nub表示'本质'。)这是nubBy的特例,允许   程序员提供自己的平等测试。


  1. 这不是实际上罗素的悖论; Russell的悖论是关于一个只包含那些包含自己的集合的集合。 那个集合不能存在,因为如果它包含自身,那么它不能包含它自己,如果它不包含它自己,那么它必须包含它自己。

答案 1 :(得分:8)

请注意,虽然Russel的Paradox有助于暗示这可能是不可计算的,但即使您将其更改为s = [e | e <- x, elem e s],它仍然会失败。

这是一个有益的手动扩展。对于任何非空列表,x

s = [e | e <- x, not (e `elem` s)]

简化为

s = do e <- x
       guard (not (e `elem` s))
       return e

s = x >>= \e -> if (not (e `elem` s)) then return e else mzero

s = concatMap (\e -> if (not (e `elem` s)) then [e] else []) x

s = foldr ((++) . (\e -> if (not (e `elem` s)) then [e] else [])) [] x

s = foldr (\e xs -> if (not (e `elem` s)) then (e:xs) else xs) [] x

s = foldr (\e ys -> if (e `elem` s) then ys else (e:ys)) [] x

然后我们可以开始评估。由于x非空,我们可以将其替换为x:xs并内联foldr

let f = (\e ys -> if (e `elem` s) then ys else (e:ys))

s = f x (foldr f [] xs)

s = (\ys -> if (x `elem` s) then ys else (x:ys)) (foldr f [] xs)

s = (\ys -> if (x `elem` f x (foldr f [] xs)) then ys else (x:ys)) (foldr f [] xs)

这是我们无限循环的地方 - 为了评估f x (foldr f [] xs),我们必须评估f x (foldr f [] xs)。你可能会说s的定义不够“足够有效”来启动它的自我递归。将此与技巧fibs定义

进行比较
fibs = 1:1:zipWith (+) fibs (tail fibs)

1:1:...启动,以便“足够高效”。然而,在s的情况下,没有(简单的)方法可以提高效率(请参阅Will Ness在下面的评论,以获得恶魔般的解决方法)。


如果我们没有那里,它只会切换if上的分支顺序,这是我们从未达到的分支。

答案 2 :(得分:1)

s = [e | (e:t) <- tails x, not (e `elem` t)]

上述并不是最有效的解决方案,而是展示了如何推理解决方案:为了只包含x的元素一次,我们需要确保它是x中的最后一个元素。这意味着我们可以搜索列表尾部的元素的出现。 Data.List.tails生成列表的所有子列表,因此我们可以包括子列表的头部,如果它没有出现在子列表的其余部分中 - 这是子列表的头部是最后一个这样的元素的条件在原始列表中。


如果使用该值的函数是严格的(急切的),则引用您定义的值可能会导致无终止计算。该函数是严格的,如果它总是需要参数的完整值以产生结果。

例如,length对列表元素的数量是严格的 - 但不一定是列表的实际元素。所以length [[i..] | i <- [1..10]]终止而不计算列表中元素的值(无限列表。但是,length [[i..] | i <- [1..]]不会终止,因为为了返回结果,它需要计算所有元素的存在,它永远不会以开放范围结束。

然而,

gtlength :: Int -> [a] -> Ordering
gtlength n [] = n `compare` 0
gtlength 0 xs = GT
gtlength n xs = gtlength (n-1) $ tail xs

甚至可以终止无限列表,因为它不需要评估整个列表。

你的功能因为elem严格而挂起。为了测试元素的不存在,它需要评估整个列表,这是不可用的。