随着\@gobble

随着\@gobble

我编写了一个宏来减去两个列表,它实现了预期的功能。然后,我在 TeX.SE 上找到了一个设置,其中包含宏\g@addto@macro\@gobble它更简单,并且原则上给出相同的输出。

在尝试使用第二种形式时,确实出现了一些问题。经过一番搜索,发现\@gobble尚未展开,在列表中添加了一个额外的元素并造成了麻烦。

这个问题的最佳解决办法是什么?

我认为最好对结果进行处理,\processedlist以便它可以用于任何需要列表的宏。然后,我们再次得到与第一个宏类似的结果,很多\expandafters,尽管这一次它们只会使用一次,因此应该会更快。

\documentclass{article}

\setlength{\parindent}{0pt}
\setlength{\parskip}{1.5ex plus 0.5ex minus 0.2ex}
\begin{document}
\makeatletter

\section*{First macro for subtracting two lists}

\newif\iffirstterm
\newif\ifisinlist

\def\subtractlists#1#2{% original list, remove list
\def\processedlist{}%
\firsttermtrue%
\@for\tempa:=#1\do{%
  \isinlistfalse%
  \@for\tempb:=#2\do{\ifx\tempa\tempb\isinlisttrue\fi}%
  \ifisinlist%
    \else%
      \iffirstterm%
          \expandafter\def\expandafter\processedlist\expandafter{\tempa}%  
          \firsttermfalse%
        \else%
          \expandafter\expandafter\expandafter\def%
          \expandafter\expandafter\expandafter\processedlist%
          \expandafter\expandafter\expandafter{%
          \expandafter\processedlist\expandafter,\tempa}%  
      \fi%
  \fi}%
}

%%% some lists for testing:
\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\def\removelist{Whisky,Oscar,Romeo,Delta}

%The list can be entered as comma separated items or as a macro:

\verb|\originallist|: \originallist\par
\verb|\removelist|: \removelist\par
\verb|\subtractlists{\originallist}{\removelist}|\par
\subtractlists{\originallist}{\removelist}
\verb|\processedlist|: \processedlist\par

%\@for\tempa:=\processedlist\do{(\tempa)}

\section*{Second macro for subtracting two lists}

\def\subtractlist#1#2{% #1:original list,#2:remove list
  \def\processedlist{\@gobble}%
  \@for\tempa:=#1\do{%
    \isinlistfalse%
    \@for\tempb:=#2\do{\ifx\tempa\tempb\isinlisttrue\fi}%
      \ifisinlist%
         \else%
           \expandafter\g@addto@macro%
           \expandafter\processedlist%
           \expandafter{\expandafter,\tempa}%      
       \fi}%
}


\verb|\originallist|: \originallist\par
\verb|\removelist|: \removelist\par
\verb|\subtractlist{\originallist}{\removelist}|\par
\subtractlist{\originallist}{\removelist}
\verb|\processedlist|: \processedlist\par

\section*{Problems with second approach}

There is some extra matter:

\@for\tempa:=\processedlist\do{(\tempa)}

the first comma has not been gobbled yet:

\verb|\meaning\processedlist|: \meaning\processedlist


\section*{What is the solution?}

This works:

\expandafter\@for\expandafter\tempa%
\expandafter:\expandafter=\processedlist\do{(\tempa)}

but maybe this is safer?:

\expandafter\@for\expandafter\tempa%
\expandafter:\expandafter=%
\expandafter{\processedlist}\do{(\tempa)}

However, the objective is to have \verb|\processedlist| clean of the extra
matter to use it as list in some other macro. Maybe:

\expandafter\expandafter\expandafter\def%
\expandafter\expandafter\expandafter\processedlist%
\expandafter\expandafter\expandafter{\processedlist}

\meaning\processedlist

which is more or less what we had in the first macro.

\end{document}

在此处输入图片描述

答案1

正如 Andrew 所说,expl3有很多高级工具可用于处理很多事情,包括列表处理。但expl3不管怎样,最重要的是了解你在做什么,并发布一个最低限度的工作例如当您遇到困难时(您的代码没有\end{document})。

因此,我将重点介绍您的“第二种方法”。您使用第一个项目初始化结果\@gobble,并期望它会神奇地消失?为什么会这样?事实并非如此,这就是\meaning向您展示的。这个想法可能是使用类似的东西来完成结果\xdef\processedlist{\processedlist},以便\@gobble吃掉后面的逗号,但这非常丑陋,因为\xdef(如\edef)在扩展来自真实项目(如\textbf\ref等)的标记时可能会导致严重的问题。\protected@xdef问题会少一些,但它仍然会毫无理由地扩展所有项目中的内容。

