正则表达式高尔夫:验证数独解决方案


65

编写一个与任何有效的数独解决方案匹配且与任何无效的数独解决方案都不匹配的正则表达式。输入是数独的展开版本,即没有行定界符。例如下面的板:

7 2 5 8 9 3 4 6 1
8 4 1 6 5 7 3 9 2
3 9 6 1 4 2 7 5 8
4 7 3 5 1 6 8 2 9
1 6 8 4 2 9 5 3 7
9 5 2 3 7 8 1 4 6
2 3 4 7 6 1 9 8 5
6 8 7 9 3 5 2 1 4
5 1 9 2 8 4 6 7 3

将给出为:

725893461841657392396142758473516829168429537952378146234761985687935214519284673

这些规则可能是目前的常识,但以防万一……数独板在以下情况下有效:

  • 每行包含一次从1到的数字9
  • 每列包含从1到的数字,9精确到一次。
  • 九个3x3子网格中的每一个都包含从19恰好一次的数字。

规则

您的答案应该由一个正则表达式组成,没有任何其他代码(可选地,使您的解决方案起作用所需的正则表达式修饰符列表除外)。您不得使用允许您以托管语言调用代码的语言正则表达式功能(例如Perl的e修饰符)。

您可以使用在挑战之前存在的任何正则表达式风味,但请指定风味。

不要假定正则表达式是隐式锚定的。例如,如果您使用的是Python,则假定您的regex与一起使用re.search,而不与一起使用re.match。您的正则表达式不需要匹配整个字符串。对于有效的解决方案,它只需要匹配至少一个子字符串(可能为空),而对于无效的解决方案,则不产生任何匹配。

您可以假设输入将始终是一个由81个正数组成的字符串。

这是正则表达式高尔夫,因此以字节为单位的最短正则表达式获胜。如果您的语言需要分隔符(通常是/.../)来表示正则表达式,请不要计算分隔符本身。如果您的解决方案需要修饰符,请为每个修饰符添加一个字节。

测试用例

有效板卡:

123456789456789123789123456231564897564897231897231564312645978645978312978312645
725893461841657392396142758473516829168429537952378146234761985687935214519284673
395412678824376591671589243156928437249735186738641925983164752412857369567293814
679543182158926473432817659567381294914265738283479561345792816896154327721638945
867539142324167859159482736275398614936241587481756923592873461743615298618924375
954217683861453729372968145516832497249675318783149256437581962695324871128796534
271459386435168927986273541518734269769821435342596178194387652657942813823615794
237541896186927345495386721743269158569178432812435679378652914924813567651794283
168279435459863271273415986821354769734692518596781342615947823387526194942138657
863459712415273869279168354526387941947615238138942576781596423354821697692734185
768593142423176859951428736184765923572389614639214587816942375295837461347651298
243561789819327456657489132374192865926845317581673294162758943735914628498236571
243156789519847326687392145361475892724918653895263471152684937436729518978531264
498236571735914628162758943581673294926845317374192865657489132819327456243561789
978531264436729518152684937895263471724918653361475892687392145519847326243156789
341572689257698143986413275862341957495726831173985426519234768734869512628157394

无效的木板:

519284673725893461841657392396142758473516829168429537952378146234761985687935214
839541267182437659367158924715692843624973518573864192298316475941285736456729381
679543182158926473432817659567381294914256738283479561345792816896154327721638945
867539142324167859159482736275398684936241517481756923592873461743615298618924375
754219683861453729372968145516832497249675318983147256437581962695324871128796534
271459386435168927986273541518734269769828435342596178194387652657942813823615794
237541896186927345378652914743269158569178432812435679495386721924813567651794283
168759432459613278273165984821594763734982516596821347615437829387246195942378651
869887283619214453457338664548525781275424668379969727517385163319223917621449519
894158578962859187461322315913849812241742157275462973384219294849882291119423759
123456789456789123564897231231564897789123456897231564312645978645978312978312645
145278369256389147364197258478512693589623471697431582712845936823956714931764825
243561789829317456657489132374192865916845327581673294162758943735924618498236571
243156789529847316687392145361475892714928653895263471152684937436719528978531264
498236571735924618162758943581673294916845327374192865657489132829317456243561789
978531264436719528152684937895263471714928653361475892687392145529847316243156789
342571689257698143986413275861342957495726831173985426519234768734869512628157394
345678192627319458892451673468793521713524986951862347179246835534187269286935714
341572689257698143986413275862341957495726831173985426519234768734869512628517394

