我正在建立一个练习数据库。练习以以下形式存储
\begin{Exer}
Text of the exercice
\item first answer
\item* second answer
\end{Exer}
其中 * 表示正确答案
由此我需要定义宏 \text、\firstanswer、\secondanswer、\correctanswer。为此我找到了 environ 和 listofitems 包,感谢这个答案。但是现在,我需要用计数器来完成此操作,即我需要根据 N 将事物定义为“\textN”。
我认为我的问题是一个扩展问题,事实上我的宏 \correctanswerN 可以工作,而同样定义的 \textN 却给我错误。我猜事实是 \correctanswer 基本上是一个数字,而 \text 是一个宏。(如果我使用 gdef 而不是 edef,我不会得到任何错误,但输出是错误的,因为东西没有“扩展”,所以连续调用 Exer 会删除我的数据)
我尝试放置一些 \expandafter\noexpand(正如我在 listofitem 文档中读到的“项目需要双重扩展”),但没有成功。
无论如何,这是我的尝试(你可以直接转到定义 \text\roman{\num} 的行)
\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}
答案1
我认为定义的尝试之所以\tmp
发生是因为嵌套可选参数没有成功。
如果可选参数的整个内容都嵌套在花括号中,则可以嵌套可选参数,即您不能执行\foo[\bar[baz]]
,但可以执行\foo[{\bar[baz]}]
或\foo[{\bar[{baz}]}]
。处理可选参数时,围绕可选参数整个内容的括号会被剥离,但在抓取可选参数时,它们的作用是隐藏]
属于嵌套可选参数的嵌套。
除此之外,您不能\edef
在内部进行赋值(如)\xdef
。\xdef
扩展可扩展标记并从结果标记集中定义一个宏。但\xdef
不会执行像这样的不可扩展标记\edef
,因此
\expandafter\xdef\csname firstanswer\roman{num}\endcsname{\edef\tmp{\listlen\myitem[2]}
您会得到类似的东西
\xdef\firstansweri{\edef\tmp{\listlen\myitem[2]}
。
现在\xdef
触发定义文本的可扩展标记的扩展:标记\edef
不可扩展。它留在原处。\tmp
如果已定义,则会扩展标记。如果未定义,您会收到一条错误消息。{
不可扩展且留在原处。序列\listlen\myitem[2]
得到扩展。}
不可扩展且留在原处。
因此你的\xdef
就像。 我想这不是你想要的。
\def\firstansweri{\edef⟨full expansion of \tmp⟩{⟨full expansion of \listlen\myitem[2]⟩}
更直接的方法可能是:
\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}
答案2
有两个主要问题。
一个是 带来的混淆\edef\tmp{\listlen\myitem[2]}\myitem[2,\tmp]
,因为\tmp
它本身包含一个右括号。事实证明,我们甚至不需要\tmp
;但是,我们必须将内部右括号隔离在其自己的组中,如\myitem[2,{\listlen\myitem[2]}]
。这样,右括号就不会混淆。
第二个问题是 的使用\xdef
。一些数学结构是不可扩展的,因此使用\xdef
会导致错误。为了避免这种情况,我(定义和)使用\gdefxx
而不是\xdef
。使用此构造,参数#2
会扩展两次,而不是完全扩展。由于#2
是列表项之一,因此两次扩展将恢复在列表定义中找到的原始标记。
\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}
答案3
这是一个expl3
版本。首先,练习文本存储在属性列表中。我认为使用名称来命名练习比编号更好,因为当练习数量很多时,很难进行计数。不过,使用数字会很容易。
\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}
当\printexercise
调用时,文本从属性列表中恢复并在处拆分\item
。第一部分是练习文本,与序列分离。接下来,我们可以使用序列的剩余部分来打印数字enumerate*
。
接下来处理该序列以打印正确的答案编号。
棘手的部分是删除*
标记正确答案的,这是通过完成的\peek_charcode_remove:NTF
;在第一种情况下,星号被简单地删除,在第二种情况下,我们吞噬文本并在出现星号时打印索引号。
请注意,我们从不依赖于完全扩展,因此一切都是安全的。
受评论提示,我建议不要\item
为此目的而重载,而应将其替换为\answer
。代码保持不变,只是在代码中第一次出现时\item
用替换。\answer