views:

5817

answers:

11

I'm looking for a simple commons method or operator that allows me to repeat some String n times. I know I could write this using a for loop, but I wish to avoid for loops whenever necessary and a simple direct method should exist somewhere.

String str = "abc";
String repeated = str.repeat(3);

repeated.equals("abcabcabc");

Related to:

repeat string javascript Create NSString by repeating another string a given number of times

Edited

I try to avoid for loops when they are not completely necessary because:

  1. They add to the number of lines of code even if they are tucked away in another function.

  2. Someone reading my code has to figure out what I am doing in that for loop. Even if it is commented and has meaningful variables names, they still have to make sure it is not doing anything "clever".

  3. Programmers love to put clever things in for loops, even if I write it to "only do what it is intended to do", that does not preclude someone coming along and adding some additional clever "fix".

  4. They are very often easy to get wrong. For loops that involving indexes tend to generate off by one bugs.

  5. For loops often reuse the same variables, increasing the chance of really hard to find scoping bugs.

  6. For loops increase the number of places a bug hunter has to look.

+14  A: 

Commons Lang StringUtils.repeat()

ChssPly76
using a one-method-dependency for the simplicity's sake in the long run can resulting in a jar-hell
dfa
Sure, except it's commons lang. I don't think I've ever seen a project over 5000 LOCS that didn't have commons lang.
e5
The solution to jar-hell is a managed build depency system such as maven. Import away.
Markus Koivisto
Maven is pretty decent in my experience!
e5
What makes you think that this doesn't simply tuck the for loop away in another function? (Criticism 1)
Imagist
Commons Lang is open source - download it and take a look. Of course it has a loop inside, but it's not quite as simple. A lot of effort went into profiling and optimizing that implementation.
ChssPly76
I don't avoid loops for performance reason (read my reasons in the question). When someone sees StringUtils.repeat, they know what I am doing. They don't have to worry that I attempted to write my own version of repeat and made a mistake. It is an atomic cognitive unit!
e5
Migrate to Maven to avoid a for-loop?? Interesting turn this is taking.
Thorbjørn Ravn Andersen
@Thorbjørn Ravn Andersen - it can get a LOT more interesting if things keep being taken out of context
ChssPly76
@e5 - The use and reuse of the commons classes, StringUtils especially, reduces maintenence load in a number of ways. Simple, descriptive method names, sensible defaults and null handlings and a stable codebase all make it easier to identfy where bugs are more likely to reside. One idiom I encourage is that loop control always uses "i"; this forces only one loop per method, which means the common problem of incrementing the wrong thing is removed.
Michael Rutherfurd
@Michael Rutherfurd, those are rules to live by! I've been following the "one for loop per method rule" for a few months now and it has improved the quality and readability of my code dramatically. Have trouble obeying "one for loop per method" when I'm taking the Cartesian product of two arrays. I should post a question on how to do that better. Java should to add the warning 'consider using stringUtils'.
e5
My generic answer to this kind of question is always "stitch with Apache Commons!"
Ravi Wallau
The Apache commons repeat uses StringBuffer internally. This will have some overhead in synchronization over your rolling your own implmementation that makes use of StringBuilder.
pjp
@pjp I hadn't heard that, can you provide a link to back this up?
e5
Synchronization of what? StringUtils.repeat() is a static method using internal StringBuffer.
ChssPly76
+9  A: 

Why do you wish to avoid a loop? Any implementation that is posted will most certainly use a loop under the covers.

Andrew Hare
I'm guessing because `StringUtils.repeat("blah", 10)` is a bit faster to write (and easier to read) then allocating StringBuffer / StringBuilder and looping manually :-)
ChssPly76
+1, you are right, a library would be more effort that the payback.
WolfmanDragon
@WolfmanDragon - if you're only EVER going to use the library in question for repeating this string - yes, it's not worth it. But Commons Lang is something that's quite often used in any decent-sized application anyway so you're getting a more clean and concise code for free by using a library.
ChssPly76
I agree with ChssPly76, I was more curious why the OP wanted to avoid a loop for something requires a loop to solve.
Andrew Hare
Replied in question. I really find for loops (++ and each) to be the bane of programming.
e5
@e5 In some cases for loops aren't ideal. This is not one of those cases.
Imagist
A: 

If you are worried about performance, just use a StringBuilder inside the loop and do a .toString() on exit of the Loop. Heck, write your own Util Class and reuse it. 5 Lines of code max.

WolfmanDragon
+4  A: 

This contains less characters than your question

