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