I enjoy participating in math competitions run by Mu Alpha Theta, a mathematics honor society in the United States. At competitions, I take a 30-question multiple choice test. There are five choices per question, labeled A through E.

My score on a test is four points for each correct answer, zero points for a question left blank, and negative one points for each wrong answer.

Write a program that grades a test according to the above scoring system. There should be two components to the input: an answer key followed by responses. Questions left blank are to be inputted as blank spaces. The first input must contain only the letters A-E (or a-e, your choice), and it can be assumed that there is no whitespace in the input. The second input must only contain blank spaces and the letters A-E (or a-e). Inputs not implementing 30-question tests must print Invalid test as output.

The output should be the grade or Invalid test.


If your program prints the number right, number left blank, and number wrong after the final score as (aR bB cW), take off 20 bytes.

Sample input


Sample output

No bonus



73 (20R 3B 7W)

Standard rules apply. Shortest code in bytes wins.

  • \$\begingroup\$ What should we do with spaces in the first line? \$\endgroup\$ – lirtosiast Nov 25 '15 at 4:25
  • \$\begingroup\$ @ThomasKwa There shouldn't be spaces in the first line. Invalid test. \$\endgroup\$ – Arcturus Nov 25 '15 at 5:16
  • 2
    \$\begingroup\$ It appears you changed the rules after answers were posted, which invalidated at least 2 of them. Please do no make changes which could invalidate answers after a challenge is posted. It's a good idea to use the Sandbox to get feedback prior to posting. \$\endgroup\$ – Alex A. Nov 25 '15 at 6:32
  • \$\begingroup\$ I think this would have been more interesting as underhanded \$\endgroup\$ – cat Nov 25 '15 at 10:56
  • \$\begingroup\$ how about case sensitivity? also, what if my language is... unpleased by whitespace? can i specify whitespace in the input should be underscores instead? \$\endgroup\$ – cat Nov 25 '15 at 11:01

20 Answers 20


Pyth, 53 51

?&!-sJ.z+d<G5&FqR30lMJ+sm?qFd4_1CJ/eJd"Invalid test

Try it online

The checks are done by seeing if the entire input contains any characters when all of the spaces and a-e are removed, and by checking if both strings have length 30.

The score calculation is done by zipping the two lines together, then by mapping each pair to: (letters are equal) ? 4 : -1. Then simply summing the values, and adding the number of spaces in the second line back to the score.

  • 1
    \$\begingroup\$ No errors. (Currently fewer bytes than Dennis's answer...) \$\endgroup\$ – Arcturus Nov 25 '15 at 4:02

Seriously, 86 bytes

,`;l5╙¬=);' UΣS" ABCDE"=(**;l`Mi@)=YWé"Invalid test"0WX@Z```i@;(=5*(' =D+`(;l@)5╙¬=IMΣ


