11

While writing a document with many sequences of numbers (and various lower and upper boundaries), I would like to define a command such as \myscale{1}{6} that would produce:

1  2  3  4  5  6 

What I am wondering is how to create the while loop that would write numbers until the value given by the second argument has been reached... Any idea ?

12

With no package, only classical TeX:

\newcount\tmpnum
\def\myscale#1#2{%
   \tmpnum=#1 \the\tmpnum
   \loop
      \ifnum\tmpnum<#2
         \advance\tmpnum by1
         \quad\the\tmpnum
      \repeat
}

Using eTeX extension and axpandable solution:

\def\myscale#1#2{#1%
   \ifnum#1<#2 \space \expandafter\myscale\expandafter{\the\numexpr#1+1\relax}{#2}\fi
}

Edit: The third version adds the features described by jfbu in the comment below. I.e. the parameters are evaluated first and the nested \if...\fi is solved.

\def\myscale#1#2{\expandafter\myscaleA\expandafter{\the\numexpr#1\expandafter}%
                                      \expandafter{\the\numexpr#2}% evaluate params
}
\def\myscaleA#1#2{#1\ifnum#1<#2 \space \myscaleB{\the\numexpr#1+1\relax}{#2}\fi}
\def\myscaleB#1\fi{\fi \expandafter\myscaleA\expandafter#1}
  • Compare egreg's solution with this. It is typical that LaTeX users generate overcomplicated codes. And such overcomplicated code is very often in LaTeX packages. – wipet Mar 23 '15 at 8:16
  • 3
    @wipet egreg's solution accounts for more possibilities; and yours does not either accepts \count's without adding extra spaces in the output; and your \numexpr based solution does not allow expressions in #2 like 2*6 and furthermore you nest many \if...\fi which egreg's solution avoids, although I agree that his \@gobble/\@firstofone could be changed into something slightly faster (through putting all the load on the executed once final branch). And egreg is no typical LaTeX user. Typical LaTeX users do not generate code at all. – user4686 Mar 23 '15 at 10:34
  • 1
    @jfbu I think teasing each other a bit should be allowed no? Plus all the speed advantage you get out of plain TeX is lost while learning it :P – percusse Mar 23 '15 at 10:49
  • @wipet I realize that when you first posted your answer, the one of cmhughes which contained almost identical code (for the non expandable loop) had been deleted already (I don't know why). About your third version: for optimization of expansion, I would rather put the next iteration already after the \fi, and in the case test fails indicating termination, then gobble what's after the \fi (five tokens or {..} with the suitable \expandafter already there. Thus, you need a gobble five but spare the specialized \myscaleB. – user4686 Mar 23 '15 at 13:02
10

With no package (but requiring e-TeX), a fully expandable solution:

\documentclass{article}

\makeatletter
\newcommand{\myscale}[2]{%
  \ifnum#1=#2
    $#1$\expandafter\@gobble
  \else
    \expandafter\@firstofone
  \fi
  {\myscale@aux{#1}{#2}}%
}
\newcommand{\myscale@aux}[2]{%
  \ifnum#1>#2
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\myscale@neg{#1}{#2}}%
  {\myscale@pos{#1}{#2}}%
}
\newcommand{\myscale@neg}[2]{%
  $#1$\expandafter\myscale@neg@aux\expandafter{\the\numexpr#1-1}{#2}%
}
\newcommand{\myscale@neg@aux}[2]{%
  \ifnum#1<#2
    \expandafter\@gobble
  \else
    \space$#1$\expandafter\@firstofone
  \fi
  {\expandafter\myscale@neg@aux\expandafter{\the\numexpr#1-1}{#2}}%
}
\newcommand{\myscale@pos}[2]{%
  $#1$\expandafter\myscale@pos@aux\expandafter{\the\numexpr#1+1}{#2}%
}
\newcommand{\myscale@pos@aux}[2]{%
  \ifnum#1>#2
    \expandafter\@gobble
  \else
    \space$#1$\expandafter\@firstofone
  \fi
  {\expandafter\myscale@pos@aux\expandafter{\the\numexpr#1+1}{#2}}%
}
\makeatother

\begin{document}

\myscale{1}{6}

\myscale{1}{1}

\myscale{6}{1}

\myscale{1}{-1}

\edef\temp{\myscale{1}{6}}

\texttt{\meaning\temp}

\end{document}

enter image description here

With optional arguments for different formatting (but not expandable, of course). The first optional argument to \myscale is the separator (default a space); the second argument is the separator between the last two items (default the same as the first argument); if specified, the third argument will be used between the last two of many items, the second if there are just two items (for the Oxford comma in the example).

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\myscale}{O{~}oomm}
 {
  \IfNoValueTF{#2}
   {
    \alain_myscale:nnnnn { #1 } { #1 } { #1 } { #4 } { #5 }
   }
   {
    \IfNoValueTF{#3}
     { \alain_myscale:nnnnn { #2 } { #1 } { #2 } { #4 } { #5 } }
     { \alain_myscale:nnnnn { #2 } { #1 } { #3 } { #4 } { #5 } }
   }
 }

\seq_new:N \l_alain_myscale_seq

\cs_new_protected:Nn \alain_myscale:nnnnn
 {
  \seq_clear:N \l_alain_myscale_seq
  \int_step_inline:nnnn { #4 } { \int_compare:nTF { #4 > #5 } { -1 } { 1 } } { #5 }
   {
    \seq_put_right:Nn \l_alain_myscale_seq { $##1$ }
   }
  \seq_use:Nnnn \l_alain_myscale_seq { #1 } { #2 } { #3 }
 }
\ExplSyntaxOff

\begin{document}

\myscale{1}{6}

\myscale[, ]{1}{6}

\myscale[, ][ and ]{1}{6}

\myscale[, ][ and ][, and ]{1}{6}

\myscale{1}{1}

\myscale{6}{1}

\myscale{1}{-1}

\end{document}

enter image description here

  • Ooooh I knew there had to be something like \int_step_inline:nnnn – wasn't expecting it to be named that, though :) – Sean Allred Mar 23 '15 at 0:36
  • @egreg in your first approach one can not use the macro with count variables (extra spaces) or with things like \myscale{1+3}{2*8}. For this reason, \xintSeq (from package xinttools expands first the #1 and #2 within a \numexpr. Furthermore it allows an optional argument while maintaining expandability. – user4686 Mar 23 '15 at 6:52
  • @egreg about expandability (of which I am a big fan) as mentioned in my last comment: naturally it doesn't make much sense if the macro output is directly for typesetting. Only one exception comes to mind: constructing tabular rows from inside a tabular. – user4686 Mar 23 '15 at 7:14
8

Here's a LuaLaTeX-based solution. The two arguments of \myscale can be any expressions that evaluate to numbers under TeX's and Lua's combined rules. If the macro's second argument is smaller than the first, nothing is printed.

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\newcommand{\myscale}[2]{\directlua{%
     for i = #1, #2 do tex.print(i) end}}
\newcommand\myvar{1}
\begin{document}
\myscale{1}{6}            % integers from 1 to 6

\myscale{1+\myvar}{2*2^3} % integers from 2 to 16

\myscale{1}{0}            % nothing is printed
\end{document}

Addendum: If decreasing arithmetic sequences are to be allowed (in which case the second argument of \myscale is smaller than the first), it's necessary to set the optional third argument of Lua's for loop -- which controls the step size of the increments -- to -1.

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\newcommand\myscale[2]{\directlua{%
         local incr = 1
         if #1 > #2 then incr = -1 end % set to -1 if decreasing sequence
         for i = #1, #2, incr do tex.print(i) end}}
\newcommand\myvar{1}  % a dummy macro
\begin{document}
\myscale{1}{6}

\myscale{math.log(1)}{2^3-\myvar}

\myscale{\myvar+2^math.exp(0)}{math.log(\myvar)}
\end{document}
  • 2
    I just… I just feel like this is cheating :) – Sean Allred Mar 23 '15 at 0:37
  • @SeanAllred - I'm not sure I understand. Are you saying this is just too easy? Well, it's what a "real" programming language -- specifically, one with a built-in for loop -- is rather good at doing... – Mico Mar 23 '15 at 0:49
  • 2
    *pouts* It takes all the fun out of it :) – Sean Allred Mar 23 '15 at 0:51
  • 2
    (To be clear, LuaTeX is really neat and a solution based thereupon should be listed here as well – if only to open a reader up to the possibilities.) – Sean Allred Mar 23 '15 at 0:56
7

interesting question, you can do this :

\documentclass{article}
\usepackage{pgffor}
\newcommand{\myscale}[2]{%
  \foreach \index in {#1,...,#2}{\index\space\space}%
}
\begin{document}
\myscale{1}{6}

\myscale{9}{17}
\end{document}
  • 2
    Welcome to the site! Does this example need any packages? It's best to post complete minimum working examples in these cases to avoid ambiguity :) – cmhughes Mar 22 '15 at 20:29
  • It can also display the ordered number of negative to positive numbers, `\myscale{-5}{5}. How to sort numbers from large to smaller? – Edy Jo Mar 22 '15 at 21:47
7

Using \whiledo of package ifthen:

MWE

    \documentclass{article}
    \usepackage{ifthen}    
    \newcounter{mycount}
    \newcommand{\myscale}[2]{
    \setcounter{mycount}{#1}
    \whiledo{\value{mycount}<#2}
    {\arabic{mycount}, \stepcounter{mycount}}%
    \arabic{mycount}.}
    \begin{document}
    \myscale{1}{6}\par
    \myscale{-4}{4}
    \end{document}

Or with the forloop package:

\documentclass{article}
\usepackage{forloop}    
\newcounter{mycount}
\newcommand{\myscale}[2]{
\setcounter{mycount}{#1}
\forloop{mycount}{#1}{\value{mycount} < #2}%
{\arabic{mycount}, }%
\arabic{mycount}.}
\begin{document}
\myscale{1}{6}\par
\myscale{-4}{4}
\end{document}
6

Just for kicks, with expl3/xparse:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\cs_new_protected:Npn \alain_range:nnN #1#2#3 {
  \seq_clear:N #3
  \int_set:Nn \l_tmpa_int { #1 }
  \int_set:Nn \l_tmpb_int { #2 }
  \int_while_do:nNnn \l_tmpa_int < \l_tmpb_int {
    \seq_push:NV #3 \l_tmpb_int
    \int_decr:N \l_tmpb_int
  }
  \seq_push:NV #3 \l_tmpb_int
}

\NewDocumentCommand \myscale {O{,~}m} {
  \seq_set_split:Nnn \l_tmpa_seq {-} {#2}
  \seq_pop:NN \l_tmpa_seq \l_tmpa_tl
  \seq_pop:NN \l_tmpa_seq \l_tmpb_tl
  \alain_range:nnN \l_tmpa_tl \l_tmpb_tl \l_tmpa_seq
  \seq_use:Nn \l_tmpa_seq {#1}
}

\ExplSyntaxOff

\begin{document}
\myscale{3-6}

\myscale[ ]{3-6}

\myscale[ and ]{1-4}
\end{document}
  • 2
    It should be protected rather than nopar. At least the recommendation is to leave nopar only for functions without arguments; at the same time, using protected except when the command is fully expandable. – Manuel Mar 22 '15 at 23:35
  • @Manuel I'll have to trust you on that until I find the time to re-read the documentation :) I'm still not entirely sure what 'protected' means! – Sean Allred Mar 22 '15 at 23:36
  • l3styleguide, section 4 Code conventions, first and second paragraph. – Manuel Mar 22 '15 at 23:42
  • 1
    @SeanAllred Challenge accepted. ;-) – egreg Mar 22 '15 at 23:55
  • @egreg I was hoping I could prompt you for some more expl3 magic! :D – Sean Allred Mar 23 '15 at 0:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.