public static String repeat(String s, int n) {
    if(s == null) {
        return null;
    }
    final StringBuilder sb = new StringBuilder();
    for(int i = 0; i < n; i++) {
        sb.append(s);
    }
    return sb.toString();
}
Pyrolistical
Should be new StringBuilder(s.length() * n).
James Schek
It contains more characters than my answer StringUtils.repeat(str, n).
e5
Unless you're already using Apache Commons, this answer is a lot less hassle - no downloading another library, including it in your classpath, making sure its license is compatible with yours, etc.
Paul Tomblin
Please, never return null - in that case return an empty string, allowing you to always use the returned value unchecked. Otherwise, what I would recommend the poster to use.
Thorbjørn Ravn Andersen
Well, there are three ways to handle if s is null. 1. Pass the error (return null), 2. Hide the error (return ""), 3. Throw an NPE. Hiding the error and throwing an NPE are not cool, so I passed the error.
Pyrolistical
+3  A: 

using only JRE classes (System.arraycopy) and trying to minimize the number of temp objects you can write something like:

public static String repeat(String toRepeat, int times) {
    if (toRepeat == null) {
        toRepeat = "";
    }

    if (times < 0) {
        times = 0;
    }

    final int length = toRepeat.length();
    final int total = length * times;
    final char[] src = toRepeat.toCharArray();
    char[] dst = new char[total];

    for (int i = 0; i < total; i += length) {
        System.arraycopy(src, 0, dst, i, length);
    }

    return String.copyValueOf(dst);
}

EDIT

and without loops you can try with:

public static String repeat2(String toRepeat, int times) {
    if (toRepeat == null) {
        toRepeat = "";
    }

    if (times < 0) {
        times = 0;
    }

    String[] copies = new String[times];
    Arrays.fill(copies, toRepeat);
    return Arrays.toString(copies).
              replace("[", "").
              replace("]", "").
              replaceAll(", ", "");
}

EDIT 2

using Collections is even shorter:

public static String repeat3(String toRepeat, int times) {
    return Collections.nCopies(times, toRepeat).
           toString().
           replace("[", "").
           replace("]", "").
           replaceAll(", ", "");
}

however I still like the first version.