对于其他测试用例,您可以使用此CJam脚本该脚本以有效的木板作为输入,并随机对其进行混洗以提供新的有效木板(只要其仅包含数字和可选的空白,输入格式就无关紧要)。

如果您的正则表达式与.NET风格兼容,则可以使用Retina 在线进行测试0对于无效的板,应打印有效的解决方案,对于有效的板,应打印一些正整数。要一次运行所有测试用例,请使用此模板并将正则表达式插入第二行。如果需要正则表达式修饰符,请`在正则表达式前加上a 并将标准修饰符放在其前面。



1
如果只是[代码保龄球],大声笑。
mbomb007 '16

等等...如果我们使用的是Python,我们只能使用正则表达式,别无其他?另外,每个板有多大?有特定尺寸吗?如果不是,我们应该如何从下面的数字集中提取每个棋盘valid boards
R. Kap

@ R.Kap不管使用哪种口味,都应该只提交正则表达式(可能还有一些修饰符),是的。每个输入正好是81位数字,代表一整板。(测试用例中的每一行都是单独的板。)
Martin Ender

我写了一个脚本来解决SQL中简单的数独问题。我敢肯定,这个问题可以改写。但是,SQL没有太多的REGEX。这会使答案失去资格吗?(脚本可能会低于400个字符)
t-clausen.dk

Answers:


40

Ruby正则表达式,71 78 73字节

^(?!.*(?=(.))(.{9}+|(.(?!.{9}*$))+|(?>.(?!.{3}*$)|(.(?!.{27}*$)){7})+)\1)

我并不真正了解Ruby,但显然它没有抱怨级联量词。

在这里尝试。

.NET正则表达式,79 78 75或77字节

因为马丁认为这是可能的...但是我想他也将这些变化也包括在内。

^(?!(.)+((.{9})+|(?>(.{9})+
|.)+|(?((...)*
)(?>(.{27})+
|.){7}|.)+)(?<=\1))

需要输入中包含尾随换行符才能工作。我不确定是否允许这样做(可能不这样做)。

在这里尝试。

77字节健全版本:

^(?!(.)+((.{9})+|((?!(.{9})*$).)+|(?((...)*$)((?!(.{27})*$).){7}|.)+)(?<=\1))

感谢Neil指出我以前版本中的错误,并打了1个字节(用于(...)*)。

在这里尝试。

PCRE,77 78字节

^(?!.*(?=(.))((.{9})+|(.(?!(?3)*$))+|(?(?=.(...)*$)(.(?!(.{27})*$)){7}|.)+)\1)

只是为了完整性。

在这里尝试。

另一个版本,也是78个字节:

^(?!.*(?=(.))((.{9})+|(.(?!(?3)*$))+|(?>.(?!(...)*$)|(.(?!(.{27})*$)){7})+)\1)

在这里尝试。

说明

^(?!.*                    # Match a string that doesn't contain the pattern,
                          # at any position.
  (?=(.))                 # Capture the next character.
  (
    (.{9})+               # Any 9*n characters. The next character is in
                          # the same column as the captured one.
  |
    (.(?!(.{9})*$))+      # Any n characters not followed by a positions 9*m
                          # to the end. The next character is in the same row
                          # as the captured one.
  |
    (                     # Any n occasions of...
    ?(.(...)*$)           # If it is at a position 3*k+1 to the end:
      (.(?!(.{27})*$)){7} # Then count 7*m characters that are not followed
                          # by a position 27*j to the end,
    |
      .                   # Else count any character.
    )+                    # The next character is in the same block as the
                          # captured one.
  )
  \1                      # Fail if the next character is the captured character.
)

哇,干得好。我只在.NET中达到83,而不得不切换到78的PCRE。毫无疑问,您最终也会击败它。:)
Martin Ender

@MartinBüttner是的。
Nic Hartley

我认为我使用前瞻性来(当时)以4个字节击败@MartinBüttner很整齐,但是您已经将它提升到了一个新的水平。
尼尔

抱歉,但这无法检测到(1,2)和(2,1)处的单元格是否相同,并且对于正方形中所有其他单元格都位于下方和左侧的单元格也是如此。
尼尔

1
@MartinBüttner我刚刚意识到我可以将我的第二个PCRE版本转换为Ruby ...我想您可以现在发布您的答案了
jimmy23013 '16

34

PCRE,117个119 130 133 147字节

^(?!(.{27})*(...){0,2}(.{9})?.?.?(.).?.?(?=(?2)*$).{6,8}(?3)?\4.{0,17}(?1)*$|.*(.)(.{8}(?3)*|((?!(?3)*$)(|.(?7))))\5)

也应该在Python,Java等版本中工作。现在进行递归!而且“递归”功能非递归地用于“子例程”,在不得不使用实际递归之前,我完全忘记了这一点。


不错的主意-避免重复,而不是匹配重复!
Qwertiy '16

1
你不能写这太可惜了.{27}*
尼尔

ah,我将您的133字节解决方案压缩为121字节,只是发现您已经重写了它,但是无论如何,这里是这样的:^(?!(.{27})*(.{9})?(...){0,2}.?.?(.).?.?(?=(...)*$)(.{9})?.{6,8}\4.{0,17}(.{27})*$|.*(.)((.{9})+|((?!(.{9})*$).)+)(<=\8))
Neil

@Neil是什么味道?PCRE或其他我知道的不允许在回溯中使用反向引用。
feersum '16

@Neil (<=\8)看起来不像是有效的语法(缺少?)。另外,我知道在lookbehinds中支持反向引用的唯一风格是.NET。
Martin Ender

15

.NET正则表达式,8339字节

是的,我知道我的解决方案非常幼稚,因为Martin告诉我他做到了130字节左右。实际上,在线尝试使用的URL很长,以至于我找不到可以接受它的URL缩短器。

(code removed, since it's so long nobody will read it here, 
and it made the post take forever to load. Just use the "Try it online" link.)

以下链接在IE中无效,但在Chrome和Firefox中有效。

在线尝试 -在一!`开始的帮助下,一次完成所有测试用例,不包括在字节数中。


