我想编写一个函数,返回列表中至少出现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
答案 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)])
两次,5
和2
一次。现在您要添加另一个4
,因此您将元组的5
元素上的列表分区等于五。结果是一个元组,其中第一个元素是所有匹配(或命中),第二个元素是那些不匹配的值的列表。
使用fst
,您可以映射该元组的import Data.Bifunctor
元素。在该地图中,您有一个元组列表。实际上,这个列表要么是空的,要么只有一个元素,但它很容易处理任意数量的元组的一般情况:提取first
元素在所有snd
和hits
的数字上,然后为刚刚找到的新值添加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)]
两次。
我们可以将5
与tally
:
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]
[]
函数多次通过累加器,因此哪种方法更快,很可能取决于输入的大小和范围。
我必须承认,我没有测量任何选项...