Durchlaufen einer Liste und Ändern der Elemente nacheinander, um eine neue Liste zu erstellen

Durchlaufen einer Liste und Ändern der Elemente nacheinander, um eine neue Liste zu erstellen

Ich lese es schon lange und schreibe zum ersten Mal.

Ich habe eine Frage zum Erstellen eines Makros, das eine Liste durchläuft. Ich habe einige Fragen anderer Benutzer gelesen, aber alle davon haben etwas mit der komplizierten Verwendung von LaTeX zu tun. Meine Frage betrifft die Verwendung von einfachem TeX zum Durchlaufen einer Liste und zum Erstellen einer neuen Liste durch Ändern jedes Elements in der alten Liste. Ich bin gerade dabei, TeX im Selbststudium zu lernen, und ich denke, dass mir das Programmieren einiger einfacher, aber vielseitiger und robuster Makros dabei helfen wird, ein besseres Gespür dafür zu entwickeln, wie das Innere von TeX funktioniert. So, genug Hintergrundinformationen, nun zu meiner Frage.

Dies ist mein Code bisher:

Ich habe den Iterator rekursiv definiert. Er liest jeweils ein Argument, ermittelt, ob das Argument ein Komma oder ein Stoppzeichen (meiner eigenen Definition, genannt \myStop) ist, fährt fort, wenn es ein Komma ist, stoppt, wenn es das Stoppzeichen ist, und ersetzt andernfalls das Element durch sich selbst und die Zeichenfolge (oder Tokenliste) „+ 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%
}

Ich musste einen Zwischeniterator namens definieren, IteratorIntermediateOneum den Befehl unterzubringen \expandafter\Iterator, da mir derzeit keine Möglichkeit bekannt ist, Begriffe nach einem \expandafterBefehl so zu gruppieren, dass sie etwa dem entsprechen \expandafter{\expandafter\Iterator}\fi\fi. Das ist also wohl meine erste Frage: Gibt es eine Möglichkeit, verschachtelte Befehle zu definieren \expandafter?

Nachdem nun alles im Kontext steht, hier mein gesamter Code:

\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}

Haftungsausschluss: Mir ist bewusst, dass hier nach dem letzten Element ein zusätzliches Komma steht. Mir ist auch bewusst, dass es nicht genügend Fälle gibt, um schlecht erstellte Listen zu erkennen. Ich weiß nicht genug über TeX, um mir auch nur ansatzweise vorstellen zu können, wie man das Makro vor unsachgemäß erstellten Token-Listen schützen kann. Entschuldigen Sie also, wenn die Erfahreneren unter Ihnen nicht anders können, als über diesen Code zu lachen.

Okay, meine andere Frage ist diese: Gibt es eine effizientere Möglichkeit, ein Makro zu definieren, das sich selbst aufruft? Ist es besser, ein Iteratormakro separat zu definieren und es mit dem integrierten \loopBefehl von TeX aufzurufen? Wenn ja, kann mir jemand erklären, wie ich das mache, denn ich habe Probleme, den Schleifenaufruf im Kontext der TeX-Prozesse Auge, Mund, Speiseröhre und Magen zu verstehen. Erweitert die Schleife das Makro, reicht \repeates an den Magen weiter und kehrt zum Makro zurück? Ich kann nirgends eine gute Erklärung finden.

Danke für deine Hilfe!

Antwort1

Die Absicht scheint zu sein, jedem Element 1 hinzuzufügen. Ich würde es eher so codieren (unter der Annahme von 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

das macht die Nachricht

2,3,4,26,457,3 

Antwort2

\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

Bildbeschreibung hier eingeben

Wenn Sie wirklich müssenspeicherndie aktualisierte Liste, können wir dies in der Token-Liste tun \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

Bildbeschreibung hier eingeben

Antwort3

Du sagtest:

… also entschuldigen Sie, wenn die Erfahreneren unter Ihnen nicht anders können, als über diesen Code zu lachen.

Als ich begann, TeX zu lernen, hatte ich das Gefühl, dass die Lernkurve sehr steil war.

Ab und zu war ich frustriert. ;-)

Ich glaube nicht, dass Leute wie Sie, die diese Lernkurve durchlaufen, in einer Situation sind, in der es angebracht ist, über ihre Versuche in der Makroprogrammierung/TeX-Programmierung zu lachen.

Ich finde, jeder Versuch, etwas Gutes zu erreichen oder zu lernen, und zwar auf eine Art und Weise, die einerseits auf Vernunft beruht und andererseits kein Vergehen darstellt, verdient grundsätzlich Respekt.

Wenn du Fragen zur Funktionsweise des Codes aus meinen Beispielen unten hast, scheue dich nicht, sie zu stellen. Es ist dann sinnvoll zu beschreiben, wie du den Code für richtig hältst und wo du mit dem Verständnis nicht weiterkommst. Meiner Erfahrung nach fällt es den Antwortenden so leichter, genau herauszufinden, welche Informationen (z.B. darüber, wie TeX-Primitive funktionieren und welche der in den hinteren Kapiteln des TeXbooks kurz angedeuteten „Nebeneffekte“ für Programmiertricks verwendet werden) noch zum Verständnis fehlen.


