#!/usr/bin/perl $CopyRight = <<'EOCOPYRIGHT'; Copyright (C) 2007 Kofi Laing (aklaing@gmail.com). This program is free. You may not sell it. If you modify it, please let me know the modifications you made. You may not assume that this program correctly does anything. EOCOPYRIGHT $HelpFile = <<'EOHELP'; SYNOPSIS: QuizMixer (qm.pl) typesets a multiple choice exam for a class (via latex), but using different question orders for different students, or different answer orders for different students, or both. In all cases grading is extremely easy because an answer key is written out for the user (which obviously depends on which randomization options were chosen, and which orders were used for the questions and answers respectively). All exams can be different in ordering, or they can be all identical. INPUT FILE FORMAT: A sequence of paragraphs (meaning chunks of text separated by two or more newlines. Each question or answer must correspond to a single paragraph in order to be identified as a unit. Questions start a new line with the three characters 'Q: ' (cap Q, colon and space). The text of the question follows in normal latex. (it should typeset cleanly as an \item in an enumerate environment), but as written here, it should not include the \item prefix, those will be provided automatically. Answers are paragraphs which do not begin with 'Q: ', and are considered to be options for the previous question. The correct answer should always be provided first after the question. Any other answers following that are wrong options, and there can be as few as one (or as many as 25) wrong options, for a total of between two and 26 options per question. 26 comes from the number of letters in the alphabet. The actual number of options provided is not counted and checked by the program -- we assume the user will provide sensible inputs. Note that even though the correct answer is always listed first in the input file format, if you choose not to randomize the order of your answers, they will still be randomized (ie the first option will not always be correct), but they will not be randomized in the sense that different students will all get the same order of answer options. The positions chosen for the correct answers will be indicated in the answer key on the last page. Finally, the last paragraph should be 'Q: end'. This is not part of the exam, but just a hack to help the parsing of the file. It is necessary for correct processing of the file. To obtain a silly example of a sample test, use the command $ qm.pl sample > It is instructive to do this and look at the sample source file, and also to run qm.pl on it to look at the corresponding sample pdf exam. OUTPUT FILE FORMAT: A single pdf file, with command line arguments documented on the first page, and a complete answer key on the last page. Each page contains an answer area which has identical format to the answer key so they can be compared quickly when placed close to each other. LIMITATIONS: This works best with no more than 12 or so questions -- as many as will fit on a single letter-sized page. This is because of the width of the answer box table, and the fact that I don't like to put exams onto many pages (don't) want to deal with breaking pages and columns nicely. USAGE: $ qm.pl $ qm.pl help both pull up the help file you are now reading. $ qm.pl sample dumps a sample source file onto standard output. This may be saved with a redirection and used to try out the program. In normal usage, the command line arguments for typesetting a source file are as follows: $ qm.pl is the source filename for the latex code which creates the quiz. The intermediate filename created will be called .tex, and the pdf file created will be .pdf. It should conform to the input file format described above. This is a natural number. This is the number of different quizzes that will be created. Indicates how many students will be taking the test (or you can fool the program if you want spare copies of the test for whatever reason). Argument format is not checked. Natural number (format unchecked). How many minutes to allot for the test. This is currently unused but could easily be used in a sentence on the exam that tells the students how many minutes they have to work. Random-looking character string, typically several chars wide. First function is to suppress meaningful ordinal information about the tests so that superstitious students are not helped or spooked by their favorite or dreaded numbers. Second function (when it is five or more characters wide) is to uniquely identify this test. This way the same questions can be used on different occasions with different values for this argument (and maybe different command line options) without confusion. This can be a natural number or the word 'system'. If it is 'system', then the randomization will seeded according to the current time, process id, and a digital bitstring derived from the state of the computer it is running. This will of course not be repeatable. OTOH if it is a natural number then the randomization will be repeatable. 1 or 0, to indicate whether or not questions should appear in different orders for different students. 1 or 0, to indicate whether or not answer options for each question should appear in different orders for different students. Example Command: $ qm.pl sampletest 5 10 9duv0A 2398 1 1 This typesets from file 'sampletest', for 5 students, allotting 10 minutes for the quiz, numbering the exams from '9duv0A', with a seed of 2398, mixing up both question orders and answer orders for each new exam. PHILOSOPHY: This program assumes (without proof, evidence, discussion or debate) that multiple choice quizzes (short quizzes in particular) are useful. The second assumption is that mixing up question orders and/or answer orders improves the effectiveness of the test against cheating with covert communication channels. However almost anything (including this program) can be abused so use with care. Traditional identical exams may be obtained by not randomizing question or answer orders. When questions are in order of increasing difficulty, that order may be preserved by randomizing answer orders but not the question order. When it is believed that the order in which answers are presented can influence the choices of a student, then one may wish to use identical answer orders for the students, but mix up the question orders. One can also create exams that are effective with questions in different orders and answers in different orders. In all cases students should be told that they need to read all the options before answering a question and not jump to conclusions after reading only some of the options. LICENSE: This program is licensed under the GPL v2. EOHELP $SampleFile = <<'EOSAMPLE'; Q: Which of the following causes the cursor to move to the beginning of the next line in C++? endl endline newline writeline writeln Q: What does the following code print out? \begin{verbatim} for(i=2; i < 11; i+=3) cout << i ; \end{verbatim} 2 5 8 1 2 3 4 5 6 7 8 9 10 2 3 4 5 6 7 8 9 10 2 4 6 8 10 2 5 8 11 Q: Which of the following statements is the odd one out? \verb|for (i=1; i < 5; i++) cout << endl;| \verb|for (i=0; i < 5; i++) cout << endl;| \verb|cout << endl << endl << endl << endl << endl;| \verb|i=0; while(i < 5) { cout << endl; i++ }| \begin{verbatim} cout << endl; cout << endl; cout << endl; cout << endl; cout << endl; \end{verbatim} Q: Which of the following statements is the odd one out? \verb|if (val < 0) abs = -1 * val; abs = val;| \verb|if (val < 0) abs = -1 * val;| \verb|if (val < 0) abs = -1 * val; else val = abs;| \verb|abs = val; if (abs < 0) abs = -1 * abs;| \verb|abs = (val < 0 ? -1 * val : val );| Q: Which of the following is not a reason why we write "using namespace std; " at the top of a program? Because it looks good and professional. Because it makes the std namespace the default. Because it makes our often makes programs easier to read. Because it allows us to use endl without qualification Because it allows us to use cout and cin without qualification Q: What is the color of the sky? Blue Green Yellow Red White Q: How many tires do most cars have? Four One Two Three Five Q: When does the sun go down? At dusk At noon At dawn At night At sunrise Q: What shape is the earth? Spherical Circular Conical Square Cubic Q: end EOSAMPLE ######################################################################## @Options = split(//, 'abcdefghijklmnopqrstuvwxyz'); sub shuffled_list { my($UpperLimit) = @_; my(@A) = (1..$UpperLimit); for ($II = 0; $II < $UpperLimit-1; $II++) { $Pos = $II + int(rand($UpperLimit - $II)); $Temp = $A[$Pos]; $A[$Pos] = $A[$II]; $A[$II] = $Temp; } return @A; } sub oneposition { my(@A) = @_; my($Ret); for ($R = 0; $R <= $#A; $R++) { if ($A[$R] == 1) { $Ret = $Options[$R]; } } # print "In oneposition: given ", join(' ', @A), " ans=", $Ret, "\n"; return $Ret; } if ($#ARGV == -1 || $#ARGV == 0 && $ARGV[0] eq 'help') { open(HELPFILE, "| less"); print HELPFILE $HelpFile; close HELPFILE; exit(0); } if ($#ARGV == 0 && $ARGV[0] eq 'sample') { print $SampleFile; exit(0); } ($FileName, $NumTests, $NumMinutes, $UIDStart, $Seed, $RandomizeQuestions, $RandomizeAnswers) = @ARGV; if ($Seed eq 'system') { $Seed = time ^ $$ ^ unpack "%L*", `ps axww | gzip`; srand ($Seed); } elsif ($Seed =~ /^\d+$/) { srand($Seed); } else { die "The seed $Seed is not good. Use a number or the word 'system'\n"; } open(FILE, "< $FileName"); $FileString = join('', ); close FILE; @Paragraphs = grep(/\S/, split(/\n\n\n*/, $FileString)); %Question = (); %Answer = (); $QNum = 0; $ANum = 0; foreach $Par (@Paragraphs) { if ($Par =~ /^Q:\s((.|\n)*)/) { $QANum{$QNum} = $ANum; $QNum++; $Question{$QNum} = $1; $ANum = 0; # print 'Question found: ', $1, "\n"; } else { $ANum++; $Answer{$QNum}{$ANum} = $Par; } } $QNum--; open(OUT, "> $FileName.tex"); print OUT <<"EOTEXT"; \\documentclass\[10pt,twocolumn\]{article} \\addtolength{\\textheight}{2.2in} \\addtolength{\\topmargin}{-0.8in} \\addtolength{\\oddsidemargin}{-0.7in} \\addtolength{\\textwidth}{1.4in} \\title{Test Source File: $FileName} \\author{Formatted by: $ENV{'USER'}} \\date{\\today} \\pagestyle{empty} \\thispagestyle{empty} \\begin{document} \\pagestyle{empty} \\thispagestyle{empty} \\maketitle {\\bf WARNING!!!} The answer key is on the last page. Please {\\bf remove} it (and this top sheet) before handing out the rest of the sheets. Below are the command line arguments with which this exam file was created. EOTEXT print OUT "\\begin{itemize}\n"; print OUT "\\item Test Source FileName: $FileName\n"; print OUT "\\item Number of Test Sheets: $NumTests\n"; print OUT "\\item Time Allotted (Minutes): $NumMinutes\n"; print OUT "\\item Starting UID: $UIDStart\n"; print OUT "\\item Random Seed: $Seed\n"; print OUT "\\item Are Questions Shuffled?: ", ($RandomizeQuestions ? "Yes" : "No"), "\n"; print OUT "\\item Are Answers Shuffled?: ", ($RandomizeAnswers ? "Yes" : "No"), "\n"; print OUT "\\end{itemize}\n"; print OUT <<"EOTEXT"; \\twocolumn \\newpage EOTEXT @UniqueIDs = (); $UID = $UIDStart; @QGuide = (1..$QNum); # Write the column headers of the answer key into a string $Ak = "\\begin{center}\n\\begin{tabular}{"; $Ak .= '|l|'; for ($J = 0; $J < $QNum; $J++) { $Ak .= 'c|'; } $Ak .= "}\n\\hline "; $Ak .= "ID & "; for ($J = 1; $J < $QNum; $J++) { $Ak .= " $J &"; } $Ak .= " $J \\\\\\hline\n"; # Write the bottom of the table $Ek = "\\end{tabular}\n\\end{center}"; for ($TestNum = 1; $TestNum <= $NumTests; $TestNum++) { $UID++; push(@UniqueIDs, $UID); # print "This is test number $TestNum\n"; if ($RandomizeQuestions) { @QGuide = &shuffled_list($QNum); # print 'Randomize Question List : ', join(' ', @QGuide), "\n"; } print OUT "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; print OUT "\n\\subsubsection*{Quiz Date: \\today}\n"; print OUT "Please write your name here: \\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\n"; print OUT "Please {\\bf do not} circle your answers --- instead, please write your answers {\\em in these boxes below}:\n"; # print out each page-specific two-line-table for answers. print OUT $Ak; # one middle line of the table print OUT $UID , " & "; for ($J = 1; $J < $QNum; $J++) { print OUT " & "; } print OUT "\\\\\\hline\n"; $AnswerKey .= $UID . " & "; print OUT $Ek; print OUT "\n\\begin{enumerate}\n"; for ($Q = 1; $Q <= $QNum; $Q++) { # print "here $Q $QNum $QGuide[$Q-1]\n"; print OUT '\item ', $Question{$QGuide[$Q-1]}, "\n"; print OUT "\\begin{enumerate}\n"; if ($RandomizeAnswers || $TestNum == 1 ) { @AGuide = &shuffled_list($QANum{$QGuide[$Q-1]}); } if (! $RandomizeAnswers) { if ($TestNum == 1) { @{$QAGuide[$QGuide[$Q-1]]} = @AGuide; } else { @AGuide = @{$QAGuide[$QGuide[$Q-1]]}; } } $AnswerKey .= &oneposition(@AGuide); for ($A = 1; $A <= $QANum{$QGuide[$Q-1]}; $A++) { print OUT "\\item "; print OUT $Answer{$QGuide[$Q-1]}{$AGuide[$A-1]}; print OUT "\n\n"; } print OUT "\\end{enumerate}\n"; print OUT "\n\n\n\n\n"; if ($Q == $QNum) { $AnswerKey .= "\\\\\\hline\n"; } else { $AnswerKey .= " & "; } } print OUT "\n\\end{enumerate}\n\\newpage"; EOTEXT } print OUT <<'EOTEXT'; \subsubsection*{Answer Key} EOTEXT print OUT $Ak; print OUT $AnswerKey, "\n"; print OUT $Ek; print OUT <<'EOTEXT'; \end{document} EOTEXT close OUT; system "pdflatex $FileName.tex"; # system "lpr $FileName.pdf"; __END__