Нужен более быстрый алгоритм Word Builder - PullRequest
0 голосов
/ 22 ноября 2018

У меня есть приложение, которое помогает изучать Эрудит.Большинство запросов выполняется намного быстрее, чем в C #, за исключением версии Word Builder.Этот поиск показывает все слова, которые могут быть образованы из заданного набора букв AZ или пробелов.Что я могу сделать, чтобы заставить его работать быстрее?Я подумывал об использовании Trie, но не нашел способа поддержать использование пробелов.Я использую SimpleCursorAdapter для заполнения ListView, поэтому я возвращаю курсор.

    public Cursor getCursor_subanagrams(String term, String filters, String ordering) {
    if (term.trim() == "")
        return null;
    // only difference between this and anagram is changing the length filter
    char[] a = term.toCharArray(); // anagram

    int[] first = new int[26]; // letter count of anagram
    int c; // array position
    int blankcount = 0;

    // initialize word to anagram
    for (c = 0; c < a.length; c++) {
        if (a[c] == '?') {
            blankcount++;
            continue;
        }
        first[a[c] - 'A']++;
    }

// gets pool of words to search through
    String lenFilter = String.format("Length(Word) <= %1$s AND Length(Word) <= %2$s", LexData.getMaxLength(), term.length());
    Cursor cursor = database.rawQuery("SELECT WordID as _id, Word, WordID, FrontHooks, BackHooks, " +
            "InnerFront, InnerBack, Anagrams, ProbFactor, OPlayFactor, Score \n" +
            "FROM     `" + LexData.getLexName() + "` \n" +
            "WHERE (" + lenFilter +
            filters +
            " ) " + ordering, null);

// creates new cursor to add valid words to
    MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_id", "Word", "WordID", "FrontHooks", "BackHooks", "InnerFront", "InnerBack",
            "Anagrams", "ProbFactor", "OPlayFactor", "Score"});

// THIS NEEDS TO BE FASTER
    while (cursor.moveToNext()) {
        String word = cursor.getString(1);
        char[] b = word.toCharArray();
        if (isAnagram(first, b, blankcount)) {
            matrixCursor.addRow(get_CursorRow(cursor));
        }
    }
    cursor.close();
    return matrixCursor;
}


private boolean isAnagram(int[] anagram, char[] word, int blankcount) {
    int matchcount = blankcount;
    int c; // each letter
    int[] second = {0,0,0,0,0, 0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0, 0};

    for (c = 0; c < word.length; c++)
        second[word[c] - 'A']++;

    for (c = 0; c < 26; c++)
    {
        matchcount += (anagram[c]<second[c]) ? anagram[c]:second[c];
    }

    if (matchcount == word.length)
        return true;
    return false;
    }

Ответы [ 3 ]

0 голосов
/ 24 ноября 2018

Есть способы ускорить вашу функцию проверки анаграммы.Самгак указал один.Другая очевидная оптимизация - возвращать false, если слово длиннее, чем количество доступных букв плюс пробелы.В конце концов, это все микрооптимизации, и в итоге вы проверите весь словарь.

Вы сказали, что рассматривали возможность использования trie.На мой взгляд, это хорошее решение, потому что структура этого слова заставит вас проверять только соответствующие слова.Создайте это так:

  • Сортируйте буквы каждого слова, чтобы «треугольник» и «интеграл» оба стали «aegilnrt».
  • Вставьте отсортированное слово в три.
  • Если вы поместите конечный маркер в обычном дереве, вы поместите список возможных слов.

Если вы искали точные анаграммы, вы бы отсортировали словочтобы проверить, проследить три и распечатать список возможных анаграмм в конце.Но здесь вам приходится иметь дело с частичными анаграммами и пробелами:

  • Регулярный обход означает, что вы берете следующую букву слова и затем спускаетесь по соответствующей ссылке в дереве, если она существует.
  • Частичные анаграммы можно найти, игнорируя следующую букву, не убирая в дереве.
  • С пробелами можно справиться, опустив все возможные ветви дерева и уменьшив количество пробелов.

Если у вас есть пробелы, вы получите дубликаты.Например, если у вас есть буквы A, B и C и пустая плитка, вы можете сделать слово CAB, но вы можете получить его четырьмя различными способами: CAB, _AB, C_B, CA _.

Выможно обойти это, сохранив список результатов в структуре данных, которая исключает дубликаты, такие как набор или упорядоченный набор, но вам все равно придется идти по одному и тому же пути несколько раз, чтобы создать дубликаты.

AЛучшее решение состоит в том, чтобы отслеживать, какие узлы Tree вы посетили, с какими параметрами, т.е. с оставшимися неиспользованными буквами и пробелами.Вы можете сократить такие пути.Вот реализация в псевдокоде:

function find_r(t, str, blanks, visited)
{
    // don't revisit explored paths
    key = make_key(t, str, blanks);

    if (key in visited) return [];
    visited ~= key;

    if (str.length == 0 and blanks == 0) {   
        // all resources have been used: return list of anagrams        
        return t.word;
    } else {
        res = [];
        c = 0;

        if (str.length > 0) {
            c = str[0];

            // regular traversal: use current letter and descend
            if (c in t.next) {
                res ~= find_r(t.next[c], str[1:], blanks, visited);
            }

            # partial anagrams: skip current letter and don't descend
            l = 1
            while (l < str.length and str[l] == c) l++;

            res ~= find_r(t, str[l:], blanks, visited);
        }

        if (blanks > 0) {
            // blanks: decrease blanks and descend
            for (i in t.next) {
                if (i < c) {
                    res ~= find_r(t.next[i], str, blanks - 1, visited);
                }
            }
        }

        return res;
    }
}

(Здесь ~ обозначает конкатенацию списка или вставку набора; [beg=0:end=length] обозначает фрагменты строки; in проверяет, содержит ли словарь или набор ключ.)

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

Теперь в игре «Скрэббл» не более двух пробелов, а стойка может содержать до семи плиток.так что на практике это может быть не так плохо.Другой вопрос, должен ли поиск учитывать слова, полученные с двумя пробелами.Список результатов будет очень длинным и будет содержать все двухбуквенные слова.Игрок может быть более заинтересован в словах с высокими показателями, которые можно воспроизвести одним пробелом.

0 голосов
/ 07 декабря 2018

Я использовал рекомендацию samgak для проверки на «несоответствия».Вместо того, чтобы проверять каждую букву перед продолжением, я выхожу из нее во время проверки каждой буквы.Я думал, что смогу просто проверить несоответствия и проигнорировать подсчет совпадений, но не смог заставить его работать точно.Необходим дополнительный анализ.

Это ускорило примерно на 20% в соответствии с первоначальными тестами с эмулятором.Возможно, я смогу ускорить процесс, проверяя буквы по частоте букв;проверьте наиболее распространенные буквы (LNSTE) перед другими (ZQJ).Теперь, чтобы выяснить, как отсортировать массив по одному полю в массиве, но это другой вопрос.

    private boolean isAnagram(int[] anagram, char[] word, int blankcount) {
    // anagram is letters in term
    int matchcount = blankcount;
    int mismatchcount = 0;
    int c;
    int[] second = {0,0,0,0,0, 0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0, 0};

    for (c = 0; c < word.length; c++)
        second[word[c] - 'A']++;

    for (c = 0; c < 26; c++)
    {
        mismatchcount += (anagram[c]<second[c] ? second[c] - anagram[c]:0);             
        if (mismatchcount > blankcount)
            return false;
        matchcount += (anagram[c]<second[c]) ? anagram[c]:second[c];
    }

    if (matchcount == word.length)
        return true;
    return false;
}

РЕДАКТИРОВАТЬ: Вот еще более компактная (более быстрая?) Версия, которая считает несоответствия при построении альтернативной версии.массив символов слова.

    private boolean isAnagram(int[] anagram, char[] word, int blankcount) {
    int mismatchcount = 0;
    int c;
    int[] second = {0,0,0,0,0, 0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0, 0};
    int letter = 0;

    for (c = 0; c < word.length; c++) {
        letter = ++second[word[c] - 'A'];
        mismatchcount += (letter > anagram[word[c]-'A'] ? 1 : 0);
        if (mismatchcount > blankcount)
            return false;
    }
    return true;
}
0 голосов
/ 23 ноября 2018

Сосредоточьтесь на ускорении наиболее типичного случая, когда слово не является (под) анаграммой, и вы возвращаете false.Если вы можете определить как можно быстрее, когда невозможно сделать word из anagram, тогда вы можете избежать дорогостоящего теста.

Один из способов сделать это - использовать битовую маску букв вслова.Вам не нужно хранить количество букв, потому что если число уникальных букв в word, которых нет в anagram, больше, чем количество пробелов, то вы не сможете это сделать и сможете быстро вернутьсяложный.Если нет, то вы можете перейти к более дорогому тесту с учетом количества букв.

Вы можете предварительно вычислить битовые маски следующим образом:

private int letterMask(char[] word)
{
    int c, mask = 0;
    for (c = 0; c < word.length; c++)
        mask |= (1 << (word[c] - 'A'));
    return mask;
}

Добавить дополнительный столбец в вашу базу данных для храненияБуквенная битовая маска для каждого слова, добавьте ее к курсору и вычислите буквенную битовую маску для букв в term и сохраните в termMask.Затем внутри цикла курсора вы можете выполнить такой тест:

// compute mask of bits in mask that are not in term:
int missingLettersMask = cursor.getInt(8) & ~termMask;
if(missingLettersMask != 0)
{
    // check if we could possibly make up for these letters using blanks:
    int remainingBlanks = blankcount;
    while((remainingBlanks-- > 0) && (missingLettersMask != 0))
        missingLettersMask &= (missingLettersMask - 1); // remove one bit

    if(missingLettersMask != 0)
        continue; // move onto the next word
}

// word can potentially be made from anagram, call isAnagram:
...