我有一个定义如下的结构:
type P = [(Int, Int)]
我需要从该结构的列表中创建一个函数,如果满足以下条件,则添加位于元组中第一个位置的项目:元组的第二个元素相同。
add :: [P] -> P
add lists = ......
例如
add [[(1,2), (3,0)], [(3,1), (7,0)]]
结果将是[(1,2), (3,1), (10.0)]
。
因为只会将元组 (3.0) 与 (7.0) 添加,因为它与 0 匹配。
我有一个定义如下的结构:
type P = [(Int, Int)]
我需要从该结构的列表中创建一个函数,如果满足以下条件,则添加位于元组中第一个位置的项目:元组的第二个元素相同。
add :: [P] -> P
add lists = ......
例如
add [[(1,2), (3,0)], [(3,1), (7,0)]]
结果将是[(1,2), (3,1), (10.0)]
。
因为只会将元组 (3.0) 与 (7.0) 添加,因为它与 0 匹配。
这是一个“键控”操作,在标准库的帮助下很容易解决。
import qualified Data.Map as M
Data.Map
实现有限关联映射——即M.Map k a
有一组类型的键k
,并且它与每个键关联一个类型的值a
。
对这类问题非常有用的功能是fromListWith
:
M.fromListWith :: (a -> a -> a) -> [(k,a)] -> M.Map k a
第二个参数,(k,a)
元组列表,只是简单的关联,将给定的键与给定的值相关联。第一个参数是一个组合函数,它说明如果重复键出现在列表中,如何处理这些值。
您还必须使用M.toList :: M.Map k a -> [(k,a)]
which 为您返回存储在 Map 中的关联列表。这是一个例子:
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, 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
.
合并和排序后(在第二个元素上),您可以使用变体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
据我了解,您想要获取一个类型列表[[(Int, Int)]]
并将具有相同第二个元素的任何元组的第一个元素相加。您可以完全忽略它是一个列表列表,并且concat
马上就可以了,除非列表的结构可以帮助您确定应该添加哪些元素(但我似乎没有任何证据表明这一点)。然后你得到 minimum 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
首先得到所有的 s 并且只调用 addT 那些,如果你可能在值上使用这个函数有很大的不同。