views:

2237

answers:

9

Hi, I have been messing around with recursion today. Often a programming technique that is not used enough.

So.. I set myself the task to recursively reverse a string, heres what I came up with :

//A method to reverse a string using recursion
    public String reverseString(String s){
        char c = s.charAt(s.length()-1);
        if(s.length() == 1) return Character.toString(c);   

        return c + reverseString(s.substring(0,s.length()-1));
    }

My question is there a better way in Java?

Thanks for the replies :-)

+4  A: 

The best way is not to use recursion. These stuff are usually used to teach students the recursion concept, not actual best practices. So the way you're doing it is just fine. Just don't use recursion in Java for these kind of stuff in real world apps ;)

PS. Aside what I just said, I'd choose "" as the base case of my recursive function:

public String reverseString(String s){
    if (s.length() == 0) 
         return s;

    return reverseString(s.substring(1,s.length()-1)) + s.charAt(0);
}
Mehrdad Afshari
yes, his original function would fail on empty string, probably something he doesn't want
yx
A: 

That's definitely how I'd go about recursively reversing a string (although it might be nice to extend it to the case of an empty string in your condition.) I don't think there is any fundamentally better way.

EDIT: It may be more efficient to operate on a character array and pass a "cutoff" length down the chain of recursion, if you get my drift, rather than making substrings. However, this is not really worth nitpicking about, since it's not a terribly efficient technique in the first place.

mquander
A: 

As Mehrdad noted, it's best not to use recursion. If you do use it, though, you might as well keep both the first and last character each call, thus halving the number of recursive calls. That is,

public String reverseString(String s){
    int len = s.length();

    if (len <= 1) {
        return s;
    }

    char fst = s.charAt(0);
    char lst = s.charAt(len - 1);

    return lst + reverseString(s.substring(1, len - 2)) + fst;
}

This also handles the case of the empty string. Perhaps passing along a StringBuilder with the appropriate capacity would speed things up even more, but that's left as an exercise to the reader ;)

Stephan202
This fails on string "a" for example. Why it's better than the original?
Mehrdad Afshari
s/==/<=/ :). It's 'better' because it requires half the number of recursive calls.
Stephan202
I like it because it halves the recursive stack,but as stated, it wud not work with a string like "a". Perhaps it would be possible to use this method with tail recursion as above, validate the string first then apply the recursion. As I can see you understood that no one would care if you reversed strings of one char, as the reverse is the same. Am I right on my assumption?
Graham
I'm at a loss why the corrected version does not work for "a": it has length 1, and hence is returned unchanged. I guess I don't understand your last remark.
Stephan202
A: 

You capture the basic idea, but extracting the last character doesn't improve clarity. I'd prefer the following, others might not:

public class Foo
{
    public static void main(String[] argv) throws Exception
    {
        System.out.println(reverse("a"));
        System.out.println(reverse("ab"));
        System.out.println(reverse("abc"));
    }


    public final static String reverse(String s)
    {
        // oft-repeated call, so reduce clutter with var
        int length = s.length();

        if (length <= 1)
            return s;
        else
            return s.substring(length - 1) + reverse(s.substring(0, length - 1));
    }
}
kdgregory
+4  A: 

You don't want to nest too deeply. Divide-and-conquer is the way to go. Also reduces total size of temporary strings and is amenable to parallelisation.

public static String reverseString(String str) {
    int len = str.length();
    return len<=1 ? str : (
        reverseString(str.substring(len/2))+
        reverseString(str.substring(0, len/2))
    );
}

(Not tested - this is stackoverflow.)

String.concat instead of + would improve performance at the expense of clarity.

Edit: Just for fun, a tail-recursion friendly version of the naive algorithm.

public static String reverseString(String str) {
    return reverseString("", str);
}
private static String reverseString(String reversed, String forward) {
    return forward.equals("") ? reversed : (
         reverseString(reversed+forward.charAt(0), forward.substring(1)) 
    );
}

Correct handling of surrogate pairs is left to the interested reader.

