test :: [String] -> [String]
test = foldr step []
where step x ys
| elem x ys = x : ys
| otherwise = ys
我正在尝试构建一个包含所有输入的不同字符串的新列表。我的测试数据是:
test ["one", "one", "two", "two", "three"]
预期结果:
["one", "two", "three"]
我是Haskell的新手,我确信我错过了一些非常基础和明显的东西,但是已经没有办法探索这个问题了。你能指出我的想法不足的地方吗?
实际回复是[]
。似乎永远不会遇到第一个保护条件(如果我用True
替换它,原始列表被复制),所以输出列表永远不会被构建。
我的理解是,折叠会累积列表中每个项目的步骤结果,并将其添加到空列表中。我预计该步骤将测试每个项目是否包含在输出列表中(测试的第一个元素不在那里)并将添加尚未包含在输出列表中的任何内容。显然不是: - )
答案 0 :(得分:7)
您的推理是正确的:您只需要切换= x : ys
和= ys
,以便在不 {{1}的元素时添加x
}}。此外,ys
完成了这一切。
答案 1 :(得分:7)
考虑一下:你的代码在说“当x在余数中时,在结果前面加x”,即创建副本。您只需要将其更改为“当x不在余数中时,将x加到结果之前”并获得正确的函数。
此功能与Data.List.nub
的重要区别在于:此功能更严格。因此:
test [1..] = _|_ -- infinite loop (try it)
nub [1..] = [1..]
nub
正确地给出了无限列表的答案 - 这意味着它不需要整个列表来启动计算结果,因此它是流处理游戏中的一个不错的播放器。
它是严格的原因是因为elem
是严格的:它在返回结果之前搜索整个列表(假设它没有找到匹配项)。你可以这样写:
nub :: (Eq a) => [a] -> [a]
nub = go []
where
go seen [] = []
go seen (x:xs) | x `elem` seen = go seen xs
| otherwise = x : go (x:seen) xs
注意seen
如何像输出一样增长,而你的增长就像输出的剩余部分。前者始终是有限的(从[]
开始并一次添加一个),而后者可能是无限的(例如[1..]
)。因此,这种变体可以更懒惰地产生元素。
如果您使用Data.Set
而不是seen
的列表,则会更快(O(n log n)而不是O(n ^ 2))。但它增加了Ord
约束。