从句子中逐个剪切最后一个单词

从句子中逐个剪切最后一个单词

我想对某个空格分隔的列表执行特定命令作为参数。然后对同一列表执行相同的命令(不包括最后一个元素),直到列表为空。作为附加点,将剪切出的元素存储到单独的列表中。

我目前拥有的:

\documentclass{standalone}

\makeatletter

\def\split#1{
  \typeout{split:#1}
  \def\init{}
  \def\last{}
  \@split#1 \@empty
}

\def\@split#1 #2{%
  \typeout{1:#1}
  \typeout{2:#2}
  \ifx #2\@empty
    \def\last{#1}
  \else
    \ifx\init\@empty
      \expandafter\expandafter\expandafter\def
      \expandafter\expandafter\expandafter\init
      \expandafter\expandafter\expandafter{\expandafter\init#1}
    \else
      \expandafter\expandafter\expandafter\def
      \expandafter\expandafter\expandafter\init
      \expandafter\expandafter\expandafter{\expandafter\init\space#1}
    \fi
    \expandafter\@split
  \fi
  #2%
}

\def\writer#1{%
  \typeout{writer:#1}
  \split{#1}
  \typeout{writer-init:\init}
  \typeout{writer-last:\last}
  \ifx \init\@empty\else\writer{\init}\fi
}

\makeatother

\begin{document}

\writer{Takes several words as argument}

\end{document}

问题是第二次迭代时@split接收空参数。我使用\typeout{}命令查看每个命令接收的参数。控制台输出如下:

writer:Takes several words as argument
split:Takes several words as argument
1:Takes
2:s
1:several
2:w
1:words
2:a
1:as
2:a
1:argument
2:
writer-init:Takes several words as
writer-last:argument
writer:Takes several words as
split:Takes several words as
1:
2:
writer-init:
writer-last:

如您所见,\split它本身接收了正确的数据,但\@split命令无法解析参数。我确信我在将数据传递给时犯了错误\@split,但不知道如何修复它。

我非常感谢任何建议。

答案1

您漏掉了几个\expandafter

\ifx\init\@empty\else\expandafter\writer\expandafter{\init}\fi

如果我更改相应的行,我会在终端上得到以下输出:

writer:Takes several words as argument
split:Takes several words as argument
1:Takes
2:s
1:several
2:w
1:words
2:a
1:as
2:a
1:argument
2:
writer-init:Takes several words as
writer-last:argument
writer:Takes several words as
split:Takes several words as
1:Takes
2:s
1:several
2:w
1:words
2:a
1:as
2:
writer-init:Takes several words
writer-last:as
writer:Takes several words
split:Takes several words
1:Takes
2:s
1:several
2:w
1:words
2:
writer-init:Takes several
writer-last:words
writer:Takes several
split:Takes several
1:Takes
2:s
1:several
2:
writer-init:Takes
writer-last:several
writer:Takes
split:Takes
1:Takes
2:
writer-init:
writer-last:Takes

这里有一个可能更好的实现,它利用标记寄存器而不是那些复杂的\expandafter's 链,或者,作为替代方案,\unexpanded它来自 e-TeX。请注意,所有最近的 TeX 发行版在调用 LaTeX 时都使用 e-TeX。

\documentclass{article}

\makeatletter

\def\split#1{%
  \typeout{split:#1}%
  \def\init{}%
  \def\last{}%
  \@split#1 \@empty
}

\def\@split#1 #2{%
  \typeout{1:#1}%
  \typeout{2:\noexpand#2}% \noexpand for showing also `\@empty`
  \ifx #2\@empty
    \def\last{#1}%
  \else
    \ifx\init\@empty
      \def\init{#1}%
    \else
      % with e-TeX use the following line
      \edef\init{\unexpanded\expandafter{\init}\space\unexpanded{#1}}%
      % without e-TeX use the following two lines
      %\toks@=\expandafter\expandafter\expandafter{\expandafter\init\space#1}%
      %\edef\init{\the\toks@}%
    \fi
    \expandafter\@split
  \fi
  #2%
}

\def\writer#1{%
  \typeout{writer:#1}%
  \split{#1}%
  \typeout{writer-init:\init}%
  \typeout{writer-last:\last}%
  \ifx\init\@empty\else\expandafter\writer\expandafter{\init}\fi
}

\makeatother

\begin{document}

\writer{Takes several words as argument}

\end{document}

这是您想要的 LaTeX3 实现。请注意,这相当简单,还可以避免混淆\split\writer

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\bronislavsplit}{m}
 {
  \bronislav_split:n { #1 }
 }

\seq_new:N \l_bronislav_input_seq
\tl_new:N \l_bronislav_last_tl

\cs_new_protected:Npn \bronislav_split:n #1
 {
  % split the input at spaces
  \seq_set_split:Nnn \l_bronislav_input_seq { ~ } { #1 }
  % start the recursion
  \__bronislav_iterate:
 }

\cs_new_protected:Npn \__bronislav_iterate:
 {
  \seq_if_empty:NF \l_bronislav_input_seq
   {% this is executed only if the sequence is not empty
    % for debugging, show the what's in the sequence
    \typeout{writer: ~ \seq_use:Nn \l_bronislav_input_seq { ~ }}
    % split off the last element
    \seq_pop_right:NN \l_bronislav_input_seq \l_bronislav_last_tl
    \seq_map_inline:Nn \l_bronislav_input_seq
     {
      % do something with each item
      \bronislav_do_item:n { ##1 }
     }
    % do something with the last item
    \bronislav_do_last_item:V \l_bronislav_last_tl
    % keep on the recursion
    \__bronislav_iterate:
   }
 }

\cs_new_protected:Npn \bronislav_do_item:n #1
 {
  \typeout { item: ~ #1 }
 }
\cs_new_protected:Npn \bronislav_do_last_item:n #1
 {
  \typeout { last ~ item: ~ #1 }
 }
% generate a variant because a variable is passed
\cs_generate_variant:Nn \bronislav_do_last_item:n { V }
\ExplSyntaxOff

\begin{document}

\bronislavsplit{Takes several words as argument}

\end{document}

这是终端上的输出:

writer: Takes several words as argument
item: Takes
item: several
item: words
item: as
last item: argument
writer: Takes several words as
item: Takes
item: several
item: words
last item: as
writer: Takes several words
item: Takes
item: several
last item: words
writer: Takes several
item: Takes
last item: several
writer: Takes
last item: Takes

当然,您希望使用最后两个命令做一些更有用的事情。

相关内容