给出一个输入字符串,逐个单词地反转字符串。例如,给定s = "the sky is blue",返回"blue is sky the"
什么构成一个单词?

非序列-space字符构成一个单词。

输入字符串是否可以包含前导或尾随空格?

是。但是,您的反向字符串不应包含前导或尾随空格。

两个单词之间的多个空格怎么样?

将它们缩小为反向字符串中的单个空格。


public static String reverseWords(String s){
    StringTokenizer strTok = new StringTokenizer(s, " ");
    Stack<String> stack=new Stack<String>();
    StringBuilder buff=new StringBuilder();
    while(strTok.hasMoreElements()) {
        String str = (String)strTok.nextToken();
        if(!str.equals("")) stack.push(str);
    }
    while(!stack.isEmpty()){
        buff.append(stack.pop()); 
        if(!stack.isEmpty()) buff.append(" ");
    }
    return buff.toString();
 }


评论

Java类库中有一个(鲜为人知的)类用于解决这些问题。 BreakIterator:docs.oracle.com/javase/7/docs/api/java/text/BreakIterator.html Javadoc甚至有一个示例,它几乎可以完全满足您的要求。

@tom:我很想看看BreakIterator的问题示例(作为答案:)

是他们缺少的东西吗?使用.toCharArray()或.split(String s)。

#1 楼



Stack似乎或多或少已被弃用。 Javadoc表示以下内容:Deque接口及其
实现提供了一套更完整和一致的LIFO堆栈
操作,应在对此类的偏爱。


我想在这里ArrayDeque是个不错的选择。


似乎StringTokenizer也已被弃用。 Javadoc表示以下内容:StringTokenizer是一个遗留类,出于兼容性原因而保留,尽管在新代码中不鼓励使用它。
建议任何寻求此功能的人都使用String的
split方法或java.util.regex包。



我已重命名



buffresult

stackwords


strword


描述其目的。可以轻松阅读和帮助理解代码。


您可以将buff的声明移到第二个while循环之前。

StringBuilder buff = new StringBuilder();
while (!stack.isEmpty()) {
    buff.append(stack.pop());
    if (!stack.isEmpty())
        buff.append(" ");
}


尝试最小化局部变量的范围。不必在方法开始时声明它们,而在首次使用它们的地方声明它们。 (有效的Java,第二版,第45项:最小化局部变量的范围)



if(!str.equals(""))



可以使用String.isEmpty()这里。更容易阅读,因为它用与英语相似的语言表示这种情况的作用。



if(!str.equals("")) stack.push(str);



Java编程语言的代码约定,


if语句始终使用大括号{}。


容易出错。我发现上面的单行代码很难阅读,因为如果您逐行扫描代码,很容易会错过在行尾有一个语句(push)的信息。


很高兴知道Apache Commons Lang中有一个类似的函数:StringUtils.reverseDelimited。您可以在线检查实现的其他思想。

(另请参见:有效的Java,第二版,项目47:了解和使用库作者仅提及JDK的内置库,但我认为推理对其他库也可能是正确的。)



以下是带有Deque的修改后的代码和其他建议:

public static String reverseWords(String input) {
    Deque<String> words = new ArrayDeque<>();
    for (String word: input.split(" ")) {
        if (!word.isEmpty()) {
            words.addFirst(word);
        }
    }
    StringBuilder result = new StringBuilder();
    while (!words.isEmpty()) {
        result.append(words.removeFirst());
        if (!words.isEmpty()) {
            result.append(" ");
        }
    }
    return result.toString();
}


评论


\ $ \ begingroup \ $
非常微小的一点,但是result.append(“”)比result.append('')昂贵。比较:append(char)与append(String)-如果仅追加一个字符,则仅增加一个字符。您需要查找字符串的大小,获取支持数组等的字符串。
\ $ \ endgroup \ $
–user22048
2014-3-10的2:27



\ $ \ begingroup \ $
好答案。我会选择使用像wordSet或wordList这样的变量名来代替单词,因为它很难区分代码中的单词和单词
\ $ \ endgroup \ $
– Arvind Sridharan
2014年3月14日下午3:27

