使用java8流,使用不同的分隔符将字符串中单词的第一个字母大写



我需要将字符串中每个单词的第一个字母大写,但这并不像看起来那么容易,因为单词被认为是任何字母、数字的序列_&"&quot-&""而所有其他字符都被认为是分隔符,即在它们之后的下一个字母必须大写。

程序应该做的示例:

对于输入:"#他&哇!r^ld";

输出应该是:#他&Llo Wo!R^Ld";

这里有一些问题听起来很相似,但有些解决方案确实于事无补。例如:

String output = Arrays.stream(input.split("[\s&]+"))
.map(t -> t.substring(0, 1).toUpperCase() + t.substring(1))
.collect(Collectors.joining(" "));

由于在我的任务中可能存在各种分隔符,因此此解决方案不起作用。

可以拆分字符串并保留分隔符,因此要考虑对分隔符的要求:

单词被认为是任何字母、数字、"的序列_&"&quot-&""而所有其他字符都被认为是分隔符

在结果数组中保留分隔符的模式为:"((?<=[^-`\w])|(?=[^-`\w]))":

[^-`\w]:除-以外的所有字符、反调和单词字符w:[A-Za-z0-9_]

然后;单词";大写,分隔符保持原样:

static String capitalize(String input) {
if (null == input || 0 == input.length()) {
return input;
}
return Arrays.stream(input.split("((?<=[^-`\w])|(?=[^-`\w]))"))
.map(s -> s.matches("[-`\w]+") ? Character.toUpperCase(s.charAt(0)) + s.substring(1) : s)
.collect(Collectors.joining(""));
}

测试:

System.out.println(capitalize("#he&l_lo-wo!r^ld"));
System.out.println(capitalize("#`he`&l+lo wo!r^ld"));

输出:

#He&l_lo-wo!R^Ld
#`he`&L+Lo Wo!R^Ld

更新
如果不仅需要处理ASCII字符集,还需要应用于其他字母或字符集(例如西里尔字母、希腊字母等),则可以使用POSIX类\p{IsWord},并且需要使用模式标志(?U):启用Unicode字符的匹配