在这里,由于项目仅由非活动字符标记组成,因此这种方法是可行的,但这会破坏更精细的输入,产生不良的副作用,因此在我看来是一种不好的做法。我的建议是\@gobble根本不要添加该项目,而只是检查是否\processedlist为空,以决定是否在新添加的项目中添加逗号。

\@gobble话虽如此,我还是会在最后给出一种没有不良副作用的方法来处理。它比下面的代码要长一点,但应该会稍微快一点,因为用于将项目添加到的代码\processedlist少了一个检查。

无论使用什么技巧去掉第一个逗号,该算法都是相对低效的,因为即使内层循环已经找到了#1中的当前项#2,它仍会继续检查的所有剩余项#2。为了避免这个问题,它expl3使用了\prg_break_point:\prg_break:,更重要的是,支持这种循环中断的映射函数(例如\clist_map_inline:nn\clist_map_break:)。

\documentclass{article}
\usepackage[T1]{fontenc}        % for the '>' character in horizontal mode

\makeatletter
\newif\ifisinlist

\def\subtractlist#1#2{% #1:original list, #2:remove list
  \def\processedlist{}%
  \@for\tempa:=#1\do{%
    \isinlistfalse
    \@for\tempb:=#2\do{\ifx\tempa\tempb\isinlisttrue\fi}%
    \ifisinlist
    \else
      \ifx\processedlist\empty
        \expandafter\gdef\expandafter\processedlist\expandafter{\tempa}%
      \else
         \expandafter\g@addto@macro
         \expandafter\processedlist
         \expandafter{\expandafter,\tempa}%
      \fi
    \fi
  }%
}
\makeatother

\setlength{\parindent}{0pt}
\setlength{\parskip}{1.5ex plus 0.5ex minus 0.2ex}

\begin{document}

\section*{Second macro for subtracting two lists}

\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\def\removelist{Whisky,Oscar,Romeo,Delta}

\verb|\originallist|: \originallist\par
\verb|\removelist|: \removelist\par
\verb|\subtractlist{\originallist}{\removelist}|\par
\subtractlist{\originallist}{\removelist}
\verb|\processedlist|: \processedlist\par

\meaning\processedlist

\end{document}

在此处输入图片描述

随着\@gobble

正如承诺的那样,这里有一种使用初始值正确执行操作的方法\@gobble

\documentclass{article}
\usepackage[T1]{fontenc}        % for the '>' character in horizontal mode

\makeatletter
\newif\ifisinlist

\def\startingPoint{\@gobble}

\def\subtractlist#1#2{% #1:original list, #2:remove list
  \global\let\processedlist\startingPoint
  \@for\tempa:=#1\do{%
    \isinlistfalse
    \@for\tempb:=#2\do{\ifx\tempa\tempb\isinlisttrue\fi}%
    \ifisinlist
    \else
      \expandafter\g@addto@macro
      \expandafter\processedlist
      \expandafter{\expandafter,\tempa}%
    \fi
  }%
  \ifx\processedlist\startingPoint
    \gdef\processedlist{}%
  \else
    % Expand twice so that the \@gobble eats the comma that follows it.
    \xdef\processedlist{%
      \unexpanded\expandafter\expandafter\expandafter{\processedlist}}%
  \fi
}
\makeatother

\setlength{\parindent}{0pt}
\setlength{\parskip}{1.5ex plus 0.5ex minus 0.2ex}

\begin{document}

\section*{Second macro for subtracting two lists}

\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\def\removelist{Whisky,Oscar,Romeo,Delta}

\verb|\originallist|: \originallist\par
\verb|\removelist|: \removelist\par
\verb|\subtractlist{\originallist}{\removelist}|\par
\subtractlist{\originallist}{\removelist}
\verb|\processedlist|: \processedlist\par

\meaning\processedlist

\end{document}

与上面相同的输出。

注意:如果您不理解下面这段代码:

\xdef\processedlist{%
  \unexpanded\expandafter\expandafter\expandafter{\processedlist}}%

它的作用与以下相同:

