# Mark a UKMT IMC Paper

I did the IMC this year. Did anyone else here do it?

In a UKMT Intermediate Maths Challenge paper, there are twenty-five questions. The first fifteen questions give you five marks if you get them right. For the other ten questions, you get six marks for getting them right. In the last ten questions, you lose marks if you get them wrong! For questions sixteen to twenty, you lose one marks and for the last five questions, you lose two marks. If you leave a question blank, no marks are awarded or deducted. No marks are deducted for getting any of the first fifteen questions wrong. The paper is multiple choice; you can choose any answer out of A, B, C, D and E for each question. There is always just one right answer for each question.

Create a program/function that takes two strings and outputs a score. The first string will be your answers to the paper. If you skip a question, use a space, a null byte or an underscore. Otherwise, use the letter A, B, C, D or E for the answer. You can either have the inputs uppercase or lowercase. The second string will be the correct answers for each question in the paper. Your program/function will then output a score. Make your code short.

Test cases:

DDDDDDDDDDDDDDDDDDDDDDDDD
ABCDEABCDEABCDEABCDEABCDE
15

BDBEACCECEDDBDABBCBDAEBCD
BDBEACCECEDDBDABBCBDAEBCD
135

DBACBDCDBAEDABCDBEECACDC_
117

_________________________
DABDABDABDABDABDABDABDABD
0

99

_______________BBBBBBBBBB
AAAAAAAAAAAAAAAAAAAAAAAAA
-15

• Should "For questions fifteen to twenty" be "For questions sixteen to twenty"? – Greg Martin Feb 11 '17 at 9:43
• Can we use a null byte to represent skipped questions? – betseg Feb 11 '17 at 9:48
• And shouldn't the first score be 27-12=15? – Greg Martin Feb 11 '17 at 9:53
• Has anyone seen/done the UKMT papers? They are really fun. Check out the puzzles at ukmt.org.uk. I get most of my ideas for challenges from maths questions. – 0WJYxW9FMN Feb 11 '17 at 12:00
• Your test cases should probably include a submission with a negative score. – Dennis Feb 11 '17 at 14:44

# C, 888786 81 bytes

c,d;i(char*a,char*b){for(c=d=0;*b;c++,a++)d+=*a^*b++?*a?-c/15-c/20:0:5+c/15;d=d;}


Try it online!

• Since ABCDE are all below the point 95, I think you can use *a<95. – Yytsi Feb 11 '17 at 13:20
• Since the question allows taking null byte instead of underscores, -(c/15+c/20)*(*a<95) can become *a?-c/15-c/20:0. – Dennis Feb 11 '17 at 16:29
• 80 bytes – ceilingcat Nov 14 at 6:19

# Jelly, 2623 22 bytes

=s5ị"“HHHQP‘D¤_2Fæ.n⁶¥


Try it online!

### How it works

=s5ị"“HHHQP‘D¤_2Fæ.n⁶¥  Main link. Argument: t (answer to test), s (answer sheet)

=                       Test the characters of t and s for equality.
s5                     Split into chunks of length 5.
“HHHQP‘              Yield the code points, i.e., [72, 72, 72, 81, 80].
D             Decimal; yield [[7, 2], [7, 2], [7, 2], [8, 1], [8, 0]].
ị"                   Index zipwith; use the Booleans in each chunk to index into
the corresponding pair. Indexing is 1-based and modular, so
1 gives the first element and 0 the last.
_2        Subtract 2 from each result.
F       Flatten the resulting 5x5 matrix.
n⁶     Test the characters of t for inequality with space.
æ.     Take the dot product of the integers to the left and the
Booleans to the right.


## JavaScript (ES6), 7068 66 bytes

Saved 2 bytes thanks to Neil
Saved 2 bytes thanks to ETHproductions

Takes applicant answers a and correct answers c in currying syntax (a)(c). Expects skipped questions to be marked with a space.

