【问题标题】:How do I write a regular expression that will match characters in any order?如何编写一个以任何顺序匹配字符的正则表达式?
【发布时间】:2013-01-25 00:52:32
【问题描述】:

我正在尝试编写一个匹配一组字符而不考虑顺序的正则表达式。例如:

str = "act" 
str.scan(/Insert expression here/)

会匹配:

cat
act
tca
atc
tac
cta

但不匹配 ca、ac 或 cata。

我在 StackOverflow 上阅读了很多类似的问题和答案,但没有找到与我的目标完全匹配的问题和答案。

澄清一下,我使用的是 ruby​​,不想允许重复字符。

【问题讨论】:

  • 我想澄清一下你想找到猫,在字符串“cat att act, ccc”中行动。或者你想检查“猫”、“行为”是行为的排列吗?
  • 请注意,如果您尝试查找是某个字符集的排列的子字符串,则正则表达式不是解决方案。
  • 我希望正则表达式能够匹配任何带有重复字符的行为排列,并且要求必须使用所有字符。
  • 所以你想在“cat att act, ccc”中找到“cat”、“act”,对吗?
  • 不是 att 或 ccc,因为它们有重复。我刚刚测试了 Carl 提出并由您改进的解决方案:^(?=[act]{3}$)(?!.*(.).*\1),效果很好。现在我只需要确保我理解它。我没有太多正面和负面展望的经验,但它们似乎非常强大。再次感谢!

标签: ruby regex


【解决方案1】:

这是你的解决方案

^(?:([act])(?!.*\1)){3}$

看here on Regexr

^                  # matches the start of the string
    (?:            # open a non capturing group 
        ([act])    # The characters that are allowed and a capturing group
        (?!.*\1)   # That character is matched only if it does not occur once more, Lookahead assertion
    ){3}           # Defines the amount of characters
$

唯一特别想的是lookahead assertion,保证字符不重复。

^ 和 $ 是匹配字符串开头和结尾的锚点。