Vorausgesetzt, dass die Einträge in Ihrer Kommaliste nicht von Leerzeichen umgeben sind und dass das \relax-Primitiv nicht innerhalb der Kommaliste vorkommt und dass \numexpreine der ε-TeX-Erweiterungen verfügbar ist, können Sie wahrscheinlich etwa Folgendes tun:

\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

Der Kerngedanke dieses Beispiels ist:

Innerhalb des \edef-Definitionstextes wird \newListdas TeX-Primitiv \expandafterzur Erweiterung verwendet \MyList. Auch die Sequenz ,\relax,wird angehängt.

Auf diese Weise entsteht bei der definitionsgesteuerten \newList \edefErweiterung des Definitionstextes \newListirgendwann eine Folge \Iterator{}Comma,sparated,items,from,\MyList,\relax,.

Damit \relax,ist die Liste zu Ende.

Nun wählt – immer noch angetrieben durch \edef-expansion – \Iterator(rekursiv) ein nicht begrenztes Argument #1(das in der ersten Iteration leer ist und in den darauffolgenden Iterationen ein Komma enthält, also das Trennzeichen, das dem Element der neuen Liste vorangestellt wird) und ein durch Kommas begrenztes Argument, #2das entweder das nächste Element aus \myListder Kommaliste von enthält oder den Endmarker \relax, und platziert in jedem Fall – in geschweiften Klammern verschachtelt – eine Token-Sequenz, die die nächste Iteration bezeichnet, gebildet durch

  • das undelimited-Argument #1, also das Trennzeichen, das vor dem nächsten Eintrag der neuen Liste stehen muss,
  • der Ausdruck \number\numexpr#2+1\relaxzum Addieren von 1 zum durch das Komma-getrennte Argument dargestellten Wert und zum Bilden des nächsten Elements der neuen Liste,
  • ein Aufruf an sich selbst zur Verarbeitung des nächsten Elements, das von der Erweiterung von übrig bleibt \myList, wobei dieses Mal ein Komma innerhalb des nicht begrenzten Arguments bereitgestellt wird, um anzuzeigen, dass dem nächsten Element der neuen Liste beim nächsten Mal ein Komma vorangestellt werden soll.

Via \ifx\relax#2wird geprüft, ob das Ende der Kommaliste/die \relaxam Anfang der \edef-Erweiterung an die Liste angehängte . Ist dies der Fall, wird die in geschweiften Klammern verschachtelte Token-Sequenz, die die nächste Iteration bezeichnet, via „verschluckt/entfernt“ \gobbleund somit nicht ausgeführt, was die Iteration/Endrekursion beendet. Ist dies nicht der Fall, werden die umgebenden geschweiften Klammern per apply aus dieser Sequenz entfernt, \firstofoneworaufhin diese Sequenz abgearbeitet wird.

Das nicht begrenzte Argument #1von \Iterator, das das Trennzeichen enthält, das dem Element der neuen Liste vorangestellt werden soll, ist nur in der ersten Iteration leer. In jeder nachfolgenden Iteration enthält es ein Komma, das in der vorherigen Iteration dieser nachfolgenden Iteration vom \Iterator-Makro selbst als Teil der Token-Sequenz bereitgestellt wurde, die dann die nächste Iteration bildete. Auf diese Weise wird (nur) dem ersten Element der neuen Liste kein Komma vorangestellt.


Wenn Sie keine ε-TeX-Erweiterungen \numexprzur Verfügung haben, kann ich Ihnen eine Routine zum Inkrementieren nicht-negativer Ganzzahlen anbieten. (Im „echten Leben“ könnten Sie an den Paketen interessiert seinintcalcUndBigintCalc.)

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

Wenn Sie eine Routine wünschen, die ohne auskommt \edef, können Sie beispielsweise die \romannumeral0-Expansion- und Argument-Austausch-Technik verwenden – der Kern der \romannumeral0-Expansion ist:

  • TeX erweitert erweiterbare Token und sammelt Token, die zu den⟨Nummer⟩-Menge, die in römischen Zahlen dargestellt werden soll.
  • Wenn das erste Token, das TeX beim Sammeln der⟨Nummer⟩-Menge ist eine Ziffer, z. B. 0, dann der Prozess des Sammelns von Token, die zu den⟨Nummer⟩-Menge wird zu einem Prozess des Sammelns weiterer Ziffern oder von etwas, das keine Ziffer ist, und beendet daher den Prozess des Sammelns. Erweiterbare Token werden beim Sammeln von Ziffern erweitert. Ein Leerzeichen-Token, das eine Ziffernfolge beendet, beendet den Prozess des Sammelns weiterer Ziffern und wird stillschweigend verworfen.
  • Wenn die gesammelte Zahl nicht positiv ist, wird TeX stillschweigend die Tokens aufnehmen, die die⟨Nummer⟩-Menge, ohne im Gegenzug ein Token zu liefern.

Dies bedeutet, dass \romannumerales verwendet werden kann, um TeX dazu zu bringen, viel Erweiterungs- und Argumentaustauscharbeit zu leisten, solange sichergestellt ist, dass am Ende eine nicht positive Zahl gefunden wird.

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

Antwort4

Da Sie neu sind, können Sie mit beginnen 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

Dies gibt

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

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

verwandte Informationen