5

I wanted to create a method that reverse upper-lower case letters in a word. The problem I'm having is that the method doesn't reverse all the letters. For example when I type "nIceToMeEtyoU" it prints "NiCETomEETYou". It didn't work for the "o", the second "e" and "t". I just couldn't figure out what's wrong with the code.

public static String reverseCase(String str) {
    char changed;
    String a = str;
    for (int i = 0; i < a.length(); i++) {
        char d = a.charAt(i);
        boolean letter = Character.isUpperCase(d);
        if (letter == true) {
            changed = Character.toLowerCase(d);
        } else {
            changed = Character.toUpperCase(d);
        }
        a = a.replace(d, changed);
    }
    return a;
}
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
HyperMelon
  • 71
  • 5
  • 4
    The problem is that `replace` doesn't replace just the one character at index `i` - it replaces all instances of the same character, including the ones you've already replaced earlier. – kaya3 Sep 19 '20 at 14:33
  • 2
    Thanks for the question. As you may have sensed, you have come to the place where we love well-asked questions. Yours has precise requirements and a short and complete code example, and it is very specific about how observed result differs from the expected. It’s good for a new Stack Overflow user. +1. – Ole V.V. Sep 19 '20 at 14:48

10 Answers10

2

String::replace returns a new String with all occurrences of the character you wanted replaced changed.

Also, Strings in Java are immutable, meaning you cannot replace a character in a string while keeping the same string. In order to replace the character at a specific index, see this post

Irad Ohayon
  • 174
  • 6
2

It didn't work for the "o","second e" and "t".

The replace() method replaces all occurrances of the character in the string.

Instead, use a StringBuffer to append each character as you iterate through the string.

Then when the loop is finished you recreate the String using the toString() method of the StringBuffer.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • 1
    What I like about this answer: you’re not only giving the correct explanation and a good way to correct the error, at the same time you’re not giving the code away but leaving to the learner to learn from writing the corrected code himself/herself. +1 – Ole V.V. Sep 19 '20 at 14:54
  • 1
    @OleV.V., You may have heard of the expression: "Give someone a fish they eat for a day. Teach someone to fish they eat for life". I try to teach so people can learn on their own rather that have the code spoon fed for them. The simple answer will force the user to read the StringBuffer API for the appropriate method. They may also learn that reading the String.replace() API will have answered their question. Valuable learning for a beginner. – camickr Sep 19 '20 at 15:07
2

Instead of using String#replace which replaces all instances of the match with the given replacement, I suggest you build the string using a StringBuilder as shown below:

public class Main {
    public static void main(String[] args) {
        // Tests
        System.out.println(reverseCase("nIceToMeEtyoU"));
        System.out.println(reverseCase("NiCETomEETYou"));
    }

    public static String reverseCase(String str) {
        StringBuilder sb = new StringBuilder();

        // Use the enhanced for loop
        for (char ch : str.toCharArray()) {
            if (Character.isUpperCase(ch)) {
                ch = Character.toLowerCase(ch);
            } else {
                ch = Character.toUpperCase(ch);
            }
            sb.append(ch);
        }
        return sb.toString();
    }
}

Output:

NiCEtOmEeTYOu
nIcetOMeetyOU

With your approach, whichever characters are being changed in one pass, will get reverted back in the next passes if they are found again in those passes.

mevada.yogesh
  • 1,118
  • 3
  • 12
  • 35
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
1

replace replaces all occurrences of a character in a string. So, e.g., the first time you encounter e you'll replace all the es with Es. Then, the when next "original" e (which is now an E) is encountered, all Es will be turned back to es and so on.

Instead of using replace, you could accumulate the characters you encounter in a new object. While you could use a String and add to it with the += operator, using a StringBuilder should have better performance:

public static String reverseCase(String str) {
    StringBuilder sb = new StringBuilder(str.length());
    for (int i = 0; i < str.length(); i++) {
        char c = a.charAt(i);
        id (Character.isUpperCase(c) {
            c = Character.toLowerCase(c);
        } else i (Character.isLowerCase(c)) {
            c = Character.toLowerCase(c);
        }
        sb.append(c);
    }
    return st.toString();
}
Mureinik
  • 297,002
  • 52
  • 306
  • 350
1

Instead of using replace (which replaces all the occurrences in the string) you can concat each character to create a new string.

public static String reverseCase(String input) {
    StringBuilder a = new StringBuilder();
    for (char c : input.toCharArray()) {
      a.append(Character.isUpperCase(c) ? Character.toLowerCase(c) : Character.toUpperCase(c));
    }
    return a.toString();
}
mevada.yogesh
  • 1,118
  • 3
  • 12
  • 35
  • 1
    This works in a simple example with a short string, but do not do this with long strings, because it will be very slow. This creates a new string in each iteration. You should use ```StringBuffer``` instead. – Donat Sep 19 '20 at 14:52
  • (1-) Use a StringBuffer or StringBuilder when String concatenation is required. – camickr Sep 19 '20 at 14:57
  • Don't use `StringBuffer` unless you need the synchronization. – WJS Sep 19 '20 at 16:19
1

You could StringBuilder for String operations which is not accessed by other threads. The main issue you are facing here is because of the replace() method as mentioned by others. The replace() method replaces all occurrances of the character in the string. Instead of trying to replace, just build new string and return. Another way you could do it is to create an array of characters and replace the character (with the variable : 'changed') at the current position inside the loop.

public static String reverseCase(String str) {
        char changed;
        StringBuilder reversedStringBuilder=new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char d = str.charAt(i);
            boolean letter = Character.isUpperCase(d);
            if (letter) {
                changed = Character.toLowerCase(d);
            } else {
                changed = Character.toUpperCase(d);
            }
            reversedStringBuilder.append(changed);
        }
        return reversedStringBuilder.toString();
    }
    
    public static void main(String[] args) {
        System.out.println(reverseCase("nIceToMeEtyoU"));
    }
