我正在尝试编写宏以在数学模式下快速格式化矩阵。这是一个简单的工作示例:
\documentclass{article}
\usepackage{amsmath}
\newcommand{\pmat}[1]{\begin{pmatrix}#1\end{pmatrix}}
\begin{document}
\begin{displaymath}
M = \pmat{1&0\\0&1}
\end{displaymath}
\end{document}
这也适用于之内一个对齐的环境(table
、align
环境、cases
环境等):
\documentclass{article}
\usepackage{amsmath}
\newcommand{\pmat}[1]{\begin{pmatrix}#1\end{pmatrix}}
\begin{document}
\begin{align}
M &= \pmat{1&0\\0&1}
\end{align}
\end{document}
通常,在align
环境中&
,符号告诉 LaTeX 开始一个新列,但由于矩阵值包含在{}
组标记之间,因此 LaTeX 不会尝试解析1&0\\0&1
参数,因此一切都按预期进行。
让我们创建一个不使用组标记{}
来表示宏参数的新宏。例如,使用方括号对参数进行分组是相当常见的,如以下工作示例所示(试试看!):
\documentclass{article}
\usepackage{amsmath}
\newcommand{\bmat}[1][]{\begin{bmatrix}#1\end{bmatrix}}
\begin{document}
\begin{displaymath}
M = \bmat[1&0\\0&1]
\end{displaymath}
\end{document}
到目前为止一切顺利。问题来了!如果我尝试使用我的新宏\bmat
之内另一个对齐的环境:
% DOES NOT WORK!!!
\documentclass{article}
\usepackage{amsmath}
\newcommand{\bmat}[1][]{\begin{bmatrix}#1\end{bmatrix}}
\begin{document}
\begin{align}
M &= \bmat[1&0\\0&1]
\end{align}
\end{document}
我收到一个错误,因为 LaTeX 尝试将&
矩阵参数内的参数解释为外部align
环境的一部分。
\pmat{}
LaTeX 处理第一个宏和第二个宏的方式之间的区别\bmat[]
在于,组标记{}
会阻止 LaTeX 解析花括号中包含的参数,直到\pmat
调用宏之后。另一方面,当 LaTeX 看到\bmat[arg]
宏时,参数没有被组标记包围,因此 LaTeX 会尝试解析它前被 吃掉\bmat
。额外的&
字符会破坏环境的语法align
,因此 LaTeX 会崩溃。至少,这是我对发生的事情的理解,但我可能错了!
因此,问题是是否有可能强制 LaTeX 以与案例中[]
相同的方式处理案例中的参数{}
(而不会引起其他问题)。换句话说,我希望能够编写一个\bmat[1&0\\0&1]
在对齐环境中工作但保留方括号语法的宏 a-la。
结论:
[]
感谢 egreg(需要xparse
并且必须包含在单独的包文件中或序言中的\makeatletter
和之间\makeatother
),这是具有工作语法的最终宏:
\DeclareDocumentCommand\mat{}{{\ifnum\z@=`}\fi\@mat}
\DeclareDocumentCommand\@mat{ g o d() d|| }
{
\IfNoValueTF{#1}
{
\IfNoValueTF{#2}
{
\IfNoValueTF{#3}
{
\IfNoValueTF{#4}
{()}
{\begin{vmatrix}#4\end{vmatrix}}
}
{
\begin{pmatrix}#3\end{pmatrix}
\IfNoValueTF{#4}{}{|#4|}
}
}
{
\begin{bmatrix}#2\end{bmatrix}
\IfNoValueTF{#3}{}{(#3)}
\IfNoValueTF{#4}{}{|#4|}
}
}
{
\begin{matrix}#1\end{matrix}
\IfNoValueTF{#2}{}{[#2]}
\IfNoValueTF{#3}{}{(#3)}
\IfNoValueTF{#4}{}{|#4|}
}
\ifnum\z@=`{\fi}
}
{}
此宏的工作方式类似于具有不同参数分隔符、[]
、()
或 的重载函数||
。可能有很多原因可以解释为什么这是一个糟糕的想法 --- 所以请在下面发表您的评论。我希望听听那些比我更了解的人的意见。
答案1
你可以做到,但是我强烈建议您不要这样做。使用[
和]
作为分隔符对强制的争论。
\documentclass{article}
\usepackage{amsmath}
\makeatletter
\newcommand\bmat{{\ifnum\z@=`}\fi\@bmat}
\def\@bmat[#1]{\begin{bmatrix}#1\end{bmatrix}\ifnum\z@=`{\fi}}
\makeatother
\begin{document}
$\bmat[1&0\\0&1]$
\begin{align}
M &= \bmat[1&0\\0&1]
\end{align}
\end{document}
(注意:我减小了文本宽度以缩短输出。)
\eegroup
当你理解了这个技巧,你就可以使用它了。:)提示:在 TeXbook 的附录 D 中查找。
我认为,定义\mat
查找下一个标记来决定分隔符是错误的。我会这样做:
\newcommand{\mat}[2][]{\begin{#1matrix}#2\end{#1matrix}}
以便
\mat{1&2\\3&4} % no fences
\mat[b]{1&2\\3&4} % brackets
\mat[p]{1&2\\3&4} % parentheses
\mat[v]{1&2\\3&4} % vertical lines
\mat[V]{1&2\\3&4} % double vertical lines
\mat[B]{1&2\\3&4} % braces
会做得更好,麻烦更少,可读性更好。
就我个人而言,我会坚持使用长格式,但这只是个人喜好问题。
答案2
为什么会发生这种情况相当有趣,因为原始代码
\begin{align}
M = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}
\end{align}
即使数组分隔符不在括号之间,它也能正常工作。原因是它bmatrix
本身专门插入括号来实现这一点,当扫描到达分隔符时,TeX 已经读取了左括号并知道它应该保护它们。
在您的代码中,似乎\bmat
在读取之前,将替换为其扩展,即上面的代码&
,但实际上,&
此时已经读取了:它需要由宏参数抓取器拾取。也就是说,即使没有&
“执行”,它也是看到,显然,即使在进行标记化时,TeX 也会检测到数组单元的结尾。
因此,无法使用常规\newcommand\bmat[1][]{}
语法来定义“只在数组中有效”的命令;egreg 的回答展示了如何自己编写一个接受相同语法但偷偷插入括号的\catcode
命令。在扫描参数之前,可能还可以进行更改,使其变成[]
功能性括号字符,但尽管如此,我建议您重新设计宏,不要使用可选参数。作为一种折衷方案,您可以{\bmat[1&0\\0&1]}
像 Scott H. 建议的那样,在它周围放置括号:。
答案3
在 TeX 中,扫描内部数据\halign
非常特殊。请注意,这是LaTeX 环境的\halign
原始实现。bmatrix
align
当\halign
在 内时\halign
,所有操作均可行,但宏扩展必须小心谨慎。@Ryan 的回答中的示例
\begin{align}
M &= \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}
\end{align}
在 TeX 原始级别上(粗略地说)执行以下操作:
\halign\bgroup #&#\cr
M &= \vbox\bgroup\halign\bgroup &#\cr 1&0\cr 0&1\cr\egroup\egroup
\cr\egroup
实现\begin{bmatrix}
为一个宏,它扩展为
\vbox\bgroup\halign\bgroup &#\cr
(不完全是,但这个宏的更多操作对于我们的情况并不重要)并\end{bmatrix}
扩展为
\cr\egroup\egroup
。重要的是,宏\begin{bmatrix}
不会读取由 分隔的参数\end{matrix}
,因此这个 LaTeX 环境的两个部分“独立”工作。数据 在主 TeX 处理器级别作为内部 数据1&0\cr 0&1
读取。\halign
\halign
当带有参数的宏被读取到外部数据时,情况就完全不同了\halign
。扫描宏参数的主要规则是:
仅当处理在...对内时&
, (或\cr
)才被解释为&
(或)\cr
{
}
,即在 catcode 1 和 2 的字符对内。并且,无论这对字符是否在宏之外( 例如在排版输出中\bgroup
... \egroup
),或者这对字符是否用于宏参数周围作为读取宏参数的语法规则,都是无关紧要的。这意味着这两个示例都有效:
\def\hh[#1]{\vbox\bgroup\halign\bgroup &##\cr #1\cr\egroup\egroup}
\halign\bgroup #&#\cr
M & = {\hh[1&0\cr 0&1]}
\cr\egroup
或者
\def\hh#1{\vbox\bgroup\halign\bgroup &##\cr #1\cr\egroup\egroup}
\halign\bgroup #&#\cr
M & = \hh{1&0\cr 0&1}
\cr\egroup
另一方面,当不满足上述规则时,或&
永远
\cr
不会在参数中“按原样”扫描:它立即被替换为单元格声明的第二部分,\endtemplate
在扫描参数时禁止使用。这意味着此示例不起作用:
\def\hh[#1]{\vbox\bgroup\halign\bgroup &##\cr #1\cr\egroup\egroup}
\halign\bgroup #&#\cr
M & = \hh[1&0\cr 0&1]
\cr\egroup
此外,这还不是全部。所有宏都会在每个数据单元的开头展开,直到找到不可扩展且无空格的标记。此时展开宏时,&
将“按原样”进行扫描。因此,它可以工作:
\def\h[#1]{\vbox\bgroup\halign\bgroup &##\cr #1\cr\egroup\egroup}
\halign\bgroup #&#\cr
M & \expandafter=\h[1&0\cr 0&1]
\cr\egroup