我被分配了一个任务来创建一个能够计算考试成绩的功能,通过将你获得的分数加到你通过其他方式收集的额外分数,然后将它们转换为成绩系统。如果额外点或检查点超过其最大值(分别为20和100),我还必须添加错误消息。
我创建的功能有效,但它可能并不接近最佳状态。
calcGrade :: Double -> Double -> Double
calcGrade x y
| x > 20 = error "Can't add more than 20 extra points"
| y > 100 = error "Can't achieve more than 100 points"
| x + y < 50 = 5.0
| x + y >= 50 && x + y < 54 = 4.0
| x + y >= 54 && x + y < 58 = 3.7
| x + y >= 58 && x + y < 62 = 3.3
| x + y >= 62 && x + y < 66 = 3.0
| x + y >= 66 && x + y < 70 = 2.7
| x + y >= 70 && x + y < 74 = 2.3
| x + y >= 74 && x + y < 78 = 2.0
| x + y >= 78 && x + y < 82 = 1.7
| x + y >= 82 && x + y < 86 = 1.3
| x + y >= 86 = 1.0
还有其他方法可以做到这一点,还是有什么我可以做得更有效率?我对Haskell和一般的编程都很陌生,所以我感谢任何建议!
答案 0 :(得分:2)
如果我想实现完全相同的功能(而不是更改规范以便我可以使代码更清晰 - 有时可能),我想我会使用{{1 } Map
编码当前使用警卫完成的查找表。所以:
lookupGT
这有一些优点:
import Data.Map (fromAscList, lookupGT)
calcGrade :: Double -> Double -> Double
calcGrade x y
| x > 20 = error "Can't add more than 20 extra points"
| y > 100 = error "Can't achieve more than 100 points"
| otherwise = case lookupGT (x+y) cutoffs of
Just (_, v) -> v
Nothing -> 1.0
where
cutoffs = fromAscList [(50, 5.0), (54, 4.0), (58, 3.7), (62, 3.3), (66, 3.0), (70, 2.7), (74, 2.3), (78, 2.0), (82, 1.7), (86, 1.3)]
,而在某些情况下,由于某种原因,不要说在视觉上相似x+y
。使用这种编码,这一点很明显,没有经过仔细的关注。x+v
的{{1}},只会进行日志数比较。由于您可能不打算动态地改变截止值,这可能无关紧要;但是这里使用的技巧偶尔会在其他地方有用,所以很容易记住那些渐近线很重要的情况。Map
到lookupGT
一个人需要处理你的代码。在我看来,唯一的瑕疵是默认分数案例(58
)并不存在于截止点旁边;虽然我不清楚如何做到这一点。
答案 1 :(得分:1)
如果您只接受了Int
个值(并且仍然返回Double
),那么您可以将其写为
calcGrade x y =
let score = (min 46 (x + y) - 46) `div` 4
grades = [5.0, 4.0, 3.7, 3.3, 3.0, 2.7, 2.3, 2.0, 1.7, 1.3] ++ repeat 1.0
in grades !! score
但这省略了前2个检查。你可以很容易地把它们放进去,但是把它放在一个不同的函数中可能会更好(同样,在Haskell中使用error
是不赞成的,最好使用一个表明函数可能失败的类型,比如作为可能或任何一种)。
这个函数的作用是首先计算x和y之和,然后说&#34;它是更小的,x + y或46?&#34;。这处理x + y < 50
的情况。接下来,它减去46,因此得分50变为4,得分54变为8,依此类推。 div
函数将整数除以4,因此得分50变为4变为1,小于50的分数变为0,而得分73变为27变为6。
等级本身存储在一个列表中,任何小于50的分数都将索引掉5.0的第一个元素,然后每个范围索引出相应的元素,因此73索引出第2.3个元素(索引6)。 ++ repeat 1.0
处理得分>= 86
。
你能解决这个问题的另一种方法可能会更有意义。只需构建范围列表:
let score = x + y
mins = [0, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 120]
ranges = zip mins (tail mins) -- [(0, 50), (50, 54), ..., (86, 120)]
grades = [5.0, 4.0, 3.7, 3.3, 3.0, 2.7, 2.3, 2.0, 1.7, 1.3, 1.0]
inRange = map (\(lower, upper) -> lower <= score && score < upper) ranges
in snd $ head $ filter fst $ zip inRange grades
我认为大部分逻辑非常明确,但最后一行可能会令人困惑。它将inRange
Bools列表与等级,第一个元素的过滤器(该范围是否包括得分),从列表中取出第一个元素,然后抓取该(Bool, Double)
元组的第二个元素
答案 2 :(得分:1)
有些事情可以尝试清理一下:
x + y
。lookupGrade (exam + extra)
。error
。相反,如果您无法计算得分,请返回Maybe Double
(Just
一个Double
或Nothing
。应用这些转换可能是编写此函数的最佳方法,除非您想使用Map(来自Data.Map
)列出大量案例。此代码将比使用一堆不需要的列表更好地表达您的意图。
答案 3 :(得分:1)
不确定性能,但这应该有效。
import Data.Map (fromAscList, filterWithKey)
calculate :: Double -> Double -> String
calculate exam bonus
| exam > 20 || bonus > 100 = "Be real!"
| otherwise = (foldr (++) "" . filterWithKey isInRange) letterGrade
where
isInRange k _ = percent `elem` k
percent = truncate $ (exam + bonus) * 10 / 12
letterGrade = fromAscList [ ([90..100], "A"), ([80..89], "B"), ([70..79], "C"), ([60..69], "D"), ([0 ..59], "F")]