繁体   English   中英

如何拆分字符串,包括标点符号?

[英]How to split a string, including punctuation marks?

我需要拆分一个字符串(在Java中),标点符号与单词存储在同一个数组中:

String sentence = "In the preceding examples, classes derived from...";
String[] split = sentence.split(" ");

我需要拆分数组:

split[0] - "In"
split[1] - "the"
split[2] - "preceding"
split[3] - "examples"
split[4] - ","
split[5] - "classes"
split[6] - "derived"
split[7] - "from"
split[8] - "..."

有没有优雅的解决方案?

你需要环顾四周:

String[] split = sentence.split(" ?(?<!\\G)((?<=[^\\p{Punct}])(?=\\p{Punct})|\\b) ?");

环顾四周断言 ,但(重要的是这里)匹配时不消耗输入。


一些测试代码:

String sentence = "Foo bar, baz! Who? Me...";
String[] split = sentence.split(" ?(?<!\\G)((?<=[^\\p{Punct}])(?=\\p{Punct})|\\b) ?");
Arrays.stream(split).forEach(System.out::println);

输出;

Foo
bar
,
baz
!
Who
?
Me
...

您可以尝试先用省略号字符替换三点:

    String sentence = "In the preceding examples, classes derived from...";
    String[] split = sentence.replace("...", "…").split(" +|(?=,|\\p{Punct}|…)");

之后您可以保持原样,或者通过在整个阵列上运行replace("…", "...")将其转换回来。

我相信这种方法会做你想要的

public static List<String> split(String str) {
    Pattern pattern = Pattern.compile("(\\w+)|(\\.{3})|[^\\s]");
    Matcher matcher = pattern.matcher(str);
    List<String> list = new ArrayList<String>();
    while (matcher.find()) {
        list.add(matcher.group());
    }
    return list;
}

它会将一个字符串拆分成

  1. 连续的字符
  2. 省略号...
  3. 由空格分隔的任何其他东西

对于这个例子

"In the preceding examples, classes.. derived from... Hello, World! foo!bar"

名单将是

[0] In
[1] the
[2] preceding
[3] examples
[4] ,
[5] classes
[6] .
[7] .
[8] derived
[9] from
[10] ...
[11] Hello
[12] ,
[13] World
[14] !
[15] foo
[16] !
[17] bar

现在我要说最简单,也许最干净的方法来实现你想要的是专注于在数组中找到你想要的数据,而不是找到分割文本的地方。

我这样说是因为split引入了很多问题,例如:

  • split(" +|(?=\\\\p{Punct})"); 将仅在空格和标点符号分割,这意味着像"abc" def这样的文本将被拆分为"abc " def 所以当你看到它 " in "abc 之后不会分裂。

  • 以前的问题可以通过添加另一个|(?<=\\\\p{Punct})条件来轻松解决,例如split(" +|(?=\\\\p{Punct})|(?<=\\\\p{Punct})") ,但由于...我们仍然没有解决你所有的问题。 因此,我们需要找出防止这些点之间分裂的方法.|.|.

    • 要做到这一点,我们可以尝试排除. 来自\\p{Punct}并尝试单独处理它但这会使我们的正则表达式相当复杂。
    • 其他方法可以用一些独特的字符串替换... ,在我们的split逻辑中添加此字符串,然后在我们的结果数组中将其替换回... 但是这种方法也需要我们知道在你的文本中永远不可能有什么字符串,所以我们每次解析文本时都需要生成它。
  • 另一个可能的问题是pre-java-8正则表达式引擎将在结果数组的开头生成空元素,如果标点符号将是第一个字符,如" 。所以在Java 7中"foo" bar string split on (?=\\p{Punct)将导致[ , "foo, " bar]元素。为了避免这个问题,你需要添加类似(?!^)正则表达式以防止在字符串的开头拆分。

无论如何这些解决方案看起来过于复杂。


