我将为您提供这类问题的通用解决方案。
大多数循环可以轻松地使用直接的map、filter或reduce完成,如果您的数据结构是递归的,那么循环自然就是递归。
然而,您的循环是一种不同类型的循环。您的循环累积了一个结果——这表明应该使用reduce——但循环还携带了一个局部变量(pc),因此它不是一个简单的reduce。
这是一种相当常见的循环。如果这是Racket,我会使用for/fold1,但由于不是,我们将不得不把您的循环塞进reduce中。
让我们定义一个名为load的函数,它返回两个东西,即处理后的代码和处理后的标签。我还将使用一个名为is-label?的辅助函数。
(defn load [asm]
(defn is-label? [x] (= (first x) :label))
{:code <<< CODE GOES HERE >>>
:labels
<<< CODE GOES HERE >>>
})
现在,你的循环做了两件事情,它处理代码和标签。尽可能地,我试图让循环只完成单一任务。这使得它们更容易理解,并且通常会揭示使用更简单的循环结构的机会。
要获取代码,我们只需要删除标签。这可以通过调用filter来实现。
{:code (filter (complement is-label?) asm)
:labels
<<< CODE GOES HERE >>>
}
Reduce通常只有一个累加器,但是你的循环需要两个:结果和本地变量pc。我将把这两个打包成一个向量,该向量将立即被循环体解构。向量的两个插槽将成为我的两个本地变量。
这两个变量的初始值出现在reduce的第二个参数中。
(first
(reduce
(fn [[result, pc] inst]
<< MORE CODE >>
[{} 0] asm))
(注意变量的初始值与声明相距甚远。如果主体很长,这可能很难阅读。这就是Racket的for/fold1解决的问题。)
reduce返回后,我调用first来丢弃本地变量pc并仅保留结果。
填充循环体很简单。如果指令是标签,则assoc到结果中,否则将pc增加一。在任何情况下,我都构造一个包含所有局部变量新值的向量。
(fn [[result, pc] [_ arg :as inst]]
(if (is-label? inst)
[(assoc result arg pc) pc]
[result (inc pc)]))
这种技术可以用来将任何带有本地累加器的循环转换为reduce。以下是完整代码。
(defn load [asm]
(defn is-label? [x] (= (first x) :label))
{:code (filter (complement is-label?) asm)
:labels
(first
(reduce
(fn [[result, pc] [_ arg :as inst]]
(if (is-label? inst)
[(assoc result arg pc) pc]
[result (inc pc)]))
[{} 0] asm))})