Try it online (you will have to manually enter the input because permalinks don't like quotes)

Working on the bonus now. Nope, adding the bonus would cost more than 20 bytes.

I knew I was forgetting something... Invalid Test wasn't being printed in the event of an error. There go my hopes of out-golfing Dennis.

  • \$\begingroup\$ Does it? Seriously? Then I have to upvote it \$\endgroup\$ – edc65 Dec 2 '15 at 12:40

JavaScript (ES6), 134 bytes

Edit: The question requirements changed. This answer is from when the program needed to make sure each answer character is A-E, each response character is A-E or space and they both have lengths of 30, else return Invalid test.

(a,r)=>[...a].map((q,i)=>q>"E"|q<"A"?x=1:(c=r[l=i])==" "?0:c>"E"|c<"A"?x=1:c==q?s+=4:s--,s=x=0)&&x|l!=29|r.length!=30?"Invalid test":s


(a,r)=>                   // a = answer string, r = responses string
  [...a].map((q,i)=>      // iterate over answers, q = answer, i = question number
    q>"E"|q<"A"?x=1:      // x = 1 if answer is invalid
    (c=r[l=i])==" "?0:    // c = question response, l = answer length, add 0 for space
    c>"E"|c<"A"?x=1:      // x = 1 if response is invalid
    c==q?s+=4:s--,        // add 4 if correct, subtract 1 if incorrect
    s=x=0                 // s = total score, x = is invalid
    x|l!=29|r.length!=30? // check input lengths for validity
      "Invalid test":
      s                   // return the score


<input type="text" id="answers" value="CABBDCABECDBACDBEAACADDBBBEDDA" /><br />
<input type="text" id="responses" value="CABEDDABDC BACDBBAADE  CBBEDDA" /><br />
<button onclick='result.innerHTML=(

(a,r)=>[...a].map((q,i)=>q>"E"|q<"A"?x=1:(c=r[l=i])==" "?0:c>"E"|c<"A"?x=1:c==q?s+=4:s--,s=x=0)&&x|l!=29|r.length!=30?"Invalid test":s

)(answers.value,responses.value)'>Go</button><pre id="result"></pre>


CJam, 60 bytes

2{l_,30=\N?}*_2$|S'F,65>+-!@2$.{=4W?}@' f=+:+"Invalid test"?

Try it online in the CJam interpreter.


JavaScript (Firefox 31+), 86 bytes

(x,y)=>(r=i=0,[for(l of y)x[i++]==l?r+=4:r-=l!=' '],i!=30|i-x.length?'Invalid test':r)

Uses array comprehension which is proposed for ES7. Thus support is limited to Firefox for the moment.

With bonus, 106 bytes (126 - 20)

(x,y)=>[r=w=i=0,[for(l of y)x[i++]==l?r++:w+=l!=' '],`${r*4-w} (${r}R ${i-r-w}B ${w}W)`,'Invalid test'][i!=30||i-x.length?3:2]

Edit: Previously my solution only checked answer or question length, now checks both.

F=(x,y)=>(r=i=0,[for(l of y)x[i++]==l?r+=4:r-=l!=' '],i!=30|i-x.length?'Invalid test':r)
G=(x,y)=>[r=w=i=0,[for(l of y)x[i++]==l?r++:w+=l!=' '],`${r*4-w} (${r}R ${i-r-w}B ${w}W)`,'Invalid test'][i!=30||i-x.length?3:2]

var score = () => result.innerHTML = (bonus.checked ? G:F)(questions.value, answers.value);

questions.oninput = answers.oninput = bonus.onchange = score;
* {
    box-sizing: border-box;

label {
  float: left;
  clear: left;
  vertical-align: middle;
  line-height: 2;
  padding-right: 0.5em;

input[type="text"] {
  width: 100%;
  vertical-align: middle;
  line-height: 2;
  font-family: monospace;
  padding: 0 0.5em;

input[type="checkbox"] {
  vertical-align: middle;
  line-height: 2;
  margin: 0.5em;

hr {
  clear: both;

.input-wrapper {
  overflow: hidden;
  width: auto;

#result, #error {
  font-weight: bold;

#error {
  display: none;
<script  type="text/javascript">
  window.onerror = () => {
    var inputEls = document.getElementsByTagName('input');
    for (var i = 0; i < inputEls.length; i++) {
      inputEls[i].disabled = true;
    error.style.display = 'block';
    output.style.display = 'none';
<label for="questions">Q:</label>
<div class="input-wrapper">
  <input id="questions" type="text" value="CABBDCABECDBACDBEAACADDBBBEDDA"/>
<label for="answers">A:</label>
<div class="input-wrapper">
  <input id="answers" type="text" value="CABEDDABDC BACDBBAADE  CBBEDDA"/>
<label for="bonus">Bonus:</label>
<div class="input-wrapper">
  <input id="bonus" type="checkbox" checked/>
<div id="output">
  Result: <span id="result"></span>
<div id="error">
  ERROR: Unsupported browser! Try Firefox 31+

  • \$\begingroup\$ You can omit the f= at the beginning and say that this generates a lambda function. \$\endgroup\$ – Conor O'Brien Nov 25 '15 at 16:49
  • 1
    \$\begingroup\$ @CᴏɴᴏʀO'Bʀɪᴇɴ Thanks I always forget it's on their from testing -_- \$\endgroup\$ – George Reith Nov 25 '15 at 16:51
  • \$\begingroup\$ This would be significantly shorter without the bonus. 86 bytes: (x,y)=>(r=i=0,[for(l of y)x[i++]==l?r+=4:r-=l!=' '],i!=30|i-x.length?'Invalid test':r) \$\endgroup\$ – user81655 Nov 25 '15 at 17:29
  • \$\begingroup\$ @user81655 True, thanks... I got a little caught up in it... the template string alone is 34 bytes \$\endgroup\$ – George Reith Nov 25 '15 at 18:00
  • \$\begingroup\$ I have a very similar answer, but I did not copied this one (my first attempt is precedent, but I deleted it as I was not checking the lengths). That said: this is not valid notwithstanding 3 upvotes,as does not check the range A...E \$\endgroup\$ – edc65 Nov 26 '15 at 10:46

Japt, 71 bytes

Japt is a shortened version of JavaScript. Interpreter

Ul ¥30©Vl ¥30«(U+V k"[A-E ]+" ?U¬r@VgZ ¥Y?X+4:VgZ ¥S?X:X-1,0 :`InvÃ. È.

The two .s at the end should be the unprintable Unicode chars U+0017 and U+0099, respectively.

How it works

Ul ==30&&Vl ==30&&!(U+V k"[A-E ]+" ?Uq r@VgZ ==Y?X+4:VgZ ==S?X:X-1,0 :"Invalid test
                    // Implicit: U = first input, V = second input
Ul ==30&&Vl ==30&&  // If both input lengths are 30, and
!(U+V k"[A-E ]+"?   // removing all ABCDE and spaces from (U+V) results in an empty string:
Uq r@            ,0 //  Reduce U with this function, starting with a value of 0:
VgZ ==Y?            //   If the matching char in V is equal to this char, 
X+4                 //    return previous value + 4.
:VgZ ==S?X          //   Else if the matching char in V is a space, return previous value.
:X-1                //   Else (if it's wrong), return previous value - 1.
:"Invalid test      // Else, return "Invalid test".
                    // Implicit: output last expression

I hope there's a shorter way to make sure both lengths are equal to 30. Suggestions welcome!


Haskell, 144 138 bytes

a%b|map length[a,b]==[30,30]&&"ABCDE"!a&&"ABCDE "!b=show$sum$zipWith(?)a b|0<1="Invalid test"
_?' '=0

Would be about 50 without the validation. sniff.


  • 1
    \$\begingroup\$ ! can be defined as all(`elem`l)s, saving 6 bytes. \$\endgroup\$ – Zgarb Nov 26 '15 at 1:16
  • 1
    \$\begingroup\$ ... or go pointfree: g=all.flip elem. \$\endgroup\$ – nimi Nov 26 '15 at 9:28

C#, 162 154 148 134 bytes

string g(string k,string a)=>k.Length!=30||a.Length!=30?"Invalid Test!":Enumerable.Range(0,30).Sum(e=>a[e]==' '?0:k[e]==a[e]?4:-1)+"";





  • \$\begingroup\$ You could change it to int s=0,i=0;for(;... to save 3 bytes. \$\endgroup\$ – LegionMammal978 Nov 25 '15 at 13:13
  • \$\begingroup\$ This wont work if I input 29 chars for first input and 31 for second.. it should print "invalid test" but will actually try to grade. \$\endgroup\$ – Johan Nov 25 '15 at 14:16
  • \$\begingroup\$ @noisyass2: string x(string k,string a)=>k.Length!=30||a.Length!=30?"Invalid Test!":Enumerable.Range(0,30).Sum(e=>a[e]==' '?0:k[e]==a[e]?4:-1)+""; (134 chars) and considers Johans input. \$\endgroup\$ – Stephan Schinkel Nov 25 '15 at 17:17
  • \$\begingroup\$ +1 for solution, but does this qualify? OP said full program. \$\endgroup\$ – Yytsi Nov 25 '15 at 17:42
  • \$\begingroup\$ Johan nice catch! @StephanSchinkel thanks for the idea on using the delegate and the Enum.range bit. I was able to shave of 3 more chars changing the condition to 30==(k.Length&a.Length) \$\endgroup\$ – noisyass2 Nov 26 '15 at 1:08

Ruby, 81 characters

->t,s{r=0;30.times{|i|r+=t[i]==s[i]?4:s[i]>' '?-1:0};t.size==30?r:'Invalid test'}

Sample run:

2.1.5 :001 > ->t,s{r=0;30.times{|i|r+=t[i]==s[i]?4:s[i]>' '?-1:0};t.size==30?r:'Invalid test'}['CABBDCABECDBACDBEAACADDBBBEDDA','CABEDDABDC BACDBBAADE  CBBEDDA']
 => 73 

2.1.5 :002 > ->t,s{r=0;30.times{|i|r+=t[i]==s[i]?4:s[i]>' '?-1:0};t.size==30?r:'Invalid test'}['CCCATCH','CABEDDABDC BACDBBAADE  CBBEDDA']
 => "Invalid test" 

Java, 183 169 bytes

This was a nice bit of Java 8 practice:

String f(String a,String r){return a.length()==30&r.length()==30?""+IntStream.range(0,30).map(i->a.charAt(i)==r.charAt(i)?4:r.charAt(i)!=' '?-1:0).sum():"Invalid test";}
  • \$\begingroup\$ I'm not a Java Golfer, but I think you can save the String.valueOf by just adding the int to an empty string (""+IntStream....) - I also believe Java allows non-short-circuiting and, so you can remove one of the & and save a byte. \$\endgroup\$ – VisualMelon Nov 26 '15 at 14:05
  • \$\begingroup\$ @VisualMelon Great tips, thanks. It was annoying me how many bytes String.valueOf took up! \$\endgroup\$ – RCB Nov 26 '15 at 14:14

brainfuck, 354 bytes


Requires an interpreter that allows you to go left from cell 0. Output is a signed byte. For example, the byte 0x49 is printed for the example input, and 0xFF is printed for input with the same first line but the second line replaced with "C" and 29 spaces.

The score starts at 0, and as the second line of input is read these changes are made to it:

  • Input is correct: Do nothing
  • Input is incorrect: Subtract 5
  • Input is a space: Subtract 4

Finally, 120 is added. This is functionally the same as assuming a perfect score and applying penalties, rather than starting at 0.

With comments:

+[--[>]<<+>-]                          Get 29

,----------[[<<<]>>->[>>>],----------] Get first line of input; for each char sub one
                                       from the 29

<<<[<<<]>>+                            Add one to the cell that originally held 29

[                                      If the cell that originally held 29 is nonzero:

  Write "Invalid test"


>[                                     If the cell to the right is nonzero:

  This block is only ever entered if "Invalid test" isn't written!

  [                                      For all 30 characters of the first input:

    >,                                     Get char from second input to the right

    ----------                             Subtract 10 for consistency

    -                                      Subtract one more

    >+++++++[<---<<+++>>>-]                Subtract 21 (plus above lines = 32)

    <[                                     If it's nonzero:

      <<+[>>+<<-]                            Add 22 to the character


    >[>]<<<[                                 If the above block wasn't entered:

      >[>+>+<<-]>>[<<+>>-]                   Make a copy of the character from input 1

      >[>>>]>----                            Subtract 4 from the score

      <<<<[<<<]>>[-]                         Go to the cell just before first character


    >[>-<-]                                Subtract input 1 char from input 2 char

    >[                                     If the result is nonzero:

      >>[>>>]>-----                          Subtract 5 from the score

      <<<<[<<<]>[-]                          Go back to the result and set it to 0


    >>                                     Move on to next character


  ----[>+++<--]>--                       Add 120 to score (perfect score)

  .                                      Print score

  <                                      Go to an empty cell to kill loop


Python 3, 187 179 175 165 155 151

lambda a,b:(['Invalid test',sum([-1,4][i==j]for i,j in zip(a,b))+b.count(' ')][len(a)==len(b)==30and set(a)^set('ABCDE')==set(b)^set('ABCDE ')==set()])

JavaScript ES7, 102

As usual, the bonus is not worth the effort.

(k,h,t=i=0)=>[for(x of h)t+=k[i++]==x?4:1-x?0:-1]|/[^ A-E]/.test(k+h)|i-30|k.length-i?"Invalid test":t

Checking for invalid spaces in first input (as this makes sense to me) 112

(k,h,t=i=0)=>[for(x of h)(y=k[i++])>' '?t+=y==x?4:1-x?0:-1:k=h+h]|/[^ A-E]/.test(k+h)|i-30|k[i]?"Invalid test":t
  • \$\begingroup\$ Ugh, the validation took up half of my code when I tried: (k,r,s=0)=>/^[A-E]{30}$/.test(k)&&/^[ A-E]{30}$/.test(r)?Object.keys(k).map(i=>k[i]==r[i]?s+=4:s-=r[i]!=' ').pop():'Invalid Test' is 129 bytes. \$\endgroup\$ – Neil Jan 13 '16 at 17:06

Python 2.7, 131, 116, 109, 139

I tried to do a "short" python solution... Well here it is, suggestions are more than welcome

lambda c,d:d.count(' ')+sum([-1,4][a==b]for a,b in zip(c,d)if b!=' ')if not set('ABCDE ')^set(c+d)and len(c)==len(d)==30 else'Test Invalid'

Adding a few more characters makes it far more readable...

def m(c, d):
    if len(c)==len(d)==30:return d.count(' ')+sum((a==b)*4+(a!=b)*-1 for a,b in zip(c,d)if b!=' ')
    return'Test Invalid'

Prolog, 165 bytes

More than half the bytes are for the invalid tests check.


A*B:-length(A,30),length(B,30),subset(A,`ABCDE`),subset(B,`ABCDE `),maplist(p,A,B,L),sum_list(L,S),write(S);write('Invalid Test').


p(X,X,4).                                       % If corresponding elements are equal, 4p
p(_,32,0).                                      % If answer is 'space', 0p
p(_,_,-1).                                      % Else, -1p
A*B:-length(A,30),length(B,30),                 % Check that input is of correct length
     subset(A,`ABCDE`),subset(B,`ABCDE `),      % Check that input has correct characters
     maplist(p,A,B,L),sum_list(L,S),write(S);   % Create a list of scores (L) and print sum
     write('Invalid Test').                     % If anything failed, write Invalid Test



Try it out online here


MATLAB, 92 90 bytes

Thanks to Tom Carpenter for helping me to reduce my answer by 2 bytes!

function c(q,a),if nnz(q)~=30,t='Invalid test';else s=q-a;t=5*nnz(~s)-sum(s<9);end,disp(t)

Function can be called by assigning the answer sheet to q and the submitted answers to a. e.g.:


The answer is simply printed to the screen. 8 bytes could be saved if it is allowed to print ans = 73

  • \$\begingroup\$ You could save 2 bytes by replacing numel(q) with nnz(q). \$\endgroup\$ – Tom Carpenter Nov 30 '15 at 0:36

C# 6.0 --> (270 - 20 = 250) 246 - 20 = 226 bytes

void m(string b,string c){if((b+c).Length==60){var a=new int[3];int s=0;for(int i=0;i<30;i++){if(b[i]==c[i]){a[0]++;s+=4;}else if(c[i]==' ')a[2]++;else{a[1]++;s--;}}Console.Write(s+$" ({a[0]} {a[2]} {a[1]})");}else Console.Write("Invalid test");}

Readable & ungolfed version:

    void m(string b, string c)
        if ((b+c).Length==60)
            var a = new int[3];
            int s = 0;
            for (int i = 0; i < 30; i++)
                if (b[i]==c[i])
                else if (c[i] == ' ')a[2]++;
            Console.Write(s+$" ({a[0]} {a[2]} {a[1]})");
        else Console.Write("Invalid test");

Really wanted to get the bonus :D

  • \$\begingroup\$ Nice work! A few of general tricks that apply here, you can declare i along with s outside the for loop. You can use var to declare a, saving 1 byte (hurrah!). You don't need many of the curly braces {} in your code, which is always a good way to trim bytes, and it's always worth looking at an ASCII table when comparing chars (you can knock a byte off c[i]==' ' somewhat by using an inequality). You should also consider counting through the strings backwards - in this case you can save at least 1 byte by rejigging the for loop somewhat. \$\endgroup\$ – VisualMelon Nov 26 '15 at 13:21
  • \$\begingroup\$ Unfortunately, your submission is currently not complicit with the criteria, as it is unable to recognise invalid inputs. \$\endgroup\$ – VisualMelon Nov 26 '15 at 13:59
  • \$\begingroup\$ @VisualMelon Ahh, I feel so dumb. I wrote this submission at school so I forgot to add the 'invalid test' things etc. I'll add them :) \$\endgroup\$ – Yytsi Nov 26 '15 at 14:03
  • \$\begingroup\$ @VisualMelon Yeah, this was submitted and written in school at the end of the class, I'll edit it. Thanks for the tricks :) \$\endgroup\$ – Yytsi Nov 26 '15 at 14:04

Groovy 2.4.5, 107 bytes

Just a simple translation of the earlier Java answer.

f={a,b->a.length()==30&b.length()==30?(0..29).collect{a[it]==b[it]?4:b[it]!=' '?-1:0}.sum():'Invalid test'}

C, 273 - 20 = 253 bytes

int main(int c,char**v){char*p=v[1],*q=v[2],*s=" ABCDE",r[]={0,0,0};if(strspn(p,s+1)!=30||p[30]||strspn(q,s)!=30||q[30])puts("Invalid test");else{for(;*p;++q)++r[(*p++!=*q)+(*q==' ')];printf("%d (%dR %dB %dW)",4**r-r[1],*r,r[2],r[1]);}}

I've taken the bonus, even though it cost me 23 bytes just to print it. :-(


#include <stdio.h>
#include <string.h>
int main(int c,char**v)
    char *p=v[1], *q=v[2],      /* arguments */
        *s=" ABCDE",            /* valid chars */
        r[]={0,0,0};            /* results - right, wrong, blank */

    if (strspn(p,s+1) != 30     /* validity check - answer key begins with [A-E]{30} */
        || p[30]                /* and ends there */
        || strspn(q,s) != 30    /* same for answers, but allow space, too */
        || q[30])
        puts("Invalid test");
    } else {
        for ( ;  *p;  ++q)      /* for each answer */
            ++r[(*p++!=*q)+(*q==' ')]; /* increment the appropriate counter */
        printf("%d (%dR %dB %dW)",4**r-r[1],*r,r[2],r[1]); /* print result */

There's twice as much code to check for invalid input as there is to count the answers - the real meat of the challenge is in the for loop near the end. In fact here's a version that assumes the input is always valid, in 163-20 = 143 bytes:

int main(int c,char**v){char*p=v[1],*q=v[2],r[]={0,0,0};for(;*p;++q)++r[(*p++!=*q)+(*q==' ')];printf("%d (%dR %dB %dW)",4**r-r[1],*r,r[2],r[1]);}

And one that makes the same assumption and prints only the score, in 133 bytes:

int main(int c,char**v){char*p=v[1],*q=v[2],r[]={4,-1,0};for(c=0;*p;++q)c+=r[(*p++!=*q)+(*q==' ')];printf("%d",c);}

SAS 9.4, 291-20 = 271 bytes(with bonus), or 231 bytes(without bonus)

With bonus:

data a;k='CABBDCABECDBACDBEAACADDBBBEDDA';r='CABEDDABDC BACDBBAADE  CBBEDDA';c=0;b=0;w=0;if length(k) ne 30 then put "Invalid test";do i=1 to 30;if substr(k,i,1)=substr(r,i,1) then c=c+1;else if substr(r,i,1) =' ' then b=b+1;else w=w+1;end;a=cat(c*4-w,' (',c,'R ',b,'B ',w,'W)');put a;run;

Without bonus:

data a;k='CABBDCABECDBACDBEAACADDBBBEDDA';r='CABEDDABDC BACDBBAADE  CBBEDDA';c=0;if length(k) ne 30 then put "Invalid test";do i=1 to 30;if substr(k,i,1)=substr(r,i,1) then c=c+4;else if substr(r,i,1)ne' ' then c=c-1;end;put c;run;

Sas does not really have an input/output, so you would need to replace the k='..' with the key, and r='..' with the response. The output is printed to the log.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.