因此,考虑使用Matcher类中的find方法而不是split方法,而是关注结果数组中要包含的内容。

尝试使用这样的模式: [.]{3}|\\p{Punct}|[\\S&&\\P{Punct}]+"

  • [.]{3}将匹配...
  • \\p{Punct}将匹配单个标点字符,根据文档是其中之一!"#$%&'()*+,-./:;<=>?@[]^_`{|}~

    ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \\ ] ^ _ ` { | } ~

  • [\\S&&\\P{Punct}]+将匹配一个或多个字符
    • \\S不是空白
    • &&
    • \\P{Punct}不是标点字符( \\P{foo}\\p{foo} )的否定。

演示:

String sentence = "In (the) preceding examples, classes derived from...";
Pattern p = Pattern.compile("[.]{3}|\\p{Punct}|[\\S&&\\P{Punct}]+");
Matcher m = p.matcher(sentence);
while(m.find()){
    System.out.println(m.group());
}

输出:

In
(
the
)
preceding
examples
,
classes
derived
from
...

你可以清除字符串替换,用“,”等表示“,”,等等你要区分的所有标点符号。

在“......”的特定情况下,您可以:

// there can be series of dots
sentence.replace(".", " .").replace(". .", "..")

然后你分手了。

编辑:用双引号替换单引号。

对于您的特定情况,两个主要挑战是排序(例如,第一个标点符号,然后是单词或其他方式)和...标点符号。

其余的你可以使用它轻松实现它

\p{Punct}

像这样:

Pattern.compile("\p{Punct}");

关于提到的两个挑战:

1.订购:您可以尝试以下方法:

private static final Pattern punctuation = Pattern.compile("\\p{Punct}");
private static final Pattern word = Pattern.compile("\\w");

public static void main(String[] args) {
    String sentence = "In the preceding examples, classes derived from...";
    String[] split = sentence.split(" ");
    List<String> result = new LinkedList<>();

    for (String s : split) {
        List<String> withMarks = splitWithPunctuationMarks(s);
        result.addAll(withMarks);
    }
}

private static void List<String> splitWithPunctuationMarks(String s) {
    Map<Integer, String> positionToString = new TreeMap<>();
    Matcher punctMatcher = punctuation.matcher(s);
    while (punctMatcher.find()) {
        positionToString.put(punctMatcher.start(), punctMatcher.group())
    }
    Matcher wordMatcher = // ... same as before
    // Then positionToString.values() will contain the 
    // ordered words and punctuation characters.
}
  1. ...你可以尝试回顾之前发生过的事情. 每次找到它时,(currentIndex - 1)处的字符。

另一个例子。 此解决方案可能适用于所有组合。

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class App {

    public static void main(String[] args) {    
        String sentence = "In the preceding examples, classes derived from...";
        List<String> list = splitWithPunctuation(sentence);
        System.out.println(list);
    }

    public static List<String> splitWithPunctuation(String sentence) {
        Pattern p = Pattern.compile("([^a-zA-Z\\d\\s]+)");
        String[] split = sentence.split(" ");
        List<String> list = new ArrayList<>();

        for (String s : split) {
            Matcher matcher = p.matcher(s);
            boolean found = false;
            int i = 0;
            while (matcher.find()) {
                found = true;
                list.add(s.substring(i, matcher.start()));
                list.add(s.substring(matcher.start(), matcher.end()));
                i = matcher.end();
            }

            if (found) {
                if (i < s.length())
                    list.add(s.substring(i, s.length()));
            } else
                list.add(s);
        }

        return list;
    }
}

输出:

In
the
preceding
examples
,
classes
derived
from 
...

一个更复杂的例子:

String sentence = "In the preced^^^in## examp!les, classes derived from...";
List<String> list = splitWithPunctuation(sentence);
System.out.println(list);

输出:

In
the
preced
^^^
in
##
examp
!
les
,
classes
derived
from
...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM