Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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.

share|improve this question
    
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 2 at 14:54
1  

5 Answers 5

up vote 5 down vote accepted

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

share|improve this answer
    
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 at 15:01
    
Thanks. I guess my question is a bit useless now. –  barto Sep 2 at 15:02
    
@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 at 15:02

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.

share|improve this answer
    
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? –  barto Sep 2 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 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. –  barto Sep 2 at 16:54
    
@barto I think that the \whileboolexpr method is comparable to the faster ones. –  egreg Sep 2 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} } }. –  Bruno Le Floch Sep 3 at 10:27

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}
share|improve this answer

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.

share|improve this answer
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 at 15:33
    
@egreg How big is the stack size? 256?? –  Steven B. Segletes Sep 2 at 16:28
    
TeX Live sets it to 5000. –  egreg Sep 2 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 2 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 at 17:33

ConTeXt solution

\def\repeatstring#1#2{\edef\concathere{\dorecurse{#1}{#2}}}
\starttext
\repeatstring{5}{abc}\concathere
\stoptext 
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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