Iterar a través de una lista y modificar elementos uno a la vez para construir una nueva lista

Iterar a través de una lista y modificar elementos uno a la vez para construir una nueva lista

Lector desde hace mucho tiempo, cartel por primera vez.

Tengo una pregunta sobre la creación de una macro que recorre en iteración una lista. He leído algunas preguntas publicadas por otros usuarios, pero todas tienen algo que ver con el uso de LaTeX de una manera complicada. Mi pregunta es sobre el uso de TeX simple para recorrer una lista y construir una nueva lista modificando cada elemento de la lista anterior. Actualmente estoy aprendiendo TeX por mi cuenta y creo que programar algunas macros básicas, pero versátiles y sólidas, me ayudará a tener una mejor idea de cómo funcionan las entrañas de TeX. De todos modos, suficiente información sobre mi pregunta.

Este es mi código hasta ahora:

Definí el iterador de forma recursiva. Lee un argumento a la vez, determina si el argumento es una coma o un marcador de parada (según mi propia definición, llamado \myStop), avanza si es una coma, se detiene si es el marcador de parada y, en caso contrario, reemplaza el elemento por sí mismo y la cadena (o 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%
}

Tuve que definir un iterador intermedio llamado IteratorIntermediateOnepara albergar el comando \expandafter\Iteratorporque actualmente no conozco una forma de agrupar términos después de un \expandaftercomando de una manera que sería equivalente a algo como \expandafter{\expandafter\Iterator}\fi\fi. Supongo que esa es mi primera pregunta: ¿Existe alguna forma de definir \expandaftercomandos anidados?

Ahora que todo está en contexto, aquí está todo mi 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}

Descargo de responsabilidad: soy consciente de la coma adicional que esto coloca después del último elemento. También soy consciente de que no hay suficientes casos para detectar listas mal construidas. No sé lo suficiente sobre TeX para siquiera empezar a imaginar cómo proteger la macro contra listas de tokens construidas incorrectamente, así que me disculpo si los más experimentados no pueden evitar reírse de este código.

Bien, mi otra pregunta es la siguiente: ¿Existe una forma más eficiente de definir una macro que se llama a sí misma? ¿Es una mejor práctica definir una macro iteradora por separado y llamarla usando el comando integrado de TeX \loop? Si es así, ¿alguien puede explicarme cómo haría eso, porque tengo problemas para entender la llamada de bucle en el contexto de los procesos de los ojos, la boca, la garganta y el estómago de TeX? ¿El bucle expande la macro, la alcanza, \repeatla pasa al estómago y regresa a la macro? No encuentro una buena explicación por ningún lado.

¡Gracias por toda tu ayuda!

Respuesta1

La intención parece ser agregar 1 a cada elemento. Lo codificaría más así (asumiendo 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

lo que hace el mensaje

2,3,4,26,457,3 

Respuesta2

\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

ingrese la descripción de la imagen aquí

Si realmente necesitasahorrarla lista actualizada, podemos hacerlo en la \mytokslista 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

ingrese la descripción de la imagen aquí

Respuesta3

Usted dijo:

... así que disculpas si los más experimentados no pueden evitar reírse de este código.

Cuando comencé a aprender TeX, tuve la sensación de que era una curva de aprendizaje muy pronunciada.

De vez en cuando me sentía frustrado. ;-)

No creo que personas como usted, que están asumiendo esta curva de aprendizaje, se encuentren en una situación en la que sea apropiado reírse de sus intentos de programación macro/programación TeX.

Creo que cualquier intento de lograr o aprender algo bueno de una manera que también se base en la razón y que en sí mismo no sea un delito, básicamente merece respeto.

Si tiene preguntas sobre cómo funciona el código de mis ejemplos a continuación, no dude en preguntarlas. Entonces resulta útil describir cómo cree que funciona el código y dónde se queda atascado en su comprensión. En mi experiencia, esto hace que sea más fácil para los encuestados descubrir exactamente qué información (por ejemplo, sobre cómo funcionan las primitivas TeX y cuáles de los "efectos secundarios" brevemente mencionados en los últimos capítulos del TeXbook se utilizan para trucos de programación) todavía se utiliza. Falta para la comprensión.


Suponiendo que las entradas en su lista de comas no están rodeadas por espacios, y que la \relaxprimitiva -no aparece dentro de la lista de comas, y que \numexprlas extensiones ε-TeX están disponibles, probablemente pueda hacer algo como esto:

