简体   繁体   中英

Reverse the words in a sentence but not punctuation using recursion

How to reverse the words in a sentence, but not punctuation using recursion. The sentence is said to use punctuation marks: ,.?!

Input: "Jack, come home!"

Output: "home, come Jack!"

Now I have somehow managed to complete the task correctly but without using recursion. How should I convert this work to use recursion to solve the problem?

Here's the method:

public static StringBuilder reverseSentenceWithPunctuation(String sentence, int i) {
        String[] parts = sentence.split(" ");
        StringBuilder newSentence = new StringBuilder();
        Map<Integer, Character> punctuationMap = new HashMap<>();

        for (int j = 0; j < parts.length; j++) {
            if (parts[j].endsWith(",") || parts[j].endsWith(".") || parts[j].endsWith("!") || parts[j].endsWith("?")) {
                char lastSymbol = parts[j].charAt(parts[j].length()-1);
                punctuationMap.put(j, lastSymbol);
                String changedWord = parts[j].replace(String.valueOf(lastSymbol), "");
                parts[j] = changedWord;
            }
        }

        for (int j = parts.length-1; j >= 0; j--) {
            newSentence.append(parts[j]);
            if (punctuationMap.containsKey(i)) {
                newSentence.append(punctuationMap.get(i));
                newSentence.append(" ");
            } else
                newSentence.append(" ");
            i++;
        }
        return newSentence;
    }

Thanks in advance!

To implement this task using recursion, a pattern matching the first and the last words followed by some delimiters should be prepared:

word1 del1 word2 del2 .... wordLast delLast

In case of matching the input the result is calculated as:

wordLast del1 REVERT(middle_part) + word1 delLast

Example implementation may be as follows (the words are considered to contain English letters and apostrophe ' for contractions):

static Pattern SENTENCE = Pattern.compile("^([A-Za-z']+)([^A-Za-z]+)?(.*)([^'A-Za-z]+)([A-Za-z']+)([^'A-Za-z]+)?$");

public static String revertSentence(String sentence) {
    
    Matcher m = SENTENCE.matcher(sentence);
    if (m.matches()) {
        return m.group(5) + (m.group(2) == null ? "" : m.group(2)) 
            + revertSentence(m.group(3) + m.group(4)) // middle part
            + m.group(1) + (m.group(6) == null ? "" : m.group(6));
    }
    return sentence;
}

Tests:

System.out.println(revertSentence("Jack, come home!"));
System.out.println(revertSentence("Jack, come home please!!"));
System.out.println(revertSentence("Jane cried: Will you come home Jack, please, don't go!"));

Output:

home, come Jack!
please, home come Jack!!
go don't: please Jack home come you, Will, cried Jane!

I don't think this is a good case for a recursive function, mainly because you need 2 loops. Also, in general, iterative algorithms are better performance-wise and won't throw a stackoverflow exception.

So I think the main reasons to work with recursive functions is readability and easiness, and honestly, in this case, I think it isn't worth it.

In any case, this is my attempt to convert your code to a recursive function. As stated before, I use 2 functions because of the 2 loops. I'm sure there is a way to achieve this with a single function that first loads the map of punctuations and then compose the final String, but to be honest that would be quite ugly.

import java.util.*;
import java.util.stream.*;

public class HelloWorld{

    static Character[] punctuationCharacters = {',','.','!'};

    public static void main(String []args){
        System.out.println(reverseSentenceWithPunctuation("Jack, come home!"));
    }
     
    private static String reverseSentenceWithPunctuation(String sentence) {
        
        String[] parts = sentence.split(" ");

        return generate(0, parts, extractPunctuationMap(0, parts));
    }
     
    private static Map<Integer, Character> extractPunctuationMap(int index, String[] parts){
        Map<Integer, Character> map = new HashMap<>();
        if (index >= parts.length) {
            return map;
        }
        char lastSymbol = parts[index].charAt(parts[index].length() - 1);
        if (Arrays.stream(punctuationCharacters).anyMatch(character -> character == lastSymbol)) {
            parts[index] = parts[index].substring(0, parts[index].length() - 1);
            map = Stream.of(new Object[][] { 
             { index,  lastSymbol}
            }).collect(Collectors.toMap(data -> (Integer) data[0], data -> (Character) data[1]));
        }
        map.putAll(extractPunctuationMap(index + 1, parts));
        return map;
    }
    
    private static String generate(int index, String[] parts, Map<Integer, Character> punctuationMap) {
        
        if (index >= parts.length) {
            return "";
        }
        String part = index == 0? " " + parts[index] : parts[index];
        if (punctuationMap.containsKey(parts.length -1 - index)) {
            part += punctuationMap.get(parts.length -1 - index);
        }
        return generate(index + 1, parts, punctuationMap) + part;
        
    }
}

In pseudocode maybe something like that:

  1. take the whole sentence

(a). get the first word

(b). get the last word

(if there is a punctuation after the first or last word, leave it there)

  1. swap(a, b) and return the remaining middle of the sentence
  2. repeat (1) and (2) until there is only two words or one
  3. return the last two (swapped) words left (if one word, just return that)

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