Ignoring stop words

Stop words are the words that are skipped during indexing and searching. Typically you’d put most frequent words to the stop words list, because they do not add much value to search results but consume a lot of resources to process.

Stemming is by default applied when parsing stop words file. That might however lead to undesired results. You can turn that off with stopwords_unstemmed.

Small enough files are stored in the table header, see embedded_limit for details.

While stop words are not indexed, they still do affect the keyword positions. For instance, assume that “the” is a stop word, that document 1 contains the line “in office”, and that document 2 contains “in the office”. Searching for “in office” as for an exact phrase will only return the first document, as expected, even though “the” in the second one is skipped as a stop word. That behavior can be tweaked through the stopword_step directive.

stopwords

  1. stopwords=path/to/stopwords/file[ path/to/another/file ...]

Stop word files list (space separated). Optional, default is empty. You can specify several file names, separated by spaces. All the files will be loaded. In the RT mode only absolute paths are allowed.

Stop words file format is simple plain text. The encoding must be UTF-8. File data will be tokenized with respect to charset_table settings, so you can use the same separators as in the indexed data.

Stop word files can either be created manually, or semi-automatically. indexer provides a mode that creates a frequency dictionary of the table, sorted by the keyword frequency, see --buildstops and --buildfreqs switch for details. Top keywords from that dictionary can usually be used as stop words.

  • SQL
  • JSON
  • PHP
  • Python
  • javascript
  • Java
  • CONFIG

SQL JSON PHP Python javascript Java CONFIG

  1. CREATE TABLE products(title text, price float) stopwords = '/usr/local/manticore/data/stopwords.txt /usr/local/manticore/data/stopwords-ru.txt /usr/local/manticore/data/stopwords-en.txt'
  1. POST /cli -d "
  2. CREATE TABLE products(title text, price float) stopwords = '/usr/local/manticore/data/stopwords.txt stopwords-ru.txt stopwords-en.txt'"
  1. $index = new \Manticoresearch\Index($client);
  2. $index->setName('products');
  3. $index->create([
  4. 'title'=>['type'=>'text'],
  5. 'price'=>['type'=>'float']
  6. ],[
  7. 'stopwords' => '/usr/local/manticore/data/stopwords.txt stopwords-ru.txt stopwords-en.txt'
  8. ]);
  1. utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'/usr/local/manticore/data/stopwords.txt /usr/local/manticore/data/stopwords-ru.txt /usr/local/manticore/data/stopwords-en.txt\'')
  1. res = await utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'/usr/local/manticore/data/stopwords.txt /usr/local/manticore/data/stopwords-ru.txt /usr/local/manticore/data/stopwords-en.txt\'');
  1. utilsApi.sql("CREATE TABLE products(title text, price float) stopwords = '/usr/local/manticore/data/stopwords.txt /usr/local/manticore/data/stopwords-ru.txt /usr/local/manticore/data/stopwords-en.txt'");
  1. table products {
  2. stopwords = /usr/local/manticore/data/stopwords.txt
  3. stopwords = stopwords-ru.txt stopwords-en.txt
  4. type = rt
  5. path = tbl
  6. rt_field = title
  7. rt_attr_uint = price
  8. }

Alternatively you can use one of the default stop word files that come with Manticore. Currently stop words for 50 languages are available. Here is the full list of aliases for them:

  • af - Afrikaans
  • ar - Arabic
  • bg - Bulgarian
  • bn - Bengali
  • ca - Catalan
  • ckb- Kurdish
  • cz - Czech
  • da - Danish
  • de - German
  • el - Greek
  • en - English
  • eo - Esperanto
  • es - Spain
  • et - Estonian
  • eu - Basque
  • fa - Persian
  • fi - Finnish
  • fr - French
  • ga - Irish
  • gl - Galician
  • hi - Hindi
  • he - Hebrew
  • hr - Croatian
  • hu - Hungarian
  • hy - Armenian
  • id - Indonesian
  • it - Italian
  • ja - Japanese
  • ko - Korean
  • la - Latin
  • lt - Lithuanian
  • lv - Latvian
  • mr - Marathi
  • nl - Dutch
  • no - Norwegian
  • pl - Polish
  • pt - Portuguese
  • ro - Romanian
  • ru - Russian
  • sk - Slovak
  • sl - Slovenian
  • so - Somali
  • st - Sotho
  • sv - Swedish
  • sw - Swahili
  • th - Thai
  • tr - Turkish
  • yo - Yoruba
  • zh - Chinese
  • zu - Zulu

For example, to use stop words for Italian language just put the following line in your config file:

  • SQL
  • JSON
  • PHP
  • Python
  • javascript
  • Java
  • CONFIG

