描述
我正在尝试定义一个宏,例如\CountToken
,分析选定的文本,计算给定标记在此文本中出现的次数,并将出现次数存储在计数器中。标记也可以采用 的形式\<some_macro_name>
,其中\<some_macro_name>
具有分隔符的功能。对于我的目的而言,只有那些未隐藏在组内的标记才是重要的。
假定的语法是
\CountToken[<selected_token>,\<counter_name>]
<text>
\finish
结果应该如下:
- 创建计数器
\<counter_name>
, - 计算以 为界的
<selected_token>
的出现次数,<text>
\finish
- 将发生的次数存储在 中
\<counter_name>
。
例子:
\CountToken[i,\cnti]
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur adipiscing elit.
\finish
这里,\the\cnti
给出6
。
自己的尝试(MWE)
这是我的最小工作示例。
\documentclass{article}
\def\finish{}
\newcount\tmp
\def\CountToken[#1,#2]{%
\def\TestedToken{#1}
\tmp=0
\newcount#2
\let\TotalOccurrence#2
\let\next\TestNext\next}
\long\def\TestNext#1{%
\def\CurrentToken{#1}%
\ifx#1\finish
\def\next{\TotalOccurrence=\the\tmp\relax}
\else
\ifx\CurrentToken\TestedToken
\advance\tmp by 1
\fi
\fi
\next}
\begin{document}
%-----------------------
\CountToken[i,\cnti]
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur adipiscing elit.
\finish
%-----------------------
number of i's: \the\cnti
%-----------------------
\CountToken[f,\cntf]
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur adipiscing elit.
\finish
%-----------------------
number of f's: \the\cntf
%-----------------------
\CountToken[\something,\cntsomething]
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur\something adipiscing elit.\something
\finish
%-----------------------
number of \verb|\something|'s: \the\cntsomething
\end{document}
一切似乎都运行良好。但是,将分数的值更改为会22/3
产生编译错误。通过实验,我发现如果分数的分子以两个相同的数字开头,就会发生这种情况。我知道,例如,写作\frac{{22}}3
有帮助,但这对我来说不是一种严肃的方式。
要求
我想知道几件事。
- 我怎样才能改进我的定义,
\CountToken
使一切运行正常?(请仅使用 LaTeX 解决方案。) - 您能描述一下上述错误的根源吗?
- 还有其他安全的方法来计算包含一般数学内容的文本片段中选定的标记吗?
答案1
问题不在于数学模式,而在于任何前两个标记相同的括号表达式。以“{22}”为例:
在某一时刻,你查看了之前的所有内容。然后的{22}
论点变成了,因此变成了。#1
\TestNext
22
\ifx#1\finish
\ifx22\finish
这里\ifx 22
是正确的,所以\finish
进行了扩展,导致了错误。
您可以通过比较\CurrentToken
而不是#1
直接使用来解决这个问题。您还需要一个存储终止符的辅助宏:
\documentclass{article}
\def\finish{}
\def\finishmarker{\finish}
\newcount\tmp
\def\CountToken[#1,#2]{%
\def\TestedToken{#1}%
\tmp=0
\newcount#2%
\let\TotalOccurrence#2%
\let\next\TestNext\next}
\long\def\TestNext#1{%
\def\CurrentToken{#1}%
\ifx\CurrentToken\finishmarker
\def\next{\TotalOccurrence=\the\tmp\relax}%
\else
\ifx\CurrentToken\TestedToken
\advance\tmp by 1
\fi
\fi
\next}
\begin{document}
%-----------------------
\CountToken[i,\cnti]
Lorem ipsum dolor $\frac{22}{3}$ sit amet,
consectetur adipiscing elit.
\finish
%-----------------------
number of i's: \the\cnti
%-----------------------
\CountToken[f,\cntf]
Lorem ipsum dolor $\frac{22}{3}$ sit amet,
consectetur adipiscing elit.
\finish
%-----------------------
number of f's: \the\cntf
%-----------------------
\CountToken[\something,\cntsomething]
Lorem ipsum dolor $\frac{22}{3}$ sit amet,
consectetur\something adipiscing elit.\something
\finish
%-----------------------
number of \verb|\something|'s: \the\cntsomething
\end{document}
代码的另一个问题是它只计算“外部”标记,这意味着“隐藏”在 {} 内部的标记将被忽略。
答案2
你这样做了\def\CurrentToken{#1}
,但随后却做了\ifx#1\finish
而不是\ifx\CurrentToken\finish
。等等!如果我们到了最后,这不会产生 true!
是的,如果你这样做的话\def\finish{\finish}
。当然,你必须小心,\finish
不要让它在某个地方膨胀。
计数器\tmp
是多余的:您有\TotalOccurrence
可用的,请使用它。
\documentclass{article}
\def\finish{\finish}
\def\CountToken[#1,#2]{%
\def\TestedToken{#1}%
\ifdefined#2#2=0 \else\newcount#2\fi
\let\TotalOccurrence#2%
\let\next\TestNext\next
}
\long\def\TestNext#1{%
\def\CurrentToken{#1}%
\ifx\CurrentToken\finish
\let\next\relax
\else
\ifx\CurrentToken\TestedToken
\advance\TotalOccurrence by 1
\fi
\fi
\next
}
\begin{document}
%-----------------------
\CountToken[i,\cnti]
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur adipiscing elit.
\finish
%-----------------------
number of i's: \the\cnti
%-----------------------
\CountToken[f,\cntf]
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur adipiscing elit.
\finish
%-----------------------
number of f's: \the\cntf
%-----------------------
\CountToken[\something,\cntsomething]
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur\something adipiscing elit.\something
\finish
%-----------------------
number of \verb|\something|'s: \the\cntsomething
\end{document}
为了完整起见,这里有一个expl3
版本:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\CountToken}{mm+m}
{% #1 = token to check, #2 = counter, #3 = text to count in
\int_zero_new:N #2
\tl_map_inline:nn { #3 }
{
\tl_if_eq:nnT { #1 } { ##1 } { \int_incr:N #2 }
}
}
\ExplSyntaxOff
\begin{document}
%-----------------------
\CountToken{i}{\cnti}{
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur adipiscing elit.
}
%-----------------------
number of i's: \the\cnti
%-----------------------
\CountToken{f}{\cntf}{
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur adipiscing elit.
}
%-----------------------
number of f's: \the\cntf
%-----------------------
\CountToken{\something}{\cntsomething}{
Lorem ipsum dolor $\frac{21}{3}$ sit amet,
consectetur\something adipiscing elit.\something
}
%-----------------------
number of \verb|\something|'s: \the\cntsomething
\end{document}
类似的问题已得到解决https://tex.stackexchange.com/a/525246/4427(也计算括号内的标记或标记集)。
让我们更详细地看一下代码。
宏\CountToken
定义为三个米andatory 参数,其中最后一个参数也可以包含\par
标记(因为+
前缀)。
该命令首先将计数器设置为零(参数#2
),如果尚不存在则分配一个新的(效果类似于\ifdefined#2#2=0 \else\newcount#2\fi
)。
接下来我们映射第三个参数:标记列表可以看作是项列表(但项之间的空格会被忽略),其中括号组算作单个项。在本例中,每个项都传递给第二个参数指定的代码
\tl_if_eq:nnT { #1 } { ##1 } { \int_incr:N #2 }
这里代表##1
映射中的当前项。T
电视rue:仅当前两个参数给出的两个标记列表相等时,才会执行第三个参数。
它本质上与上面的代码相同,但是构建循环和检查相等性的细节已经由标准函数提供。