语言设计:二维模式匹配


49

这是每两周挑战#6。主题:语言设计

这个挑战有一个聊天室。如果您想讨论想法,请加入我们!

现在换个完全不同的东西...

这两个星期,我们想尝试一种新型的挑战。在这个挑战中,您将设计一种语言!模式匹配是编程中一个非常普遍的问题,并且通常对于代码高尔夫非常有用。例如,可以使用正则表达式来检测文本行中的模式。但是,没有任何成熟的方法来描述和检测二维模式。

挑战

您将要设计一种模式匹配语言,该语言允许在文本块中描述二维模式。该运作模式你的语言将类似于正则表达式(虽然你的语言并不一定有什么共同点与正则表达式,否则):

  • 作为输入,您将收到一个矩形文本块。您可以假设文本仅由可打印的ASCII字符(0x20至0x7E)以及换行符(0x0A)组成,以分隔网格的行。
  • 如果根据模式说明将匹配项找到为该文本块的任何子集,则应返回或打印该匹配项。如果匹配项可以是非矩形的,则应将其填充到具有某些保留字符的矩形区域。如果存在多个有效的匹配项,则可以决定如何选择返回的匹配项(最大,最小,第一个等)。

对于某些应用程序,如果您的实现可以返回匹配项的位置而不是匹配项本身,则可能会很有用,但这不是必需的。

至少,您的语言应该能够将模式匹配为其输入的连续矩形子区域。

您的答案应包含:

  • 一个描述语言。
  • 一个有效的实现。它可以是程序,也可以是您选择的语言的一组功能/类。
  • 您应该通过显示语言来解决下面提供的示例来演示您的语言。您的语言不一定必须能够匹配所有语言,但是您必须至少能够匹配其中8种语言。如果您的语言可以做一些我们没想到的花哨的事情,请随时将其包括在内。

如果您的答案是建立在现有想法的基础上的,那很好,但是请在适当的时候给予感谢。

扩展名

上面描述了有效提交必须满足的最低要求。但是,多种概括可以使这种模式匹配语言更加有用,包括但不限于:

  • 能够将样式锚定到一个或多个边缘,以便可以检查整个输入区域是否具有特定样式。
  • 产生所有匹配而不是一个。您可以选择重叠匹配的语义。
  • 以非矩形文字为输入。
  • 允许模式指定非矩形匹配。在这种情况下,应将输出填充到带有某些保留字符的矩形中。
  • 允许图案指定带孔的匹配。
  • 允许非连续匹配,例如两个字符以一定的偏移量出现。
  • 轻松指定旋转和反射。
  • 可选地,将输入循环地视为圆柱或圆环,以使相对的边缘被视为相邻。

计分

这项挑战的主要目标是产生一种有效的2D模式匹配语言,该语言将来可能会使用。这样,诸如“用于解决示例的最短组合长度”之类的评分系统将导致以牺牲通用性为代价来对某些功能进行硬编码。因此,我们认为最好将这项挑战作为人气竞赛。净投票最多的提交者获胜。尽管我们不能强迫人们投票,但以下是一些有关选民理想选择的准则:

  • 表现力。语言可以解决各种问题,甚至超出本问题中提供的示例吗?它是否支持任何建议的扩展?
  • 可读性。该符号的直观性(至少对于了解基本语法的人而言)?
  • 高尔夫度。这仍然是CodeGolf.SE。对于本站点而言,拥有一种只需很少代码即可描述模式的匹配语言当然会很好。

示例问题

以下堆栈片段显示了16种示例问题,二维模式匹配语言可以解决这些问题。每个示例都包含一个简短的问题描述,然后通常后面跟一个可以找到匹配项的输入示例和一个找不到匹配项的示例(如果适用)。

如上所述,您的语言仅需要能够解决其中的8个问题。最重要的是可选的,但是当然应该增加您获得的投票数。

(不,您不需要阅读该HTML代码。请点击“运行代码段”按钮,以使其在浏览器中呈现得很好,然后您也可以全屏查看。)


这些问题有一般的时间限制吗?我对解决此问题非常感兴趣,但我很忙,可能很容易需要2个星期的时间。
德文·帕森斯

7
@DevonParsons没有截止日期。
PhiNotPi

看起来很有趣,尤其是因为您为此创建了一个新标签。我认为为它创建2D语言应该有加分。
mbomb007'3

1
@ mbomb007创建2-D语言时有加分。我很确定它将获得相当数量的支持。;)
Martin Ender 2015年

@MartinBüttner我真的不知道如何创建语言。就像创建一个Python程序一样(简单吗?),该程序需要一个新语言代码的文件(并根据定义的语法对其进行解释/执行)并产生输出?
mbomb007'3

Answers:


32

SnakeEx

到目前为止解决15/16问题!

在线口译-满语规格 - JavaScript源

口译员截图

这种语言背后的想法是使用类似于regex的语法定义“蛇”,使其绕文本检查字符移动。

SnakeEx中的程序由使用不同命令序列的蛇定义列表组成。蛇可以使用这些定义生成其他蛇,这就是SnakeEx发挥其最大功能的地方-我们可以匹配分支结构,甚至可以进行递归(请参阅Paren Matching示例)。

每个程序从本质上来说看起来像是一组正则表达式,但是增加了改变蛇的方向的形式的方向命令<dir>,并调用{label<dir>params}了产生更多蛇的形式的命令。

对于入口点,解释器会使用第一个定义向右移动生成一条蛇。

它不是非常简洁,但是它非常强大并且(我认为)可读性强。

更新

  • 变了!逻辑不和〜不标记匹配项
  • 添加<!>来解决共线性
  • 解决了匹配的十字架
  • 以不太可怕的方式解决了棋盘
  • 添加了有界闭包语法 %{min,max}
  • 添加了递归示例

解决方案

解决15项,进行中1项

您可以使用上面的在线解释器轻松地试用这些程序!

问题1-查找棋盘

