最近我发现了python模块pyparsing
,这是一个很好的工具,可以通过编写语法来解析数据,而不是解析器。我对无上下文语法的想法不熟悉,所以请纠正这个问题中的任何错误假设。
Pyparsing可以实现BNF(Backus–Naur Form)无上下文语法。这个语法可以是递归的,但是它可以有前瞻性的前瞻吗?自从我偶然发现this question以来,我一直想知道答案。让我举一个具体的例子。考虑字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
让语法看起来像:
<number> :: __<digit>__
<block> :: <number>(x) (<number> <number> <number> .... x times)
即。读取第一个数字标记,将其另存为x
,然后使用下一个x
数字并将它们组合在一起。解析后的行应如下所示:
[[1, 2], [3, 4, 5, 6], [7, 8, 9, 10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]]
我使用pyparsing编写了一个简单的python MWE 而不是,所以我很清楚我正在尝试做什么:
A = range(1,31)
B, sub_b = [], []
consume = 0
for a in A:
if consume:
sub_b.append(a)
consume -= 1
else:
if sub_b: B.append(sub_b)
sub_b = [a,]
consume = a
B.append(sub_b)
print B
两个(相关)问题:这可以通过BNF无上下文语法完成吗?如果是/否我怎样才能使用pyparsing
?
答案 0 :(得分:3)
在无上下文语法中没有参数化大小 - 或者在常规语法中没有这样的东西。像consume
这样的数值参数不是CFG模型的一部分,可以证明以不同的方式获得效果是不可能的。但是,您可以为任何固定长度编写作品,因此您可以为长度为1,长度为2,长度为3的块编写作品:
<block3> :: <number> <number> <number>
或类似地,将长度匹配为前缀或甚至作为后缀:
<block3a> :: 3 <number> <number> <number>
<block3b> :: <number> <number> <number> 3
等。为了做你想做的事,你需要一个包含那种规则的语法,对于你可能需要的所有N
。
给定的CFG将仅包含有限数量的这些作品。在数学上不可能编写可以处理无限参数化大小的CFG(以BNF或任何其他形式),无论是作为前缀还是作为后缀。实际上,您可以根据需要随时使用新产品更新您的CFG。例如,读取一个数字N
并为您的语法创建一个规则blockN
(如果它尚不存在)。但是没有单个 CFG可以捕获无限制的参数化大小。
编辑,因为您还询问了上下文相关的语法:它仍然不会这样做。问题是使用整数运算,而不是语法的类。 Chomsky层次结构中的任何形式语言都是根据有限数量的符号(标记)定义的,并且因为它们是无限多个整数,所以它们不能被赋予不同的含义(请注意,您的解析过程依赖于整数算术)。
如果您要将长度预处理为一系列恒星(* * * 4 7 10
),则CFG解析器非常简单:
<group> :: * <group> <number>
<group> :: * <number>
这就是所谓的a^n b^n
语言。您也可以使用符号“十”等符号。但是,如果没有预处理,唯一的解决方案(以及您的程序或图灵机在实践中所做的)是在您的语法中解释数字符号。例如,将“21”解析为十个十一。我怀疑这可以在CFG中完成(问题是处理任意长的数字而没有单独的规则,数百万,数十亿等),但我不太确定。无论哪种方式,它只是一个有趣的学术练习,因为使用真正的整数是如此容易。我相信人们已经用整数研究了形式语言的属性,但我不能说出任何相关的内容。
答案 1 :(得分:3)
Pyparsing包含帮助器countedArray
,它完全符合您的要求。它需要一个参数expr
,并将解析一个整数后跟&#39; n&#39; expr的实例。对于输入字符串,您可以写:
from pyparsing import *
from pprint import pprint
# make a long string of integers, starting at 0
source = ' '.join(map(str, range(50)))
# define an integer as a 'word' of numeric digits
integer = Word(nums)
# add a parse action to convert the parsed strings to integers, at parse time
integer.setParseAction(lambda t:int(t[0]))
# parse the source string into counted strings of integers, and print out
# with pprint
lists = OneOrMore(countedArray(integer)).parseString(source)
pprint(lists.asList())
打印出来:
[[],
[2],
[4, 5, 6],
[8, 9, 10, 11, 12, 13, 14],
[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]]
附加到integer
的解析操作会转换数字字符串
返回整数(列表中的数字周围没有引号)。
请注意 开头的空列表,由领先的&#39; 0&#39;的 源字符串。
即使源字符串包含更多数字 过去30,对于另一个计数阵列来说还不够。
countedArray
通过创建与前导整数匹配的表达式,然后是强制未定义的正向表达式来实现此技巧。附加到前导整数的解析操作会将一个expr*n
表达式注入到强制转发中,然后解析以下内容:&#39; n&#39; exprs。您可以轻松编写countedArray(Word(alphas))
并解析"4 the quick brown fox"
以获取['the', 'quick', 'brown', 'fox']
。正如@Aaron在他的回答中指出的那样,没有必要保留领先的计数器,因为你可以通过获取返回列表的len来轻松获得它。
pyparsing还支持使用FollowedBy和NotAny构造的更传统的前瞻(NotAny可以通过使用&#39;〜&#39;运算符缩写)。在这个字符串中,&#34;四分和七年前......&#34;你可以使用Word(alphas) + FollowedBy(Word('aeiou',alphas))
挑选出以元音开头的单词后面的字符串,这些单词会匹配&#39;得分&#39;和&#39;年&#39 ;;或使用Word(alphas) + ~Literal('.')
的句点后面没有句号的句子,这些句子会匹配每个单词,但前面有&#39;。在这种情况下,尝试通过输入字符串搜索匹配项,您可以使用searchString
或scanString
代替parseString
。
答案 2 :(得分:1)
不是。
拥有语法的重点在于它允许以人类可理解的方式定义数据。您的演示字符串是可读的,但为什么1
不是3
?
您案例中的正确输入是:
[[2], [4, 5, 6], [8, 9, 10, 11, 12, 13, 14], [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]]
,语法看起来像这样:
<model> :: <term>
<list> :: <term> | <term> <opt-whitespace> <list>
<term> :: '[' <list> ']'
然后,您可以通过查看列表的长度来恢复缺少的计数元素。