我没有从我的 LaTeX 代码中获得我想要的输出,我认为问题在于我没有在正确的时间扩展事物。
代码
\documentclass{article}
\usepackage{tikz}
\usepackage{etextools}
\makeatletter
% The result of calling
% \@defineLine{foo}{35}
% is that the command \wickerson@foo@pos gets defined as 35.
\newcommand*\@defineLine[2]{%
\typeout{Defining wickerson@#1@pos = #2}
\expandafter\xdef\csname wickerson@##1@pos\endcsname{#2}
}
% Project the pos-field from the line with the given
% identifier. For instance, if \wickerson@foo@pos=35 then
% \@getLinePos{foo}
% will give 35.
\xdef\@getLinePos#1{\csname wickerson@#1@pos\endcsname}
% A comma-separated list of "active" lines. Each element is
% a line identifier.
\newcommand*\@activeLines{}
% Add the given identifier to the list of active lines.
\newcommand*\@addToActiveLines[1]{%
\ifx\@activeLines\@empty\else\g@addto@macro\@activeLines{,}\fi
\g@addto@macro\@activeLines{#1}
}
% Remove the given identifier from the list of active lines.
\newcommand*\@removeFromActiveLines[1]{%
\@expandtwoargs\@removeelement{#1}\@activeLines\@activeLines
}
% Print each active line identifier, associated with its
% corresponding pos field. Typical output:
% {a ↦ 10, b ↦ 30, c ↦ 50, d ↦ 70,}
\newcommand*\@printState{%
Current state: $\{
\foreach\i in \@activeLines {%
\i \mapsto \@getLinePos\i,
}
\}$ \par
}
\begin{document}
\foreach \x/\xvalue in {a/10, b/30, c/50, d/70} {
\ExpandNextTwo\@defineLine{\x}{\xvalue}
\ExpandNext\@addToActiveLines{\x}
\@printState
}
\foreach \x in {a, c} {
\ExpandNext\@removeFromActiveLines{\x}
\@printState
}
\end{document}
解释
第一个 foreach 循环遍历一串对,构建一个从标识符(例如 、 等)到数字的函数a
。b
此函数的值存储在一系列宏中(例如,\wickerson@a@pos
存储标识符的函数值a
),此函数的域是逗号分隔的列表\@activeLines
。最初\@activeLines
为空,最后为a,b,c,d
。
我在每次循环后打印我的函数。最后我希望打印
{a↦10,b↦30,c↦50,d↦70}
但它打印的是
{a↦70,b↦70,c↦70,d↦70}
我怀疑问题在于当我向函数添加映射时,我没有充分扩展值,因此当我添加另一个映射时它会发生变化。我以为使用ExpandNextTwo
frometextools
可以很好地扩展所有内容,但似乎没有帮助。有什么想法吗?
第二个 foreach 循环尝试在命令的帮助下从我的函数中删除一些映射\@removeelement
。不幸的是,它不起作用。映射仅在当前循环迭代的范围内被删除,并且在下一次迭代时,映射已返回。我怀疑问题是\@removeelement
没有\@activeLines
全局重新定义,但我不知道如何解决这个问题。有什么想法吗?(我希望我不需要切换到语法expl3
来实现这一点,因为这将需要对我的代码进行相当大的更改!)
电流输出
期望输出
答案1
以下两个宏需要修复:
\newcommand*\@defineLine[2]{%
\typeout{Defining wickerson@#1@pos = #2}
\expandafter\xdef\csname wickerson@##1@pos\endcsname{#2}
}
这将定义为\@defineLine{a}{10}
宏\wickerson@#1@pos
而不是\wickerson@a@pos
。修复:
\newcommand*\@defineLine[2]{%
\typeout{Defining wickerson@#1@pos = #2}
\expandafter\xdef\csname wickerson@#1@pos\endcsname{#2}
}
下一个宏使用它:
% Project the pos-field from the line with the given
% identifier. For instance, if \wickerson@foo@pos=35 then
% \@getLinePos{foo}
% will give 35.
\xdef\@getLinePos#1{\csname wickerson@#1@pos\endcsname}
由于\xdef
扩展,因此使用宏名称的一部分而不是参数\csname
来执行。修复:\wickerson@#1@pos
#1
\gdef\@getLinePos#1{\csname wickerson@#1@pos\endcsname}
删除最后一个逗号
的一个特性\foreach
可以用来删除输出中最新的逗号:
\newcommand*\@printState{%
Current state: $\{
\foreach\i [count=\ii] in \@activeLines {%
\ifnum\ii>1 ,\fi
\i \mapsto \@getLinePos\i
}
\}$ \par
}
移除
该问题使用以下定义来删除元素:
\newcommand*\@removeFromActiveLines[1]{% %
\@expandtwoargs\@removeelement{#1}\@activeLines\@activeLines %
} %
确实,LaTeX 的效果\@removeelement
仅限于当前组。如果你想进行全局更改\@activeLines
,则\global\let
有帮助:
\newcommand*\@removeFromActiveLines[1]{% %
\@expandtwoargs\@removeelement{#1}\@activeLines\@activeLines %
\global\let\@activeLines=\@activeLines
} %
答案2
我相信它expl3
更直接,因为它不依赖于了解需要扩展什么以及何时扩展。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\seq_new:N \l_wickerson_list_seq
\NewDocumentCommand{\printState}{>{\SplitList{,}}m}
{
\seq_clear:N \l_wickerson_list_seq
\ProcessList{#1}{\splitatslash}
Current~state\tl_to_str:n { : } ~ %
$\seq_use:Nnnn \l_wickerson_list_seq { , } { , } { , }$
}
\NewDocumentCommand{\splitatslash}{>{\SplitArgument{1}{/}}m}
{
\wickerson_split_element:nn #1
}
\cs_new_protected:Npn \wickerson_split_element:nn #1 #2
{
\seq_put_right:Nn \l_wickerson_list_seq { #1 \mapsto #2 }
}
\ExplSyntaxOff
\begin{document}
\printState{a/10}
\printState{a/10, b/30}
\printState{a/10, b/30, c/50}
\printState{a/10, b/30, c/50, d/70}
\end{document}
一种不同的实现,可以删除状态;我既使用了“斜线前”部分的序列,也使用了包含值的属性列表(“斜线后”)。
该命令\defineStates
重新初始化变量,同时\addStates
将新状态放入变量中;同样,\removeStates
删除状态。最后,\printStates
仅给出状态的可视化表示。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
%%% variables
\seq_new:N \g_wickerson_list_seq
\prop_new:N \g_wickerson_states_prop
%%% document commands
\NewDocumentCommand{\defineStates}{m}
{
\wickerson_definestates:n { #1 }
}
\NewDocumentCommand{\addStates}{m}
{
\wickerson_addstates:n { #1 }
}
\NewDocumentCommand{\removeStates}{m}
{
\clist_map_inline:nn { #1 }
{
\seq_gremove_all:Nn \g_wickerson_list_seq { ##1 }
% The following also removes the state from the property list
% You can choose whether keep it or comment it out
\prop_gremove:Nn \g_wickerson_states_prop { ##1 }
}
}
\NewDocumentCommand{\printStates}{}
{
Current~state\tl_to_str:n { : } ~ %
$
\seq_map_inline:Nn \g_wickerson_list_seq
{ ##1 \mapsto \prop_get:Nn \g_wickerson_states_prop { ##1 }, }
$
}
%%% internal commands
\cs_new_protected:Npn \wickerson_addstates:n #1
{
\clist_map_inline:nn { #1 }
{
\wickerson_split_element:ww ##1 \q_stop
}
}
\cs_new_protected:Npn \wickerson_definestates:n #1
{
\seq_gclear:N \g_wickerson_list_seq
\prop_gclear:N \g_wickerson_states_prop
\wickerson_addstates:n { #1 }
}
\cs_new_protected:Npn \wickerson_split_element:ww #1 / #2 \q_stop
{
\seq_gput_right:Nn \g_wickerson_list_seq { #1 }
\prop_gput:Nnn \g_wickerson_states_prop { #1 } { #2}
}
\ExplSyntaxOff
\begin{document}
%% Initialize
\defineStates{a/10,b/30}\printStates
%% Add new states
\addStates{c/50}\printStates
\addStates{d/70,e/90}\printStates
%% remove some states
\removeStates{a,c}\printStates
\end{document}