m:{v<R>2}{h<>1}
v:{c<L>A1}+
h:{c<R>A2}+
c:_?(#_)+#?

有关详细的介绍,请从问题3开始。

问题2-验证棋盘

m:{v<R>2}{h<>1}
v:${c<L>A1}+$
h:${c<R>A2}+$
c:$_?(#_)+#?$

用越界符号预订适当的蛇类$是使程序仅匹配整个输入的一种方法。

问题3-检测矩形

m:{c<R>A1}%{2,}
c:[0-9]%{2,}

m右蛇移动时,以最小的2种蛇产卵(%{2,}是一个封闭的意思是“2到无穷大”),使用定义C( c)和右移动(<R>),或者更确切地说,在向下这种情况下,因为所有的方向是相对于当前的蛇。该A参数是糖,只是指定了产卵蛇应该调用后移动。该1参数是我们如何限制火柴矩形-数参数放蛇在“组”。除非同一组中的所有蛇行进完全相同的距离,否则比赛不计算在内。

问题4-在单词搜索中查找单词

m:<*>GOLF

direction命令<*>指定蛇应沿任何对角线或正交方向转弯。然后,它寻找简单的正则表达式。

问题5-检测平方输入

m:{v<R>1}{h<>1}
v:${c<L>A1}+$
h:${c<R>A1}+$
c:$.+$

此处的键是特殊字符$,仅当蛇越界时才匹配。我们产生一条水平蛇和一条垂直蛇。每条蛇沿着边缘延伸时会产生更多的蛇,并且所有蛇都在同一组中并且长度必须相同。

问题6-在生活游戏中寻找滑翔机

m:<+>[({l1<R>A}{l2<R>A}{l3<R>})({l1<L>A}{l2<L>A}{l3<L>})]
l1:##\.
l2:[(#\.)(\.#)]#
l3:#\.\.

m从四个正交方向(<+>)中的任何一个开始,实现旋转。然后,按顺序在三行中向左或向右看,以实现反射。

(请注意,我仅将空格替换为句点是因为如果解释器中使用的HTML文本区域连续有多个空格,那么它们似乎会做一些愚蠢的事情)

问题7-匹配虚空传送门

m:{e<R>A1}{d<R>A1}%{2,22}{e<R>1}
e:~.X%{3,22}~.
d:X\.+X

m蛇向右移动,产卵蛇检查左边缘,2-22蛇检查中柱,最后一条蛇来检查右边缘。该~运营商表示,无论如下应进行检查,但不应该被标记为解决方案的一部分。

有界的闭包现在使我们能够完全正确地解决这个问题!

问题8-Minecraft胸部放置

m:~{s<>}~!{d<+>}\.
s:<+>.<BR>([$\.]<R>)%{3}
d:.<+>CC

这里我们使用逻辑非(!),当且仅当以下标记不匹配任何内容时,它才匹配。该声明d在特定方向上检测到双箱,因此请!{d<+>}确保在任何正交方向上都没有双箱。s在当前正方形周围移动一个小菱形,确认其中至少3个空间是空的或不在木板上。\.假设所有前面的条件都成功,则尾随最终将与平方匹配。

问题9-水平和垂直对齐

m:<R>?#~.*#

m可以<R>?在匹配序列之前右转()。 .是通配符,例如在正则表达式中。

问题10-共线点

m:<!>#~.*#~.*#

通过添加<!>方向,我们现在可以解决此问题! <!>就像<+>但不是在四个方向分支,而是在每个可能的方向分支。

问题12-避免字母Q

m:{h<R>A}%{4}
h:[^Qq]%{4}

只需生成4条蛇,每条蛇会寻找四个不是字母Q的字符。

问题13-钻石开采

m:{tl<RB>1}{tr<RF>1}
tl:X/*{bl<L>1}X
tr:X\\*{br<R>1}X
bl:X\\*X
br:X/*X

这个人很整齐。m产生两条蛇,一条蛇向右后方,一条蛇向右前方。其中每个跟随斜线到X,然后以与其当前方向成直角的方式生成另一条蛇,跟随其斜线到底部X。

问题14-匹配十字架

m:{a<R>A}+{b<R>A}+{a<R>A}+
a:{e<>P1}{c<>P2}{e<>P3}
b:{c<>P1}{c<>P2}{c<>P3}
e:\.+
c:#+

这是我第一次使用Piggyback参数。通常,这些蛇是独立的,但是如果您使用此参数进行呼叫,则呼叫的蛇将与被呼叫者一起移动。因此e2可以检查一个序列“。”,然后检查一个序列“#”,然后检查另一个序列“。”,并将它们全部作为单独的调用,以便我们可以将它们与“ 1、2”和“ 3”分组,迫使它们的长度匹配。

问题15-在Boggle板上匹配单词

m{I}:<*>p<*>a<*>n<*>a<*>m<*>a

简单,如果罗word。Iparameter指定不区分大小写(我们可以在定义以及调用中指定参数)。蛇向任何方向旋转,与角色匹配,再次弯曲等等。

m{EI}:<*>p<*>a<*>n<*>a<*>m<*>a

这是无重用字符版本。该EXCLUSIVE从参数匹配已经被标记,就像feersum的泥道的任何字符禁止蛇。

问题16-环绕边缘

m{W}:{c<R>WA}%{3}
c:###

W参数允许蛇在越界时自动换行。我们也有H并且V只允许水平或垂直包装。

Extra-迷宫求解器

m{E}:$(<P>\.)+$

解决步行楼层为句点的ASCII迷宫。的<P>方向是指向前,向左,或向右(糖[<F><L><R>])。

额外-配对

m:\(~{r<>P}\)
r:[^\(\)]*(\({r<>P}\))?[^\(\)]*

尚未弄清楚如何做Prelude,但这是第一步!在这里,我r通过检查蛇之间是否没有不匹配的括号来递归地使用蛇来匹配相应的括号。

额外-ASCII拓扑:计数循环


您是否考虑添加语法,以便该语言可以进行替换而不仅仅是匹配?我想使用一些解决方案来解决这个挑战,为codegolf.stackexchange.com/questions/51231/编写一个条目。但是这里没有一个解决方案可以找到并替换。(我知道我的回答由于语言规范的时机规定而无效)
Sparr

@Sparr。这不是一个坏主意,对于代码高尔夫来说肯定会更有用。不知道我什么时候有时间自己做,但是我会牢记在心。
BMac 2015年

3
分别:方向更改的语法令人困惑。因为蛇是在匹配一个角色后才前进的,所以我似乎不得不在我认为合适的位置之前先改变方向。例如:字符串“ ABC \ nDEF”,我想匹配ABCF定义的L形俄罗斯方块,我想将蛇写为“ m:ABC <R> F”,但我必须写“ m:AB <R>” CF”代替。我了解这符合规范,但我发现这很违反直觉。
Sparr 2015年

我对序言语法有部分解决方案。唯一的问题是,如果它与整个输入不匹配,我将使其不匹配: m:{a<>} a:[({n<R>A})({n<R>A}*{l<R>A}{a<>P}{r<R>A})]*{n<R>A}* l:$[^\(\)]*\([^\(\)]*$ r:$[^\(\)]*\)[^\(\)]*$ n:$[^\(\)]+$
TheNumberOne

21

Slip,Python 3.4(Github Wiki在线解释器

像feersum的提交一样,这也是基于遍历网格的,但是像CarpetPython的提交一样,这也是基于正则表达式的。不知何故,我似乎取得了中间立场。

我想添加很多未实现的功能,因此在相关的地方,我已经指出了我有空时打算做的事情。


更新

(有关详细新闻,请参见Github页面)

  • 2015年4月5日:Slip现在拥有在线翻译!它仍处于早期阶段,因此可能存在一些错误。

  • 2015年4月5日:效率更新!现在,下级门户的大量输入速度更快(2秒)。另外,还进行了许多语法更改,因此请务必检查Wiki。组编号也固定。


滑条有一个匹配指针,该指针从特定的正方形开始,最初朝右。当匹配一个char时,指针将沿当前方向向前移动(尽管实现并不能完全按照该顺序执行操作)。

匹配指针的方向可以设置为与一个特定的方向^<digit>,其中^0^1^2,...,^7设置指针到N,NE,E,...,NW分别(打算顺时针)。

也可以使用以下快捷方式:

  • ^* 检查所有8个正交或对角线方向,
  • ^+ 检查所有4个正交方向。

(未来计划:允许设置任意方向,例如(1, 2)骑士移动)

例如(问题4-在单词搜索中找到单词),

^*GOLF

尝试所有8个正交或对角线方向,寻找单词“ GOLF”。默认情况下,Slip尝试所有可能的起始平方,并从每个平方中返回一个结果,过滤出重复项,因此像

GOLF
O
L
FLOG

仅返回第一行和第二行作为匹配项(因为第一行和左列“ GOLF”从同一正方形开始)。要获得所有匹配项,o可以使用重叠匹配模式。

同样(问题15-在Boggle板上匹配单词),

p^*a^*n^*a^*m^*a

panama通过每次尝试不同的方向进行匹配。使用该i标志可区分大小写。Slip默认情况下会重用char,但是可以使用rno-repeat标志来切换。

(未来计划:搜索模式修改器,它在每次移动后自动检查方向集合,因此^*无需重复)

比赛指针的方向也可以分别使用<或左右旋转90度>。例如,

 `#`#< `#<  <`#<`#

寻找模式

  #
## 
 ##

通过按以下顺序查看:

765
894
123

这使我们能够解决问题6 -寻找滑翔机

^+(`#`# >`# > `#>`#> |`#`# <`# < `#<`#< | `#`#> `#>  >`#>`#| `#`#< `#<  <`#<`#)

其中第1部分和第2部分编码滑翔机形状,第3部分和第4部分编码其反射对应物。

请注意,Slip使用反引号`进行转义。这是因为Slip具有另一种运动形式,该运动为语言指定了名称-slip命令。/将匹配指针正交向左\滑动,而将匹配指针正交向右滑动。

例如,

abc   ghi
   def

可以由匹配abc\def/ghi

尽管单独使用滑动效果不是特别有用,但与(?| <regex> )固定组结合使用时,滑动变得尤为重要,固定组的作用类似于匹配的前瞻。内部的正则表达式被匹配,然后在其末尾将匹配指针的位置和方向重置为固定组之前的状态。

例如,

abc
def
ghi

可以与匹配(?|abc)\(?|def)\(?|ghi)

同样,问题12-避免字母Q可以用

%(\(?|[^qQ]{4})){4}

其中%有一个用于阻止第一个\激活的防滑命令。

Slip还具有一个assert长度(?_(<group num>) <regex> ),仅当其匹配长度与给定组num的长度相同时,才匹配内部的正则表达式。

例如,问题13-钻石开采可轻松解决

^1X(`/*)X>(?_(1)`\*)X>(?_(1)`/*)X>(?_(1)`\*)

首先尝试匹配菱形的左上角,然后断言其他三个边的长度相同。

(运行带有v标志的详细输出)

Match found in rectangle: (8, 0), (12, 4)
  X
 / \
X   X
 \ /
  X

Match found in rectangle: (0, 0), (6, 6)
   X
  / \
 /   \
X     X
 \   /
  \ /
   X

Match found in rectangle: (2, 2), (4, 4)
 X
X X
 X

Match found in rectangle: (10, 2), (14, 6)
  X
 / \
X   X
 \ /
  X

Match found in rectangle: (5, 3), (9, 7)
  X
 / \
X   X
 \ /
  X

Match found in rectangle: (0, 6), (2, 8)
 X
X X
 X

高尔夫球手的选择是

^1X(`/*)(X>(?_(1)`\*)X>(?_(1)`/*)){2}

匹配第一面两次。

使用滑动,固定组和长度声明可以解决许多其他问题:

问题14-配对十字架:

(?|(`.+)(`#+)(`.+))(\(?|(?_(2)`.+)(?_(3)`#+)(?_(4)`.+)))*(\(?|(?_(2)`#+)(?_(3)`#+)(?_(4)`#+)))+(\(?|(?_(2)`.+)(?_(3)`#+)(?_(4)`.+)))+

捕获第一行中.s和#s 的宽度后,它就一直滑落下来。

输出:

Match found in rectangle: (0, 1), (5, 5)
.###..
######
######
.###..
.###..

Match found in rectangle: (4, 6), (6, 8)
.#.
###
.#.

一旦我整理出一些错误,就可以对这个进行一点递归了。

问题3-检测数字矩形:

(?|`d`d+)(\(?|(?_(1)`d+)))+

匹配两个或多个数字的第一行,然后确保下面的每一行都具有相同的长度。`d是等同于的预定义字符类[0-9]

请注意,这会找到所有匹配项

问题7-匹配地下门户:

(?|.X{2,22}.)\((?|(?_(1)X`.+X))\){3,22}(?_(1).X+.)

对于原始线程的顶部示例,该命令输出:

Match found in rectangle: (2, 1), (5, 5)
XXXX
X..X
X..X
X..X
XXXX

Match found in rectangle: (9, 1), (14, 5)
.XXXX.
X....X
X....X
X....X
.XXXX.

Match found in rectangle: (13, 4), (17, 8)
.XXXX
X...X
X...X
X...X
.XXX.

最后,Slip的其他一些功能包括:

  • $0, $1, $2, ..., $7锚定北边,东北角,东边等。$+锚定任意边并$*锚定任意角。
  • $后面跟一个小写字符在当前位置设置一个锚点,稍后可以与之匹配,$再跟上对应的大写字符,例如$a$A
  • # 切换no-move标志,这将阻止匹配指针在下一个匹配之后向前移动。
  • ,与char类似.,但不会将其添加到输出中,从而允许非连续匹配,同时可以被识别(?_())

... 和更多。此页面上确实有太多东西要列出。

其他问题

问题1-查找棋盘:

(?|`#?(`_`#)+`_?)(?_(1)(?|...+))(\(?_(1)(?|`#?(`_`#)+`_?$a)))+<(?|`#?(`_`#)+`_?)(?_(9)(?|...+))(\(?_(9)(?|`#?(`_`#)+`_?)))+$A

当然,这两个棋盘问题并不是Slip的专长。我们匹配顶部的行,然后确保长度至少为3,并在#和之间交替_,然后滑动并匹配后续的行。最后,$a锚点应位于棋盘的右下角。

然后,我们左转并重复列,确保$A最后匹配。

问题2-验证棋盘:

$7%(\(?|`_?(`#`_)*`#?$2))+$5<%(\(?|`_?(`#`_)*`#?$0))+$3

像前面的问题一样,我们检查每一行是否正确,然后向左旋转并对列进行相同的操作。使用锚来确保仅匹配整个板。

问题9-水平和垂直对齐:

>?`#,*`#

我们应用可选的吗?>旋转命令的量词,以便我们向右或向下匹配。在示例中,我们发现所有5个都有o重叠模式,但是从那以后只有4个没有重叠模式,#.,##并且#.,#从同一位置开始。

问题10-共线点

^+`#(?|(,*)<(,*))(((?_(2),*)<(?_(3),*),>)+#`#){2}

匹配a,#然后匹配一些水平字符和一些垂直字符,然后重复直到第二个#,然后重复直到第三个#

问题5-检测平方输入:

$7.(.*)>(?_(1).*)$3>((?|.*)\)*

锚定左上角,并在锚定右下角之前检查上边缘与右边缘的长度是否相同。如果输入通过了此操作,那么我们将向后移动以匹配整个输入。

问题8-我的世界的胸部放置:

`.^+(($^|(?|`.))>){3}($^|`.|C<(($^|(?|`.))>){3})

运行p标志以获取每个比赛的位置。$^是一个锚点,如果下一步移动会使匹配指针超出范围,则匹配。

首先我们匹配一个.,然后检查我们是否被三个.s /边界包围,然后确保周围的第四个正方形也是一个./ boundary或一个箱子(通过检查周围的正方形)。

问题11-验证Prelude语法

$7>%(/(?|[^()]+$4)(?1)?|/(?|[^()]*`([^()]*$4)(?1)?/(?|[^()]*`)[^()]*$4)(?1)?)$1

做了几次尝试,但我认为这是正确的。在这里,我们使用递归,其语法与PCRE相同。

这种方法要求输入是矩形的,但是一旦我完成了非矩形匹配,就不需要进行假设了。

这是相同的方法,具有更多的递归:

$7>%((/(?|([^()]*)$4)|/(?|(?4)`((?3))(?1)?/(?|(?4)`)(?3)))*)$1

问题16-环绕边缘:

%(\(?|`#{3})){3}

(注意:包装尚未推送到在线解释器中)

这需要包装标志w。从技术上讲%,不需要打滑的首字母缩写,但是比赛将被计为从高一平方开始。

问题EX 1-迷宫求解器:

S(^+`.)*^+E

BMac在聊天中的评论问题。将r标志用于无重复模式,以免匹配指针来回移动。

问题EX 2 - 面部识别

(?|O(,*),(?_(2),*)O)(?_(2)(\(?|,))*)\(?|,(?_(2),*)O)(?_(2)(\(?|,))*)\`\(?_(2)`_*)`_(?_(2)`_*)`/

请注意,我只匹配面孔,没有进行任何清理。请注意,该问题包含欧元符号,需要将其替换为一些可打印的ASCII才能起作用。


该哈希模式是Conway滑翔机
Heimdall

17

PMA /蜗牛(C ++)

我将语言设想为蜗牛围绕网格移动并执行命令。蜗牛在它们移动到的每个方块上都留下一团粘液,默认情况下,这会导致方块随后无法匹配。如果到达命令列表的末尾,则匹配成功。

现在有足够多的运算符,我们将需要一个优先级列表来跟踪它们。这些操作按以下顺序解决:

  1. 组内 ( ) [ ]
  2. 沿交替字符分裂 |
  3. `分组评估a左侧的所有内容
  4. 量词[m],[n]n
  5. 断言 = !
  6. 级联

解释器是用C ++编写的。糟糕的源代码可以在这里找到。

问题4:单词搜索

该程序

z\G\O\L\F

足以获取是否找到该单词的真假值。z(15个绝对或相对方向命令之一)在任何八边形方向上匹配。多个连续的方向命令进行或运算。例如,ruldy将几乎等同于z,因为这些命令分别是右,上,左,下以及任何对角线方向的命令。但是,将以不同的顺序测试方向。无论方向如何,匹配的第一个字符始终是蜗牛开始的字符。\<character>匹配单个文字字符。

默认策略是尝试在左对齐输入的边界框内的每个正方形处的图案,然后输出匹配项的数量。如果布尔值10要求,下面的程序可用于:

?
z\G\O\L\F

如果源文件中至少有一个换行符,则将第一行视为用于将输入初始转换为矩形形式的选项。该?选项将打印0或1,具体取决于是否存在匹配项。

问题15:

实现交替后,现在可以解决Boggle了。对此最好使用不区分大小写的匹配,但是实现它不是我的最高优先级。

\p|\P)z(\a|\A)z{\n|\N)z{\a|\A}z(\m|\M)z(\a|\A

|与一维正则表达式完全相同。有两个匹配的定界符配对对,即(){}。右方括号会封闭位于它和最接近的同一类型之间的任何相反类型的开放组。例如,在之后{({{),仅最左边的组保持打开状态。如您所见,边缘处不匹配的分组符号被隐式关闭。还有另一个`我现在不会讨论的分组命令。

问题8:《我的世界》宝箱

该程序将打印有效的胸部放置数量。这是我实际上必须打的第一个高尔夫球,并且几次减少了字节数(17)。

\.!(o\C)2o(!\Cw)3
  • \. 在比赛的起点从字面上匹配一个点。
  • !(o\C)2等同于!((o\C)2)量化优先于断言。<atom> <number>意味着重复<atom>精确的<number>时间。o在任何正交方向上旋转蜗牛。!是一个否定的断言。因此,这部分检查是否没有相邻的双胸。
  • o 沿某个正交方向旋转。
    • (!\Cw)3断言C蜗牛前面没有东西,然后逆时针旋转3次。

问题2:验证棋盘

&
\#!(o^_)|\_!(o^#

&选项将程序的输出设置为1在所有位置都成功匹配,0否则进行设置。^c匹配的字符不c等同于[^c]在正则表达式。总体而言,该程序的意思是:如果在输入边界矩形的每个位置处都存在#不正交于not的字符_,或者_不垂直于is的字符,则打印1。不#; 否则为0。


煤泥踪迹的想法是一个很好的解决方法,我对此感到有些麻烦
BMac 2015年

对于Boggle问题,这是一个不错的解决方案。我用我的方法无法解决这个问题。
逻辑骑士

14

Re2d类,Python 2

更新:添加了“ 9.对齐”问题。

我的方法是使用Python re模块进行搜索和匹配。Re2d类准备要处理的文本,执行re函数,并格式化结果以输出。

请注意,这不是一种全新的语言-它是标准的正则表达式语言,投影到2维中,并添加了用于额外2D模式的标志。

该类具有以下用法:

re2dobject = Re2d(<horizontal pattern>, [<vertical pattern>], [<flags>])

两种模式都是标准的线性文本RE模式。如果未提供垂直样式,则该班级也将使用水平样式进行垂直匹配。这些标志是带有2D扩展名的标准RE标志。

测试中

1. Finding chessboards
Chessboard pattern at (2, 1, 4, 3)

print '\n1. Finding chessboards'
reob1 = Re2d('#(_#)+_?|_(#_)+#?')
found = reob1.search('~______~\n~##_#_#~\n~#_#_##~\n~##_#_#~\n~______~')
print 'Chessboard pattern at', found
assert not reob1.search('#_##\n_#_#\n__#_\n#_#_\n#_#_')

搜索方法找到了一个棋盘图案并返回一个四元组位置。元组具有x,y匹配的第一个字符的位置和匹配 width, height的区域的位置。仅给出一种模式,因此它将用于水平垂直匹配。

2. Verifying chessboards
Is chess? True

print '\n2. Verifying chessboards'
reob2 = Re2d('^#(_#)*_?|_(#_)*#?$')
print 'Is chess?', reob2.match('_#_#_#_#\n#_#_#_#_\n_#_#_#_#')
assert not reob2.match('_#_#_#__\n__#_#_#_\n_#_#_#__')

棋盘使用match方法验证,该方法返回布尔值。注意,必须使用^和以及$开始和结束字符来匹配整个 文本。

3. Rectangle of digits
Found: [(0, 1, 5, 3), (1, 1, 4, 3), (2, 1, 3, 3), (3, 1, 2, 3), (0, 2, 5, 2), (1, 2, 4, 2), (2, 2, 3, 2), (3, 2, 2, 2), (6, 3, 2, 2)]
Not found: None

print '\n3. Rectangle of digits'
reob3 = Re2d(r'\d\d+', flags=MULTIFIND)
print 'Found:', reob3.search('hbrewvgr\n18774gwe\n84502vgv\n19844f22\ncrfegc77')
print 'Not found:', reob3.search('uv88wn000\nvgr88vg0w\nv888wrvg7\nvvg88wv77')

现在,我们使用该MULTIFIND标志返回2位以上数字块的所有可能匹配项。该方法找到9个可能的匹配项。请注意,它们可以重叠。

4. Word search (orthogonal only)
Words: [(0, 0, 4, 1), (0, 3, 4, 1), (3, 3, -4, -1), (3, 2, -4, -1), (3, 0, -4, -1)] [(0, 0, 1, 4), (3, 0, 1, 4), (3, 3, -1, -4), (0, 3, -1, -4)]
Words: ['SNUG', 'WOLF', 'FLOW', 'LORE', 'GUNS'] ['S\nT\nE\nW', 'G\nO\nL\nF', 'F\nL\nO\nG', 'W\nE\nT\nS']
No words: [] []

print '\n4. Word search (orthogonal only)'
words = 'GOLF|GUNS|WOLF|FLOW|LORE|WETS|STEW|FLOG|SNUG'
flags = HORFLIP | VERFLIP | MULTIFIND
reob4a, reob4b = Re2d(words, '.', flags), Re2d('.', words, flags)
matching = 'SNUG\nTEQO\nEROL\nWOLF'
nomatch = 'ABCD\nEFGH\nIJKL\nMNOP'
print 'Words:', reob4a.search(matching), reob4b.search(matching)
print 'Words:', reob4a.findall(matching), reob4b.findall(matching)
print 'No words:', reob4a.findall(nomatch), reob4b.findall(nomatch)

此测试显示了垂直和水平翻转的使用。这允许匹配的单词被颠倒。不支持对角词。该 MULTIFIND标志允许在所有4个方向上进行多个重叠匹配。findall方法使用搜索来找到匹配的框,然后提取匹配的文本块。请注意,搜索是如何使用负宽度和/或高度进行反向匹配的。垂直方向上的单词具有换行符-这与2D字符块的概念一致。

7. Calvins portals
Portals found: [(3, 1, 5, 6)]
Portal not found None

print '\n7. Calvins portals'
reob7 = Re2d(r'X\.{2,22}X|.X{2,22}.', r'X\.{3,22}X|.X{3,22}.', MULTIFIND)
yes = '....X......\n.XXXXXX.XX.\n...X...X...\n.X.X...XXX.\n...X...X.X.\n.XXX...X.X.\nX..XXXXX.X.'
no = 'XX..XXXX\nXX..X..X\nXX..X..X\n..X.X..X\n.X..X.XX'
print 'Portals found:', reob7.search(yes)
print 'Portal not found', reob7.search(no)

此搜索需要为每个维度使用单独的模式,因为每个维度的最小尺寸都不同。

9. Alignment
Found: ['#.,##', '##'] ['#\n.\n,\n.\n#', '#\n,\n.\n#']
Found: [(3, 4, 5, 1), (6, 4, 2, 1)] [(7, 0, 1, 5), (3, 1, 1, 4)]
Not found: None None

print '\n9. Alignment'
reob9a = Re2d(r'#.*#', r'.', MULTIFIND)
reob9b = Re2d(r'.', r'#.*#', MULTIFIND)
matching = '.,.,.,.#.,\n,.,#,.,.,.\n.,.,.,.,.,\n,.,.,.,.,.\n.,.#.,##.,\n,.,.,.,.,.'
nomatch = '.,.#.,.,\n,.,.,.#.\n.,#,.,.,\n,.,.,.,#\n.#.,.,.,\n,.,.#.,.\n#,.,.,.,\n,.,.,#,.'
print 'Found:', reob9a.findall(matching), reob9b.findall(matching)
print 'Found:', reob9a.search(matching), reob9b.search(matching)
print 'Not found:', reob9a.search(nomatch), reob9b.search(nomatch)

这组2个搜索找到2个垂直匹配和2个水平匹配,但找不到嵌入的#.,#字符串。

10. Collinear Points (orthogonal only)
Found: [(0, 1, 7, 1)] [(3, 1, 1, 4)]
Not found: None None

print '\n10. Collinear Points (orthogonal only)'
matching = '........\n#..#..#.\n...#....\n#.......\n...#....'
nomatch = '.#..#\n#..#.\n#....\n..#.#'
reob10h = Re2d(r'#.*#.*#', '.')
reob10v = Re2d('.', r'#.*#.*#')
flags = MULTIFIND
print 'Found:', reob10h.search(matching, flags), reob10v.search(matching, flags)
print 'Not found:', reob10h.search(nomatch, flags), reob10v.search(nomatch, flags)

在这里,我们使用2个搜索来查找两个方向上的匹配项。它能够找到多个正交匹配,但是这种方法不支持对角匹配。

12. Avoid qQ
Found: (2, 2, 4, 4)
Not found: None

print '\n12. Avoid qQ'
reob12 = Re2d('[^qQ]{4,4}')
print 'Found:', reob12.search('bhtklkwt\nqlwQklqw\nvtvlwktv\nkQtwkvkl\nvtwlkvQk\nvnvevwvx')
print 'Not found:', reob12.search('zxvcmn\nxcvncn\nmnQxcv\nxcvmnx\nazvmne')

此搜索找到第一个匹配项。

13. Diamond Mining
.X.
X.X
.X.

.X.
X.X
.X.

..X..
./.\.
X...X
.\./.
\.X..

..X..
./.\.
X...X
.\./.
..X..

.XX.\
//.\.
X...X
.\./.
..X..

...X...
../.\..
./.X.\.
X.X.X.X
.\.X.//
..\./X.
.X.X..\

Diamonds: [(2, 2, 3, 3), (0, 6, 3, 3)] [(8, 0, 5, 5), (10, 2, 5, 5), (5, 3, 5, 5)] [(0, 0, 7, 7)]
Not found: None None None

print '\n13. Diamond Mining'
reob13a = Re2d(r'.X.|X.X', flags=MULTIFIND)
reob13b = Re2d(r'..X..|./.\\.|X...X|.\\./.', flags=MULTIFIND)
reob13c = Re2d(r'...X...|../.\\..|./...\\.|X.....X|.\\.../.|..\\./..', flags=MULTIFIND)
match = '''
...X......X....
../.\..../.\...
./.X.\..X...X..
X.X.X.XX.\./.\.
.\.X.//.\.X...X
..\./X...X.\./.
.X.X..\./...X..
X.X....X.......
.X.............
'''.strip().replace(' ', '')
nomatch = '''
.X......./....
.\....X.......
...X.\.\...X..
..X.\...\.X.\.
...X.X...X.\.X
../X\...\...X.
.X...\.\..X...
..\./.X....X..
...X..../.....
'''.strip().replace(' ', '')
for diamond in reob13a.findall(match)+reob13b.findall(match)+reob13c.findall(match):
    print diamond+'\n'
print 'Diamonds:', reob13a.search(match), reob13b.search(match), reob13c.search(match)
print 'Not found:', reob13a.search(nomatch), reob13b.search(nomatch), reob13c.search(nomatch)

钻石问题更加困难。三种尺寸需要三个搜索对象。它可以在测试集中找到六颗钻石,但无法缩放为可变大小的钻石。这只是钻石问题的部分解决方案。

Python 2代码

import sys
import re

DEBUG = re.DEBUG
IGNORECASE = re.IGNORECASE
LOCALE = re.LOCALE
UNICODE = re.UNICODE
VERBOSE = re.VERBOSE
MULTIFIND = 1<<11
ROTATED = 1<<12     # not implemented
HORFLIP = 1<<13
VERFLIP = 1<<14
WRAPAROUND = 1<<15  # not implemented

class Re2d(object):
    def __init__(self, horpattern, verpattern=None, flags=0):
        self.horpattern = horpattern
        self.verpattern = verpattern if verpattern != None else horpattern
        self.flags = flags

    def checkblock(self, block, flags):
        'Return a position if block matches H and V patterns'
        length = []
        for y in range(len(block)):
            match = re.match(self.horpattern, block[y], flags)
            if match:
                length.append(len(match.group(0)))
            else:
                break
        if not length:
            return None
        width = min(length)
        height = len(length)
        length = []
        for x in range(width):
            column = ''.join(row[x] for row in block[:height])
            match = re.match(self.verpattern, column, flags)
            if match:
                matchlen = len(match.group(0))
                length.append(matchlen)
            else:
                break
        if not length:
            return None
        height = min(length)
        width = len(length)
        # if smaller, verify with RECURSIVE checkblock call:
        if height != len(block) or width != len(block[0]):
            newblock = [row[:width] for row in block[:height]]
            newsize = self.checkblock(newblock, flags)
            return newsize
        return width, height

    def mkviews(self, text, flags):
        'Return views of text block from flip/rotate flags, inc inverse f()'
        # TODO add ROTATED to generate more views
        width = len(text[0])
        height = len(text)
        views = [(text, lambda x,y,w,h: (x,y,w,h))]
        if flags & HORFLIP and flags & VERFLIP:
            flip2text = [row[::-1] for row in text[::-1]]
            flip2func = lambda x,y,w,h: (width-1-x, height-1-y, -w, -h)
            views.append( (flip2text, flip2func) )
        elif flags & HORFLIP:
            hortext = [row[::-1] for row in text]
            horfunc = lambda x,y,w,h: (width-1-x, y, -w, h)
            views.append( (hortext, horfunc) )
        elif flags & VERFLIP:
            vertext = text[::-1]
            verfunc = lambda x,y,w,h: (x, height-1-y, w, -h)
            views.append( (vertext, verfunc) )
        return views

    def searchview(self, textview, flags=0):
        'Return matching textview positions or None'
        result = []
        for y in range(len(textview)):
            testtext = textview[y:]
            for x in range(len(testtext[0])):
                size = self.checkblock([row[x:] for row in testtext], flags)
                if size:
                    found = (x, y, size[0], size[1])
                    if flags & MULTIFIND:
                        result.append(found)
                    else:
                        return found
        return result if result else None

    def search(self, text, flags=0):
        'Return matching text positions or None'
        flags = self.flags | flags
        text = text.split('\n') if type(text) == str else text
        result = []
        for textview, invview in self.mkviews(text, flags):
            found = self.searchview(textview, flags)
            if found:
                if flags & MULTIFIND:
                    result.extend(invview(*f) for f in found)
                else:
                    return invview(*found)
        return result if result else None

    def findall(self, text, flags=0):
        'Return matching text blocks or None'
        flags = self.flags | flags
        strmode = (type(text) == str)
        text = text.split('\n') if type(text) == str else text
        result = []
        positions = self.search(text, flags)
        if not positions:
            return [] if flags & MULTIFIND else None
        if not flags & MULTIFIND:
            positions = [positions]
        for x0,y0,w,h in positions:
            if y0+h >= 0:
                lines = text[y0 : y0+h : cmp(h,0)]
            else:
                lines = text[y0 : : cmp(h,0)]
            if x0+w >= 0:
                block = [row[x0 : x0+w : cmp(w,0)] for row in lines]
            else:
                block = [row[x0 : : cmp(w,0)] for row in lines]
            result.append(block)
        if strmode:
            result = ['\n'.join(rows) for rows in result]
        if flags & MULTIFIND:
            return result
        else:
            return result[0]

    def match(self, text, flags=0):
        'Return True if whole text matches the patterns'
        flags = self.flags | flags
        text = text.split('\n') if type(text) == str else text
        for textview, invview in self.mkviews(text, flags):
            size = self.checkblock(textview, flags)
            if size:
                return True
        return False

如果钻石问题尚不清楚,则钻石可以是任意大小,而不仅仅是0、1或2。编辑:我已经编辑了规格,使这一点更加清晰。
PhiNotPi

得到它了。我会在回答中记下Re2d只能部分解决此问题。它无法缩放为可变大小。这可以吗?
逻辑骑士

是的,没关系。
PhiNotPi

14

格莱姆·哈斯克尔

介绍

Grime基于布尔语法。基本思想是从较小的组件构造矩形图案,并检查是否在输入矩阵中找到它们。到目前为止,Grime仅支持矩形匹配,至少可以优雅地解决至少11个问题。

编辑:修复了十字架(感谢DLosc发现了错误),并增加了钻石开采。

EDIT2:添加了受Slip启发的角色类。还更改了选项标志的语法,改进了表达式解析器,并增加了无Q问题。

EDIT3:实现了大小限制,并增加了下界门户问题。

用法

Grime程序称为语法语法的正确文件扩展名为.gr,尽管未强制执行。语法被评估为

runhaskell grime.hs [options] grammarfile matrixfile

其中matrixfile是包含字符矩阵的文件。例如,数字语法将被评估为

runhaskell grime.hs digits.gr digit-matrix

为了提高速度,我建议使用优化功能编译文件:

ghc -O2 grime.hs
./grime digits.gr digit-matrix

默认情况下,解释器将打印找到的第一个匹配项,但这可以使用选项标志来控制:

  • -e:比赛只是整个矩阵,打印1的比赛,并0为不匹配。
  • -n:打印匹配数,或者打印整个矩阵(如果有的话-e)。
  • -a:打印所有匹配项。
  • -p:也以格式打印比赛的位置(x,y,w,h)
  • -s:不要自己打印火柴。
  • -d:打印调试信息。

也可以在语法中指定选项,方法是将选项插入任何行之前并添加逗号,(请参见下面的示例)。

语法和语义

Grime语法由一个或多个定义组成,每个定义都位于单独的行上。它们每个都定义一个非终结符的值,并且其中一个必须定义匿名顶级非终结符。定义的语法为N=EE,其中N为大写字母且E表达式

表达式的构造如下。

  • 转义为的任何字符都与包含该字符的\任何1x1矩形匹配。
  • . 匹配任何单个字符。
  • $匹配1x1字符矩阵外部的矩形。
  • _ 匹配宽度或高度为零的任何矩形。
  • 预定义的字符组是digit,uppercase,lundercase,alphabetic,alpha numeric和symbol。
  • 可以通过语法定义新的字符类[a-prt-w,d-gu]。包括了左侧的字母,而排除了右侧的字母,因此这与字母完全匹配abchijklmnoprtvw。如果左侧为空,则将其包含所有字符。如果右侧为空,则可以省略逗号。字符[],-\必须使用进行转义\
  • 未转义的大写字母是一个非终结符,并且与为其分配的表达式匹配。
  • 如果PQ是表达式,则PQ仅是它们的水平串联,P/Q而是它们的垂直串联,并P在顶部。
  • P+P水平对齐的一个或多个s,P/+垂直对齐的是一个或多个。
  • 布尔运算表示P|QP&QP!
  • P?P|_P*for P+|_P/*for的简写P/+|_
  • P#匹配任何包含匹配项的矩形P
  • P{a-b,c-d}其中abcd为非负整数,是一个大小的限制P。如果P是字符类,则表达式匹配mxn仅包含那些字符的任何矩形,前提m是介于a和之间b,以及n介于c和之间d。在其他情况下,表达式匹配具有正确大小且P也匹配的任何矩形。如果ac省略,则将它们视为0,如果将bd省略,则它们是无限的。如果省略了a和之间的连字符b,那么我们在间隔的两端使用相同的数字。如果整个c-d省略零件,两个轴都受约束。要澄清的{-b}是,等效于{0-b,0-b},并且{a-,c}等效于{a-infinity,c-c}

笔记

Grime确实允许诸如A=A!未定义行为之类的自相矛盾的定义。但是,它们不会导致崩溃或无限循环。

Grime支持非矩形输入;这些行只是简单地向左对齐,可以使用来匹配间隙$

将来,我想实现以下功能:

  • 更快的匹配。当前,解释器没有考虑例如.只能匹配1x1矩形的事实。在找到匹配项之前,它将依次尝试所有大小的所有矩形,每个矩形都会立即失效。
  • 旋转和反射操作,用于单词搜索和滑行挑战。
  • 使用contexts进行非矩形匹配,这对于Boggle棋盘挑战很有帮助。例如,Pv(Q>R)意味着P具有底部上下文(Q具有右侧上下文R)。它会匹配L形图案

    PPP
    PPP
    QQQRRRR
    QQQRRRR
    QQQRRRR
    

任务

大致按复杂度给出。

矩形数字

d{2-}

这很简单:一个至少为的数字矩形2x2

没有q或Q

[,qQ]{4}

这几乎和第一个一样简单。现在我们有一个更受限制的大小和一个自定义字符类。

水平和垂直对齐

\#.*\#|\#/./*/\#

现在我们有一些转义字符。基本上,它匹配一个#,然后匹配任意数量的任何字符,然后匹配#,无论是水平还是垂直。

检测平方输入

S=.|S./+/.+
e,S

这个语法非常简单,基本上定义了一个正方形是1x1矩形,或者是较小的正方形,在其右边缘附加了一列,在其底部附加了一行。还要注意e顶级非终结符之前的选项,它会切换整个输入验证。

在单词搜索中找到单词

G=\G
O=\O
L=\L
F=\F
GOLF|FLOG|G/O/L/F|F/L/O/G|G.../.O../..L./...F|...G/..O./.L../F...|F.../.L../..O./...G|...F/..L./.O../G...

这很可怕,因为Grime没有进行旋转或反射的操作。这也是非常缓慢,因为Grime的不知道,比赛只能是大小4x11x44x4

滑翔机问题可以用类似的方法解决,但是我懒得写下来。

虚空传送门

.\X+./\X/+\.{2-22,3-22}\X/+/.\X+.

使用大小限制运算符,这并不难。中间部分\.{2-22,3-22}匹配任何.具有正确大小的矩形,然后我们仅在Xs的两边添加s 列,并在Xs的顶部和底部添加带有被忽略端的s 行。

匹配的十字架

E=\.+/+
F=\#+/+
EFE/F/EFE&(E/F/E)F(E/F/E)

我们这里是两个表达式的合取(逻辑与)。非终结符E匹配的非空矩形.S,和F一个非空矩形#秒。连词的左侧与该类型的矩形匹配

...####..
...####..
...####..
#########
#########
.....##..
.....##..

EFE顶部的位置,然后是F,然后EFE再次。右侧与这些转置相匹配,因此我们恰好得到了十字。

钻石开采

C=./+
T=\X|CTC/\/.+\\
B=\X|\\.+\//CBC
CTC/\X.+\X/CBC

非终结符C是任何1xn列的简写。钻石的上半部分由匹配T:它是一个X,也可以是另一个T,在两侧均被一列包围,而在其下一行/[something]\B以相同的方式匹配钻石的底部,并且最高端的非终端只是X[something]X上半部和下半部之间形式的行。

寻找棋盘

(\#\#|\#/\#|\_\_|\_/\_)#!&[#_]{3-}

右侧[#_]{3-}匹配s和s的任何3x3或更大的矩形,而左侧保证它不包含两个相邻的s或s。#_#_

验证棋盘

e,(\#\#|\#/\#|\_\_|\_/\_)#!&[#_]+/+

这与上面的基本相同,除了我们可以匹配任何非空矩形,但需要使用该e标志进行整个输入验证。

验证前奏语法

A=[,()]/*
P=A*|P(A/\(/A)P(A/\)/A)P
e,P

这可能是迄今为止最有趣的语法。非终结符A与不包含(或的任何列匹配),并且P与之间有多个As的某些s或两个匹配的括号匹配P


@DLosc已修复,感谢您发现错误!
Zgarb 2015年

\#(.*|./*)\#工作吗?
Seequ 2015年

@Sieg对齐?不幸的是没有,因为它将被解析为“一个#在左边,然后是任何行或列,然后#在右边一个”。我需要指定#使用斜线将s垂直连接到该列/
2015年

10

特马尔

模板匹配和识别语言

描述

我的解释器占用24K字符(代码段占用字符?),因此可以在此处找到完整的描述。

最好的部分:解释器使用Javascript,这意味着您可以在这里尝试!

对于问题:

#1-查找棋盘

$#_#
a
$_#_
bvacvbS5&(avcS5)G0G2P

&追加搜索。最后的G2获得match元素中的第三个元素,即实际匹配。前两个元素是x和y坐标(基于1,而不是0)。

#3-检测数字的矩形

$DD
$DD
S1G2P

我认为这很简单。

#4-在单词搜索中查找单词

$GO\LF
a
$G
$*O
$**\L
$***F
S6&(aS6)G0G2P

S参数是偶数,以便它将搜索所有旋转。它大于4,因为它可以随后附加到下一个搜索(不能附加单个匹配项)。

#5-检测平方输入

IL-(IG0L)!P

不确定这是否完全合法,因为只有在输入为矩形的情况下,才能正确确定方形度。它将输入IL的长度与第一行的长度进行比较并将其IG0L取反。

#6-在生活游戏中寻找滑翔机

$## 
$# #
$# 
a
$ ##
$## 
$  #
bS6&(bMS6)&(aS6)&(aMS6)G0G2P

最后,用于镜子!

#12-避免字母Q

$[^Qq]
~4*4S1G2P

S1,因为只需要1个匹配项。

稍后,我将做一些较难的事情。

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.