Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Is this code okay?

import java.util.Stack;

public class StackReverse {

    public static void main(String[] args) {

        final String inputString = "code review";

        final String reversed = reverseString(inputString);
        System.out.println("The reversed string is " + reversed);

    }

    public static String reverseString(String originalString) {
        Stack<Character> stack = new Stack<>();
        String reversed = "";
        for (int i = 0; i < originalString.length(); i++) {
            char ch = originalString.charAt(i);
            stack.push(ch);
        }
        for (int i = 0; i < originalString.length(); i++) {
            char ch = stack.pop();
            reversed = reversed + ch;

        }
        return reversed;

    }

}
share|improve this question
2  
On your second for loop, I would recommend iterating over the stack's size instead of the original string's length - what if you later want to add a step where you put some more characters in the stack? –  Devon Parsons 10 hours ago
    
Java already has a built-in stack that would be used implicitly by making reverseString() recursive! –  rbp 5 hours ago

5 Answers 5

up vote 11 down vote accepted

Unicode is hard to get right, especially in Java as it has a more or less broken concept of a “character”. An Unicode code point is a 21-bit number. These code points are encoded to bytes with the UTF-8 or UTF-16 encodings (well, there are a couple more…). But when Java was created, Unicode only had characters in a 16-bit range, and code points were encoded with the (now deprecated) UCS-2 encoding.

There's a lot of pain from the fact that the 21-bit Unicode code points don't fit into a single 16-bit char or Character in Java. Two consecutive chars might actually be surrogate pairs. When reversing a string, we have to special-case these. We can use the Character.isHighSurrogate(char) function to test whether a given char starts a surrogate pair. If we encounter such a code point, we advance to the next char in the string, and push it onto the stack first:

for (int i = 0; i < originalString.length(); i++) {
    char ch = originalString.charAt(i);
    if (Character.isHighSurrogate(ch)) {
        i++;
        if (i < originalString.length()) {
            stack.push(originalString.charAt(i));
        }
    }
    stack.push(ch);
}

As a test case, you can reverse a string with the smiley character 😃: "hi \ud83d\ude03". Reversed, it should be "\ud83d\ude03 ih".


It is best to think of Java's char as “an UTF-16 code unit” (see Wikipedia for all the details about UTF-16 you didn't want to know, but should). To learn about what Unicode is, and how to tame it, start with Joel Spolsky's Unicode blog post.

share|improve this answer
1  
Well if you start going to such lengths to take care of unicode, then you probably would want to really make the whole thing unicode aware. In which case a single unicode codepoint does not necessarily map to a single logical character, so you would have to take care of that too. Doing so in vanilla Java is a horrible undertaking though. –  Voo 4 hours ago
    
@Voo excellent catch! We'd need to find and reverse grapheme clusters. Feel free to write another answer or to edit my post to show how it's done (because I currently lack the knowledge to find grapheme clusters). –  amon 3 hours ago

This question provides a really good example of how learning 'tools' is important, but understanding efficiency is better.

The purpose of the task was probably to teach you to use a stack, but there are properties of this challenge which are not well suited to an actual stack solution, and, more importantly, there are other ways of implementing a stack than a Stack object.

Let me make an assertion here: A Stack can be implemented with an array

Now, consider the things we know about this problem.... we know:

  • the input is a string
  • the output will be a string with the same length
  • an array of char (char[]) can be used as the stack

How do you use an array of char as a stack? Well, you add the chars from the input string to an array, and as you do that, you increment the index in the array, so you are 'pushing' to the end, and then you can pop from the end to get the reverse mechanism.

Now, a quick way to create and push the chars from the input, to a stack, is:

char[] stack = input.toCharArray();

Then, a new String can be created with:

char[] reversed = new char[stack.length];
int pos = stack.length - 1;
for (int i = 0; i < stack.length; i++) {
    reversed[i] = stack[pos--]; // pop the stack on to the reversed array
}
return new String(reversed);

This will be significantly faster, it does use a stack, but implemented in an array.

share|improve this answer
    
Major lesson? Many ways to do the same thing as a programmer. Kudos on this one. –  WernerCD 3 hours ago

The code look good so far.

The only thing I would change is o use a StringBuilder for the reversed String as String concatenation with + is very inefficient as in your loop the compiler creates the following:

String reversed = "";
// some code
for (int i = 0; i < originalString.length(); i++) {
    char ch = stack.pop();
    StringBuffer tmp = new StringBuffer(reversed);
    tmp.append(ch);
    reversed = tmp.toString();
}
return reversed;

You create a whole lot of objects without any need.

Replace this part of the code with

StringBuffer reverse = new StringBuffer();;
// some code
for (int i = 0; i < originalString.length(); i++) {
    char ch = stack.pop();
    reversedappend(ch);
}
return reversed.toString();

This is much more efficient in time and memory consumption.

share|improve this answer

Sure, the code is ok, but it's not going to be the fastest way to reverse a string(*) (I'm assuming performance is not the main concern here).

Temporary Variables

You don't really need the two ch variables, you are using them only once, right on the next line. Just write:

stack.push(originalString.charAt(i));
[...]
reversed = reversed + stack.pop();

Naming

  • ch is too short, I would name it char.
  • reverseString could also be named reverse, it's obvious from the method signature (string in, string out), that it's reversing a string.
  • originalString vs reversed: one contains String, one doesn't, this seems inconsistent.

Misc

  • remove the additional newlines before closing }.
  • reversed = reversed + ch; can be written as reversed += ch; (if you care about performance, use a StringBuilder).

(*) just for fun some quick numbers, reversing a string 10k times:

reverseStack:               187900620 <- your method
reverseStackBuilder:        46680501  <- your method with StringBuilder
reverseBuilder:             20020504  <- new StringBuilder(originalString).reverse().toString()
reverseIt:                  21517253  <-- see http://stackoverflow.com/questions/7569335/reverse-a-string-in-java

share|improve this answer
3  
You won't name any variable char, at least not when you want it to compile. Moreover, note that repeating the type name usually carries no information. I personally never use long name for variables spanning no more than 1-3 lines, but it's just me. –  maaartinus 6 hours ago
    
@maaartinus good point, char was not the best alternative to choose :) And my suggestion was to get rid of it entirely, but if it is kept, I think it should have a better name then ch, otherwise there is really no reason for it to exist. Maybe charToStore and retrievedChar, or charToAppend. –  tim 6 hours ago

@amon wrote a great answer. With Java 8 handling of surrogate pairs is even easier. There is a codePoints() method on the CharSequence interface.

public static String reverseString(String originalString) {
    Stack<Integer> stack = new Stack<>();
    StringBuilder reversed = new StringBuilder();

    originalString.codePoints().forEach(cp -> stack.push(cp));
    while (!stack.empty()) {
        reversed.appendCodePoint(stack.pop());
    }
    return reversed.toString();
}

In a professional project I would expect:

new StringBuilder(originalString).reverse().toString();

share|improve this answer
2  
Hi, and welcome to Code Review, nice first answer, +1 –  rolfl 8 hours ago
1  
@maaartinus No, it does work with surrogate pairs. From Javadoc: If there are any surrogate pairs included in the sequence, these are treated as single characters for the reverse operation. Thus, the order of the high-low surrogates is never reversed. –  dusky 3 hours ago
    
Sorry, I see, I can't read Javadoc. Removing my comment. –  maaartinus 3 hours ago

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.