添加元组的第一个元素列表

时间:2013-05-31 11:21:38

标签: haskell

我的结构定义如下:

type P = [(Int, Int)]

我需要从该结构的列表中创建一个函数,如果满足以下条件,则添加元组中第一个位置的项:元组的第二个元素是相同的。

add :: [P] -> P
add lists = ......

E.g。

add [[(1,2), (3,0)], [(3,1), (7,0)]]

结果为[(1,2), (3,1), (10.0)]

因为它只会添加元组(3.0)和(7.0),因为它匹配0。

4 个答案:

答案 0 :(得分:7)

这是一个“键控”操作,可以在标准库的帮助下轻松解决。

import qualified Data.Map as M

Data.Map实现有限关联映射 - 即M.Map k a有一组类型为k的键,并且每个键都关联一个a类型的值。< / p>

这类问题非常有用的功能是fromListWith

M.fromListWith :: (a -> a -> a) -> [(k,a)] -> M.Map k a

第二个参数,(k,a)元组列表,只是关联,将给定键与给定值相关联。第一个参数是组合函数,它表示如果列表中出现重复键,该如何对值进行操作。

您还必须使用M.toList :: M.Map k a -> [(k,a)],它会返回存储在地图中的关联列表。这是一个例子:

ghci> M.toList (M.fromListWith (+) [(1,2), (2,3), (1,4), (3,5)])
[(1,6),(2,3),(3,5)]

注意键是元组的第一个元素,与你如何陈述问题相反。我们已将(1,2)(1,4)合并为(1,6)。我们添加了因为我们提供了组合函数(+)

这一功能解决了你的问题首当其冲 - 剩下的只是一点管道,我会留给你。

答案 1 :(得分:4)

让我们分解这个问题。

所以你有一份清单

[
  [(1, 0), (2, 1), (3, 2)]
  [(2, 0), (3, 2), (4, 2)]
  [(5, 0), (1, 1), (9, 9)]
]

如果第二个元素全部(全部?或某些?)相同,则需要垂直第一个元素的总和,否则在第一个列表中的元组(根据您的示例判断,您的规范实际上并未说明会发生什么事。)

你要做的第一件事是transpose这个列表,以便得到更适合我们计算的东西。

[
  [(1, 0), (2, 0), (5, 0)]
  [(2, 1), (3, 2), (1, 1)]
  [(3, 2), (4, 2), (9, 9)]
]

现在问题基本上是每个这些子列表的折叠。因此,您编写了一个函数来为其中一个列表执行此操作:

collapse :: [(Int, Int)] -> (Int, Int)

然后你在整个转置列表上map这个函数:

add lists = map collapse $ transpose lists
// or point-free
add = map collapse . transpose

现在你只需要写collapse

答案 2 :(得分:1)

根据我的理解,您希望获取类型[[(Int, Int)]]的列表,并将具有相同第二元素的任何元组的第一个元素求和。您可以完全忽略它是一个列表列表,只是立即concat,除非列表的结构可以帮助您确定应添加哪些元素(但我似乎没有任何证据表明这一点)。然后得到最小snd,按snd值对列表进行排序,然后通过列表递归,调用span :: (a -> Bool) -> [a] -> ([a], [a]),它接受​​一个谓词,并在最后一个满足谓语。由于列表已排序,因此将捕获具有相同snd的所有元组。然后我们折叠那个元组列表。

add :: [[(Int, Int)]] -> [(Int, Int)]
add list = addT (minBound :: Int) $ sortBy sortF (concat list)
    where sortF (_,x) (_,y)
                | x < y  = LT
                | x > y  = GT
                | x == y = EQ
          addT n list = sumFunc $ span (sndIs n) list
              where sndIs n (_,y) = y == n 
                    sumFunc ([],b)= addT (n+1) b
                    sumFunc (a,[])= [(foldr (\(x,y) (w,v) -> (x+w,y)) (0,0) a)]
                    sumFunc (a,b) = (foldr (\(x,y) (w,v) -> (x+w,y)) (0,0) a):(addT (n+1) b)

然后:

> add [[(1,2), (3,0)], [(3,1), (7,0)]]
> [(10,0),(3,1),(1,2)]

唯一的问题是复杂性非常糟糕(尝试add [[(43, minBound :: Int), (10, maxBound :: Int)]])。如果你想让它变得更快,你应该首先获得所有snd并且只使用这些函数调用addT,如果你可能在不同的值上使用这个函数。

答案 3 :(得分:1)

合并和排序后(在第二个元素上),您可以使用group(By)的变体来组合具有相同第二个元素的元组。

import Data.List
import Data.Ord

add :: [P] -> P
add = combine . sortBy (comparing snd) . concat
  where
    combine []     = []
    combine (x:xs) = (fst x + foldr ((+) . fst) 0 ys, snd x) : combine zs
      where
        (ys, zs) = span ((snd x ==) . snd) xs

或者,您可以使用groupBy为每个不同的snd值获取一个列表,并折叠每个列表:

import Data.Function

add' :: [P] -> P
add' = map sumFst . groupBy ((==) `on` snd) . sortBy (comparing snd) . concat
  where
    sumFst = foldr1 $ \(a, b) (x, _) -> (a+x, b)
    -- or, importing Data.Biapplicative from bifunctors package:
    --sumFst = foldr1 $ (+) `biliftA2` const