7

I'm trying to define a command \repeatstring{#1}{#2} which repeats the string #2 #1 times. Here is what I tried: (it is based on what I found in How to concatenate strings into a single command?)

\documentclass[11pt, a4paper]{article}
\usepackage{etoolbox}
\newcounter{countdown}
\newcommand\concathere{}
\newcommand\repeatstring[2]{
    \setcounter{countdown}{#1}
    \renewcommand\concathere{}
    \whileboolexpr
        {test {\ifnumcomp{\thecountdown}{>}{0}{true}{false}}}
        {
        \addtocounter{countdown}{-1}
        \appto\concathere{#2}          % here the concatenation happens
        }
    \concathere
    }
\begin{document}
\repeatstring{5}{abc}
\end{document}

\concathere is meant to contain the string the desired number of times. When I run \repeatstring{5}{abc} the output is simply "true". Nothing more, nothing less. Interestingly, when I replace the final line of my command by aaa\concathere bbb the result is "true aaabbb". This should indicate where things go wrong, but I can't figure it out. Probably it simply prints "true" because 5>0, and then stops for some reason.

Bart Michels
  • 1,298
  • 2
  • 13
  • 24
  • please complete your snippet to a full but minimal compilable document. Like this, it is hard to help you and we need to type all the basic stuff for you. Thank you. – LaRiFaRi Sep 02 '14 at 14:54
  • 1
    See http://tex.stackexchange.com/questions/141920/how-to-repeat-text – John Kormylo Sep 02 '14 at 14:59

5 Answers5

8

There are various ways to accomplish your needs.

\documentclass[11pt]{article}
\usepackage{xparse} % for D

% for A, B, C
\newcounter{mycount}

\makeatletter
\newcommand\repeatstringA[2]{%
  \setcounter{mycount}{#1}%
  \ifnum\themycount>0
    #2%
    \addtocounter{mycount}{-1}%
    \expandafter\@firstofone
  \else
    \expandafter\@gobble
  \fi
  {\repeatstringA{\themycount}{#2}}%
}

\newcommand\repeatstringB[2]{%
  \setcounter{mycount}{#1}%
  \@whilenum{\value{mycount}>0}\do{#2\addtocounter{mycount}{-1}}%
}

\newcommand\repeatstringC[2]{%
  \ifnum#1>0
    \expandafter\@firstofone
  \else
    \expandafter\@gobble
  \fi
  {#2\expandafter\repeatstringC\expandafter{\the\numexpr#1-1\relax}{#2}}%
}
\makeatother

\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\repeatstringD}{mm}
 {
  \prg_replicate:nn { #1 } { #2 }
 }
\ExplSyntaxOff

\begin{document}
\repeatstringA{5}{abc}

\repeatstringB{5}{abc}

\repeatstringC{5}{abc}

\repeatstringD{3*2-1}{abc}
\end{document}

The last one is particularly appealing. The first two are essentially equivalent.

enter image description here

If you want to produce a control sequence containing the repetitions, then the changes are easy:

\documentclass[11pt]{article}
\usepackage{xparse} % for D

% for A, B, C
\newcounter{mycount}

% a container
\newcommand{\concathere}{}

\makeatletter
\newcommand\repeatstringA[2]{%
  \renewcommand{\concathere}{}%
  \setcounter{mycount}{#1}%
  \ifnum\themycount>0
    \expandafter\def\expandafter\concathere\expandafter{\concathere #2}%
    \addtocounter{mycount}{-1}%
    \expandafter\@firstofone
  \else
    \expandafter\@gobble
  \fi
  {\repeatstringA{\themycount}{#2}}%
}

\newcommand\repeatstringB[2]{%
  \setcounter{mycount}{#1}%
  \@whilenum{\value{mycount}>0}\do{%
     \expandafter\def\expandafter\concathere\expandafter{\concathere #2}%
     \addtocounter{mycount}{-1}%
  }%
}

\newcommand\repeatstringC[2]{%
  \ifnum#1>0
    \expandafter\@firstofone
  \else
    \expandafter\@gobble
  \fi
  {%
   \expandafter\def\expandafter\concathere\expandafter{\concathere #2}%
   \expandafter\repeatstringC\expandafter{\the\numexpr#1-1\relax}{#2}%
  }%
}
\makeatother

\ExplSyntaxOn
\NewDocumentCommand{\repeatstringD}{mm}
 {
  %\tl_clear:N \concathere
  %\prg_replicate:nn { #1 } { \tl_put_right:Nn \concathere { #2 } }
  % a faster method suggested by Bruno Le Floch
  \tl_set:Nx \concathere { \prg_replicate:nn { #1 } { \exp_not:n { #2 } } }
 }
\ExplSyntaxOff

\begin{document}
\repeatstringA{5}{abc}\concathere

\repeatstringB{5}{abc}\concathere

\repeatstringC{5}{abc}\concathere

\repeatstringD{3*2-1}{abc}\concathere
\end{document}

At the end of the execution of \repeatstringX{5}{abc} the macro \concathere will contain abcabcabcabcabc.

Instead of

\expandafter\def\expandafter\concathere\expandafter{\concathere #2}

(where \def is used for efficiency), you can of course use

\appto\concathere{#2}

provided you have loaded etoolbox.

None of these methods will overflow the input stack size; however, big numbers may overflow other parts of the memory.

egreg
  • 1,121,712
  • I'm just wondering, as I think you have quite some insight in all these methods: Is there a reason to prefer one of your solutions above mine? (I guess there is.) Which one would be the fastest? – Bart Michels Sep 02 '14 at 16:23
  • 1
    @barto A quick test shows that the first is the slowest, while the other three have small differences. I tested 10000 repetitions with the string abc. – egreg Sep 02 '14 at 16:38
  • Waw, thank you very much! I'll take the second one because it seems easiest to understand (though I tink I can figure out what the others do too). I keep Joseph Wright's answer as the accepted because I think it is my duty to accept the answer which adresses my actual question, but you get +1 for the good advice. – Bart Michels Sep 02 '14 at 16:54
  • @barto I think that the \whileboolexpr method is comparable to the faster ones. – egreg Sep 02 '14 at 18:07
  • At least the last one, isn't it faster with \tl_set:Nx \concathere { \prg_replicate:nn { #1 } { #2 } }? I don't know how do you measure the time of compilation, but it seems (easy to have errors :D) to compile faster. – Manuel Sep 02 '14 at 18:22
  • @Manuel With the macro, the fastest is the first, while the other three are the same. – egreg Sep 02 '14 at 19:51
  • 1
    @egreg I took the liberty to fix your last edit (you made the wrong command expandable). I think what Manuel meant is that it is much faster to set \concathere in one x-expanding assignment rather than appending to it multiple times. The correct way is \tl_set:Nx \concathere { \prg_replicate:nn {#1} { \exp_not:n {#2} } }. – Bruno Le Floch Sep 03 '14 at 10:27
8

Repetition via \romannumeral trick

The number is multiplied with 1000 to convert it to a roman number. Then TeX produces a long string consisting of letter m, whose length is the original number.

Then \repeatstringX looks at the next token, if it is an m, then the string unit is output. Otherwise the next token is the end marker F and the loop stops.

\documentclass[11pt, a4paper]{article}

\newcommand\repeatstring[2]{%
  \def\tempParam{#2}%
  \expandafter\repeatstringX\romannumeral\the\numexpr(#1)\relax000 F%
}
\newcommand*{\repeatstringX}[1]{%
  \csname repeatstring#1\endcsname
}
\newcommand*{\repeatstringm}{%
  \tempParam
  \repeatstringX
}
\newcommand*{\repeatstringF}{}

\begin{document}
  [\repeatstring{5}{abc}]
\end{document}

Result

Fixing the MWE

  • If \ifnumcomp is used inside the expression for test, then the arguments for true and false are omitted, see the description of test in the documentation of etoolbox.

  • There are many unwanted white spaces, caused by the end of lines in the definition of \repeatstring.

Fixed MWE:

\documentclass[11pt, a4paper]{article}
\usepackage{etoolbox}
\newcounter{countdown}
\newcommand\concathere{}
\newcommand\repeatstring[2]{%
  \setcounter{countdown}{#1}%
  \renewcommand\concathere{}%
  \whileboolexpr
     {test {\ifnumcomp{\thecountdown}{>}{0}}}
     {%
       \addtocounter{countdown}{-1}%
       \appto\concathere{#2}% here the concatenation happens
     }%
  \concathere
}
\begin{document}
  [\repeatstring{5}{abc}]
\end{document}
Heiko Oberdiek
  • 271,626
5

You are misunderstanding how the logical <expression> should be given. The result should be 'logically true', not the text true:

\documentclass{article}

\usepackage{etoolbox}
\newcounter{countdown}
\newcommand\concathere{}
\newcommand\repeatstring[2]{%
    \setcounter{countdown}{#1}%
    \renewcommand\concathere{}%
    \whileboolexpr
        {test {\ifnumcomp{\thecountdown}{>}{0}}}% 
        {%
        \addtocounter{countdown}{-1}%
        \appto\concathere{#2}% here the concatenation happens
        }%
    \concathere
    }
\begin{document}
\repeatstring{5}{abc}

\end{document}

Notice that in the expression part I've just got something that will give a logical result. There are a few examples in the etoolbox manual: see page 21 for example.

(I've prevented spurious spaces appearing in the result by adding appropriate % at the end of lines: not relevant to the issue but important for real use.)

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
4

Here, I set up a recursive loop. Works with macros, too.

\documentclass[11pt]{article}
\newcounter{mycount}
\def\repeatstring#1#2{%
  \setcounter{mycount}{#1}%
  \ifnum\value{mycount}>0\relax#2%
    \addtocounter{mycount}{-1}%
    \repeatstring{\value{mycount}}{#2}%
  \fi%
}
\begin{document}
\repeatstring{5}{abc}

\repeatstring{3}{\today}
\end{document}

enter image description here

As egreg points out in my comments, the above method will fail if the stack size (5000) is exceeded by the repeat count. He also provides the remedy, which in the context of my MWE, can be achieved in the following way (by expanding the \fi before proceeding to the next recursion):

\documentclass[11pt]{article}
\newcounter{mycount}
\def\repeatstring#1#2{%
  \setcounter{mycount}{#1}%
  \ifnum\value{mycount}>0\relax#2%
    \addtocounter{mycount}{-1}%
    \def\tmp{\repeatstring{\value{mycount}}{#2}}%
    \expandafter\tmp%
  \fi%
}
\begin{document}
\repeatstring{5}{abc}

\repeatstring{3}{\today}

\repeatstring{5555}{i$\!\!$ }
\end{document}

Answer EDITED to replace \themycount with \value{mycount}, per egreg's recommendation.

  • 2
    This will overflow the memory as soon as the number of repetitions is bigger than the stack size. It's much better to use tail recursion. – egreg Sep 02 '14 at 15:33
  • @egreg How big is the stack size? 256?? – Steven B. Segletes Sep 02 '14 at 16:28
  • TeX Live sets it to 5000. – egreg Sep 02 '14 at 16:32
  • @egreg Well, it is good to know that limitation... however, at 5000, the problem sounds pretty "theoretical" to me. One could always check #1 and warn accordingly. – Steven B. Segletes Sep 02 '14 at 17:01
  • 3
    As you see from my answer, it's easy to avoid the problem: expand the \fi before executing the next step. – egreg Sep 02 '14 at 17:33
  • \value{mycount}, not \themycount. There's a big difference. – egreg Sep 02 '14 at 17:51
  • @egreg Thanks again. Is that because \themycount could, under certain conditions, end up in roman numerals, for example? – Steven B. Segletes Sep 02 '14 at 17:58
  • Yes, that's the reason. You want to use the counter's value, not its representation. It's the difference between “the number ten” and 10: the former is an abstract object, the latter one of its possible representations. – egreg Sep 02 '14 at 18:09
4

ConTeXt solution

\def\repeatstring#1#2{\edef\concathere{\dorecurse{#1}{#2}}}
\starttext
\repeatstring{5}{abc}\concathere
\stoptext 
Manuel
  • 27,118