Daily Challenge #68 - Grade Book

dev.to staff on September 16, 2019

Today's challenge is to write a function that accepts three integer values, calculates the mean, then returns the letter value associated with that... [Read Full]
markdown guide
 

CSS

This will only work on Safari because, for some forsaken reason, Safari is the only browser that supports min() and max() at the moment. Which is funny considering that normally it is the other way around 🤷‍♂️

This can be represented as a decision machine with different ranges for different grades. Then the idea would be to have an element to which we pass the three grades as CSS variables (all integers or it will fail), calculate the z-index to set which element should be visible. I would need to double check the logic, I think it's correct. Also, I think the code can be simplified.

Here is the code running. Remember it will only work on Safari:

 

Here's mine:

const sum = (...nums) => nums.reduce((total, cur) => total + cur, 0);
const meanGrade = (...grades) => sum(...grades) / grades.length;
const letterForGrade = grade => {
    const lastDigit = grade % 10;
    const gradeLetter = Object.entries({90: "A", 80: "B", 70: "C", 60: "D", 0: "F"})
        .sort(([a], [b]) => a < b)
        .find(([minGrade]) => grade >= minGrade)
        [1] + ((lastDigit < 5) ? "-" :
               (lastDigit > 5) ? "+" : "");
    return gradeLetter;
};

Gist: gist.github.com/kerrishotts/559154...

 
 

Too much ternary operators...

const grade = (a,b,c) =>{
    const mean = (a + b + c) / 3;

    const prefix = mean % 10 < 5 ? '-': 
                   mean % 10 > 5 ? '+': '';

    return   mean < 59 ?          `F (${mean})`:
             mean < 70 ? `D${prefix} (${mean})` : 
             mean < 80 ? `C${prefix} (${mean})`: 
             mean < 90 ? `B${prefix} (${mean})` : 
                         `A${prefix} (${mean})`;

}
 

You could use something like that:

({"-1": "-", "1": "+", "0": ""})[mean.toString().localeCompare("5")]

Not tested but I believe it would work in JavaScript.

 

I've Started to write a c# answer only to find out I'm going the exact same route as your answer...

 

Clojure (my new adventure):

(def grades {"A" [90 101]
             "B" [80 90]
             "C" [70 80]
             "D" [60 70]
             "F" [0 60]})

(defn grade
  "Calculates a letter for the mean of given grades."
  [a b c]
  (let [mean (quot (+ a b c) 3)
        sign (if (< (rem mean 10) 5) "-" "+")
        letter (->> grades
                   (filter (fn [[_ [low high]]]
                             (and (>= mean low) (< mean high))))
                   (map key)
                   first)]
    (str letter sign)))
 

Elm

module GradeBook exposing (gradeBook)


computeGradeSign : Int -> String
computeGradeSign mean =
    case compare (modBy 10 mean) 5 of
        EQ ->
            ""

        LT ->
            "-"

        GT ->
            "+"


gradeBook : Int -> Int -> Int -> String
gradeBook grade1 grade2 grade3 =
    let
        mean =
            toFloat (grade1 + grade2 + grade3) / 3

        gradeSign =
            computeGradeSign <| round mean
    in
    if mean < 60 then
        "F" ++ gradeSign

    else if mean < 70 then
        "D" ++ gradeSign

    else if mean < 80 then
        "C" ++ gradeSign

    else if mean < 90 then
        "B" ++ gradeSign

    else
        "A" ++ gradeSign

Tests

module GradeBookTest exposing (suite)

import Expect
import GradeBook exposing (gradeBook)
import Test exposing (Test)


suite : Test
suite =
    Test.describe "Grade Book"
        [ Test.test "It should return C- when passing 64, 55 & 92" <|
            \_ ->
                Expect.equal "C-" <| gradeBook 64 55 92
        , Test.test "It should return A- when passing 99, 89 & 93" <|
            \_ ->
                Expect.equal "A-" <| gradeBook 99 89 93
        , Test.test "It should return C+ when passing 33, 99 & 95" <|
            \_ ->
                Expect.equal "C+" <| gradeBook 33 99 95
        , Test.test "It should return C when passing 64, 70 & 92" <|
            \_ ->
                Expect.equal "C" <| gradeBook 64 70 92
        ]
 

Rust:

fn grade(a: u32, b: u32, c: u32) -> String {
    let mean = (a + b + c) / 3;
    let letter = match dbg!(mean) {
        _ if (90..=100).contains(&mean) => "A",
        _ if (80..90).contains(&mean) => "B",
        _ if (70..80).contains(&mean) => "C",
        _ if (60..70).contains(&mean) => "D",
        _ if (0..60).contains(&mean) => "F",
        _ => unreachable!("mean ({}) can't be < 0", mean),
    };
    let sign = if mean % 10 < 5 { "-" } else { "+" };
    format!("{}{}", letter, sign)
}
 

Python

