g | x | w | all
Bytes Lang Time Link
nan241217T030234Zuser2166
100PHP220917T161629Zhuanglx
144Python 3220915T173321ZSectorCo
084Excel220916T153641ZEngineer
nanC clang220915T065236Zmousetai
081Knight v2.0alpha220918T055833ZSampersa
092Python 3.8220917T222537ZAlbert K
029Pyth220915T021303Zhakr14
093Python 3220916T223505ZZachary
067JavaScript ES6220914T230349ZArnauld
098Python220915T203942ZTess2552
299sed E220915T190156ZJiří
034Charcoal220915T195109ZNeil
090Retina 0.8.2220915T190030ZNeil
024Jelly220915T181849ZJonathan
124Red220915T084721ZGalen Iv
02605AB1E220915T075336ZKevin Cr
038APLDyalog Unicode220914T220901ZAdá

Here is the basic steps:

  1. Define a conversion scale: Map letter grades (A, B, C, etc.) to their respective grade points.
  2. Input the grades: Accept an array of letter grades.
  3. Convert to grade points: Use the defined conversion scale to get the grade points for each letter.
  4. Find the average: Add all the grade points together, divide by the total number of grades, and round to one decimal place.

In Javascript implementation:

function calculateGPA(grades) {
    // Map of letter grades to grade points
    const gradePoints = {
        A: 4.0,
        A_minus: 3.7,
        B_plus: 3.3,
        B: 3.0,
        B_minus: 2.7,
        C_plus: 2.3,
        C: 2.0,
        C_minus: 1.7,
        D_plus: 1.3,
        D: 1.0,
        F: 0.0,
    };

    // Accumulate grade points
    let totalPoints = 0;
    grades.forEach((grade) => {
        totalPoints += gradePoints[grade] || 0; // Use 0 if the grade is invalid
    });

    // Calculate GPA
    const gpa = totalPoints / grades.length;
    return Math.round(gpa * 10) / 10; // Round to 1 decimal place
}

// Example usage
const grades = ["A", "B", "B_plus", "C", "A_minus"];
console.log(calculateGPA(grades)); // Output: 3.2
Try this implementation online

PHP, 165 100 bytes

Thanks to @Steffan for the whopping 39% size reduction.

function($a){foreach($a as$v)$r+=strpos("FDCBA",$v[$l++*0])+($v[1]?$v[1]..3:0);echo round($r/$l,1);}

Try it online!

Original Solution

function($a){$i='array_search';$g=str_split("FDCBA");$r=0;foreach($a AS$v){@$r+=eval("return {$i($v[0], $g)}".($v[1]?$v[1].".3;":";"));}die(round($r/count($a),1));};

Try it online!

This is my first time golfing in PHP, so I probably missed a few places where I could have been more efficient.

Original Explanation

PHP is completely and utterly broken, which is why golfing in it is so fun. We approach this problem by using a string and splitting it, using the index as GPA value. The array_search function grabs the proper GPA value for us. We then use the + or - in the input to add or subtract 0.3 to the GPA value and use a standard algorithm to find the average value.

Python 3, 168 150 144 bytes

lambda n:int(sum([{'A':4,'A-':3.7,'B+':3.3,'B':3,'B-':2.7,'C+':2.3,'C':2,'C-':1.7,'D+':1.3,'D':1,'D-':0.7,'F':0}[i]for i in n])/len(n)*10+.5)/10

Could save some bytes on the dictionary creation.

Returns a float.

Excel, 89 84 bytes

Saved 5 bytes thanks to JvdV

=ROUND(AVERAGE(IF(A:A=0,"",FIND(LEFT(A:A),"FDCBA")-1+IFERROR(--RIGHT(A:A)&.3,0))),1)

Input is in the first column. One grade per row. When you paste the formula, Excel will automatically add a leading zero so .3 becomes 0.3.

Screenshot

C (clang), 113 96 bytes

-17 bytes thanks to celingcat and jdt

k;i;main(j){for(;gets(&j);k++)i+=j%8<5?50-j%8*10:0,i+=(j>>=8)?132-j*3:0;printf("%.1f",i/10./k);}

Try it online!

Attempt This Online!

Takes input as one grade per line.

Explanation

gets(&j)

Read the string into a integer field j. This is perfectly valid in C, and is a compact way to read strings of upto 3 bytes long.

j%8<5

Check for F

50-j%8*10

Extract the last byte from the string, this will be the letter (since little endian). 178-10*char will be the letter grade, 40, 30, 20, 10 etc.

