[英]F1 Results with Haskell
做到这一点最简单的解决方案是什么?
在函数“f1Results”中定义一个用于计算一级方程式比赛系列的函数。 该参数是一个列表,其元素是基于每个大奖赛的结果的列表。 每个此类列表都应包含按 GP 结果列出的参赛者姓名(获胜者为列表中的第一位)。 该函数的结果必须是按整个 F1 赛季的积分数排序的 F1 飞行员姓名列表。
f1Results :: [[String]] -> [String]
必须为每个位置授予积分,如下面的列表(同样,25 分是为获胜者)积分(例如:第一名 25 分,第二名 18 分等)。
pointsScoring :: [Int]
pointsScoring = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1]
你有一个常数是什么得分
scoring :: [Int]
scoring = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1]
然后你需要一种方法来将一个司机与他们得到的分数配对。 每当你在 Haskell 中配对两个东西时,规范的选择是使用元组。 从两个列表构造元组列表的最简单方法是zip
函数:
zip :: [a] -> [b] -> [(a, b)]
在这种情况下,可用于为比赛分配分数:
assignScores :: [String] -> [(String, Int)]
assignScores race = zip race scoring
现在,我们需要一种方法来计算每场比赛车手的得分。 我们希望能够转动类似
[("Bob", 12), ("Joe", 10), ("Bob", 18), ("Joe", 25)]
进入
[("Bob", 30), ("Joe", 35)]
最简单的方法是列出所有比赛的所有分数
assignAllScores :: [[String]] -> [(String, Int)]
assignAllScores races = concatMap assignScores races
然后我们可以使用sortBy
的Data.List
来获取所有彼此相邻的相同名称
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
compare :: Ord a => a -> a -> Ordering
sortByDriver :: [(String, Int)] -> [(String, Int)]
sortByDriver races = sortBy (\(n1, s1) (n2, s2) -> compare n1 n2) races
然后我们可以使用groupBy
(也来自Data.List
)按名称对它们进行分组
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
groupByDriver :: [(String, Int)] -> [[(String, Int)]]
groupByDriver races = groupBy (\(n1, s1) (n2, s2) -> n1 == n2) races
但这给了我们一个列表
[[("Bob", 12), ("Bob", 18)], [("Joe", 10), ("Joe", 25)]]
我们现在需要一种方法将其转换为形式
[("Bob", [12, 18]), ("Joe", [10, 25])]
所有分数都汇总回一个列表中,我们根本不重复名称。 这留作练习。
aggregateScores :: [[(String, Int)]] -> [(String, [Int])]
然后我们最终可以计算出这些分数的总和
sumScores :: [(String, [Int])] -> [(String, Int)]
sumScores races = map (\(name, scores) -> (name, sum scores)) races
然后最后我们可以按分数排序,让每个人都井井有条
sortByScore :: [(String, Int)] -> [(String, Int)]
sortByScore races = sortBy (\(n1, s1) (n2, s2) -> compare s2 s1) races
请注意,我有compare s2 s1
而不是compare s1 s2
,这意味着它将按降序而不是升序排序。
最后一步是去掉分数,现在我们有我们的司机名单,从赢家到输家
removeScores :: [(String, Int)] -> [String]
removeScores races = map fst races
所以把所有东西组合成一个功能
f1Results :: [[String]] -> [String]
f1Results races =
removeScores $
sortByScore $
sumScores $
aggregateScores $
groupByDriver $
sortByDriver $
assignAllScores races
有几个技巧可以使这段代码更短,即Data.Function.on
、 Data.Ord.comparing
和Control.Arrow
一个有趣的运算符。 不要把它当作作业,我只是想展示一个使用更少代码的替代方案。
import Data.List
import Data.Function (on)
import Data.Ord (comparing)
scoring :: [Int]
scoring = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1]
f1Results :: [[String]] -> [String]
f1Results =
map fst . sortBy (on (flip compare) snd) .
map ((head *** sum) . unzip) .
groupBy (on (==) fst) . sortBy (comparing fst) .
concatMap (`zip` scoring)
或使用Data.Map
:
import Data.Map (assocs, fromListWith)
import Data.List
import Data.Ord (comparing)
scoring :: [Int]
scoring = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1]
f1Results :: [[String]] -> [String]
f1Results =
reverse . map fst . sortBy (comparing snd) .
assocs . fromListWith (+) .
concatMap (`zip` scoring)
一种方法是使用地图,其中键是司机的名字,值是分数。 要将分数与 f1results 中每个子列表的驱动程序相关联,可以使用 pointsScoring 压缩子列表。 一个函数可以获取每个压缩的子列表,并为在地图中遇到(或新添加到)的每个驱动程序添加分数。 然后根据值对地图进行排序。
例如,
import qualified Data.Map as M
import Data.List(sortBy)
pointsScoring :: [Int]
pointsScoring = [25, 18, 15, 12]
f1List :: [[String]]
f1List = [["alex","bart","cecyl","dark"]
,["dark","bart","cecyl","alex"]
,["bart","cecyl","alex","dark"]]
f1Results :: [[String]] -> [String]
f1Results list = map fst . sortBy (\(n1,s1) (n2,s2) -> compare s2 s1) . M.toList
$ foldr f M.empty (concatMap (zip pointsScoring) list)
where f (score,name) results = M.insertWithKey updateMap name score results
updateMap key newValue oldValue = newValue + oldValue
输出:
*Main> f1Results f1List
["bart","alex","dark","cecyl"]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.