#The function
def call_mean (x,y,z):
  mean = ((x+y+z)/3)
  mean_s = (str(mean))
  print ("\nMean: ",mean)
  if (mean > 100): print("\nWrong input, try again...")
  else:
    #The statement
    if ((mean <= 100 and mean >= 90)):
      if (mean_s[1] >= '5'): print ("A+")
      else: print ("A-")
    elif ((mean < 90 and mean >= 80)):
      if (mean_s[1] >= '5'): print ("B+")
      else: print ("B-")
    elif ((mean < 80 and mean >= 70)):
      if (mean_s[1] >= '5'): print ("C+")
      else: print ("C-")
    elif ((mean < 70 and mean >= 60)):
      if (mean_s[1] >= '5'): print ("D+")
      else: print ("D-")
    elif ((mean < 60 and mean >= 0)):
      if (mean_s[1] >= '5'): print ("F+")
      else: print ("F-")

#Three value from input
x = int(input("first int: "))
y = int(input("second int: "))
z = int(input("third int: "))

#Call the function
call_mean(x,y,z)
 

Here's my type-level implementation in haskell:

{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}

module Lib where
import GHC.TypeLits

data PlusMinus g = Plus g | Minus g | None g

data Grade = A | B | C | D | F

infixr 5 :->
data GradeMapping minScore grade = minScore :-> grade

type StandardGradingCurve = '[90 :-> A, 80 :-> B, 70 :-> C, 60 :-> D, 0 :-> F]

type family LookupGrade (c :: [GradeMapping score grade]) (s :: score) :: grade where
  LookupGrade m k = LookupGrade' m k k

type family LookupGrade' (c :: [GradeMapping score grade]) (s :: score) (s' :: score) :: grade where
  LookupGrade' '[] k k' = F
  LookupGrade' ((k :-> v) ': c) k k' = v
  LookupGrade' ((k :-> v) ': c) 0 k' = LookupGrade' c k' k'
  LookupGrade' ((k :-> v) ': c) n k' = LookupGrade' ((k :-> v) ': c) (n - 1) k'
  LookupGrade' ((kvm) ': c) k k' = LookupGrade' c k k'

type family IfThenElse (cond :: Bool) (whenTrue :: b) (whenFalse :: b) :: b where
  IfThenElse True trueBranch falseBranch = trueBranch
  IfThenElse False trueBranch falseBranch = falseBranch

type family LessThan (a :: Nat) (b :: Nat) :: Bool where
  LessThan a a = False
  LessThan a 0 = False
  LessThan 0 b = True
  LessThan a b = LessThan (a - 1) (b - 1)

type family OnesFamily (n :: Nat) :: Nat where
  OnesFamily 0 = 0
  OnesFamily n = IfThenElse (LessThan n 10) n (OnesFamily (n - 10))

type family MakePlusMinus (n :: Nat) (val :: a) :: PlusMinus a where
  MakePlusMinus score val =
    IfThenElse (LessThan (OnesFamily score) 5) (Minus val) (IfThenElse (LessThan 5 (OnesFamily score)) (Plus val) (None val))

type family Sum (vals :: [Nat]) :: Nat where
  Sum vals = Sum' 0 vals

type family Sum' (total :: Nat) (vals :: [Nat]) :: Nat where
  Sum' n '[] = n
  Sum' n (a ': as) = Sum' (n + a) as

