按fst过滤并按对的snd计算

时间:2014-03-25 00:33:24

标签: haskell

我想定义一个函数pointCounts,它取一个第一个成员是名字的对的列表,然后是每个名字的计数点对的点值和返回列表。

我为此奋斗了好几天,但我无法想象如何做到这一点。

输入示例应如下所示:

pointCount [("Ferp",25),("Herp",18),("Derp",15),("Ferp",25),("Herp",15),("Derp",18),("Jon",10)]

所需的输出示例:

[("Ferp",50),("Herp",33),("Derp",33),("Jon",10)] 

3 个答案:

答案 0 :(得分:6)

我使用Data.Map作为中间数据结构:

import qualified Data.Map as M

pointCount :: Num a => [(String, a)] -> [(String, a)]
pointCount = M.toList . foldr f M.empty
    where f (name, val) = M.insertWith (+) name val
          -- or pointfree
          -- f = uncurry (M.insertWith (+))

甚至更好(正如Daniel Wagner指出的那样)

pointCount = M.toList . M.fromListWith (+)

答案 1 :(得分:1)

这是另一种使用比列表更具异国情调的解决方案。但cdk的解决方案更简单,运行时间更长。

import Data.List

pointCount :: Num a => [(String, a)] -> [(String, a)]
pointCount [] = []
pointCount ((x, n):xs) =
    let (eqx, neqx) = partition ((==x).fst) xs in
        (x, n + (sum$ map snd eqx)) : pointCount neqx

答案 2 :(得分:1)

这是我怎么做的,假设结果的顺序并不重要,而且只用在小清单上(即效率并不重要) :

import           Data.Ord      (comparing)
import           Data.Function (on)
import           Data.List     (groupBy, sortBy, foldl1')

pointCount :: Num a => [(String, a)] -> [(String, a)]
pointCount =   map     (foldl1' sumSecond)
             . groupBy ((==) `on` fst)
             . sortBy  (comparing fst)
  where
    sumSecond :: Num a => (String, a) -> (String, a) -> (String, a)
    sumSecond (_, accum) (name, v) = (name, accum + v)

这是另一个可能的(类似的)解决方案,利用求和的半群性质,找到非空列表的第一项和半群对,以及得到半群的事实当你组成两个半群时(使用semigroupssemigroupoids包):

import           Data.Ord                  (comparing)
import           Data.Function             (on)
import           Data.List                 (groupBy, sortBy)
import           Data.Semigroup            (First (..), Sum (..))
import           Data.Semigroup.Foldable   (foldMap1)
import qualified Data.List.NonEmpty as NE
import           Control.Arrow             ((***))

pointCount :: Num a => [(String, a)] -> [(String, a)]
pointCount =   map     ( unpackResult
                       . foldMap1 packSemigroup
                       . NE.fromList
                       )
             . groupBy ((==) `on` fst)
             . sortBy  (comparing fst)
  where
    packSemigroup :: Num a => (String, a) -> (First String, Sum a)
    packSemigroup   = First *** Sum

    unpackResult  :: Num a => (First String, Sum a) -> (String, a)
    unpackResult = getFirst *** getSum

我可能会选择第一个解决方案,但第二个解决方案说明了如何将问题的本质视为对半群组合的操作。该操作具体是半群同态,由unpackResult . foldMap1 packSemigroup部分表示。