Простой алгоритм проверки орфографии

мне было поручено создать простую проверку орфографии для задания, но не дали никаких указаний, поэтому было интересно, может ли кто-нибудь помочь мне. Я не за тем, чтобы кто-то делал задание для меня, но любое направление или помощь с алгоритмом были бы потрясающими! Если то, о чем я прошу, не находится в пределах гильдии сайта, тогда мне жаль, и я посмотрю в другом месте. :)

проект загружает правильно пишется строчными буквами слова и предложения правописание на основе двух критериев:

  • разница в одну букву (либо добавлено, либо вычитается, чтобы получить слово так же, как слово в словаре). Например, 'стек' будет предложение 'staick' и 'круто' будет предложение 'Гуль'.

  • замена одной буквы. Так, например, "плохо" было бы предложением для "БПК".

Итак, просто чтобы убедиться, что я объяснил правильно.. Я мог бы добавить слова: [привет, прощай!, фантастический, хороший, Бог], а затем предложения для (неправильно написанного) слова "godd" будут [good, god].

скорость это мое главное соображение здесь, поэтому, хотя я думаю, что знаю способ получить эту работу, я действительно не слишком уверен в том, насколько она будет эффективной. То, как я думаю это сделать, - создать map<string, vector<string>> а затем для каждого правильно написанного слова, которое загружено, добавьте правильно написанную работу в качестве ключа на карте и заполните вектор, чтобы быть всем возможные "неправильные" перестановки этого слова.

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

Кажется, что это займет кучу памяти, хотя, потому что наверняка будут тысячи перестановок для каждого слова? Также кажется, что это будет очень медленно, если мой первоначальный словарь правильно написанные слова были большими?

Я думал, что, может быть, я мог бы сократить время немного, только глядя на клавиши, которые похожи на слово, на которое я смотрю. Но опять же, если они похожи каким-то образом, это, вероятно, означает, что ключом будет предложение, означающее, что мне не нужны все эти перестановки!

Так что да, я немного озадачен тем, в каком направлении я должен смотреть. Я был бы очень признателен за любую помощь, поскольку я действительно не уверен, как оценить скорость различные способы делать вещи (нас этому вообще не учили в классе).

4 ответов


более простой способ решить проблему-это действительно предварительно вычисленная карта [плохое слово] - > [предложения].

проблема в том, что при удалении письма создает несколько "плохих слов", для добавления или замены у вас много кандидатов.

поэтому я бы предложил другое решение ;)

Примечание: расстояние редактирования, которое вы описываете, называется Расстояние Левенштейна

решение описывается в пошаговом режиме, обычно скорость поиска должна постоянно улучшаться при каждой идее, и я попытался сначала организовать их с более простыми идеями (с точки зрения реализации). Не стесняйтесь останавливаться, когда вам удобно с результатами.


0. Предварительные

  • реализация алгоритма Левенштейна
  • хранить словарь в отсортированной последовательности (std::set например, хотя сортировка std::deque или std::vector было бы лучше производительность мудрый)

Ключи, Очки:

  • вычисление расстояния Левенштейна использует массив, на каждом шаге следующая строка вычисляется только с предыдущей строкой
  • минимальное расстояние в строке всегда превосходит (или равно) минимуму в предыдущей строке

последнее свойство допускает реализацию короткого замыкания: если вы хотите ограничить себя 2 ошибками (treshold), затем, когда минимум текущей строки превышает 2, Вы можете отказаться от вычисления. Простая стратегия-вернуть порог + 1, как расстояние.


1. Первое Предварительное

давайте начнем простого.

мы реализуем линейное сканирование: для каждого слова мы вычисляем расстояние (короткое замыкание), и мы перечисляем те слова, которые достигли меньшего расстояния до сих пор.

он работает очень хорошо на smallish словари.


2. Улучшение структуры данных

расстояние Левенштейна-это по крайней мере равно разнице в длине.

используя в качестве ключа пару (длина, слово) вместо просто слова, вы можете ограничить свой поиск диапазоном длины [length - edit, length + edit] и значительно уменьшить пространство поиска.


3. Приставки и обрезка

чтобы улучшить это, мы можно отметить, что когда мы строим матрицу расстояний, строка за строкой, один мир полностью сканируется (слово, которое мы ищем), но другой (референт) нет: мы используем только одну букву для каждой строки.

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

помните, что я прошу вас сохранить сортированный словарь ? Это означает, что слова с одинаковым префиксом примыкающий.

предположим, что вы проверяете свое слово против cartoon и car вы понимаете, что это не работает (расстояние уже слишком долго), то любое слово, начинающееся на car не работает, вы можете пропустить слова, пока они начинаются с car.

сам пропуск можно сделать либо линейно, либо с помощью поиска (найти первое слово, которое имеет более высокий префикс, чем car):

  • linear работает лучше всего, если префикс длинный (несколько слов пропустить)
  • двоичный поиск работает лучше всего для короткого префикса (много слов, чтобы пропустить)

как долго "долго" зависит от вашего словаря, и вам придется измерять. Я бы начал с бинарного поиска.

Примечание: секционирование длины работает против секционирования префикса, но оно сокращает гораздо больше пространства поиска


4. Префиксы и повторное использование

теперь мы попробуйте повторно использовать вычисление как можно больше (а не только результат "это не работает")

предположим, что у вас есть два слова:

  • мультфильм
  • автомойка

сначала вычислить матрицы по строкам, для cartoon. Тогда при чтении carwash необходимо определить длину общего префикса (здесь car), и вы можете сохранить первые 4 строки матрицы (соответствующие void,c, a, r).

следовательно, когда начинают вычислять carwash, вы фактически начинаете итерацию в w.

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


5. Использование "лучшей" структуры данных

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

это последний вариант. Это дорого.


вы должны взглянуть на это объяснение Питера Норвига о том, как написать корректор орфографии .

Как написать корректор правописания

все хорошо объяснить в этой статье, в качестве примера код python для проверки орфографии выглядит так:

import re, collections

def words(text): return re.findall('[a-z]+', text.lower()) 

def train(features):
    model = collections.defaultdict(lambda: 1)
    for f in features:
        model[f] += 1
    return model

NWORDS = train(words(file('big.txt').read()))

alphabet = 'abcdefghijklmnopqrstuvwxyz'

def edits1(word):
   splits     = [(word[:i], word[i:]) for i in range(len(word) + 1)]
   deletes    = [a + b[1:] for a, b in splits if b]
   transposes = [a + b[1] + b[0] + b[2:] for a, b in splits if len(b)>1]
   replaces   = [a + c + b[1:] for a, b in splits for c in alphabet if b]
   inserts    = [a + c + b     for a, b in splits for c in alphabet]
   return set(deletes + transposes + replaces + inserts)

def known_edits2(word):
    return set(e2 for e1 in edits1(word) for e2 in edits1(e1) if e2 in NWORDS)

def known(words): return set(w for w in words if w in NWORDS)

def correct(word):
    candidates = known([word]) or known(edits1(word)) or known_edits2(word) or [word]
    return max(candidates, key=NWORDS.get)

надеюсь, вы можете найти то, что вам нужно на веб-сайте Peter Norvig.


для проверки орфографии многие структуры данных были бы полезны, например, BK-Tree. Проверка чертовски крутые алгоритмы, Часть 1: BK-деревья Я сделал реализацию для того же здесь

Моя более ранняя ссылка кода может вводить в заблуждение,этот правильно для корректора орфографии.


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

должно быть довольно быстро, но я не уверен в самом коде, я не очень хорошо разбираюсь в c++