简体   繁体   English

Haskell 的 F1 结果

[英]F1 Results with Haskell

What is the most simple solution to do this ?做到这一点最简单的解决方案是什么?

Define a function for calculating the series of Formula 1 race in function "f1Results".在函数“f1Results”中定义一个用于计算一级方程式比赛系列的函数。 The parameter is a list whose elements are lists based on the result of each Grand Prix.该参数是一个列表,其元素是基于每个大奖赛的结果的列表。 Each such list shall contain the names of the competitors listed by the GP result (the winner is the first of the list).每个此类列表都应包含按 GP 结果列出的参赛者姓名(获胜者为列表中的第一位)。 The result of the function has to be a list of names of F1 pilots sorted by number of points in the overall F1 season.该函数的结果必须是按整个 F1 赛季的积分数排序的 F1 飞行员姓名列表。

   f1Results :: [[String]] -> [String]

Points have to be granted for each position like in the list bellow (again the 25 is for the winner) pointsScoring (eg: 1st place 25 points, 2nd place 18 points, etc.).必须为每个位置授予积分,如下面的列表(同样,25 分是为获胜者)积分(例如:第一名 25 分,第二名 18 分等)。

   pointsScoring :: [Int]
   pointsScoring = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1]

You have a constant for what the scoring is你有一个常数是什么得分

scoring :: [Int]
scoring = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1]

Then you need a way for pairing up a driver with the score they got.然后你需要一种方法来将一个司机与他们得到的分数配对。 Whenever you're pairing two things in Haskell, the canonical choice is to use a tuple.每当你在 Haskell 中配对两个东西时,规范的选择是使用元组。 The easiest way to construct a list of tuples from two lists is the zip function:从两个列表构造元组列表的最简单方法是zip函数:

zip :: [a] -> [b] -> [(a, b)]

And in this case can be used to assign scores for a race:在这种情况下,可用于为比赛分配分数:

assignScores :: [String] -> [(String, Int)]
assignScores race = zip race scoring

Now, we need a way to total up the scores for a driver for each race.现在,我们需要一种方法来计算每场比赛车手的得分。 We want to be able to turn something like我们希望能够转动类似

[("Bob", 12), ("Joe", 10), ("Bob", 18), ("Joe", 25)]

into进入

[("Bob", 30), ("Joe", 35)]

The easiest way would be to make a single list of all the scores for all the races最简单的方法是列出所有比赛的所有分数

assignAllScores :: [[String]] -> [(String, Int)]
assignAllScores races = concatMap assignScores races

Then we can use sortBy from Data.List to get all the same names next to each other然后我们可以使用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

Then we can use groupBy (also from Data.List ) to group them all by name然后我们可以使用groupBy (也来自Data.List )按名称对它们进行分组

groupBy :: (a -> a -> Bool) -> [a] -> [[a]]

groupByDriver :: [(String, Int)] -> [[(String, Int)]]
groupByDriver races = groupBy (\(n1, s1) (n2, s2) -> n1 == n2) races

But this gives us a list like但这给了我们一个列表

[[("Bob", 12), ("Bob", 18)], [("Joe", 10), ("Joe", 25)]]

We now need a way to convert this into the form我们现在需要一种方法将其转换为形式

[("Bob", [12, 18]), ("Joe", [10, 25])]

where all the scores are aggregated back into a list, and we don't repeat the names at all.所有分数都汇总回一个列表中,我们根本不重复名称。 This is left as an exercise.这留作练习。

aggregateScores :: [[(String, Int)]] -> [(String, [Int])]

Then we can finally calculate the sum of these scores然后我们最终可以计算出这些分数的总和

sumScores :: [(String, [Int])] -> [(String, Int)]
sumScores races = map (\(name, scores) -> (name, sum scores)) races

Then finally we can sort by the scores to get everyone in order然后最后我们可以按分数排序,让每个人都井井有条

sortByScore :: [(String, Int)] -> [(String, Int)]
sortByScore races = sortBy (\(n1, s1) (n2, s2) -> compare s2 s1) races

Notice that I have compare s2 s1 instead of compare s1 s2 , this means it will be sorted in descending order instead of ascending order.请注意,我有compare s2 s1而不是compare s1 s2 ,这意味着它将按降序而不是升序排序。

The last step is to strip out the scores, now we have our list of drivers in order from winner to loser最后一步是去掉分数,现在我们有我们的司机名单,从赢家到输家

removeScores :: [(String, Int)] -> [String]
removeScores races = map fst races

So to combine everything together into one function所以把所有东西组合成一个功能

f1Results :: [[String]] -> [String]
f1Results races =
    removeScores $
    sortByScore  $
    sumScores    $
    aggregateScores $
    groupByDriver $
    sortByDriver $
    assignAllScores races

There are several tricks that can make this code shorter, namely Data.Function.on , Data.Ord.comparing , and a fun operator from Control.Arrow .有几个技巧可以使这段代码更短,即Data.Function.onData.Ord.comparingControl.Arrow一个有趣的运算符。 Don't turn this in as homework, I just wanted to show an alternative that uses less code.不要把它当作作业,我只是想展示一个使用更少代码的替代方案。

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)

Or using Data.Map :或使用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)

One way could be to use a map where the keys are the drivers' names and the values are the scores.一种方法是使用地图,其中键是司机的名字,值是分数。 To associate scores with drivers for each sublist in f1results, one could zip the sublist with pointsScoring.要将分数与 f1results 中每个子列表的驱动程序相关联,可以使用 pointsScoring 压缩子列表。 A function could take each of the zipped sublists and add the score for each driver encountered in (or newly added to) the map.一个函数可以获取每个压缩的子列表,并为在地图中遇到(或新添加到)的每个驱动程序添加分数。 Then sort the map according to the values.然后根据值对地图进行排序。

For example,例如,

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

Output:输出:

*Main> f1Results f1List
["bart","alex","dark","cecyl"]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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