如何创建匹配给定字符集的模式?

时间:2011-02-28 15:51:49

标签: java regex

我得到一组字符,例如作为包含所有这些的String,需要一个与其中任何一个匹配的charclass模式。例如

  • for "abcde"我想要"[a-e]"
  • for "[]^-"我想要"[-^\\[\\]]"

如何创建紧凑型解决方案以及如何处理空集和所有字符集等边界情况?

哪些字符需要转义?

澄清

我想创建一个charclass模式,即“[...]”之类的东西,没有重复,也没有这样的东西。它必须适用于任何输入,这也是我对角落案件感兴趣的原因。

4 个答案:

答案 0 :(得分:1)

空集为[^\u0000-\uFFFF],所有字符集为[\u0000-\uFFFF]。不确定你需要前者,因为它不匹配任何东西。我会在空字符串上抛出IllegalArgumentException()。

  

哪些字符需要转义?

- ^ \ [ ] - 这就是全部,我实际上已经测试过了。与其他一些正则表达式实现不同,[被认为是字符类中的元字符,可能是因为内部字符类可能与运算符一起使用。

剩下的任务听起来很简单,但相当乏味。首先,您需要选择唯一的字符。然后遍历它们,附加到StringBuilder,可能会转义。如果需要字符范围,则需要先对字符进行排序,然后在循环时选择连续范围。如果您希望-位于范围的开头且没有转义,则设置一个标志,但不要追加它。在循环之后,如果设置了标志,则在将结果包装到-之前将[]添加到结果中。

答案 1 :(得分:1)

这是一个开始:

import java.util.*;

public class RegexUtils {

    private static String encode(char c) {
        switch (c) {
            case '[':
            case ']':
            case '\\':
            case '-':
            case '^':
                return "\\" + c;
            default:
                return String.valueOf(c);
        }
    }

    public static String createCharClass(char[] chars) {

        if (chars.length == 0) {
            return "[^\\u0000-\\uFFFF]";
        }

        StringBuilder builder = new StringBuilder();

        boolean includeCaret = false;
        boolean includeMinus = false;

        List<Character> set = new ArrayList<Character>(new TreeSet<Character>(toCharList(chars)));

        if (set.size() == 1<<16) {
            return "[\\w\\W]";
        }

        for (int i = 0; i < set.size(); i++) {

            int rangeLength = discoverRange(i, set);

            if (rangeLength > 2) {
                builder.append(encode(set.get(i))).append('-').append(encode(set.get(i + rangeLength)));
                i += rangeLength;
            } else {
                switch (set.get(i)) {
                    case '[':
                    case ']':
                    case '\\':
                        builder.append('\\').append(set.get(i));
                            break;
                    case '-':
                        includeMinus = true;
                        break;
                    case '^':
                        includeCaret = true;
                        break;
                    default:
                        builder.append(set.get(i));
                        break;
                }
            }
        }

        builder.append(includeCaret ? "^" : "");
        builder.insert(0, includeMinus ? "-" : "");

        return "[" + builder + "]";
    }

    private static List<Character> toCharList(char[] chars) {
        List<Character> list = new ArrayList<Character>();
        for (char c : chars) {
            list.add(c);
        }
        return list;
    }

    private static int discoverRange(int index, List<Character> chars) {
        int range = 0;
        for (int i = index + 1; i < chars.size(); i++) {
            if (chars.get(i) - chars.get(i - 1) != 1) break;
            range++;
        }
        return range;
    }

    public static void main(String[] args) {
        System.out.println(createCharClass("daecb".toCharArray()));
        System.out.println(createCharClass("[]^-".toCharArray()));
        System.out.println(createCharClass("".toCharArray()));
        System.out.println(createCharClass("d1a3e5c55543b2000".toCharArray()));
        System.out.println(createCharClass("!-./0".toCharArray()));
    }
}

如您所见,输入:

"daecb".toCharArray()
"[]^-".toCharArray()
"".toCharArray()
"d1a3e5c55543b2000".toCharArray()

打印:

[a-e]
[-\[\]^]
[^\u0000-\uFFFF]
[0-5a-e]
[!\--0]

角色类中的角落案例是:

  • \
  • [
  • ]

需要转发\。字符^如果放置在字符类的开头,则不需要转义,-在放置在字符类时不需要转义字符类的开头或结尾(因此代码中的boolean标志)。

答案 2 :(得分:0)

匹配所有字符“。*”(匹配任何字符*的零个或多个重复.

匹配一个空行“^ $”(匹配行^的开头和行$的结尾。请注意,在行的中间缺少匹配的内容)。

不确定最后一个模式是否正是您想要的,因为对“无匹配”有不同的解释。

答案 3 :(得分:0)

一个快速,肮脏,几乎没有伪代码的答案:

StringBuilder sb = new StringBuilder("[");
Set<Character> metaChars = //...appropriate initialization
while (sourceString.length() != 0) {
 char c = sourceString.charAt(0);
 sb.append(metaChars.contains(c) ? "\\"+c : c);
 sourceString.replace(c,'');
}
sb.append("]");
Pattern p = Pattern.compile(sb.toString());
//...can check here for the appropriate sb.length cases
// e.g, 2 = empty, all chars equals the count of whatever set qualifies as all chars, etc

它为您提供了您想要匹配的唯一字符串,并替换了meta-characters。它不会将事物转换为范围(我认为这很好 - 这样做会让我觉得过早优化)。您可以对简单设置案例进行一些后期测试 - 比如将sb与数字,非数字等匹配,但除非您知道这将为您带来很多性能(或者简化就是这个程序的重点),我不会打扰。

如果你真的想做范围,你可以改为sourceString.toCharArray(),对其进行排序,迭代删除重复并进行某种范围检查,并在将内容添加到StringBuilder时替换元字符。< / p>

编辑:我其实很喜欢toCharArray版本,所以伪编码也是如此:

//...check for empty here, if not...
char[] sourceC = sourceString.toCharArray();
Arrays.sort(sourceC);
lastC = sourceC[0];
StringBuilder sb = new StringBuilder("[");
StringBuilder range = new StringBuilder();
for (int i=1; i<sourceC.length; i++) {
  if (lastC == sourceC[i]) continue;
  if (//.. next char in sequence..//) //..add to range
  else {
    // check range size, append accordingly to sb as a single item, range, etc
  }
  lastC = sourceC[i];
}