\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

La esencia de este ejemplo es:

Dentro del \edeftexto de definición de \newListla primitiva TeX \expandafterse utiliza para expandir \MyList. También ,\relax,se adjunta la secuencia .

De esta manera, en el momento de la definición, \newList \edefla expansión impulsada del texto de definición de \newListen algún momento produce una secuencia \Iterator{}Comma,sparated,items,from,\MyList,\relax,.

Esto \relax,marca el final de la lista.

Ahora, todavía impulsado por \edefla expansión, \Iterator(recursivamente) elige un argumento no delimitado #1(que en la primera iteración está vacío y en iteraciones consecutivas contiene una coma, es decir, que contiene el separador para anteponer al elemento de la nueva lista) y un argumento delimitado por comas #2que contiene el siguiente elemento procedente de \myListla lista de comas o contiene el marcador final \relaxy, en cualquier caso, coloca (entre llaves) una secuencia simbólica que denota la siguiente iteración, formada por

  • el argumento no delimitado #1, es decir, el separador que debe preceder al siguiente elemento de la nueva lista,
  • la expresión \number\numexpr#2+1\relaxpara sumar 1 al valor representado por el argumento delimitado por comas y de esta manera formar el siguiente elemento de la nueva lista,
  • una llamada a sí mismo para procesar el siguiente elemento restante de la expansión de \myList, esta vez proporcionando una coma dentro del argumento no delimitado, lo que indica que la próxima vez el siguiente elemento de la nueva lista irá precedido por una coma.

A través de él se comprueba si se ha llegado \ifx\relax#2al final de la lista de comas/lo \relaxañadido a la lista al principio de la expansión. \edefSi es así, la secuencia de token anidada entre llaves que denota la siguiente iteración es "engullida/eliminada" \gobbley por lo tanto no se lleva a cabo, lo que termina la iteración/recursión de cola. De lo contrario, las llaves circundantes se eliminan de esa secuencia aplicándolas \firstofoney luego se procesa esa secuencia.

El argumento no delimitado #1de \Iterator, que contiene el separador para anteponer al elemento de la nueva lista, solo en la primera iteración está vacío. En cada iteración consecutiva, contiene una coma que en la iteración anterior de esa iteración consecutiva fue proporcionada por la \Iteratorpropia macro como parte de la secuencia de tokens que luego formó la siguiente iteración. De esta forma (únicamente) el primer elemento de la nueva lista no estará precedido por una coma.


Si no tiene extensiones ε-TeX \numexprdisponibles, puedo ofrecerle una rutina para incrementar números enteros no negativos. (En la "vida real" quizás te interesen los paquetesintcalcybigintcalc.)

%------------------------------------------------------------------------------
% 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

Si desea una rutina que prescinda de \edef, puede, por ejemplo, utilizar la \romannumeral0técnica de -expansión-y de intercambio de argumentos; la esencia de \romannumeral0-expansión es:

  • TeX expande los tokens expandibles mientras reúne tokens que pertenecen al⟨número⟩-Cantidad que se quiere representar en números romanos.
  • Si el primer token que TeX encuentra mientras recopila el⟨número⟩-la cantidad es un dígito, por ejemplo 0, entonces el proceso de reunir tokens que pertenecen al⟨número⟩-la cantidad se convierte en un proceso de juntar más dígitos o algo que no es un dígito y por lo tanto termina el proceso de juntar. Los tokens expandibles se expanden mientras acumulan dígitos. Un token espacial que termina una secuencia de dígitos finaliza el proceso de reunir más dígitos y se descarta silenciosamente.
  • Si el número reunido no es positivo, TeX se tragará silenciosamente los tokens que forman el⟨número⟩-cantidad sin entregar ningún token a cambio.

Esto implica que \romannumeralse puede utilizar para engañar a TeX para que realice una gran cantidad de trabajos de expansión e intercambio de argumentos, siempre y cuando se garantice que al final se encuentre un número no positivo.

%------------------------------------------------------------------------------
% 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

Respuesta4

Como eres nuevo, puedes empezar con 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

Esto produce

> \ListA=macro:
->2,3,4,42.
l.50 \show\ListA

?
> \ListB=macro:
->-1-,-2-,-3-.
l.51 \show\ListB

información relacionada