简体   繁体   中英

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". 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). 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.

   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.).

   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. The easiest way to construct a list of tuples from two lists is the zip function:

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

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 :: (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.

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 . 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 :

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. 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"]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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