【问题标题】:Modify the characters of words in a Java string with punctuation, but keep the positions of said punctuation?用标点修改Java字符串中单词的字符,但保留所述标点的位置?
【发布时间】:2019-04-15 03:09:13
【问题描述】:

例如,使用下面的Strings 列表,忽略引号:

"Hello"
"Hello!"
"I'm saying Hello!"
"I haven't said hello yet, but I will."

现在假设我想对每个单词的字符执行某种操作——例如,假设我想反转字符,但是保持标点的位置。所以结果是:

"olleH"
"olleH!"
"m'I gniyas olleH!"
"I tneva'h dias olleh tey, tub I lliw."

理想情况下,我希望我的代码独立于对字符串执行的操作(另一个例子是字母的随机改组),并且独立于所有标点符号 - 所以连字符、撇号、逗号、句号、en/em 破折号等。all 在执行操作后保持在原来的位置。这可能需要某种形式的正则表达式。

为此,我在想我应该保存给定单词中所有标点符号的索引和字符,执行操作,然后在正确位置重新插入所有标点符号。但是,我想不出一种方法来做到这一点,或者使用一个类。

我有第一次尝试,但不幸的是这不适用于标点符号,这是关键:

jshell> String str = "I haven't said hello yet, but I will."
str ==> "I haven't said hello yet, but I will."

jshell> Arrays.stream(str.split("\\s+")).map(x -> (new StringBuilder(x)).reverse().toString()).reduce((x, y) -> x + " " + y).get()
$2 ==> "I t'nevah dias olleh ,tey tub I .lliw"

有人知道我该如何解决这个问题吗?非常感谢。不需要完整的工作代码——也许只是我可以用来执行此操作的适当类的路标。

【问题讨论】:

  • 我认为您需要正则表达式来匹配非字母数字字符,然后记下它们发生的索引,删除它们并在反转后重新插入。
  • 尝试使用过滤器

标签: java string


【解决方案1】:

不需要为此使用正则表达式,当然也不应该使用split("\\s+"),因为您会丢失连续的空格,并且空白字符的类型(即结果的空格)可能不正确。

您也不应该使用 charAt() 或类似的东西,因为它不支持来自 Unicode 补充平面的字母,即作为代理对存储在 Java 字符串中的 Unicode 字符。

基本逻辑:

  • 找到单词的开头,即字符串的开头或空格后面的第一个字符。
  • 找到单词的结尾,即空格或字符串结尾之前的最后一个字符。
  • 从头到尾并行迭代:
    • 跳过非字母字符。
    • 交换字母。

作为 Java 代码,具有完整的 Unicode 支持:

public static String reverseLettersOfWords(String input) {
    int[] codePoints = input.codePoints().toArray();
    for (int i = 0, start = 0; i <= codePoints.length; i++) {
        if (i == codePoints.length || Character.isWhitespace(codePoints[i])) {
            for (int end = i - 1; ; start++, end--) {
                while (start < end && ! Character.isLetter(codePoints[start]))
                    start++;
                while (start < end && ! Character.isLetter(codePoints[end]))
                    end--;
                if (start >= end)
                    break;
                int tmp = codePoints[start];
                codePoints[start] = codePoints[end];
                codePoints[end] = tmp;
            }
            start = i + 1;
        }
    }
    return new String(codePoints, 0, codePoints.length);
}

测试

System.out.println(reverseLettersOfWords("Hello"));
System.out.println(reverseLettersOfWords("Hello!"));
System.out.println(reverseLettersOfWords("I'm saying Hello!"));
System.out.println(reverseLettersOfWords("I haven't said hello yet, but I will."));
System.out.println(reverseLettersOfWords("Works with surrogate pairs: ???+? "));

输出

olleH
olleH!
m'I gniyas olleH!
I tneva'h dias olleh tey, tub I lliw.
skroW htiw etagorrus sriap: ???+? 

请注意,末尾的特殊字母是“脚本(或书法)”、“粗体”列中显示的前 4 个 here,例如? 是 Unicode Character 'MATHEMATICAL BOLD SCRIPT CAPITAL A' (U+1D4D0),在 Java 中是两个字符 "\uD835\uDCD0"


