Haskell函数可保留列表的重复元素

时间:2018-12-01 11:24:15

标签: list sorting haskell filter

以下是预期的输入/输出:

  

重复的“密西西比州” ==“ ips”

     

重复[1,2,3,4,2,5,6,7,1] == [1,2]

     

重复“” ==“”

这是到目前为止的代码:

repeated :: String -> String
repeated "" = "" 
repeated x = group $ sort x 

我知道代码的最后一部分不起作用。我当时正在考虑对列表进行排序,然后将其分组,然后我想对大于1或类似名称的列表进行过滤。

3 个答案:

答案 0 :(得分:4)

您的代码已经完成了一半的工作

> group $ sort "Mississippi"
["M","iiii","pp","ssss"]

您说过要过滤掉重复项。让我们定义一个谓词,该谓词标识具有至少两个元素的列表:

atLeastTwo :: [a] -> Bool
atLeastTwo (_:_:_) = True
atLeastTwo _       = False

使用此:

> filter atLeastTwo . group $ sort "Mississippi"
["iiii","pp","ssss"]

好。现在,我们只需要从此类列表中获取第一个元素。由于列表不是空的,因此我们可以安全地使用head

> map head . filter atLeastTwo . group $ sort "Mississippi"
"ips"

或者,我们可以将过滤器替换为filter (\xs -> length xs >= 2),但这会降低效率。

另一个选择是使用列表理解

> [ x | (x:_y:_) <- group $ sort "Mississippi" ]
"ips"

此模式在以x开头并至少具有另一个元素_y的列表上匹配,将过滤器和获取头组合在一起。

答案 1 :(得分:2)

好的,好的开始。一个直接的问题是,规范要求该函数在数字列表上起作用,但是您为字符串定义了该函数。该列表必须排序,因此其元素必须具有类型Ord。因此,让我们修复类型签名:

repeated :: Ord a => [a] -> [a]

调用sortgroup之后,您将获得列表列表[[a]]。让我们来考虑使用filter的想法。这样可行。如您所说,谓词应检查列表中每个列表的length,然后将该length与1进行比较。

过滤列表列表将为您提供[[a]]类型的子集,这是列表的另一个列表。您需要展平此列表。您想要做的是将列表列表中的每个条目map指向其元素之一。例如第一个。 Prelude中有一个功能可以做到这一点。

因此,您可以填写以下框架:

module Repeated (repeated) where

import Data.List (group, sort)

repeated :: Ord a => [a] -> [a]
repeated = map _
         . filter (\x -> _)
         . group
         . sort 

我以无谓风格编写了此语句,并将过滤谓词作为lambda表达式,但是其他许多编写方法也同样不错。找到您喜欢的一个! (例如,您还可以以无点形式编写filter谓词,它由两个函数组成:比较length的结果。)

当您尝试对此进行编译时,编译器会告诉您有两个型孔,即等号右边的_条目。它还会告诉您孔的类型。第一个孔需要一个函数,该函数需要一个列表并为您提供单个元素。第二个孔需要使用x的布尔表达式。正确填写这些内容,您的程序即可运行。

答案 2 :(得分:0)

还有其他一些方法,可以使用group $ sort来评估@chepner对解决方案的评论。 (这些解决方案看起来更简单,因为某些复杂性隐藏在库例程中。)

  

虽然排序确实是O(n lg n),...

使用group的不仅是排序,而且还特别是span:它们都可以构建和销毁临时列表。即他们这样做:

  

未排序列表的线性遍历将需要一些其他数据结构来跟踪所有可能的重复项,并且每个中的查找至少会增加空间复杂性。虽然可以使用精心选择的数据结构来维持总体O(n)运行时间,但常数可能会使该算法在实践中比O(n lg n)解决方案要慢,...

group/span大大增加了这种复杂性,因此O(n lg n)不是正确的度量。

  

同时大大简化了实现。

以下所有内容仅遍历输入列表一次。是的,他们建立了辅助清单。 (可能是Set可以提供更好的性能/更快速的查找。)它们可能看起来更复杂,但是要比较Apple与Apple,还可以查看group/span的代码。

repeated2, repeated3, repeated4 :: Ord a => [a] -> [a]
  • repeated2/inserter2构建[(a, Bool)]对的辅助列表,如果Bool出现多次,Truea {1}},如果到目前为止只有一次。

    False
  • repeated2 xs = sort $ map fst $ filter snd $ foldr inserter2 [] xs inserter2 :: Ord a => a -> [(a, Bool)] -> [(a, Bool)] inserter2 x [] = [(x, False)] inserter2 x (xb@(x', _): xs) | x == x' = (x', True): xs | otherwise = xb: inserter2 x xs 建立repeated3/inserter3对的辅助列表,其中[(a, Int)]对出现的Int进行计数。辅助列表无论如何都只是为了它的排序而排序。

    a
  • repeated3 xs = map fst $ filter ((> 1).snd) $ foldr inserter3 [] xs inserter3 :: Ord a => a -> [(a, Int)] -> [(a, Int)] inserter3 x [] = [(x, 1)] inserter3 x xss@(xc@(x', c): xs) = case x `compare` x' of { LT -> ((x, 1): xss) ; EQ -> ((x', c+1): xs) ; GT -> (xc: inserter3 x xs) } 构建已知重复的元素的输出列表。它遍历输入列表时维护了一次(到目前为止)遇到的元素的中间列表。如果遇到重复:将其添加到输出列表;从中间列表中删除它;将该元素从输入列表的尾部过滤掉。

    repeated4/go4

    repeated4 xs = sort $ go4 [] [] xs go4 :: Ord a => [a] -> [a] -> [a] -> [a] go4 repeats _ [] = repeats go4 repeats onces (x: xs) = case findUpd x onces of { (True, oncesU) -> go4 (x: repeats) oncesU (filter (/= x) xs) ; (False, oncesU) -> go4 repeats oncesU xs } findUpd :: Ord a => a -> [a] -> (Bool, [a]) findUpd x [] = (False, [x]) findUpd x (x': os) | x == x' = (True, os) -- i.e. x' removed | otherwise = let (b, os') = findUpd x os in (b, x': os') 中最后的列表修饰与findUpd非常相似。)