我的结构定义如下:
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。
答案 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