static String capitalizeUnicode(String input) {
if (null == input || 0 == input.length()) {
return input;
}

return Arrays.stream(input.split("(?U)((?<=[^-`\p{IsWord}])|(?=[^-`\p{IsWord}]))")
.map(s -> s.matches("(?U)[-`\p{IsWord}]+") ? Character.toUpperCase(s.charAt(0)) + s.substring(1) : s)
.collect(Collectors.joining(""));
}

测试:

System.out.println(capitalizeUnicode("#he&l_lo-wo!r^ld"));
System.out.println(capitalizeUnicode("#привет&`ёж`+дос^βιδ/ως"));

输出:

#He&L_lo-wo!R^Ld
#Привет&`ёж`+Дос^Βιδ/Ως

你不能那么容易地使用split——split会消除分隔符,只给你介于两者之间的东西。由于你需要分离器,没有可以做。

一个真正肮脏的把戏是使用一种叫做"前瞻"的东西。传递给split的参数是一个正则表达式。正则表达式中的大多数"字符"都具有使用匹配输入的属性。如果您执行input.split("\s+"),那么这不仅"只"分割空白,还会消耗它们:空白不再是字符串数组中单个条目的一部分。

但是,请考虑^$。或CCD_ 11。这些仍然匹配东西,但不会消耗任何东西。您不使用"字符串末尾"。事实上,^^^hello$$$与字符串"hello"同样匹配。您可以使用前瞻自己完成此操作:当前瞻存在时匹配,但不会消耗它:

String[] args = "Hello World$Huh   Weird".split("(?=[\s_$-]+)");
for (String arg : args) System.out.println("*" + args[i] + "*");

不幸的是,这"有效",因为它节省了您的分离器,但并没有让您更接近一个解决方案:

*Hello*
* World*
*$Huh*
* *
* *
* Weird*

你也可以使用lookbacking,但它是有限的;例如,他们不做可变长度。

结论应该很快变成:事实上,用split这样做是一个错误。

然后,一旦拆分被取消,你也不应该再使用流:一旦你需要了解流中前一个元素的信息来完成这项工作,流就不好了:字符流不起作用,因为你需要知道前一个字符是否是非字母。

一般来说;我想做X;是个错误。保持开放的心态。这类似于问:";我想在我的吐司上涂黄油,然后用锤子来敲;。哦,你也许可以这么做,但是,嗯,为什么?抽屉里有黄油刀,只是。。放下锤子,干杯。一颗钉子也没有。

这里也是。

一个简单的循环可以解决这个问题,没有问题:

private static final String BREAK_CHARS = "&-_`";
public String toTitleCase(String input) {
StringBuilder out = new StringBuilder();
boolean atBreak = true;
for (char c : input.toCharArray()) {
out.append(atBreak ? Character.toUpperCase(c) : c);
atBreak = Character.isWhitespace(c) || (BREAK_CHARS.indexOf(c) > -1);
}
return out.toString();
}

很简单。有效率的易于阅读。易于修改。例如,如果您想使用"任何非字母计数",则琐碎:atBreak = Character.isLetter(c);

相比之下,流解决方案是脆弱的、奇怪的、效率低得多的,并且需要一个regexp,它需要半页的注释才能让任何人理解。

你能用流来做这件事吗?对你也可以用锤子在吐司上涂黄油。但这不是一个好主意放下锤子

您可以在遍历字符串中的字符时使用一个简单的FSM,它有两种状态,一个状态在一个单词中,另一个状态不在一个词中。如果你不在一个单词中,而下一个字符是一个字母,请将其转换为大写,否则,如果它不是一个字母或你已经在一个词中,只需不加修改地复制它。

boolean isWord(int c) {
return c == '`' || c == '_' || c == '-' || Character.isLetter(c) || Character.isDigit(c);
}
String capitalize(String s) {
StringBuilder sb = new StringBuilder();
boolean inWord = false;
for (int c : s.codePoints().toArray()) {
if (!inWord && Character.isLetter(c)) {
sb.appendCodePoint(Character.toUpperCase(c));
} else {
sb.appendCodePoint(c);
}
inWord = isWord(c);
}
return sb.toString();
}

注意:我使用了codePoints()appendCodePoint(int)int,以便正确处理基本多语言平面之外的字符(代码点大于64k)。

我需要在每个单词中的第一个字母大写

这里有一种方法。诚然,这可能需要更长的时间,但您需要将第一个字母改为大写(而不是第一个数字或第一个非字母),这需要一个辅助方法。否则会更容易。其他一些人似乎忽略了这一点。

建立单词模式,并测试数据。

String wordPattern = "[\w_-`]+";
Pattern p = Pattern.compile(wordPattern);
String[] inputData = { "#he&llo wo!r^ld", "0hel`lo-w0rld" };

现在,这只需根据已建立的正则表达式查找字符串中的每个连续单词。当找到每个单词时,它会将单词中的第一个字母改为大写,然后将其放在找到匹配的正确位置的字符串缓冲区中。

for (String input : inputData) {
StringBuilder sb = new StringBuilder(input);
Matcher m = p.matcher(input);
while (m.find()) {
sb.replace(m.start(), m.end(),
upperFirstLetter(m.group()));
}
System.out.println(input + " -> " + sb);
}

打印

#he&llo wo!r^ld -> #He&Llo Wo!R^Ld
0hel`lo-w0rld -> 0Hel`lo-W0rld

由于单词可能以数字开头,并且要求将第一个字母(而不是字符)转换为大写。此方法查找第一个字母,将其转换为大写,然后返回新字符串。因此01_hello将成为01_Hello


public static String upperFirstLetter(String word) {
char[] chs = word.toCharArray();
for (int i = 0; i < chs.length; i++) {
if (Character.isLetter(chs[i])) {
chs[i] = Character.toUpperCase(chs[i]);
break;
}
}
return String.valueOf(chs);
}

最新更新