
長年の読者ですが、初めて投稿します。
リストを反復するマクロの作成について質問があります。他のユーザーが投稿した質問をいくつか読みましたが、それらはすべて LaTeX を複雑な方法で使用することに関するものでした。私の質問は、プレーン TeX を使用してリストを反復し、古いリストの各要素を変更して新しいリストを作成することです。現在、TeX を独学していますが、基本的でありながら多用途で堅牢なマクロをプログラミングすると、TeX の仕組みをよりよく理解できると思います。さて、背景はこれで十分です。質問に移りましょう。
これまでの私のコードは次のとおりです:
私はイテレータを再帰的に定義しました。イテレータは一度に 1 つの引数を読み取り、引数がコンマかストップ マーカー (\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
を格納するために と呼ばれる中間 Iterator を定義する必要がありました。これが私の最初の質問だと思います。ネストされたコマンドを定義する方法はあるのでしょうか?\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 について十分に理解していないため、不適切に構築されたトークン リストからマクロを保護する方法を想像することすらできません。そのため、経験豊富な方がこのコードを見て笑わずにはいられない場合はお詫び申し上げます。
さて、私のもう一つの質問はこれです。自分自身を呼び出すマクロを定義するより効率的な方法はありますか?反復マクロを別途定義し、TeX の組み込み\loop
コマンドを使用して呼び出す方がよい方法でしょうか?もしそうなら、その方法を誰か説明してもらえませんか?TeX の目、口、食道、胃のプロセスのコンテキストでループ呼び出しの意味が理解できないからです。ループはマクロを展開し、\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 の後半の章で簡単に触れられている「副作用」のどれがプログラミング トリックに使用されているかなど) を正確に把握しやすくなります。
コンマ リスト内のエントリがスペースで囲まれておらず、\relax
- プリミティブがコンマ リスト内に出現せず、\numexpr
ε-TeX 拡張機能が使用可能であると仮定すると、おそらく次のような操作を実行できます。
\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
この例の要点は次のとおりです。
TeX プリミティブ\edef
の -definition-text内ではが展開に使用されます。また、シーケンスが追加されます。\newList
\expandafter
\MyList
,\relax,
この方法では、 を定義するときに、ある段階\newList
\edef
での定義テキストの 駆動型展開により、シーケンス が生成されます。\newList
\Iterator{}Comma,sparated,items,from,\MyList,\relax,
これで\relax,
リストは終わりです。
ここで、依然として\edef
-expansionによって駆動され、 \Iterator
(再帰的に)非区切り引数#1
(最初の反復では空で、その後の反復ではコンマ、つまり新しいリストの項目の先頭に追加する区切り文字を保持する)と、のコンマリスト#2
からの次の項目を保持する\myList
か、終了マーカーを保持するコンマ区切り引数\relax
を選択し、いずれの場合も、次の反復を示すトークンシーケンスを中括弧で囲んで配置します。トークンシーケンスは、
- 区切られていない引数
#1
、つまり新しいリストの次の項目の前に置かなければならない区切り文字、 \number\numexpr#2+1\relax
カンマ区切りの引数で表される値に1を加算し、新しいリストの次の項目を形成する式。- の展開から残っている次の項目を処理するために自分自身を呼び出します。
\myList
今回は、区切られていない引数内にコンマが提供され、次回は新しいリストの次の項目の前にコンマが付くことを示します。
を介して、\ifx\relax#2
カンマ リストの末尾または-expansion\relax
の先頭のリストに追加された\edef
ものに到達したかどうかがチェックされます。到達した場合、次の反復を示す中括弧でネストされたトークン シーケンスは を介して「取り込まれ/削除」され\gobble
、実行されず、反復/末尾再帰が終了します。到達しなかった場合、 を適用してそのシーケンスから周囲の中括弧が削除され\firstofone
、その後そのシーケンスが処理されます。
の区切られていない引数#1
は\Iterator
、新しいリストの項目の先頭に追加する区切り文字を保持しますが、最初の反復でのみ空です。後続の各反復では、その連続する反復の前の反復で -macro\Iterator
自体によってトークン シーケンスの一部として提供され、次の反復を形成するコンマを保持します。このように、新しいリストの最初の項目 (のみ) の前にはコンマが付きません。
ε-TeX拡張機能が\numexpr
利用できない場合は、負でない整数を増分するルーチンを提供できます。(「現実の世界」では、次のパッケージに興味があるかもしれません。整数計算そしてビッグイント計算。
%------------------------------------------------------------------------------
% 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
場合、その数字に属するトークンを集めるプロセスは、⟨番号⟩-quantity は、より多くの数字または数字ではないものを集めるプロセスに変わり、その結果、収集プロセスが終了します。拡張可能なトークンは、数字を収集しながら拡張されます。数字シーケンスを終了するスペース トークンは、より多くの数字を収集するプロセスを終了し、暗黙的に破棄されます。 - 集めた数が正でない場合、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