0,

I think this just changed the operator precidence? Not sure.

i+=(j>>=8)?132-j*3:0

Add or subtract 3 depending on the sign, since + is 43 and - is 45

printf("%.1f",i/10./k)

Round and print the final number as a float.

Knight (v2.0-alpha), 81 bytes

O+++=s=n"";W=gP;=n+1n=s++*100&!?70=cA[g-69c*30&]g-44A]g s/=q/+/s n 5=t 10t'.'%q t

Try it online!

The basic algorithm is as follows, with a few golfing optimizations added:

# Set `s` and `n` to the empty string. This could be any zero value,3
# but an empty string helps with `OUTPUT`
; + = s = n ""

# Each grade is on a separate line
; WHILE = grade PROMPT
    ; = n + 1 n # increment the amount of grades

    # The grade part is `100 * (c == 'F' ? 0 : 69 - c)`, 
    # ie the grade offset for normal grades, or `0` for `F`.
    ; = grade_part * 100 &(!? 70 = c ASCII [grade) (- 69 c) #nice

    # The modifier is `grade.len == 1 ? 0 : 30 * (44 - grade[1].ascii)`
    ; = modifier_part * 30 (& ]grade (- 44 ASCII ]grade))

    # `s` will be coerced to an int the first tiem around.
    : = sum ++ grade_part modifier_part s

# Calculate the average * 10. To accommodate the rounding rule,
# we add five, then divide (`sum`, which is 100x a GPA) by ten.
; = avg / (+ /sum n 5) 10

# Now, simply output the number in the float format
: OUTPUT ++(+ "" /avg 10) '.' (% avg 10)

```

Python 3.8, 95 93 92 bytes

s=.01;i=0
for g,a,*_ in open(0):i+=1;s+='FDCBA'.find(g);exec(f's=s{a}.3')
exit(f'{s/i:.1f}')

Input (stdin)

A
B+
C
D

Very straightforward. Open stdin with open(0), then read each line with for g. Python has an annoying property where it leaves a trailing \r after each line, which we exploit in an f-string passed to exec. In case there's no appendix after grade, f-string becomes s=s\r.3, which is essentially a no-op. Very unsafe ;)

Pyth, 24 29 bytes

.OmeS,0v++"14-0x"d?tld".3";.z

Try it online!

Python 3, 93 bytes

lambda l:int(0.5+sum(3*(c=='+')-3*(c=='-')or'FDCBA'.index(c)*10for c in''.join(l))/len(l))/10

Takes input as a list of strings (['A', 'B+']).

JavaScript (ES6), 67 bytes

Returns a number.

a=>(a.map(([x,y])=>t+=940.5%(n++,'0x5'+x)%85+~~(y+3),n=t=0)|t/n)/10

Try it online!

How?

The purpose of the black-magic formula 940.5 % ('0x5' + x) % 85 is to turn a grade into its base value pre-multiplied by \$10\$ and with an offset of \$1/2\$ for the final rounding.

x N = '0x5' + x As decimal 940.5 mod N mod 85
A 0x5A 90 40.5 40.5
B 0x5B 91 30.5 30.5
C 0x5C 92 20.5 20.5
D 0x5D 93 10.5 10.5
F 0x5F 95 85.5 0.5

We use the expression ~~(y + 3) to add an additional offset of \$\pm 3\$ if the grade is followed by + or -. This evaluates to ~~NaN (which is \$0\$) if there's no modifier.

Given the sum \$t\$ of all these values, the GPA is given by \$\lfloor t/n\rfloor/10\$, where \$n\$ is the number of grades.


JavaScript (ES6), 76 bytes

Original answer, returning a string.

a=>(eval(a.map(s=>s<'F'&&'14-0x'+s+[s[1]&&.3]).join`+`)/a.length).toFixed(1)

Try it online!

How?

If the grade is "F", we turn it into "false".

Otherwise:

Examples:

Python, 98 bytes

lambda i:round(sum(x*i.count(y)for y,x in zip("FDCBA-+",[*range(5),-.3,.3]))/len(i.split())+.01,1)

Attempt This Online!

sed -E, 302 299 bytes

:a
s/ //
s/[A-D]/&aaaaaaaaaa/g
y/ABCD/BCDF/
s/\+/aaa/
/-/{s/-//;s/a//;s/a//;s/a//}
s/Fa/aF/
ta
s/F/!F/
:b
s/F/a/
s/(a+)(a*!)\1$/X\2\1/
tb
s/!.*/\U&/
s/AA?/a/g
/(a+)!\1$/s/a/X/
s/[a!]//g
s/X{10}/Y/g
s/$/I.0/
:c
/X/y/012345678/123456789/
/Y/y/IJKLMNOPQ/JKLMNOPQ9/
s/X//
s/Y//
tc
y/IJKLMNOPQ/012345678/

Attempt This Online!

Explanation:

                  # store dividend as unary "a" and divisor as unary "F"
:a
s/ //                 # removes all spaces
s/[A-D]/&aaaaaaaaaa/g # for each of letters ABCD ten "a" are added
y/ABCD/BCDF/          # each of letters ABCD are substracted to BCDE
s/\+/aaa/             # for every + three "a" are added
/-/{s/-//;s/a//;s/a//;s/a//} # for every - three "a" are removed
s/Fa/aF/              # sort the string into format "aaaaaFFFFF"
ta
                  # now perform the division
s/F/!F/               # transforms string to format "aaaaa!FFFFF"
:b
s/F/a/                # transforms string to format "aaaaa!aaaaa"
s/(a+)(a*!)\1$/X\2\1/ # if there are more or equal "a" on left side than on the right append "X" to beggining
tb
                  # now do the rounding
s/!.*/\U&/            # transorm "a" after "!" to "A"
s/AA?/a/g             # now the amount of "A" is divided by 2
/(a+)!\1$/s/a/X/      # check if the reminder is more or equal to half of divisor and if so, then add one more "X"
s/[a!]//g             # remove leftover characters
                  # now the result is stored as unary "X"
                  # conversion from unary to decimal
s/X{10}/Y/g           # convert every ten "X" to "Y"
s/$/I.0/              # adds string for output "I.0"
:c
/X/y/012345678/123456789/ # for every "X" the "0" is increased
/Y/y/IJKLMNOPQ/JKLMNOPQ9/ # for every "Y" the "I" is increased
s/X//
s/Y//
tc
y/IJKLMNOPQ/012345678/ # now the "I" is transformed to digit

Charcoal, 34 bytes

WS⊞υ⁺⌕FDCBA§ι⁰×·³⁻№ι+№ι-﹪%.1f∕ΣυLυ

Try it online! Link is to verbose version of code. Takes input as a list of newline-terminated grade strings. Explanation:

WS

Repeat for each input grade...

⊞υ⁺⌕FDCBA§ι⁰×·³⁻№ι+№ι-

... calculate its value by looking up the letter in the string FDCBA and adjusting by 0.3 depending on the number of +s or -s.

﹪%.1f∕ΣυLυ

Take the average and format it to one decimal place.

Retina 0.8.2, 90 bytes

T`L`4-0
\d
0$&$*
1
20$*
\+
6$*
1{6}-

O`.
0+
$.&$*1$.&$*1,$.&$*
\D*(1+),(\1)*1*
$#2
.$
.$&