#2 楼

我只需要使用split,然后向后迭代结果数组即可创建结果:

@Nonnull
public static String reverse(@Nonnull final String sentence) {
  final StringBuilder result = new StringBuilder();
  final String[] words = sentence.split("\s+");      
  for (int i = words.length - 1 ; 0 <= i; i--) {
    result.append(words[i]).append(' ');
  }
  return result.toString().trim();
}


P.s。我认为StackStringTokenizer对此有点繁琐,没有它们,实现起来还是很苗条的。

评论


\ $ \ begingroup \ $
我> = 0我认为。
\ $ \ endgroup \ $
– Anirban Nag'tintinmj'
2014年9月9日15:56

\ $ \ begingroup \ $
@tintinmj:您说对了,将其修复
\ $ \ endgroup \ $
– MrSmith42
2014年9月9日18:41

#3 楼

在这里使用Stack和StringTokenizer似乎有点过大。可以这样编写一个更简化的版本:

public static String reverse(final String input) {
  Objects.requireNonNull(input);
  final StringBuilder stringBuilder = new StringBuilder();
  for (final String part : input.split("\s+")) {
    if (!part.isEmpty()) {
      if (stringBuilder.length() > 0) {
        stringBuilder.insert(0, " ");
      }
      stringBuilder.insert(0, part);
    }
  }
  return stringBuilder.toString();
}


它使用split()的能力根据给定的Regex将字符串分开。因此,将根据一个或多个空白序列对字符串进行拆分,这与您的初始要求相符。请注意,如果输入为空字符串,则split将返回一个空数组。

for-each循环然后遍历该数组并将该部分插入stringBuilder的开头(第一次除外) ),这将有效地反转数组。大多数人可能会在这里使用反向for(i...)循环,但是因为这是代码审查,所以我尝试更加正确,这是更安全的版本:您不能通过for-each引起ArrayIndexOutOfBoundsExceptions。

任何前导/后缀空格将导致空白部分出现在拆分列表中,因此在循环中对其进行了检查。我在这里使用isEmpty(),因为它通常比length() > 0更安全(例如:做错字的机会更少),并且在执行速度方面受益于String的潜在内部优化。您可以预先调用trim(),但这会导致一些不必要的String操作和创建,因此此版本效率更高。

请还请注意在开始时对null的检查。尝试在null上进行拆分时,否则会抛出该代码,但事先进行检查被认为是一种好习惯,因为从理论上讲(不在此代码中,而是在其他代码中),否则可能会使系统处于未定义状态。

我还使用final,它可以进行一些编译器优化,并防止您遇到一些基本的编码错误,这被认为是很好的做法。

测试

对功能进行一些快速测试(包括基本的边沿情况)总是一个好主意,因此这里是:

要检查我是否正确去除了空白,我将stringBuilder.insert(0, " ")更改为stringBuilder.insert(0, "+")进行测试,以使空白可见。

使用System.out.println(reverse("the sky is blue"));测试时,结果为:
blue+is+sky+the满足初始要求。

使用System.out.println(reverse(" \t the sky is\t blue "));测试时,结果为:
blue+is+sky+the正确删除了开头,结尾和中间的空白。

使用reverse("")测试时,结果为预期的空字符串。

当使用reverse(null)测试时,在函数的开头抛出NullPointerException。

评论


\ $ \ begingroup \ $
使用单个字符进行插入和追加时,insert(int,char)的代码比insert(int,String)的代码便宜-chars始终是单个单位,而Strings需要查找的大小字符串,获取支持数组,检查null等。
\ $ \ endgroup \ $
–user22048
2014-3-10的2:30



\ $ \ begingroup \ $
是的,这是一个好点。但也仅在上述方法每秒运行一百万次时才有意义。 ;)
\ $ \ endgroup \ $
–TwoThe
2014-03-10 11:47

