Что я делаю не так, что моя реализация так чертовски медленна?
Ваш алгоритм имеет экспоненциальную сложность. Похоже, вы предполагаете, что звонки запоминаются для вас, но это не так.
Как это исправить?
Вам необходимо добавить явное напоминание, возможно, используя массив или какой-либо другой метод.
Говоря о «ленивой оценке»: я понимаю, что если levenshtein "cat" "kit" 2 2
называется трижды, он рассчитывается только один раз. Это правильно?
Нет, Haskell не выполняет автоматическое запоминание. Лень означает, что если вы выполните let y = f x in y + y
, то f x
будет оцениваться (один раз) только в том случае, если запрашивается результат суммы. не означает, что f x + f x
будет вычислять только за один вызов f x
. Вы должны быть явными, если хотите поделиться результатами подвыражений.
Должно быть что-то встроенное для моего bool2int
, верно?
Да, есть instance Enum Bool
, поэтому вы можете использовать fromEnum
.
*Main> fromEnum True
1
*Main> fromEnum False
0
Любой другой вход будет высоко оценен, если он подтолкнет меня к трудному пути к овладению haskell.
Хотя написание материала с нуля может быть увлекательным и познавательным, важно научиться использовать множество замечательных библиотек в Hackage при выполнении подобных вещей.
Например, существует реализация расстояния Левенштейна в пакете edit-distance .
Я перевел ваш код на Haskell обратно на Python для сравнения:
def levenshtein(u, v, i, j):
if i == 0: return j
if j == 0: return i
return min(1 + levenshtein(u, v, i, (j-1)),
1 + levenshtein(u, v, (i-1), j),
(u[i-1] != v[j-1]) + levenshtein(u, v, (i-1), (j-1)))
def distance(u, v):
return levenshtein(u, v, len(u), len(v))
if __name__ == "__main__":
print distance("abbacabaab", "abaddafaca")
Даже без устранения проблемы индексации O (n), на которую chrisdb указал в своем ответе , при компиляции это работает медленнее, чем версия на Haskell:
$ time python levenshtein.py
6
real 0m4.793s
user 0m4.690s
sys 0m0.020s
$ time ./Levenshtein
6
real 0m0.524s
user 0m0.520s
sys 0m0.000s
Конечно, они оба проигрывают правильно запомненной версии в пакете редактирования расстояния:
$ time ./LevenshteinEditDistance
6
real 0m0.015s
user 0m0.010s
sys 0m0.000s
Вот простая запоминающаяся реализация, использующая Data.Array
:
import Data.Array
distance u v = table ! (m, n)
where table = listArray ((0, 0), (m, n)) [levenshtein i j | i <- [0..m], j <- [0..n]]
levenshtein 0 j = j
levenshtein i 0 = i
levenshtein i j = minimum [ 1 + table!(i, j-1)
, 1 + table!(i-1, j)
, fromEnum (u'!(i-1) /= v'!(j-1)) + table!(i-1, j-1) ]
u' = listArray (0, m-1) u
v' = listArray (0, n-1) v
m = length u
n = length v
main = print $ distance "abbacabaab" "abaddafaca"
Он работает аналогично исходному коду Python:
$ time python levenshtein-original.py
6
real 0m0.037s
user 0m0.030s
sys 0m0.000s
$ time ./LevenshteinArray
6
real 0m0.017s
user 0m0.010s
sys 0m0.000s