使用记忆化在 Clojure 中实现 Levenshtein

Levenshtein implementation in Clojure with memoization

这是使用递归的 Clojure 的最小 Levenshtein(编辑距离)实现:

(defn levenshtein [s1, i1, s2, i2]
  (cond
    (= 0 i1) i2
    (= 0 i2) i1
    :else
    (min (+ (levenshtein s1 (- i1 1) s2 i2) 1)
         (+ (levenshtein s1 i1 s2 (- i2 1)) 1)
         (+ (levenshtein s1 (- i1 1) s2 (- i2 1)) (if (= (subs s1 i1 (+ i1 1)) (subs s2 i2 (+ i2 1))) 0 1))
         )
    )
  )

(defn levenshteinSimple [s1, s2]
  (levenshtein s1, (- (count s1) 1), s2, (- (count s2) 1)))

可以这样使用:

(println (levenshteinSimple "hello", "hilloo"))
(println (levenshteinSimple "hello", "hilloo"))
(println (levenshteinSimple "bananas", "bananas"))
(println (levenshteinSimple "ananas", "bananas"))

并打印:

2
2
0
1

如何将 memoize 添加到此实现中以提高性能?

请注意:我是 Clojure 初学者。这些是我在 Clojure 中的第一行

最简单的方法就是使用 memoize 函数。它需要一个函数,并且 return 是一个记忆函数:

(let [mem-lev (memoize levenshteinSimple]
  (println (mem-lev "hello", "hilloo"))
  (println (mem-lev "hello", "hilloo"))
  (println (mem-lev "bananas", "bananas"))
  (println (mem-lev "ananas", "bananas")))

mem-lev 会记住你给它的每个参数和你的函数 returns 的结果,如果它已经看到你给的参数,return 会缓存结果它。

请注意,这 不会 导致递归调用被记忆化,但任何递归调用都不太可能从记忆化中受益。

这也不会导致您的原始函数被记忆。在这个例子中,只有 mem-lev 会被记忆。如果你真的想要记忆你的全局函数,你可以将你的定义更改为:

(def levenshteinSimple
  (memoize
    (fn [s1, s2]
      ...

但我不建议这样做。这会导致函数本身保持状态,这是不理想的。它还会在程序运行期间保持该状态,如果滥用可能会导致内存问题。

(作为一个很好的练习,尝试编写您自己的 memoize 版本。我通过这样做学到了很多东西)。