Eu recentemente implementei o algoritmo de distância Damerau-Levenshtein a partir do pseudocódigo na Wikipedia. Eu não poderia encontrar qualquer explicação sobre exatamente como ele funciona eo pseudocódigo usa nomes de variáveis completamente uninformative como DA
, DB
, i1
, e j1
que deixou-me coçar a cabeça.
Aqui está minha implementação em Python: https://gist.github.com/badocelot/5327337
A implementação do Python me ajudou a percorrer o programa e descobrir o que estava acontecendo, renomeando as variáveis para nomes mais úteis. Eu estava familiarizado o suficiente com a abordagem de Wagner-Fischer para calcular a distância de Levenshtein e tinha um quadro de referência.
Correndo o risco de ser excessivamente longo, eis como eu entendo Damerau-Levenshtein:
As variáveis misteriosas:
DA
(last_row
no meu código) é um tipo de mapa que contém a última linha em que cada elemento foi visto; no meu código é um dicionário Python realDB
(last_match_col
) mantém a última coluna em que a letrab
corresponde à letraa
da linha atuali1
(last_matching_row
) é o número da linha daDA
letra atual emb
j1
é apenas uma cópia do valor deDB
/last_match_col
antes de ser potencialmente atualizado; no meu código acabei de me mudar para ondelast_match_col
é atualizado e eliminado essa variável
O custo de transposição:
H[i1][j1] + (i-i1-1) + 1 + (j-j1-1)
está calculando o custo de trocar o caractere atual b
pelo último caractere b
conhecido por a
(a última correspondência), tratando todos os caracteres intermediários como adições ou exclusões.
Componentes do custo:
H[i1][j1]
reverte o custo base para o ponto nos cálculos anteriores à transposição, pois encontrar uma transposição invalida trabalhos anteriores(i-i1-1)
é a distância entre a linha atual e a última linha correspondente ao caractere atual, que é o número de exclusões que seriam necessárias(j-j1-1)
é a distância entre a coluna atual e a última coluna com uma correspondência, que é o número de adições- O extra
+ 1
é apenas o custo da própria transposição
Se essa análise estiver incorreta, eu adoraria saber onde errei. Como eu disse, não consegui encontrar nenhuma explicação detalhada de como o algoritmo funciona online.
Versão melhorada?
Tendo descoberto isso, no entanto, ocorreu-me que, calculando o custo de ambas as adições e exclusões entre as letras transpostas parecia falho: uma adição e uma eliminação é equivalente a uma substituição, que isso não está verificando.
Se tudo estiver correto, a solução deve ser trivial: o custo das letras entre as letras transpostas deve ser o mais alto das adições e exclusões: converta o maior número possível de substituições e adicione quaisquer adições ou exclusões restantes.
Portanto, o custo seria:
H[i1][j1] + max((i-i1-1), (j-j1-1)) + 1
Aqui está o meu código para esta versão: https://gist.github.com/badocelot/5327427
De alguns testes simples, isso parece correto. Por exemplo, "abcdef" -> "abcfad" fornece uma distância de edição de 2 (transponha "d" e "f", altere "e" para "a"), enquanto o algoritmo original fornece uma distância de 3 (três últimos) letras são substituições ou 1 transposição + 1 adição + 1 exclusão).
Agora, não posso ser a primeira pessoa a pensar nisso. Então, por que eu não o encontrei? Eu apenas não procurei por tempo suficiente? Ou existe alguma falha sutil que impede que isso realmente funcione?