# Исправление орфографии

Исправление орфографии, также известное как:

* Автокоррекция
* Коррекция текста
* Исправление орфографических ошибок
* Допуск опечаток
* "Возможно, вы имели в виду?"

и так далее, это функциональность программного обеспечения, которая предлагает альтернативы или автоматически исправляет введённый вами текст. Концепция исправления набранного текста восходит к 1960-м годам, когда учёный-компьютерщик Уоррен Тейтельман, который также изобрёл команду "отменить", представил философию вычислений под названием D.W.I.M., или "Делай Что Я Имею в Виду". Вместо того чтобы программировать компьютеры на приём только идеально отформатированных инструкций, Тейтельман утверждал, что их следует программировать на распознавание очевидных ошибок.

Первым известным продуктом, предоставившим функциональность исправления орфографии, был Microsoft Word 6.0, выпущенный в 1993 году.

### Как это работает

Есть несколько способов реализации исправления орфографии, но важно отметить, что не существует чисто программного способа с достойным качеством преобразовать вашу опечатку "ipone" в "iphone". В основном, необходима система, основанная на наборе данных. Набор данных может быть:

* Словарём правильно написанных слов, который, в свою очередь, может быть:
  * Основан на ваших реальных данных. Идея здесь в том, что по большей части орфография в словаре, составленном из ваших данных, правильная, и система пытается найти слово, наиболее похожее на введённое (мы скоро обсудим, как это можно сделать с помощью Manticore).
  * Или он может быть основан на внешнем словаре, не связанном с вашими данными. Проблема, которая может здесь возникнуть, заключается в том, что ваши данные и внешний словарь могут слишком сильно отличаться: некоторые слова могут отсутствовать в словаре, в то время как другие могут отсутствовать в ваших данных.
* Не только основанным на словаре, но и учитывающим контекст, например, "white ber" будет исправлено на "white bear", а "dark ber" — на "dark beer". Контекстом может быть не только соседнее слово в вашем запросе, но и ваше местоположение, время суток, грамматика текущего предложения (чтобы изменить "there" на "their" или нет), история ваших поисков и практически любые другие факторы, которые могут повлиять на ваше намерение.
* Ещё один классический подход — использовать предыдущие поисковые запросы в качестве набора данных для исправления орфографии. Это ещё более активно используется в функциональности [автодополнения](../Searching/Autocomplete.md), но также имеет смысл и для автокоррекции. Идея в том, что пользователи в основном правы в орфографии, поэтому мы можем использовать слова из истории их поиска в качестве источника истины, даже если у нас нет этих слов в наших документах или мы не используем внешний словарь. Учёт контекста также возможен здесь.

Manticore предоставляет опцию нечёткого поиска и команды `CALL QSUGGEST` и `CALL SUGGEST`, которые могут быть использованы для целей автоматического исправления орфографии.

## Нечёткий поиск

Функция нечёткого поиска позволяет осуществлять более гибкое сопоставление, учитывая небольшие вариации или опечатки в поисковом запросе. Она работает аналогично обычному оператору SQL `SELECT` или JSON-запросу `/search`, но предоставляет дополнительные параметры для управления поведением нечёткого сопоставления.

> ПРИМЕЧАНИЕ: Опция `fuzzy` требует наличия [Manticore Buddy](../Installation/Manticore_Buddy.md). Если она не работает, убедитесь, что Buddy установлен.

> ПРИМЕЧАНИЕ: Опция `fuzzy` недоступна для [многозапросов](../Searching/Multi-queries.md).

## Общий синтаксис

### SQL

<!-- example Fuzzy_Search_SQL -->

```sql
SELECT
  ...
  MATCH('...')
  ...
  OPTION fuzzy={0|1}
  [, distance=N]
  [, preserve={0|1}]
  [, layouts='{be,bg,br,ch,de,dk,es,fr,uk,gr,it,no,pt,ru,se,ua,us}']
}
```

Примечание: При проведении нечёткого поиска через SQL, в выражении MATCH не должно быть никаких полнотекстовых операторов, кроме [оператора поиска по фразе](../Searching/Full_text_matching/Operators.md#Phrase-search-operator), и оно должно содержать только слова, которые вы намерены сопоставить.

<!-- intro -->
##### SQL:

<!-- request SQL -->

```sql
SELECT * FROM mytable WHERE MATCH('someting') OPTION fuzzy=1, layouts='us,ua', distance=2;
```

<!-- request SQL with additional filters -->
Пример более сложного запроса нечёткого поиска с дополнительными фильтрами:

```sql
SELECT * FROM mytable WHERE MATCH('someting') OPTION fuzzy=1 AND (category='books' AND price < 20);
```

<!-- request JSON -->

```json
POST /search
{
  "table": "test",
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "*": "ghbdtn"
          }
        }
      ]
    }
  },
  "options": {
    "fuzzy": true,
    "layouts": ["us", "ru"],
    "distance": 2
  }
}
```

<!-- response SQL -->

```sql
+------+-------------+
| id   | content     |
+------+-------------+
|    1 | something   |
|    2 | some thing  |
+------+-------------+
2 rows in set (0.00 sec)
```

<!-- request SQL with preserve option -->

```sql
SELECT * FROM mytable WHERE MATCH('hello wrld') OPTION fuzzy=1, preserve=1;
```

<!-- request JSON with preserve option -->

```json
POST /search
{
  "table": "test",
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "*": "hello wrld"
          }
        }
      ]
    }
  },
  "options": {
    "fuzzy": true,
    "preserve": 1
  }
}
```

<!-- response SQL with preserve option -->

```sql
+------+-------------+
| id   | content     |
+------+-------------+
|    1 | hello wrld  |
|    2 | hello world |
+------+-------------+
2 rows in set (0.00 sec)
```

<!-- end -->

### JSON

```json
POST /search
{
  "table": "table_name",
  "query": {
    <full-text query>
  },
  "options": {
    "fuzzy": {true|false}
    [,"layouts": ["be","bg","br","ch","de","dk","es","fr","uk","gr","it","no","pt","ru","se","ua","us"]]
    [,"distance": N]
    [,"preserve": {0|1}]
  }
}
```

Примечание: Если вы используете [query_string](../Searching/Full_text_matching/Basic_usage.md#query_string), имейте в виду, что он не поддерживает полнотекстовые операторы, кроме [оператора поиска по фразе](../Searching/Full_text_matching/Operators.md#Phrase-search-operator). Строка запроса должна состоять исключительно из слов, которые вы хотите сопоставить.

### Опции

- `fuzzy`: Включает или выключает нечёткий поиск.
- `distance`: Устанавливает расстояние Левенштейна для сопоставления. По умолчанию `2`.
- `preserve`: `0` или `1` (по умолчанию: `0`). При установке в `1` сохраняет слова, не имеющие нечётких совпадений, в результатах поиска (например, "hello wrld" возвращает и "hello wrld", и "hello world"). При установке в `0` возвращает только слова с успешными нечёткими совпадениями (например, "hello wrld" возвращает только "hello world"). Особенно полезно для сохранения коротких слов или имён собственных, которых может не быть в Manticore Search.
- `layouts`: Раскладки клавиатуры для обнаружения ошибок ввода, вызванных несоответствием раскладки клавиатуры (например, ввод "ghbdtn" вместо "привет" при использовании неправильной раскладки). Manticore сравнивает позиции символов в разных раскладках, чтобы предложить исправления. Требуется как минимум 2 раскладки для эффективного обнаружения несоответствий. По умолчанию раскладки не используются. Используйте пустую строку `''` (SQL) или массив `[]` (JSON), чтобы отключить это. Поддерживаемые раскладки включают:
  - `be` - Бельгийская раскладка AZERTY
  - `bg` - Стандартная болгарская раскладка
  - `br` - Бразильская раскладка QWERTY
  - `ch` - Швейцарская раскладка QWERTZ
  - `de` - Немецкая раскладка QWERTZ
  - `dk` - Датская раскладка QWERTY
  - `es` - Испанская раскладка QWERTY
  - `fr` - Французская раскладка AZERTY
  - `uk` - Британская раскладка QWERTY
  - `gr` - Греческая раскладка QWERTY
  - `it` - Итальянская раскладка QWERTY
  - `no` - Норвежская раскладка QWERTY
  - `pt` - Португальская раскладка QWERTY
  - `ru` - Русская раскладка JCUKEN
  - `se` - Шведская раскладка QWERTY
  - `ua` - Украинская раскладка JCUKEN
  - `us` - Американская раскладка QWERTY


### Ссылки

* <a href="https://github.manticoresearch.com/manticoresoftware/manticoresearch?query=fature&filters%5Bcomment%5D%5B%5D=28798446&filters%5Bcommon%5D%5Brepo_id%5D%5B%5D=95614931&sort=&search=keyword-search-fuzzy-layouts">Это демо</a> демонстрирует функциональность нечёткого поиска:
  ![Пример нечёткого поиска](fuzzysearch.png){.scale-0.7}
* Пост в блоге о Нечётком поиске и автодополнении - https://manticoresearch.com/blog/new-fuzzy-search-and-autocomplete/

## CALL QSUGGEST, CALL SUGGEST

Обе команды доступны через SQL и поддерживают запросы к локальным (plain и real-time) и распределённым таблицам. Синтаксис следующий:
```sql
CALL QSUGGEST(<word or words>, <table name> [,options])
CALL SUGGEST(<word or words>, <table name> [,options])

options: N as option_name[, M as another_option, ...]
```

Эти команды возвращают все предложения из словаря для заданного слова. Они работают только для таблиц, где включен [infixing](../Creating_a_table/NLP_and_tokenization/Wildcard_searching_settings.md#min_infix_len) и задан [dict=keywords](../Creating_a_table/NLP_and_tokenization/Low-level_tokenization.md#dict). Они не работают с таблицами, использующими `dict=keywords_32k`. Они возвращают предложенные ключевые слова, расстояние Левенштейна между предложенным и исходным ключевыми словами, а также статистику документов для предложенного ключевого слова.

Если первый параметр содержит несколько слов, то:
* `CALL QSUGGEST` вернёт предложения только для **последнего** слова, игнорируя остальные.
* `CALL SUGGEST` вернёт предложения только для **первого** слова.

Это единственное различие между ними. Поддерживается несколько опций для настройки:

| Опция | Описание | По умолчанию |
| - | - | - |
| limit | Возвращает N лучших совпадений | 5 |
| max_edits | Оставляет только слова из словаря с расстоянием Левенштейна меньше или равным N | 4 |
| result_stats | Предоставляет расстояние Левенштейна и количество документов для найденных слов | 1 (включено) |
| delta_len | Оставляет только слова из словаря с разницей в длине меньше N | 3 |
| max_matches | Количество совпадений для сохранения | 25 |
| reject | Отклонённые слова — это совпадения, которые не лучше уже находящихся в очереди совпадений. Они помещаются в очередь отклонённых, которая сбрасывается, если слово всё же может попасть в очередь совпадений. Этот параметр определяет размер очереди отклонённых (как reject*max(max_matched,limit)). Если очередь отклонённых заполнена, движок прекращает поиск потенциальных совпадений | 4 |
| result_line | альтернативный режим отображения данных, возвращающий все предложения, расстояния и количество документов каждое в отдельной строке | 0 |
| non_char | не пропускать слова из словаря с неалфавитными символами | 0 (пропускать такие слова) |
| sentence | Возвращает исходное предложение с заменой последнего слова на подобранное. | 0 (не возвращать полное предложение) |
| force_bigrams | Принудительно использовать биграммы (n-граммы из 2 символов) вместо триграмм для всех длин слов, что может улучшить сопоставление для слов с ошибками транспозиции | 0 (использовать триграммы для слов ≥6 символов) |
| search_mode | Уточняет предложения, выполняя поиск по индексу. Принимает `'phrase'` для точного соответствия фразы или `'words'` для соответствия по модели "мешок слов". При включении добавляет столбец `found_docs`, показывающий количество документов, и пересортировывает результаты по убыванию `found_docs`, затем по возрастанию `distance`. | N/A (отключено по умолчанию) |

Чтобы показать, как это работает, создадим таблицу и добавим в неё несколько документов.

```sql
create table products(title text) min_infix_len='2';
insert into products values (0,'Crossbody Bag with Tassel'), (0,'microfiber sheet set'), (0,'Pet Hair Remover Glove');
```
<!-- example single -->
##### Пример с одним словом
Как видно, слово с опечаткой "crossb**U**dy" исправляется на "crossbody". По умолчанию `CALL SUGGEST/QSUGGEST` возвращают:

* `distance` — расстояние Левенштейна, означающее, сколько правок потребовалось, чтобы преобразовать заданное слово в предложенное
* `docs` — количество документов, содержащих предложенное слово

Чтобы отключить отображение этой статистики, можно использовать опцию `0 as result_stats`.


<!-- intro -->
##### Пример:

<!-- request Example -->

```sql
call suggest('crossbudy', 'products');
```
<!-- response Example -->

```sql
+-----------+----------+------+
| suggest   | distance | docs |
+-----------+----------+------+
| crossbody | 1        | 1    |
+-----------+----------+------+
```
<!-- end -->
<!-- example first -->
##### CALL SUGGEST берёт только первое слово
Если первый параметр — не одно слово, а несколько, то `CALL SUGGEST` вернёт предложения только для первого слова.


<!-- intro -->
##### Пример:

<!-- request Example -->

```sql
call suggest('bagg with tasel', 'products');
```
<!-- response Example -->

```sql
+---------+----------+------+
| suggest | distance | docs |
+---------+----------+------+
| bag     | 1        | 1    |
+---------+----------+------+
```
<!-- end -->
<!-- example last -->
##### CALL QSUGGEST берёт только последнее слово
Если первый параметр — не одно слово, а несколько, то `CALL SUGGEST` вернёт предложения только для последнего слова.


<!-- intro -->
##### Пример:

<!-- request Example -->

```sql
CALL QSUGGEST('bagg with tasel', 'products');
```
<!-- response Example -->

```sql
+---------+----------+------+
| suggest | distance | docs |
+---------+----------+------+
| tassel  | 1        | 1    |
+---------+----------+------+
```
<!-- end -->

<!-- example last2 -->

Добавление `1 as sentence` заставляет `CALL QSUGGEST` возвращать всё предложение с исправленным последним словом.

<!-- request Example -->
```sql
CALL QSUGGEST('bag with tasel', 'products', 1 as sentence);
```
<!-- response Example -->
```sql
+-------------------+----------+------+
| suggest           | distance | docs |
+-------------------+----------+------+
| bag with tassel   | 1        | 1    |
+-------------------+----------+------+
```
<!-- end -->

##### Другой режим отображения
Опция `1 as result_line` меняет способ отображения предложений в выводе. Вместо показа каждого предложения в отдельной строке, она отображает все предложения, расстояния и количество документов в одной строке. Вот пример, демонстрирующий это:

<!-- intro -->
##### Пример:

<!-- request Example -->

```sql
CALL QSUGGEST('bagg with tasel', 'products', 1 as result_line);
```
<!-- response Example -->

```sql
+----------+--------+
| name     | value  |
+----------+--------+
| suggests | tassel |
| distance | 1      |
| docs     | 1      |
+----------+--------+
```
<!-- end -->

##### Использование force_bigrams для лучшей обработки транспозиций
Опция `force_bigrams` может помочь со словами, содержащими ошибки транспозиции, например, "ipohne" и "iphone". Используя биграммы вместо триграмм, алгоритм может лучше обрабатывать перестановки символов.

<!-- intro -->
##### Пример:

<!-- request Example -->

```sql
CALL SUGGEST('ipohne', 'products', 1 as force_bigrams);
```
<!-- response Example -->

```sql
+--------+----------+------+
| suggest| distance | docs |
+--------+----------+------+
| iphone | 2        | 1    |
+--------+----------+------+
```
<!-- end -->

##### Уточнение предложений с помощью search_mode
Опция `search_mode` улучшает предложения, выполняя фактические поисковые запросы по индексу для подсчёта количества документов, содержащих каждую предложенную фразу или комбинацию слов. Это помогает ранжировать предложения на основе реальной релевантности документов, а не только статистики словаря.

Опция принимает два значения:
- `'phrase'` - Выполняет поиск точных фраз. Например, при предложении "bag with tassel" выполняется поиск точной фразы `"bag with tassel"` и подсчитываются документы, содержащие эти слова как смежную фразу.
- `'words'` - Выполняет поиск по модели "мешок слов". Например, при предложении "bag with tassel" выполняется поиск `bag with tassel` (без кавычек) и подсчитываются документы, содержащие все эти слова, независимо от порядка или наличия других слов между ними.

> ПРИМЕЧАНИЕ: Опция `search_mode` работает только тогда, когда включен режим `sentence` (т.е. когда ввод содержит несколько слов). Для запросов из одного слова `search_mode` игнорируется.

> ПРИМЕЧАНИЕ: **Соображения производительности**: Каждый кандидат на предложение запускает отдельный поисковый запрос к индексу. Если вам нужно оценить много кандидатов, рассмотрите использование меньшего значения `limit`, чтобы сократить количество выполняемых поисков.

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

<!-- intro -->
##### Пример с фразовым соответствием:

<!-- request Example -->

```sql
CALL QSUGGEST('bag with tasel', 'products', 1 as sentence, 'phrase' as search_mode);
```

<!-- response Example -->

```sql
+-------------------+----------+------+-------------+
| suggest           | distance | docs | found_docs  |
+-------------------+----------+------+-------------+
| bag with tassel   | 1        | 13   | 10          |
| bag with tazer    | 2        | 27   | 3           |
+-------------------+----------+------+-------------+
```

<!-- end -->

<!-- intro -->
##### Пример сравнения фразового соответствия и соответствия по словам:

<!-- request Example -->

```sql
-- With phrase matching: finds exact phrases only
CALL QSUGGEST('test carp', 'products', 1 as sentence, 'phrase' as search_mode);

-- With words matching: finds documents with all words regardless of order
CALL QSUGGEST('test carp', 'products', 1 as sentence, 'words' as search_mode);
```

<!-- response Example -->

```sql
-- Phrase mode results:
+----------------+----------+------+-------------+
| suggest        | distance | docs | found_docs  |
+----------------+----------+------+-------------+
| test car       | 1        | 17   | 5           |
| test carpet    | 2        | 19   | 4           |
+----------------+----------+------+-------------+

-- Words mode results (more matches for "test carpet" due to word separation):
+----------------+----------+------+-------------+
| suggest        | distance | docs | found_docs  |
+----------------+----------+------+-------------+
| test carpet    | 2        | 19   | 19          |
| test car       | 1        | 17   | 5           |
+----------------+----------+------+-------------+
```

<!-- end -->

**Понимание разницы**:
- **Фразовое соответствие** (`'phrase'`): Ищет точные последовательности. Запрос `"test carpet"` соответствует только документам, где эти слова встречаются вместе в точном порядке (например, "test carpet cleaning" соответствует, а "test the carpet" или "carpet test" - нет).
- **Соответствие по модели "мешок слов"** (`'words'`): Ищет наличие всех слов в документе, порядок не имеет значения. Запрос `test carpet` соответствует любому документу, содержащему оба слова "test" и "carpet" где угодно (например, "test the carpet", "test red carpet", "carpet test" - все соответствуют).

### Демонстрация

* [Этот интерактивный курс](https://play.manticoresearch.com/didyoumean/) показывает, как работает `CALL SUGGEST` в небольшом веб-приложении.

![Пример CALL SUGGEST](didyoumean.png){.scale-0.5}


<!-- proofread -->
