我一直试图解决这个问题,但我无法弄明白。所以,我有一个包含元组的列表,例如:
[("Mary", 10), ("John", 45), ("Bradley", 30), ("Mary", 15), ("John", 10)]
我想得到的是一个带有元组的列表,如果名称相同,那么应该添加这些元组的数量,如果没有,那个元组也必须是最终列表的一部分,举例说明:
[("Mary",25), ("John", 55), ("Bradley", 30)]
我不知道我是否真的很好地解释了自己,但我想你可能会理解这些例子。
我试过这个,但它不起作用:
test ((a,b):[]) = [(a,b)]
test ((a,b):(c,d):xs) | a == c = (a,b+d):test((a,b):xs)
| otherwise = (c,d):test((a,b):xs)
答案 0 :(得分:8)
对于列表来说,做这类事总是很尴尬,因为它们具有顺序性 - 它们并不适合像“查找匹配项”或“通过组合列表元素的特定组合来计算新列表”这样的操作或其他本质上非顺序的东西。
如果您退一步,那么您真正想要做的是,对于列表中的每个不同的String
,找到与其关联的所有数字并将其添加。这听起来更适合于键值样式的数据结构,Haskell is found in Data.Map
中最标准的数据结构为您提供了任何值类型和任何有序键类型的键值映射(即, Ord
)。
因此,要从列表中构建Map
,您可以使用fromList
中的Data.Map
函数...,方便地,希望以密钥列表的形式输入值元组。所以你可以这样做......
import qualified Data.Map as M
nameMap = M.fromList [("Mary", 10), ("John", 45), ("Bradley", 30), ("Mary", 15), ("John", 10)]
...但这并不好,因为直接插入它们会覆盖数字而不是添加它们。插入重复键时可以use M.fromListWith
to specify how to combine values - 在一般情况下,通常使用它来为每个键或类似事物构建值列表。
但在你的情况下,我们可以直接跳到所需的结果:
nameMap = M.fromListWith (+) [("Mary", 10), ("John", 45), ("Bradley", 30), ("Mary", 15), ("John", 10)]
如果找到新名称,将直接插入,否则将在副本上添加值(数字)。如果您愿意,可以使用M.toList
:
namesList = M.toList $ M.fromListWith (+) [("Mary", 10), ("John", 45), ("Bradley", 30), ("Mary", 15), ("John", 10)]
这给了我们[("Bradley",30),("John",55),("Mary",25)]
的最终结果。
但是如果你想用名字/数字的集合做更多的事情,那么在你完成之前将它保持为Map
会更有意义。
答案 1 :(得分:4)
以下是使用列表的另一种方式:
import Data.List
answer :: [(String, Int)] -> [(String, Int)]
answer = map (foo . unzip) . groupBy (\x y -> fst x == fst y) . sort
where foo (names, vals) = (head names, sum vals)
这是一种相当简单的方法。
首先,点(.)
表示函数组合,它允许我们将值从一个函数传递到下一个函数,也就是说,一个函数的输出成为下一个函数的输入,依此类推。我们首先应用sort
,它会在列表中自动将名称彼此相邻移动。接下来,我们使用groupBy
将具有相似名称的每一对放入一个列表中。我们最终得到一个列表列表,每个列表包含具有相似名称的对:
[[("Bradley",30)], [("John",10),("John",45)], [("Mary",10),("Mary", 15)]]
鉴于此列表,您将如何处理每个子列表? 也就是说,你将如何处理包含所有相同名称的列表?
显然我们希望将它们缩小为一对,其中包含名称和值的总和。为了实现这一点,我选择了函数(foo . unzip)
,但还有很多其他方法可以解决这个问题。 unzip
获取对列表并创建一对。该对包含2个列表,第一个包含所有名称,第二个包含所有值。然后通过函数组合将该对传递给foo
,如前所述。 foo
使用模式将其分开,然后将head
应用于名称,只返回一个名称(它们全部相同),并将sum
应用于列表价值观sum
是另一个标准列表函数,它自然地对列表中的值求和。
但是,此(foo . unzip)
仅适用于单个列表对,但我们有一个列表列表。这是map
的用武之地。map
会将(foo . unzip)
函数应用于列表中的每个列表,或者更一般地说,列表中的每个元素。我们最终得到一个列表,其中包含将(foo . unzip)
应用于每个子列表的结果。
我建议查看Data.List
中使用的所有列表函数。
答案 2 :(得分:1)
我认为您的潜在解决方案不起作用的原因是,如果它们按列表中的相同键顺序发生,它将仅将元素组合在一起。所以相反,我将使用一张地图(如果你使用其他语言,通常称为字典)来记住我们看过哪些键并保留总数。首先,我们需要导入我们需要的功能。
import Data.Map hiding (foldl, foldl', foldr)
import Data.List (foldl')
现在我们可以沿着列表折叠,并且对于每个键值对,相应地更新我们的地图。
sumGroups :: (Ord k, Num n) => [(k, n)] -> Map k n
sumGroups list = foldl' (\m (k, n) -> alter (Just . maybe n (+ n)) k m) empty list
所以,foldl'带着一个函数沿着列表走。它用每个元素(这里是对(k,n))和另一个参数(累加器)调用函数。这是我们的地图,从空开始。对于每个元素,我们使用Maybe n - >中的函数来改变地图。也许是这反映了地图在密钥k下可能还没有任何内容的事实 - 所以我们处理这两种情况。如果没有先前的值,我们只返回n,否则我们将n添加到先前的值。这给了我们一个最后的地图,它应该包含各组的总和。在结果上调用toList函数可以为您提供所需的列表。
在ghci中测试这个:
$ ghci
GHCi, version 7.6.1: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> import Data.Map hiding (foldl, foldl', foldr)
Prelude Data.Map> import Data.List (foldl')
Prelude Data.Map Data.List> let sumGroups list = foldl' (\m (k, n) -> alter (Just . maybe n (+ n)) k m) empty list
Loading package array-0.4.0.1 ... linking ... done.
Loading package deepseq-1.3.0.1 ... linking ... done.
Loading package containers-0.5.0.0 ... linking ... done.
Prelude Data.Map Data.List> toList $ sumGroups $ [("Mary", 10), ("John", 45), ("Bradley", 30), ("Mary", 15), ("John", 10)]
[("Bradley",30),("John",55),("Mary",25)]
Prelude Data.Map Data.List>
这些组以排序的顺序出现作为奖励,因为内部地图使用二叉树的形式,因此按顺序遍历并输出排序(好的,按键排序)列表相对简单。
答案 3 :(得分:0)
这是我的两分钱。仅使用Haskell Prelude。
test tup = sumAll
where
collect ys [] = ys
collect ys (x:xs) =
if (fst x) `notElem` ys
then collect (fst x : ys) xs
else collect ys xs
collectAllNames = collect [] tup
sumOne [] n x = (x, n)
sumOne (y:ys) n x =
if fst y == x
then sumOne ys (n + snd y) x
else sumOne ys n x
sumAll = map (sumOne tup 0) collectAllNames
此方法多次遍历原始列表。 Collect构建一个只包含名称的临时列表,跳过名称重复。 sumOne采用名称,检查列表中的名称是否匹配,并添加其编号。它返回名称和总和。