type family Length (vals :: [a]) :: Nat where
  Length '[] = 0
  Length (val ': vals) = 1 + (Length vals)

type family Mean (vals :: [Nat]) :: Nat where
  Mean vals = Div (Sum vals) (Length vals)

type family CalcGrade (vals :: [Nat]) :: PlusMinus Grade where
  CalcGrade vals = MakePlusMinus (Mean vals) (LookupGrade StandardGradingCurve (Mean vals))
 

My first stab at writing something other than "Hello World!" in golang:

package main

import (
    "fmt"
    "math"
)

func grade(x, y, z float64) string {
    mean := float64((x + y + z) / 3)
    grade := ""

    if mean >= 90 && mean <= 100 {
        grade = "A"
    } else if mean >= 80 && mean < 90 {
        grade = "B"
    } else if mean >= 70 && mean < 80 {
        grade = "C"
    } else if mean >= 60 && mean < 70 {
        grade = "D"
    } else if mean >= 0 && mean < 60 {
        grade = "F"
    }

    if math.Mod(mean, 10.0) < 5 && grade != "F" {
        grade += "-"
    } else if math.Mod(mean, 10.0) >= 5 && grade != "F" {
        grade += "+"
    }

    return grade
}

func main() {
    //output := grade(64, 55, 92)
    //output := grade(99, 89, 93)
    output := grade(33, 99, 95)

    fmt.Println(output)
}
 

Perl solution, using a regex to extract the last digit (but we need to replace 100 by 99 for it to work).

#!/usr/bin/perl
use warnings;
use strict;
use List::Util qw{ sum };

sub grade {
    my $mean = int(sum(@_) / @_);
    return 'F' if $mean < 60;

    $mean = 99 if $mean == 100;
    my $sign = ($mean =~ /(.)$/)[0] < 5 ? '-' : '+';

    return qw( D C B A )[ ($mean - 60) / 10 ] . $sign
}

use Test::More tests => 6;

is grade(100, 100, 100) => 'A+';
is grade( 60,  60,  60) => 'D-';
is grade( 64,  55,  92) => 'C-';
is grade( 99,  89,  93) => 'A-';
is grade( 33,  99,  95) => 'C+';
is grade( 60,  60,  59) => 'F';
 

APL (I'm using Dyalog APL):

{md←10|m←(+⌿÷⍴)⍵ ⋄ ((1+60 70 80 90 101⍸m)⊃'FDCBA'),' -+'[1+(md≠5)×1+(,5)⍸md]}

If you think that's too short to work - here's the proof:
First, let's define that as a function:
getMark←{md←10|m←(+⌿÷⍴)⍵ ⋄ ((1+60 70 80 90 101⍸m)⊃'FDCBA'),' -+'[1+(md≠5)×1+(,5)⍸md]}

And now you can simply do
getMark 64 55 92
C-
getMark 99 89 93
A-
getMark 33 99 95
C+

And if you still don't believe me, try it here or there! ;-)
(These online solutions use the symbol ≢ instead of ⍴ - they are equivalent in this case, but ≢ renders incorrectly on dev.to. There's also an issue with another APL-Character: ⍸ - if someone from the dev-team would contact me, I'd love to help sorting that out...)

 

Scala

def grade (a: Int, b: Int, c: Int): String = {
  val average = (a + b + c) / 3f
  letter(average) ++ sign(average)
}

def letter (grade: Float): String =
  grade match {
    case x if x >= 90 => "A"
    case x if x >= 80 => "B"
    case x if x >= 70 => "C"
    case x if x >= 60 => "D"
    case _            => "F"
  }

def sign (grade: Float): String =
  (grade % 10) match {
    case x if x < 5 => "-"
    case x if x > 5 => "+"
    case _          => ""
  }
 

This is my quick python solution

def grade(x, y, z):
    board = { 9: "A", 8: "B", 7: "C", 6: "D", 5: "F" }
    sum = x + y + z
    mean = sum / 3
    if mean >= 5:
        return board.get(mean)

 

F#:

module DailyChallenge

let private gradeLetter mean =
    if mean < 60. then 'F'
    else if mean < 70. then 'D'
    else if mean < 80. then 'C'
    else if mean < 90. then 'B'
    else 'A'

let gradeSign mean =
    match int mean % 10 with
    | x when x > 5 -> "+"
    | 5 -> ""
    | _ -> "-"

let grade grade1 grade2 grade3 = 
    let mean = float (grade1 + grade2 + grade3) / 3.
    sprintf "%c%s" (gradeLetter mean) (gradeSign mean)

Tests:

module DailyChallengeTest

open FsUnit.Xunit
open Xunit
open DailyChallenge

[<Fact>]
let ``C-``() = grade 64 55 92 |> should equal "C-"

[<Fact>]
let ``A-``() = grade 99 89 93 |> should equal "A-"

[<Fact>]
let ``C+``() = grade 33 99 98 |> should equal "C+"

[<Fact>]
let ``C``() = grade 64 70 92 |> should equal "C"

[<Fact>]
let ``F``() = grade 55 55 55 |> should equal "F"
 

I wrote this in Python with the assumption that the input of the function will all be integers between 0 and 100.

def grade(*grades):
    grade_range = [(90, 'A'),(80, 'B'),(70, 'C'),(60, 'D')]
    mean = 0

    for _, score in enumerate(grades):
        mean += score

    mean = int(mean / len(grades))

    polarity = "+" if mean % 10 >= 5 else "-"

    for grade in grade_range:
        if mean >= grade[0]:
            return grade[1] + polarity

    return "F" + polarity
 

My solution in js

const grade = (...values) => {
  const grades = {9: 'A', 8: 'B', 7: 'C', 6: 'D'},
    mean = values.reduce((acc, value) => acc += value, 0) / values.length;
  return grades[Math.floor(mean / 10)] ? `${grades[Math.floor(mean / 10)]}${mean >= (Math.floor(mean / 10) * 10 + 5) ? '+' : '-'}` : Math.floor(mean / 10) > 9 ? 'A+' : 'F'
}
 

Solved Using Purescript inspired from Amin Nairi

grade :: Int -> String
grade average =
    case compare (mod 10 average) 5 of
        EQ ->
          ""

        LT ->
          "-"

        GT ->
          "+"  

resultGrade :: Int -> Int -> Int -> String
resultGrade grade1 grade2 grade3 =
   let 
        average = (grade1 + grade2 + grade3) / 3

        finalGrade = grade $ fromMaybe 0 $ fromNumber $ Math.round 
                      (toNumber average)
   in
   if average < 60 then 
       "F" <> finalGrade

   else if average < 70 then 
       "D" <> finalGrade 

   else if average < 80 then 
       "C" <> finalGrade 

   else if average < 90 then 
       "B" <> finalGrade 

   else 
       "A" <> finalGrade
code of conduct - report abuse