dfa
+1, clever! Claps hands!
e5
Have you tried it with e.g. repeat3("[, ]", 5) ?
Thorbjørn Ravn Andersen
-1: too clever by half. If your aim is to make you code readable or efficient, these "solutions" are not a good idea. 'repeat' could simply be rewritten using a StringBuilder (setting the initial capacity). And 'repeat2' / 'repeat3' are really inefficient, and depend on the unspecified syntax of the String produced by String[].toString().
Stephen C
They are cool tho, I wouldn't use them in my code, but you have to appreciate that they are pretty neat hacks.
e5
@Thorb: absolutely, with this code you cannot use "metacharacter", [],
dfa
@Stephen: the question was edited to request **explicitly** no loops. A StringBuilder based answer was already provided so I avoided to post a duplicate
dfa
@Stephan: I cannot figure out the downvote. My edited answer is loop-free as requeted. There are no requests about efficiency. I think that this question is just an *intellectual effort* to produce a concatenation without a loop.
dfa
@Stephan: String produced via Collection.toString (and Arrays.toString) are **clearly specified** in AbstractCollection.toString: " The string representation consists of a list of the collection's elements in the order they are returned by its iterator, enclosed in square brackets ("[]"). Adjacent elements are separated by the characters ", " (comma and space)."
dfa
@dfa: I take back my point about toString() being unspecified. (I didn't know that ...). But I don't read the OP as saying that this is merely an intellectual effort. Rather I think he is serious ... and seriously misguided ... and might even copy-and-paste one of your "too clever" solutions into his code!
Stephen C
A: 

Despite your desire not to use loops, I think you should use a loop.

String repeatString(String s, int repetitions)
{
    if(repetitions < 0) throw SomeException();

    else if(s == null) return null;

    StringBuilder stringBuilder = new StringBuilder(s.length() * repetitions);

    for(int i = 0; i < repetitions; i++)
        stringBuilder.append(s);

    return stringBuilder.toString();
}

Your reasons for not using a for loop are not good ones. In response to your criticisms:

  1. Whatever solution you use will almost certainly be longer than this. Using a pre-built function only tucks it under more covers.
  2. Someone reading your code will have to figure out what you're doing in that non-for-loop. Given that a for-loop is the idiomatic way to do this, it would be much easier to figure out if you did it with a for loop.
  3. Yes someone might add something clever, but by avoiding a for loop you are doing something clever. That's like shooting yourself in the foot intentionally to avoid shooting yourself in the foot by accident.
  4. Off-by-one errors are also mind-numbingly easy to catch with a single test. Given that you should be testing your code, an off-by-one error should be easy to fix and catch. And it's worth noting: the code above does not contain an off-by-one error. For loops are equally easy to get right.
  5. So don't reuse variables. That's not the for-loop's fault.
  6. Again, so does whatever solution you use. And as I noted before; a bug hunter will probably be expecting you to do this with a for loop, so they'll have an easier time finding it if you use a for loop.
Imagist
-1. Here's two exercises for you: a) run your code with `repetitions = -5`. b) download Commons Lang and run `repeatString('a', 1000)` a million times in a loop; do the same with your code; compare the times. For extra credit do the same with `repeatString('ab', 1000)`.
ChssPly76
Fixed the first issue. As for the second issue, right, because speed is the ONLY concern. Given that the OP considers for loops to be the bane of programming, he's more concerned about readability than micro-optimizations.
Imagist
Are you arguing that your code is more readable then `StringUtils.repeat("ab",1000)`? Because that was my answer that you've downvoted. It also performs better and has no bugs.
ChssPly76
So you're coming out and admitting that you downvoted my answer because I downvoted yours, not on the merits of my answer? For your information, I downvoted your answer, not because of readability, but because as dfa said, "using a one-method-dependency for the simplicity's sake in the long run can resulting in a jar-hell" in addition to my own criticism, "What makes you think that this doesn't simply tuck the for loop away in another function?". Since the first is a concern and the second is obviously a concern to the OP, I thought my downvote was justified.
Imagist
"Admitting"? I explicitly said "-1" and specified the reasons why. "Jar hell" is a non-issue; it's been addressed in comments above. OP was not trying to avoid loops at all costs; he did not want to write one if there was already a library function that would do this task for him. Your code (which uses the loop; is not as readable; had a bug; and doesn't perform well) is a perfect illustration why.
ChssPly76
"Jar hell" may be a non-issue with Maven, but one shouldn't have to install an entire dependency system and a library just to implement a few lines of code. If he plans to use it elsewhere, fine, but I saw no indication of that in the original post. As for avoiding loops at all costs: the OP said, "I try to avoid for loops at all costs because:" and listed a bunch of reasons. So I think that he WAS trying to avoid loops at all costs.
Imagist
Read the 2nd sentence in the question you're quoting. "I try to avoid for loops at all costs because" was added to the question as a clarification in response to Andrew Hare's answer after my reply - not that it matters because if the position you're taking is "answer is bad if loop is used _anywhere_" there are no answers to the OP question. Even dfa's solutions - inventive as they are - use for loops inside. "jar hell" was replied to above; commons lang is used in every decent-sized application anyway and thus doesn't add a new dependency.
ChssPly76
@ChssPly76 at this point I'm pretty sure imagist is trolling. I really have a hard time seeing how anyone could read what I wrote and seriously think the responses typed above.
e5
-1 for trolling.
e5
Disagreement is not equivalent to trolling.
Imagist
@Imagist it is trolling when you go out of your way to misstate the positions of others, and then attack these misstated positions. That is, in fact, a pretty standard troll tactic.
e5
@e5: Imagist is not a troll, this is not irc :) :)
dfa
@e5 I may have misstated the position of someone else, but I'm not sure whose opinion I misstated or where I misstated it. And if I did, I assure you it was not my intent to do so. As Hanlon once said, "Never attribute to malice that which is adequately explained by stupidity."
Imagist
@ChssPly76 my answers don't have any loops at all :-p
fortran
+10  A: 

So you want to avoid loops?

Here you have it:

public static String repeat(String s, int times) {
    if (times <= 0) return "";
    else return s + repeat(s, times-1);
}

