簡體   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