a=>c=>a.replace(/\S/g,(a,i)=>s+=a==c[j=i>14,i]?5+j:-j^i>19,s=0)&&s


### Test cases

let f =

a=>c=>a.replace(/\S/g,(a,i)=>s+=a==c[j=i>14,i]?5+j:-j^i>19,s=0)&&s

console.log(f
("DDDDDDDDDDDDDDDDDDDDDDDDD")
("ABCDEABCDEABCDEABCDEABCDE")
);
console.log(f
("BDBEACCECEDDBDABBCBDAEBCD")
("BDBEACCECEDDBDABBCBDAEBCD")
);
console.log(f
("DBACBDCDBAEDABCDBEECACDC ")
);
console.log(f
("                         ")
("DABDABDABDABDABDABDABDABD")
);
console.log(f
);
console.log(f
("               BBBBBBBBBB")
("AAAAAAAAAAAAAAAAAAAAAAAAA")
);

• If you change the skipped question to a non-word character (e.g. space) then you can use /\w/g to save you two bytes. – Neil Feb 11 '17 at 12:09
• I think -j-(i>19) is the same as -j^i>19, though I'm not certain. – ETHproductions Feb 11 '17 at 15:46
• @ETHproductions Indeed. This is parsed as (-j)^(i>19) so, yes, this works. – Arnauld Feb 11 '17 at 20:16

# Python 2, 868583 77 bytes

f=lambda t,s,i=24:~i and(i/10*-(14<i<t[i]<'_'),5+i/15)[t[i]==s[i]]+f(t,s,i-1)


Try it online!

### How it works

This defines a recursive function f that takes two non-optimal arguments: t (the answers to the test) and s (the answer sheet). When called only with these two arguments, f initializes i to 24, the last index of both t and s.

Every time f is called, it first checks if ~i (the bitwise NOT of i) is truthy/non-zero. Since ~(-1) = 0, this happens once the i reaches the value -1. If i = -1, ~i = 0 is returned, but as i takes values from 24 to 0 (all indices of t and s), the code following and is executed and f returns the result.

While i is non-negative, the following happens. First,

(i/10*-(14<i<t[i]<'_'),5+i/15)


creates a tuple of length 2:

• The quotient i/10 is 0 if 0 ≤ i < 10, 1 if 10 ≤ i < 20, and 2 if 20 ≤ i < 25. The chained comparison 14<i<t[i]<'_' returns True if and only if all individual comparisons return True, i.e., if and only if i ≥ 15 (the range of questions with penalty), i is smaller than t[i] (always true since all numbers are smaller than all iterables in Python 2), and t[i] is not an underscore.

If the comparison returns False, the unary - returns 0 and the entire expression evaluates to 0. However, if the comparison returns True, the unary - returns -1, so the entire expression evaluates to 0 if 0 ≤ i < 10, -1 if 10 ≤ i < 20, and -2 if 20 ≤ i < 25; these are the net results for wrong or missing answers for all indices i.

• 5+i/15 returns 5 + 0 = 5 if 0 ≤ i < 15 and 5 + 1 = 6 if 15 ≤ i < 25. These are the net results for correct answers for all indices i.

Finally, [t[i]==s[i]] selects the first element of the constructed tuple if t[i] and s[i] differ (wrong or missing answer) and the second one if they are equal (correct answer), then adds the return value of f called with decremented i to that result. Once i reaches -1, the final score has been computed and is returned by f.

# Mathematica, 114 bytes

