【问题标题】:Java string tokenization: Split on pattern and retain patternJava 字符串标记化:按模式拆分并保留模式
【发布时间】:2018-02-06 15:43:06
【问题描述】:

我的问题是 Python 上 this 查询的 Scala (Java) 变体。

特别是,我有一个字符串val myStr = "Shall we meet at, let's say, 8:45 AM?"。我想标记它 并 保留分隔符(除了空格)。如果我的分隔符只是字符,例如., :, ? 等等,我可以这样做:

val strArr = myStr.split("((\\s+)|(?=[,.;:?])|(?<=\\b[,.;:?]))")

产生

[Shall, we, meet, at, ,, let's, say, ,, 8, :, 45, AM, ?]

但是,我希望将拍号\\d+:\\d+ 设为分隔符,并且仍想保留它。所以,我想要的是

[Shall, we, meet, at, ,, let's, say, ,, 8:45, AM, ?]

注意:

  1. 在 split 语句的表达式中添加析取 (?=(\\d+:\\d+)) 没有帮助
  2. 在拍号之外,: 本身就是一个分隔符

我怎样才能做到这一点?

【问题讨论】:

  • 你有机会检查我的方法吗?
  • 是的,到目前为止,我一直在检查提到的两种方法。我只是在我拥有的更通用的示例上进行尝试。
  • 好,请注意我的方法匹配 1) 时间子字符串作为整个单词,或 2) 任何分隔符字符块,或 3) 任何不是你的分隔符和时间字符串作为整个单词.我相信它足够全面,可以按照您需要的方式标记字符串。

标签: java regex string scala tokenize


【解决方案1】:

我建议匹配所有标记,而不是拆分字符串,因为这样您可以更好地控制获得的内容:

 \b\d{1,2}:\d{2}\b|[,.;:?]+|(?:(?!\b\d{1,2}:\d{2}\b)[^\s,.;:?])+

请参阅regex demo。

我们开始匹配最具体的模式,最后一个是最通用的。

详情

  • \b\d{1,2}:\d{2}\b - 1 到 2 位数字,:,用字边界括起来的 2 位数字
  • | - 或
  • [,.;:?]+ - 1 个或多个 ,、.、;、:、? 字符
  • | - 或
  • (?:(?!\b\d{1,2}:\d{2}\b)[^\s,.;:?])+ - 匹配任何不是我们的分隔符字符或空格 ([^\s,.;:?]) 的字符,它们不是时间字符串的起点。

考虑this snippet:

val str = "Shall we meet at, let's say, 8:45 AM?"
var rx = """\b\d{1,2}:\d{2}\b|[,.;:?]+|(?:(?!\b\d{1,2}:\d{2}\b)[^\s,.;:?])+""".r
rx findAllIn str foreach println

输出:

Shall
we
meet
at
,
let's
say
,
8:45
AM
?

【讨论】:

  • 请注意,如果您只想匹配任意上下文中的 1-2 位、:、2 位的序列,则可以删除 \d{1,2}:\d{2} 周围的单词边界。
  • 我的查询只是我拥有的大量用例中的一个示例。您的解决方案可以扩展到任意模式。例如。我可以为电子邮件地址、日期、时间戳等指定模式字符串。并将它们连接在一起。谢谢。
【解决方案2】:
/**
 * StringPatternTokenizer is simlular to java.util.StringTokenizer
 * But it uses regex string as the tokenizer separator.
 * See inside method #testCase for detail usage.
 */
public class StringPatternTokenizer {
    Pattern pattern;

    public StringPatternTokenizer(String regex) {
        this.pattern = Pattern.compile(regex);
    }

    public void getTokens(String str, NextToken nextToken) {
        Matcher matcher = pattern.matcher(str);

        int index = 0;
        Result result = null;
        while (matcher.find()) {
            if (matcher.start() > index) {
                result = nextToken.visit(null, str.substring(index, matcher.start()));
            }
            if (result != Result.STOP) {
                index = matcher.end();
                result = nextToken.visit(matcher, null);
            }

            if (result == Result.STOP) {
                return;
            }
        }

        if (index < str.length()) {
            nextToken.visit(null, str.substring(index));
        }
    }

    enum Result {
        CONTINUE,
        STOP,
    }

    public interface NextToken {
        Result visit(Matcher matcher, String str);
    }

    /***********************************/
    /***** test cases FOR IT ***********/
    /***********************************/

    public void testCase() {

        // as a test, it tries access tokenizer result for each part,
        // then replace variable parts by given values.
        // And finally, we collect the result target string as  output.

        String strSource = "My name is {{NAME}}, nice to meet you.";
        String strTarget = "My name is TokenTst, nice to meet you.";

        // separator pattern for: variable names in two curly brackets
        String variableRegex = "\\{\\{([A-Za-z]+)\\}\\}";

        // variable values
        org.json.JSONObject data = new org.json.JSONObject(
                java.util.Collections.singletonMap("NAME", "TokenTst")
        );

        StringBuilder sb = new StringBuilder();
        new StringPatternTokenizer(variableRegex)
                .getTokens(strSource, (matcher, str) -> {
                    sb.append(matcher == null ? str
                            : data.optString(matcher.group(1), ""));
                    return StringPatternTokenizer.Result.CONTINUE;
                });

        // check the result as expected
        org.junit.Assert.assertEquals(strTarget, sb.toString());
    }
}

【讨论】:

  • 纯代码答案不如带解释的代码有用。尤其是在回答这么老的问题时,指出您的答案与公认答案的不同之处/原因是有帮助的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-11
  • 1970-01-01
  • 2022-07-12
  • 1970-01-01
  • 2018-11-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多