更新

上面的实现是为反转单词的字母而优化的。要应用任意操作来破坏单词的字母,请使用以下实现:

public static String mangleLettersOfWords(String input) {
    int[] codePoints = input.codePoints().toArray();
    for (int i = 0, start = 0; i <= codePoints.length; i++) {
        if (i == codePoints.length || Character.isWhitespace(codePoints[i])) {
            int wordCodePointLen = 0;
            for (int j = start; j < i; j++)
                if (Character.isLetter(codePoints[j]))
                    wordCodePointLen++;
            if (wordCodePointLen != 0) {
                int[] wordCodePoints = new int[wordCodePointLen];
                for (int j = start, k = 0; j < i; j++)
                    if (Character.isLetter(codePoints[j]))
                        wordCodePoints[k++] = codePoints[j];
                int[] mangledCodePoints = mangleWord(wordCodePoints.clone());
                if (mangledCodePoints.length != wordCodePointLen)
                    throw new IllegalStateException("Mangled word is wrong length: '" + new String(wordCodePoints, 0, wordCodePoints.length) + "' (" + wordCodePointLen + " code points)" +
                                                                " vs mangled '" + new String(mangledCodePoints, 0, mangledCodePoints.length) + "' (" + mangledCodePoints.length + " code points)");
                for (int j = start, k = 0; j < i; j++)
                    if (Character.isLetter(codePoints[j]))
                        codePoints[j] = mangledCodePoints[k++];
            }
            start = i + 1;
        }
    }
    return new String(codePoints, 0, codePoints.length);
}
private static int[] mangleWord(int[] codePoints) {
    return mangleWord(new String(codePoints, 0, codePoints.length)).codePoints().toArray();
}
private static CharSequence mangleWord(String word) {
    return new StringBuilder(word).reverse();
}

如果需要,您当然可以将对 mangleWord 方法的硬编码调用替换为对传入的 Function&lt;int[], int[]&gt;Function&lt;String, ? extends CharSequence&gt; 参数的调用。

mangleWord 方法实现的结果与原始实现相同,但您现在可以轻松实现不同的修饰算法。

例如要随机化字母,只需 shuffle codePoints 数组:

private static int[] mangleWord(int[] codePoints) {
    Random rnd = new Random();
    for (int i = codePoints.length - 1; i > 0; i--) {
        int j = rnd.nextInt(i + 1);
        int tmp = codePoints[j];
        codePoints[j] = codePoints[i];
        codePoints[i] = tmp;
    }
    return codePoints;
}

样本输出

Hlelo
Hlleo!
m'I nsayig oHlel!
I athen'v siad eohll yte, btu I illw.
srWok twih rueoatrsg rpasi: ???+?

【讨论】:

  • 整洁。有没有办法可以将其扩展为对单词的任何其他操作,例如随机改组?也许使用Function
【解决方案2】:

我怀疑有一个更有效的解决方案,但这是一个幼稚的解决方案:

  1. 在空格上将句子拆分为单词(注意 - 如果您有多个空格,我的实现会出现问题)
  2. 带标点符号
  3. 将每个单词颠倒
  4. 遍历每个字母,并根据需要插入反转单词中的字符并插入原始单词中的标点符号
public class Reverser {

    public String reverseSentence(String sentence) {
        String[] words = sentence.split(" ");
        return Arrays.stream(words).map(this::reverseWord).collect(Collectors.joining(" "));
    }

    private String reverseWord(String word) {
        String noPunctuation = word.replaceAll("\\W", "");
        String reversed = new StringBuilder(noPunctuation).reverse().toString();
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < word.length(); ++i) {
            char ch = word.charAt(i);
            if (!Character.isAlphabetic(ch) && !Character.isDigit(ch)) {
                result.append(ch);
            }
            if (i < reversed.length()) {
                result.append(reversed.charAt(i));
            }
        }
        return result.toString();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-09
    • 1970-01-01
    • 1970-01-01
    • 2013-12-25
    相关资源
    最近更新 更多