g | x | w | all
Bytes Lang Time Link
022Vyxal 3 o250806T000524Zpacman25
037Pip s170329T222719ZDLosc
046Jolf170330T183652ZConor O&
nanStacked170329T230310ZConor O&
045CJam170330T145241ZBusiness
088JavaScript ES6170329T214537ZETHprodu
04305AB1E170330T082527ZEmigna
242Batch170330T075742ZNeil
093Python 2170329T220700ZRod

Vyxal 3 -o, 22 bytes

'|s~⨥=:eṬ›„,∥∑L÷k1×◌①÷

Vyxal It Online!

so uh the input can just be split on | and then take the strings of length 1, 6 bytes are spent on rounding the score properly

Pip -s, 48 45 43 47 44 37 bytes

I'm definitely adding a rounding operator to my golfing language after this. :P
– DLosc, Mar 30, 2017 at 19:37

Well, it only took seven years...

/t*RNm-m/#b*#PFI{$NIb&Ua}MEbZa@`\|..`

Takes input as command-line arguments (the scan-tron page will need quoting and escaping of newlines if you run it from an actual command line). Outputs the missed questions first, then the score. Attempt This Online!

Explanation

I'm gonna do this in two parts: the incorrect questions list and the score.

PFI{$NIb&Ua}MEbZa@`\|..`
                          a,b are cmdline args (implicit)
                a@`\|..`  Find all occurrences in a of | followed by 2 chars
                          Regex matches don't overlap, so this finds each
                          selected answer and nothing else
              bZ          Zip with b, the correct answers
   {       }ME            Enumerate the resulting pairs and map this function
                          to them:
    $NIb                    Fold the pair on the not-in operator
                            Gives 0 if correct answer matches selected answer,
                            1 otherwise
        &                   Logical and
         Ua                 Enumeration index incremented (1, 2, ...)
                            This gives the question number if the answer
                            was incorrect, or 0 if it was correct
 FI                       Filter out the falsey (0) elements
P                         Print, joining on spaces (-s flag)
/t*RNm-m/#b*#...
                  a,b are cmdline args, t is 10, m is 1000 (implicit)
             ...  The code from the first part
            #     Length of that list (i.e. number of incorrect answers)
       m/#b*      Times 1000/(number of questions)
     m-           Subtracted from 1000
                  We now have a number like 666.666666666667
   RN             Round to nearest integer
/t*               Times 1/10
                  Print (implicit)

Jolf, 46 bytes

I can't seem to break 46 bytes. I have two solutions of this length. Try one here!

ΆRγψ~mΖ mi«\|..»d?=€H.xSEhSdHήSmX*~1/-lζlγlζ_1

(Replace with 0x7f in the next one)

ΆRγψΜΖψGi'|d=1lHd?□=H.xShSEdHήSmX*~1/-lζlγlζ_1

In either case, 15 bytes for rounding: mX*~1/-lζlγlζ_1. They are, for the most part, the same, except one uses a regex match to get the results, and the other splits on pipes.

Stacked, 68 + 1 = 69 bytes

'|'split[#'1-]NO neq::size:@z~>*[]YES' '#`out is0 sum z/100*1 nround

Try it online! +1 for -p flag (this script can be executed as stacked -pe "...")

Takes two inputs from the top of the stack.

Some interesting features:

[#'1-]NO
[    ]NO   do not keep members where
 #'          its length
   1-          -1
             is truthy (in this case, not equal to zero).

This yields all letters surrounded by pipes.

:size:@z~>*[]YES
:                 duplicate indices of incorrect answers
 size             length of incorrect answers
     :@z          (stored into z)
        ~>        range from 1 to this length
          *       and multiply by this range
           []YES  keep truthy elements

This gives us all incorrect question numbers.

CJam, 47 45 bytes

lqN/(;3%_'|f#:).=.=__:+\,d/e2XmOn:!_,,:).*0-p

Try it online!

Explanation

The program is in three main parts:

Right/wrong list

l                    e# Read the first line of input (answer key)
 qN/                 e# Read the rest of the input and split it on newlines
    (;3%             e# Delete the first line, then select every 3rd line 
        _            e# Duplicate the array
         '|f#        e# Find the index of the first | in each answer
             :)      e# Increment each, gives the index of the selected letter for each answer
               .=    e# Vectorized get-element-at with the answer strings
                 .=  e# Vectorized equality check with the answer key

After this section, we have an array of 0s and 1s, where 0 indicates a wrong answer and 1 a right answer.

Score

__              e# Duplicate the right/wrong list twice
  :+            e# Take the sum of it (number of right answers)
    \,          e# Swap top elements and take the length (total number of questions)
      d/        e# Divide (casting to double so it's not integer division)
        e2      e# Multiply by 10^2
          XmO   e# Round to 1 decimal place
             n  e# Pop and print with a newline

After this section, the stack contains only the right/wrong list, and the percentage score has been output.

Wrong answers

:!            e# Logically negate each element of the right/wrong list
  _,,:)       e# Generate the inclusive range 1...length(list)
       .*     e# Vectorized multiplication of the two lists
         0-   e# Remove any 0s from the result
           p  e# Print it

JavaScript (ES6), 88 bytes

x=>y=>x.replace(/\w(?=\|)/g,c=>c==y[i++]?t++:a+=i+" ",a=i=t="")&&(t/i*1e3+.5|0)/10+`
`+a

I could save 5 bytes by using commas and returning everything one one line:

x=>y=>x.replace(/\w(?=\|)/g,c=>c==y[i++]?t++:a+=[,i],a=i=t="")&&(t/i*1e3+.5|0)/10+a

05AB1E, 43 bytes

U|3ôø`\vyy'|k>èXNèQˆ}¯OXg/3°*2z+ïT/XgL¯_Ï‚»

Try it online!

Explanation

U                                            # store the answer key in X
 |3ô                                         # split the question-rows in chunks of 3
    ø`                                       # zip and flatten
      \                                      # discard top of stack, leaving the list of
                                             # answer rows on top
       v                                     # for each answer row
         y'|k                                # get the index of the first "|"
        y    >è                              # get the character after that from the row
               XNèQ                          # compare it to the corresponding entry in 
                                             # the answer key
                   ˆ                         # add it to the global list
                    }                        # end loop
                     ¯O                      # calculate the number of correct answers
                       Xg/                   # divide by the length of the answer key
                          3°*                # multiply by 1000
                             2z+             # add 0.5
                                ï            # convert to integer
                                 T/          # divide by 10
                                   XgL       # push range [1 ... len(answer key)]
                                      ¯_Ï    # keep only numbers corresponding to 
                                             # wrong answers
                                          ‚» # format output

Batch, 242 bytes

@echo off
set/as=c=0
set m=
set/pk=
:l
set/ac+=1
set/pt=
set/pl=
set/pt=
set "l=%l:*|=%
if %l:~,1%==%k:~,1% (set/as+=1)else set m=%m% %c%
set k=%k:~1%
if not "%k%"=="" goto l
set/as=(s*2000/c+1)/2
echo(%s:~,-1%.%s:~-1%
echo(%m%

Reads in the answer key on STDIN first, then n*3 question rows. Note: Score is printed without a leading zero if it is less than 1.0. Missed answers are printed with a leading space.

Python 2, 94 93 bytes

-1 byte thanks to L3viathan

s,a=input()
l=len(s)
w=[i+1for i in range(l)if"|%s|"%a[i]not in s[i]]
print(l-len(w))*1e2/l,w

Try it online!