我正在与学生一起创建一个有限状态机的简单演示:
s2 = {}
s1 = {}
s1["0"] = s2
s1["1"] = s1
s2["0"] = s1
s2["1"] = s2
machine = {"start": s1, "accepting": [s1]}
def recognize(fsm, in_string):
return do_recognize(fsm, fsm["start"], in_string)
def do_recognize(fsm, current, in_string):
if len(in_string) == 0:
return current in fsm["accepting"]
return do_recognize(fsm, current[in_string[0]] ,
in_string[1:])
print (recognize(machine, "0"))
本机识别偶数为0的字符串,并且在“好”字符串(例如“1”或“010”)上工作正常。但是对于像上面那样的“坏”字符串,它会得到 进入无限循环,然后在fsm [“接受”]的返回电流处堆叠溢出。
我能够确定问题是两个州的比较。事实上,我只需编写s1 == s2即可生成完全相同的错误。但是s1 == s1(一个好状态)工作正常。
我对正在发生的事情的最好猜测是,它正在进行深度比较并尝试跟踪s2中的所有引用,这些引用都是循环的。但为什么它不对称(即为什么s1 == s1没有同样的问题)?我怎么能避免它?
答案 0 :(得分:4)
问题与s1
和s2
之间的循环引用有关。
这使得无法将s1
与s2
进行比较(就cmp()
而言,两个词典具有无限深度)。请考虑以下事项:
print s1 == s1 # immediately returns True, probably due to equal object ids
print s1 == s2 # RuntimeError: maximum recursion depth exceeded in cmp
这解释了为什么s1 in fsm["accepting"]
有效,s2 in fsm["accepting"]
休息。
解决此问题的一种简单方法是替换
return current in fsm["accepting"]
与
return id(current) in map(id, fsm["accepting"])
这会按身份比较状态,而不是尝试按值比较两个无限深度的词典。
答案 1 :(得分:2)
比较字典时,字典中的每个项目(键/值对)也会进行比较,因此如果您在字典之间有循环引用,其中循环引用涉及相同的键,您将比较它们时,会超出此最大递归深度错误:
例如,如果您有s1 == {'0': s2}
和s2 == {'0': s1}
,那么尝试s1 == s2
将导致以下比较,这说明了递归的发生方式:
s1 == s2 --> s1['0'] == s2['0'] --> s2 == s1 --> s2['0'] == s1['0'] --> s1 == s2 --> ...
s1 in [s2]
或s2 in [s1]
等遏制测试也会导致此等式比较,这就是current in fsm["accepting"]
代码中出现的原因。
您可以使用身份比较而不是相等比较来解决此递归问题,只需将current in fsm["accepting"]
替换为以下内容:
any(s is current for s in fsm["accepting"])
更好的解决方案可能是不使用循环引用,方法是让状态引用标识符而不是对象本身,例如,您可以使用如下结构:
states = {"s1": {"0": "s2", "1": "s1"},
"s2": {"0": "s1", "1": "s2"}}
machine = {"start": "s1", "accepting": ["s1"]}
def recognize(fsm, in_string):
return do_recognize(fsm, fsm["start"], in_string)
def do_recognize(fsm, current, in_string):
if len(in_string) == 0:
return current in fsm["accepting"]
return do_recognize(fsm, states[current][in_string[0]], in_string[1:])