我需要扩展 listofitems 包中定义的宏

我需要扩展 listofitems 包中定义的宏

我正在建立一个练习数据库。练习以以下形式存储

\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

相关内容