这是我用来生成它的Python脚本(下面的代码):

R=range(0,9)
S=range(1,10)

o = ""

# validate rows
T = "(?=.{%s,%s}%s)"
for j in R:
    for i in S:
        o += T % (9*j,9*(j+1)-1, i)

# validate columns
# "(?=(.{%s}|.{%s}|.{%s}|.{%s}|.{%s}|.{%s}|.{%s}|.{%s}|.{%s})%s)"
T = "(?=("+"|".join([".{%s}"]*9)+")%s)"
for j in R:
    for i in S:
        o += T % (j,j+9,j+18,j+27,j+36,j+45,j+54,j+63,j+72, i)

# validate boxes
# (?=.{0,2}1|.{9,11}1|.{18,20}1)(?=.{3,5}1|.{12,14}1|.{21,23}1)
# (?=.{27,29}1|.{36,38}1|.{45,47}1)
T = ".{%s,%s}%s"
for i in S:
    for x in (0,27,54):
        for y in (0,3,6):
            o += "(?="+"|".join(T % (x+y+z,x+y+z+2, i) for z in (0,9,18))+")"

o += ".{81}"

o = o.replace(".{0}","").replace(",80}",",}")
print(o)

1
我为您
加油

4
知道有什么好笑的吗?try it在线链接会因为太长而使IE崩溃:P

15
@cat这就是IE唯一真正的目的就是允许用户下载Firefox(或Chromium)的原因。
字节指挥官

2
@cat尽管正则表达式未得到正确处理,但它不会使Windows 8.1上的IE11崩溃。
Nzall '16

2
@cat不会使Windows 7上的IE 11崩溃。它只是截断URL。
mbomb007 '16

14

.NET正则表达式,121字节

^(?!(.{27})*(.{9})?(...){0,2}.?.?(.).?.?(?=(...)*$)(.{9})?.{6,8}\4.{0,17}(.{27})*$|.*(?=(.))((.{9})+|(.(?!(.{9})*$))+)\8)