Tom Hawtin - tackline
Not much difference... It's still O(n) operation (assuming string concatenation as an O(1) op). It just looks more like MergeSort and other stuff but it's not. The only benefit is -as you described- the depth of recursion tree.
Mehrdad Afshari
Concatenation is O(n) (for big enough n).
Tom Hawtin - tackline
I think, my method gives O(n log n) temporary string characters, whereas the linear method gives O(n^2).
Tom Hawtin - tackline
Both give O(n) temporary Strings.
Tom Hawtin - tackline
Both methods have O(n) temp strings.
Mehrdad Afshari
I see how divide and conquor is a good way to cut down the depth of the recursion tree here. However that alternative way of writing an if statement really makes me have look and concentrate a bit more. lol
Graham
Mehrdad: Yes, as I said. However, my Strings have a mean length of O(log n) instead of O(n).
Tom Hawtin - tackline
On 6:28 AM May 9th dhanji tweeted that "Array insertion and deletion are constant time operations." Then "clarification: for all array sizes less than the capacity of a block memory transfer, array insertion or deletion is a O(1) operation." So O(n) is constant for n less than a constant. Blimey, but we do need to remember the size of n.
Tom Hawtin - tackline
+2  A: 

Just for the heck of it, here's a tail-recursive method using StringBuilder (which is generally recommended over manipulating Strings).

public String reverseString(String s_) {
    StringBuilder r = new StringBuilder();
    StringBuilder s = new StringBuilder(s_);
    r = reverseStringHelper(r, s);
    return r.toString();
}
private StringBuilder reverseStringHelper(StringBuilder r, StringBuilder s) {
    if (s.length() == 0)
        return r;
    else
        return reverseStringHelper(r.append(s.charAt(0)), s.deleteCharAt(0));
}

Untested, I haven't dealt with Java in many years, but this should be about right.

ephemient
i like it, very cool :-)Ofcourse we all could be lasy and use the StringBuilder reverse method but wheres the recursion in that?!lol
Graham
It would work better if (common implementations of) StringBuilder used off+len (or similar) instead of just len. Also, I don't see the point of your return value.
Tom Hawtin - tackline
@Tom: pretending that these methods return new objects instead of mutating in-place makes it feel more functional ;)
ephemient
+1  A: 

It depends on what you define as "better". :-) Seriously, though; your solution essentially uses the maximum depth of recursion; if stack size is of a concern for your definition of "better", then you'd be better off using something like this:

public String reverseString(String s) {
   if (s.length() == 1) return s;
   return reverseString(s.substring(s.length() / 2, s.length() -1) + reverseString(0, s.length() / 2);
}
McWafflestix
Ah I see. Is this approach of essentially cutting the problem in half and apply the base case against each half of the problem a common way of "getting the best from recursion" ?
Graham
Well, like I say, it's how you define "better". This method isn't appreciably faster, but the highest depth of recursion (and therefore the largest stack) that you get to using this method is log(s.length()), whereas with the original proposed method, the highest depth of recursion you get to is s.length(). This way will save a little bit of stack size.
McWafflestix
Looks like you have a stray -1, missing substring and some other stuff going on in that very long line. And it doesn't work for "".
Tom Hawtin - tackline
@tom: It's just pseudocode. I'm not writing the solution for the OP; just giving a suggestion and providing a quick idea of what I'm talking about.
McWafflestix
+2  A: 

If you're going to do this, you want to operate on a character array, because a String is immutable and you're going to be copying Strings all over the place if you do it that way.

This is untested and totally stream of consciousness. It probably has an OB1 somewhere. And very not-Java.

public String reverseString(String s)
  {
  char[] cstr = s.getChars();
  reverseCStr(cstr, 0, s.length - 1);

  return new String(cstr);
  }

/**
 * Reverse a character array in place.
 */
private void reverseCStr(char[] a, int s, int e)
  {
  // This is the middle of the array; we're done.
  if (e - s <= 1)
    return;

  char t = a[s];
  a[s] = a[e];
  a[e] = t;
  reverseCStr(a, s + 1, e - 1);
  }
Adam Jaskiewicz
Yup I am aware of the C/C++ ish way of using character strings, I am more interested in a Java implementation but I do like how efficient this is in comparison.
Graham
Well, this *is* written in Java. It just isn't a Java-ish way of doing things. In any case, I wouldn't do this recursively in Java *or* C, so writing C into Java didn't feel as wrong as it should have.
Adam Jaskiewicz
A: 

If you're writing real code (not learning recursion), use StringBuilder's reverse() method. The Java Tutorial gives this example:

String palindrome = "Dot saw I was Tod";   
StringBuilder sb = new StringBuilder(palindrome);
sb.reverse();  // reverse it
System.out.println(sb);
Jim Ferrans