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.

Bonus

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

CABBDCABECDBACDBEAACADDBBBEDDA    //answer key


Sample output

No bonus

73


Bonus

73 (20R 3B 7W)


Standard rules apply. Shortest code in bytes wins.

• What should we do with spaces in the first line? Nov 25, 2015 at 4:25
• @ThomasKwa There shouldn't be spaces in the first line. Invalid test. Nov 25, 2015 at 5:16
• 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. Nov 25, 2015 at 6:32
• I think this would have been more interesting as underhanded
– cat
Nov 25, 2015 at 10:56
• how about case sensitivity? also, what if my language is... unpleased by whitespace? can i specify whitespace in the input should be underscores instead?
– cat
Nov 25, 2015 at 11:01

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.

• No errors. (Currently fewer bytes than Dennis's answer...) Nov 25, 2015 at 4:02

Seriously, 86 bytes

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


Takes input like "CABBDCABECDBACDBEAACADDBBBEDDA", "CABEDDABDC BACDBBAADE CBBEDDA"

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.

• Does it? Seriously? Then I have to upvote it Dec 2, 2015 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


Explanation

(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


Test

<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;
score();
* {
box-sizing: border-box;
}

label {
float: left;
clear: left;
vertical-align: middle;
line-height: 2;
}

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

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';
}
</script>
<label for="questions">Q:</label>
<div class="input-wrapper">
</div>
<div class="input-wrapper">
</div>
<label for="bonus">Bonus:</label>
<div class="input-wrapper">
<input id="bonus" type="checkbox" checked/>
</div>
<hr/>
<div id="output">
Result: <span id="result"></span>
</div>
<div id="error">
ERROR: Unsupported browser! Try Firefox 31+
</div>

• You can omit the f= at the beginning and say that this generates a lambda function. Nov 25, 2015 at 16:49
• @CᴏɴᴏʀO'Bʀɪᴇɴ Thanks I always forget it's on their from testing -_- Nov 25, 2015 at 16:51
• 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) Nov 25, 2015 at 17:29
• @user81655 True, thanks... I got a little caught up in it... the template string alone is 34 bytes Nov 25, 2015 at 18:00
• 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 Nov 26, 2015 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!

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'


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'


a%b|map length[a,b]==[30,30]&&"ABCDE"!a&&"ABCDE "!b=show$sum$zipWith(?)a b|0<1="Invalid test"
l!s=all(eleml)
_?' '=0
x?y|x==y=4|0<1=0-1


Would be about 50 without the validation. sniff.

Usage: "ABCDEABCDEABCDEABCDEABCDEABCDE" % "AAAAABBBBBCCCCCDDDDDEEEEEAAAAA"

• ! can be defined as all(eleml)s, saving 6 bytes. Nov 26, 2015 at 1:16
• ... or go pointfree: g=all.flip elem.
– nimi
Nov 26, 2015 at 9:28

C#, 162154148 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)+"";


Usage

g("CABBDCABECDBACDBEAACADDBBBEDDA", "CABEDDABDC BACDBBAADE  CBBEDDA")


Test

• You could change it to int s=0,i=0;for(;... to save 3 bytes. Nov 25, 2015 at 13:13
• 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. Nov 25, 2015 at 14:16
• @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. Nov 25, 2015 at 17:17
• +1 for solution, but does this qualify? OP said full program. Nov 25, 2015 at 17:42
• 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) Nov 26, 2015 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";}

• 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. Nov 26, 2015 at 14:05
• @VisualMelon Great tips, thanks. It was annoying me how many bytes String.valueOf took up!
– RCB
Nov 26, 2015 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.

+[--[>]<<+>-]                          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

• 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.
– Neil
Jan 13, 2016 at 17:06

Prolog, 165 bytes

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

Code:

p(X,X,4).
p(_,32,0).
p(_,_,-1).
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').


Explained:

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


Example:

CABBDCABECDBACDBEAACADDBBBEDDA*CABEDDABDC BACDBBAADE  CBBEDDA.
73


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

c('CABBDCABECDBACDBEAACADDBBBEDDA','CABEDDABDC BACDBBAADE  CBBEDDA')


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

• You could save 2 bytes by replacing numel(q) with nnz(q). Nov 30, 2015 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]) { 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");
}


Really wanted to get the bonus :D

• 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. Nov 26, 2015 at 13:21
• Unfortunately, your submission is currently not complicit with the criteria, as it is unable to recognise invalid inputs. Nov 26, 2015 at 13:59
• @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 :) Nov 26, 2015 at 14:03
• @VisualMelon Yeah, this was submitted and written in school at the end of the class, I'll edit it. Thanks for the tricks :) Nov 26, 2015 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

#include<stdio.h>
#include<string.h>
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. :-(

Explanation

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

#include<stdio.h>
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:

#include<stdio.h>
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.