(of course I know this is ugly and inefficient, but it doesn't have loops :-p)

You want it simpler and prettier? use jython:

s * 3

Edit: let's optimize it a little bit :-D

public static String repeat(String s, int times) {
   if (times <= 0) return "";
   else if (times % 2 == 0) return repeat(s+s, times/2);
   else return s + repeat(s+s, times/2);
}

Edit2: I've done a quick and dirty benchmark for the 4 main alternatives, but I don't have time to run it several times to get the means and plot the times for several inputs... So here's the code if anybody wants to try it:

public class Repeat {
    public static void main(String[] args)  {
        int n = Integer.parseInt(args[0]);
        String s = args[1];
        int l = s.length();
        long start, end;

        start = System.currentTimeMillis();
        for (int i = 0; i < n; i++) {
            if(repeatLog2(s,i).length()!=i*l) throw new RuntimeException();
        }
        end = System.currentTimeMillis();
        System.out.println("RecLog2Concat: " + (end-start) + "ms");

        start = System.currentTimeMillis();
        for (int i = 0; i < n; i++) {
            if(repeatR(s,i).length()!=i*l) throw new RuntimeException();
        }               
        end = System.currentTimeMillis();
        System.out.println("RecLinConcat: " + (end-start) + "ms");

        start = System.currentTimeMillis();
        for (int i = 0; i < n; i++) {
            if(repeatIc(s,i).length()!=i*l) throw new RuntimeException();
        }
        end = System.currentTimeMillis();
        System.out.println("IterConcat: " + (end-start) + "ms");

        start = System.currentTimeMillis();
        for (int i = 0; i < n; i++) {
            if(repeatSb(s,i).length()!=i*l) throw new RuntimeException();
        }
        end = System.currentTimeMillis();
        System.out.println("IterStrB: " + (end-start) + "ms");
    }

    public static String repeatLog2(String s, int times) {
        if (times <= 0) {
            return "";
        }
        else if (times % 2 == 0) {
            return repeatLog2(s+s, times/2);
        }
        else {
           return s + repeatLog2(s+s, times/2);
        }
    }

    public static String repeatR(String s, int times) {
        if (times <= 0) {
            return "";
        }
        else {
            return s + repeatR(s, times-1);
        }
    }

    public static String repeatIc(String s, int times) {
        String tmp = "";
        for (int i = 0; i < times; i++) {
            tmp += s;
        }
        return tmp;
    }

    public static String repeatSb(String s, int n) {
        final StringBuilder sb = new StringBuilder();
        for(int i = 0; i < n; i++) {
            sb.append(s);
        }
        return sb.toString();
    }
}

It takes 2 arguments, the first is the number of iterations (each function run with repeat times arg from 1..n) and the second is the string to repeat.

So far, a quick inspection of the times running with different inputs leaves the ranking something like this (better to worse):

  1. Iterative StringBuilder append (1x).
  2. Recursive concatenation log2 invocations (~3x).
  3. Recursive concatenation linear invocations (~30x).
  4. Iterative concatenation linear (~45x).

I wouldn't ever guessed that the recursive function was faster than the for loop :-o

Have fun(ctional xD).

fortran
+1 for recursion and obviously being a lisp hacker. I don't think this is so inefficient either, string concatenation isn't the warcrime it once was, because + really is just a stringBuilder UTH. Seehttp://stackoverflow.com/questions/47605/java-string-concatenation andhttp://schneide.wordpress.com/2009/02/23/about-string-concatenation-in-java-or-dont-fear-the/ .I wonder how much all those stack pushes and pops from the recursion cost, or if hotspot takes care of them. Really wish I had the free time to benchmark it. Someone else maybe?
e5
+1 a very elegant solution
dfa
@e5: fortran is right; this solution could be made more efficient. This implementation will unnecessarily create a new StringBuilder (and a new String) for each recursion. Still a nice solution though.
rob
@e5 I'd wish I were a Lisp hacker xD... If I were, I would have used a tail recursive function :-p
fortran
+2  A: 

based on fortran's answer, this is a recusive version that uses a StringBuilder:

public static void repeat(StringBuilder stringBuilder, String s, int times) {
    if (times > 0) {
        repeat(stringBuilder.append(s), s, times - 1);
    }
}

public static String repeat(String s, int times) {
    StringBuilder stringBuilder = new StringBuilder(s.length() * times);
    repeat(stringBuilder, s, times);
    return stringBuilder.toString();
}
dfa
+1, I like the use of recursion by reference.
e5
Agreed that's a pretty cool method there.
Imagist
+8  A: 

Here's a way to do it using only standard String functions and no explicit loops:

// create a string made up of  n  copies of  s
repeated = String.format(String.format("%%0%dd", n), 0).replace("0",s);

+1 Sweet mother of Jesus that is an awesome line of code.

I. J. Kennedy
Amazing :-) Although beware of n becoming zero…!
Yang
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html#replace%28char,%20char%29 - replace() accepts two char parameters. Not Strings!
Vijay Dev
I think he meant `replaceAll`
fortran
+1  A: 

using Dollar is simple as typing:

@Test
public void repeatString() {
    String string = "abc";
    assertThat($(string).repeat(3).toString(), is("abcabcabc"));
}

PS: repeat works also for array, List, Set, etc

dfa
A: 

I find for loops to be easier to read then "vectorized" or other cleverness. I do like hiding them in static functions like the OP, but I don't get at all why the static function wouldn't have for loops.

I ended up on this thread because I am looking for a general repeat in Java (rep in R, [val]*n in Python). I am going to assume one doesn't exist?

Jer
It does exist, you just have to use a apache-commons lib Commons Lang StringUtils.repeat() see: http://stackoverflow.com/questions/1235179/simple-way-to-repeat-a-string-in-java/1235184#1235184
e5