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.

2

5 Answers 5

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.)

2
  • I've answered on the basis of fixing the code in the question: I'd probably go with an expandable approach like the one suggested by John Kormylo in real use.
    – Joseph Wright
    Sep 2, 2014 at 15:01
  • @barto No: the use of the etoolbox conditionals is not necessarily obvious, so I think it's quite reasonable to ask.
    – Joseph Wright
    Sep 2, 2014 at 15:02
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}
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.

7
  • 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? Sep 2, 2014 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 2, 2014 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. Sep 2, 2014 at 16:54
  • @barto I think that the \whileboolexpr method is comparable to the faster ones.
    – egreg
    Sep 2, 2014 at 18:07
  • 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} } }. Sep 3, 2014 at 10:27
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.

8
  • 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 2, 2014 at 15:33
  • @egreg How big is the stack size? 256?? Sep 2, 2014 at 16:28
  • TeX Live sets it to 5000.
    – egreg
    Sep 2, 2014 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. Sep 2, 2014 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 2, 2014 at 17:33
4

ConTeXt solution

\def\repeatstring#1#2{\edef\concathere{\dorecurse{#1}{#2}}}
\starttext
\repeatstring{5}{abc}\concathere
\stoptext 

You must log in to answer this question.

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