Haskell 使用元组列表
我一直想解决这个问题,但我就是想不出来。我有一个元组列表,例如:Haskell 使用元组列表,haskell,tuples,Haskell,Tuples,我一直想解决这个问题,但我就是想不出来。我有一个元组列表,例如: [("Mary", 10), ("John", 45), ("Bradley", 30), ("Mary", 15), ("John", 10)] 我想得到的是一个包含元组的列表,如果名称相同,则应添加这些元组的编号,如果名称不相同,则该元组也必须是最终列表的一部分,举例说明: [("Mary",25), ("John", 55), ("Bradley", 30)] 我不知道我是否解释得很好,但我想你可能会理解这些例子 我试过
[("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)
对于列表来说,这样做总是很尴尬的,因为它们是连续的——它们实际上不适合执行诸如“查找匹配项”或“通过组合列表元素的特定组合来计算新列表”之类的操作,或者其他本质上不连续的操作 如果您退后一步,您真正想做的是,对于列表中每个不同的
字符串
,找到与之关联的所有数字并将它们相加。这听起来更适合键值样式的数据结构,它是Haskell中最标准的,它为任何值类型和任何有序键类型(即,Ord
的实例)提供键值映射
因此,要从列表中构建Map
,可以使用Data.Map
中的fromList
函数。。。它方便地期望以键值元组列表的形式输入。所以你可以这样做
import qualified Data.Map as M
nameMap = M.fromList [("Mary", 10), ("John", 45), ("Bradley", 30), ("Mary", 15), ("John", 10)]
…但这不好,因为直接插入它们将覆盖数字,而不是添加它们。您可以在插入重复键时使用它——在一般情况下,通常使用它为每个键或类似的东西构建一个值列表
但在您的情况下,我们可以直接跳到期望的结果:
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)]
这给了我们一个[(“布拉德利,30岁),(“约翰,55岁),(“玛丽,25岁)]
的最终结果
但是,如果您想对名称/数字的集合做更多的工作,在完成之前将其作为
映射可能更有意义。我认为您的潜在解决方案不起作用的原因是,只有当元素在列表中以相同的键顺序出现时,它才会将元素分组在一起。因此,我将使用地图(如果您使用过其他语言,通常称为字典)来记住我们看到的键并保存总数。首先,我们需要导入所需的函数
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->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>
这些组以排序的顺序出现是一种奖励,因为内部映射使用二叉树的形式,因此按顺序遍历并输出排序(无论如何,按键排序)列表相对简单。下面是使用列表的另一种方法:
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)
这是一个相当简单的方法。
首先,点(.)
表示函数组合,它允许我们将值从一个函数传递到下一个函数,也就是说,一个函数的输出成为下一个函数的输入,依此类推。我们首先应用排序
,它会自动将列表中的名称移到相邻的位置。接下来,我们使用groupBy
将具有相似名称的每一对放入一个列表中。我们最终得到一个列表列表,每个列表包含具有相似名称的对:
[(“布拉德利”,30)],[(“约翰”,10),(“约翰”,45)],[(“玛丽”,10),(“玛丽”,15)]
如果有这样一个列表,您将如何处理每个子列表?
也就是说,如何处理包含所有相同名称的列表
显然,我们希望将它们缩小为一对,其中包含名称和值的总和。为了实现这一点,我选择了函数(foo.unzip)
,但还有很多其他方法<代码>解压
获取对列表并创建单个对。该对包含两个列表,第一个包含所有名称,第二个包含所有值。如前所述,然后通过函数组合将该对传递给foo
foo
使用模式将其分离,然后对名称应用head
,只返回一个名称(它们都相同),并对值列表应用sum
sum
是另一个标准的列表函数,它自然地对列表中的值求和
然而,这个(foo.unzip)
只适用于单个对列表,而我们有一个列表。这就是map
的用武之地map
将把我们的(foo.unzip)
函数应用于列表中的每个列表,或者更一般地,应用于列表中的每个元素。我们最终得到一个列表,其中包含对每个子列表应用(foo.unzip)
的结果
我建议查看
Data.list
中使用的所有列表函数 这是我的两分钱。只用哈斯克尔的前奏曲
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取一个名字,检查列表中匹配的名字,并添加它们的数字。它返回名称和总和。对您有帮助吗?您能解决吗