说明:

^(?!                     Invert match (because we're excluding duplicates)
 (.{27})*                Skip 0, 3 or 6 rows
 (.{9})?                 Optionally skip another row
 (...){0,2}              Skip 0, 3 or 6 columns
 .?.?(.).?.?(?=(...)*$)  Choose any of the next three cells
 (.{9})?                 Optionally skip another row
 .{6,8}\4                Match any of the three cells below
 .{0,17}(.{27})*$        As long as they're from the same square
|                        OR
 .*(?=(.))(              Choose any cell
  (.{9})+                Skip at least one row
 |                       or
  (.                     Skip cells
   (?!(.{9})*$)          Without reaching the end of the row
  )+                     For at least one cell (i.e. the cell chosen above)
 )\8)                    Match the chosen cell to the next cell
)

很好,行和列的组合非常聪明。我自己的解决方案节省了4个字节。:)
Martin Ender

8

PCRE,3579字节

绝对可怕的暴力解决方案。负向后隐藏!

我为此花了太多时间放弃它,所以在这里,为了后代。

从好的方面来说,如果Sudoku突然开始使用另一组9个字符,那仍然可以使用,我想...

http://pastebin.com/raw/CwtviGkC

我不知道如何操作Retina,但是您也可以将其粘贴到https://regex101.com或类似文件中,并且可以匹配。

用于生成正则表达式的Ruby代码:

# Calculate the block you're in
def block(x)
    x /= 3
    x + x%3 - x%9
end

81.times do |i|
    row, col = i.divmod(9)
    neg = []
    neg += (0...col).map {|e| 9*row + e + 1}
    neg += (0...row).map {|e| 9*e + col + 1}
    neg += (0...i).map {|e| e + 1 if block(e) == block(i)}.compact
    neg = neg.uniq.sort.map {|e| "\\#{e}"}
    if neg.size > 0
        print "(?!#{neg.join '|'})"
    end
    print "(.)"
end

8

红宝石风味,75 74字节

感谢jimmy23013节省了1个字节。

^(?!(.{9}*(.|(.)){,8}|.*(\g<2>.{8})*|.{27}?.{3}?(\g<2>{3}.{6}){,2}.?.?)\3).

在这里测试。

现在它终于被击败了,我可以分享自己的解决方案。:)我在过程中发现了一种有趣的(也许是新的?)正则表达式技术(该(.|(.)){,8}\3部分),如果它不能与正则表达式的其他部分结合使用(在jimmy23013的回答中就是这种情况),这可能是无与伦比的。 。

说明

像其他简短的答案一样,我使用的是否定前瞻,它会搜索行,列或块中的重复项。解决方案的基本构建块是这样的:

(.|(.))...\3

请注意,\3在三个不同的替代方案(它们都使用组3进行重复检测)之间重用了。

左侧的组(即group 2,包含group 3)用于可以包含重复数字前半部分的任何位置(在一个组中不能包含重复数字)。然后...是让我们知道可能出现该数字的下一个位置(如有必要),并\3尝试通过向后引用找到重复项的下半部分。起作用的原因是回溯。引擎首次匹配时,(.|(.))它只会.每次使用并且不会捕获任何东西。现在\3,最后的失败。但是现在,引擎将逐渐尝试使用(.)代替.进行单独匹配。最终,如果重复,它将在以下位置找到组合(.)是最后一次在重复项的第一位使用(以便以后不会覆盖捕获),然后使用更多位.来弥合差距与反向引用。如果有重复项,则回溯将始终找到它。

让我们看一下使用它的三个不同部分:

.{9}*(.|(.)){,8}

这将检查某行中的重复项。首先,我们跳到的任何行.{9}*。然后,使用可选的重复捕获,最多匹配8个字符(即该行中除最后一位数字外的其他任何字符),并尝试找到其后的字符\3

.*(\g<2>.{8})*

这会在某些列中查找重复项。首先,请注意这\g<2>是一个子例程调用,因此与以下内容相同:

.*((.|(.)).{8})*

我们刚刚插入的两个组仍称为23