John Thomas
  • 212
  • 3
  • 21
1

a.replace(d, changed) replaces every occurrence of d in the string, not just the one at the index you are looking at.

  • So for every letter that occurs an even number of times in the string, the replace of that letter is performed an even number of times, leaving all occurrences at the same case that the first occurrence had from the outset. This was what happened to o, for example. When you met the first o, both occurrences were replaced to upper case O. When you met the next O, it was already upper case, so both occurrences were changed to lower case again.
  • Conversely if a letter occurs an odd number of times, it will through the same procedure be left at the opposite case of what the first occurrence had from the outset. From the outset you had e twice and E once. First the two lower case e were changed to upper case. Second alll three were changed to lower case. Third, all to upper case.

Instead do not perform the replacements directly in the string. Use either a StringBuffer or StringBuilder or a char array. In each of these you can perform the replacement by index so that you are only affecting the occurrence of the letter that you intend to affect.

Bonus tip: if (letter == true) is considered mediocre style. Prefer just if (letter).

Bonus information: Letters exist that become more than one letter when switching case. If I enter the German word Füße (feet), ß only exists in lower case, it becomes SS in upper case. So the correct reverse of the word would be fÜSSE. However, Character.toUpperCase() cannot return two chars, so just leaves it at ß.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
1

Using Streams

import java.util.stream.Collectors;

public class FlipCase {

    static char flipCase(char ch) {
        if (Character.isUpperCase(ch)) return Character.toLowerCase(ch);
        else return Character.toUpperCase(ch);
    }

    public static String reverseCase(String str) {
        return str
                .chars()
                .mapToObj(i -> (char) i)
                .map(ch -> String.valueOf(FlipCase.flipCase(ch)))
                .collect(Collectors.joining());
    }

    public static void main(String[] args) {
        System.out.println(reverseCase("nIceToMeEtyoU")); // NiCEtOmEeTYOu
    }
}
Pritam Kadam
  • 2,388
  • 8
  • 16
1

As has been stated, the issue is the replace. Best to use a StringBuilder and create a new String.

I also offer an alternative that you may find interesting.

You can do it by flipping the bit from upper case to lower case and visa versa.

The key is knowing that the sixth bit from the right is the bit between cases for Strings (as in your example). That would be 2 to the 5th or 32. The Exclusive OR operator (^) can be used to flip the bit.

String s = "nIceToMeEtyoU";
System.out.println(reverseCase(s));
    
public static String reverseCase(String s) {
   StringBuilder sb = new StringBuilder(s.length());
   for(char c : s.toCharArray()) {
       // change the case if letter
       sb.append(Character.isLetter(c) ? (char) (c ^ 32) : c);
   }
   return sb.toString();
}

Prints

NiCEtOmEeTYOu
WJS
  • 36,363
  • 4
  • 24
  • 39
  • XOR operator will cause an issue if there are any non-alphabetic characters present in the input string. – mevada.yogesh Sep 19 '20 at 16:50
  • Did you see that I said, *per your example* in my answer. All are letters. But I'll fix it anyway as it was a good catch. Thanks! – WJS Sep 19 '20 at 16:52
  • As we are unaware of the specifics of input the user will provide. That's why I just raised a thought over the solution. If all are going to be alphabets then your solution is best in that perspective. – mevada.yogesh Sep 19 '20 at 16:57
1

Did you try Apache Commons before? I'm sure that it is the most simple way to handle this case:

package com.awm4n.swapcaseexample;
import org.apache.commons.lang3.StringUtils;
public class SwapCaseExample {
    public static void main(String[] args) {
        String str = "TheMOStsimPLEWAytoswaPCASE";            
        System.out.println( StringUtils.swapCase(str) );
    }
}

And the output will be:

tHEmosTSIMplewaYTOSWApcase
Man1s
  • 192
  • 1
  • 7