1

I am using exam class to typeset an exam. As MWE, my exam has an MCQ section and Short answer sections.

In the grade table, I want the MCQ section to display as one row of total marks (instead of 20-30 boxes of 1 point), and the Short section to be by question. Since there is no pre-defined command, I thought to write my own table generator:

\documentclass[addpoints]{exam}

\begin{document}

\newcounter{qnitr} \setcounter{qnitr}{\firstqinrange{short}} \begin{tabular}{|c|c|c|} \hline Qns & Points & Score \\hline MCQ & \pointsinrange{mcq} & \\hline \whiledo{ \not ( \theqnitr > \lastqinrange{short} ) }{% \ifthenelse{ \not ( \theqnitr > \lastqinrange{short} ) }{% \theqnitr & \pointsofquestion{\theqnitr} & \ \hline }{blah} \stepcounter{qnitr} } Total: & \numpoints & \\hline \end{tabular}

\begin{questions} \section{MCQ Section} \begingradingrange{mcq} \question[2] MCQ Question 1

    \question[2]
    MCQ Question 2

    \question[2]
    MCQ Question 3
\endgradingrange{mcq}

\section{Short Answer Section}
\begingradingrange{short}
    \question
    This question has 3 parts.
    \begin{parts}
        \part[2]
            Part 1.
        \part[2]
            Part 2.
        \part[3]
            Part 3
    \end{parts}

    \question
    This question has 2 parts.
        \begin{parts}
        \part[5]
            Part 1.
        \part[4]
            Part 2.
    \end{parts}
\endgradingrange{short}
\end{questions}

\end{document}

What I want to achieve is a table like this:

enter image description here

However, \setcounter{qnitr}{\firstqinrange{short}} is giving me an error.

I've spent hours trying to look up expanding macros for use in \setcounter but I can't seem to find a solution. I learned that \setcounter is fragile and requires expansion to a number. But I've tried various combinations of \noexpand, \protect, \value and due to my lack of knowledge and understanding of LaTeX evaluation, I am just randomly trying.

I looked into the exam.cls to find clues on how the table is generated and found codes like

\edef\tbl@firstq{\csname range@\tbl@range @firstq\endcsname}%
\edef\tbl@lastq{\csname range@\tbl@range @lastq\endcsname}%
\let\first@pq@index=\tbl@firstq
\let\last@pq@index=\tbl@lastq

...

\setcounter{num@cols}{\tbl@lastq}% \addtocounter{num@cols}{-\tbl@firstq}%

How come the exam class is able to set a counter to a command but when I try to do it, it keeps failing?

welcomb
  • 80
  • You don't have to do anything special to expand a macro inside of \setcounter's second argument, but the macro you're using there has to be expandable. I'd guess that \firstqinrange isn't fully expandable, and hence you're getting these problems. – Skillmon Nov 08 '23 at 07:11
  • I still don't quite understand the expansion and evaluation mode. Even after spending few hours researching the on this. I'm guessing that \firstqinrange needs to be evaluated, but \setcounter is only doing expansion? In this case how can I evaluate it to a number, then use it in \setcounter? – welcomb Nov 08 '23 at 17:16

1 Answers1

2

By checking the exam.cls file. The actual number of question range generated using \firstqinrange and \lastqinrange can be called by these two marcos \range@short@firstq,\range@short@lastq

So the code should be edited as follow:

\documentclass[addpoints]{exam}

\begin{document}

\newcounter{fqr} \newcounter{lqr} \makeatletter \setcounter{fqr}{\range@short@firstq} \setcounter{lqr}{\range@short@lastq} \makeatother

\begin{tabular}{|c|c|c|} \hline Qns & Points & Score \\hline MCQ & \pointsinrange{mcq} & \\hline \whiledo{ \not ( \thefqr > \thelqr ) }{% \ifthenelse{ \not ( \thefqr > \thelqr ) }{% \thefqr & \pointsofquestion{\thefqr} & \ \hline% }{blah}% \stepcounter{fqr}% } Total: & \numpoints & \\hline \end{tabular}

\begin{questions} \section{MCQ Section} \begingradingrange{mcq} \question[2] MCQ Question 1

    \question[2]
    MCQ Question 2

    \question[2]
    MCQ Question 3
