【问题标题】:Regular expression for crossword solution填字游戏的正则表达式
【发布时间】:2016-08-09 19:56:01
【问题描述】:

这是一个填字游戏。示例:

  • 解决方案是一个以“r”开头并以“r”结尾的 6 个字母的单词
  • 因此模式是“r....r”
  • 未知的 4 个字母必须从字母“a”、“e”、“i”和“p”池中抽取
  • 每个字母只能使用一次
  • 我们有大量候选 6 字母单词

解决方案:“剑杆”或“修复”。

过滤模式“r....r”很简单,但是在“未知”槽中找到也有 [aeip] 的单词是我无法做到的。

这个问题是否适合正则表达式,还是必须通过详尽的方法来解决?

【问题讨论】:

  • 这么多问题:你的意思是来自池a, e, i, p?字母也可以使用两次,还是每个单词都必须只使用一次?您使用的是哪种语言或工具?你试过什么?
  • 我不明白你为什么说“未知的 4 个字母必须从字母池“a”、“e”、“i”和“r”中抽取出来,但你给两个还包含“p”的示例解决方案?
  • 对不起! - m.buettner 和 Mikkel 是正确的 - 这个例子中的未知字母是“a”、“e”、“i”和“p”,而不是“a”、“e”、“i”和“r”每个字母只能从“池”中使用一次。
  • @mikeham 您应该在您的问题中添加“仅一次”标准,因为这是关键部分。看看我的答案,它应该为你解决问题

标签: regex crossword


【解决方案1】:

试试这个:

r(?:(?!\1)a()|(?!\2)e()|(?!\3)i()|(?!\4)p()){4}r

...或者更具可读性:

r
(?:
  (?!\1) a () |
  (?!\2) e () |
  (?!\3) i () |
  (?!\4) p ()
){4}
r

空组用作复选标记,在每个字母被使用时打勾。例如,如果要匹配的单词是repair,则e 将是此构造匹配的第一个字母。如果正则表达式稍后尝试匹配另一个 e,则该替代将不匹配它。负前瞻(?!\2) 将失败,因为第 2 组已参加比赛,更不用说它没有消耗任何东西。

真正酷的是它同样适用于包含重复字母的字符串。以您的redeem 为例:

r
(?:
  (?!\1) e () |
  (?!\2) e () |
  (?!\3) e () |
  (?!\4) d ()
){4}
m

在使用第一个e 后,第一个替代方案被有效禁用,因此第二个替代方案取而代之。等等……

不幸的是,这种技术不适用于所有正则表达式。一方面,他们并不都将空/失败的组捕获视为相同的。 ECMAScript 规范明确指出,对非参与组的引用应该总是成功的。

正则表达式风格还必须支持前向引用,即出现在正则表达式中它们引用的组之前的反向引用。 (ref) 据我所知,它应该适用于 .NET、Java、Perl、PCRE 和 Ruby。

