
Давний читатель, впервые публикую.
У меня есть вопрос о создании макроса, который перебирает список. Я прочитал несколько вопросов, опубликованных другими пользователями, но все они как-то связаны с использованием LaTeX сложным способом. Мой вопрос об использовании простого TeX для перебирания списка и создания нового списка путем изменения каждого элемента в старом списке. В настоящее время я самостоятельно изучаю TeX и думаю, что программирование некоторых базовых, но универсальных и надежных макросов поможет мне лучше понять, как работают внутренности TeX. В любом случае, достаточно предыстории, перейдем к моему вопросу.
Вот мой код на данный момент:
Я определил итератор рекурсивно. Он считывает один аргумент за раз, определяет, является ли аргумент запятой или маркером остановки (моего собственного определения, называемого \myStop), движется дальше, если это запятая, останавливается, если это маркер остановки, и в противном случае заменяет элемент самим собой и строкой (или списком токенов) "+ 1".
\edef\myStop{)}
\def\Iterator#1{
\ifx\myStop#1 %do nothing {end of list}
\else %
\ifx,#1 \expandafter\IteratorIntermediateOne % currently 2 if-statements deep
\else #1 + 1, \expandafter\IteratorIntermediateOne %currently 2 if-statements deep
\fi%
\fi%
}
Мне пришлось определить промежуточный итератор, вызываемый IteratorIntermediateOne
для размещения команды, \expandafter\Iterator
поскольку в настоящее время я не знаю способа группировать термины после \expandafter
команды таким образом, чтобы это было эквивалентно чему-то вроде \expandafter{\expandafter\Iterator}\fi\fi
. Так что, полагаю, это мой первый вопрос: есть ли способ определять вложенные \expandafter
команды?
Теперь, когда все в контексте, вот весь мой код:
\edef\MyList{1,2,3}
\edef\myStop{)}
\def\IteratorIntermediateOne{\expandafter\Iterator}
\def\Iterator#1{%
\ifx\myStop#1 %do nothing {end of list}
\else %
\ifx,#1 \expandafter\IteratorIntermediateOne % currently 2 if-statements deep
\else #1 + 1, \expandafter\IteratorIntermediateOne %currently 2 if-statements deep
\fi%
\fi%
}
\edef\newList{\expandafter\Iterator\MyList\myStop}
Отказ от ответственности: я знаю о дополнительной запятой, которая ставится после последнего элемента. Я также знаю, что недостаточно случаев для обнаружения плохо составленных списков. Я не знаю о TeX достаточно хорошо, чтобы даже начать представлять, как защитить макрос от неправильно составленных списков токенов, так что извините, если более опытные из вас не могут не смеяться над этим кодом.
Хорошо, мой другой вопрос таков: есть ли более эффективный способ определить макрос, который вызывает сам себя? Лучше ли определить макрос итератора отдельно и вызывать его с помощью встроенной \loop
команды TeX? Если да, может ли кто-нибудь объяснить мне, как это сделать, потому что у меня возникли проблемы с пониманием вызова цикла в контексте процессов TeX eye, mouth, gullet и stomach. Цикл расширяет макрос, достигает, \repeat
передает его в желудок и возвращает обратно в макрос? Я нигде не могу найти хорошего объяснения.
Спасибо за вашу помощь!
решение1
Кажется, намерение состоит в том, чтобы добавить 1 к каждому элементу. Я бы закодировал это примерно так (предполагая etex)
\edef\MyList{1,2,3,25,456,2}
\def\Iterator#1{\expandafter\xiterator#1\stopiteration,}
\def\xiterator#1,{\the\numexpr#1+1\relax,\xiterator}
\def\stopiteration#1\relax#2\xiterator{#1\relax}
\message{\Iterator\MyList}
\bye
что делает сообщение
2,3,4,26,457,3
решение2
\input listofitems
\def\MyList{1,2,3,25,456,2}
\def\processlist#1{%
\readlist\myterms\MyList
\foreachitem\z\in\myterms{%
\ifnum\zcnt=1\else,\fi
\the\numexpr\z+1\relax
}%
}
\processlist\Mylist
\bye
Если вам действительно нужносохранятьобновленный список, мы можем сделать это в \mytoks
списке токенов:
\input listofitems
\newtoks\mytoks
\def\MyList{1,2,3,25,456,2}
\def\processlist#1{%
\mytoks{}%
\readlist\myterms\MyList
\foreachitem\z\in\myterms{%
\ifnum\zcnt=1\else\mytoks\expandafter{\the\mytoks,}\fi
\mytoks\expandafter\expandafter\expandafter{%
\expandafter\the\expandafter\mytoks\the\numexpr\z+1\relax}
}%
}
\processlist\Mylist
List is \the\mytoks
\bye
решение3
Вы сказали:
...так что извините, если более опытные из вас не смогут удержаться от смеха, глядя на этот код.
Когда я начал изучать TeX, у меня было ощущение, что это очень крутая кривая обучения.
Время от времени я был расстроен. ;-)
Я не верю, что люди вроде вас, которые берутся за этот процесс обучения, находятся в ситуации, когда уместно смеяться над их попытками макропрограммирования/программирования в TeX.
Я думаю, что любая попытка достичь чего-то хорошего или научиться чему-то хорошему способом, который одновременно основан на разуме и сам по себе не является злодеянием, в принципе заслуживает уважения.
Если у вас есть вопросы о том, как работает код из моих примеров ниже, не стесняйтесь их задавать. Затем полезно описать, как, по вашему мнению, работает код и где вы застряли с пониманием. По моему опыту, это облегчает респондентам задачу выяснить, какой именно информации (например, о том, как работают примитивы TeX и какие из «побочных эффектов», на которые кратко намекают в последних главах TeXbook, используются для программных трюков) все еще не хватает для понимания.
Предполагая, что записи в вашем списке запятых не окружены пробелами, и что -primitive \relax
не встречается в списке запятых, и что \numexpr
ε-TeX-extensions доступен, вы, вероятно, можете сделать что-то вроде этого:
\long\def\gobble#1{}%
\long\def\firstofone#1{#1}%
\def\Iterator#1#2,{%
% #1 - element-separator to prepend; empty in the 1st iteration;
% comma in consecutive iterations
% #2 - either current element of old list or the \relax that was
% appended for denoting the end of the list
\ifx\relax#2\expandafter\gobble\else\expandafter\firstofone\fi
{%
#1\number\numexpr#2+1\relax\Iterator{,}%
}%
}%
\def\MyList{1,2,3}
\edef\newList{\expandafter\Iterator\expandafter{\expandafter}\MyList,\relax,}
\begingroup
\tt
\string\MyList: \meaning\MyList
\string\newList: \meaning\newList
\endgroup
\bye
Суть этого примера такова:
В \edef
-definition-text \newList
примитива TeX \expandafter
используется для расширения \MyList
. Также последовательность ,\relax,
добавляется.
Таким образом, во время определения \newList
\edef
, управляемое расширение текста определения \newList
на некотором этапе дает последовательность \Iterator{}Comma,sparated,items,from,\MyList,\relax,
.
На этом \relax,
список заканчивается.
Теперь — по-прежнему управляемый \edef
-expansion — \Iterator
(рекурсивно) выбирает неразделенный-аргумент #1
(который в первой итерации пуст, а в последовательных итерациях содержит запятую, т. е. содержит разделитель, добавляемый к элементу нового списка) и разделенный-запятой-аргумент, #2
который либо содержит следующий элемент из \myList
comma-list , либо содержит конечный-маркер \relax
, и в любом случае помещает — вложенным в фигурные скобки — последовательность токенов, обозначающую следующую итерацию, образованную
- неотграниченный-аргумент
#1
, т.е. разделитель, который должен предшествовать следующему элементу нового списка, - выражение
\number\numexpr#2+1\relax
для добавления 1 к значению, представленному аргументом, разделенным запятой, и таким образом формирования следующего элемента нового списка, - вызов самого себя для обработки следующего элемента, оставшегося от расширения
\myList
, на этот раз с запятой внутри неразделенного аргумента, обозначающей, что в следующий раз следующему элементу нового списка должна предшествовать запятая.
Via \ifx\relax#2
проверяется, достигнут ли конец списка запятых/ \relax
добавленного к списку в начале -expansion . Если да, то последовательность токенов, вложенная в фигурные скобки, обозначающая следующую итерацию, "поглощается/удаляется" via и, таким образом, не выполняется, что завершает итерацию/хвостовую рекурсию. Если нет, окружающие фигурные скобки удаляются из этой последовательности путем применения, после чего эта последовательность обрабатывается.\edef
\gobble
\firstofone
Неразделенный аргумент , #1
который \Iterator
содержит разделитель для добавления к элементу нового списка, пуст только в первой итерации. В каждой последующей итерации он содержит запятую, которая в предыдущей итерации этой последовательной итерации была предоставлена самим -макросом \Iterator
как часть последовательности токенов, которая затем сформировала следующую итерацию. Таким образом (только) первый элемент нового списка не предваряется запятой.
Если у вас нет ε-TeX-extensions, \numexpr
я могу предложить процедуру для увеличения неотрицательных целых чисел. (В «реальной жизни» вас могут заинтересовать пакетыintcalcиbigintcalc.)
%------------------------------------------------------------------------------
% Expandable incrementing of non-negative integer number formed by a sequence
% of explicit catcode-12-character-tokens from the set {0,1,2,3,4,5,6,7,8,9}
%..............................................................................
% \Increment{<non-negative integer number k as sequence of explicit
% catcode-12-character-tokens from the set 0123456789>}
% ->
% <natural number (k+1) as sequence of explicit catcode-12-character-tokens
% from the set 0123456789>
% In expansion-contexts the result is delivered after two expansion-steps/is
% obtained by "hitting" \Increment with \expandafter twice.
%------------------------------------------------------------------------------
\def\Increment#1{%
\romannumeral0%
\IncrementReverse{\IncrementFork{}}{\relax}{}#1\relax
}%
\def\IncrementReverse#1#2#3#4{%
% #1 - tokens to prepend to reversed list
% #2 - tokens to append to reversed list
% #3 - reversed list constructed so far
% #4 - current element of not-reversed list
\ifx\relax#4%
\expandafter\firstoftwo
\else
\expandafter\secondoftwo
\fi
{#1#3#2}{\IncrementReverse{#1}{#2}{#4#3}}%
}%
\def\IncrementSelect#10123456789\relax#2#3!!{#2}%
\def\IncrementFork#1#2{%
% #1 - digits incremented so far
% #2 - current digit to increment or end-marker \relax
\IncrementSelect
#2123456789\relax{\IncrementReverse{ }{}{}#11}%
0#223456789\relax{\IncrementReverse{ }{}{}#12}%
01#23456789\relax{\IncrementReverse{ }{}{}#13}%
012#2456789\relax{\IncrementReverse{ }{}{}#14}%
0123#256789\relax{\IncrementReverse{ }{}{}#15}%
01234#26789\relax{\IncrementReverse{ }{}{}#16}%
012345#2789\relax{\IncrementReverse{ }{}{}#17}%
0123456#289\relax{\IncrementReverse{ }{}{}#18}%
01234567#29\relax{\IncrementReverse{ }{}{}#19}%
012345678#2\relax{\IncrementFork{#10}}%
0123456789#2{\IncrementReverse{ }{}{}#11\relax}%
0123456789\relax{\IncrementReverse{ }{}{}#11#2}%
!!%
}%
%%-----------------------------------------------------------------------------
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\Iterator#1#2,{%
% #1 - element-separator to prepend
% #2 - current element of old list
\ifx\relax#2\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{}{%
#1\Increment{#2}\Iterator{,}%
}%
}%
\def\MyList{1,2,3}
\edef\newList{\expandafter\Iterator\expandafter{\expandafter}\MyList,\relax,}
\begingroup
\tt
\string\MyList: \meaning\MyList
\string\newList: \meaning\newList
\endgroup
\bye
Если вам нужна процедура, которая обходится без \edef
, вы можете, например, использовать \romannumeral0
технику -расширения- и обмена-аргументами — суть \romannumeral0
-расширения такова:
- TeX расширяет расширяемые токены, собирая токены, принадлежащие⟨число⟩-количество, которое должно быть представлено римскими цифрами.
- Если первый токен, который TeX находит при сборе⟨число⟩-количество - это цифра, например,
0
, то процесс сбора токенов, которые принадлежат⟨число⟩-количество превращается в процесс сбора большего количества цифр или чего-то, что не является цифрой, и поэтому завершает процесс сбора. Расширяемые жетоны расширяются при сборе цифр. Пробел-токен, завершающий последовательность цифр, завершает процесс сбора большего количества цифр и молча отбрасывается. - Если собранное число не положительно, TeX молча поглотит токены, формирующие⟨число⟩-количество без предоставления каких-либо токенов взамен.
Это означает, что его \romannumeral
можно использовать для того, чтобы заставить TeX выполнить большой объем работы по расширению и обмену аргументами, при условии, что в итоге будет найдено неположительное число.
%------------------------------------------------------------------------------
% Expandable incrementing of non-negative integer number formed by a sequence
% of explicit catcode-12-character-tokens from the set {0,1,2,3,4,5,6,7,8,9}
%..............................................................................
% \Increment{<non-negative integer number k as sequence of explicit
% catcode-12-character-tokens from the set 0123456789>}
% ->
% <natural number (k+1) as sequence of explicit catcode-12-character-tokens
% from the set 0123456789>
% In expansion-contexts the result is delivered after two expansion-steps/is
% obtained by "hitting" \Increment with \expandafter twice.
%------------------------------------------------------------------------------
\def\Increment#1{%
\romannumeral0%
\IncrementReverse{\IncrementFork{}}{\relax}{}#1\relax
}%
\def\IncrementReverse#1#2#3#4{%
% #1 - tokens to prepend to reversed list
% #2 - tokens to append to reversed list
% #3 - reversed list constructed so far
% #4 - current element of not-reversed list
\ifx\relax#4%
\expandafter\firstoftwo
\else
\expandafter\secondoftwo
\fi
{#1#3#2}{\IncrementReverse{#1}{#2}{#4#3}}%
}%
\def\IncrementSelect#10123456789\relax#2#3!!{#2}%
\def\IncrementFork#1#2{%
% #1 - digits incremented so far
% #2 - current digit to increment or end-marker \relax
\IncrementSelect
#2123456789\relax{\IncrementReverse{ }{}{}#11}%
0#223456789\relax{\IncrementReverse{ }{}{}#12}%
01#23456789\relax{\IncrementReverse{ }{}{}#13}%
012#2456789\relax{\IncrementReverse{ }{}{}#14}%
0123#256789\relax{\IncrementReverse{ }{}{}#15}%
01234#26789\relax{\IncrementReverse{ }{}{}#16}%
012345#2789\relax{\IncrementReverse{ }{}{}#17}%
0123456#289\relax{\IncrementReverse{ }{}{}#18}%
01234567#29\relax{\IncrementReverse{ }{}{}#19}%
012345678#2\relax{\IncrementFork{#10}}%
0123456789#2{\IncrementReverse{ }{}{}#11\relax}%
0123456789\relax{\IncrementReverse{ }{}{}#11#2}%
!!%
}%
%%-----------------------------------------------------------------------------
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\exchange#1#2{#2#1}%
\def\Iterator#1,#2\relax#3#4{%
% #1 - current element of old list
% #2 - remaining elements of old list
% #3 - element-separator to prepend
% #4 - new list constructed so far
\ifx\relax#1\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{ #4}{%
\expandafter\exchange
\expandafter{%
\expandafter{%
\romannumeral0%
\expandafter\expandafter\expandafter\exchange
\expandafter\expandafter\expandafter{%
\Increment{#1}}{ #4#3}}}{\Iterator#2\relax{,}}%
}%
}%
\def\MyList{0,1,2,3}
\expandafter\def
\expandafter\newList
\expandafter{%
\romannumeral0\expandafter\Iterator\MyList,{\relax},\relax{}{}}%
\begingroup
\tt
\string\MyList: \meaning\MyList
\string\newList: \meaning\newList
\endgroup
\bye
решение4
Поскольку вы новичок, вы можете начать с expl3
.
\documentclass{article}
\usepackage{xparse,xfp}
\ExplSyntaxOn
\NewDocumentCommand{\generatelist}{mmm}
{% #1=output, #2=input, #3=iterator
\harry_list_generate:nnn { #1 } { #2 } { #3 }
}
% variables
\clist_new:N \l__harry_list_input_clist
\clist_new:N \l__harry_list_output_clist
% the main function
\cs_new_protected:Nn \harry_list_generate:nnn
{
% if the input is a single token, assume it is a control sequence
\tl_if_single:nTF { #2 }
{ \clist_set_eq:NN \l__harry_list_input_clist #2 }
{ \clist_set:Nn \l__harry_list_input_clist { #2 } }
% now \l__harry_list_input_clist contains the input
% clear the output list
\clist_clear:N \l__harry_list_output_clist
% map the input list applying the iterator to each item
\clist_map_inline:Nn \l__harry_list_input_clist
{
\clist_put_right:Nx \l__harry_list_output_clist { #3 { ##1 } }
}
% make the output list
\clist_set_eq:NN #1 \l__harry_list_output_clist
}
\ExplSyntaxOff
% two example iterators
\newcommand{\addone}[1]{\inteval{#1+1}}
\newcommand{\addhyphens}[1]{-#1-}
% a control sequence expanding to a list
\newcommand{\List}{1,2,3,41}
\generatelist{\ListA}{\List}{\addone}
\generatelist{\ListB}{1,2,3}{\addhyphens}
\show\ListA
\show\ListB
Это выводит
> \ListA=macro:
->2,3,4,42.
l.50 \show\ListA
?
> \ListB=macro:
->-1-,-2-,-3-.
l.51 \show\ListB