Tr@(m=MapThread)[#/.True->#2/.False->-#3&,{Tr/@Partition[m[Equal,#/."_"->u]/.u==_->0,5],{5,5,5,6,6},{0,0,0,1,2}}]&


Pure function taking an ordered pair of lists of characters and returning an integer. m[Equal,#/."_"->u] returns a list of booleans, except for unevaluated entries of the form u=="B" in places where the answer equaled "_"; then right away, u==_->0 turns those unevaluated entries into 0s. Tr/@Partition[...,5] adds these entries up 5 at a time, resulting in a list like {4False+True, 4False+True, 4False+True, 4False+True, 4False+True} for the first test case or {5True, 5True, 5True, 2True, 2True} for the last test case. Then in each coordinate, True and False are mapped to the appropriate scores, and the results are added together.

# Jelly, 22 21 bytes

Zm0ṁ135µ;t€⁶E€’;E€ṫ⁹S


Try it online!

I thought @Dennis's answer was probably beatable. And after trying out a huge number of different possibilities and benefitting from an amazing coincidence, I've finally managed it!

This program takes a pair of [student's answers, correct answers] as input, and uses spaces to indicate a missing answer.

## Explanation

This program uses some bizarre internal input formats to keep track of what's going on, so we'll take this a step at a time.

1. Z

This transposes the input, so we'll end up with a list of 25 elements, one for each question; each element is of the form [student's answer, correct answer]. We'll indicate an element of this form with a capital letter; A for question 1, B for question 2, and so on. So the answers are currently being stored as

ABCDEFGHIJKLMNOPQRSTUVWXY

2. m0

This is a "greater palindrome" operation; we append the reverse of the current value to the value itself, yielding this:

ABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQPONMLKJIHGFEDCBA

3. ṁ135

The ṁ (mold) operator does a number of things, but in this context, it effectively takes the first 135 elements of the infinite list produced by appending the current value to itself repeatedly. That gives us the following (which I've broken into groups of 50 elements for convenience; this is just a list of 135 pairs internally):

ABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQPONMLKJIHGFEDCBA
ABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQPONMLKJIHGFEDCBA
ABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQP

4. µ;

µ sets the current value as the new default for missing operands. We then immediately see a builtin that's missing an operand; ; appends, but we haven't specified what to append with. As a result, the current value is appended to the value as of the last µ (which is also the current value), giving us the following 270-element current value:

ABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQPONMLKJIHGFEDCBA
ABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQPONMLKJIHGFEDCBA
ABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQPABCDEFGHIJKLMNO
PQRSTUVWXYYXWVUTSRQPONMLKJIHGFEDCBAABCDEFGHIJKLMNO
PQRSTUVWXYYXWVUTSRQPONMLKJIHGFEDCBAABCDEFGHIJKLMNO
PQRSTUVWXYYXWVUTSRQP

5. t€⁶

Remember that all the capital letters above represent pairs of [student's answer, correct answer]. The t€⁶ operation operates on each (€) pair, and deletes (t) spaces (⁶) from either side of the pair (i.e. any space that appears in the pair). So we still have the same convoluted list of 270 questions with many repeats, but they're of the form [correct answer] (student didn't answer) or [student's answer, correct answer] (student did answer).

6. E€’

The E€’ operation also operates on each (€) element, and, due to the use of E, replaces the element with 1 if all the elements are equal (i.e. the student didn't answer or got the question right), or 0 if not all the elements are equal (i.e. the student answered but got the question wrong). The use of ’ here changes the numbering, meaning that we now use -1 or 0 respectively. I'll use lowercase letters for this new sort of element, which uses -1 for an answer which would be penalised if it were on a penalty-eligible question, or 0 for a missing or correct answer:

abcdefghijklmnopqrstuvwxyyxwvutsrqponmlkjihgfedcba
abcdefghijklmnopqrstuvwxyyxwvutsrqponmlkjihgfedcba
abcdefghijklmnopqrstuvwxyyxwvutsrqpabcdefghijklmno
pqrstuvwxyyxwvutsrqponmlkjihgfedcbaabcdefghijklmno
pqrstuvwxyyxwvutsrqponmlkjihgfedcbaabcdefghijklmno
pqrstuvwxyyxwvutsrqp

7. ;E€

We've seen both E€ and ; before; we're appending something to the current value, and we're using a format of 1 if all the elements are equal, or 0 if some are different (no ’ this time!). There's a missing operand here, so we use the value as of the last µ (i.e. the output of step 3). Back in step 3, we hadn't deleted spaces from the elements, so we'll have 1 for a correct answer, or 0 for an incorrect or missing answer (because a space won't match the correct answer). From now on, I'll use capital letters for this 1=correct, 0=incorrect/missing format, and continue to use lowercase letters for 0=correct/missing, -1=incorrect. The resulting value has 405 elements, and looks like this:

abcdefghijklmnopqrstuvwxyyxwvutsrqponmlkjihgfedcba
abcdefghijklmnopqrstuvwxyyxwvutsrqponmlkjihgfedcba
abcdefghijklmnopqrstuvwxyyxwvutsrqpabcdefghijklmno
pqrstuvwxyyxwvutsrqponmlkjihgfedcbaabcdefghijklmno
pqrstuvwxyyxwvutsrqponmlkjihgfedcbaabcdefghijklmno
pqrstuvwxyyxwvutsrqpABCDEFGHIJKLMNOPQRSTUVWXYYXWVU
TSRQPONMLKJIHGFEDCBAABCDEFGHIJKLMNOPQRSTUVWXYYXWVU
TSRQPONMLKJIHGFEDCBAABCDEFGHIJKLMNOPQRSTUVWXYYXWVU
TSRQP

8. ṫ⁹

Here comes the amazing coincidence I mentioned earlier. Before talking about this bit of the code, I want to take stock of where we've got to.

Each capital letter represents +1 for a correct answer; the first 15 questions (A through O) appear 5 times each in the string, and the last 10 questions (P through Y) appear 6 times each. That bit isn't really magical; I designed it that way when I chose the number 135 earlier on in the program (which is 5 × 15 + 6 × 10), and the only stroke of luck here is that 5 happens to be an odd number (so it's the last 10 questions that end up appearing the extra times, rather than the first 10). The 15 letters immediately preceding this contain p through t (the -1 penalty questions) once, and u through y (the -2 penalty questions) twice. That also isn't much of a coincidence; because we used m0 earlier, the extra copies of the questions are in the order PQRSTUVWXYYXWVUTSRQP, and the later questions will naturally occur near the middle of that string (so taking the last 15 of the "extra" questions will give fewer repeats to the ones near the edges; and of course, it's not a surprise that the "extra" questions come last).

Because each lowercase letter subtracts 1 from the score for an incorrect, non-missing answer, and each uppercase letter adds 1 to the score for a correct answer, we therefore simply need to take the last 135 + 15 = 150 elements in order to get each sort of element the correct number of times. Jelly's command for getting a substring at the end of a list is ṫ; however, it doesn't specify the number of elements you want, but rather the index of the first element you want. We have 405 elements at this point, and want 150, so we need to start at index (405 - 150 + 1), or 256. In an amazing coincidence, 256 happens to be the number of distinct octets that exist, and thus has a short representation in Jelly (⁹). There was very little I could do in order to make this happen; step 4 added another 135 elements to the start of the list in order to hit the round number, but the fact that it was 135 elements I had to add (a value which was readily available at that point in the program) was really convenient, with basically any other number being completely unhelpful in this situation.

Here's how the internal value looks now:

uvwxyyxwvutsrqpABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQP
ONMLKJIHGFEDCBAABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQP
ONMLKJIHGFEDCBAABCDEFGHIJKLMNOPQRSTUVWXYYXWVUTSRQP

9. S

Finally, now we've got a list of modifications to the score from questions, all we need to do is sum them using S, and we're done.

# Python 2, 93 91 bytes

f=lambda a,b,n=0:a>""and((a[0]==b[0])*(5+n/15)or-(n/15*n/10)*(a[0]<"^"))+f(a[1:],b[1:],n+1)


Try it online!

-2 bytes thanks to @KritixiLithos

## Input:

• a : Answers of the student as a string, _ for skipped question
• b : correct answers
• n : the number of the current question 0-based, defaults to 0
• You can do a[0]<'^' instead of a[0]!="_" to save bytes – Kritixi Lithos Feb 11 '17 at 11:06
• I think a>"" can work instead of a!="" – Kritixi Lithos Feb 11 '17 at 11:07
• If your initial check is just ending the recursion when a is empty, can't you just do a and? An empty string is false, otherwise it's true. – FlipTack Feb 11 '17 at 17:17
• @FlipTack this would throw a TypeError as the the last recursive call would return a string – ovs Feb 11 '17 at 17:25

# k, 52 bytes

Function takes 2 strings, format per the test cases

{+/(~x="_")*(+,/'(15 5 5#'0 -1 -2;15 10#'5 6))@'x=y}


Example:

k){+/(~x="_")*(+,/'(15 5 5#'0 -1 -2;15 10#'5 6))@'x=y}["DBADBDCDBAEDABCD_E__A__C_";"DBADBDCDBAEDABCDBEEDACDCA"]
99


i x a b|a>'Z'=0|a==b=6-0^x|1<2= -x
w x=x<\$[1..5*3^0^x]
(sum.).zipWith3 i(w=<<[0..2])


Usage example: ((sum.).zipWith3 i(w=<<[0..2])) "DBADBDCDBAEDABCD_E__A__C_" "DBADBDCDBAEDABCDBEEDACDCA" -> 99. Try it online!.

How it works: i x a b calculates the score for a single answer a with correct result b and the penalty x for a wrong answer (a non-negative value). If you skip (a>'Z'), the score is 0, if the answer is right (a==b), the score is 6-0^x, else the score is -x.

w=<<[0..2] makes a list of penalties for all 25 questions by applying w to 0, 1 and 2, i.e. making 5*3^0^x copies of each number (-> 15 times 0, 5 times 1 and 5 times 2).

zipWith3 applies i to the list of penalties, list of answers and list of correct results. Finally all scores are added (sum).

# Octave, 61 54 bytes

@(a,b)[a==b,-(a<95&a~=b)]*[(x=1:25>15)+5,(1:25>20)+x]'


Try it online!

@(a,b)(m=a==b)*(((f=kron(z=[0 0 0:2],z|1)')&1)+5)-(a<95&~m)*f


# JavaScript (ES6), 105103101948988858478 77 bytes

My first solution in ES6, maybe even first in Javascript O.o

f=(s,a,i=24)=>i+1&&(s[i]>'Z'?0:s[i]==a[i]?5+(i>14):~(i>19)*(i>14))+f(s,a,i-1)


s is the submitted solution and a is the correct solution. Both will be taken as strings.

Here's a non-recursive solution at 78 bytes:

s=>a=>eval([...s].map((c,i)=>c>'Z'?0:c==a[i]?5+(i>14):~(i>19)*(i>14)).join+)


Takes input through the currying syntax.

Thanks to @ETHproductions for saving 9 bytes! s[i] to c and (-1-(i>19|0)) to ~(i>19).

Thanks to @Kritixi Lithos for saving a byte! c=='_' to c>'Z'.

Try it online!

• Talking of this being your first solution in JS, what is your main language for code golf? I've only ever used Python and brainfuck for code golf. – 0WJYxW9FMN Feb 11 '17 at 13:32
• @J843136028 My main language is Python, but I have golfed quite a bit with C# too. Haxe has been laying off for a while, but I might return to it. – Yytsi Feb 11 '17 at 13:34
• Cool! I've only ever used brainfuck once, so my main language is Python too. – 0WJYxW9FMN Feb 11 '17 at 13:35
• @J843136028 Yeah, I've actually seen your answers here and there. As my bio yields, I don't spend that much time figuring out solutions, so they aren't of interest most of the time. I've only done so little with BrainF*ck, as it takes a lot of time to find short solutions, even to intermediate problems. – Yytsi Feb 11 '17 at 13:41
• I know what you mean about BF. I'm surprised people look at my answers. – 0WJYxW9FMN Feb 11 '17 at 13:55