在这里,.*简单地跳过了尽可能多的距离(这里最多可以匹配8个字符,但这会花费更多的字节)。然后,外部组一次匹配一个完整的行(该行可能环绕两个物理行),可以选择捕获第一个字符。之后\3将立即查找,以确保捕获和反向引用之间的垂直对齐。

最后,检查块:

.{27}?.{3}?(\g<2>{3}.{6}){,2}.?.?

同样,\g<2>是子程序调用,因此与以下内容相同:

.{27}?.{3}?((.|(.)){3}.{6}){,2}.?.?

要验证这些块,请注意,由于我们已经检查了所有行和列,因此我们只需要检查4个3x3块。当我们知道所有行和列以及这些3x3块均正确时:

XX.
XX.
...

然后,我们知道剩余的块中不可能有重复项。因此,我只检查这四个块。此外,请注意,我们不必在3x3块的同一行中查找重复项。在一行中找到重复项的前半部分,然后再向下搜索一遍,就足够了。

现在,对于代码本身,我们首先使用跳到四个块之一的开头.{27}?.{3}?(可选地跳过三行,可选地跳过三列)。然后,我们尝试使用与之前的行相同的技巧来匹配3x3块的最多两行:

(.|(.)){3}.{6}

我们允许但不要求捕获3x3块当前行中的3个单元格中的任何一个,然后使用跳到下一行.{6}。最后,我们尝试在最终到达的行的3个单元格中的任何一个中查找重复项:

.?.?

就是这样。


74 :^(?!(.*((.|(.)).{8})*|.{9}*\g<3>{,8}|.{27}?.{3}?(\g<3>{3}.{6}){,2}.?.?)\4);73 :^(?!(.*((.|(.)|\4()).{8})*|.{9}*\g<3>{9}|.{27}?.{3}?(\g<3>{3}.{6}){3})\5)
jimmy23013 '16

@ jimmy23013我实际上\4()在3x3块的较早版本中使用了该技巧,但是由于它较长,最终放弃了它。:D
Martin Ender

@ jimmy23013尽管73人没有检查最后一行:341572689257698143986413275862341957495726831173985426519234768734869512628517394
Martin Ender

6

正则表达式的Javascript,532 530 481 463个字符

验证行:

/^((?=.{0,8}1)(?=.{0,8}2)(?=.{0,8}3)(?=.{0,8}4)(?=.{0,8}5)(?=.{0,8}6)(?=.{0,8}7)(?=.{0,8}8)(?=.{0,8}9).{9})+$/

验证列:

/^((?=(.{9}){0,8}1)(?=(.{9}){0,8}2)(?=(.{9}){0,8}3)(?=(.{9}){0,8}4)(?=(.{9}){0,8}5)(?=(.{9}){0,8}6)(?=(.{9}){0,8}7)(?=(.{9}){0,8}8)(?=(.{9}){0,8}9).){9}/

从第一个字符开始验证square:

/(?=.?.?(.{9}){0,2}1)(?=.?.?(.{9}){0,2}2)(?=.?.?(.{9}){0,2}3)(?=.?.?(.{9}){0,2}4)(?=.?.?(.{9}){0,2}5)(?=.?.?(.{9}){0,2}6)(?=.?.?(.{9}){0,2}7)(?=.?.?(.{9}){0,2}8)(?=.?.?(.{9}){0,2}9)/

将预览设置为正方形的起点:

/^(((?=)...){3}.{18})+$/

以及整个表达:

/^(?=((?=.{0,8}1)(?=.{0,8}2)(?=.{0,8}3)(?=.{0,8}4)(?=.{0,8}5)(?=.{0,8}6)(?=.{0,8}7)(?=.{0,8}8)(?=.{0,8}9).{9})+$)(?=((?=(.{9}){0,8}1)(?=(.{9}){0,8}2)(?=(.{9}){0,8}3)(?=(.{9}){0,8}4)(?=(.{9}){0,8}5)(?=(.{9}){0,8}6)(?=(.{9}){0,8}7)(?=(.{9}){0,8}8)(?=(.{9}){0,8}9).){9})(((?=.?.?(.{9}){0,2}1)(?=.?.?(.{9}){0,2}2)(?=.?.?(.{9}){0,2}3)(?=.?.?(.{9}){0,2}4)(?=.?.?(.{9}){0,2}5)(?=.?.?(.{9}){0,2}6)(?=.?.?(.{9}){0,2}7)(?=.?.?(.{9}){0,2}8)(?=.?.?(.{9}){0,2}9)...){3}.{18})+$/

