我想创建一个带有列表的函数,例如
[('A',3), ('B',2), ('C',2), ('A',5), ('C',3), ('C',2)]
然后在字母相同时将数字加在一起。所以上面的输入会产生
[('A',8), ('B',2), ('C',7)].
有人可以请你告诉我如何解决这个问题,我想尝试尽可能多地做自己!
答案 0 :(得分:7)
您可以使用Data.Map
的{{1}}来构建具有求和值的地图。它的类型是:
fromListWith
它将一对对象列表(如您所述)作为第一个arg,如果它看到重复,它使用一些函数(第一个arg),将原始值与新值组合。从那里,您可以将此地图转换回对列表(也是Data.Map中的函数)。
您可以使用纯列表执行此操作,但它可能不会那么有效,因为您将经常构建新列表(或部分列表)。
答案 1 :(得分:3)
好吧,首先将问题分解为更小的部分。
首先,忽略额外条件。最终目标是什么?添加一些数字,你可能已经知道如何做到这一点。
但是你不想添加所有数字,你怎么知道要添加哪些?看看这些字母是否匹配。所以你需要以某种方式比较它们。
因此,一旦您知道如何添加数字,并决定是否应添加任意两个数字,您需要一种方法来处理整个列表。你不会把它们全部混合在一起,所以你需要根据要添加的数字分开。您可以通过创建子列表一次完成所有操作,或者您可以一次过滤一个或其他各种方法,其中一些方法可能比其他方法效率更高或更低。
无论如何,这是基本的想法。因此,回过头来看,您可以从列表开始,根据比较字母分离出项目组,然后在每个结果组中添加所有数字。
我显然已经跳过了几个步骤 - 例如如何比较字母并在将它们组合成元组时添加数字 - 因为你确实说过你宁愿自己解决问题。 :
答案 2 :(得分:3)
除了基于Map
的优秀解决方案之外,我认为纯粹的基于列表的解决方案可能非常有启发性。与Map
一样,有趣的是将具有相同第一元素的元素组合在一起。有一个内置函数group
(及其通用版本groupBy
)可以合并相邻的元素:
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
第一个参数告诉何时合并两个元素。例如:
> groupBy (\x y -> odd x == odd y) [1,1,3,4,6,5,7]
[[1,1,3],[4,6],[5,7]]
不幸的是,正如我们在这里看到的那样,它只会合并相邻的元素,因此我们需要找到一种方法来首先将原始列表的元素组合在一起,以便那些具有相同键的元素紧挨着彼此。还有另一个内置函数可以执行类似的操作:sort
。最后一个技巧是总结所有具有相同第一元素的块的第二个元素。让我们编写一个函数,只假设它传递了一个非空的块,其中包含所有相同的第一个元素:
sumSnds :: Num b => [(a,b)] -> (a,b)
sumSnds abs = (a, sum bs) where
(a:_, bs) = unzip abs
现在我们可以将所有这些部分串在一起:
solution :: (Ord a, Ord b, Num b) => [(a,b)] -> [(a,b)]
solution = map sumSnds . groupBy (\x y -> fst x == fst y) . sort
答案 3 :(得分:1)
有几种方法可以解决这个问题,Adam已经基于地图为您提供了一种有效的方法。但是,我认为仅使用列表和递归的解决方案也是有益的,特别是在学习Haskell时。既然你已经得到了答案,我希望我能在这里写一个解决方案而不会破坏任何东西。
我接近这个的方法是考虑如何将输入列表减少到输出列表。我们从
开始[('A',3), ('B',2), ('C',2), ('A',5), ('C',3), ('C',2)]
目标是最终得到一个结果列表,其中每个元组以唯一字符开头。构建这样的结果列表可以递增地完成:从空列表开始,然后将元组插入列表,确保不复制字符。类型将是
insertInResult :: (Char, Integer) -> [(Char, Integer)] -> [(Char, Integer)]
它与('A',3)
一样,将其插入现有的唯一对列表中。结果是一个新的唯一对列表。这可以这样做:
insertInResult (c, n) [] = [(c, n)]
insertInResult (c, n) ((c', n'):results)
| c == c' = (c, n + n') : results
| otherwise = (c', n') : (insertInResult (c, n) results)
说明:将元组插入空结果列表很简单,只需插入即可。如果结果列表不为空,那么我们将获得具有模式匹配的第一个结果(c', n')
。我们检查字符是否与警卫匹配,如果是,则添加数字。否则,我们只需复制结果元组并将(c, n)
元组插入剩余的结果中。
我们现在可以做到
*Main> insertInResult ('A',3) []
[('A',3)]
*Main> insertInResult ('B',2) [('A',3)]
[('A',3),('B',2)]
下一步是在输入列表上重复使用insertInResult
,以便我们构建结果列表。我调用了这个函数sumPairs'
,因为我调用了顶级函数sumPairs
:
sumPairs' :: [(Char, Integer)] -> [(Char, Integer)] -> [(Char, Integer)]
sumPairs' [] results = results
sumPairs' (p:pairs) results = sumPairs' pairs (insertInResult p results)
这是一个简单的函数,只是迭代第一个参数并将每个对插入到结果列表中。最后一步是使用空结果列表调用此辅助函数:
sumPairs :: [(Char, Integer)] -> [(Char, Integer)]
sumPairs pairs = sumPairs' pairs []
有效! : - )
*Main> sumPairs [('A',3), ('B',2), ('C',2), ('A',5), ('C',3), ('C',2)]
[('A',8),('B',2),('C',7)]
此解决方案的复杂性不如基于Data.Map
的解决方案。对于包含 n 对的列表,我们从insertInResult
调用sumPairs'
n 次。每次调用insertInResult
都可以迭代 n 次,直到找到匹配的结果元组,或者到达结果的末尾。这给出了O( n ²)的时间复杂度。基于Data.Map
的解决方案将具有O( n log n )时间复杂度,因为它使用log n 时间来插入和更新每个 n 元素。
请注意,如果您对输入列表进行了排序,然后扫描一次以添加具有相同字符的相邻元组,则会产生相同的复杂性:
sumPairs pairs = sumSorted (sort pairs) []
sumSorted [] result = result
sumSorted (p:pairs) [] = sumSorted pairs [p]
sumSorted ((c,n) : pairs) ((c',n') : results)
| c == c' = sumSorted pairs ((c,n + n') : results)
| otherwise = sumSorted pairs ((c,n) : (c',n') : results)