typestar

grade_report.hs in Haskell

Averages each student's scores, assigns a letter grade, and prints a ranked table with a class summary.

import Data.List (sortOn)
import Data.Ord (Down (..))

students :: [(String, [Int])]
students =
  [ ("Ada", [92, 88, 95, 79])
  , ("Bruno", [71, 64, 80, 77])
  , ("Chen", [88, 91, 84, 90])
  , ("Dara", [55, 48, 58, 51])
  , ("Esme", [100, 96, 93, 98])
  ]

-- An empty score list averages to zero rather than dividing by zero.
average :: [Int] -> Double
average [] = 0
average xs = fromIntegral (sum xs) / fromIntegral (length xs)

-- Guards read as a table of cutoffs, checked from the top down.
letterFor :: Double -> Char
letterFor avg
  | avg >= 90 = 'A'
  | avg >= 80 = 'B'
  | avg >= 70 = 'C'
  | avg >= 60 = 'D'
  | otherwise = 'F'

-- Round to one decimal by scaling into Int and splitting the digits.
oneDecimal :: Double -> String
oneDecimal x = show whole ++ "." ++ show frac
  where
    (whole, frac) = round (x * 10) `divMod` (10 :: Int)

pad :: Int -> String -> String
pad n s = s ++ replicate (n - length s) ' '

lpad :: Int -> String -> String
lpad n s = replicate (n - length s) ' ' ++ s

row :: (String, [Int]) -> String
row (name, scores) =
  pad 8 name ++ lpad 7 (oneDecimal avg) ++ lpad 7 [letterFor avg]
  where
    avg = average scores

rule :: String
rule = replicate 22 '-'

main :: IO ()
main = do
  let ranked = sortOn (Down . average . snd) students
      classAvg = average (concatMap snd students)
      passing = length [s | s <- students, average (snd s) >= 60]
  putStrLn "term grades, best first"
  putStrLn (pad 8 "student" ++ lpad 7 "average" ++ lpad 7 "grade")
  putStrLn rule
  mapM_ (putStrLn . row) ranked
  putStrLn rule
  putStrLn (pad 8 "class" ++ lpad 7 (oneDecimal classAvg)
              ++ lpad 7 [letterFor classAvg])
  putStrLn (show passing ++ " of " ++ show (length students) ++ " passing")

How it works

  1. average divides fromIntegral (sum xs) by the count, answering zero for an empty list.
  2. letterFor picks a grade through guards, testing avg >= 90 = 'A' first and falling through to otherwise.
  3. sortOn (Down . average . snd) puts the best first, and oneDecimal rounds by scaling to Int and splitting with divMod.

Keywords and builtins used here

The run, in numbers

Lines
60
Characters to type
1663
Tokens
419
Three-star pace
75 tpm

At the three-star pace of 75 tokens a minute, this run takes about 335 seconds.

Type this snippet

Step 2 of 3 in Encore, step 26 of 27 in Language basics.

← Previous Next →