【讨论】:

    【解决方案2】:

    [act]{3} 或 ^[act]{3}$ 在大多数正则表达式方言中都会这样做。如果您可以缩小您正在使用的系统,这将有助于您获得更具体的答案。

    编辑:正如@georgydyer 在下面的 cmets 中提到的,您的问题不清楚是否允许重复字符。如果没有,您可以调整this question 的答案并获得:

    ^(?=[act]{3}$)(?!.*(.).*\1).*$
    

    也就是说,先用正向先行检查匹配,然后用反向引用进行否定先行以排除重复字符。

    【讨论】:

    • 不过,这也会匹配任何重复的字符,例如 aaa、aac、att,对吧?
    • 是的,会的。重读这个问题让我不清楚这是否允许。如果不是,则问题与stackoverflow.com/questions/3101366/… 重复,那里接受的答案应该可以正常工作。
    • 那里的解决方案只适用于一组字符的排列,不适用于多组。另一件事是,^(?=[act]{3}$)(?!.*(.).*\1) 足够了。
    • 什么是多重集?我的第一个示例支持重复字符。
    • @CarlNorum:允许重复字符的集合(但您只能准确选择集合中指定的字符数,例如abss 的集合--> bass 可以,但是abba 不行)。另一件事是,如果我正确理解scan 的功能,我怀疑上面的表达式是否适用于scan。
    【解决方案3】:

    下面是我的做法:

    regex = /\b(?:#{ Regexp.union(str.split('').permutation.map{ |a| a.join }).source })\b/
    # => /(?:act|atc|cat|cta|tac|tca)/
    
    %w[
      cat act tca atc tac cta
      ca ac cata
    ].each do |w|
      puts '"%s" %s' % [w, w[regex] ? 'matches' : "doesn't match"]
    end
    

    输出:

    "cat" matches
    "act" matches
    "tca" matches
    "atc" matches
    "tac" matches
    "cta" matches
    "ca" doesn't match
    "ac" doesn't match
    "cata" doesn't match
    

    我在很多事情上都使用了将数组传递给Regexp.union 的技术;我使用散列的键特别好,并将散列传递给gsub,以便在文本模板上快速搜索/替换。这是来自gsub 文档的示例:

    'hello'.gsub(/[eo]/, 'e' => 3, 'o' => '*') #=> "h3ll*"
    

    Regexp.union 创建一个正则表达式,在提取生成的实际模式时使用source 而不是to_s 很重要:

    puts regex.to_s
    => (?-mix:\b(?:act|atc|cat|cta|tac|tca)\b)
    
    puts regex.source
    => \b(?:act|atc|cat|cta|tac|tca)\b
    

    注意to_s 如何将模式的标志嵌入到字符串中。如果您不期望它们,您可能会不小心将该模式嵌入到另一个中,这不会像您预期的那样运行。去过那里,做到了,并有凹陷的头盔作为证据。

    如果您真的想玩得开心,请查看 CPAN 上可用的 Perl Regexp::Assemble 模块。使用它,加上List::Permutor,让我们生成更复杂的模式。在像这样的简单字符串上,它不会节省太多空间,但在长字符串或所需命中的大型数组上,它可以产生巨大的差异。不幸的是,Ruby 没有这样的东西,但是可以用单词或单词数组编写一个简单的 Perl 脚本,并让它生成正则表达式并将其传回:

    use List::Permutor;
    use Regexp::Assemble;
    
    my $regex_assembler = Regexp::Assemble->new;
    my $perm = new List::Permutor split('', 'act');
    while (my @set = $perm->next) {
        $regex_assembler->add(join('', @set));
    }
    print $regex_assembler->re, "\n";
    (?-xism:(?:a(?:ct|tc)|c(?:at|ta)|t(?:ac|ca)))
    

    有关在 Ruby 中使用 Regexp::Assemble 的更多信息,请参阅“Is there an efficient way to perform hundreds of text substitutions in Ruby?”。

    【讨论】:

    • 作为一般解决方案,要查找所有匹配项,我宁愿使用滑动窗口和循环而不是正则表达式。
    • 这是个好主意,但单个正则表达式的性能将大大优于循环,并且如果操作正确,可以告诉您正则表达式匹配的值。
    • 滑动窗口也可以告诉子字符串的开始和结束位置,所以功能上来说,两者应该是一样的。 (保持开始和结束位置最有可能在正则表达式引擎中完成)。性能有点问题。
    【解决方案4】:

    我将在这里假设几件事: - 您正在寻找给定字符的排列 - 你正在使用红宝石

    str = "act"
    permutations = str.split(//).permutation.map{|p| p.join("")}
    
    # and for the actual test
    permutations.include?("cat")
    

    虽然它不是正则表达式。

    【讨论】:

    • 你想通过排列输入字符串来做什么?
    • 给定的正确匹配示例是输入字符的排列:cat act tca atc​​ tac cta
    【解决方案5】:

    毫无疑问 - 使用正/负前瞻和反向引用的正则表达式很巧妙,但如果你只处理三个字符,我会通过显式枚举@scones 建议的字符排列来避免冗长。

    "act".split('').permutation.map(&:join)
    => ["act", "atc", "cat", "cta", "tac", "tca"]
    

    如果你真的需要一个正则表达式来扫描更大的字符串,你总是可以:

    Regexp.union "act".split('').permutation.map(&:join)
    => /\b(act|atc|cat|cta|tac|tca)\b/
    

    显然,如果您的搜索字符串增长,此策略不会扩展,但在我看来,这样更容易观察代码的意图。

    编辑:根据@theTinMan 的反馈,为cata 的误报添加了单词边界。

    【讨论】:

    • @theTinMan 这是 irb 的复制/粘贴输出。你能不能对你认为有问题的地方不那么神秘?
    • 对照 OP 的命中和遗漏单词列表测试您的正则表达式。
    • @theTinMan 啊。 cata。感谢您指出了这一点。我也不知道不使用source 可能出现的潜在嵌入问题。感谢您的详细回答。我已经 +1 了。
    • 没问题。在这里和工作场所进行测试都很重要。正则表达式可能很危险,可能会发生泄漏。
    • 我只是想告诉你,尽管正则表达式在 Rubular 中运行良好,但我无法让它在我的实际应用程序中匹配。结果,我最终在我的应用程序中使用了您的解决方案。非常感谢!
    猜你喜欢
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-02
    相关资源
    最近更新 更多