给出一个输入字符串,逐个单词地反转字符串。例如,给定
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();
}
#1 楼
Stack
似乎或多或少已被弃用。 Javadoc表示以下内容:Deque接口及其实现提供了一套更完整和一致的LIFO堆栈
操作,应在对此类的偏爱。
我想在这里
ArrayDeque
是个不错的选择。似乎
StringTokenizer
也已被弃用。 Javadoc表示以下内容:StringTokenizer是一个遗留类,出于兼容性原因而保留,尽管在新代码中不鼓励使用它。 建议任何寻求此功能的人都使用String的
split方法或java.util.regex包。
我已重命名
buff
至result
stack
至words
str
至word
描述其目的。可以轻松阅读和帮助理解代码。
您可以将
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。我认为
Stack
和StringTokenizer
对此有点繁琐,没有它们,实现起来还是很苗条的。评论
\ $ \ 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
评论
Java类库中有一个(鲜为人知的)类用于解决这些问题。 BreakIterator:docs.oracle.com/javase/7/docs/api/java/text/BreakIterator.html Javadoc甚至有一个示例,它几乎可以完全满足您的要求。@tom:我很想看看BreakIterator的问题示例(作为答案:)
是他们缺少的东西吗?使用.toCharArray()或.split(String s)。