函数像catMaybes,但计算Nothing值

时间:2014-08-27 18:37:53

标签: list haskell maybe

我有一个这样的清单:

let foo = [Just 1, Just 2, Nothing, Just 3, Nothing, Nothing]

使用catMaybes我只能提取Just - 构造的值:

catMaybes foo -- [1,2,3]

我现在正在寻找一个函数,它不仅可以生成Just的列表,还可以通过遍历一次来获得有限列表的Nothing个计数。它应该有这样的签名:

catMaybesCount :: [Maybe a] -> ([a], Int)

注意:这个问题已经回答Q& A风格,因此故意不会展示任何研究成果!

6 个答案:

答案 0 :(得分:23)

import Data.Monoid
import Data.Foldable

catMaybesCount = foldMap inject where
    inject Nothing  = ([ ], Sum 1)
    inject (Just x) = ([x], Sum 0)

答案 1 :(得分:5)

我们可以有一个左折叠用于严格计数和一个右折叠同时建立懒惰列表:

catMC :: (Num t) => [Maybe a] -> ([a], t)
catMC xs = g 0 xs 
  where 
    g !c (Nothing:xs) = g (c+1) xs 
    g !c (Just  v:xs) = let (a,b)=g c xs in (v:a,b) 
    g !c []           = ([],c)

这也适用于无限列表,只要我们不访问结果的计数字段(snd),同时以严格,有效的方式计算计数,就像可变累加器变量一样。

答案 2 :(得分:4)

我的选择只是foldr

import Control.Arrow

catMaybesCount :: [Maybe a] -> ([a], Int)
catMaybesCount = foldr (maybe (second succ) (first . (:))) ([], 0)

在这种情况下左右折叠都有利有弊,因为右侧折叠使得列表结果适当懒惰和高效,而严格左折叠则更有效地计算长度结果。

答案 3 :(得分:3)

我会使用Writer monad:

import Control.Arrow ( (***) )
import Data.Monoid ( Sum(..) )
import Control.Monad.Writer ( execWriter, tell )

catMaybesCount xs = (id *** getSum) $ execWriter (mapM_ go xs)
   where
      go (Just v) = tell ([v], Sum 0)
      go Nothing = tell ([], Sum 1)

根据(++)的定义,mapM_应该正确关联。

答案 4 :(得分:2)

最天真的解决方案是单独进行两种评估:

catMaybesCount :: [Maybe a] -> ([a], Int)
catMaybesCount xs = (catMaybes xs, length $ filter isNothing xs)

我不知道GHC是否能够正确地优化这一点,但length . filter p计数Nothings的解决方案仍有一些特点(请参阅this SO post了解概述)。

理论上,此解决方案可能需要在列表上进行两次传递,而不是一次

这是解决我提出的这个问题的递归解决方案:

import Data.Maybe

-- | Equivalent to @catMaybes@, but additonally counts @Nothing@ values
catMaybesCount :: [Maybe a] -> ([a], Int)
catMaybesCount xs =  catMaybesCountWorker xs [] 0

-- | Worker function for @catMaybesCount@   
catMaybesCountWorker :: [Maybe a] -> [a] -> Int -> ([a], Int)
catMaybesCountWorker [] justs cnt = (justs, cnt)
catMaybesCountWorker (Nothing:xs) justs cnt =
    catMaybesCountWorker xs justs (cnt + 1)
catMaybesCountWorker ((Just v):xs) justs cnt =
    catMaybesCountWorker xs (justs ++ [v]) cnt

将其应用于列表时,应仅对列表进行一次评估,这应该更有效。

但是我担心justs ++ [v]反成语,因为(:)会更有效率(见this discussion)。但是,这会反转结果列表。也许对此主题有更多了解的人可以看看它?

请注意,此函数不会因无限列表而终止,因为Nothing计数永远不会完成评估。

答案 5 :(得分:2)

对于这样的案例,Gabriel Gonzalez的foldl套餐非常方便。您可以简单地使用预定义的折叠或定义下面的自定义折叠,并使用应用程序界面将它们组合在一起:

import qualified Control.Foldl as L
import Control.Applicative ((<$>),(<*>))
import Data.Monoid
import qualified Data.DList as DL

catMaybesCount :: [Maybe a] -> ([a], Int)
catMaybesCount = L.fold $ (,) <$> elemJust <*> countJust

-- L.Fold :: (x -> a -> x) -> x -> (x -> b) -> L.Fold a b

elemJust :: L.Fold (Maybe a) [a]
elemJust = L.Fold step DL.empty DL.toList
  where step xs (Just x) = DL.snoc xs x
        step xs Nothing = xs

countJust :: L.Fold (Maybe a) Int
countJust = L.Fold step (Sum 0) getSum
  where step acc (Just _) = acc
        step acc Nothing = acc <> Sum 1