简体   繁体   中英

setCharAt method not working as expected inside for loop (Java)

I am trying to develop a method to reverse the vowels in a string. For this, I have developed my own small stack. I am iterating backwards through the string twice, once to populate the stack with each vowel I locate, the second time to replace the vowel with the vowel stored at the top of the stack. I have added a bunch of print statements to determine where this is failing and it seems to be failing at the setCharAt method. For testing purposes, I provide a string aeiou and I am expecting to get back uoiea . The chars are getting replaced during each iteration, but unfortunately, they don't stay that way. As of the next iteration, the chars that were replaced on the previous iteration return to what they were before. As a result, I am getting back ueiou , where only the first character in the string is as expected (and the third which is a noop). Here is my code. Any tips are appreciated.

import java.util.*;

public class Solution {

    static String result;

    static class StackOfVowels {
        static Node top;

        public StackOfVowels() {
            Node top = null;
        }

        static class Node {
            Node next = null; 
            char vowel = '\0';
            Node(char value) {
                this.vowel = value;
            }
        }

        public void push(char item) {
            Node node = new Node(item);
            if (top == null) {
                top = node;
            }
            else { 
                node.next = top;
                top = node;
            }
        }

        public char top() {
            if (top == null) throw new EmptyStackException();
            return top.vowel; 
        }

        public void pop() {
            int result = -1;
            if (top != null) {
                result = top.vowel;
                top = top.next;
            }
        }
    }

    public static String reverseVowels(String s) {

        StackOfVowels stackOfVowels = new StackOfVowels();

        for(int i = s.length()-1; i >= 0; i--) {
            char c = s.charAt(i);
            if ((c == 'a') || (c == 'e') || (c == 'i') || (c == 'o') || (c == 'u')) {
                System.out.println("Initial sequence of iterations identified vowel: " + c);
                stackOfVowels.push(c);
                System.out.println("Pushed to stack, current top is: " + stackOfVowels.top());
            }
        }

        for(int j = s.length()-1; j >= 0; j--) {
            char b = s.charAt(j);
            if ((b == 'a') || (b == 'e') || (b == 'i') || (b == 'o') || (b == 'u')) {
                System.out.println("Second sequence of iterations identified vowel: " + b);
                StringBuilder newstr = new StringBuilder(s);
                char d = stackOfVowels.top();
                System.out.println("Variable d set to top of: " + stackOfVowels.top());
                newstr.setCharAt(j, d);
                result = newstr.toString();
                System.out.println("Here is the new string: " + result);
                stackOfVowels.pop();
                System.out.println("Stack was popped");
            }
        }
        return result;
    }

    public static void main(String[] args) {
        String s = "aeiou";
        reverseVowels(s);
        System.out.println("Final result: " + result);
    }
}

You generate always a new StringBuilder in your second loop with StringBuilder newstr = new StringBuilder(s); But you always use ´s´ and not ´result´ to build your new StringBuilder.

It would be better anyways to not create a new Object of StringBuilder in each Iteration.

Also it is confusing that your methode has a return value, which is never used.

Following methode should work:

public static String reverseVowels(String s) {

    StackOfVowels stackOfVowels = new StackOfVowels();

    for(int i = s.length()-1; i >= 0; i--) {
        char c = s.charAt(i);
        if ((c == 'a') || (c == 'e') || (c == 'i') || (c == 'o') || (c == 'u')) {
            System.out.println("Initial sequence of iterations identified vowel: " + c);
            stackOfVowels.push(c);
            System.out.println("Pushed to stack, current top is: " + stackOfVowels.top());
        }
    }
    StringBuilder result_builder = new StringBuilder(s);
    for(int j = s.length()-1; j >= 0; j--) {
        char b = s.charAt(j);

        if ((b == 'a') || (b == 'e') || (b == 'i') || (b == 'o') || (b == 'u')) {
            System.out.println("Second sequence of iterations identified vowel: " + b);
            char d = stackOfVowels.top();
            System.out.println("Variable d set to top of: " + stackOfVowels.top());
            result_builder.setCharAt(j, d);
            stackOfVowels.pop();
            System.out.println("Stack was popped");
        }
    }
    result = result_builder.toString();
    return result_builder.toString();
}

I would approach the problem a little differently, first I would use a regular expression to remove all consonants from the String to create a String of vowels . Then I would iterate through the characters of the String , using StringBuilder to build the output (passing consonants through, but replacing vowels using the vowels String previously created). Like,

public static String reverseVowels(String s) {
    // Match all consonants, and remove them. Reverse that String.
    String vowels = new StringBuilder(s.replaceAll("[^AEIOUaeiou]", ""))
            .reverse().toString();
    StringBuilder sb = new StringBuilder();
    for (int i = 0, p = 0; i < s.length(); i++) {
        switch (Character.toLowerCase(s.charAt(i))) {
        // Note that these fall-through...
        case 'a': case 'e': case 'i': case 'o': case 'u':
            sb.append(vowels.charAt(p++));
            break;
        default:
            sb.append(s.charAt(i));
        }
    }
    return sb.toString();
}

This is one of the places that switch and fall-through behavior can be taken advantage of.

And if you're using Java 8+, you could express the same with an IntStream and map the characters; like

String vowels = new StringBuilder(s.replaceAll("[^AEIOUaeiou]", ""))
        .reverse().toString();
int[] counter = new int[1]; // <-- A bit of a kludge really.
return IntStream.range(0, s.length()).mapToObj(i -> {
    switch (Character.toLowerCase(s.charAt(i))) {
    case 'a': case 'e': case 'i': case 'o': case 'u':
        return Character.toString(vowels.charAt(counter[0]++));
    default:
        return Character.toString(s.charAt(i));
    }
}).collect(Collectors.joining());

A slight improvement (debatable) on @Elliot answer is to create the StringBuilder with the original String and only replace when necessary

    String vowels = new StringBuilder(s.replaceAll("[^AEIOUaeiou]", ""))
            .reverse().toString();
    StringBuilder sb = new StringBuilder(s);
    for (int i = 0, p = 0; i < s.length(); i++) {
        switch (Character.toLowerCase(s.charAt(i))) {
        // Note that these fall-through...
        case 'a': case 'e': case 'i': case 'o': case 'u':
            sb.setCharAt(i, vowels.charAt(p++));
            break;
        default:
            break;
        }
    }
    return sb.toString();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM