使用Java8流使用不同的分隔符将字符串中单词的首字母大写

pgky5nke  于 2021-06-27  发布在  Java
关注(0)|答案(4)|浏览(481)

我需要将字符串中每个单词的第一个字母大写,但这并不像看上去那么容易,因为这个单词被认为是字母、数字、“\”、“-”、“`”的任意序列,而所有其他字符被认为是分隔符,即在它们之后的下一个字母必须大写。
示例程序应该做什么:
输入:“#他和我!“风险”
输出应该是:“#他和我!“风险”
这里有些问题听起来很相似,但解决方法确实没有帮助。例如:

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

因为在我的任务中可能有各种分隔符,所以这个解决方案不起作用。

5jvtdoz2

5jvtdoz21#

我需要把每个单词的首字母大写
这里有一个方法。诚然,这可能会更长,但您需要将第一个字母更改为大写(而不是第一个数字或第一个非字母),这需要一个helper方法。否则就容易多了。其他一些人似乎忽略了这一点。
建立单词模式,测试数据。

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);
}

e3bfsja2

e3bfsja22#

你不能轻易地使用split,split会消除分隔符,只给你中间的东西。因为你需要分离器,没有人能做到。
一个真正肮脏的把戏是使用一种叫做“向前看”的东西。传递给split的参数是一个正则表达式。regexp中的大多数“字符”都具有使用匹配输入的属性。如果你这样做了 input.split("\\s+") 然后,这不仅仅是对空白进行分割,它还会消耗它们:空白不再是字符串数组中单个条目的一部分。
但是,请考虑 ^ 以及 $ . 或者 \\b . 这些仍然匹配的东西,但不消耗任何东西。您不使用“字符串结尾”。事实上, ^^^hello$$$ 匹配字符串 "hello" 也一样。您可以自己使用lookahead:在存在lookahead但不使用它时进行匹配:

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

不幸的是,这样做“有效”,因为它节省了分隔符,但并没有让您离解决方案更近:


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

你也可以用lookback,但它是有限的;例如,他们不做可变长度。
结论应该很快变成:实际上,用 split 是个错误。
然后,一旦split离开了表,您就不应该再使用streams了,或者:一旦您需要了解流中前一个元素的信息来完成任务,streams就不能很好地工作:字符流不能工作,因为您需要知道前一个字符是否是非字母。
一般来说,“我想做x,用y”是个错误。保持开放的心态。这类似于问:“我想涂黄油,然后用锤子去做。”。哦,你也许可以这么做,但是,呃,为什么?抽屉里有黄油刀,只是。。把锤子放下,那是烤面包。不是钉子。
彼此彼此。
一个简单的循环可以解决这个问题,没有问题:

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();
}

很简单。效率高。易于阅读。易于修改。例如,如果您想使用“any non-letter counts”,琐碎: atBreak = Character.isLetter(c); .
与流解决方案相比,流解决方案脆弱、怪异、效率低得多,并且需要一个regexp,任何人都需要半页的注解才能理解它。
你能用溪流做这个吗?对。你也可以用锤子给烤面包涂黄油。但这不是个好主意。把锤子放下!

x6yk4ghg

x6yk4ghg3#

在遍历字符串中的字符时,可以使用一个简单的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)之外的字符。

az31mfrm

az31mfrm4#

可以拆分字符串并保留分隔符,因此考虑到对分隔符的要求:
单词被认为是任何字母、数字、“\”、“-”、“”的序列,而所有其他字符被认为是分隔符 在结果数组中保留分隔符的模式是:"((?<=[^-\\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} 可以使用,需要使用模式标志启用unicode字符的匹配 (?U) :

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

# Привет&`ёж`+Дос^Βιδ/Ως

相关问题