\expandafter\expandafter
\expandafter\gdef
\expandafter\expandafter
\expandafter\processedlist
\expandafter\expandafter
\expandafter{\processedlist}%

PS:我发现简单的代码比用于完成非常简单的事情的expl3一堆代码更具可读性。\expandafter

答案2

这本质上是expl3开箱即用的。其思想是映射原始列表;如果某个项目不在删除列表中,则将其添加到临时列表中,最后将原始列表重置为临时列表。

我在名称列表中添加了一些语法糖,这样就可以拥有许多名称列表而无需定义新的宏。

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\definelist}{mm}
 {% #1 = list name, #2 = items
  % allocate a new list or clear an existing one
  \clist_clear_new:c { l_kessels_#1_clist }
  % set the list
  \clist_set:cn { l_kessels_#1_clist } { #2 }
 }

\NewDocumentCommand{\subtractlist}{mm}
 {% #1 = original, #2 = items to subtract
  \kessels_list_subtract:nn { #1 } { #2 }
 }

\NewDocumentCommand{\uselist}{mm}
 {% #1 = list name, #2 = output separator
  \clist_use:cn { l_kessels_#1_clist } { #2 }
 }

\clist_new:N \l__kessels_list_temp_clist

\cs_new_protected:Nn \kessels_list_subtract:nn
 {
  % clear the temporary list
  \clist_clear:N \l__kessels_list_temp_clist
  % map the original list
  \clist_map_inline:cn { l_kessels_#1_clist }
   {
    % if the item doesn't appear in the remove list, add it to the temp one
    \clist_if_in:cnF { l_kessels_#2_clist } { ##1 }
     {
      \clist_put_right:Nn \l__kessels_list_temp_clist { ##1 }
     }
   }
  % reconstitute the original list
  \clist_set_eq:cN { l_kessels_#1_clist } \l__kessels_list_temp_clist
 }

\ExplSyntaxOff

\begin{document}

\definelist{original}{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\definelist{remove}{Whisky,Oscar,Romeo,Delta}

\uselist{original}{, }

\subtractlist{original}{remove}

\uselist{original}{, }

\end{document}

在此处输入图片描述

人们可以考虑添加\copylist保存列表的副本并\maplist定义对项目的操作。

更完整的实现。的第二个参数\maplist是一个将每个项目作为的模板#1

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\definelist}{mm}
 {% #1 = list name, #2 = items
  % allocate a new list or clear an existing one
  \clist_clear_new:c { l_kessels_#1_clist }
  % set the list
  \clist_set:cn { l_kessels_#1_clist } { #2 }
 }
\NewDocumentCommand{\prependtolist}{mm}
 {
  \clist_put_left:cn { l_kessels_#1_clist } { #2 }
 }
\NewDocumentCommand{\appendtolist}{mm}
 {
  \clist_put_right:cn { l_kessels_#1_clist } { #2 }
 }

\NewDocumentCommand{\subtractlist}{mm}
 {% #1 = original, #2 = items to subtract
  \kessels_list_subtract:nn { #1 } { #2 }
 }

\NewDocumentCommand{\uselist}{mm}
 {% #1 = list name, #2 = output separator
  \clist_use:cn { l_kessels_#1_clist } { #2 }
 }

\NewDocumentCommand{\copylist}{mm}
 {
  \clist_clear_new:c { l_kessels_#2_clist }
  \clist_set_eq:cc { l_kessels_#2_clist } { l_kessels_#1_clist }
 }

\NewDocumentCommand{\maplist}{mm}
 {
  \cs_set:Nn \__kessels_list_map:n { #2 }
  \clist_map_function:cN { l_kessels_#1_clist } \__kessels_list_map:n
 }

\clist_new:N \l__kessels_list_temp_clist

\cs_new_protected:Nn \kessels_list_subtract:nn
 {
  % clear the temporary list
  \clist_clear:N \l__kessels_list_temp_clist
  % map the original list
  \clist_map_inline:cn { l_kessels_#1_clist }
   {
    % if the item doesn't appear in the remove list, add it to the temp one
    \clist_if_in:cnF { l_kessels_#2_clist } { ##1 }
     {
      \clist_put_right:Nn \l__kessels_list_temp_clist { ##1 }
     }
   }
  % reconstitute the original list
  \clist_set_eq:cN { l_kessels_#1_clist } \l__kessels_list_temp_clist
 }

\ExplSyntaxOff

\begin{document}

\definelist{original}{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\definelist{remove}{Whisky,Oscar,Romeo,Delta}

\uselist{original}{, }

\copylist{original}{changed}

\subtractlist{changed}{remove}

\uselist{changed}{, }

\maplist{changed}{\fbox{#1\vphantom{Ay}} }

\maplist{original}{\fbox{#1\vphantom{Ay}} }

\end{document}

在此处输入图片描述

答案3

另一种方法listofitems。本质上,我使用 remove-list 作为列表分隔符。因此,它们不会出现在摘要列表反刍中,因为它们作为列表分隔符被删除(连同多余的逗号)。

\documentclass{article}
\usepackage{listofitems}
\newcommand\subtractlists[2]{%
  \expandafter\setsepchar\expandafter{\expandafter,\expandafter/#2}%
  \readlist*\mylist{#1}%
  \foreachitem\z\in\mylist[]{%
    \ifnum\zcnt>1\relax\ifnum\listlen\mylist[\zcnt]=1\relax,\fi\fi
    \foreachitem\zz\in\mylist[\zcnt]{%
      \ifnum\zzcnt>0\relax\zz\fi}}%
}
\begin{document}
\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
List: \originallist

\def\removelist{Whisky||Oscar||Romeo||Delta}
Remove: \removelist

Result:
\subtractlists{\originallist}{\removelist}
\end{document}

在此处输入图片描述

如果坚持删除列表用逗号分隔(而不是||),则需要做一些额外的事情:

\documentclass{article}
\usepackage{listofitems}
\makeatletter
\newcommand\subtractlists[2]{%
  \setsepchar{,}%
  \readlist\remlist{#2}%
  \def\tmp{,/}%
  \foreachitem\z\in\remlist[]{\ifnum\zcnt=1\else\g@addto@macro\tmp{||}\fi
    \expandafter\g@addto@macro\expandafter\tmp\expandafter{\z}}%
  \expandafter\setsepchar\expandafter{\tmp}%
  \readlist*\mylist{#1}%
  \foreachitem\z\in\mylist[]{%
    \ifnum\zcnt>1\relax\ifnum\listlen\mylist[\zcnt]=1\relax,\fi\fi
    \foreachitem\zz\in\mylist[\zcnt]{%
      \ifnum\zzcnt>0\relax\zz\fi}}%
}
\makeatother
\begin{document}
\def\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
List: \originallist

\def\removelist{Whisky,Oscar,Romeo,Delta}
Remove: \removelist

Result:
\subtractlists{\originallist}{\removelist}
\end{document}

在此处输入图片描述

答案4

etoolbox还提供列表处理功能。下面我实现了一些与您的需求类似的功能:

在此处输入图片描述

\documentclass{article}

\usepackage{etoolbox}

\newcommand{\createlist}[1]{\forcsvlist{\listadd{#1}}}% Create a list
\newcommand{\subtractlists}[3][]{% Subtract two lists
  % Check for empty optional argument (https://tex.stackexchange.com/a/53091/5764)
  \if\relax\detokenize{#1}\relax
    % How each item is processed
    \renewcommand*{\do}[1]{\listremove{#2}{##1}}% Remove elements from original list
  \else
    \let#1#2% Copy original list
    % How each item is processed
    \renewcommand*{\do}[1]{\listremove{#1}{##1}}% Remove elements from optional list argument
  \fi
  \dolistloop{#3}% Process list
}
% https://tex.stackexchange.com/a/89187/5764
\newcommand{\printlist}[2][,\,]{% \printlist[<delim>]{<list cs>}
  \def\itemdelim{\def\itemdelim{#1}}% Item delimiter delayed by one cycle
  \renewcommand*{\do}[1]{\itemdelim##1}% How each item is processed
  \dolistloop{#2}}% Process list

\setlength{\parindent}{0pt}% Just for this example

\begin{document}

% Create some lists for testing
\createlist\originallist{Lima,Alpha,Delta,Oscar,Tango,Whisky,Echo,Romeo,Xray}
\createlist\removelist{Whisky,Oscar,Romeo,Delta}

\verb|\originallist|: \printlist\originallist \par
\verb|\removelist|: \printlist\removelist \par
\verb|\subtractlists{\originallist}{\removelist}|:
  \subtractlists[\processedlist]{\originallist}{\removelist}\printlist\processedlist \par
\verb|\processedlist|: \printlist\processedlist

\end{document}

相关内容