Try it online! Link includes test cases. Output always includes the decimal but omits the leading 0 on scores of less than 1. Explanation:

T`L`4-0

Translate uppercase letters to digits A=4, B=4, C=2, D=1, otherwise 0.

\d
0$&$*

Convert to unary and prefix a marker 0 to keep count of the number of grades.

1
20$*

Multiply by 20.

\+
6$*

Add 6 for a +.

1{6}-

Subtract 6 for a -.

O`.

Collect the markers and scores together.

0+
$.&$*1$.&$*1,$.&$*

Add the count to the sum (so that the division will round) and double the count.

\D*(1+),(\1)*1*
$#2

Divide the sum by the doubled count.

.$
.$&

Divide by 10.

Jelly, 24 bytes

ON%7’»0_2¦€4ḋ10,3Æm+.Ḟ÷⁵

A monadic Link that accepts a list of lists of characters and yields a number.

Try it online!

How?

ON%7’»0_2¦€4ḋ10,3Æm+.Ḟ÷⁵ - Link: list of grades  e.g. ['A-', 'B+', 'F', 'F']
O                        - ordinals (vectorises)      [[65, 45], [66, 43], [70], [70]]
 N                       - negate                     [[-65, -45], [-66, -43], [-70], [-70]]
  %7                     - mod seven                  [[5, 4], [4, 6], [0], [0]]
    ’                    - decrement                  [[4, 3], [3, 5], [-1], [-1]]
     »0                  - max with zero              [[4, 3], [3, 5], [0], [0]]
        2¦€              - from 2nd of each:
       _   4             -   subtract four            [[4, -1], [3, 1], [0], [0]]
            ḋ10,3        - dot product with [10,3]    [37, 33, 0, 0]
                 Æm      - mean                       17.5
                   +.    - add a half                 18.0
                     Ḟ   - floor                      18
                      ÷⁵ - divide by ten              1.8

If banker's rounding was used \$21\$ bytes is possible with ON%7’»0_2¦€4Uḅ.3Æmær1 (U reverses each, ḅ.3 converts from base \$0.3\$, and ær1 uses Python's round to round to 1 decimal place).

Red, 124 bytes

func[a][forall a[parse a/1[change p: skip(rejoin[max 69 - p/1 0" "])opt[skip insert" .3"]]a/1: do a/1]round/to average a .1]

Try it online!

More readable:

f: func [a][
    forall a [
        parse a/1 [
            change set g skip (rejoin [max 69 - g 0 space])
            opt [skip insert " 0.3"]
        ]
        a/1: do a/1
    ]
    round/to average a 0.1
]

The input is a block (list) of strings. I modify each string in place by changing the letter with the corresponding grade value and optionally appending "0.3" after + or -. Then the string is replaced by its evaluated value. Finally I find and round the average of the block.

05AB1E, 30 26 bytes

ε14sáC-.3y¦'\ìθ.VDd*}ÅA1.ò

-4 bytes by semi-porting @Arnauld's JavaScript answer

Try it online or verify all test cases.

Original 30 bytes answer:

•H₁yΔи•.¥RA4£…+ -â¦ðмuIkèÅAòT/

Try it online or verify all test cases.

Explanation:

ε            # Map over the (implicit) input-list:
 14          #  Push 14
   s         #  Swap so the current grade-string is at the top
    á        #  Only keep its letters (removing a potential "+"/"-")
     C       #  Convert it to 'binary': "A"=10, "B"=11, "C"=12, "D"=13, "F"=15
      -      #  Subtract it from the 14
 .3          #  Push 0.3
   y         #  Push the current grade-string again
    ¦        #  Remove its first character (the letter)
     '\ì    '#  Prepend a leading "\"
        θ    #  Pop and only keep its last character
         .V  #  Execute it as 05AB1E code
             #  ("+"/"-" does what you'd expect; "\" discards the 0.3 from the stack)
 D           #  Duplicate the decimal
  d          #  Check if it's non-negative (1 if >=0; 0 if <0)
   *         #  Multiply that to the decimal to change the "F"=1 to 0
}            # Close the map
 ÅA          # Get the average of this list
   1.ò       # Round it to 1 decimal point
             # (after which the result is output implicitly)
•H₁yΔи•      # Push compressed integer 73343343343
       .¥    # Undelta its digits with leading 0: [0,7,10,13,17,20,23,27,30,33,37,40]
         R   # Reverse it: [40,37,33,30,27,23,20,17,13,10,7,0]
A            # Push the lowercase alphabet
 4£          # Only keep its first four characters: "abcd"
   …+ -      # Push string "+ -"
       â     # Get the cartesian product of the two: ["a+","a ","a-",...,"d+","d ","d-"]
        ¦    # Remove the leading "a+"
         ðм  # Remove all spaces from each string
Il           # Push the input-list, and convert each grade-string to lowercase
  k          # Get the indices of each lowercase grade-string in the string-list we've created)
             # (-1 for "f", since it's not in the list)
  è          # Use that to index into the earlier list of integers
             # (-1 wraps around to the last item, which is the 0)
   ÅA        # Get the average of this list
     ò       # Round it to an integer
      T/     # Divide it by 10
             # (after which the result is output implicitly)

See this 05AB1E tip of mine (section How to compress large integers?) to understand why •H₁yΔи• is 73343343343.

APL(Dyalog Unicode), 38 bytes SBCS

Anonymous prefix lambda taking a string argument of whatever format is convenient.

{1⍕(+/l,.3×-⌿'+-'∘.=⍵)÷≢l←0⌈5-⎕A⍳⍵∩⎕A}

Try it on APLgolf!

{} "dfn"; argument is :

⍵∩⎕A intersection of argument and uppercase Alphabet

⎕A⍳ indices of those letters in the uppercase Alphabet (A=1)

5- subtract those indices from 5

0⌈ maximum of 0 and those numbers

l← assign to l

 count those

( divide the following by that:

  '+-'∘.=⍵ comparison table of signs vs the argument characters

  -⌿ subtract the bottom row from the top row

  .3× multiply those differences by 0.3

  l, prepend l

+/ sum

1⍕ format as string with 1 decimal