Реализация простого Trie для эффективного вычисления расстояния Левенштейна - Java

ОБНОВЛЕНИЕ 3

Готово. Ниже приведен код, который, наконец, прошел все мои тесты. Опять же, это смоделировано после модифицированной версии алгоритма Стива Ханова Мурило Васконсело. Спасибо всем, что помогло!

/**
 * Computes the minimum Levenshtein Distance between the given word (represented as an array of Characters) and the
 * words stored in theTrie. This algorithm is modeled after Steve Hanov's blog article "Fast and Easy Levenshtein
 * distance using a Trie" and Murilo Vasconcelo's revised version in C++.
 * 
 * http://stevehanov.ca/blog/index.php?id=114
 * http://murilo.wordpress.com/2011/02/01/fast-and-easy-levenshtein-distance-using-a-trie-in-c/
 * 
 * @param ArrayList word - the characters of an input word as an array representation
 * @return int - the minimum Levenshtein Distance
 */
private int computeMinimumLevenshteinDistance(ArrayList word) {

    theTrie.minLevDist = Integer.MAX_VALUE;

    int iWordLength = word.size();
    int[] currentRow = new int[iWordLength + 1];

    for (int i = 0; i <= iWordLength; i++) {
        currentRow[i] = i;
    }

    for (int i = 0; i < iWordLength; i++) {
        traverseTrie(theTrie.root, word.get(i), word, currentRow);
    }
    return theTrie.minLevDist;
}

/**
 * Recursive helper function. Traverses theTrie in search of the minimum Levenshtein Distance.
 * 
 * @param TrieNode node - the current TrieNode
 * @param char letter - the current character of the current word we're working with
 * @param ArrayList word - an array representation of the current word
 * @param int[] previousRow - a row in the Levenshtein Distance matrix
 */
private void traverseTrie(TrieNode node, char letter, ArrayList word, int[] previousRow) {

    int size = previousRow.length;
    int[] currentRow = new int[size];
    currentRow[0] = previousRow[0] + 1;

    int minimumElement = currentRow[0];
    int insertCost, deleteCost, replaceCost;

    for (int i = 1; i < size; i++) {

        insertCost = currentRow[i - 1] + 1;
        deleteCost = previousRow[i] + 1;

        if (word.get(i - 1) == letter) {
            replaceCost = previousRow[i - 1];
        } else {
            replaceCost = previousRow[i - 1] + 1;
        }

        currentRow[i] = minimum(insertCost, deleteCost, replaceCost);

        if (currentRow[i] < minimumElement) {
            minimumElement = currentRow[i];
        }
    }

    if (currentRow[size - 1] < theTrie.minLevDist && node.isWord) {
        theTrie.minLevDist = currentRow[size - 1];
    }

    if (minimumElement < theTrie.minLevDist) {

        for (Character c : node.children.keySet()) {
            traverseTrie(node.children.get(c), c, word, currentRow);
        }
    }
}

ОБНОВЛЕНИЕ 2

Наконец, мне удалось заставить это работать для большинства моих тестовых примеров. Моя реализация является практически прямым переводом из версии C ++ Мурило из Стива Ханова ' s алгоритм . Итак, как мне провести рефакторинг этого алгоритма и / или провести оптимизацию? Ниже приведен код ...

public int search(String word) {

    theTrie.minLevDist = Integer.MAX_VALUE;

    int size = word.length();
    int[] currentRow = new int[size + 1];

    for (int i = 0; i <= size; i++) {
        currentRow[i] = i;
    }
    for (int i = 0; i < size; i++) {
        char c = word.charAt(i);
        if (theTrie.root.children.containsKey(c)) {
            searchRec(theTrie.root.children.get(c), c, word, currentRow);
        }
    }
    return theTrie.minLevDist;
}
private void searchRec(TrieNode node, char letter, String word, int[] previousRow) {

    int size = previousRow.length;
    int[] currentRow = new int[size];
    currentRow[0] = previousRow[0] + 1;

    int insertCost, deleteCost, replaceCost;

    for (int i = 1; i < size; i++) {

        insertCost = currentRow[i - 1] + 1;
        deleteCost = previousRow[i] + 1;

        if (word.charAt(i - 1) == letter) {
            replaceCost = previousRow[i - 1];
        } else {
            replaceCost = previousRow[i - 1] + 1;
        }
        currentRow[i] = minimum(insertCost, deleteCost, replaceCost);
    }

    if (currentRow[size - 1] < theTrie.minLevDist && node.isWord) {
        theTrie.minLevDist = currentRow[size - 1];
    }

    if (minElement(currentRow) < theTrie.minLevDist) {

        for (Character c : node.children.keySet()) {
            searchRec(node.children.get(c), c, word, currentRow);

        }
    }
}

Спасибо всем, кто участвовал в этом вопросе. Я пытался заставить работать автоматы Левенштейна, но не смог.

Я ищу предложения по рефакторингу и / или оптимизации приведенного выше кода. Пожалуйста, дайте мне знать, если возникнет путаница. Как всегда, я могу предоставить остальной исходный код по мере необходимости.


ОБНОВЛЕНИЕ 1

Итак, я реализовал простую структуру данных Trie и пытался следовать руководству Стива Ханова по питону, чтобы вычислить расстояние Левенштейна. . На самом деле, меня интересует вычисление минимального расстояния Левенштейна между заданным словом и словами в Trie, поэтому я следил за Мурило Васконселосом ' s версия алгоритма Стива Ханова . Он работает не очень хорошо, но вот мой класс Trie:

public class Trie {

    public TrieNode root;
    public int minLevDist;

    public Trie() {
        this.root = new TrieNode(' ');
    }

    public void insert(String word) {

        int length = word.length();
        TrieNode current = this.root;

        if (length == 0) {
            current.isWord = true;
        }
        for (int index = 0; index < length; index++) {

            char letter = word.charAt(index);
            TrieNode child = current.getChild(letter);

            if (child != null) {
                current = child;
            } else {
                current.children.put(letter, new TrieNode(letter));
                current = current.getChild(letter);
            }
            if (index == length - 1) {
                current.isWord = true;
            }
        }
    }
}

... и класс TrieNode:

public class TrieNode {

    public final int ALPHABET = 26;

    public char letter;
    public boolean isWord;
    public Map children;

    public TrieNode(char letter) {
        this.isWord = false;
        this.letter = letter;
        children = new HashMap(ALPHABET);
    }

    public TrieNode getChild(char letter) {

        if (children != null) {
            if (children.containsKey(letter)) {
                return children.get(letter); 
            }
        }
        return null;
    }
}

Теперь я попытался реализовать поиск, поскольку он есть у Мурило Васконселос , но кое-что выключен, и мне нужна помощь в его отладке. Пожалуйста, дайте предложения, как это исправить, и / или укажите, где находятся ошибки. Самое первое, что я хотел бы отрефакторить, - это глобальная переменная minCost, но это самая маленькая вещь. В любом случае, вот код ...

public void search(String word) {

    int size = word.length();
    int[] currentRow = new int[size + 1];

    for (int i = 0; i <= size; i++) {
        currentRow[i] = i;
    }
    for (int i = 0; i < size; i++) {
        char c = word.charAt(i);
        if (theTrie.root.children.containsKey(c)) {
            searchRec(theTrie.root.children.get(c), c, word, currentRow);
        }
    }
}

private void searchRec(TrieNode node, char letter, String word, int[] previousRow) {

    int size = previousRow.length;
    int[] currentRow = new int[size];
    currentRow[0] = previousRow[0] + 1;

    int replace, insertCost, deleteCost;

    for (int i = 1; i < size; i++) {

        char c = word.charAt(i - 1);

        insertCost = currentRow[i - 1] + 1;
        deleteCost = previousRow[i] + 1;
        replace = (c == letter) ? previousRow[i - 1] : (previousRow[i - 1] + 1);

        currentRow[i] = minimum(insertCost, deleteCost, replace);
    }

    if (currentRow[size - 1] < minCost && !node.isWord) {
        minCost = currentRow[size - 1];
    }
    Integer minElement = minElement(currentRow);
    if (minElement < minCost) {

        for (Map.Entry entry : node.children.entrySet()) {
            searchRec(node, entry.getKey(), word, currentRow);
        }
    }
}

Прошу прощения за отсутствие комментариев. Так что я делаю не так?

НАЧАЛЬНАЯ СТАТЬЯ

Я читал статью Быстрое и легкое расстояние Левенштейна с использованием Trie , в надежде найти эффективный способ вычисления расстояния Левенштейна между двумя строками. Моя главная цель - с учетом большого набора слов найти минимальное расстояние Левенштейна между входным словом (ями) и этим набором слов.

В моей тривиальной реализации я вычисляю расстояние Левенштейна между входным словом и набором слов для каждого входного слова и возвращаю минимум. Это работает, но неэффективно ...

Я искал реализации Trie на Java и наткнулся на два, казалось бы, хороших источника:

  • версия Koders.com
  • код .google.com версия Я вычисляю расстояние Левенштейна между входным словом и набором слов для каждого входного слова и возвращаю минимум. Это работает, но неэффективно ...

    Я искал реализации Trie на Java и наткнулся на два, казалось бы, хороших источника:

    • версия Koders.com
    • код .google.com версия Я вычисляю расстояние Левенштейна между входным словом и набором слов для каждого входного слова и возвращаю минимум. Это работает, но неэффективно ...

      Я искал реализации Trie на Java и наткнулся на два, казалось бы, хороших источника:

      Однако эти реализации кажутся слишком сложными для того, что я пытаюсь сделать. Когда я читал их, чтобы понять, как они работают и как вообще работают структуры данных Trie, я только больше запутался.

      Так как мне реализовать простую структуру данных Trie в Java? Моя интуиция подсказывает мне, что каждый TrieNode должен хранить строку, которую он представляет, а также ссылки на буквы алфавита, не обязательно на все буквы. Моя интуиция верна?

      Как только это будет реализовано, следующая задача - вычислить расстояние Левенштейна. Я прочитал пример кода Python в статье выше, но я не говорю на Python, и моя реализация Java исчерпывает память кучи, как только я нажимаю на рекурсивный поиск. Итак, как мне вычислить расстояние Левенштейна, используя структуру данных Trie? У меня есть тривиальная реализация, построенная по образцу этого исходного кода , но она не использует Trie ... это неэффективно.

      Было бы здорово увидеть код в дополнение к вашим комментариям и предложениям. В конце концов, для меня это процесс обучения ... Я никогда не реализовывал Trie ... так что мне есть чему поучиться на этом опыте.

      Спасибо.

      ps Я могу предоставить любой исходный код, если потребуется. Кроме того, я уже прочитал и попытался использовать BK-Tree, как это было предложено в блоге Ника Джонсона , но оно не так эффективно, как я думаю ... или, возможно, моя реализация неверна.

37
задан Michael Veksler 17 June 2019 в 08:56
поделиться