繁体   English   中英

Haskell 的 F1 结果

[英]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

然后我们可以使用sortByData.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.onData.Ord.comparingControl.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM