好的,所以我问过一些关于这个项目的小问题,但我对我想出来的设计仍然没有多少信心,所以我会问更广泛的问题规模。
我正在解析课程目录的先决条件描述。描述几乎总是遵循某种形式,这使我认为我可以解析其中的大多数。
从文本中,我想生成一个关于课程必备关系的图表。 (在我解析数据之后,那部分会很简单。)
一些样本输入和输出:
"CS 2110" => ("CS", 2110) # 0
"CS 2110 and INFO 3300" => [("CS", 2110), ("INFO", 3300)] # 1
"CS 2110, INFO 3300" => [("CS", 2110), ("INFO", 3300)] # 1
"CS 2110, 3300, 3140" => [("CS", 2110), ("CS", 3300), ("CS", 3140)] # 1
"CS 2110 or INFO 3300" => [[("CS", 2110)], [("INFO", 3300)]] # 2
"MATH 2210, 2230, 2310, or 2940" => [[("MATH", 2210), ("MATH", 2230), ("MATH", 2310)], [("MATH", 2940)]] # 3
如果整个描述只是一个课程,则直接输出。
如果课程是连结的(“和”),则它们都在同一个列表中输出
如果课程不相关(“或”),则它们位于不同的列表中
在这里,我们有“和”和“或”。
有一点需要注意:“and”/“或”短语的嵌套似乎永远不会超过示例3所示。
最好的方法是什么?我从PLY开始,但我无法弄清楚如何解决减少/减少冲突。 PLY的优点是可以轻松操纵每个解析规则生成的内容:
def p_course(p):
'course : DEPT_CODE COURSE_NUMBER'
p[0] = (p[1], int(p[2]))
使用PyParse,如何修改parseString()
的输出不太清楚。我正在考虑建立@Alex Martelli的想法,即将状态保持在一个对象中并从中构建输出,但我不确定这是如何做得最好的。
def addCourse(self, str, location, tokens):
self.result.append((tokens[0][0], tokens[0][1]))
def makeCourseList(self, str, location, tokens):
dept = tokens[0][0]
new_tokens = [(dept, tokens[0][1])]
new_tokens.extend((dept, tok) for tok in tokens[1:])
self.result.append(new_tokens)
例如,处理“或”案件:
def __init__(self):
self.result = []
# ...
self.statement = (course_data + Optional(OR_CONJ + course_data)).setParseAction(self.disjunctionCourses)
def disjunctionCourses(self, str, location, tokens):
if len(tokens) == 1:
return tokens
print "disjunction tokens: %s" % tokens
disjunctionCourses()
如何知道要删除哪些较小的短语?所有得到的都是令牌,但到目前为止解析的内容存储在result
中,那么该函数如何判断result
中哪些数据对应token
的哪些元素?我想我可以搜索令牌,然后找到具有相同数据的result
元素,但感觉很复杂......
此外,有许多描述包括misc文本,例如:
"CS 2110 or permission of instructor"
"INFO 3140 or equivalent experience"
"PYSCH 2210 and sophomore standing"
但是我解析该文本并不重要。
解决此问题的更好方法是什么?
答案 0 :(得分:24)
def parse(astr):
astr=astr.replace(',','')
astr=astr.replace('and','')
tokens=astr.split()
dept=None
number=None
result=[]
option=[]
for tok in tokens:
if tok=='or':
result.append(option)
option=[]
continue
if tok.isalpha():
dept=tok
number=None
else:
number=int(tok)
if dept and number:
option.append((dept,number))
else:
if option:
result.append(option)
return result
if __name__=='__main__':
tests=[ ("CS 2110" , [[("CS", 2110)]]),
("CS 2110 and INFO 3300" , [[("CS", 2110), ("INFO", 3300)]]),
("CS 2110, INFO 3300" , [[("CS", 2110), ("INFO", 3300)]]),
("CS 2110, 3300, 3140", [[("CS", 2110), ("CS", 3300), ("CS", 3140)]]),
("CS 2110 or INFO 3300", [[("CS", 2110)], [("INFO", 3300)]]),
("MATH 2210, 2230, 2310, or 2940", [[("MATH", 2210), ("MATH", 2230), ("MATH", 2310)], [("MATH", 2940)]])]
for test,answer in tests:
result=parse(test)
if result==answer:
print('GOOD: {0} => {1}'.format(test,answer))
else:
print('ERROR: {0} => {1} != {2}'.format(test,result,answer))
break
产量
GOOD: CS 2110 => [[('CS', 2110)]]
GOOD: CS 2110 and INFO 3300 => [[('CS', 2110), ('INFO', 3300)]]
GOOD: CS 2110, INFO 3300 => [[('CS', 2110), ('INFO', 3300)]]
GOOD: CS 2110, 3300, 3140 => [[('CS', 2110), ('CS', 3300), ('CS', 3140)]]
GOOD: CS 2110 or INFO 3300 => [[('CS', 2110)], [('INFO', 3300)]]
GOOD: MATH 2210, 2230, 2310, or 2940 => [[('MATH', 2210), ('MATH', 2230), ('MATH', 2310)], [('MATH', 2940)]]
答案 1 :(得分:7)
对于简单的语法,我非常喜欢解析表达式语法(PEG),这相当于编写递归下降解析器的规范,结构化的方式。在像Python这样的动态类型语言中,你可以做一些有用的东西,而无需单独的“解析器生成器”。这意味着没有与减少 - 减少冲突或LR解析的其他奥秘的废话。
我做了一些搜索,pyPEG似乎是一个很好的Python库。
答案 2 :(得分:1)
我不会假装对解析语法有太多了解,对于你的情况,unutbu的解决方案就是你所需要的。但是我在最近的系列博客文章中从Eric Lippert那里学到了很多东西。
http://blogs.msdn.com/b/ericlippert/archive/2010/04/26/every-program-there-is-part-one.aspx
这是一个7部分系列,通过创建和解析语法,然后优化语法,使解析更容易,更高效。他生成C#代码来生成特定语法的所有组合,但将它转换为python以解析你自己的一个相当简单的语法不应该太过分。
答案 3 :(得分:1)
我知道这个问题已有十年之久,现在肯定已经回答了。我主要是发布此答案,以证明自己我终于了解了PEG
解析器。我在这里使用了奇妙的parsimonious
module。
话虽如此,您可以想出一种解析语法,构建一个ast并访问它以获得所需的结构:
from parsimonious.nodes import NodeVisitor
from parsimonious.grammar import Grammar
from itertools import groupby
grammar = Grammar(
r"""
term = course (operator course)*
course = coursename? ws coursenumber
coursename = ~"[A-Z]+"
coursenumber = ~"\d+"
operator = ws (and / or / comma) ws
and = "and"
or = (comma ws)? "or"
comma = ","
ws = ~"\s*"
"""
)
class CourseVisitor(NodeVisitor):
def __init__(self):
self.current = None
self.courses = []
self.listnum = 1
def generic_visit(self, node, children):
pass
def visit_coursename(self, node, children):
if node.text:
self.current = node.text
def visit_coursenumber(self, node, children):
course = (self.current, int(node.text), self.listnum)
self.courses.append(course)
def visit_or(self, node, children):
self.listnum += 1
courses = ["CS 2110", "CS 2110 and INFO 3300",
"CS 2110, INFO 3300", "CS 2110, 3300, 3140",
"CS 2110 or INFO 3300", "MATH 2210, 2230, 2310, or 2940"]
for course in courses:
tree = grammar.parse(course)
cv = CourseVisitor()
cv.visit(tree)
courses = [list(v) for _, v in groupby(cv.courses, lambda x: x[2])]
print(courses)
在这里,我们从下往上走,从空白之类的砖块开始,然后是运算符or
,and
和,
,它们最终将导致课程发展,最终{ {1}}。访客类将构建所需的结构(嗯,有点,需要摆脱最后一个元组元素)。
答案 4 :(得分:0)
如果您遇到减少/减少冲突,则需要指定“或”和“和”的优先级。我猜测“和”绑定最紧密,意味着“CS 101和CS 102或CS 201”意味着[[CS 101,CS 102] [CS 201]]。
如果你能找到两者的例子,那么语法是不明确的,你运气不好。但是,您可以将这种含糊不清的内容置之不明,这完全取决于您将如何处理结果。
PS,看起来语言是常规的,您可以考虑使用DFA。