为了学习Clojure,我正在解决4clojure的问题。我目前正在对问题164咬牙切齿,您将在其中列举(部分)DFA接受的语言。一个有趣的条件是语言可能是无限的,所以解决方案必须是惰性的(在这种情况下,解决方案的测试用例(take 2000 ...
.
我有一个可以在我的机器上运行的解决方案,但是当我在网站上提交它时,它会炸毁堆栈(如果我将要确定的可接受字符串的数量从 2000 增加到 20000,我也会在本地炸毁堆栈,所以这是一个我的解决方案的缺陷)。
我的解决方案[1] 是:
(fn [dfa]
(let [start-state (dfa :start)
accept-states (dfa :accepts)
transitions (dfa :transitions)]
(letfn [
(accept-state? [state] (contains? accept-states state))
(follow-transitions-from [state prefix]
(lazy-seq (mapcat
(fn [pair] (enumerate-language (val pair) (str prefix (key pair))))
(transitions state))))
(enumerate-language [state prefix]
(if (accept-state? state)
(cons prefix (follow-transitions-from state prefix))
(follow-transitions-from state prefix)))
]
(enumerate-language start-state ""))
)
)
它接受 DFA
'{:states #{q0 q1 q2 q3}
:alphabet #{a b c}
:start q0
:accepts #{q1 q2 q3}
:transitions {q0 {a q1}
q1 {b q2}
q2 {c q3}}}
并返回 DFA 接受的语言 ( #{a ab abc}
)。但是,在确定 DFA 的前 2000 个接受的字符串时
(take 2000 (f '{:states #{q0 q1}
:alphabet #{0 1}
:start q0
:accepts #{q0}
:transitions {q0 {0 q0, 1 q1}
q1 {0 q1, 1 q0}}}))
它炸毁了堆栈。显然我应该将解决方案重组为尾递归,但我不明白这是怎么可能的。特别是,我看不到如何将惰性与尾递归结合起来(通过recur
or 或trampoline
)。该lazy-seq
函数创建了一个闭包,因此使用recur
insidelazy-seq
将使用闭包作为递归点。当使用lazy-seq
insiderecur
时,lazy-seq
总是会评估,因为recur
发出一个需要评估其参数的函数调用。
使用时trampoline
,我看不到如何迭代地构造一个可以懒惰地评估其元素的列表。正如我使用它并看到它使用的那样,trampoline
它只能在它最终完成时返回一个值(即其中一个蹦床函数不返回函数)。
其他解决方案被认为超出范围
我认为这个 4Clojure 问题的另一种解决方案超出了这个问题的范围。我目前正在使用 解决方案iterate
,其中每个步骤仅计算“下一步”(从当前状态转换之后)接受的字符串,因此它根本不会递归。然后,您只需跟踪当前状态和使您进入该状态的字符串(它们是下一个状态的前缀)。在这种情况下,困难在于检测何时接受有限语言的 DFA 将不再返回任何结果。我还没有为take-while
周围环境设计一个适当的停止标准iterate
,但我很确定我会设法让这个解决方案发挥作用。对于这个问题,我对基本问题感兴趣:可以将惰性和尾递归结合起来还是根本不可能?
[1] 请注意,该站点存在一些限制,例如无法使用def
and defn
,这可能解释了我的代码的一些特殊性。