Discutons du prochain FlexSearch v0.8 ici : #415
Démarrage de base • Référence API • Index de documents • Utilisation de Worker • Journal des modifications
Vous pouvez m'aider en faisant un don personnel pour maintenir ce projet en vie et aussi en apportant toute votre contribution pour résoudre vos besoins.
Antithèse Opérations LLC
En ce qui concerne la vitesse de recherche brute, FlexSearch surpasse toutes les bibliothèques de recherche disponibles et offre également des fonctionnalités de recherche flexibles telles que la recherche multi-champs, les transformations phonétiques ou la correspondance partielle.
En fonction des options utilisées, il fournit également l'index le plus efficace en termes de mémoire. FlexSearch introduit un nouvel algorithme de notation appelé « index contextuel » basé sur une architecture de dictionnaire lexical pré-noté qui exécute des requêtes jusqu'à 1 000 000 de fois plus rapidement que d'autres bibliothèques. FlexSearch vous fournit également un modèle de traitement asynchrone non bloquant ainsi que des Web Workers pour effectuer des mises à jour ou des requêtes sur l'index en parallèle via des threads équilibrés dédiés.
Plateformes prises en charge :
Comparaison de bibliothèques "Les voyages de Gulliver":
Plugins (projets externes) :
Construire | Déposer | CDN |
flexsearch.bundle.js | Télécharger | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.bundle.js |
flexsearch.light.js | Télécharger | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.light.js |
flexsearch.compact.js | Télécharger | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.compact.js |
flexsearch.es5.js * | Télécharger | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.es5.js |
Module ES6 | Télécharger | Le dossier /dist/module/ de ce dépôt Github |
* Le bundle "flexsearch.es5.js" inclut des polyfills pour le support EcmaScript 5.
npm install flexsearch
Le package Node.js inclut toutes les fonctionnalités de
flexsearch.bundle.js
.
Fonctionnalité | flexsearch.bundle.js | flexsearch.compact.js | flexsearch.light.js |
Préréglages | ✓ | ✓ | - |
Recherche asynchrone | ✓ | ✓ | - |
Travailleurs (Web + Node.js) | ✓ | - | - |
Index contextuels | ✓ | ✓ | ✓ |
Indexer les documents (recherche sur le terrain) | ✓ | ✓ | - |
Magasin de documents | ✓ | ✓ | - |
Correspondance partielle | ✓ | ✓ | ✓ |
Notation de pertinence | ✓ | ✓ | ✓ |
Cache auto-équilibré par popularité | ✓ | - | - |
Balises | ✓ | - | - |
Suggestions | ✓ | ✓ | - |
Correspondance phonétique | ✓ | ✓ | - |
Jeu de caractères/langue personnalisable (Matcher, Encoder, Tokenizer, Stemmer, Filter, Split, RTL) | ✓ | ✓ | ✓ |
Index d'exportation/importation | ✓ | - | - |
Taille du fichier (gzip) | 6,8 Ko | 5,3 Ko | 2,9 Ko |
Comparaison d'exécution : benchmark de performances "Les voyages de Gulliver"
Opération par secondes, plus haut c'est mieux, sauf le test "Mémoire" sur lequel plus bas c'est mieux.
Rang | Bibliothèque | Mémoire | Requête (terme unique) | Requête (multiterme) | Requête (longue) | Requête (dupes) | Requête (introuvable) |
1 | FlexRecherche | 17 | 7084129 | 1586856 | 511585 | 2017142 | 3202006 |
2 | JSii | 27 | 6564 | 158149 | 61290 | 95098 | 534109 |
3 | Patauger | 424 | 20471 | 78780 | 16693 | 225824 | 213754 |
4 | Recherche JS | 193 | 8221 | 64034 | 10377 | 95830 | 167605 |
5 | Elasticlunr.js | 646 | 5412 | 7573 | 2865 | 23786 | 13982 |
6 | Recherche groupée | 1021 | 3069 | 3141 | 3333 | 3265 | 21825569 |
7 | MiniRecherche | 24348 | 4406 | 10945 | 72 | 39989 | 17624 |
8 | bm25 | 15719 | 1429 | 789 | 366 | 884 | 1823 |
9 | Lunr.js | 2219 | 255 | 271 | 272 | 266 | 267 |
10 | Recherche floue | 157373 | 53 | 38 | 15 | 32 | 43 |
11 | Fusible | 7641904 | 6 | 2 | 1 | 2 | 3 |
Il existe 3 types d'index :
Index
est un index plat hautes performances qui stocke les paires identifiant-contenu.Worker
/ WorkerIndex
est également un index plat qui stocke les paires identifiant-contenu mais s'exécute en arrière-plan en tant que thread de travail dédié.Document
est un index multi-champs qui peut stocker des documents JSON complexes (il peut également exister des index de travail).La plupart d’entre vous n’en auront probablement besoin que d’un seul, selon votre scénario.
< script src =" node_modules/flexsearch/dist/flexsearch.bundle.min.js " > </ script >
< script >
// FlexSearch is available on window.FlexSearch
// Access FlexSearch static methods via bundled export (static class methods of FlexSearch)
const index = FlexSearch . Index ( options ) ;
const document = FlexSearch . Document ( options ) ;
const worker = FlexSearch . Worker ( options ) ;
</ script >
< script type =" module " >
// FlexSearch is NOT available on window.FlexSearch
// Access FlexSearch static methods by importing them explicitly
import Index from "./node_modules/flexsearch/dist/module/index" ;
import Document from "./node_modules/flexsearch/dist/module/document" ;
import Worker from "./node_modules/flexsearch/dist/module/worker" ;
const index = new Index ( options ) ;
const document = new Document ( options ) ;
const worker = new Worker ( options ) ;
</ script >
< script type =" module " >
// FlexSearch is NOT available on window.FlexSearch
// Access FlexSearch static methods via bundled export (static class methods of FlexSearch)
import FlexSearch from "./node_modules/flexsearch/dist/flexsearch.bundle.module.min.js" ;
const index = FlexSearch . Index ( options ) ;
const document = FlexSearch . Document ( options ) ;
const worker = FlexSearch . Worker ( options ) ;
</ script >
Ou via CDN :
< script src =" https://cdn.jsdelivr.net/gh/nextapps-de/[email protected]/dist/flexsearch.bundle.min.js " > </ script >
AMD/CommonJS :
var FlexSearch = require ( "./node_modules/flexsearch/dist/flexsearch.bundle.min.js" ) ;
npm install flexsearch
Dans votre code, incluez ce qui suit :
const { Index , Document , Worker } = require ( "flexsearch" ) ;
const index = new Index ( options ) ;
const document = new Document ( options ) ;
const worker = new Worker ( options ) ;
Ou:
const FlexSearch = require ( "flexsearch" ) ;
const index = new FlexSearch . Index ( options ) ;
const document = new FlexSearch . Document ( options ) ;
const worker = new FlexSearch . Worker ( options ) ;
index . add ( id , text ) ;
index . search ( text ) ;
index . search ( text , limit ) ;
index . search ( text , options ) ;
index . search ( text , limit , options ) ;
index . search ( options ) ;
document . add ( doc ) ;
document . add ( id , doc ) ;
document . search ( text ) ;
document . search ( text , limit ) ;
document . search ( text , options ) ;
document . search ( text , limit , options ) ;
document . search ( options ) ;
worker . add ( id , text ) ;
worker . search ( text ) ;
worker . search ( text , limit ) ;
worker . search ( text , options ) ;
worker . search ( text , limit , options ) ;
worker . search ( text , limit , options , callback ) ;
worker . search ( options ) ;
Le worker
hérite du type Index
et n'hérite pas du type Document
. Par conséquent, un WorkerIndex fonctionne essentiellement comme un index FlexSearch standard. Worker-Support dans les documents doit être activé en passant simplement l'option appropriée lors de la création { worker: true }
.
Chaque méthode appelée sur un index
Worker
est traitée comme asynchrone. Vous recevrez unePromise
ou vous pourrez également fournir une fonction de rappel comme dernier paramètre.
Méthodes globales :
Méthodes d'indexation :
Méthodes WorkerIndex :
Méthodes de documentation :
* Pour chacune de ces méthodes il existe un équivalent asynchrone :
Version asynchrone :
Les méthodes asynchrones renverront une Promise
, vous pouvez également transmettre une fonction de rappel comme dernier paramètre.
Les méthodes export
et d' import
sont toujours asynchrones, ainsi que chaque méthode que vous appelez sur un index basé sur un travailleur.
FlexSearch est hautement personnalisable. Utiliser les bonnes options peut vraiment améliorer vos résultats ainsi que l'économie de mémoire et le temps de requête.
Option | Valeurs | Description | Défaut |
préréglage | "mémoire" "performance" "correspondre" "score" "défaut" | Le profil de configuration comme raccourci ou comme base pour vos paramètres personnalisés. | "défaut" |
tokeniser | "strict" "avant" "inverse" "complet" | Le mode d'indexation (tokenizer). Choisissez l’un des éléments intégrés ou transmettez une fonction de tokenizer personnalisée. | "strict" |
cache | Booléen Nombre | Activer/Désactiver et/ou définir la capacité des entrées mises en cache. Lors de la transmission d'un nombre comme limite, le cache équilibre automatiquement les entrées stockées en fonction de leur popularité . Remarque : Lorsque vous utilisez simplement "true", le cache n'a aucune limite et sa croissance est illimitée. | FAUX |
résolution | Nombre | Définit la résolution de notation (par défaut : 9). | 9 |
contexte | Booléen Options contextuelles | Activer/Désactiver l'indexation contextuelle. Lors du passage de "true" comme valeur, les valeurs par défaut du contexte seront prises. | FAUX |
optimiser | Booléen | Lorsqu'il est activé, il utilise un flux de pile optimisé en mémoire pour l'index. | vrai |
booster | fonction (arr, str, int) => float | Une fonction boost personnalisée utilisée lors de l'indexation du contenu dans l'index. La fonction a cette signature : Function(words[], term, index) => Float . Il comporte 3 paramètres dans lesquels vous obtenez un tableau de tous les mots, le terme actuel et l'index actuel où le terme est placé dans le tableau de mots. Vous pouvez appliquer votre propre calcul, par exemple les occurrences d'un terme et renvoyer ce facteur (<1 signifie que la pertinence est réduite, >1 signifie que la pertinence est augmentée).Remarque : cette fonctionnalité est actuellement limitée à l'utilisation du tokenizer "strict" uniquement. | nul |
Options et codage spécifiques à la langue : | |||
jeu de caractères | Charge utile du jeu de caractères Chaîne (clé) | Fournissez une charge utile de jeu de caractères personnalisée ou transmettez l'une des clés des jeux de caractères intégrés. | "latin" |
langue | Charge utile linguistique Chaîne (clé) | Fournissez une charge utile de langue personnalisée ou transmettez un indicateur de raccourci de langue (ISO-3166) des langues intégrées. | nul |
encoder | FAUX "défaut" "simple" "équilibre" "avancé" "supplémentaire" fonction(str) => [mots] | Le type d'encodage. Choisissez l’un des éléments intégrés ou transmettez une fonction d’encodage personnalisée. | "défaut" |
égrappoir | FAUX Chaîne Fonction | FAUX | |
filtre | FAUX Chaîne Fonction | FAUX | |
matcheur | FAUX Chaîne Fonction | FAUX | |
Options supplémentaires pour les index de documents : | |||
travailleur | Booléen | Activer/Désactiver et définir le nombre de threads de travail en cours d'exécution. | FAUX |
document | Descripteur de document | Comprend des définitions pour l'index et le stockage des documents. |
Option | Valeurs | Description | Défaut |
résolution | Nombre | Définit la résolution de notation pour le contexte (par défaut : 1). | 1 |
profondeur | FAUX Nombre | Activer/Désactiver l’indexation contextuelle et définit également la distance contextuelle de pertinence. La profondeur est le nombre maximum de mots/jetons d'un terme pour être considéré comme pertinent. | 1 |
bidirectionnel | Booléen | Définit le résultat de la recherche bidirectionnelle. Si activé et que le texte source contient "red hat", il sera trouvé pour les requêtes "red hat" et "hat red". | vrai |
Option | Valeurs | Description | Défaut |
identifiant | Chaîne | "identifiant"" | |
étiqueter | FAUX Chaîne | "étiqueter" | |
indice | Chaîne Tableau<Chaîne> Tableau<Objet> | ||
magasin | Booléen Chaîne Tableau<Chaîne> | FAUX |
Option | Valeurs | Description | Défaut |
diviser | FAUX Expression régulière Chaîne | La règle pour diviser les mots lors de l'utilisation d'un tokenizer non personnalisé (intégré par exemple "forward"). Utilisez une chaîne/un caractère ou utilisez une expression régulière (par défaut : /W+/ ). | /[W_]+/ |
rtl | Booléen | Active le codage de droite à gauche. | FAUX |
encoder | fonction(str) => [mots] | La fonction d'encodage personnalisé. | /lang/latin/default.js |
Option | Valeurs | Description |
égrappoir | FAUX Chaîne Fonction | Désactivez ou transmettez l'indicateur de raccourci de langue (ISO-3166) ou un objet personnalisé. |
filtre | FAUX Chaîne Fonction | Désactivez ou transmettez l'indicateur de raccourci de langue (ISO-3166) ou un tableau personnalisé. |
matcheur | FAUX Chaîne Fonction | Désactivez ou transmettez l'indicateur de raccourci de langue (ISO-3166) ou un tableau personnalisé. |
Option | Valeurs | Description | Défaut |
limite | nombre | Définit la limite des résultats. | 100 |
compenser | nombre | Appliquer un décalage (ignorer des éléments). | 0 |
suggérer | Booléen | Active les suggestions dans les résultats. | FAUX |
Option | Valeurs | Description | Défaut |
indice | Chaîne Tableau<Chaîne> Tableau<Objet> | Définit les champs du document qui doivent être recherchés. Lorsqu'aucun champ n'est défini, tous les champs seront recherchés. Les options personnalisées par champ sont également prises en charge. | |
étiqueter | Chaîne Tableau<Chaîne> | Définit les champs du document qui doivent être recherchés. Lorsqu'aucun champ n'est défini, tous les champs seront recherchés. Les options personnalisées par champ sont également prises en charge. | FAUX |
enrichir | Booléen | Enrichissez les identifiants des résultats avec les documents correspondants. | FAUX |
bouffon | "et" "ou" | Définit l'opérateur logique utilisé lors de la recherche dans plusieurs champs ou balises. | "ou" |
Tokenizer affecte également la mémoire requise, le temps de requête et la flexibilité des correspondances partielles. Essayez de choisir le plus haut de ces tokenizer qui correspond à vos besoins :
Option | Description | Exemple | Facteur de mémoire (n = longueur du mot) |
"strict" | indexer des mots entiers | foobar | * 1 |
"avant" | indexer progressivement les mots vers l'avant | fo obarfoob ar | *n |
"inverse" | indexer progressivement les mots dans les deux sens | foob ar pour obar | * 2n - 1 |
"complet" | indexer toutes les combinaisons possibles | fo oba roob ar | *n* (n-1) |
L'encodage affecte également la mémoire requise en tant que temps de requête et correspondances phonétiques. Essayez de choisir le plus haut de ces encodeurs qui correspond à vos besoins, ou passez un encodeur personnalisé :
Option | Description | Faux positifs | Compression |
FAUX | Désactiver l'encodage | Non | 0% |
"défaut" | Encodage insensible à la casse | Non | 0% |
"simple" | Encodage insensible à la casse Normalisations du jeu de caractères | Non | ~ 3% |
"équilibre" | Encodage insensible à la casse Normalisations du jeu de caractères Transformations littérales | Non | ~ 30% |
"avancé" | Encodage insensible à la casse Normalisations du jeu de caractères Transformations littérales Normalisations phonétiques | Non | ~ 40% |
"supplémentaire" | Encodage insensible à la casse Normalisations du jeu de caractères Transformations littérales Normalisations phonétiques Transformations Soundex | Oui | ~ 65% |
fonction() | Passer l'encodage personnalisé via function(string):[words] |
var index = new Index ( ) ;
Créez un nouvel index et choisissez l'un des préréglages :
var index = new Index ( "performance" ) ;
Créez un nouvel index avec des options personnalisées :
var index = new Index ( {
charset : "latin:extra" ,
tokenize : "reverse" ,
resolution : 9
} ) ;
Créez un nouvel index et étendez un préréglage avec des options personnalisées :
var index = new FlexSearch ( {
preset : "memory" ,
tokenize : "forward" ,
resolution : 5
} ) ;
Voir toutes les options personnalisées disponibles.
Chaque contenu qui doit être ajouté à l'index a besoin d'un identifiant. Lorsque votre contenu n'a pas d'identifiant, vous devez en créer un en transmettant un index, un nombre ou autre chose comme identifiant (une valeur du type number
est fortement recommandée). Ces identifiants sont des références uniques à un contenu donné. Ceci est important lorsque vous mettez à jour ou ajoutez du contenu via des identifiants existants. Lorsque le référencement n'est pas un problème, vous pouvez simplement utiliser quelque chose de simple comme count++
.
Indice. ajouter (identifiant, chaîne)
index . add ( 0 , "John Doe" ) ;
Indice. recherche(chaîne | options, <limite>, <options>)
index . search ( "John" ) ;
Limitez le résultat :
index . search ( "John" , 10 ) ;
Vous pouvez vérifier si un identifiant a déjà été indexé par :
if ( index . contain ( 1 ) ) {
console . log ( "ID is already in index" ) ;
}
Vous pouvez appeler chaque méthode dans sa version asynchrone, par exemple index.addAsync
ou index.searchAsync
.
Vous pouvez attribuer des rappels à chaque fonction asynchrone :
index . addAsync ( id , content , function ( ) {
console . log ( "Task Done" ) ;
} ) ;
index . searchAsync ( query , function ( result ) {
console . log ( "Results: " , result ) ;
} ) ;
Ou ne transmettez pas de fonction de rappel et récupérez plutôt une Promise
:
index . addAsync ( id , content ) . then ( function ( ) {
console . log ( "Task Done" ) ;
} ) ;
index . searchAsync ( query ) . then ( function ( result ) {
console . log ( "Results: " , result ) ;
} ) ;
Ou utilisez async
et await
:
async function add ( ) {
await index . addAsync ( id , content ) ;
console . log ( "Task Done" ) ;
}
async function search ( ) {
const results = await index . searchAsync ( query ) ;
console . log ( "Results: " , result ) ;
}
Vous pouvez ajouter du contenu à un index existant comme :
index . append ( id , content ) ;
Cela n'écrasera pas l'ancien contenu indexé comme ce serait le cas lors de l'exécution index.update(id, content)
. Gardez à l'esprit que index.add(id, content)
effectuera également une "mise à jour" sous le capot lorsque l'identifiant était déjà en cours d'indexation.
Les contenus annexés auront leur propre contexte et également leur propre resolution
complète. Par conséquent, la pertinence n’est pas empilée mais obtient son propre contexte.
Prenons cet exemple :
index . add ( 0 , "some index" ) ;
index . append ( 0 , "some appended content" ) ;
index . add ( 1 , "some text" ) ;
index . append ( 1 , "index appended content" ) ;
Lorsque vous interrogez index.search("index")
vous obtiendrez l'ID d'index 1 comme première entrée du résultat, car le contexte commence à zéro pour les données ajoutées (n'est pas empilé sur l'ancien contexte) et ici "index " est le premier terme.
Si vous ne souhaitez pas ce comportement, utilisez simplement le standard index.add(id, content)
et fournissez la longueur complète du contenu.
Indice. mise à jour (identifiant, chaîne)
index . update ( 0 , "Max Miller" ) ;
Indice. supprimer (identifiant)
index . remove ( 0 ) ;
Un tokenizer divise les mots/termes en composants ou partiels.
Définissez un tokenizer personnalisé privé lors de la création/initialisation :
var index = new FlexSearch ( {
tokenize : function ( str ) {
return str . split ( / s-/ / g ) ;
}
} ) ;
La fonction tokenizer obtient une chaîne en paramètre et doit renvoyer un tableau de chaînes représentant un mot ou un terme. Dans certaines langues, chaque caractère est un terme et n'est pas non plus séparé par des espaces.
Stemmer : plusieurs mutations linguistiques du même mot (par exemple "run" et "running")
Filtre : une liste noire de mots à filtrer de l'indexation (par exemple "et", "à" ou "être")
Attribuez un stemmer ou un filtre personnalisé privé lors de la création/initialisation :
var index = new FlexSearch ( {
stemmer : {
// object {key: replacement}
"ational" : "ate" ,
"tional" : "tion" ,
"enci" : "ence" ,
"ing" : ""
} ,
filter : [
// array blacklist
"in" ,
"into" ,
"is" ,
"isn't" ,
"it" ,
"it's"
]
} ) ;
En utilisant un filtre personnalisé, par exemple :
var index = new FlexSearch ( {
filter : function ( value ) {
// just add values with length > 1 to the index
return value . length > 1 ;
}
} ) ;
Ou attribuez un stemmer/filtres globalement à une langue :
Les Stemmer sont passés en tant qu'objet (paire clé-valeur), les filtres en tant que tableau.
FlexSearch . registerLanguage ( "us" , {
stemmer : { /* ... */ } ,
filter : [ /* ... */ ]
} ) ;
Ou utilisez un stemmer ou un filtre prédéfini de vos langues préférées :
< html >
< head >
< script src =" js/flexsearch.bundle.js " > </ script >
< script src =" js/lang/en.min.js " > </ script >
< script src =" js/lang/de.min.js " > </ script >
</ head >
...
Vous pouvez désormais attribuer un stemmer intégré lors de la création/initialisation :
var index_en = new FlexSearch . Index ( {
language : "en"
} ) ;
var index_de = new FlexSearch . Index ( {
language : "de"
} ) ;
Dans Node.js, tous les fichiers des modules linguistiques intégrés sont disponibles :
const { Index } = require ( "flexsearch" ) ;
var index_en = new Index ( {
language : "en"
} ) ;
Réglez le tokenizer au moins sur "reverse" ou "full" lors de l'utilisation de RTL.
Définissez simplement le champ "rtl" sur true et utilisez un tokenizer compatible :
var index = new Index ( {
encode : str => str . toLowerCase ( ) . split ( / [^a-z]+ / ) ,
tokenize : "reverse" ,
rtl : true
} ) ;
Définissez un tokenizer personnalisé qui correspond à vos besoins, par exemple :
var index = FlexSearch . create ( {
encode : str => str . replace ( / [x00-x7F] / g , "" ) . split ( "" )
} ) ;
Vous pouvez également transmettre une fonction d'encodeur personnalisée pour appliquer certaines transformations linguistiques.
index . add ( 0 , "一个单词" ) ;
var results = index . search ( "单词" ) ;
En supposant que notre document a une structure de données comme celle-ci :
{
"id" : 0 ,
"content" : " some text "
}
Ancienne syntaxe FlexSearch v0.6.3 ( n'est plus supportée ! ):
const index = new Document ( {
doc : {
id : "id" ,
field : [ "content" ]
}
} ) ;
Le descripteur du document a légèrement changé, il n'y a plus de branche
field
, il suffit d'appliquer un niveau supérieur, donckey
devient un membre principal des options.
Pour la nouvelle syntaxe le champ "doc" a été renommé en document
et le champ "field" a été renommé en index
:
const index = new Document ( {
document : {
id : "id" ,
index : [ "content" ]
}
} ) ;
index . add ( {
id : 0 ,
content : "some text"
} ) ;
L' id
du champ décrit l'emplacement de l'identifiant ou de la clé unique dans vos documents. La clé par défaut obtient la valeur id
par défaut lorsqu'elle n'est pas transmise, vous pouvez donc raccourcir l'exemple ci-dessus à :
const index = new Document ( {
document : {
index : [ "content" ]
}
} ) ;
L' index
des membres contient une liste de champs que vous souhaitez indexer à partir de vos documents. Lorsque vous sélectionnez simplement un champ, vous pouvez transmettre une chaîne. Lorsque vous utilisez également id
de clé par défaut, cela se réduit à :
const index = new Document ( { document : "content" } ) ;
index . add ( { id : 0 , content : "some text" } ) ;
En supposant que vous disposez de plusieurs champs, vous pouvez ajouter plusieurs champs à l'index :
var docs = [ {
id : 0 ,
title : "Title A" ,
content : "Body A"
} , {
id : 1 ,
title : "Title B" ,
content : "Body B"
} ] ;
const index = new Document ( {
id : "id" ,
index : [ "title" , "content" ]
} ) ;
Vous pouvez transmettre des options personnalisées pour chaque champ :
const index = new Document ( {
id : "id" ,
index : [ {
field : "title" ,
tokenize : "forward" ,
optimize : true ,
resolution : 9
} , {
field : "content" ,
tokenize : "strict" ,
optimize : true ,
resolution : 5 ,
minlength : 3 ,
context : {
depth : 1 ,
resolution : 3
}
} ]
} ) ;
Les options de champ sont héritées lorsque des options globales ont également été transmises, par exemple :
const index = new Document ( {
tokenize : "strict" ,
optimize : true ,
resolution : 9 ,
document : {
id : "id" ,
index : [ {
field : "title" ,
tokenize : "forward"
} , {
field : "content" ,
minlength : 3 ,
context : {
depth : 1 ,
resolution : 3
}
} ]
}
} ) ;
Remarque : Les options de contexte du champ "contenu" sont également héritées par les options de champ correspondantes, alors que ces options de champ ont été héritées par l'option globale.
Supposons que le tableau de documents semble plus complexe (comporte des branches imbriquées, etc.), par exemple :
{
"record" : {
"id" : 0 ,
"title" : " some title " ,
"content" : {
"header" : " some text " ,
"footer" : " some text "
}
}
}
Utilisez ensuite la notation séparée par deux points root:child:child
pour définir la hiérarchie dans le descripteur de document :
const index = new Document ( {
document : {
id : "record:id" ,
index : [
"record:title" ,
"record:content:header" ,
"record:content:footer"
]
}
} ) ;
Ajoutez simplement les champs sur lesquels vous souhaitez interroger. N'ajoutez pas de champs à l'index, vous avez juste besoin du résultat (mais vous n'avez pas effectué de requête). A cet effet vous pouvez stocker des documents indépendamment de leur index (lire ci-dessous).
Lorsque vous souhaitez interroger un champ, vous devez transmettre la clé exacte du champ que vous avez défini dans la doc
comme nom de champ (avec la syntaxe deux-points) :
index . search ( query , {
index : [
"record:title" ,
"record:content:header" ,
"record:content:footer"
]
} ) ;
Identique à :
index . search ( query , [
"record:title" ,
"record:content:header" ,
"record:content:footer"
] ) ;
Utilisation d'options spécifiques au champ :
index . search ( [ {
field : "record:title" ,
query : "some query" ,
limit : 100 ,
suggest : true
} , {
field : "record:title" ,
query : "some other query" ,
limit : 100 ,
suggest : true
} ] ) ;
Vous pouvez effectuer une recherche dans le même champ avec différentes requêtes.
Lorsque vous transmettez des options spécifiques à un champ, vous devez fournir la configuration complète pour chaque champ. Ils ne sont pas hérités comme le descripteur de document.
Vous devez suivre 2 règles pour vos documents :
[ // <-- not allowed as document start!
{
"id" : 0 ,
"title" : "title"
}
]
{
"records" : [ // <-- not allowed when ID or tag lives inside!
{
"id" : 0 ,
"title" : "title"
}
]
}
Voici un exemple de document complexe pris en charge :
{
"meta" : {
"tag" : " cat " ,
"id" : 0
},
"contents" : [
{
"body" : {
"title" : " some title " ,
"footer" : " some text "
},
"keywords" : [ " some " , " key " , " words " ]
},
{
"body" : {
"title" : " some title " ,
"footer" : " some text "
},
"keywords" : [ " some " , " key " , " words " ]
}
]
}
Le descripteur de document correspondant (lorsque tous les champs doivent être indexés) ressemble à :
const index = new Document ( {
document : {
id : "meta:id" ,
tag : "meta:tag" ,
index : [
"contents[]:body:title" ,
"contents[]:body:footer" ,
"contents[]:keywords"
]
}
} ) ;
Encore une fois, lors de la recherche, vous devez utiliser la même chaîne séparée par deux points de votre définition de champ.
index . search ( query , {
index : "contents[]:body:title"
} ) ;
Cet exemple enfreint les deux règles ci-dessus :
[ // <-- not allowed as document start!
{
"tag" : "cat" ,
"records" : [ // <-- not allowed when ID or tag lives inside!
{
"id" : 0 ,
"body" : {
"title" : "some title" ,
"footer" : "some text"
} ,
"keywords" : [ "some" , "key" , "words" ]
} ,
{
"id" : 1 ,
"body" : {
"title" : "some title" ,
"footer" : "some text"
} ,
"keywords" : [ "some" , "key" , "words" ]
}
]
}
]
Vous devez appliquer une sorte de normalisation de la structure.
Une solution de contournement pour une telle structure de données ressemble à ceci :
const index = new Document ( {
document : {
id : "record:id" ,
tag : "tag" ,
index : [
"record:body:title" ,
"record:body:footer" ,
"record:body:keywords"
]
}
} ) ;
function add ( sequential_data ) {
for ( let x = 0 , data ; x < sequential_data . length ; x ++ ) {
data = sequential_data [ x ] ;
for ( let y = 0 , record ; y < data . records . length ; y ++ ) {
record = data . records [ y ] ;
index . add ( {
id : record . id ,
tag : data . tag ,
record : record
} ) ;
}
}
}
// now just use add() helper method as usual:
add ( [ {
// sequential structured data
// take the data example above
} ] ) ;
Vous pouvez ignorer la première boucle lorsque les données de votre document n'ont qu'un seul index comme tableau externe.
Ajoutez un document à l'index :
index . add ( {
id : 0 ,
title : "Foo" ,
content : "Bar"
} ) ;
Mettez à jour l'index avec un seul objet ou un tableau d'objets :
index . update ( {
data : {
id : 0 ,
title : "Foo" ,
body : {
content : "Bar"
}
}
} ) ;
Supprimez un seul objet ou un tableau d'objets de l'index :
index . remove ( docs ) ;
Lorsque l'identifiant est connu, vous pouvez aussi simplement le supprimer (plus rapidement) :
index . remove ( id ) ;
Dans l'exemple complexe ci-dessus, le champ keywords
est un tableau mais ici le balisage n'avait pas de crochets comme keywords[]
. Cela détectera également le tableau, mais au lieu d'ajouter chaque entrée à un nouveau contexte, le tableau sera joint à une grande chaîne et ajouté à l'index.
La différence entre les deux types d’ajout de contenu de tableau réside dans la pertinence lors de la recherche. Lors de l'ajout de chaque élément d'un tableau via append()
à son propre contexte en utilisant la syntaxe field[]
, alors la pertinence de la dernière entrée est concurrente à la première entrée. Lorsque vous avez laissé les crochets dans la notation, le tableau sera joint à une chaîne séparée par des espaces. Ici, la première entrée a la plus grande pertinence, tandis que la dernière entrée a la plus faible pertinence.
Donc, en supposant que les mots-clés de l’exemple ci-dessus soient pré-triés par pertinence par rapport à leur popularité, vous souhaitez alors conserver cet ordre (informations pertinentes). Pour cela, n'ajoutez pas de parenthèses à la notation. Sinon, les entrées seraient prises dans un nouveau contexte de notation (l'ancien ordre serait perdu).
Vous pouvez également utiliser la notation entre parenthèses gauches pour de meilleures performances et une empreinte mémoire réduite. Utilisez-le lorsque vous n'avez pas besoin de la granularité de la pertinence des entrées.
Rechercher dans tous les champs :
index . search ( query ) ;
Rechercher dans un champ spécifique :
index . search ( query , { index : "title" } ) ;
Effectuez une recherche dans un ensemble donné de champs :
index . search ( query , { index : [ "title" , "content" ] } ) ;
Identique à :
index . search ( query , [ "title" , "content" ] ) ;
Transmettez des modificateurs et des requêtes personnalisés à chaque champ :
index . search ( [ {
field : "content" ,
query : "some query" ,
limit : 100 ,
suggest : true
} , {
field : "content" ,
query : "some other query" ,
limit : 100 ,
suggest : true
} ] ) ;
Vous pouvez effectuer une recherche dans le même champ avec différentes requêtes.
Voir toutes les options de recherche sur le terrain disponibles.
Schéma de l'ensemble de résultats :
fields[] => { field, result[] => { document }}
Le premier index est un tableau de champs auxquels la requête a été appliquée. Chacun de ces champs possède un enregistrement (objet) avec 2 propriétés "champ" et "résultat". Le "résultat" est également un tableau et inclut le résultat de ce champ spécifique. Le résultat pourrait être un tableau d’identifiants ou enrichi avec des données de documents stockées.
Un jeu de résultats non enrichi ressemble désormais à :
[ {
field : "title" ,
result : [ 0 , 1 , 2 ]
} , {
field : "content" ,
result : [ 3 , 4 , 5 ]
} ]
Un jeu de résultats enrichi ressemble désormais à :
[ {
field : "title" ,
result : [
{ id : 0 , doc : { /* document */ } } ,
{ id : 1 , doc : { /* document */ } } ,
{ id : 2 , doc : { /* document */ } }
]
} , {
field : "content" ,
result : [
{ id : 3 , doc : { /* document */ } } ,
{ id : 4 , doc : { /* document */ } } ,
{ id : 5 , doc : { /* document */ } }
]
} ]
Lorsque vous utilisez pluck
au lieu de "field", vous pouvez explicitement sélectionner un seul champ et récupérer une représentation plate :
index . search ( query , { pluck : "title" , enrich : true } ) ;
[
{ id : 0 , doc : { /* document */ } } ,
{ id : 1 , doc : { /* document */ } } ,
{ id : 2 , doc : { /* document */ } }
]
Cet ensemble de résultats remplace la « recherche booléenne ». Au lieu d'appliquer votre logique booléenne à un objet imbriqué, vous pouvez appliquer votre logique vous-même de manière dynamique au-dessus de l'ensemble de résultats. Cela ouvre énormément de possibilités sur la façon dont vous traitez les résultats. Par conséquent, les résultats des champs ne sont plus regroupés en un seul résultat. Cela conserve certaines informations importantes, comme le nom du domaine ainsi que la pertinence des résultats de chaque champ qui ne se mélangeaient plus.
Une recherche par champ appliquera par défaut une requête avec la logique booléenne « ou ». Chaque champ a son propre résultat pour la requête donnée.
Il existe une situation dans laquelle la propriété bool
est toujours prise en charge. Lorsque vous souhaitez changer la logique "ou" par défaut de la recherche par champ en "et", par exemple :
index . search ( query , {
index : [ "title" , "content" ] ,
bool : "and"
} ) ;
Vous obtiendrez simplement des résultats contenant la requête dans les deux champs. C'est ça.
Comme pour la key
de l'ID, définissez simplement le chemin d'accès à la balise :
const index = new Document ( {
document : {
id : "id" ,
tag : "tag" ,
index : "content"
}
} ) ;
index . add ( {
id : 0 ,
tag : "cat" ,
content : "Some content ..."
} ) ;
Vos données peuvent également avoir plusieurs balises sous forme de tableau :
index . add ( {
id : 1 ,
tag : [ "animal" , "dog" ] ,
content : "Some content ..."
} ) ;
Vous pouvez effectuer une recherche spécifique à une balise en :
index . search ( query , {
index : "content" ,
tag : "animal"
} ) ;
Cela vous donne simplement le résultat qui a été étiqueté avec la balise donnée.
Utilisez plusieurs balises lors de la recherche :
index . search ( query , {
index : "content" ,
tag : [ "cat" , "dog" ]
} ) ;
Cela vous donne des résultats qui sont étiquetés avec l'une des balises données.
Plusieurs balises s'appliqueront comme booléen "ou" par défaut. Il suffit qu’une des balises existe.
Il s'agit d'une autre situation dans laquelle la propriété bool
est toujours prise en charge. Lorsque vous souhaitez basculer la logique « ou » par défaut de la recherche de balises vers « et », par exemple :
index . search ( query , {
index : "content" ,
tag : [ "dog" , "animal" ] ,
bool : "and"
} ) ;
Vous obtiendrez simplement des résultats contenant les deux balises (dans cet exemple, il n'y a qu'un seul enregistrement comportant les balises "chien" et "animal").
Vous pouvez également récupérer les résultats d'une ou plusieurs balises lorsqu'aucune requête n'a été transmise :
index . search ( { tag : [ "cat" , "dog" ] } ) ;
Dans ce cas, l'ensemble de résultats ressemble à :
[ {
tag : "cat" ,
result : [ /* all cats */ ]
} , {
tag : "dog" ,
result : [ /* all dogs */ ]
} ]
Par défaut, chaque requête est limitée à 100 entrées. Les requêtes illimitées entraînent des problèmes. Vous devez définir la limite comme option pour ajuster la taille.
Vous pouvez définir la limite et le décalage pour chaque requête :
index . search ( query , { limit : 20 , offset : 100 } ) ;
Vous ne pouvez pas pré-compter la taille de l'ensemble de résultats. C'est une limite liée à la conception de FlexSearch. Lorsque vous avez vraiment besoin d'un décompte de tous les résultats que vous pouvez parcourir, attribuez simplement une limite suffisamment élevée, récupérez tous les résultats et appliquez votre décalage de pagination manuellement (cela fonctionne également côté serveur). FlexSearch est suffisamment rapide pour que ce ne soit pas un problème.
Seul un index de documents peut avoir un magasin. Vous pouvez utiliser un index de document au lieu d'un index plat pour obtenir cette fonctionnalité également lorsque vous stockez uniquement des paires ID-contenu.
Vous pouvez définir indépendamment quels champs doivent être indexés et quels champs doivent être stockés. De cette façon, vous pouvez indexer les champs qui ne doivent pas être inclus dans le résultat de la recherche.
N'utilisez pas de magasin lorsque : 1. un tableau d'identifiants car le résultat est suffisamment bon, ou 2. vous avez déjà le contenu/documents stocké ailleurs (en dehors de l'index).
Lorsque l'attribut
store
a été défini, vous devez inclure tous les champs qui doivent être stockés explicitement (agit comme une liste blanche).
Lorsque l'attribut
store
n'a pas été défini, le document original est stocké comme solution de secours.
Cela ajoutera tout le contenu original à la boutique :
const index = new Document ( {
document : {
index : "content" ,
store : true
}
} ) ;
index . add ( { id : 0 , content : "some text" } ) ;
Vous pouvez obtenir des documents indexés depuis la boutique :
var data = index . get ( 1 ) ;
Vous pouvez mettre à jour/modifier le contenu du magasin directement sans modifier l'index en :
index . set ( 1 , data ) ;
Pour mettre à jour le magasin et également mettre à jour l'index, utilisez simplement index.update
, index.add
ou index.append
.
Lorsque vous effectuez une requête, qu'il s'agisse d'un index de document ou d'un index plat, vous récupérerez toujours un tableau d'identifiants.
En option, vous pouvez enrichir automatiquement les résultats de la requête avec le contenu stocké en :
index . search ( query , { enrich : true } ) ;
Vos résultats ressemblent maintenant à :
[ {
id : 0 ,
doc : { /* content from store */ }
} , {
id : 1 ,
doc : { /* content from store */ }
} ]
Cela ajoutera uniquement des champs spécifiques d'un document au magasin (l'ID n'est pas nécessaire à conserver en magasin) :
const index = new Document ( {
document : {
index : "content" ,
store : [ "author" , "email" ]
}
} ) ;
index . add ( id , content ) ;
Vous pouvez configurer indépendamment ce qui doit être indexé et ce qui doit être stocké. Il est fortement recommandé d’en profiter chaque fois que vous le pouvez.
Voici un exemple utile de configuration de doc et store :
const index = new Document ( {
document : {
index : "content" ,
store : [ "author" , "email" ]
}
} ) ;
index . add ( {
id : 0 ,
author : "Jon Doe" ,
email : "[email protected]" ,
content : "Some content for the index ..."
} ) ;
Vous pouvez interroger le contenu et récupérerez les valeurs stockées à la place :
index . search ( "some content" , { enrich : true } ) ;
Vos résultats ressemblent maintenant à :
[ {
field : "content" ,
result : [ {
id : 0 ,
doc : {
author : "Jon Doe" ,
email : "[email protected]" ,
}
} ]
} ]
Les champs « auteur » et « email » ne sont pas indexés.
Enchaînez simplement des méthodes telles que :
var index = FlexSearch . create ( )
. addMatcher ( { 'â' : 'a' } )
. add ( 0 , 'foo' )
. add ( 1 , 'bar' ) ;
index . remove ( 0 ) . update ( 1 , 'foo' ) . add ( 2 , 'foobar' ) ;
Remarque : Cette fonctionnalité est désactivée par défaut en raison de son utilisation étendue de la mémoire. Lisez ici pour obtenir plus d'informations sur et comment l'activer.
FlexSearch introduit un nouveau mécanisme de notation appelé Recherche contextuelle qui a été inventé par Thomas Wilkerling, l'auteur de cette bibliothèque. Une recherche contextuelle augmente considérablement les requêtes à un tout nouveau niveau, mais nécessite également de la mémoire supplémentaire (en fonction de la profondeur ). L'idée de base de ce concept est de limiter la pertinence par son contexte au lieu de calculer la pertinence sur toute la distance du document correspondant. De cette manière, la recherche contextuelle améliore également les résultats des requêtes basées sur la pertinence sur une grande quantité de données textuelles.
Créez un index et utilisez le contexte par défaut :
var index = new FlexSearch ( {
tokenize : "strict" ,
context : true
} ) ;
Créez un index et appliquez des options personnalisées pour le contexte :
var index = new FlexSearch ( {
tokenize : "strict" ,
context : {
resolution : 5 ,
depth : 3 ,
bidirectional : true
}
} ) ;
Seul le tokenizer "strict" est réellement pris en charge par l'index contextuel.
L'index contextuel nécessite une quantité de mémoire supplémentaire en fonction de la profondeur.
Vous devez initialiser le cache et sa limite lors de la création de l'index :
const index = new Index ( { cache : 100 } ) ;
const results = index . searchCache ( query ) ;
Un scénario courant d'utilisation d'un cache est une recherche automatique ou instantanée lors de la saisie.
Lors de la transmission d'un nombre comme limite, le cache équilibre automatiquement les entrées stockées liées à leur popularité.
Lorsque vous utilisez simplement "true", le cache est illimité et fonctionne en fait 2 à 3 fois plus rapidement (car l'équilibreur n'a pas besoin de s'exécuter).
Le nouveau modèle de travailleur de la v0.7.0 est divisé en "champs" du document (1 travailleur = 1 index de champ). De cette façon, le travailleur devient capable de résoudre complètement les tâches (sous-tâches). L'inconvénient de ce paradigme est qu'ils n'ont peut-être pas été parfaitement équilibrés dans le stockage du contenu (les champs peuvent avoir une longueur de contenu différente). En revanche, rien n’indique que l’équilibrage du stockage présente un quelconque avantage (ils nécessitent tous la même quantité au total).
Lorsque vous utilisez un index de document, appliquez simplement l'option "worker" :
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
index . add ( {
id : 1 , tag : "cat" , name : "Tom" , title : "some" , text : "some"
} ) . add ( {
id : 2 , tag : "dog" , name : "Ben" , title : "title" , text : "content"
} ) . add ( {
id : 3 , tag : "cat" , name : "Max" , title : "to" , text : "to"
} ) . add ( {
id : 4 , tag : "dog" , name : "Tim" , title : "index" , text : "index"
} ) ;
Worker 1: { 1: "cat", 2: "dog", 3: "cat", 4: "dog" }
Worker 2: { 1: "Tom", 2: "Ben", 3: "Max", 4: "Tim" }
Worker 3: { 1: "some", 2: "title", 3: "to", 4: "index" }
Worker 4: { 1: "some", 2: "content", 3: "to", 4: "index" }
Lorsque vous effectuez une recherche sur tous les champs, cette tâche est parfaitement équilibrée entre tous les travailleurs, qui peuvent résoudre leurs sous-tâches de manière indépendante.
Ci-dessus, nous avons vu que les documents créeront automatiquement un travailleur pour chaque champ. Vous pouvez également créer un WorkerIndex directement (comme si vous utilisiez Index
au lieu de Document
).
Utiliser comme module ES6 :
import WorkerIndex from "./worker/index.js" ;
const index = new WorkerIndex ( options ) ;
index . add ( 1 , "some" )
. add ( 2 , "content" )
. add ( 3 , "to" )
. add ( 4 , "index" ) ;
Ou lorsque la version groupée a été utilisée à la place :
var index = new FlexSearch . Worker ( options ) ;
index . add ( 1 , "some" )
. add ( 2 , "content" )
. add ( 3 , "to" )
. add ( 4 , "index" ) ;
Un tel WorkerIndex fonctionne à peu près de la même manière qu'une instance créée de Index
.
Un WorkerIndex ne prend en charge que la variante
async
de toutes les méthodes. Cela signifie que lorsque vous appelezindex.search()
sur un WorkerIndex, cela fonctionnera également de manière asynchrone de la même manière queindex.searchAsync()
.
Le modèle de travail pour Node.js est basé sur des « threads de travail » et fonctionne exactement de la même manière :
const { Document } = require ( "flexsearch" ) ;
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
Ou créez une seule instance de travailleur pour un index non-document :
const { Worker } = require ( "flexsearch" ) ;
const index = new Worker ( { options } ) ;
Un travailleur fonctionnera toujours de manière asynchrone. Lors d'un appel de méthode de requête, vous devez toujours gérer la promesse renvoyée (par exemple, utiliser await
) ou transmettre une fonction de rappel comme dernier paramètre.
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
Toutes les requêtes et sous-tâches s'exécuteront en parallèle (en donnant la priorité à "toutes les tâches terminées") :
index . searchAsync ( query , callback ) ;
index . searchAsync ( query , callback ) ;
index . searchAsync ( query , callback ) ;
Aussi (privilégiez « toutes les tâches terminées » ):
index . searchAsync ( query ) . then ( callback ) ;
index . searchAsync ( query ) . then ( callback ) ;
index . searchAsync ( query ) . then ( callback ) ;
Ou lorsque vous n'avez qu'un seul rappel lorsque toutes les requêtes sont terminées, utilisez simplement Promise.all()
qui donne également la priorité à "toutes les tâches terminées" :
Promise . all ( [
index . searchAsync ( query ) ,
index . searchAsync ( query ) ,
index . searchAsync ( query )
] ) . then ( callback ) ;
Dans le rappel de Promise.all()
vous obtiendrez également un tableau de résultats comme premier paramètre respectivement pour chaque requête que vous effectuez.
Lorsque vous utilisez await
vous pouvez prioriser la commande (prioriser "première tâche terminée") et résoudre les demandes une par une et simplement traiter les sous-tâches en parallèle :
await index . searchAsync ( query ) ;
await index . searchAsync ( query ) ;
await index . searchAsync ( query ) ;
Idem pour index.add()
, index.append()
, index.remove()
ou index.update()
. Il existe ici un cas particulier qui n'est pas désactivé par la bibliothèque, mais que vous devez garder à l'esprit lorsque vous utilisez Workers.
Lorsque vous appelez la version "synchronisée" sur un index de travail :
index . add ( doc ) ;
index . add ( doc ) ;
index . add ( doc ) ;
// contents aren't indexed yet,
// they just queued on the message channel
Bien sûr, vous pouvez le faire, mais gardez à l'esprit que le thread principal ne dispose pas de file d'attente supplémentaire pour les tâches de travail distribuées. Leur exécution dans une longue boucle envoie massivement du contenu vers le canal de message via worker.postMessage()
en interne. Heureusement, le navigateur et Node.js géreront automatiquement ces tâches entrantes (tant que suffisamment de RAM libre est disponible). Lors de l'utilisation de la version « synchronisée » sur un index de travail, le contenu n'est pas indexé une ligne en dessous, car tous les appels sont traités comme asynchrones par défaut.
Lors de l'ajout/mise à jour/suppression de grandes quantités de contenu à l'index (ou à haute fréquence), il est recommandé d'utiliser la version asynchrone avec
async/await
pour conserver une faible empreinte mémoire pendant les processus longs.
L'export a légèrement changé. L'exportation se compose désormais de plusieurs petites parties, au lieu d'un seul gros volume. Vous devez passer une fonction de rappel qui a 2 arguments "key" et "data". Cette fonction de rappel est appelée par chaque partie, par exemple :
index . export ( function ( key , data ) {
// you need to store both the key and the data!
// e.g. use the key for the filename and save your data
localStorage . setItem ( key , data ) ;
} ) ;
L'exportation de données vers le stockage local n'est pas vraiment une bonne pratique, mais si la taille n'est pas un problème, utilisez-la si vous le souhaitez. L'exportation existe principalement pour une utilisation dans Node.js ou pour stocker les index que vous souhaitez déléguer d'un serveur au client.
La taille de l'export correspond à la consommation mémoire de la bibliothèque. Pour réduire la taille de l'exportation, vous devez utiliser une configuration qui a moins d'empreinte mémoire (utilisez le tableau en bas pour obtenir des informations sur les configurations et son allocation de mémoire).
Lorsque votre routine de sauvegarde s'exécute de manière asynchrone, vous devez renvoyer une promesse :
index . export ( function ( key , data ) {
return new Promise ( function ( resolve ) {
// do the saving as async
resolve ( ) ;
} ) ;
} ) ;
Vous ne pouvez pas exporter la table supplémentaire pour la fonctionnalité "fastupdate". Ces tables contiennent des références et lorsqu'elles sont stockées, elles sont entièrement sérialisées et deviennent trop volumineuses. La bibliothèque les gérera automatiquement pour vous. Lors de l'importation de données, l'index désactive automatiquement "fastupdate".
Avant de pouvoir importer des données, vous devez d'abord créer votre index. Pour les index de documents, fournissez le même descripteur de document que vous avez utilisé lors de l'exportation des données. Cette configuration n'est pas stockée dans l'export.
var index = new Index ( { ... } ) ;
Pour importer les données, transmettez simplement une clé et des données :
index . import ( key , localStorage . getItem ( key ) ) ;
Vous devez importer chaque clé ! Sinon, votre index ne fonctionne pas. Vous devez stocker les clés de l'export et utiliser ces clés pour l'importation (l'ordre des clés peut différer).
Ceci est uniquement à titre de démonstration et n'est pas recommandé, car vous pourriez avoir d'autres clés dans votre localStorage qui ne sont pas prises en charge en tant qu'importation :
var keys = Object . keys ( localStorage ) ;
for ( let i = 0 , key ; i < keys . length ; i ++ ) {
key = keys [ i ] ;
index . import ( key , localStorage . getItem ( key ) ) ;
}
Les définitions spécifiques à la langue sont divisées en deux groupes :
function(string):string[]
boolean
{string: string}
{string: string}
string[]
Le jeu de caractères contient la logique de codage, le langage contient un stemmer, un filtre de mots vides et des matchers. Plusieurs définitions de langage peuvent utiliser le même encodeur de jeu de caractères. Cette séparation vous permet également de gérer différentes définitions de langue pour des cas d'utilisation particuliers (par exemple, noms, villes, dialectes/argot, etc.).
Pour décrire entièrement une langue personnalisée à la volée, vous devez réussir :
const index = FlexSearch ( {
// mandatory:
encode : ( content ) => [ words ] ,
// optionally:
rtl : false ,
stemmer : { } ,
matcher : { } ,
filter : [ ]
} ) ;
Lorsqu'il ne passe aucun paramètre, il utilise le schéma latin:default
par défaut.
Champ | Catégorie | Description |
encoder | jeu de caractères | La fonction encodeur. Doit renvoyer un tableau de mots séparés (ou une chaîne vide). |
rtl | jeu de caractères | Une propriété booléenne qui indique un codage de droite à gauche. |
filtre | langue | Les filtres sont également appelés « mots vides », ils filtrent complètement les mots et les empêchent d'être indexés. |
égrappoir | langue | Stemmer supprime les terminaisons de mots et constitue une sorte de « normalisation partielle ». Une fin de mot vient de correspondre lorsque la longueur du mot est plus grande que le partiel correspondant. |
matcheur | langue | Matcher remplace toutes les occurrences d'une chaîne donnée quelle que soit sa position et constitue également une sorte de "normalisation partielle". |
Le moyen le plus simple d'attribuer un codage spécifique au jeu de caractères/à la langue via des modules est :
import charset from "./dist/module/lang/latin/advanced.js" ;
import lang from "./dist/module/lang/en.js" ;
const index = FlexSearch ( {
charset : charset ,
lang : lang
} ) ;
Importez simplement l’ exportation par défaut de chaque module et attribuez-les en conséquence.
L’exemple complet ci-dessus est :
import { encode , rtl } from "./dist/module/lang/latin/advanced.js" ;
import { stemmer , filter , matcher } from "./dist/module/lang/en.js" ;
const index = FlexSearch ( {
encode : encode ,
rtl : rtl ,
stemmer : stemmer ,
matcher : matcher ,
filter : filter
} ) ;
L'exemple ci-dessus est l'interface standard qui est au moins exportée depuis chaque jeu de caractères/langue.
Vous pouvez également définir directement l'encodeur et laisser toutes les autres options :
import simple from "./dist/module/lang/latin/simple.js" ;
const index = FlexSearch ( {
encode : simple
} ) ;
Vous pouvez attribuer un jeu de caractères en transmettant le jeu de caractères lors de l'initialisation, par exemple charset: "latin"
pour l'encodeur de jeu de caractères par défaut ou charset: "latin:soundex"
pour une variante d'encodeur.
Les définitions de langue (en particulier les matchers) pourraient également être utilisées pour normaliser le dialecte et l'argot d'une langue spécifique.
Vous devez rendre les définitions de jeu de caractères et/ou de langue disponibles en :
flexsearch.bundle.js
, mais aucune définition spécifique à la langue n'est incluse./dist/lang/
(les fichiers font référence aux langues, les dossiers sont des jeux de caractères)Lors du chargement des modules linguistiques, assurez-vous que la bibliothèque a été chargée avant :
< script src =" dist/flexsearch.light.js " > </ script >
< script src =" dist/lang/latin/default.min.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
Lorsque vous utilisez la version complète "Bundle", les encodeurs latins intégrés sont déjà inclus et il vous suffit de charger le fichier de langue:
< script src =" dist/flexsearch.bundle.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
Parce que vous chargez des packs sous forme de packages externes (modules non ES6), vous devez les initialiser par raccourcis:
const index = FlexSearch ( {
charset : "latin:soundex" ,
lang : "en"
} ) ;
Utilisez la notation
charset:variant
pour attribuer le charse et ses variantes. Lorsque le simple fait de passer le Charset sans variant se résoudra automatiquement commecharset:default
.
Vous pouvez également remplacer les définitions existantes, par exemple:
const index = FlexSearch ( {
charset : "latin" ,
lang : "en" ,
matcher : { }
} ) ;
Les définitions passées n'étendront pas les définitions par défaut, elles les remplaceront.
Lorsque vous aimez prolonger une définition, créez simplement un nouveau fichier de langue et mettez dans toute la logique.
Il est assez simple lors de l'utilisation d'une variante d'encodeur:
< script src =" dist/flexsearch.light.js " > </ script >
< script src =" dist/lang/latin/advanced.min.js " > </ script >
< script src =" dist/lang/latin/extra.min.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
Lorsque vous utilisez la version complète "Bundle", les encodeurs latins intégrés sont déjà inclus et il vous suffit de charger le fichier de langue:
< script src =" dist/flexsearch.bundle.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
const index_advanced = FlexSearch ( {
charset : "latin:advanced"
} ) ;
const index_extra = FlexSearch ( {
charset : "latin:extra"
} ) ;
Dans FlexSearch, vous ne pouvez pas fournir votre propre tokenizer partiel, car il s'agit d'une dépendance directe à l'unité centrale. Le jetons intégré de FlexSearch divise chaque mot en fragments par différents modèles:
Il s'agit du pipeline par défaut fourni par FlexSearch:
Jetez d'abord un coup d'œil dans le pipeline par défaut dans src/common.js
. C'est très simple et simple. Le pipeline traitera comme une sorte d'inversion de contrôle, la mise en œuvre finale de l'encodeur doit gérer le charse et également les transformations spécifiques à la langue. Cette solution de contournement est parti de nombreux tests.
Injectez le pipeline par défaut par EG:
this . pipeline (
/* string: */ str . toLowerCase ( ) ,
/* normalize: */ false ,
/* split: */ split ,
/* collapse: */ false
) ;
Utilisez le schéma du pipeline d'en haut pour comprendre l'itération et la différence de précodage et de post-codage. STEMMER et les matchs doivent être appliqués après la normalisation du charse mais avant les transformations du langage, les filtres également.
Voici un bon exemple de pipelines étendus: src/lang/latin/extra.js
→ src/lang/latin/advanced.js
→ src/lang/latin/simple.js
.
Recherchez votre langue dans src/lang/
, si elle existe, vous pouvez étendre ou fournir des variantes (comme le dialecte / argot). Si la langue n'existe pas, créez un nouveau fichier et vérifiez si l'un dessets existants (par exemple latin) s'adapte à votre langue. Lorsqu'aucun charme n'existe, vous devez fournir un charme comme base pour la langue.
Un nouveau charme devrait fournir au moins:
encode
une fonction qui normalise le charme d'un contenu de texte passé (supprimez des caractères spéciaux, des transformations linguaux, etc.) et renvoie un tableau de mots séparés . Le filtre STEMMer, Matcher ou Mord de mots doit également être appliqué ici. Lorsque la langue n'a pas de mots, assurez-vous de fournir quelque chose de similaire, par exemple, chaque signe chinois pourrait également être un "mot". Ne renvoyez pas l'intégralité du contenu du texte sans séparer.rtl
un drapeau booléen qui indique le codage de droite à gaucheFondamentalement, le Charset doit juste fournir une fonction d'encodeur ainsi qu'un indicateur pour le codage droit à gauche:
export function encode ( str ) { return [ str ] }
export const rtl = false ;
Chaîne de référence: "Björn-Phillipp Mayer"
Requête | défaut | simple | avancé | supplémentaire |
björn | Oui | Oui | Oui | Oui |
björ | Oui | Oui | Oui | Oui |
björn | Non | Oui | Oui | Oui |
bjoern | Non | Non | Oui | Oui |
philipp | Non | Non | Oui | Oui |
décoller | Non | Non | Oui | Oui |
björnphillip | Non | Oui | Oui | Oui |
Meier | Non | Non | Oui | Oui |
Björn Meier | Non | Non | Oui | Oui |
Meier Fhilip | Non | Non | Oui | Oui |
mair byorn | Non | Non | Non | Oui |
(Faux positifs) | Non | Non | Non | Oui |
Le livre "Gulliver's Travels Swift Jonathan 1726" a été entièrement indexé pour les exemples ci-dessous.
Le paramètre significatif le plus optimisé à la mémoire allouera seulement 1,2 Mo pour tout le livre indexé! C'est probablement l'empreinte mémoire la plus petite que vous obtiendrez dans une bibliothèque de recherche.
import { encode } from "./lang/latin/extra.js" ;
index = new Index ( {
encode : encode ,
tokenize : "strict" ,
optimize : true ,
resolution : 1 ,
minlength : 3 ,
fastupdate : false ,
context : false
} ) ;
Le livre "Gulliver's Travels" (Swift Jonathan 1726) a été complètement indexé pour ce test:
Par défaut, un index lexical est très petit:
depth: 0, bidirectional: 0, resolution: 3, minlength: 0
=> 2,1 Mo
Une résolution plus élevée augmentera l'allocation de la mémoire:
depth: 0, bidirectional: 0, resolution: 9, minlength: 0
=> 2,9 Mo
L'utilisation de l'index contextuel augmentera l'allocation de mémoire:
depth: 1, bidirectional: 0, resolution: 9, minlength: 0
=> 12,5 Mo
Une profondeur contextuelle plus élevée augmentera l'allocation de la mémoire:
depth: 2, bidirectional: 0, resolution: 9, minlength: 0
=> 21,5 Mo
Une longueur de mine plus élevée diminuera l'allocation de la mémoire:
depth: 2, bidirectional: 0, resolution: 9, minlength: 3
=> 19,0 Mo
L'utilisation de bidirection réduira l'allocation de la mémoire:
depth: 2, bidirectional: 1, resolution: 9, minlength: 3
=> 17,9 Mo
Activer l'option "FustUpdate" augmentera l'allocation de la mémoire:
depth: 2, bidirectional: 1, resolution: 9, minlength: 3
=> 6,3 Mo
Chaque bibliothèque de recherche est constamment en concurrence avec ces 4 propriétés:
FlexSearch vous fournit de nombreux paramètres que vous pouvez utiliser pour ajuster l'équilibre optimal pour votre cas d'utilisation spécifique.
Modificateur | Impact de la mémoire * | Impact des performances ** | Impact assorti ** | Impact de notation ** |
résolution | +1 (par niveau) | +1 (par niveau) | 0 | +2 (par niveau) |
profondeur | +4 (par niveau) | -1 (par niveau) | -10 + profondeur | +10 |
minlengle | -2 (par niveau) | +2 (par niveau) | -3 (par niveau) | +2 (par niveau) |
bidirectionnel | -2 | 0 | +3 | -1 |
se détendre | +1 | +10 (mise à jour, supprimer) | 0 | 0 |
Optimiser: vrai | -7 | -1 | 0 | -3 |
Encodeur: "ICase" | 0 | 0 | 0 | 0 |
Encodeur: "Simple" | -2 | -1 | +2 | 0 |
Encodeur: "Avancé" | -3 | -2 | +4 | 0 |
Encodeur: "Extra" | -5 | -5 | +6 | 0 |
Encodeur: "SoundEx" | -6 | -2 | +8 | 0 |
tokenize: "strict" | 0 | 0 | 0 | 0 |
tokenize: "en avant" | +3 | -2 | +5 | 0 |
tokenize: "inverse" | +5 | -4 | +7 | 0 |
tokenize: "plein" | +8 | -5 | +10 | 0 |
index des documents | +3 (par champ) | -1 (par champ) | 0 | 0 |
Tags de document | +1 (par balise) | -1 (par étiquette) | 0 | 0 |
magasin: vrai | +5 (par document) | 0 | 0 | 0 |
Store: [champs] | +1 (par champ) | 0 | 0 | 0 |
Cache: vrai | +10 | +10 | 0 | 0 |
Cache: 100 | +1 | +9 | 0 | 0 |
Type d'identité: numéro | 0 | 0 | 0 | 0 |
Type d'ID: chaîne | +3 | -3 | 0 | 0 |
memory
(optimiser primaire pour la mémoire)performance
(principale optimiser pour les performances)match
(optimiser primaire pour l'appariement)score
(Optimiser primaire pour la notation)default
(le profil équilibré par défaut)Ces profils couvrent les cas d'utilisation standard. Il est recommandé d'appliquer une configuration personnalisée au lieu d'utiliser des profils pour tirer le meilleur parti de votre situation. Chaque profil pourrait être optimisé davantage à sa tâche spécifique, par exemple une configuration optimisée de performances extrêmes ou une mémoire extrême, etc.
Vous pouvez passer un préréglage lors de la création / initialisation de l'indice.
Il est recommandé d'utiliser les valeurs d'ID numérique comme référence lors de l'ajout de contenu à l'index. La longueur d'octets des ID adoptées influence considérablement la consommation de mémoire. Si cela n'est pas possible, vous devez envisager d'utiliser un tableau d'index et de cartographier les ID avec des index, cela devient important, en particulier lorsque vous utilisez des index contextuels sur une grande quantité de contenu.
Chaque fois que vous le pouvez, essayez de diviser le contenu par des catégories et de les ajouter à son propre index, par exemple:
var action = new FlexSearch ( ) ;
var adventure = new FlexSearch ( ) ;
var comedy = new FlexSearch ( ) ;
De cette façon, vous pouvez également fournir différents paramètres pour chaque catégorie. C'est en fait le moyen le plus rapide d'effectuer une recherche floue.
Pour rendre cette solution plus extensible, vous pouvez utiliser une aide courte:
var index = { } ;
function add ( id , cat , content ) {
( index [ cat ] || (
index [ cat ] = new FlexSearch
) ) . add ( id , content ) ;
}
function search ( cat , query ) {
return index [ cat ] ?
index [ cat ] . search ( query ) : [ ] ;
}
Ajouter du contenu à l'index:
add ( 1 , "action" , "Movie Title" ) ;
add ( 2 , "adventure" , "Movie Title" ) ;
add ( 3 , "comedy" , "Movie Title" ) ;
Effectuer des requêtes:
var results = search ( "action" , "movie title" ) ; // --> [1]
Les index divisés par catégories améliorent considérablement les performances.
Copyright 2018-2023 Thomas Wilkerling, organisé par NextApps GmbH
Libéré sous la licence Apache 2.0