# macro for sequence generation

I'm looking for a macro that takes in input a general term of a sequence and write the sequence extensively.

What I mean is a command \GenSeq{general term}{index}{min index}{max index} so that for example

\GenSeq{f(i)}{i}{1}{n} produces

\GenSeq{f(i)}{i}{k}{n} produces

\GenSeq{\theta^{(s)}}{s}{s}{T}

I wonder whether such a thing can be programmed in latex

• could be done although the syntax \GenSeq{\theta^{(#1)}}{s}{T} would be easier if you'd allow that, to save finding the parameter in the template "by hand" just use the built in parameter mechanism. – David Carlisle Nov 12 '20 at 14:19

\documentclass{article}

\def\GenSeq#1#2#3{%
\def\zz##1{#1}%
\def\zzstart{#2}%
\zz{#2},
\ifx\zzstart\zzzero\zz{1}\else\ifx\zzstart\zzone\zz{2}\else\zz{#2+1}\fi\fi,
\ldots,\zz{#3}}
\def\zzzero{0}
\def\zzone{1}
\begin{document}

\parskip\bigskipamount

$\GenSeq{f(#1)}{1}{n}$

$\GenSeq{f(#1)}{k}{n}$

$\GenSeq{\theta^{(#1)}}{s}{T}$

\end{document}

• Alright, I've upvoted by I have absolutely not the faintest idea how this is possibly working... – campa Nov 12 '20 at 15:10
• @campa Hint: If \GenSeq's 1st argument is the sequence f(#1), then \def\zz##1{#1} from within \GenSeq's definition text expands to \def\zz#1{f(#1)}. (#1 from \GenSeq's definition text is replaced by the tokens that are passed as first argument, i.e., is replaced by the tokens f(#1) and ##1 from \GenSeq's definition text is reduced to #1). – Ulrich Diez Nov 12 '20 at 17:25
• @UlrichDiez Erm... I fear you are grossly overestimating my intellect... – campa Nov 12 '20 at 19:53
• @campa it is doing \newcommand\zz[1]{f(#1)} \zz{1}, \z{2},\ldots,\zz{n} with a test for the start of the list being 0 or 1 in which case the second item uses 1 or 2 rather than appending +1 – David Carlisle Nov 12 '20 at 19:55

An implementation with expl3.

The first mandatory argument to \GenSeq is a template, with #1 standing for the current index in the “loop“. The second argument is the start point, the third argument is the end point.

If the second argument is an integer (recognized via a regular expression, zero or one hyphen/minus sign and one or more digits), the second printed item will have the index computed, otherwise it will be <start point>+1. However

1. if the start point and end point coincide, just one item will be printed;
2. if the start point is numeric and the end point is numeric as well and differs by one or two, only the relevant items will be printed;
3. otherwise, the starting item, the next, dots and the ending item will be printed.

With \GenSeq*, dots will be added at the end to indicate an infinite sequence.

\documentclass{article}
\usepackage{amsmath}
%\usepackage{xparse} % not needed with LaTeX 2020-10-01 or later

\ExplSyntaxOn

\NewDocumentCommand{\GenSeq}{smmm}
{% #1 = optional *
% #2 = template
% #3 = starting point
% #4 = end point
\pinkcollins_genseq:nnn { #2 } { #3 } { #4 }
\IfBooleanT{#1}{,\dotsc}
}

\cs_new_protected:Nn \pinkcollins_genseq:nnn
{
% turn the template into a (temporary) function
\cs_set:Nn \__pinkcollins_genseq_temp:n { #1 }
% do the main work
\tl_if_eq:nnTF { #2 } { #3 }
{% if #2=#3, not much to do
\__pinkcollins_genseq_temp:n { #2 }
}
{% now the hard work
\__pinkcollins_genseq_do:nn { #2 } { #3 }
}
}

\cs_new_protected:Nn \__pinkcollins_genseq_do:nn
{% #1 = start point, #2 = end point
% first check whether #1 is an integer
% \-? = one optional minus sign
% [[:digit:]]+ = one or more digits
% \Z = up to the end of the input
\regex_match:nnTF { \-? [[:digit:]]+ \Z } { #1 }
{
\__pinkcollins_genseq_number:nn { #1 } { #2 }
}
{
\__pinkcollins_genseq_symbolic:nn { #1 } { #2 }
}
}

\cs_new_protected:Nn \__pinkcollins_genseq_number:nn
{% #1 = start point, #2 = end point
\tl_if_eq:enTF { \int_eval:n { #1 + 1 } } { #2 }
{
\__pinkcollins_genseq_temp:n { #1 },\__pinkcollins_genseq_temp:n { #2 }
}
{
\__pinkcollins_genseq_temp:n { #1 },
\__pinkcollins_genseq_temp:n { \int_eval:n { #1+1 } },
\tl_if_eq:enF { \int_eval:n { #1 + 2 } } { #2 } { \dots, }
\__pinkcollins_genseq_temp:n { #2 }
}
}
\prg_generate_conditional_variant:Nnn \tl_if_eq:nn { e } { T, F, TF }

\cs_new_protected:Nn \__pinkcollins_genseq_symbolic:nn
{% #1 = start point, #2 = end point
\__pinkcollins_genseq_temp:n { #1 },
\__pinkcollins_genseq_temp:n { #1+1 },
\dots,
\__pinkcollins_genseq_temp:n { #2 }
}

\ExplSyntaxOff

\begin{document}

$\GenSeq{f(#1)}{1}{n}$

$\GenSeq{f(#1)}{0}{n}$

$\GenSeq{f(#1)}{1}{1}$

$\GenSeq{f(#1)}{1}{2}$

$\GenSeq{f(#1)}{1}{3}$

$\GenSeq{f(#1)}{1}{4}$

$\GenSeq{f(#1)}{-2}{k}$

$\GenSeq{f(#1)}{k}{n}$

$\GenSeq*{\theta^{(#1)}}{s}{T}$

\end{document}


A different usage of the *-variant could be to make the sequence descending:

\documentclass{article}
\usepackage{amsmath}
%\usepackage{xparse} % not needed with LaTeX 2020-10-01 or later

\ExplSyntaxOn

\NewDocumentCommand{\GenSeq}{smmm}
{% #1 = optional * for reverse sequence
% #2 = template
% #3 = starting point
% #4 = end point
\IfBooleanTF{#1}
{
\cs_set:Nn \__pinkcollins_genseq_sign: { - }
}
{
\cs_set:Nn \__pinkcollins_genseq_sign: { + }
}
\pinkcollins_genseq:nnn { #2 } { #3 } { #4 }
}

\cs_new_protected:Nn \pinkcollins_genseq:nnn
{
% turn the template into a (temporary) function
\cs_set:Nn \__pinkcollins_genseq_temp:n { #1 }
% do the main work
\tl_if_eq:nnTF { #2 } { #3 }
{% if #2=#3, not much to do
\__pinkcollins_genseq_temp:n { #2 }
}
{% now the hard work
\__pinkcollins_genseq_do:nn { #2 } { #3 }
}
}

\cs_new_protected:Nn \__pinkcollins_genseq_do:nn
{% #1 = start point, #2 = end point
% first check whether #1 is an integer
% \-? = one optional minus sign
% [[:digit:]]+ = one or more digits
% \Z = up to the end of the input
\regex_match:nnTF { \-? [[:digit:]]+ \Z } { #1 }
{
\__pinkcollins_genseq_number:nn { #1 } { #2 }
}
{
\__pinkcollins_genseq_symbolic:nn { #1 } { #2 }
}
}

\cs_new_protected:Nn \__pinkcollins_genseq_number:nn
{% #1 = start point, #2 = end point
\tl_if_eq:enTF { \int_eval:n { #1 \__pinkcollins_genseq_sign: 1 } } { #2 }
{
\__pinkcollins_genseq_temp:n { #1 },\__pinkcollins_genseq_temp:n { #2 }
}
{
\__pinkcollins_genseq_temp:n { #1 },
\__pinkcollins_genseq_temp:n { \int_eval:n { #1\__pinkcollins_genseq_sign: 1 } },
\tl_if_eq:enF { \int_eval:n { #1 \__pinkcollins_genseq_sign: 2 } } { #2 } { \dots, }
\__pinkcollins_genseq_temp:n { #2 }
}
}
\prg_generate_conditional_variant:Nnn \tl_if_eq:nn { e } { T, F, TF }

\cs_new_protected:Nn \__pinkcollins_genseq_symbolic:nn
{% #1 = start point, #2 = end point
\__pinkcollins_genseq_temp:n { #1 },
\__pinkcollins_genseq_temp:n { #1\__pinkcollins_genseq_sign:1 },
\dots,
\__pinkcollins_genseq_temp:n { #2 }
}

\ExplSyntaxOff

\begin{document}

\textbf{Ascending}

$\GenSeq{f(#1)}{1}{n}$

$\GenSeq{f(#1)}{0}{n}$

$\GenSeq{f(#1)}{1}{1}$

$\GenSeq{f(#1)}{1}{2}$

$\GenSeq{f(#1)}{1}{3}$

$\GenSeq{f(#1)}{1}{4}$

$\GenSeq{f(#1)}{-2}{k}$

$\GenSeq{f(#1)}{k}{n}$

$\GenSeq{\theta^{(#1)}}{s}{T}$

\textbf{Descending}

$\GenSeq*{f(#1)}{n}{1}$

$\GenSeq*{f(#1)}{n}{0}$

$\GenSeq*{f(#1)}{1}{1}$

$\GenSeq*{f(#1)}{2}{1}$

$\GenSeq*{f(#1)}{3}{1}$

$\GenSeq*{f(#1)}{4}{1}$

$\GenSeq*{f(#1)}{k}{-2}$

$\GenSeq*{f(#1)}{k}{n}$

$\GenSeq*{\theta^{(#1)}}{s}{T}$

\end{document}


For the sake of having fun playing around with expl3 I wanted to do it with expl3.

But I ended up doing it with a mixture of expl3 and my own code:

• I use expl3-regex-code for checking if ⟨min index⟩—⁠(!)⁠ ⁠without expanding tokens of ⟨min index⟩⁠ ⁠(!)⁠—forms a sequence of at most one sign and some decimal digits and—if so—for incrementing and passing on to the replacement-routine the (incremented) value of ⟨min index⟩.
• I use my own code for replacing ⟨index⟩ within ⟨general term⟩.

⟨min index⟩ is not expanded for checking if it denotes/yields (only) a valid TeX-⟨number⟩-quantity. I reject the idea of such a check/test for the following reason: No testing-method for checking whether fully expanding ⟨min index⟩ yields only a valid TeX-⟨number⟩-quantity is known to me which is not flawed in some way and/or which imposes no restrictions on possible user-input. When attempting to implement an algorithm for such a test, then you are facing the halting-problem: At the time of expanding them, the tokens forming ⟨min index⟩ may form an arbitrary expansion-based algorithm. Having an algorithm check whether such an algorithm in the end yields a valid TeX-⟨number⟩-quantity implies having an algorithm check whether an other arbitrary algorithm terminates at all/terminates without error-messages. This is the halting-problem. Alan Turing proved in 1936 that it is not possible to implement an algorithm which for any arbitrary algorithm can "decide" whether that algorithm will ever terminate.

In the beginning I intended to do the replacing of ⟨index⟩ by means of expl3-routines also:

Part VII - The l3tl package - Token lists, section 3 Modifying token list variables of interface3.pdf (Released 2020-10-27) states:

\tl_replace_all:Nnn ⟨tl var⟩ {⟨old tokens⟩} {⟨new tokens⟩}

Replaces all occurrences of ⟨old tokens⟩ in the ⟨tl var⟩ with ⟨new tokens⟩. ⟨Old tokens⟩ cannot contain {, } or # (more precisely, explicit character tokens with category code 1 (begin-group) or 2 (end-group), and tokens with category code 6). As this function operates from left to right, the pattern ⟨old tokens⟩ may remain after the replacement (see \tl_remove_all:Nn for an example).

(You are told that category code 1 is "begin-group" and that category code 2 is "end-group". I wonder why you are not told that category code 6 is "parameter". ;-) )

I tried doing it with \tl_replace_all:Nnn.

But this failed because the statement is not true.

(You can test yourself:

In the example below not all occurrences of u get replaced by d:

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\tl_set:Nn \l_tmpa_tl {uu{uu}uu{uu}}
\tl_replace_all:Nnn \l_tmpa_tl {u} {d}
\tl_show:N \l_tmpa_tl
\stop


⟨old tokens⟩ is u.
⟨new tokens⟩ is d.
All restrictions for ⟨old tokens⟩ and ⟨new tokens⟩ are obeyed.

Console output is:

\l_tmpa_tl=dd{uu}dd{uu}.


It seems that only occurrences not nested between a pair of matching explicit character tokens with category code 1 (begin-group) respective 2 (end-group) are replaced.

So the statement of all occurrences being replaced is wrong.

If the statement was correct, then console output would be:

\l_tmpa_tl=dd{dd}dd{dd}.


)

So I decided to write my own replacement-routine \ReplaceAllIndexOcurrences from scratch, without expl3.

As a side-effect \ReplaceAllIndexOcurrences does replace all explicit character tokens of category code 1 by {1 and all explicit character tokens of category code 2 by }2.

\documentclass[landscape, a4paper]{article}

\csname @ifundefined\endcsname{pagewidth}{}{\pagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pdfpagewidth}{}{\pdfpagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pageheight}{}{\pageheight=\paperheight}%
\csname @ifundefined\endcsname{pdfpageheight}{}{\pdfpageheight=\paperheight}%
\textwidth=\paperwidth
\oddsidemargin=1.5cm
\marginparsep=.2\oddsidemargin
\marginparwidth=\oddsidemargin
\evensidemargin=\oddsidemargin
\textheight=\paperheight
\topmargin=1.5cm
\footskip=.5\topmargin
\pagestyle{plain}
\parindent=0ex
\parskip=0ex
\topsep=0ex
\partopsep=0ex

\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand\GenSeq{mmmm}{
\group_begin:
% #1 = general term
% #2 = index
% #3 = min index
% #4 = max index
\sqrt{%
\vphantom{(}%
ParenthesesIfMoreThanOneUndelimitedArgument{\Weird\Woozles}\cdot\ParenthesesIfMoreThanOneUndelimitedArgument{\Weird\Woozles}%
\vphantom{)}%
}%
}%
{\Weird\Woozles}%
{s}%
{T}$\end{verbatim} yields:$\GenSeq{%
\sqrt{%
\vphantom{(}%
\ParenthesesIfMoreThanOneUndelimitedArgument{\Weird\Woozles}\cdot\ParenthesesIfMoreThanOneUndelimitedArgument{\Weird\Woozles}%
\vphantom{)}%
}%
}%
{\Weird\Woozles}%
{s}%
{T}$\vfill Let's use the explicit space token as \textit{$\langle$index$\rangle$}---\verb|$\GenSeq{f( )}{ }{k}{n}$| yields:$\GenSeq{f( )}{ }{k}{n}$\vfill Let's use the explicit space token as \textit{$\langle$index$\rangle$}---\verb|$\GenSeq{f( )}{ }{-5}{n}$| yields:$\GenSeq{f( )}{ }{-5}{n}\$

\vfill\vfill

\end{document}