SQL JSON PHP Python javascript Java CONFIG

  1. CREATE TABLE products(title text, price float) stopwords = 'it'
  1. POST /cli -d "
  2. CREATE TABLE products(title text, price float) stopwords = 'it'"
  1. $index = new \Manticoresearch\Index($client);
  2. $index->setName('products');
  3. $index->create([
  4. 'title'=>['type'=>'text'],
  5. 'price'=>['type'=>'float']
  6. ],[
  7. 'stopwords' => 'it'
  8. ]);
  1. utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'it\'')
  1. res = await utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'it\'');
  1. utilsApi.sql("CREATE TABLE products(title text, price float) stopwords = 'it'");
  1. table products {
  2. stopwords = it
  3. type = rt
  4. path = tbl
  5. rt_field = title
  6. rt_attr_uint = price
  7. }

If you need to use stop words for multiple languages you should list all their aliases, separated with commas (RT mode) or spaces (plain mode):

  • SQL
  • JSON
  • PHP
  • Python
  • javascript
  • Java
  • CONFIG

SQL JSON PHP Python javascript Java CONFIG

  1. CREATE TABLE products(title text, price float) stopwords = 'en, it, ru'
  1. POST /cli -d "
  2. CREATE TABLE products(title text, price float) stopwords = 'en, it, ru'"
  1. $index = new \Manticoresearch\Index($client);
  2. $index->setName('products');
  3. $index->create([
  4. 'title'=>['type'=>'text'],
  5. 'price'=>['type'=>'float']
  6. ],[
  7. 'stopwords' => 'en, it, ru'
  8. ]);
  1. utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'en, it, ru\'')
  1. res = await utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'en, it, ru\'');
  1. utilsApi.sql("CREATE TABLE products(title text, price float) stopwords = 'en, it, ru'");
  1. table products {
  2. stopwords = en it ru
  3. type = rt
  4. path = tbl
  5. rt_field = title
  6. rt_attr_uint = price
  7. }

stopword_step

  1. stopword_step={0|1}

Position increment on stopwords. Optional, allowed values are 0 and 1, default is 1.

  • SQL
  • JSON
  • PHP
  • Python
  • javascript
  • Java
  • CONFIG

SQL JSON PHP Python javascript Java CONFIG

  1. CREATE TABLE products(title text, price float) stopwords = 'en' stopword_step = '1'
  1. POST /cli -d "
  2. CREATE TABLE products(title text, price float) stopwords = 'en' stopword_step = '1'"
  1. $index = new \Manticoresearch\Index($client);
  2. $index->setName('products');
  3. $index->create([
  4. 'title'=>['type'=>'text'],
  5. 'price'=>['type'=>'float']
  6. ],[
  7. 'stopwords' => 'en, it, ru',
  8. 'stopword_step' => '1'
  9. ]);
  1. utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'en\' stopword_step = \'1\'')
  1. res = await utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'en\' stopword_step = \'1\'');
  1. utilsApi.sql("CREATE TABLE products(title text, price float) stopwords = \'en\' stopword_step = \'1\'");
  1. table products {
  2. stopwords = en
  3. stopword_step = 1
  4. type = rt
  5. path = tbl
  6. rt_field = title
  7. rt_attr_uint = price
  8. }

stopwords_unstemmed

  1. stopwords_unstemmed={0|1}

Whether to apply stop words before or after stemming. Optional, default is 0 (apply stop word filter after stemming).

By default, stop words are stemmed themselves, and applied to tokens after stemming (or any other morphology processing). In other words, by default, a token is stopped when stem(token) is equal to stem(stopword). That can lead to unexpected results when a token gets (erroneously) stemmed to a stopped root. For example, ‘Andes’ might get stemmed to ‘and’, so when ‘and’ is a stopword, ‘Andes’ is also skipped.

stopwords_unstemmed directive changed this behaviour. When it’s enabled, stop words are applied before stemming (and therefore to the original word forms), and the tokens are skipped when token is equal to stopword.

  • SQL
  • JSON
  • PHP
  • Python
  • javascript
  • Java
  • CONFIG

SQL JSON PHP Python javascript Java CONFIG

  1. CREATE TABLE products(title text, price float) stopwords = 'en' stopwords_unstemmed = '1'
  1. POST /cli -d "
  2. CREATE TABLE products(title text, price float) stopwords = 'en' stopwords_unstemmed = '1'"
  1. $index = new \Manticoresearch\Index($client);
  2. $index->setName('products');
  3. $index->create([
  4. 'title'=>['type'=>'text'],
  5. 'price'=>['type'=>'float']
  6. ],[
  7. 'stopwords' => 'en, it, ru',
  8. 'stopwords_unstemmed' => '1'
  9. ]);
  1. utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'en\' stopwords_unstemmed = \'1\'')
  1. res = await utilsApi.sql('CREATE TABLE products(title text, price float) stopwords = \'en\' stopwords_unstemmed = \'1\'');
  1. utilsApi.sql("CREATE TABLE products(title text, price float) stopwords = \'en\' stopwords_unstemmed = \'1\'");
  1. table products {
  2. stopwords = en
  3. stopwords_unstemmed = 1
  4. type = rt
  5. path = tbl
  6. rt_field = title
  7. rt_attr_uint = price
  8. }