\ $ \ begingroup \ $
使用字符版本而不是字符串版本,但对应用程序不重要的事情要牢记,并成为一个人的标准“这就是你的方式”,这是一件好事这样,无需考虑将来是否应该使用“”或“”。尽管此处的性能可能不是问题,但遵循最佳实践并预先编写最有效的代码始于一次代码审查。
\ $ \ endgroup \ $
–user22048
2014年10月10日14:30

#4 楼

这种类型的问题总是很有趣,因为我总是觉得做事情是最简单,最好的方法。最简单的方法通常是使用本机机制进行工作。我认为最好的方法是经常使用基元并尽可能少地创建对象。

这是我为最简单/最好的两个建议解决方案。最简单的方法是使用String.split()方法,但是与此处建议的其他解决方案不同,它是纯粹的“加法”。中间没有干扰,也没有插入多余的东西而被删除。它还进行一些输入验证。在拆分之前“修整”值很重要。...

“最佳”选项是普通O(n)性能(某些解决方案中使用的StringBuilder.insert(...)不会产生整体O(n )解决方案。此建议的解决方案不会创建其他解决方案创建的所有中间String对象,而是使用O(1)额外的内存空间。

public static void main(String[] args) {
    String[] input = {"", null, "a", "this is", "this    is", " this is "};
    for (String in : input) {
        System.out.println("Split: -> |" + reverseString(in) + "|");
        System.out.println("Array: -> |" + reverseStringFP(in) + "|");

    }
}


private static final String reverseString(String input) {
    if (input == null) {
        return null;
    }
    String[] parts = input.trim().split("\s+");
    if (parts.length == 0) {
        return "";
    }
    StringBuilder sb = new StringBuilder();
    sb.append(parts[parts.length - 1]);
    for (int i = parts.length - 2; i >= 0; i--) {
        sb.append(" ").append(parts[i]);
    }
    return sb.toString();
}

private static final String reverseStringFP(final String input) {
    if (input == null) {
        return null;
    }
    if (input.isEmpty()) {
        return "";
    }
    final char[] inchar = input.toCharArray();
    // + 1 to allow for a temporary trailing space
    final char[] outchar = new char[inchar.length + 1];
    int outpos = 0;
    int wordend = inchar.length - 1;
    while (wordend >= 0) {
        while (wordend >= 0 && inchar[wordend] == ' ') {
            wordend--;
        }
        int wordstart = wordend;
        while (wordstart > 0 && inchar[wordstart - 1] != ' ') {
            wordstart--;
        }
        if (wordstart >= 0) {
            int len = wordend - wordstart + 1;
            System.arraycopy(inchar, wordstart, outchar, outpos, len);
            outpos += len;
            outchar[outpos++] = ' ';
            wordend = wordstart - 1;
        }
    }
    if (outpos > 0) {
        // deal with trailing space
        outpos--;
    }
    return new String(outchar, 0, outpos);
}


评论


\ $ \ begingroup \ $
+1。小注释:我将使用变量而不是注释:boolean hasTrailingSpace = outpos> 0;如果(hasTrailingSpace)...
\ $ \ endgroup \ $
–palacsint
2014年9月9日13:33

\ $ \ begingroup \ $
FP在reverseStringFP中代表什么?
\ $ \ endgroup \ $
– Anirban Nag'tintinmj'
2014年3月9日在16:02



\ $ \ begingroup \ $
@tintinmj-第一原理-没有引用任何其他库...我怎么想象基本的实现会做到这一点。
\ $ \ endgroup \ $
–rolfl
2014年9月9日在16:06

\ $ \ begingroup \ $
嗯,但是您不使用System和String吗?
\ $ \ endgroup \ $
– Anirban Nag'tintinmj'
2014年9月9日在16:10

\ $ \ begingroup \ $
人们划清界限是他们的选择。我没有使用Java语言规范和java.lang。*之外的任何内容(即,我没有使用正则表达式,Collections等,它们本身必须对值逐个字符进行迭代)
\ $ \ endgroup \ $
–rolfl
2014年9月9日在16:13