匹配令牌序列

时间:2011-04-30 00:07:21

标签: regex parsing sequences

我有一组n个令牌(例如,a,b,c)分布在一堆其他令牌中。我想知道我的集合中的所有成员是否出现在给定数量的位置(窗口大小)内。在我看来,有可能编写一个RegEx来捕获这个状态,但确切的语法使我无法接受。

          11111
012345678901234
ab ab bc  a cba

在此示例中,给定窗口大小= 5,我想匹配位置12-14的cba和位置3-7的abc

有没有办法用RegEx做到这一点,还是有其他类型的语法可以用来捕获这个逻辑?

我希望在Java中实现这一点。

4 个答案:

答案 0 :(得分:2)

这是一个匹配包含所有'a','b'和'c'的5个字母序列的正则表达式:

(?=.{0,4}a)(?=.{0,4}b)(?=.{0,4}c).{5}

因此,虽然基本上匹配任意5个字符(.{5}),但匹配必须遵守三个先决条件。它们中的每一个都需要一个令牌/字母(最多4个字符后跟'a'等)。 (?=X)匹配“X,零宽度正向前瞻”,其中零宽度表示匹配时不移动字符位置。

使用正则表达式执行此操作很慢,但是..这是一个更直接的版本(似乎比使用正则表达式快15倍):

public static void find(String haystack, String tokens, int windowLen) {
    char[] tokenChars = tokens.toCharArray();
    int hayLen = haystack.length();

    int pos = 0;
    nextPos:
    while (pos + windowLen <= hayLen) {
        for (char c : tokenChars) {
            int i = haystack.indexOf(c, pos);
            if (i < 0) return;

            if (i - pos >= windowLen) {
                pos = i - windowLen + 1;
                continue nextPos;
            }
        }

        // match found at pos
        System.out.println(pos + ".." + (pos + windowLen - 1) + ": " + haystack.substring(pos, pos + windowLen));
        pos++;
    }
}

答案 1 :(得分:2)

这个经过测试的Java程序有一个注释的正则表达式可以解决这个问题:

import java.util.regex.*;
public class TEST {
    public static void main(String[] args) {
        String s = "ab ab bc  a cba";
        Pattern p = Pattern.compile(
            "# Match 5 char sequences containing: a and b and c\n" +
            "(?=[abc])     # Assert first char is a, b or c.\n" +
            "(?=.{0,4}a)   # Assert an 'a' within 5 chars.\n" +
            "(?=.{0,4}b)   # Assert an 'b' within 5 chars.\n" +
            "(?=.{0,4}c)   # Assert an 'c' within 5 chars.\n" +
            ".{5}          # If so, match the 5 chers.", 
            Pattern.COMMENTS);
        Matcher m = p.matcher(s);
        while (m.find()) {
            System.out.print("Match = \""+ m.group() +"\"\n");
        } 
   }
}

请注意,测试数据中有另一个有效序列S9:13" a cb"(在S12:14"cba"之前。假设您不想与此匹配,我添加了一个额外的约束来过滤它,这需要5个字符窗口必须以abc开头。

以下是脚本的输出:

Match = "ab bc"
Match = "a cba"

答案 2 :(得分:1)

嗯,一种可能性(尽管是完全不切实际的)只是与所有排列相匹配:

abc..|ab.c.|ab..c| .... etc.

这可以在某种程度上分解:

ab(c..|.c.|..c)|a.(bc.|b.c .... etc.

我不确定你是否可以用正则表达式做得更好。

答案 3 :(得分:0)

Pattern p = Pattern.compile("(?:a()|b()|c()|.){5}\\1\\2\\3");
String s = "ab ab bc  a cba";
Matcher m = p.matcher(s);
while (m.find())
{
  System.out.println(m.group());
}

输出:

ab bc
 a cb

这受Regular Expressions Cookbook中食谱#5.7的启发。每个反向引用(\1\2\3)的作用类似于零宽度断言,表示相应的捕获组参与了匹配,即使组本身没有消耗任何字符。

作者警告说,这种技巧依赖于大多数口味中没有记录的行为。它适用于Java,.NET,Perl,PHP,Python和Ruby(原始和Oniguruma),但不适用于JavaScript或ActionScript。