\endgradingrange{mcq}

\section{Short Answer Section}
\begingradingrange{short}
    \question
    This question has 3 parts.
    \begin{parts}
        \part[2]
            Part 1.
        \part[2]
            Part 2.
        \part[3]
            Part 3
    \end{parts}

    \question
    This question has 2 parts.
        \begin{parts}
        \part[5]
            Part 1.
        \part[4]
            Part 2.
    \end{parts}
\endgradingrange{short}
\end{questions}

\end{document}

enter image description here

Edit: I will try my best to ask your question. If anything mentioned here aren't right. Please correct me. The problem come from the \def command in \firstqinrange and \lastqinrange definition. If you take a look the definition of these two commands in exam.cls file. For example, for \firstqinrange:

\newcommand{\firstqinrange}[1]{%
  \def\tbl@range{#1}% define a macro using #1, e.g. If this is the short range, #1=short.
  \@ifundefined{range@\tbl@range @firstq}% use previous macro in here to judge if command \range@short@firstq has not been defined.
  {\bad@range}% If true, return a error.
  {\csname range@#1@firstq\endcsname}% If false, return \range@short@firstq.
}% firstqinrange

In this definition, the command \def is not expandable. Why? You could take a look this post. So to better illustrate this, we could patch the command \firstqinrange and \lastqinrange to eliminate the \def commands using package etoolbox.

\documentclass[addpoints]{exam}

\usepackage{etoolbox} \makeatletter \patchcmd{\firstqinrange}{\def\tbl@range{#1}@ifundefined{range@\tbl@range @firstq}}{@ifundefined{range@#1@firstq}}{}{}

%% get rid of \def\tbl@range{#1} and using range@#1@firstq as value testing the define state.

\patchcmd{\lastqinrange}{\def\tbl@range{#1}@ifundefined{range@\tbl@range @lastq}}{@ifundefined{range@#1@lastq}}{}{}

%% get rid of \def\tbl@range{#1} and using range@#1@lastq as value testing the define state. \makeatother \begin{document}

\newcounter{fqr} \newcounter{lqr} \setcounter{fqr}{\firstqinrange{short}}%%use \firstqinrange{short} to set the counter \setcounter{lqr}{\lastqinrange{short}}%%use \firstqinrange{short} to set the counter

\begin{tabular}{|c|c|c|} \hline Qns & Points & Score \\hline MCQ & \pointsinrange{mcq} & \\hline \whiledo{ \not ( \thefqr > \thelqr ) }{% \ifthenelse{ \not ( \thefqr > \thelqr ) }{% \thefqr & \pointsofquestion{\thefqr} & \ \hline% }{blah}% \stepcounter{fqr}% } Total: & \numpoints & \\hline \end{tabular}

\begin{questions} \section{MCQ Section} \begingradingrange{mcq} \question[2] MCQ Question 1

    \question[2]
    MCQ Question 2

    \question[2]
    MCQ Question 3
\endgradingrange{mcq}

\section{Short Answer Section}
\begingradingrange{short}
    \question
    This question has 3 parts.
    \begin{parts}
        \part[2]
            Part 1.
        \part[2]
            Part 2.
        \part[3]
            Part 3
    \end{parts}

    \question
    This question has 2 parts.
        \begin{parts}
        \part[5]
            Part 1.
        \part[4]
            Part 2.
    \end{parts}
\endgradingrange{short}
\end{questions}

\end{document}

Right now the code is using \setcounter{fqr}{\firstqinrange{short}} to set the counter and It is successfully compiled without errors.

Tom
  • 7,318
  • 4
  • 21
  • I guess that works. I tried doing that in the document but it fails because the macro name has a @. I read that macros uses such special characters are not meant to be used externally. Which is the purpose of \firstqinrange. So while this "hack" immediately solves my problem, I would still like to learn if there is a less "hackish" solution? – welcomb Nov 08 '23 at 17:19
  • I'll accept this answer because it solves my problem. But I would still like to learn why \setcounter{range@short@firstq} works but \setcounter{\firstqinrange{short}} fails. – welcomb Nov 13 '23 at 07:16
  • @welcomb I made some additional explanations for your question. See the new edits in the answer. – Tom Nov 13 '23 at 21:46