
Leitor de longa data, postador pela primeira vez.
Tenho uma dúvida sobre como criar uma macro que itere em uma lista. Eu li algumas perguntas postadas por outros usuários, mas todas elas têm algo a ver com o uso do LaTeX de uma forma complicada. Minha pergunta é sobre como usar o TeX simples para percorrer uma lista e construir uma nova lista modificando cada elemento da lista antiga. Atualmente estou aprendendo TeX por conta própria e acho que programar algumas macros básicas, mas versáteis e robustas, me ajudará a ter uma noção melhor de como funciona a essência do TeX. De qualquer forma, informações suficientes para minha pergunta.
Este é o meu código até agora:
Eu defini o iterador recursivamente. Ele lê um argumento por vez, determina se o argumento é uma vírgula ou um marcador de parada (de minha própria definição, chamado \myStop), segue em frente se for uma vírgula, para se for o marcador de parada e, caso contrário, substitui o elemento por ele mesmo e a string (ou lista de tokens) "+ 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%
}
Tive que definir um Iterador intermediário chamado IteratorIntermediateOne
para abrigar o comando \expandafter\Iterator
porque atualmente não conheço uma maneira de agrupar termos após um \expandafter
comando de uma forma que seria equivalente a algo como \expandafter{\expandafter\Iterator}\fi\fi
. Então acho que essa é minha primeira pergunta: existe uma maneira de definir \expandafter
comandos aninhados?
Agora que tudo está no contexto, aqui está todo o meu código:
\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}
Isenção de responsabilidade: estou ciente da vírgula extra colocada após o último elemento. Também estou ciente de que não há casos suficientes para detectar listas mal construídas. Eu não sei o suficiente sobre TeX para sequer começar a imaginar como proteger a macro contra listas de tokens construídas incorretamente, então peço desculpas se os mais experientes de vocês não puderem deixar de rir desse código.
Ok, minha outra pergunta é esta: existe uma maneira mais eficiente de definir uma macro que chama a si mesma? É uma prática recomendada definir uma macro iteradora separadamente e chamá-la usando o comando interno do TeX \loop
? Nesse caso, alguém pode me explicar como eu faria isso, porque estou tendo problemas para entender a chamada de loop no contexto dos processos dos olhos, boca, garganta e estômago do TeX. O loop expande a macro, \repeat
passa para o estômago e retorna para a macro? Não consigo encontrar uma boa explicação em lugar nenhum.
Obrigado por toda sua ajuda!
Responder1
A intenção parece ser adicionar 1 a cada item. Eu codificaria mais assim (assumindo 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
o que torna a mensagem
2,3,4,26,457,3
Responder2
\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
Se você realmente precisasalvarna lista atualizada, podemos fazer isso na \mytoks
lista de tokens:
\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
Responder3
Você disse:
... então, peço desculpas se os mais experientes de vocês não conseguem deixar de rir desse código.
Quando comecei a aprender TeX, tive a sensação de que era uma curva de aprendizado muito acentuada.
De vez em quando eu ficava frustrado. ;-)
Não acredito que pessoas como você, que estão assumindo essa curva de aprendizado, estejam em uma situação em que seja apropriado rir de suas tentativas de programação macro/programação TeX.
Acho que qualquer tentativa de alcançar ou aprender algo bom de uma forma que também seja baseada na razão e em si não seja um delito, basicamente merece respeito.
Se você tiver dúvidas sobre como funciona o código dos exemplos abaixo, não hesite em perguntar. É então útil descrever como você acha que o código funciona e onde você fica preso no entendimento. Na minha experiência, isso torna mais fácil para os entrevistados descobrirem exatamente quais informações (por exemplo, sobre como funcionam as primitivas do TeX e quais dos "efeitos colaterais" brevemente sugeridos nos últimos capítulos do TeXbook são usados para truques de programação) ainda são falta para compreensão.
Supondo que as entradas em sua lista de vírgulas não estejam cercadas por espaços, e que o \relax
-primitivo não ocorra dentro da lista de vírgulas, e que \numexpr
as extensões ε-TeX estejam disponíveis, você provavelmente pode fazer algo assim:
\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
A essência deste exemplo é:
Dentro do \edef
-definition-text do \newList
TeX-primitive \expandafter
é usado para expansão \MyList
. Além disso, a sequência ,\relax,
é anexada.
Dessa forma, no momento da definição \newList
\edef
, a expansão do texto de definição \newList
em algum estágio produz uma sequência \Iterator{}Comma,sparated,items,from,\MyList,\relax,
.
Então \relax,
marca o fim da lista.
Agora - ainda impulsionado por \edef
-expansão \Iterator
- (recursivamente) escolhe um argumento não delimitado #1
(que na primeira iteração está vazio e em iterações consecutivas contém uma vírgula, ou seja, que contém o separador para preceder o item da nova lista) e um argumento delimitado por vírgula #2
que contém o próximo item vindo da \myList
lista de vírgulas ou contém o marcador final \relax
e, em qualquer caso, coloca - aninhado entre chaves - uma sequência de token denotando a próxima iteração, formada por
- o argumento indelimitado
#1
, ou seja, o separador que deve preceder o próximo item da nova lista, - a expressão
\number\numexpr#2+1\relax
para adicionar 1 ao valor representado pelo argumento delimitado por vírgula e assim formar o próximo item da nova lista, - uma chamada para si mesmo para processar o próximo item restante da expansão de
\myList
, desta vez fornecendo uma vírgula dentro do argumento indelimitado, denotando que na próxima vez o próximo item da nova lista será precedido por uma vírgula.
Através dele \ifx\relax#2
é verificado se o final da lista de vírgulas/o \relax
anexado à lista no início da \edef
expansão foi alcançado. Nesse caso, a sequência de tokens aninhada entre chaves que denotam a próxima iteração é "devorada/removida" \gobble
e, portanto, não executada, o que encerra a iteração/recursão final. Caso contrário, as chaves circundantes são removidas dessa sequência aplicando- \firstofone
se o que significa que essa sequência é processada.
O argumento indelimitado #1
de \Iterator
, que contém o separador para preceder o item da nova lista, apenas na primeira iteração está vazio. Em cada iteração consecutiva, ele contém uma vírgula que, naquela iteração anterior da iteração consecutiva, foi fornecida pela \Iterator
própria -macro como parte da sequência de tokens que então formou a próxima iteração. Desta forma (apenas) o primeiro item da nova lista não é precedido de vírgula.
Se você não tiver extensões ε-TeX \numexpr
disponíveis, posso oferecer uma rotina para incrementar números inteiros não negativos. (Na "vida real" você pode estar interessado nos pacotesintcalcebigintcalc.)
%------------------------------------------------------------------------------
% 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
Se você deseja uma rotina que dispensa \edef
, você pode, por exemplo, usar a \romannumeral0
técnica de -expansão e troca de argumentos - a essência de \romannumeral0
-expansão é:
- TeX expande tokens expansíveis enquanto coleta tokens que pertencem ao⟨número⟩-quantidade que deve ser representada em algarismos romanos.
- Se o primeiro token que o TeX encontrar ao coletar o⟨número⟩-quantidade é um dígito, por exemplo,
0
, então o processo de coleta de tokens que pertencem ao⟨número⟩-quantidade se transforma em um processo de coleta de mais dígitos ou algo que não é um dígito e, portanto, encerra o processo de coleta. Os tokens expansíveis são expandidos durante a coleta de dígitos. Um token espacial que encerra uma sequência de dígitos encerra o processo de coleta de mais dígitos e é descartado silenciosamente. - Se o número obtido não for positivo, o TeX engolirá silenciosamente os tokens que formam o⟨número⟩-quantidade sem entregar nenhum token em troca.
Isso implica que \romannumeral
pode ser usado para enganar o TeX para que ele faça muito trabalho de expansão e troca de argumentos, desde que seja garantido que no final um número não positivo seja encontrado.
%------------------------------------------------------------------------------
% 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
Responder4
Como você é novo, você pode começar com 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
Isso produz
> \ListA=macro:
->2,3,4,42.
l.50 \show\ListA
?
> \ListB=macro:
->-1-,-2-,-3-.
l.51 \show\ListB