【讨论】:

    【解决方案2】:

    假设您的意思是未知字母必须在 [aeip] 之间,那么合适的正则表达式可能是:

    /r[aeip]{4,4}r/
    

    【讨论】:

    • 感谢@mikkel,但问题是“池”[aeip] 中的字母只能使用一次;并且“池”可能包含重复的字母。
    【解决方案3】:

    用来比较字符串的前端语言是什么,是java,.net...

    这是一个使用 java 的示例/伪代码

        String mandateLetters = "aeio"
        String regPattern = "\\br["+mandateLetters+"]*r$";  // or if for specific length \\br[+mandateLetters+]{4}r$
    
        Pattern pattern = Pattern.compile(regPattern);
        Matcher matcher = pattern.matcher("is this repair ");
    
        matcher.find();
    

    【讨论】:

      【解决方案4】:

      为什么不替换每个 '.'在您的原始模式中使用“[aeip]”?

      你会得到一个正则表达式字符串r[aeip][aeip][aeip][aeip]r

      这当然可以缩短为r[aeip]{4,4}r,但在一般情况下实现起来会很痛苦,并且可能不会改进代码。

      这并没有解决重复字母使用的问题。如果我编写它,我会在正则表达式之外的代码中处理它 - 主要是因为正则表达式会变得比我愿意处理的更复杂。

      【讨论】:

        【解决方案5】:

        所以“只有一次”部分是关键。列出所有排列显然是不可行的。如果您的语言/环境支持前瞻和反向引用,您可以让它更容易一些:

        r(?=[aeip]{4,4})(.)(?!\1)(.)(?!\1|\2)(.)(?!\1|\2|\3).r
        

        仍然很丑,但它是这样工作的:

        r     # match an r
        (?=   # positive lookahead (doesn't advance position of "cursor" in input string)
          [aeip]{4,4}
        )     # make sure that there are the four desired character ahead
        (.)   # match any character and capture it in group 1
        (?!\1)# make sure that the next character is NOT the same as the previous one
        (.)   # match any character and capture it in group 2
        (?!\1|\2)
              # make sure that the next character is neither the first nor the second
        (.)   # match any character and capture it in group 3
        (?!\1|\2|\3)
              # same thing again for all three characters
        .     # match another arbitrary character
        r     # match an r
        

        Working demo.

        这既不优雅也不可扩展。因此,您可能只想使用r([aiep]{4,4})r(捕获四个关键字母)并确保没有正则表达式的附加条件。

        编辑:事实上,如果您只想确保有 4 个不相同的字符,上述模式才是真正有用和必要的。对于您的具体情况,再次使用前瞻,有更简单(尽管更长)的解决方案:

        r(?=[^a]*a[^a]*r)(?=[^e]*e[^e]*r)(?=[^i]*i[^i]*r)(?=[^p]*p[^p]*r)[aeip]{4,4}r
        

        解释:

        r       # match an r
        (?=     # lookahead: ensure that there is exactly one a until the next r
          [^a]* # match an arbitrary amount of non-a characters
          a     # match one a
          [^a]* # match an arbitrary amount of non-a characters
          r     # match the final r
        )       # end of lookahead
        (?=[^e]*e[^e]*r)  # ensure that there is exactly one e until the next r
        (?=[^i]*i[^i]*r)  # ensure that there is exactly one i until the next r
        (?=[^p]*p[^p]*r)  # ensure that there is exactly one p until the next r
        [aeip]{4,4}r      # actually match the rest to include it in the result
        

        Working demo.

        对于具有deee 池的r....m,可以将其调整为:

        r(?=[^d]*d[^d]*m)(?=[^e]*(?:e[^e])*{3,3}m)[de]{4,4}m
        

        这样可以确保只有一个 d 和 3 个 es。

        Working demo.

        【讨论】:

        • m.buettner 得到了问题(谢谢!),尽管我这么笨拙地说。但是,他的解决方案是否适用于 - 模式:r....m - 缺少字母池:“e”、“e”、“e”、“d” 解决方案:“redeem”
        • 感谢您的出色工作(尤其是花时间将 cmets 添加到正则表达式),但我认为您会同意 Alan Moore 的解决方案是最直接的。我也感谢您在gskinner.com/RegExr 上提供指向“RegExr”的指针,这对我来说是新的。
        【解决方案6】:

        由于 sed 多正则表达式操作,没有完全正则表达式

        sed -n -e '/^r[aiep]\{4,\}r$/{/\([aiep]\).*\1/!p;}' YourFile
        

        raeipsourround 组中的模式4 字母,只保留子组中没有找到字母的行两次。

        【讨论】:

          【解决方案7】:

          一个更具可扩展性的解决方案(无需为每个字母或位置写 \1、\2、\3 等)是使用负前瞻来断言每个字符以后不会出现:

          ^r(?:([aeip])(?!.*\1)){4}r$
          

          更具可读性:

          ^r
          (?:
            ([aeip])
            (?!.*\1)
          ){4}
          r$
          

          改进

          这是一个适用于您给我们的情况的快速解决方案,但这里有一些额外的限制来获得更强大的版本:

          • 如果“字母池”可能与字符串结尾共享一些字母,则在前瞻中包括模式结尾:

            ^r(?:([aeip])(?!.*\1.*\2)){4}(r$)
            

            (可能无法在所有正则表达式风格中按预期工作,在这种情况下,复制粘贴模式的结尾而不是使用\2

          • 如果某些字母不仅必须出现一次,而且必须出现不同的固定次数,请为共享此次数的所有字母添加单独的前瞻。例如,带有一个“a”和一个“p”但两个“e”的“r....r”将被这个正则表达式匹配(但“rapper”和“repeer”不会):

            ^r(?:([ap])(?!.*\1.*\3)|([e])(?!.*\2.*\2.*\3)){4}(r$)
            

            非捕获组现在有 2 个备选方案:([ap])(?!.*\1.*\3) 匹配 "a" 或 "p" 在任何地方都不会被另一个结尾,而([e])(?!.*\2.*\2.*\3) 匹配 "e" 直到以 2 结尾才出现任何地方其他的(所以如果总共有 3 个,它会在第一个失败)。 顺便说一句,此解决方案包括上述解决方案,但模式的结尾在这里转移到 \3(另请参阅有关风味的注释)。

          【讨论】:

            猜你喜欢
            • 2018-02-26
            • 1970-01-01
            • 2015-02-24
            • 1970-01-01
            • 2011-12-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-21
            相关资源
            最近更新 更多