我想知道如何使用Haskell列表理解从列表中获取唯一值。因此,如果我输入[2,4,5,4,4,6,2],它将返回[2,4,5,6]。
最初我开始使用unique(y:ys)= [x | x< - (y:ys)]我知道我需要x上的其他条件,但我不知道如何到达那里。
答案 0 :(得分:9)
来自@amalloy的评论列表理解局限于“本地”视角是这里的关键见解。将nub
写成列表理解是一种明智的方法,但首先需要改变观点。
遗憾地从库中省略的一个常用函数是用它的上下文装饰列表的每个元素的函数。
picks :: [x] -> [([x], x, [x])]
picks [] = []
picks (x : xs) = ([], x, xs) : [(x : bs, y, as) | (bs, y, as) <- picks xs]
所以
picks [1,2,3] =
[([],1,[2,3]), ([1],2,[3]), ([1,2],3,[])]
列表的每个元素都放在三元组的中间,左边是元素'before',右边是元素'。
This answer of mine解释了深层结构,它使picks
在某种意义上成为一种“标准”操作,可以从列表结构中得出。但我们不需要背景信息来部署它。
picks
函数准确地为我们提供了编写nub
作为列表理解所需的上下文信息。我们需要做的就是挑选出他们自己的“前列表”中没有出现的元素。
myNub :: Eq x => [x] -> [x]
myNub xs = [x | (bs, x, as) <- picks xs, not (elem x bs)]
我对此操作的效率没有任何承诺,但我确实喜欢将列表理解与额外空间背景相结合的清晰度。
答案 1 :(得分:0)
你可以用一种懒惰的方式(也许是不必要的聪明)来做,从一些循环推理开始:输入的每个元素应该出现在输出中,只有它没有出现在输出中。 / p>
也就是说,对于像[0, 0, 1]
这样的输入列表,应添加第一个0
,但不应添加第二个0
。
显然,这样的事情是行不通的:
unique xs = us
where us = [x | x <- xs, x `notElem` us]
因为它会陷入无限循环,试图测试尚未生成的输出元素。你可以做的是改变推理:输入的每个元素都应该出现在输出中,只有当输出中没有已经时才会出现。
您可以通过考虑“已经”含义直接实现此目的:当前值不得出现在当前索引之前的索引。
unique xs = catMaybes us
where
us =
[ if Just x `elem` take i us -- If the element has appeared before here
then Nothing -- then don’t include it again
else Just x -- otherwise do include it.
| (i, x) <- zip [0..] xs -- (Zip the elements with their indices.)
]
因此,对于输入列表xs = [0, 0, 1]
,这将生成xs' = [Just 0, Nothing, Just 1]
,catMaybes
会将其展平为[0, 1]
。使用QuickCheck进行测试确认这相当于nub
,并暂停,因为我们只检查每一步take i
的{{1}}个元素,确保我们不检查任何元素尚未生成。
值得注意的是,与us
一样,输入的长度为O(n 2 )。