2

I'm building an exercise database. The exercises are stored in the form

\begin{Exer}
 Text of the exercice
  \item first answer
  \item* second answer
 \end{Exer}

where the * denotes the correct answer

And from this I need to define macros \text, \firstanswer, \secondanswer, \correctanswer. For this I found packages environ, and listofitems, thank to this answer. But now, I need to do it with a counter conting, namely I need to define thing as "\textN", depending on N.

I think that my problem is an expansion problem, indeed my macro \correctanswerN works, while the very same defined \textN gives me errors. I guess that the fact is that \correctanswer is basically a number, while \text is a macro. (and if I use gdef instead of edef, I get no errors, but the output is wrong as things are not "expanded" so successive calls of Exer erase my data)

I tried to put some \expandafter\noexpand around (as I read in the listofitem doc that "items need a double expansion") but without succes.

Anyway, here my attempt (you may go directly to lines where \text\roman{\num} is defined)

\documentclass{amsart}
\usepackage{listofitems,environ}
\newcounter{num}
\setcounter{num}{0}

\NewEnviron{Exer}{%
  \stepcounter{num}% 
  \setsepchar{\item/*}%
  \greadlist*\myitem{\BODY}%
  \gdef\correct{0}%
  \foreachitem\z\in\myitem[]{%
    \ifnum\listlen\myitem[\zcnt]>1\relax
    \xdef\correct{\the\numexpr\zcnt-1}\fi
}%      
\expandafter\xdef\csname text\roman{num}\endcsname{\myitem[1]}%
%
\expandafter\xdef\csname firstanswer\roman{num}\endcsname{\edef\tmp{\listlen\myitem[2]}%
  \myitem[2,\tmp]}%
%
\expandafter\xdef\csname secondanswer\roman{num}\endcsname{\edef\tmp{\listlen\myitem[3]}%
  \myitem[3,\tmp]}%
%
\expandafter\xdef\csname correctanswer\roman{num}\endcsname{\correct}%
}


\begin{document}
\begin{Exer}
First exercise
\item First answer
\item* Second answer
\end{Exer}

\begin{Exer}
SECOND exercise
\item* A
\item B
\end{Exer}

 
The first exercise:

 \texti

 possibilities: 1) \firstansweri, 2) \secondansweri


 The correct answer is  \correctansweri


\

 

 Second exercise

 \textii
 
 Possibilities: 1) \firstanswerii, 2) \secondanswerii

 The correct answer is \correctanswerii

\end{document}
4
  • 1
    Just an FYI. A double expansion would be, for example, \expandafter\expandafter\expandafter\BLAH\myitem[3]. In such a case, \BLAH would see the actual tokens comprising \myitem[3]. Apr 30, 2022 at 15:04
  • @StevenB.Segletes so just put 3 \expandafter before \myitem would work! You are my hero!
    – user126154
    Apr 30, 2022 at 15:09
  • @StevenB.Segletes wow, this works fine for my magro \text, but NOT for the macro \firstanswer. Where I should put the tree \expandafter in this case?
    – user126154
    Apr 30, 2022 at 15:12
  • @StevenB.Segletes No Way, it "seemed" to work because in simple example, the content of \myitem[1] is not complicated, but if it is something like a mathematical formula, it gives errors, no matter how many \expandafter I put before
    – user126154
    Apr 30, 2022 at 15:50

3 Answers 3

2

I suppose the attempts at defining \tmp take place because nesting optional arguments didn`t succeed.

You can nest optional arguments if the entire content of the optional argument is nested in curly braces, i.e., you can't do \foo[\bar[baz]], but you can do \foo[{\bar[baz]}] or \foo[{\bar[{baz}]}]. The braces surrounding the entire content of an optional argument get stripped off when the optional argument is processed, but at the time of grabbing the optional argument they serve the purpose of hiding nested ] belonging to a nested optional argument.

Besides this you can't do assignments (like \edef) inside \xdef. \xdef expands expandable tokens and defines a macro from the resulting set of tokens. But \xdef does not carry out unexpandable tokens like \edef, so with
\expandafter\xdef\csname firstanswer\roman{num}\endcsname{\edef\tmp{\listlen\myitem[2]}
you get something like
\xdef\firstansweri{\edef\tmp{\listlen\myitem[2]}.
Now \xdef triggers expansion of expandable tokens of the definition-text: The token \edef is not expandable. It is left in place. The token \tmp is expanded if already defined. If undefined, you get an error-message. { is not expandable and is left in place. The sequence \listlen\myitem[2] gets expanded. } is not expandable and is left in place.
Thus your \xdef is like
\def\firstansweri{\edef⟨full expansion of \tmp⟩{⟨full expansion of \listlen\myitem[2]⟩}.
I suppose this is not what you had in mind.

A more straightforward approach might be:

\documentclass{amsart}
\usepackage{listofitems,environ}
\newcounter{num}

\makeatletter
\@ifdefinable\gobbleanswer{\def\gobbleanswer answer{}}%
\newcommand\MapNumberToOrdinalword[1]{%
  \ifcase\expandafter\@firstofone\expandafter{\number#1}\@sptoken text\expandafter\gobbleanswer
  \or first%
  \or second%
  \or third%
  \or fourth%
  %...
  \else last%
  \fi
}%
\NewEnviron{Exer}{%
  \stepcounter{num}% 
  \setsepchar{\item/*}%
  \greadlist*\myitem{\BODY}%
  \foreachitem\z\in\myitem[]{%
    \ifnum\listlen\myitem[{\zcnt}]>1\relax
      \expandafter\@ifdefinable\csname correctanswer\roman{num}\endcsname{%
        \expandafter\xdef\csname correctanswer\roman{num}\endcsname{\the\numexpr\zcnt-1\relax}%
      }%
    \fi
    \expandafter\@ifdefinable
    \csname\MapNumberToOrdinalword{\numexpr\zcnt-1\relax}answer\roman{num}\endcsname{%
      \expandafter\gdef
      \csname\MapNumberToOrdinalword{\numexpr\zcnt-1\relax}answer\roman{num}%
      \expandafter\expandafter\expandafter\endcsname
      \expandafter\expandafter\expandafter{%
         \myitem[{\zcnt,\listlen\myitem[{\zcnt}]}]%
      }%
    }%
  }%      
}%
\makeatother

\begin{document}
\begin{Exer}
FIRST exercise -- let $F:X\to Y$ be as $f:X\to Y f=\begin{pmatrix} a&b\\ c&d\end{pmatrix}$ 
\item First answer
\item* Second answer
\end{Exer}

\begin{Exer}
SECOND exercise
\item* A
\item B
\end{Exer}

 
The first exercise:

\texti

possibilities: 1) \firstansweri, 2) \secondansweri

The correct answer is  \correctansweri


\bigskip

The second exercise

\textii
 
Possibilities: 1) \firstanswerii, 2) \secondanswerii

The correct answer is \correctanswerii

\end{document}

enter image description here

3
  • That's great! But there is some "expansion problem". Try tu put as text of first exercise some complicated formula like FIRST exercise let $F:X\to Y$ be as $f:X\to Y f=\begin{pmatrix} a&b\\ c&d\end{pmatrix}$ It will gives errors
    – user126154
    Apr 30, 2022 at 17:16
  • and the same if one has complicated formulas in answers
    – user126154
    Apr 30, 2022 at 17:17
  • The problem with expansion has been solved. Another problem with this code is the presence of * in questions. I solved it by replacing the procedure of checking which answer is correct. Namely, the proposed code check if the list \myitem[\zcnt] is longer than one, which happens every times that some anwer contains some * around. The workaround is to check if the first element of \myitem[\zcnt] is empty (which happens only if after \item there is immediately *).
    – user126154
    May 13, 2022 at 15:51
3

There are two main issues.

One was the confusion brought on by \edef\tmp{\listlen\myitem[2]}\myitem[2,\tmp], because \tmp itself contained a closing right bracket. It turns out one doesn't even need \tmp; however, one must isolate the inner right bracket in its own group, as in \myitem[2,{\listlen\myitem[2]}]. In this way, the closing right brackets don't get confused.

The 2nd issue is the use of \xdef. Some math constructs are unexpandable, and so the use of \xdef provokes an error. To avoid that, I (define and) use \gdefxx instead of \xdef. With this construct, the argument #2 gets expanded twice, instead of fully expanded. Since #2 is one of the list items, this twice-expansion will recover the original tokens found in the list definition.

\documentclass{amsart}
\usepackage{listofitems,environ}
\newcounter{num}
\setcounter{num}{0}

\newcommand\gdefx[2]{\expandafter\gdef\expandafter#1\expandafter{#2}}
\newcommand\gdefxx[2]{\expandafter\gdefx\expandafter#1\expandafter{#2}}

\NewEnviron{Exer}{%
  \stepcounter{num}% 
  \setsepchar{\item/*}%
  \greadlist*\myitem{\BODY}%
  \gdef\correct{0}%
  \foreachitem\z\in\myitem[]{%
    \ifnum\listlen\myitem[\zcnt]>1\relax
    \gdefx\correct{\the\numexpr\zcnt-1}\fi
}%      
\expandafter\gdefxx\csname text\roman{num}\endcsname{\myitem[1]}%
%
\expandafter\gdefxx\csname firstanswer\roman{num}\endcsname{%
  \myitem[2,{\listlen\myitem[2]}]}%
%
\expandafter\gdefxx\csname secondanswer\roman{num}\endcsname{%
  \myitem[3,{\listlen\myitem[3]}]}%
%
\expandafter\gdefx\csname correctanswer\roman{num}\endcsname{\correct}%
}


\begin{document}
\begin{Exer}
My First exercise is, what is the answer to $y=mx+b$?
\item First answer
\item* Second answer
\end{Exer}

\begin{Exer}
SECOND exercise
\item* A
\item B
\end{Exer}

\begin{Exer} 
My next exercise is, let $f:X \to Y, f=\begin{pmatrix}a & b\\ c&d\end{pmatrix}$ 
\item ok 
\item* right 
\end{Exer}

 
The first exercise:

 \texti

 possibilities: 1) \firstansweri, 2) \secondansweri


 The correct answer is  \correctansweri

 

 Second exercise

 \textii
 

 Possibilities: 1) \firstanswerii, 2) \secondanswerii

 The correct answer is \correctanswerii


 Third exercise

 \textiii
 

 Possibilities: 1) \firstansweriii, 2) \secondansweriii

 The correct answer is \correctansweriii

\end{document}

enter image description here

10
  • I said in previous comment, thise "seems" to work, and indeed in this simple exapmle it works, but if the value of \myitem[1] is a complicated formula, then it does not work. For instance, try to use as text of first exercise something like $f:X\to Y =\begin{pmatrix} a & b\\ c&d\end{pmatrix}. In this cases the definition of \texti does not work!
    – user126154
    Apr 30, 2022 at 16:32
  • @user126154 I cannot recreate your problem. For example, if I use \begin{Exer} My First exercise is, what is the answer to $y=mx+b$? \item First answer \item* Second answer \end{Exer}, everything still works. Apr 30, 2022 at 16:36
  • it must be more complicated, try \begin{Exer} My firs exercise is, let $f:X\toY, f=\begin{pmatrix}a & b\\ c&d\end{pmatrix}$ \item ok \item* right \end{Exer}
    – user126154
    Apr 30, 2022 at 16:40
  • by the way, thanks for the \tmp issue. I reproduced my error in a very simple way, for instance \def\foo{let $f:X\toY, f=\begin{pmatrix}a & b\\ c&d\end{pmatrix}$} \xdef\macro{\foo} gives me errors, if I put \noexpand\foo get no errors, but I need expanded!
    – user126154
    Apr 30, 2022 at 16:43
  • 1
    @user126154 I have revised my answer to deal properly with the expansion, performing a twice-expansion instead of an \xdef. Apr 30, 2022 at 17:10
2

Here's an expl3 version. First the exercise texts are stored in a property list. I believe that using names for the exercises is better than numbering them, because it would be difficult to keep a count when there are many of them. It would be easy to use numbers, though.

\documentclass{amsart}
\usepackage[inline]{enumitem}

\theoremstyle{definition}
\newtheorem{exercise}{Exercise}

\ExplSyntaxOn

\NewDocumentEnvironment{Exer}{m +b}
 {
  \exer_store:nn { #1 } { #2 }
 }
 {}

\NewDocumentCommand{\printexercise}{m}
 {
  \exer_print:n { #1 }
 }

\prop_new:N \g_exer_texts_prop
\seq_new:N \l__exer_body_seq
\tl_new:N \l__exer_full_tl
\tl_new:N \l__exer_text_tl


\cs_generate_variant:Nn \seq_set_split:Nnn { Nnv }

\cs_new_protected:Nn \exer_store:nn
 {
  \prop_gput:Nnn \g_exer_texts_prop { #1 } { #2 }
 }

\cs_new_protected:Nn \exer_print:n
 {
  % recover the text
  \prop_get:NnN \g_exer_texts_prop { #1 } \l__exer_full_tl
  % split the text at \item
  \seq_set_split:NnV \l__exer_body_seq { \item } \l__exer_full_tl
  % detach the exercise text
  \seq_pop_left:NN \l__exer_body_seq \l__exer_text_tl
  % print the answers, we need to remove a leading *
  \begin{exercise}
  \tl_use:N \l__exer_text_tl \par
  \begin{enumerate*}
  \cs_set_eq:NN \par \prg_do_nothing:
  \seq_map_inline:Nn \l__exer_body_seq
   {
    \item \peek_charcode_remove:NT * { \ignorespaces } ##1
   }
  \end{enumerate*}
  \end{exercise}
  % print the correct answer
  The~correct~answer~is~
  \seq_map_indexed_inline:Nn \l__exer_body_seq
   {
    \peek_charcode_remove:NTF * { \__exer_number:nw { ##1 } } { \__exer_gobble:w } ##2 \q_stop
   }
 }
\cs_new:Npn \__exer_number:nw #1 #2 \q_stop { #1 }
\cs_new:Npn \__exer_gobble:w #1 \q_stop { }

\ExplSyntaxOff

\begin{document}

\begin{Exer}{functions}
FIRST exercise -- let $F:X\to Y$ be as $f:X\to Y f=\begin{pmatrix} a&b\\ c&d\end{pmatrix}$ 
\item First answer
\item* Second answer
\end{Exer}

\begin{Exer}{whatever}
SECOND exercise
\item* A
\item B
\end{Exer}

\printexercise{functions}

\printexercise{whatever}

\end{document}

When \printexercise is called, the text is recovered from the property list and split at \item. The first part is the exercise text and is detached from the sequence. Next we can use the remaining part of the sequence to also print the numbers with enumerate*.

Next the sequence is processed to print the correct answer number.

The tricky part is to remove the * that marks the correct answer, which is done through \peek_charcode_remove:NTF; in the first case the asterisk is simply removed, in the second case we gobble the text and print the index number when the asterisk is present.

Note that we never rely on full expansion, so everything is safe.

enter image description here

Prompted by a comment, I'd suggest not to overload \item for this purpose and to replace it with \answer. The code remains the same, except for replacing \item with \answer in its first occurrence in the code.

5
  • Is the code you posted correct? I try to compile but it seems that \NewDocumentEnvironment is undefined control sequence. I added \usepackage{expl3} but still get errors
    – user126154
    May 11, 2022 at 20:22
  • @user126154 Add \usepackage{xparse}, for the time being, but update your TeX system.
    – egreg
    May 11, 2022 at 21:01
  • it seems that expl3 seems very powerfull, where I can start learning?
    – user126154
    May 11, 2022 at 21:41
  • your code works also if the text (or some answer) of an exercise contains some itemize environment?
    – user126154
    May 11, 2022 at 21:44
  • @user126154 No, it doesn't. For that case it would be better to replace \item in the main part with some other command (and also in the code, of course). I added a suggestion; in your case you probably want also to replace enumerate* with enumerate.
    – egreg
    May 11, 2022 at 22:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .