Итерация списка и изменение элементов по одному для создания нового списка

Итерация списка и изменение элементов по одному для создания нового списка

Давний читатель, впервые публикую.

У меня есть вопрос о создании макроса, который перебирает список. Я прочитал несколько вопросов, опубликованных другими пользователями, но все они как-то связаны с использованием 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который либо содержит следующий элемент из \myListcomma-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

Связанный контент