Word forms

Word forms are applied after tokenizing incoming text by charset_table rules. They essentially let you replace one word with another. Normally, that would be used to bring different word forms to a single normal form (e.g. to normalize all the variants such as “walks”, “walked”, “walking” to the normal form “walk”). It can also be used to implement stemming exceptions, because stemming is not applied to words found in the forms list.

wordforms

  1. wordforms = path/to/wordforms.txt
  2. wordforms = path/to/alternateforms.txt
  3. wordforms = path/to/dict*.txt

Word forms dictionary. Optional, default is empty.

The dictionaries are used to normalize incoming words both during indexing and searching. Therefore, when it comes to a plain table, it’s required to rotate the table in order to pick up changes in the word forms file.

Word forms support in Manticore is designed to handle large dictionaries well. They moderately affect indexing speed; for example, a dictionary with 1 million entries slows down indexing by about 1.5 times. Searching speed is not affected at all. The additional RAM impact is roughly equal to the dictionary file size, and dictionaries are shared across tables. For instance, if the very same 50 MB word forms file is specified for 10 different tables, the additional searchd RAM usage will be about 50 MB.

The dictionary file should be in a simple plain text format. Each line should contain source and destination word forms, in UTF-8 encoding, separated by a “greater than” sign. Rules from the charset_table will be applied when the file is loaded, so if you are using built-in charset_table options, it is typically case-insensitive, just like your other full-text indexed data. Here is a sample file contents:

  1. walks > walk
  2. walked > walk
  3. walking > walk

There is a bundled utility called Spelldump that helps you create a dictionary file in a format that Manticore can read. The utility can read from source .dict and .aff dictionary files in the ispell or MySpell format, as bundled with OpenOffice.

You can map several source words to a single destination word. The process happens on tokens, not the source text, so differences in whitespace and markup are ignored.

