21

I wanted to create a new command, which allows me to repeat any characters I want for n times. After some searching and trying, I came up with this:

\usepackage{multido}
\newcommand{\myrepeat}[2]{%
    \newcount\iterations%
    \iterations #1%
    \advance\iterations -1
    \multido{\iN=0+1}{\iterations}{#2\ }#2%
}

There was some weird spacing after the command in the resulting PDF, so I added the comment symbols % and then it disappeared.

My question is: Is there a better way of doing this, which is as easy to understand as this one, preferably not introducing many dependencies?

One without multido is fine too, if it's not too complicated, or if you can explain it, so that it's not complicated anymore. I guess using multido for such a thing is ok, since its name means to do something multiple times, but I am not sure I took the easiest and cleanest way of doing it.

Note that my solutions adds one less space than it adds items to the pdf. The way of substracting one seems suspiciously verbose to me.

I've got two versions working now:

The one from @egreg modified:

\makeatletter
\newcount\my@repeat@count% initialize a new counter for the loop
\newcommand{\myrepeat}[3]{% new command with 2 arguments
    \begingroup% ???
    \my@repeat@count=1% initialize at 1, so that there are argument - 1 iterations and the last iterations doesn't have a separator following it
    \@whilenum\my@repeat@count<#1\do{#2#3\advance\my@repeat@count1}#2% as long as the iteration count is smaller than the argument, advance, meaning that the counter will be increased by 1
    \endgroup% ???
}
\makeatother

\newcommand{\mediumgap}{%
    \myrepeat{5}{.....}{\ }
}

The one from @christian modified:

\newcount\myloopcounter
\newcommand{\repeatit}[3][10]{%
    \myloopcounter1% initialize the loop counter
    \loop\ifnum\myloopcounter < #1
    #2#3%
    \advance\myloopcounter by 1%
    \repeat% start again
    #2%
}
\newcommand{\longgap}{%
    \repeatit[5]{.....}{\ }
}

I don't know if one has any advantage over the other. Maybe there is also a better way of removing the last whitespace or separation character, instead of doing one less iteration and writing only the charactersto repeat again without the separator. I introduced the separator, because I thought it could be useful and it's just a third parameter.

8
  • Possible duplicate: Repeat command n times?
    – Werner
    Mar 21, 2016 at 21:36
  • The above answers your question as it shows a number of ways of doing what you ask without any reference to which "better", "easier" or "cleaner." And there are tricks to avoid you printing a space (or whatever) at the end of the list...
    – Werner
    Mar 21, 2016 at 21:38
  • 1
    you should not have the % after -1 otherwise \multido wlil start expanding before the \advance is finished. (it probably accidentally works in this case but...) Mar 21, 2016 at 21:39
  • @DavidCarlisle Removed the % after -1, still works, thanks! Although I don't understand in which cases it might be a problem. Can you explain? @Werner I saw that one, but didn't find an acceptable answer, and went on searching. Inacceptable: PGF/TikZ dependency, expl3 looks like manually writing a loop for only 12 cases, \romannumeral solution thingy I don't understand what's going on there, \numexpr solution thingy looks complicated too and I don't understand it as no explanation in detail was given. Note: Still a beginner, this is one of the first commands I define. Mar 21, 2016 at 21:47
  • 2
    @Zelphir: You shouldn't use a \newcount in the command, since this allocates a register each time the loop command is used
    – user31729
    Mar 21, 2016 at 22:00

4 Answers 4

31

No packages:

\documentclass{article}

\makeatletter
\newcount\my@repeat@count
\newcommand{\myrepeat}[2]{%
  \begingroup
  \my@repeat@count=\z@
  \@whilenum\my@repeat@count<#1\do{#2\advance\my@repeat@count\@ne}%
  \endgroup
}
\makeatother

\begin{document}

\myrepeat{4}{x}

\myrepeat{4}{\myrepeat{2}{x}}

\end{document}

Why the group? It allows nested calls.

enter image description here

If you just want to repeat a character:

\documentclass{article}

\newcommand\myrepeat[2]{%
  \begingroup
  \lccode`m=`#2\relax
  \lowercase\expandafter{\romannumeral#1000}%
  \endgroup
}

\begin{document}

\myrepeat{4}{x}

\end{document}

As Ulrich Diez rightly comments, this code would not allow a counter register as number of repetitions; in order to support this, a slightly more complicated version is

\newcommand\myrepeat[2]{%
  \begingroup
  \lccode`m=`#2\relax
  \lowercase\expandafter{\romannumeral\number\number#1 000}%
  \endgroup
}

\romannumeral triggers the first \number which expands the second one; if we have, say, 4 as #1 we get successively (where denotes a space token

\romannumeral\number\number4•000
\romannumeral\number4000
\romannumeral4000
mmmm

If, instead, we have \count27 as #1 and \count27 holds the value 4, we get

\romannumeral\number\number\count27•000
\romannumeral\number4000
\romannumeral4000
mmmm

If we have \foo (a named counter register), again holding the value 4, we have

\romannumeral\number\number\foo•000
\romannumeral\number4•000
\romannumeral4000
mmmm

Therefore two instances of \number are necessary, in order to cover the third case. This exploits the fact that a space ends the search for an “explicit” number and is then ignored (the space token is not ignored in the third case, after the expansion of the second \number token).

If you also want a text to put in between the repetitions, the first approach is very simple: just start the loop at 1.

\makeatletter
\newcount\my@repeat@count
\newcommand{\myrepeat}[3]{%
  % #1 = number of repetition
  % #2 = text to repeat
  % #3 = text in between
  \begingroup
  #2
  \my@repeat@count=\@ne
  \@whilenum\my@repeat@count<#1\do{#2#3\advance\my@repeat@count\@ne}%
  \endgroup
}
\makeatother

(don't call this with zero repetitions).

A different solution with a recursion and the old idea of \romannumeral#1000 that produces a long string of m's that we can consume one at a time.

\documentclass{article}

\makeatletter
\newcommand\myrepeat[3]{%
  % #1 is the number of repetitions
  % #2 is the code to repeat
  % #3 is the code to put in the middle
  \expandafter\myrepeat@aux\expandafter{\romannumeral\number\number#1 000}{#2}{#3}%
}
\newcommand{\myrepeat@aux}[3]{\myrepeat@auxi{#2}{#3}#1;;}
\def\myrepeat@auxi#1#2#3#4{%
  \ifx#3;%
    \expandafter\@gobble % recursion has ended
  \else
    \expandafter\@firstofone % still one m to swallow
  \fi
  {\myrepeat@auxii{#1}{#2}{#4}}%
}
\def\myrepeat@auxii#1#2#3{%
  #1\ifx#3;\else#2\fi
  \myrepeat@auxi{#1}{#2}#3% restart the recursion
}
\makeatletter

\begin{document}

\myrepeat{4}{x}{-}

\myrepeat{1}{x}{-}

X\myrepeat{0}{x}{-}X

\end{document}

enter image description here

The recursion consumes two tokens at a time, in order to distinguish whether it has only one more step to do. The second token is put back when the recursion restarts.


However, this is just for study. In a real world application I'd do

\usepackage{xparse}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\myrepeat}{O{}mm}
 {
  \int_compare:nT { #2 > 0 }
   {
    #3 \prg_replicate:nn { #2 - 1 } { #1#3 }
   }
 }
\ExplSyntaxOff

to be called like \myrepeat[-]{4}{x} to get

x-x-x-x

and \myrepeat{3}{A} to get

AAA

7
  • I think I'll go with the first variant. However, I have some questions: Is there an advantage to using \@ne instead of just 1? and also, what is \z@ ? Zero? If so, why can't I simply write 0 instead? Why do I have to do the \begingroup thing? What does that do? Also for the second variant: What does this all have to do with roman numbers? Mar 22, 2016 at 4:08
  • Oh and how do I insert some character in between the iteration items, but not at the very end? For example: "X-X-X-X-X" but not "X-X-X-X-X-" Mar 22, 2016 at 4:18
  • @Zelphir See tex.stackexchange.com/questions/55400/what-does-z-do for \z@. I added some variations on the theme.
    – egreg
    Mar 22, 2016 at 7:46
  • Even more variations : ) So what would be the reason for choosing the one using \ExplSyntaxOn and xparse over the others? Mar 22, 2016 at 12:39
  • 1
    @raffamaiden Calling `\myrepeat{100}{A\hskip{0pt}} would, but expect underfull boxes.
    – egreg
    Oct 28, 2018 at 13:42
20

Here's a version without any package, just using plain \loop and \repeat etc.

\documentclass{article}

\newcount\myloopcounter

\newcommand{\repeatit}[2][10]{%
  \myloopcounter0% initialize the loop counter
  \loop\ifnum\myloopcounter < #1 % Test if the loop counter is < #1
  #2%
  \advance\myloopcounter by 1 % 
  \repeat % start again
}

\begin{document}

\repeatit[5]{A}


\repeatit[20]{And now for something completely different\par}

\end{document}

enter image description here

4

FWIW, in ConTeXt you can use \dorecurse to repeat something. For example:

\dorecurse{10}{Hello world. }

will print Hello world. 10 times. The current iteration number is stored in the macro \recurselevel.

2
  • How would you prevent the last whitespace though? Mar 22, 2016 at 4:19
  • Add a \removeunwantedspaces afterwards: \dorecurse{10}{Hello World. }\removeunwantedspaces. This can be wrapped in a macro, if you like.
    – Aditya
    Mar 22, 2016 at 4:25
1

In OpTeX, you can use \fornum macro:

\def\myrepeat#1#2{\fornum 1..#1\do {#2}}

\myrepeat{4}{x}

\myrepeat{4}{\myrepeat{2}{x}}

\message{\myrepeat{4}{\myrepeat{2}{x}}} % prints xxxxxxxx on the terminal.

\bye

The \fornum macro is fully expandable, you can use nested loops without opening or closing group.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .