我正在尝试获取元组列表的列表,并将每个元组列表转换为单个元组。像这样:
当前拥有:
[[("Erikson,Ann",2.0,3),("Erikson,Ann",3.33,3)],[("Lewis,Buck",2.66,1),
("Lewis,Buck",2.0,3)],[("Smith,John",0.0,1),("Smith,John",1.66,3),
("Smith,John",1.33,3)],[("Torvell,Sarah",4.0,3)]]
而且我希望表单成为元组的单个列表。每个人姓名一个元组。 除了将元组列表合并为一个元组外,我还想使用每个元组的第二个和第三个元素来计算人的gpa。第二个数字是该课程的学分,第三个数字是该课程的学分。 我需要做的是获取每个元组的学分总和*等级点,然后将该总和除以每个元组中所有学分的总和。
到目前为止,这是行不通的...
calcGPA :: MyType2 -> MyType2 -> (String, Double, Double)
calcGPA (a,b,c) (d,e,f) = (a, ((b*(fromIntegral c))+(e*(fromIntegral
f))/(b+e)),
(b+e))
我要传递的位置是我在帖子顶部显示的列表的第一个列表。
我要朝正确的方向解决这个问题。任何提示或帮助将不胜感激。
谢谢
编辑
谢谢您的帮助!帮助我了解实际情况。我写了cumulativeSums函数,如下:
cumulativeSums :: (Double, Int) -> (String, Double, Int) -> (Double,
Int)
cumulativeSums (a,b) (c,d,e) = (a+(d*e), b+e)
我对let上面的代码块感到困惑。这去哪儿了?我是否将它放在自己的函数中,称为元组列表传递?
谢谢
________________________________________________________________________________现在我正在尝试输出信用额
calcGPA :: [(String, Double, Int)] -> (String, Double, Int)
calcGPA grades = let name = (\ (name, _, _) ->
name) (head grades)
(name, weightedSum, sumOfWeights) = foldl
cumulativeSums (name, 0, 0) grades
gpa = weightedSum / sumOfWeights
in (name, gpa, credits)
答案 0 :(得分:2)
如果您打算将foldl
或foldr
与calcGPA
函数一起使用,将会朝着正确的方向前进。
折叠时我们要做的是拥有一个函数,该函数具有到目前为止的结果,列表中的下一个元素以及仅此结果。使用foldl
(最适合求和),就列表而言,类型和参数是:
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f startingResult items = …
我们看到您的函数将需要输入(b -> a -> b)
。在类型签名的其他地方,foldl
的最终结果是类型b
。列表中元素的类型为a
。
因此,您提供的foldl
函数的作用是接受两个参数:result-so-far和列表中的下一项。然后,它希望您的函数能立即返回结果。
每次在列表中的下一个元素上运行函数时,您都会在新项目中“折叠”到结果。因此,让我们看一下列表元素类型是什么以及结果类型将是什么。
我们的列表元素类型类似于(String, Double, Int)
。我们的结果类型为(Double, Int)
。因此,我们折叠功能的类型签名为:
cumulativeSums :: (Double, Int) -> (String, Double, Int) -> (Double, Int)
到目前为止,一切都很好。现在,foldl
的其他参数呢?我们知道items
参数:这是我们一个人的成绩的子列表。我们知道f
,这是我们要编写的cumulativeSums
函数。什么是startingResult
?好吧,两个总和都应以0开头,因此为(0, 0)
。我们有:
let name = (\ (name, _, _) -> name) (head grades)
(weightedSum, sumOfWeights) = foldl cumulativeSums (0, 0) grades
gpa = weightedSum / sumOfWeights
in (name, gpa)
现在我们写cumulativeSums
。记住,到目前为止,我们已得知结果以及列表中的项目。我们只需要归还结果就可以了。看看是否可以编写该部分。
对于已经提供的代码,建议您编写自己的版本。上面的代码中存在一些与混合Int
和Double
混合在一起的类型错误。
答案 1 :(得分:2)
您需要遍历每个子列表,以便累积值。像这样:
averageGdp :: [[(String, Double, Double)]] -> [(String, Double, Double)]
averageGdp = fmap f
where
f = (,,) <$> fst . head <*> totalAvg <*> totalCredit
fst (a, _, _) = a
totalCredit = getSum . foldMap (\(_, _, c) -> pure c)
total = getSum . foldMap (\(_, b, c) -> pure $ b * c)
totalAvg = (/) <$> total <*> totalCredit
f将内部列表作为其输入并产生一个三元组。然后将f映射到外部列表。
答案 2 :(得分:1)
一站式服务来完成您的要求:
import Control.Category ( (>>>) )
g :: [[(t, Double, Double)]] -> [(t, Double, Double)]
g = filter (not . null) >>>
map (unzip3 >>> \ (a,b,c) -> (head a, sum (zipWith (*) b c) / sum c, sum c))
unzip3 :: [(a, b, c)] -> ([a], [b], [c])
在前奏中。
>>>
是从左到右的功能组合(f >>> g) x = g (f x)
。
filter
确保在进行进一步处理之前删除所有空组。
答案 3 :(得分:1)
由于存在这种分组问题,我认为数据看起来已经分组有点让人讨厌。你能永远确定吗?如果数据如下所示,该怎么办?
[[("Erikson,Ann",2.0,3),("Erikson,Ann",3.33,3),("Lewis,Buck",2.66,1)],
[("Lewis,Buck",2.0,3)]]
还是这样?
[[("Erikson,Ann",2.0,3),("Erikson,Ann",3.33,3),("Lewis,Buck",2.66,1)], []]
请注意,在第一个示例中,"Lewis,Buck"
的一个条目与"Erikson,Ann"
的条目一起分组。另一方面,第二个示例包含一个空列表。
我见过的大多数尝试通过使用{strong> unsafe (即非总计)功能(例如head
)来解决此类问题的方法。这可能导致错误的实现或运行时崩溃。
Haskell是一门很棒的语言,正是因为您可以使用类型系统来保持诚实。如果原始输入尚未分组,则使用未分组的数据会更安全。否则,您可以使用concat
展平输入。我在这里假设OP中的示例数据称为sample
:
*Q52527030> concat sample
[("Erikson,Ann",2.0,3.0),("Erikson,Ann",3.33,3.0),("Lewis,Buck",2.66,1.0),
("Lewis,Buck",2.0,3.0),("Smith,John",0.0,1.0),("Smith,John",1.66,3.0),
("Smith,John",1.33,3.0),("Torvell,Sarah",4.0,3.0)]
这为您提供了一个不错的平面列表,您可以在其上执行自定义分组操作:
import qualified Data.Map.Strict as Map
arrangeByFst :: Ord a => [(a, b, c)] -> [(a, [(b, c)])]
arrangeByFst = Map.toList . foldl updateMap Map.empty
where updateMap m (x, y, z) = Map.insertWith (++) x [(y, z)] m
在这里,我选择采取快捷方式并使用内置的Map
模块,但是否则,在元组列表上编写类似于Map.insertWith
的函数也不是 努力。
此函数采用一个三元组的平面列表,并将它们分组为由第一个元素键控的对,而另一个元素为数据列表。
如果将其应用于展平的sample
输入,则会得到以下信息:
*Q52527030> arrangeByFst $ concat sample
[("Erikson,Ann",[(3.33,3.0),(2.0,3.0)]),("Lewis,Buck",[(2.0,3.0),(2.66,1.0)]),
("Smith,John",[(1.33,3.0),(1.66,3.0),(0.0,1.0)]),("Torvell,Sarah",[(4.0,3.0)])]
这是一种更可靠的方法,因为它不依赖于有关数据如何排序的任何特定假设。
此列表中的每个元素都是一对,其中第一个元素是名称,第二个元素是成绩列表。您可以添加一个函数来计算此类对的GPA:
calculateGPA :: Fractional b => (a, [(b, b)]) -> (a, b)
calculateGPA (n, ts) = (n, sumOfGrades ts / numberOfGrades ts)
where
sumOfGrades grades = sum $ map (\(gp, c) -> gp * c) grades
numberOfGrades grades = fromIntegral (length grades)
此函数将一个元组作为输入,其中第二个元素是元组ts
的列表。它通过将成绩分sumOfGrades
和学分gp
的每个元组映射到两者的乘积中,然后取这些数字的c
来计算sum
。然后用该数字除以成绩列表的长度。
您现在可以映射上一步中生成的列表,以计算每个人的GPA:
*Q52527030> map calculateGPA $ arrangeByFst $ concat sample
[("Erikson,Ann",7.995),("Lewis,Buck",4.33),("Smith,John",2.9899999999999998),
("Torvell,Sarah",12.0)]
除了使用Data.Map.Strict
之外,我还特意尝试在保持基本性和安全性之间取得平衡。一种更复杂的方法可以使用fmap
代替map
,使用join
代替concat
,使用更无点的样式,依此类推。总是有改进的余地。