You can use the => symbol instead of >. Comments (starting with #) are also allowed. Finally, if a line starts with a tilde (~), the wordform will be applied after morphology, instead of before (note that only a single source word is supported in this case).

  1. core 2 duo > c2d
  2. e6600 > c2d
  3. core 2duo => c2d # Some people write '2duo' together...
  4. ~run > walk # Along with stem_en morphology enabled replaces 'run', 'running', 'runs' (and any other words that stem to just 'run') to 'walk'

You can specify multiple destination tokens:

  1. s02e02 > season 2 episode 2
  2. s3 e3 > season 3 episode 3

You can specify multiple files, not just one. Masks can be used as a pattern, and all matching files will be processed in simple ascending order.

In the RT mode, only absolute paths are allowed.

If multi-byte codepages are used and file names include foreign characters, the resulting order may not be exactly alphabetic. If the same wordform definition is found in multiple files, the latter one is used and overrides previous definitions.

  • SQL
  • JSON
  • PHP
  • Python
  • javascript
  • Java
  • CONFIG

SQL JSON PHP Python javascript Java CONFIG

  1. CREATE TABLE products(title text, price float) wordforms = '/var/lib/manticore/wordforms.txt' wordforms = '/var/lib/manticore/alternateforms.txt /var/lib/manticore/dict*.txt'
  1. POST /cli -d "
  2. CREATE TABLE products(title text, price float) wordforms = '/var/lib/manticore/wordforms.txt' wordforms = '/var/lib/manticore/alternateforms.txt' wordforms = '/var/lib/manticore/dict*.txt'"
  1. $index = new \Manticoresearch\Index($client);
  2. $index->setName('products');
  3. $index->create([
  4. 'title'=>['type'=>'text'],
  5. 'price'=>['type'=>'float']
  6. ],[
  7. 'wordforms' => [
  8. '/var/lib/manticore/wordforms.txt',
  9. '/var/lib/manticore/alternateforms.txt',
  10. '/var/lib/manticore/dict*.txt'
  11. ]
  12. ]);
  1. utilsApi.sql('CREATE TABLE products(title text, price float) wordforms = \'/var/lib/manticore/wordforms.txt\' wordforms = \'/var/lib/manticore/alternateforms.txt\' wordforms = \'/var/lib/manticore/dict*.txt\'')
  1. res = await utilsApi.sql('CREATE TABLE products(title text, price float)wordforms = \'/var/lib/manticore/wordforms.txt\' wordforms = \'/var/lib/manticore/alternateforms.txt\' wordforms = \'/var/lib/manticore/dict*.txt\'');
  1. utilsApi.sql("CREATE TABLE products(title text, price float) wordforms = '/var/lib/manticore/wordforms.txt' wordforms = '/var/lib/manticore/alternateforms.txt' wordforms = '/var/lib/manticore/dict*.txt'");
  1. table products {
  2. wordforms = /var/lib/manticore/wordforms.txt
  3. wordforms = /var/lib/manticore/alternateforms.txt
  4. wordforms = /var/lib/manticore/dict*.txt
  5. type = rt
  6. path = tbl
  7. rt_field = title
  8. rt_attr_uint = price
  9. }

Exceptions

Exceptions (also known as synonyms) allow to map one or more tokens (including tokens with characters that would normally be excluded) to a single keyword. They are similar to wordforms in that they also perform mapping, but have a number of important differences.

Short summary of the differences from wordforms is as follows:

ExceptionsWord forms
Case sensitiveCase insensitive
Can use special characters that are not in charset_tableFully obey charset_table
Underperform on huge dictionariesDesigned to handle millions of entries

exceptions

  1. exceptions = path/to/exceptions.txt

Tokenizing exceptions file. Optional, default is empty. In the RT mode only absolute paths are allowed.

The expected file format is also plain text, with one line per exception, and the line format is as follows:

  1. map-from-tokens => map-to-token

Example file:

  1. at & t => at&t
  2. AT&T => AT&T
  3. Standarten Fuehrer => standartenfuhrer
  4. Standarten Fuhrer => standartenfuhrer
  5. MS Windows => ms windows
  6. Microsoft Windows => ms windows
  7. C++ => cplusplus
  8. c++ => cplusplus
  9. C plus plus => cplusplus

All tokens here are case sensitive: they will not be processed by charset_table rules. Thus, with the example exceptions file above, “at&t” text will be tokenized as two keywords “at” and “t”, because of lowercase letters. On the other hand, “AT&T” will match exactly and produce single “AT&T” keyword.

Note that this map-to keyword is:

  • always interpreted as a single word
  • and is both case and space sensitive

In our sample, “ms windows” query will not match the document with “MS Windows” text. The query will be interpreted as a query for two keywords, “ms” and “windows”. And what “MS Windows” gets mapped to is a single keyword “ms windows”, with a space in the middle. On the other hand, “standartenfuhrer” will retrieve documents with “Standarten Fuhrer” or “Standarten Fuehrer” contents (capitalized exactly like this), or any capitalization variant of the keyword itself, eg. “staNdarTenfUhreR”. (It won’t catch “standarten fuhrer”, however: this text does not match any of the listed exceptions because of case sensitivity, and gets indexed as two separate keywords.)

Whitespace in the map-from tokens list matters, but its amount does not. Any amount of the whitespace in the map-form list will match any other amount of whitespace in the indexed document or query. For instance, “AT & T” map-from token will match “AT & T” text, whatever the amount of space in both map-from part and the indexed text. Such text will therefore be indexed as a special “AT&T” keyword, thanks to the very first entry from the sample.

Exceptions also allow to capture special characters (that are exceptions from general charset_table rules; hence the name). Assume that you generally do not want to treat ‘+’ as a valid character, but still want to be able search for some exceptions from this rule such as ‘C++’. The sample above will do just that, totally independent of what characters are in the table and what are not.

Exceptions are applied to raw incoming document and query data during indexing and searching respectively. Therefore, when it comes to a plain table to pick up changes in the file it’s required to reindex and restart searchd.

  • SQL
  • JSON
  • PHP
  • Python
  • javascript
  • Java
  • CONFIG

SQL JSON PHP Python javascript Java CONFIG

  1. CREATE TABLE products(title text, price float) exceptions = '/usr/local/manticore/data/exceptions.txt'
  1. POST /cli -d "
  2. CREATE TABLE products(title text, price float) exceptions = '/usr/local/manticore/data/exceptions.txt'"
  1. $index = new \Manticoresearch\Index($client);
  2. $index->setName('products');
  3. $index->create([
  4. 'title'=>['type'=>'text'],
  5. 'price'=>['type'=>'float']
  6. ],[
  7. 'exceptions' => '/usr/local/manticore/data/exceptions.txt'
  8. ]);
  1. utilsApi.sql('CREATE TABLE products(title text, price float) exceptions = \'/usr/local/manticore/data/exceptions.txt\'')
  1. res = await utilsApi.sql('CREATE TABLE products(title text, price float) exceptions = \'/usr/local/manticore/data/exceptions.txt\'');
  1. utilsApi.sql("CREATE TABLE products(title text, price float) exceptions = '/usr/local/manticore/data/exceptions.txt'");
  1. table products {
  2. exceptions = /usr/local/manticore/data/exceptions.txt
  3. type = rt
  4. path = tbl
  5. rt_field = title
  6. rt_attr_uint = price
  7. }