匹配整个字符串。


用Javascript ES6测试:

r = /^(?=((?=.{0,8}1)(?=.{0,8}2)(?=.{0,8}3)(?=.{0,8}4)(?=.{0,8}5)(?=.{0,8}6)(?=.{0,8}7)(?=.{0,8}8)(?=.{0,8}9).{9})+$)(?=((?=(.{9}){0,8}1)(?=(.{9}){0,8}2)(?=(.{9}){0,8}3)(?=(.{9}){0,8}4)(?=(.{9}){0,8}5)(?=(.{9}){0,8}6)(?=(.{9}){0,8}7)(?=(.{9}){0,8}8)(?=(.{9}){0,8}9).){9})(((?=.?.?(.{9}){0,2}1)(?=.?.?(.{9}){0,2}2)(?=.?.?(.{9}){0,2}3)(?=.?.?(.{9}){0,2}4)(?=.?.?(.{9}){0,2}5)(?=.?.?(.{9}){0,2}6)(?=.?.?(.{9}){0,2}7)(?=.?.?(.{9}){0,2}8)(?=.?.?(.{9}){0,2}9)...){3}.{18})+$/
;`123456789456789123789123456231564897564897231897231564312645978645978312978312645
725893461841657392396142758473516829168429537952378146234761985687935214519284673
395412678824376591671589243156928437249735186738641925983164752412857369567293814
679543182158926473432817659567381294914265738283479561345792816896154327721638945
867539142324167859159482736275398614936241587481756923592873461743615298618924375
954217683861453729372968145516832497249675318783149256437581962695324871128796534
271459386435168927986273541518734269769821435342596178194387652657942813823615794
237541896186927345495386721743269158569178432812435679378652914924813567651794283
168279435459863271273415986821354769734692518596781342615947823387526194942138657
863459712415273869279168354526387941947615238138942576781596423354821697692734185
768593142423176859951428736184765923572389614639214587816942375295837461347651298`
.split`
`.every(r.test.bind(r))
&&
`519284673725893461841657392396142758473516829168429537952378146234761985687935214
839541267182437659367158924715692843624973518573864192298316475941285736456729381
679543182158926473432817659567381294914256738283479561345792816896154327721638945
867539142324167859159482736275398684936241517481756923592873461743615298618924375
754219683861453729372968145516832497249675318983147256437581962695324871128796534
271459386435168927986273541518734269769828435342596178194387652657942813823615794
237541896186927345378652914743269158569178432812435679495386721924813567651794283
168759432459613278273165984821594763734982516596821347615437829387246195942378651
869887283619214453457338664548525781275424668379969727517385163319223917621449519
894158578962859187461322315913849812241742157275462973384219294849882291119423759
123456789456789123564897231231564897789123456897231564312645978645978312978312645
145278369256389147364197258478512693589623471697431582712845936823956714931764825`
.split`
`.every(s => !r.test(s))

我认为列应该比行容易得多,因此我很好奇您的列正则表达式比第一行长。
彼得·泰勒

@PeterTaylor,它们具有相同的结构,但是对于列,我必须跳过9个字符而不是1个字符,因此由于使用了next ,因此.将其放入(.{9})括号中{0,8}。您为什么认为列应该更短?
Qwertiy '16

@PeterTaylor,是的,如果我猜想检查否定的话,列会更简单。
Qwertiy '16

@ SuperJedi224,为什么要使用JavaScript?我希望这个正则表达式在任何地方都有效。
Qwertiy '16

6
@Qwertiy尽管您的正则表达式可以使用多种样式,但它依赖于先行(例如,在Lua或OCaml中不可用)。它也是无效的基本或扩展正则表达式,它们使用完全不同的语法。即使解决方案在许多其他解决方案中都可以使用,最好还是选择有效性声明。
丹尼斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.