至少出现在列表中

时间:2018-06-16 10:22:36

标签: haskell

我想编写一个函数,返回列表中至少出现n次的元素列表。它应该像这样工作:

ghci> atLeast [4,5,2,5,4,3,1,3,4] 2

输出:[5,3,4]

我的代码:

count:: Eq a => a -> [a] -> Int
count n [] = 0
count n (h:t) | n == h = 1 + count n t
              | otherwise = count n t

atLeast :: [Int] -> Int -> [Int]
atLeast list min = nub $ filter (\b-> count b list >= min) list

2 个答案:

答案 0 :(得分:0)

一种更有效的方法来编写它,它给出了你指定的结果:

atLeast :: [Int] -> Int -> [Int]
atLeast list min = map head $ filter (\list -> length list >= min) $ groupBy (==) $ sortBy (flip compare) list

例如

> atLeast [4,5,2,5,4,3,1,3,4] 2                                                                                                                                                                   
[5,4,3]

答案 1 :(得分:0)

撰写标准功能

正如 pikapika 在另一个答案中指出的那样,通过从基础库中编写函数,可以实现所需的结果。

乍一看,来自group的{​​{1}}这样的函数看起来很有希望,但问题是它只对连续运行进行分组:

Data.List

为了获得所需的行为,您首先必须对输入进行排序:

Prelude Data.List> group [4,5,2,5,4,3,1,3,4]
[[4],[5],[2],[5],[4],[3],[1],[3],[4]]
Prelude Data.List> group [4,4,2,5,5,3,1,3,4]
[[4,4],[2],[5,5],[3],[1],[3],[4]]

那更好:现在你可以将它分组:

Prelude Data.List> sort [4,5,2,5,4,3,1,3,4]
[1,2,3,3,4,4,4,5,5]

但请注意,这需要对数据进行两次传递。列表的第一个传递是Prelude Data.List> group $ sort [4,5,2,5,4,3,1,3,4] [[1],[2],[3,3],[4,4,4],[5,5]] 。第二遍是你sort第一遍的输出。

不幸的是,你还没有完成。现在您需要过滤该列表列表:

group

或者,如果你更喜欢无点样式,你可以 eta reduce

Prelude Data.List> filter (\xs -> 2 <= length xs) $ group $ sort [4,5,2,5,4,3,1,3,4]
[[3,3],[4,4,4],[5,5]]

最后,您需要提取每个嵌套列表的公共值。一般来说,虽然Prelude Data.List> filter ((2 <=) . length) $ group $ sort [4,5,2,5,4,3,1,3,4] [[3,3],[4,4,4],[5,5]] 不安全,但我们应该确信没有一个列表是空的:

head

关于此实现的一个细节是值按升序到达。

这种实现对于大多数用途来说可能已经足够好了,但可能被认为效率低下,至少有三次列表遍历。对于冗长的列表,这可能是一个问题。

要明确的是,与讨论的关键绩效一如既往:衡量!

算作折叠

对于简短列表,以下几点效率不高,但对于具有较少可能值的长列表,使用较少的传递可能会更好。所以,让我们看看我们是否可以做到这一点。

我将使用的策略是累积一个元组列表,其中每个元组将包含该值以及它被观察的次数。对于OP输入值Prelude Data.List> map head $ filter ((2 <=) . length) $ group $ sort [4,5,2,5,4,3,1,3,4] [3,4,5] ,我们需要这样的内容作为中间结果:

[4,5,2,5,4,3,1,3,4]

首先,我们需要一个向累加器添加数字的函数。当累积开始时,列表将为空,但稍后,累加器中可能已存在一个数字。查找数字的一种方法是使用[(4,3),(5,2),(2,1),(3,2),(1,1)] 函数:

partition

在这里,您有一个部分累积的数字列表,表示您已经遇到*Q50887054> partition ((5 ==) . fst) [(5,2),(2,1),(4,1)] ([(5,2)],[(2,1),(4,1)]) 两次,52一次。现在您要添加另一个4,因此您将元组的5元素上的列表分区等于五。结果是一个元组,其中第一个元素是所有匹配(或命中),第二个元素是那些不匹配的值的列表。

使用fst,您可以映射该元组的import Data.Bifunctor元素。在该地图中,您有一个元组列表。实际上,这个列表要么是空的,要么只有一个元素,但它很容易处理任意数量的元组的一般情况:提取first元素在所有sndhits的数字上,然后为刚刚找到的新值添加sum。创建一个新计数的元组,并将要添加的数字作为键:

1

这将返回一个元组,其中第一个元素是一个值,第二个元素是一个列表。您可以取消cons运算符以从该元组中生成单个列表:

*Q50887054> first (\hits -> (5, (sum $ snd <$> hits) + 1)) $ partition ((5 ==) . fst) [(5,2),(2,1),(4,1)]
((5,3),[(2,1),(4,1)])

以下是您import Data.List import Data.Bifunctor tally :: (Num t, Eq a) => a -> [(a, t)] -> [(a, t)] tally k = uncurry (:) . first (\hits -> (k, sum (snd <$> hits) + 1)) . partition ((k ==) . fst) 列表tally的开始方式。首先是空累加器和列表中的第一个值[4,5,2,5,4,3,1,3,4]

4

累加器现在为*Q50887054> tally 4 [] [(4,1)] ,表示您已经看过[(4,1)]一次。转到输入列表4中的下一个元素,现在5使用累加器:

tally

现在,您继续将新累加器与列表的其他值一起使用:

*Q50887054> tally 5 [(4,1)]
[(5,1),(4,1)]

请注意,此时我们已经看过*Q50887054> tally 2 [(5,1),(4,1)] [(2,1),(5,1),(4,1)] *Q50887054> tally 5 [(2,1),(5,1),(4,1)] [(5,2),(2,1),(4,1)] 两次。

我们可以将5tally

一起使用,而不是手动执行此操作
foldl'

我们还没有完成,但现在我们有一个列表,我们可以轻松*Q50887054> foldl' (\acc x -> tally x acc) [] [4,5,2,5,4,3,1,3,4] [(4,3),(3,2),(1,1),(5,2),(2,1)] filter

map

稍微抛光代码,你也可以使它成为一个函数:

*Q50887054> map fst $ filter ((2 <=) . snd) $ foldl' (\acc x -> tally x acc) [] [4,5,2,5,4,3,1,3,4]
[4,3,5]

以下是一些例子:

atLeast :: (Ord a, Num a, Eq b, Foldable t) => a -> t b -> [b]
atLeast n = map fst . filter ((n <=) . snd) . foldl' (flip tally) []

与上面的编写标准函数的解决方案相比,这个实现只传递了输入列表两次:一次折叠,一次映射和过滤。

另一方面,*Q50887054> atLeast 1 [4,5,2,5,4,3,1,3,4] [4,3,1,5,2] *Q50887054> atLeast 2 [4,5,2,5,4,3,1,3,4] [4,3,5] *Q50887054> atLeast 3 [4,5,2,5,4,3,1,3,4] [4] *Q50887054> atLeast 4 [4,5,2,5,4,3,1,3,4] [] 函数多次通过累加器,因此哪种方法更快,很可能取决于输入的大小和范围。

我必须承认,我没有测量任何选项...