Lassen Sie uns hier über die kommende FlexSearch v0.8 sprechen: #415
Grundlegender Start • API-Referenz • Dokumentindizes • Verwenden von Worker • Änderungsprotokoll
Sie können mir helfen, indem Sie eine persönliche Spende leisten, um dieses Projekt am Leben zu erhalten und gleichzeitig den gesamten Beitrag zur Lösung Ihrer Bedürfnisse zu leisten.
Antithesis Operations LLC
Wenn es um die reine Suchgeschwindigkeit geht, übertrifft FlexSearch jede einzelne Suchbibliothek auf dem Markt und bietet außerdem flexible Suchfunktionen wie die Suche in mehreren Feldern, phonetische Transformationen oder teilweise Übereinstimmung.
Abhängig von den verwendeten Optionen bietet es auch den speichereffizientesten Index. FlexSearch führt einen neuen Bewertungsalgorithmus namens „Kontextindex“ ein, der auf einer vorab bewerteten lexikalischen Wörterbucharchitektur basiert und Abfragen im Vergleich zu anderen Bibliotheken tatsächlich bis zu 1.000.000 Mal schneller durchführt. FlexSearch bietet Ihnen außerdem ein nicht blockierendes asynchrones Verarbeitungsmodell sowie Web-Worker, um alle Aktualisierungen oder Abfragen des Index parallel über dedizierte ausgeglichene Threads durchzuführen.
Unterstützte Plattformen:
Bibliotheksvergleich „Gullivers Reisen“:
Plugins (externe Projekte):
Bauen | Datei | CDN |
flexsearch.bundle.js | Herunterladen | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.bundle.js |
flexsearch.light.js | Herunterladen | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.light.js |
flexsearch.compact.js | Herunterladen | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.compact.js |
flexsearch.es5.js * | Herunterladen | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.es5.js |
ES6-Module | Herunterladen | Der Ordner /dist/module/ dieses Github-Repositorys |
* Das Bundle „flexsearch.es5.js“ enthält Polyfills für EcmaScript 5-Unterstützung.
npm install flexsearch
Das Node.js-Paket enthält alle Funktionen von
flexsearch.bundle.js
.
Besonderheit | flexsearch.bundle.js | flexsearch.compact.js | flexsearch.light.js |
Voreinstellungen | ✓ | ✓ | - |
Asynchrone Suche | ✓ | ✓ | - |
Arbeiter (Web + Node.js) | ✓ | - | - |
Kontextbezogene Indizes | ✓ | ✓ | ✓ |
Indexdokumente (Feldsuche) | ✓ | ✓ | - |
Dokumentenspeicher | ✓ | ✓ | - |
Teilweise Übereinstimmung | ✓ | ✓ | ✓ |
Relevanzbewertung | ✓ | ✓ | ✓ |
Automatisch ausgeglichener Cache nach Beliebtheit | ✓ | - | - |
Schlagworte | ✓ | - | - |
Vorschläge | ✓ | ✓ | - |
Phonetisches Matching | ✓ | ✓ | - |
Anpassbarer Zeichensatz/Sprache (Matcher, Encoder, Tokenizer, Stemmer, Filter, Split, RTL) | ✓ | ✓ | ✓ |
Indizes exportieren/importieren | ✓ | - | - |
Dateigröße (gzip) | 6,8 KB | 5,3 KB | 2,9 KB |
Laufvergleich: Leistungsbenchmark „Gullivers Reisen“
Betrieb pro Sekunde, höher ist besser, mit Ausnahme des Tests „Speicher“, bei dem niedriger ist besser.
Rang | Bibliothek | Erinnerung | Abfrage (einzelner Begriff) | Abfrage (mehrere Begriffe) | Abfrage (lang) | Abfrage (Dupes) | Abfrage (nicht gefunden) |
1 | FlexSearch | 17 | 7084129 | 1586856 | 511585 | 2017142 | 3202006 |
2 | JSii | 27 | 6564 | 158149 | 61290 | 95098 | 534109 |
3 | Waten | 424 | 20471 | 78780 | 16693 | 225824 | 213754 |
4 | JS-Suche | 193 | 8221 | 64034 | 10377 | 95830 | 167605 |
5 | Elasticlunr.js | 646 | 5412 | 7573 | 2865 | 23786 | 13982 |
6 | Massensuche | 1021 | 3069 | 3141 | 3333 | 3265 | 21825569 |
7 | MiniSearch | 24348 | 4406 | 10945 | 72 | 39989 | 17624 |
8 | bm25 | 15719 | 1429 | 789 | 366 | 884 | 1823 |
9 | Lunr.js | 2219 | 255 | 271 | 272 | 266 | 267 |
10 | FuzzySearch | 157373 | 53 | 38 | 15 | 32 | 43 |
11 | Sicherung | 7641904 | 6 | 2 | 1 | 2 | 3 |
Es gibt 3 Arten von Indizes:
Index
ist ein flacher Hochleistungsindex, der ID-Inhaltspaare speichert.Worker
/ WorkerIndex
ist ebenfalls ein flacher Index, der ID-Inhaltspaare speichert, aber im Hintergrund als dedizierter Worker-Thread ausgeführt wird.Document
ist ein Index mit mehreren Feldern, der komplexe JSON-Dokumente speichern kann (es können auch Worker-Indizes vorhanden sein).Die meisten von Ihnen benötigen je nach Szenario wahrscheinlich nur eine davon.
< 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 >
Oder über 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
Fügen Sie in Ihren Code Folgendes ein:
const { Index , Document , Worker } = require ( "flexsearch" ) ;
const index = new Index ( options ) ;
const document = new Document ( options ) ;
const worker = new Worker ( options ) ;
Oder:
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 ) ;
Der worker
erbt vom Typ Index
und nicht vom Typ Document
. Daher funktioniert ein WorkerIndex grundsätzlich wie ein Standard-FlexSearch-Index. Die Worker-Unterstützung in Dokumenten muss aktiviert werden, indem bei der Erstellung einfach die entsprechende Option übergeben wird { worker: true }
.
Jede für einen
Worker
Index aufgerufene Methode wird als asynchron behandelt. Sie erhalten einPromise
zurück oder können alternativ eine Callback-Funktion als letzten Parameter angeben.
Globale Methoden:
Indexmethoden:
WorkerIndex-Methoden:
Dokumentmethoden:
* Für jede dieser Methoden gibt es ein asynchrones Äquivalent:
Asynchrone Version:
Asynchrone Methoden geben ein Promise
zurück. Alternativ können Sie als letzten Parameter eine Callback-Funktion übergeben.
export
und import
sind immer asynchron, ebenso wie jede Methode, die Sie für einen Worker-basierten Index aufrufen.
FlexSearch ist hochgradig anpassbar. Durch den Einsatz der richtigen Optionen können Sie Ihre Ergebnisse sowie den Speicherverbrauch und die Abfragezeit erheblich verbessern.
Option | Werte | Beschreibung | Standard |
voreingestellt | "Erinnerung" "Leistung" "übereinstimmen" "Punktzahl" "Standard" | Das Konfigurationsprofil als Verknüpfung oder als Basis für Ihre benutzerdefinierten Einstellungen. | "Standard" |
tokenisieren | "strikt" "nach vorne" "umkehren" "voll" | Der Indizierungsmodus (Tokenizer). Wählen Sie eine der integrierten Funktionen oder übergeben Sie eine benutzerdefinierte Tokenizer-Funktion. | "strikt" |
Cache | Boolescher Wert Nummer | Aktivieren/deaktivieren und/oder legen Sie die Kapazität zwischengespeicherter Einträge fest. Bei der Übergabe einer Zahl als Limit gleicht der Cache die gespeicherten Einträge automatisch entsprechend ihrer Beliebtheit aus . Hinweis: Wenn Sie nur „true“ verwenden, sind dem Cache keine Grenzen gesetzt und das Wachstum unbegrenzt. | FALSCH |
Auflösung | Nummer | Legt die Scoring-Auflösung fest (Standard: 9). | 9 |
Kontext | Boolescher Wert Kontextoptionen | Kontextbezogene Indizierung aktivieren/deaktivieren. Wenn „true“ als Wert übergeben wird, werden die Standardwerte für den Kontext verwendet. | FALSCH |
optimieren | Boolescher Wert | Wenn diese Option aktiviert ist, wird ein speicheroptimierter Stapelfluss für den Index verwendet. | WAHR |
Schub | function(arr, str, int) => float | Eine benutzerdefinierte Boost-Funktion, die beim Indizieren von Inhalten im Index verwendet wird. Die Funktion hat diese Signatur: Function(words[], term, index) => Float . Es verfügt über 3 Parameter, mit denen Sie ein Array aller Wörter erhalten, den aktuellen Begriff und den aktuellen Index, an dem der Begriff im Wortarray platziert wird. Sie können Ihre eigene Berechnung anwenden, z. B. das Vorkommen eines Begriffs, und diesen Faktor zurückgeben (<1 bedeutet, dass die Relevanz verringert ist, >1 bedeutet, dass die Relevanz erhöht ist).Hinweis: Diese Funktion ist derzeit nur durch die Verwendung des Tokenizers „strict“ eingeschränkt. | null |
Sprachspezifische Optionen und Kodierung: | |||
Zeichensatz | Charset-Nutzlast String (Schlüssel) | Stellen Sie eine benutzerdefinierte Zeichensatz-Nutzlast bereit oder übergeben Sie einen der Schlüssel integrierter Zeichensätze. | "lateinisch" |
Sprache | Sprachnutzlast String (Schlüssel) | Stellen Sie eine benutzerdefinierte Sprachnutzlast bereit oder übergeben Sie das Sprachkürzel-Flag (ISO-3166) der integrierten Sprachen. | null |
kodieren | FALSCH "Standard" "einfach" "Gleichgewicht" "fortschrittlich" "Extra" function(str) => [Wörter] | Der Codierungstyp. Wählen Sie eine der integrierten Funktionen oder übergeben Sie eine benutzerdefinierte Kodierungsfunktion. | "Standard" |
Stemmer | FALSCH Zeichenfolge Funktion | FALSCH | |
Filter | FALSCH Zeichenfolge Funktion | FALSCH | |
Matcher | FALSCH Zeichenfolge Funktion | FALSCH | |
Zusätzliche Optionen für Dokumentindizes: | |||
Arbeitnehmer | Boolescher Wert | Aktivieren/Deaktivieren und legen Sie die Anzahl der ausgeführten Arbeitsthreads fest. | FALSCH |
dokumentieren | Dokumentdeskriptor | Enthält Definitionen für den Dokumentenindex und die Speicherung. |
Option | Werte | Beschreibung | Standard |
Auflösung | Nummer | Legt die Bewertungsauflösung für den Kontext fest (Standard: 1). | 1 |
Tiefe | FALSCH Nummer | Aktivieren/deaktivieren Sie die kontextbezogene Indizierung und legen Sie außerdem den kontextbezogenen Relevanzabstand fest. Tiefe ist die maximale Anzahl von Wörtern/Tokens, die ein Begriff enthält, um als relevant angesehen zu werden. | 1 |
bidirektional | Boolescher Wert | Legt ein bidirektionales Suchergebnis fest. Wenn aktiviert und der Quelltext „red hat“ enthält, wird er für Abfragen „red hat“ und „hat red“ gefunden. | WAHR |
Option | Werte | Beschreibung | Standard |
Ausweis | Zeichenfolge | "Ausweis"" | |
Etikett | FALSCH Zeichenfolge | "Etikett" | |
Index | Zeichenfolge Array<String> Array<Objekt> | ||
speichern | Boolescher Wert Zeichenfolge Array<String> | FALSCH |
Option | Werte | Beschreibung | Standard |
Teilt | FALSCH RegExp Zeichenfolge | Die Regel zum Aufteilen von Wörtern bei Verwendung eines nicht benutzerdefinierten Tokenizers (integrierte Funktionen, z. B. „forward“). Verwenden Sie eine Zeichenfolge/ein Zeichen oder einen regulären Ausdruck (Standard: /W+/ ). | /[W_]+/ |
RTL | Boolescher Wert | Aktiviert die Codierung von rechts nach links. | FALSCH |
kodieren | function(str) => [Wörter] | Die benutzerdefinierte Codierungsfunktion. | /lang/latin/default.js |
Option | Werte | Beschreibung |
Stemmer | FALSCH Zeichenfolge Funktion | Deaktivieren oder übergeben Sie das Sprachkürzel-Flag (ISO-3166) oder ein benutzerdefiniertes Objekt. |
Filter | FALSCH Zeichenfolge Funktion | Deaktivieren oder übergeben Sie das Sprachkürzel-Flag (ISO-3166) oder ein benutzerdefiniertes Array. |
Matcher | FALSCH Zeichenfolge Funktion | Deaktivieren oder übergeben Sie das Sprachkürzel-Flag (ISO-3166) oder ein benutzerdefiniertes Array. |
Option | Werte | Beschreibung | Standard |
Limit | Nummer | Legt die Ergebnisgrenze fest. | 100 |
versetzt | Nummer | Offset anwenden (Elemente überspringen). | 0 |
vorschlagen | Boolescher Wert | Ermöglicht Vorschläge in Ergebnissen. | FALSCH |
Option | Werte | Beschreibung | Standard |
Index | Zeichenfolge Array<String> Array<Objekt> | Legt die Dokumentfelder fest, die durchsucht werden sollen. Wenn kein Feld festgelegt ist, werden alle Felder durchsucht. Benutzerdefinierte Optionen pro Feld werden ebenfalls unterstützt. | |
Etikett | Zeichenfolge Array<String> | Legt die Dokumentfelder fest, die durchsucht werden sollen. Wenn kein Feld festgelegt ist, werden alle Felder durchsucht. Benutzerdefinierte Optionen pro Feld werden ebenfalls unterstützt. | FALSCH |
bereichern | Boolescher Wert | Reichern Sie IDs aus den Ergebnissen mit den entsprechenden Dokumenten an. | FALSCH |
bool | "Und" "oder" | Legt den verwendeten logischen Operator fest, wenn mehrere Felder oder Tags durchsucht werden. | "oder" |
Der Tokenizer beeinflusst den erforderlichen Speicher sowie die Abfragezeit und die Flexibilität von Teilübereinstimmungen. Versuchen Sie, den besten dieser Tokenizer auszuwählen, der Ihren Anforderungen entspricht:
Option | Beschreibung | Beispiel | Gedächtnisfaktor (n = Wortlänge) |
"strikt" | ganze Wörter indizieren | foobar | * 1 |
"nach vorne" | Wörter inkrementell in Vorwärtsrichtung indizieren | fo obarfoob ar | * N |
"umkehren" | Indizieren Sie Wörter schrittweise in beide Richtungen | foob ar fo obar | * 2n - 1 |
"voll" | Indizieren Sie jede mögliche Kombination | fo oba roob ar | * n * (n - 1) |
Die Kodierung beeinflusst den benötigten Speicher ebenso wie die Abfragezeit und phonetische Übereinstimmungen. Versuchen Sie, den besten dieser Encoder auszuwählen, der Ihren Anforderungen entspricht, oder geben Sie einen benutzerdefinierten Encoder ein:
Option | Beschreibung | Falsch-Positive | Kompression |
FALSCH | Deaktivieren Sie die Codierung | NEIN | 0% |
"Standard" | Bei der Codierung wird die Groß-/Kleinschreibung nicht beachtet | NEIN | 0% |
"einfach" | Bei der Codierung wird die Groß-/Kleinschreibung nicht beachtet Zeichensatznormalisierungen | NEIN | ~ 3% |
"Gleichgewicht" | Bei der Codierung wird die Groß-/Kleinschreibung nicht beachtet Zeichensatznormalisierungen Wörtliche Transformationen | NEIN | ~ 30 % |
"fortschrittlich" | Bei der Codierung wird die Groß-/Kleinschreibung nicht beachtet Zeichensatznormalisierungen Wörtliche Transformationen Phonetische Normalisierungen | NEIN | ~ 40 % |
"Extra" | Bei der Codierung wird die Groß-/Kleinschreibung nicht beachtet Zeichensatznormalisierungen Wörtliche Transformationen Phonetische Normalisierungen Soundex-Transformationen | Ja | ~ 65 % |
Funktion() | Übergeben Sie die benutzerdefinierte Codierung über function(string):[words] |
var index = new Index ( ) ;
Erstellen Sie einen neuen Index und wählen Sie eine der Voreinstellungen aus:
var index = new Index ( "performance" ) ;
Erstellen Sie einen neuen Index mit benutzerdefinierten Optionen:
var index = new Index ( {
charset : "latin:extra" ,
tokenize : "reverse" ,
resolution : 9
} ) ;
Erstellen Sie einen neuen Index und erweitern Sie eine Voreinstellung mit benutzerdefinierten Optionen:
var index = new FlexSearch ( {
preset : "memory" ,
tokenize : "forward" ,
resolution : 5
} ) ;
Sehen Sie sich alle verfügbaren benutzerdefinierten Optionen an.
Jeder Inhalt, der dem Index hinzugefügt werden soll, benötigt eine ID. Wenn Ihr Inhalt keine ID hat, müssen Sie eine erstellen, indem Sie einen Index, eine Anzahl oder etwas anderes als ID übergeben (ein Wert vom Typ „ number
wird dringend empfohlen). Diese IDs sind eindeutige Verweise auf einen bestimmten Inhalt. Dies ist wichtig, wenn Sie Inhalte über vorhandene IDs aktualisieren oder hinzufügen. Wenn die Referenzierung kein Problem darstellt, können Sie einfach etwas Einfaches wie count++
verwenden.
Index. add(id, string)
index . add ( 0 , "John Doe" ) ;
Index. search(string | Optionen, <Limit>, <Optionen>)
index . search ( "John" ) ;
Begrenzen Sie das Ergebnis:
index . search ( "John" , 10 ) ;
Sie können überprüfen, ob eine ID bereits indiziert wurde durch:
if ( index . contain ( 1 ) ) {
console . log ( "ID is already in index" ) ;
}
Sie können jede Methode in ihrer asynchronen Version aufrufen, z. B. index.addAsync
oder index.searchAsync
.
Sie können jeder asynchronen Funktion Rückrufe zuweisen:
index . addAsync ( id , content , function ( ) {
console . log ( "Task Done" ) ;
} ) ;
index . searchAsync ( query , function ( result ) {
console . log ( "Results: " , result ) ;
} ) ;
Oder übergeben Sie keine Callback-Funktion und erhalten Sie stattdessen ein Promise
zurück:
index . addAsync ( id , content ) . then ( function ( ) {
console . log ( "Task Done" ) ;
} ) ;
index . searchAsync ( query ) . then ( function ( result ) {
console . log ( "Results: " , result ) ;
} ) ;
Oder verwenden Sie async
und 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 ) ;
}
Sie können Inhalte an einen vorhandenen Index anhängen wie:
index . append ( id , content ) ;
Dadurch werden die alten indizierten Inhalte nicht überschrieben, wie dies bei der Ausführung index.update(id, content)
der Fall wäre. Beachten Sie, dass index.add(id, content)
im Hintergrund auch eine „Aktualisierung“ durchführt, wenn die ID bereits indiziert wurde.
Angehängte Inhalte haben ihren eigenen Kontext und auch ihre eigene vollständige resolution
. Daher wird die Relevanz nicht gestapelt, sondern erhält einen eigenen Kontext.
Nehmen wir dieses Beispiel:
index . add ( 0 , "some index" ) ;
index . append ( 0 , "some appended content" ) ;
index . add ( 1 , "some text" ) ;
index . append ( 1 , "index appended content" ) ;
Wenn Sie index.search("index")
abfragen, erhalten Sie die Index-ID 1 als ersten Eintrag im Ergebnis, da der Kontext für die angehängten Daten bei Null beginnt (nicht mit dem alten Kontext gestapelt ist) und hier "index „ist der erste Begriff.
Wenn Sie dieses Verhalten nicht wünschen, verwenden Sie einfach den index.add(id, content)
und geben Sie die volle Länge des Inhalts an.
Index. update(id, string)
index . update ( 0 , "Max Miller" ) ;
Index. entfernen(id)
index . remove ( 0 ) ;
Ein Tokenizer zerlegt Wörter/Begriffe in Komponenten oder Teilbestandteile.
Definieren Sie während der Erstellung/Initialisierung einen privaten benutzerdefinierten Tokenizer:
var index = new FlexSearch ( {
tokenize : function ( str ) {
return str . split ( / s-/ / g ) ;
}
} ) ;
Die Tokenizer-Funktion erhält einen String als Parameter und muss ein Array von Strings zurückgeben, die ein Wort oder einen Begriff darstellen. In manchen Sprachen ist jedes Zeichen ein Begriff und auch nicht durch Leerzeichen getrennt.
Stemmer: mehrere sprachliche Mutationen desselben Wortes (z. B. „run“ und „running“)
Filter: eine schwarze Liste von Wörtern, die überhaupt aus der Indexierung herausgefiltert werden sollen (z. B. „und“, „zu“ oder „sein“)
Weisen Sie während der Erstellung/Initialisierung einen privaten benutzerdefinierten Stemmer oder Filter zu:
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"
]
} ) ;
Verwenden eines benutzerdefinierten Filters, z. B.:
var index = new FlexSearch ( {
filter : function ( value ) {
// just add values with length > 1 to the index
return value . length > 1 ;
}
} ) ;
Oder weisen Sie einer Sprache global Stemmer/Filter zu:
Stemmer werden als Objekt (Schlüssel-Wert-Paar) übergeben, Filter als Array.
FlexSearch . registerLanguage ( "us" , {
stemmer : { /* ... */ } ,
filter : [ /* ... */ ]
} ) ;
Oder verwenden Sie einen vordefinierten Wortstammentferner oder Filter Ihrer bevorzugten Sprachen:
< 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 >
...
Jetzt können Sie bei der Erstellung/Initialisierung einen integrierten Stemmer zuweisen:
var index_en = new FlexSearch . Index ( {
language : "en"
} ) ;
var index_de = new FlexSearch . Index ( {
language : "de"
} ) ;
In Node.js sind alle integrierten Sprachpaketdateien verfügbar:
const { Index } = require ( "flexsearch" ) ;
var index_en = new Index ( {
language : "en"
} ) ;
Stellen Sie den Tokenizer bei Verwendung von RTL mindestens auf „reverse“ oder „full“ ein.
Setzen Sie einfach das Feld „rtl“ auf true und verwenden Sie einen kompatiblen Tokenizer:
var index = new Index ( {
encode : str => str . toLowerCase ( ) . split ( / [^a-z]+ / ) ,
tokenize : "reverse" ,
rtl : true
} ) ;
Legen Sie einen benutzerdefinierten Tokenizer fest, der Ihren Anforderungen entspricht, z. B.:
var index = FlexSearch . create ( {
encode : str => str . replace ( / [x00-x7F] / g , "" ) . split ( "" )
} ) ;
Sie können auch eine benutzerdefinierte Encoderfunktion übergeben, um einige sprachliche Transformationen anzuwenden.
index . add ( 0 , "一个单词" ) ;
var results = index . search ( "单词" ) ;
Angenommen, unser Dokument hat eine Datenstruktur wie diese:
{
"id" : 0 ,
"content" : " some text "
}
Alte Syntax FlexSearch v0.6.3 ( wird nicht mehr unterstützt! ):
const index = new Document ( {
doc : {
id : "id" ,
field : [ "content" ]
}
} ) ;
Der Dokumentdeskriptor hat sich leicht geändert, es gibt keine
field
mehr, sondern wird nur eine Ebene höher angewendet, sodasskey
zum Hauptelement der Optionen wird.
Für die neue Syntax wurde das Feld „doc“ in document
“ und das Feld „field“ in index
umbenannt:
const index = new Document ( {
document : {
id : "id" ,
index : [ "content" ]
}
} ) ;
index . add ( {
id : 0 ,
content : "some text"
} ) ;
Die Feld id
beschreibt, wo sich die ID oder der eindeutige Schlüssel in Ihren Dokumenten befindet. Der Standardschlüssel erhält standardmäßig den Wert id
wenn er nicht übergeben wird. Sie können das obige Beispiel also wie folgt kürzen:
const index = new Document ( {
document : {
index : [ "content" ]
}
} ) ;
Der index
enthält eine Liste von Feldern, die in Ihren Dokumenten indiziert werden sollen. Wenn Sie nur ein Feld auswählen, können Sie eine Zeichenfolge übergeben. Wenn auch die Standardschlüssel id
verwendet wird, verkürzt sich dies auf Folgendes:
const index = new Document ( { document : "content" } ) ;
index . add ( { id : 0 , content : "some text" } ) ;
Vorausgesetzt, Sie haben mehrere Felder, können Sie dem Index mehrere Felder hinzufügen:
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" ]
} ) ;
Sie können für jedes Feld benutzerdefinierte Optionen übergeben:
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
}
} ]
} ) ;
Feldoptionen werden vererbt, wenn auch globale Optionen übergeben wurden, z. B.:
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
}
} ]
}
} ) ;
Hinweis: Die Kontextoptionen aus dem Feld „Inhalt“ werden auch von den entsprechenden Feldoptionen geerbt, während diese Feldoptionen von der globalen Option geerbt wurden.
Angenommen, das Dokumentarray sieht komplexer aus (hat verschachtelte Zweige usw.), z. B.:
{
"record" : {
"id" : 0 ,
"title" : " some title " ,
"content" : {
"header" : " some text " ,
"footer" : " some text "
}
}
}
Verwenden Sie dann die durch Doppelpunkte getrennte Notation root:child:child
, um die Hierarchie innerhalb des Dokumentdeskriptors zu definieren:
const index = new Document ( {
document : {
id : "record:id" ,
index : [
"record:title" ,
"record:content:header" ,
"record:content:footer"
]
}
} ) ;
Fügen Sie einfach Felder hinzu, die Sie abfragen möchten. Fügen Sie dem Index keine Felder hinzu, Sie benötigen nur das Ergebnis (aber es wurde keine Abfrage durchgeführt). Zu diesem Zweck können Sie Dokumente unabhängig von ihrem Index speichern (siehe unten).
Wenn Sie ein Feld abfragen möchten, müssen Sie den genauen Schlüssel des Feldes, das Sie im doc
definiert haben, als Feldnamen (mit Doppelpunktsyntax) übergeben:
index . search ( query , {
index : [
"record:title" ,
"record:content:header" ,
"record:content:footer"
]
} ) ;
Gleich wie:
index . search ( query , [
"record:title" ,
"record:content:header" ,
"record:content:footer"
] ) ;
Verwendung feldspezifischer Optionen:
index . search ( [ {
field : "record:title" ,
query : "some query" ,
limit : 100 ,
suggest : true
} , {
field : "record:title" ,
query : "some other query" ,
limit : 100 ,
suggest : true
} ] ) ;
Sie können das gleiche Feld mit unterschiedlichen Abfragen durchsuchen.
Wenn Sie feldspezifische Optionen übergeben, müssen Sie die vollständige Konfiguration für jedes Feld bereitstellen. Sie werden nicht wie der Dokumentdeskriptor vererbt.
Sie müssen zwei Regeln für Ihre Dokumente befolgen:
[ // <-- not allowed as document start!
{
"id" : 0 ,
"title" : "title"
}
]
{
"records" : [ // <-- not allowed when ID or tag lives inside!
{
"id" : 0 ,
"title" : "title"
}
]
}
Hier ein Beispiel für ein unterstütztes komplexes Dokument:
{
"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 " ]
}
]
}
Der entsprechende Dokumentdeskriptor (wenn alle Felder indiziert werden sollen) sieht so aus:
const index = new Document ( {
document : {
id : "meta:id" ,
tag : "meta:tag" ,
index : [
"contents[]:body:title" ,
"contents[]:body:footer" ,
"contents[]:keywords"
]
}
} ) ;
Auch hier müssen Sie bei der Suche dieselbe durch Doppelpunkte getrennte Zeichenfolge aus Ihrer Felddefinition verwenden.
index . search ( query , {
index : "contents[]:body:title"
} ) ;
Dieses Beispiel bricht beide oben genannten Regeln:
[ // <-- 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" ]
}
]
}
]
Sie müssen eine Art Strukturnormalisierung anwenden.
Eine Problemumgehung für eine solche Datenstruktur sieht folgendermaßen aus:
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
} ] ) ;
Sie können die erste Schleife überspringen, wenn Ihre Dokumentdaten nur einen Index als äußeres Array haben.
Fügen Sie dem Index ein Dokument hinzu:
index . add ( {
id : 0 ,
title : "Foo" ,
content : "Bar"
} ) ;
Index mit einem einzelnen Objekt oder einem Array von Objekten aktualisieren:
index . update ( {
data : {
id : 0 ,
title : "Foo" ,
body : {
content : "Bar"
}
}
} ) ;
Entfernen Sie ein einzelnes Objekt oder ein Array von Objekten aus dem Index:
index . remove ( docs ) ;
Wenn die ID bekannt ist, können Sie sie auch einfach (schneller) entfernen:
index . remove ( id ) ;
Im obigen komplexen Beispiel ist das Feld keywords
ein Array, aber hier enthielt das Markup keine Klammern wie keywords[]
. Dadurch wird auch das Array erkannt, aber anstatt jeden Eintrag an einen neuen Kontext anzuhängen, wird das Array zu einer großen Zeichenfolge zusammengefügt und dem Index hinzugefügt.
Der Unterschied beider Arten des Hinzufügens von Array-Inhalten liegt in der Relevanz bei der Suche. Wenn jedes Element eines Arrays über append()
mithilfe der Syntax field[]
zu seinem eigenen Kontext hinzugefügt wird, dann ist die Relevanz des letzten Eintrags gleichzeitig mit dem ersten Eintrag. Wenn Sie die Klammern in der Notation belassen, wird das Array zu einer durch Leerzeichen getrennten Zeichenfolge verbunden. Dabei hat der erste Eintrag die höchste Relevanz, wohingegen der letzte Eintrag die niedrigste Relevanz hat.
Angenommen, die Schlüsselwörter aus dem obigen Beispiel sind nach Relevanz und Beliebtheit vorsortiert, dann möchten Sie diese Reihenfolge beibehalten (relevante Informationen). Fügen Sie zu diesem Zweck keine Klammern in die Notation ein. Andernfalls würden die Einträge in einen neuen Bewertungskontext übernommen (die alte Reihenfolge geht verloren).
Für eine bessere Leistung und einen geringeren Speicherbedarf können Sie auch die Schreibweise mit linken Klammern verwenden. Verwenden Sie es, wenn Sie die Granularität der Relevanz der Einträge nicht benötigen.
Alle Felder durchsuchen:
index . search ( query ) ;
Durchsuchen Sie ein bestimmtes Feld:
index . search ( query , { index : "title" } ) ;
Durchsuchen Sie einen bestimmten Satz von Feldern:
index . search ( query , { index : [ "title" , "content" ] } ) ;
Gleich wie:
index . search ( query , [ "title" , "content" ] ) ;
Übergeben Sie benutzerdefinierte Modifikatoren und Abfragen an jedes Feld:
index . search ( [ {
field : "content" ,
query : "some query" ,
limit : 100 ,
suggest : true
} , {
field : "content" ,
query : "some other query" ,
limit : 100 ,
suggest : true
} ] ) ;
Sie können das gleiche Feld mit unterschiedlichen Abfragen durchsuchen.
Alle verfügbaren Feldsuchoptionen anzeigen.
Schema der Ergebnismenge:
fields[] => { field, result[] => { document }}
Der erste Index ist ein Array von Feldern, auf die die Abfrage angewendet wurde. Jedes dieser Felder verfügt über einen Datensatz (Objekt) mit den beiden Eigenschaften „Feld“ und „Ergebnis“. Das „Ergebnis“ ist ebenfalls ein Array und enthält das Ergebnis für dieses spezielle Feld. Das Ergebnis könnte ein Array von IDs oder eine Anreicherung mit gespeicherten Dokumentdaten sein.
Ein nicht angereicherter Ergebnissatz sieht nun so aus:
[ {
field : "title" ,
result : [ 0 , 1 , 2 ]
} , {
field : "content" ,
result : [ 3 , 4 , 5 ]
} ]
Ein angereicherter Ergebnissatz sieht jetzt so aus:
[ {
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 */ } }
]
} ]
Wenn Sie pluck
anstelle von „field“ verwenden, können Sie explizit nur ein Feld auswählen und erhalten eine flache Darstellung zurück:
index . search ( query , { pluck : "title" , enrich : true } ) ;
[
{ id : 0 , doc : { /* document */ } } ,
{ id : 1 , doc : { /* document */ } } ,
{ id : 2 , doc : { /* document */ } }
]
Diese Ergebnismenge ist ein Ersatz für die „boolesche Suche“. Anstatt Ihre Bool-Logik auf ein verschachteltes Objekt anzuwenden, können Sie Ihre Logik selbst dynamisch auf die Ergebnismenge anwenden. Dies eröffnet enorme Möglichkeiten für die Verarbeitung der Ergebnisse. Daher werden die Ergebnisse aus den Feldern nicht mehr in einem Ergebnis zusammengefasst. Dadurch bleiben einige wichtige Informationen erhalten, wie der Name des Feldes sowie die Relevanz der einzelnen Feldergebnisse, die nicht mehr vermischt wurden.
Bei einer Feldsuche wird standardmäßig eine Abfrage mit der booleschen „Oder“-Logik angewendet. Jedes Feld hat sein eigenes Ergebnis für die gegebene Abfrage.
Es gibt eine Situation, in der die bool
-Eigenschaft weiterhin unterstützt wird. Wenn Sie die standardmäßige „oder“-Logik der Feldsuche in „und“ ändern möchten, z. B.:
index . search ( query , {
index : [ "title" , "content" ] ,
bool : "and"
} ) ;
Sie erhalten lediglich Ergebnisse, die die Abfrage in beiden Feldern enthalten. Das ist es.
Definieren Sie wie beim key
für die ID einfach den Pfad zum Tag:
const index = new Document ( {
document : {
id : "id" ,
tag : "tag" ,
index : "content"
}
} ) ;
index . add ( {
id : 0 ,
tag : "cat" ,
content : "Some content ..."
} ) ;
Ihre Daten können auch mehrere Tags als Array haben:
index . add ( {
id : 1 ,
tag : [ "animal" , "dog" ] ,
content : "Some content ..."
} ) ;
Sie können eine tagspezifische Suche durchführen, indem Sie:
index . search ( query , {
index : "content" ,
tag : "animal"
} ) ;
Dadurch erhalten Sie lediglich ein Ergebnis, das mit dem angegebenen Tag versehen wurde.
Verwenden Sie bei der Suche mehrere Tags:
index . search ( query , {
index : "content" ,
tag : [ "cat" , "dog" ]
} ) ;
Dadurch erhalten Sie Ergebnisse, die mit einem der angegebenen Tags versehen sind.
Mehrere Tags werden standardmäßig als boolesches „oder“ angewendet. Es muss lediglich eines der Tags vorhanden sein.
Dies ist eine weitere Situation, in der die bool
Eigenschaft weiterhin unterstützt wird. Wenn Sie die standardmäßige „oder“-Logik der Tag-Suche in „und“ ändern möchten, z. B.:
index . search ( query , {
index : "content" ,
tag : [ "dog" , "animal" ] ,
bool : "and"
} ) ;
Sie erhalten lediglich Ergebnisse, die beide Tags enthalten (in diesem Beispiel gibt es nur einen Datensatz mit den Tags „Hund“ und „Tier“).
Sie können Ergebnisse auch von einem oder mehreren Tags abrufen, wenn keine Abfrage übergeben wurde:
index . search ( { tag : [ "cat" , "dog" ] } ) ;
In diesem Fall sieht die Ergebnismenge so aus:
[ {
tag : "cat" ,
result : [ /* all cats */ ]
} , {
tag : "dog" ,
result : [ /* all dogs */ ]
} ]
Standardmäßig ist jede Abfrage auf 100 Einträge begrenzt. Unbegrenzte Abfragen führen zu Problemen. Sie müssen das Limit als Option festlegen, um die Größe anzupassen.
Sie können das Limit und den Offset für jede Abfrage festlegen:
index . search ( query , { limit : 20 , offset : 100 } ) ;
Sie können die Größe des Ergebnissatzes nicht im Voraus berechnen. Das ist eine Grenze durch das Design von FlexSearch. Wenn Sie wirklich eine Zählung aller Ergebnisse benötigen, die Sie durchblättern können, weisen Sie einfach ein ausreichend hohes Limit zu, erhalten alle Ergebnisse zurück und wenden Ihren Paging-Offset manuell an (dies funktioniert auch auf der Serverseite). FlexSearch ist schnell genug, dass dies kein Problem darstellt.
Nur ein Dokumentindex kann einen Speicher haben. Sie können einen Dokumentindex anstelle eines flachen Index verwenden, um diese Funktionalität auch dann zu erhalten, wenn Sie nur ID-Inhaltspaare speichern.
Sie können unabhängig definieren, welche Felder indiziert und welche Felder gespeichert werden sollen. Auf diese Weise können Sie Felder indizieren, die nicht in das Suchergebnis aufgenommen werden sollen.
Verwenden Sie keinen Speicher, wenn: 1. ein Array von IDs als Ergebnis gut genug ist oder 2. Sie die Inhalte/Dokumente bereits an anderer Stelle (außerhalb des Index) gespeichert haben.
Wenn das
store
Attribut festgelegt wurde, müssen Sie alle Felder einschließen, die explizit gespeichert werden sollen (wirkt wie eine Whitelist).
Wenn das
store
Attribut nicht festgelegt wurde, wird das Originaldokument als Fallback gespeichert.
Dadurch wird der gesamte Originalinhalt zum Store hinzugefügt:
const index = new Document ( {
document : {
index : "content" ,
store : true
}
} ) ;
index . add ( { id : 0 , content : "some text" } ) ;
Indizierte Dokumente erhalten Sie im Store:
var data = index . get ( 1 ) ;
Sie können Store-Inhalte direkt aktualisieren/ändern, ohne den Index zu ändern, indem Sie:
index . set ( 1 , data ) ;
Um den Store und auch den Index zu aktualisieren, verwenden Sie einfach index.update
, index.add
oder index.append
.
Wenn Sie eine Abfrage durchführen, unabhängig davon, ob es sich um einen Dokumentindex oder einen flachen Index handelt, erhalten Sie immer ein Array von IDs zurück.
Optional können Sie die Abfrageergebnisse automatisch mit gespeicherten Inhalten anreichern, indem Sie:
index . search ( query , { enrich : true } ) ;
Ihre Ergebnisse sehen jetzt so aus:
[ {
id : 0 ,
doc : { /* content from store */ }
} , {
id : 1 ,
doc : { /* content from store */ }
} ]
Dadurch werden nur bestimmte Felder aus einem Dokument zum Speicher hinzugefügt (die ID muss nicht im Speicher aufbewahrt werden):
const index = new Document ( {
document : {
index : "content" ,
store : [ "author" , "email" ]
}
} ) ;
index . add ( id , content ) ;
Sie können unabhängig konfigurieren, was indiziert und was gespeichert werden soll. Es wird dringend empfohlen, dies zu nutzen, wann immer Sie können.
Hier ein nützliches Beispiel für die Konfiguration von Doc und 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 ..."
} ) ;
Sie können den Inhalt abfragen und erhalten stattdessen die gespeicherten Werte zurück:
index . search ( "some content" , { enrich : true } ) ;
Ihre Ergebnisse sehen jetzt so aus:
[ {
field : "content" ,
result : [ {
id : 0 ,
doc : {
author : "Jon Doe" ,
email : "[email protected]" ,
}
} ]
} ]
Sowohl das Feld „Autor“ als auch „E-Mail“ werden nicht indiziert.
Verketten Sie einfach Methoden wie:
var index = FlexSearch . create ( )
. addMatcher ( { 'â' : 'a' } )
. add ( 0 , 'foo' )
. add ( 1 , 'bar' ) ;
index . remove ( 0 ) . update ( 1 , 'foo' ) . add ( 2 , 'foobar' ) ;
Hinweis: Diese Funktion ist aufgrund der erhöhten Speichernutzung standardmäßig deaktiviert. Lesen Sie hier, um weitere Informationen zur Aktivierung zu erhalten.
FlexSearch führt einen neuen Bewertungsmechanismus namens Contextual Search ein, der von Thomas Wilkerling, dem Autor dieser Bibliothek, erfunden wurde. Eine kontextbezogene Suche hebt Abfragen unglaublich auf ein völlig neues Niveau, erfordert aber auch etwas zusätzlichen Speicher (abhängig von der Tiefe ). Die Grundidee dieses Konzepts besteht darin, die Relevanz anhand ihres Kontexts zu begrenzen, anstatt die Relevanz über die gesamte Distanz des entsprechenden Dokuments zu berechnen. Auf diese Weise verbessert die kontextbezogene Suche auch die Ergebnisse relevanter Abfragen bei großen Textdatenmengen.
Erstellen Sie einen Index und verwenden Sie den Standardkontext:
var index = new FlexSearch ( {
tokenize : "strict" ,
context : true
} ) ;
Erstellen Sie einen Index und wenden Sie benutzerdefinierte Optionen für den Kontext an:
var index = new FlexSearch ( {
tokenize : "strict" ,
context : {
resolution : 5 ,
depth : 3 ,
bidirectional : true
}
} ) ;
Nur der Tokenizer „strict“ wird tatsächlich vom Kontextindex unterstützt.
Der Kontextindex erfordert je nach Tiefe zusätzlichen Speicher.
Sie müssen den Cache und sein Limit während der Indexerstellung initialisieren:
const index = new Index ( { cache : 100 } ) ;
const results = index . searchCache ( query ) ;
Ein häufiges Szenario für die Verwendung eines Caches ist eine automatische Vervollständigung oder eine sofortige Suche beim Tippen.
Bei der Übergabe einer Zahl als Limit gleicht der Cache die gespeicherten Einträge automatisch entsprechend ihrer Beliebtheit aus.
Wenn Sie nur „true“ verwenden, ist der Cache unbegrenzt und arbeitet tatsächlich zwei- bis dreimal schneller (da der Balancer nicht ausgeführt werden muss).
Das neue Worker-Modell ab v0.7.0 ist in „Felder“ aus dem Dokument unterteilt (1 Worker = 1 Feldindex). Auf diese Weise wird der Mitarbeiter in die Lage versetzt, Aufgaben (Teilaufgaben) vollständig zu lösen. Der Nachteil dieses Paradigmas besteht darin, dass die Speicherung von Inhalten möglicherweise nicht perfekt ausbalanciert ist (Felder können unterschiedliche Inhaltslängen haben). Andererseits gibt es keinen Hinweis darauf, dass der Speicherausgleich einen Vorteil bringt (alle benötigen insgesamt die gleiche Menge).
Wenn Sie einen Dokumentenindex verwenden, verwenden Sie einfach die 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" }
Wenn Sie eine Feldsuche über alle Felder durchführen, wird diese Aufgabe optimal auf alle Mitarbeiter verteilt, die ihre Teilaufgaben selbstständig lösen können.
Oben haben wir gesehen, dass Dokumente für jedes Feld automatisch einen Worker erstellen. Sie können auch direkt einen WorkerIndex erstellen (genau wie die Verwendung Index
anstelle von Document
).
Verwendung als ES6-Modul:
import WorkerIndex from "./worker/index.js" ;
const index = new WorkerIndex ( options ) ;
index . add ( 1 , "some" )
. add ( 2 , "content" )
. add ( 3 , "to" )
. add ( 4 , "index" ) ;
Oder wenn stattdessen die gebündelte Version verwendet wurde:
var index = new FlexSearch . Worker ( options ) ;
index . add ( 1 , "some" )
. add ( 2 , "content" )
. add ( 3 , "to" )
. add ( 4 , "index" ) ;
Ein solcher WorkerIndex funktioniert im Wesentlichen genauso wie eine erstellte Instanz von Index
.
Ein WorkerIndex unterstützt nur die
async
Variante aller Methoden. Das heißt, wenn Sieindex.search()
für einen WorkerIndex aufrufen, wird dies auch asynchron ausgeführt, genauso wieindex.searchAsync()
.
Das Worker-Modell für Node.js basiert auf „Worker-Threads“ und funktioniert genauso:
const { Document } = require ( "flexsearch" ) ;
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
Oder erstellen Sie eine einzelne Worker-Instanz für einen Nicht-Dokument-Index:
const { Worker } = require ( "flexsearch" ) ;
const index = new Worker ( { options } ) ;
Ein Worker wird immer asynchron arbeiten. Bei einem Abfragemethodenaufruf sollten Sie immer das zurückgegebene Versprechen verarbeiten (z. B. „ await
verwenden) oder eine Rückruffunktion als letzten Parameter übergeben.
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
Alle Anfragen und Unteraufgaben werden parallel ausgeführt (Priorität „alle Aufgaben erledigt“):
index . searchAsync ( query , callback ) ;
index . searchAsync ( query , callback ) ;
index . searchAsync ( query , callback ) ;
Außerdem (priorisieren Sie „alle erledigten Aufgaben“):
index . searchAsync ( query ) . then ( callback ) ;
index . searchAsync ( query ) . then ( callback ) ;
index . searchAsync ( query ) . then ( callback ) ;
Oder wenn Sie nur einen Rückruf haben, wenn alle Anfragen erledigt sind, verwenden Sie einfach Promise.all()
das auch „alle erledigten Aufgaben“ priorisiert:
Promise . all ( [
index . searchAsync ( query ) ,
index . searchAsync ( query ) ,
index . searchAsync ( query )
] ) . then ( callback ) ;
Im Rückruf von Promise.all()
erhalten Sie außerdem jeweils als ersten Parameter für jede von Ihnen eingegebene Abfrage ein Ergebnisarray.
Wenn Sie await
verwenden, können Sie die Reihenfolge priorisieren (priorisieren „erste Aufgabe erledigt“), Anfragen einzeln lösen und die Unteraufgaben einfach parallel bearbeiten:
await index . searchAsync ( query ) ;
await index . searchAsync ( query ) ;
await index . searchAsync ( query ) ;
Gleiches gilt für index.add()
, index.append()
, index.remove()
oder index.update()
. Hier gibt es einen Sonderfall, der von der Bibliothek nicht deaktiviert wird, den Sie jedoch bei der Verwendung von Workern beachten müssen.
Wenn Sie die „synchronisierte“ Version für einen Worker-Index aufrufen:
index . add ( doc ) ;
index . add ( doc ) ;
index . add ( doc ) ;
// contents aren't indexed yet,
// they just queued on the message channel
Natürlich können Sie das tun, aber bedenken Sie, dass der Hauptthread keine zusätzliche Warteschlange für verteilte Worker-Aufgaben hat. Wenn diese in einer langen Schleife ausgeführt werden, werden Inhalte über worker.postMessage()
intern massiv an den Nachrichtenkanal gesendet. Glücklicherweise erledigen der Browser und Node.js solche eingehenden Aufgaben automatisch für Sie (sofern genügend freier RAM verfügbar ist). Wenn Sie die „synchronisierte“ Version für einen Worker-Index verwenden, wird der Inhalt nicht eine Zeile darunter indiziert, da alle Aufrufe standardmäßig als asynchron behandelt werden.
Beim Hinzufügen/Aktualisieren/Entfernen großer Inhaltsmengen zum Index (oder bei hoher Häufigkeit) wird empfohlen, die asynchrone Version zusammen mit
async/await
zu verwenden, um bei langen Prozessen einen geringen Speicherbedarf zu gewährleisten.
Der Export hat sich leicht geändert. Der Export besteht nun aus mehreren kleineren Teilen statt nur einer großen Masse. Sie müssen eine Rückruffunktion übergeben, die zwei Argumente „key“ und „data“ hat. Diese Rückruffunktion wird von jedem Teil aufgerufen, z. B.:
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 ) ;
} ) ;
Das Exportieren von Daten in den localStorage ist keine wirklich gute Praxis, aber wenn die Größe keine Rolle spielt, können Sie es verwenden, wenn Sie möchten. Der Export dient hauptsächlich der Verwendung in Node.js oder zum Speichern von Indizes, die Sie von einem Server an den Client delegieren möchten.
Die Größe des Exports entspricht dem Speicherverbrauch der Bibliothek. Um die Exportgröße zu reduzieren, müssen Sie eine Konfiguration verwenden, die weniger Speicherbedarf hat (verwenden Sie die Tabelle unten, um Informationen über Konfigurationen und deren Speicherzuweisung zu erhalten).
Wenn Ihre Speicherroutine asynchron ausgeführt wird, müssen Sie ein Versprechen zurückgeben:
index . export ( function ( key , data ) {
return new Promise ( function ( resolve ) {
// do the saving as async
resolve ( ) ;
} ) ;
} ) ;
Sie können die zusätzliche Tabelle für die Funktion „Fastupdate“ nicht exportieren. Diese Tabelle besteht aus Referenzen und wenn sie gespeichert werden, werden sie vollständig serialisiert und werden zu groß. Die Bibliothek erledigt diese automatisch für Sie. Beim Importieren von Daten deaktiviert der Index automatisch „Fastupdate“.
Bevor Sie Daten importieren können, müssen Sie zunächst Ihren Index erstellen. Geben Sie für Dokumentindizes denselben Dokumentdeskriptor an, den Sie beim Exportieren der Daten verwendet haben. Diese Konfiguration wird nicht im Export gespeichert.
var index = new Index ( { ... } ) ;
Um die Daten zu importieren, übergeben Sie einfach einen Schlüssel und Daten:
index . import ( key , localStorage . getItem ( key ) ) ;
Sie müssen jeden Schlüssel importieren! Andernfalls funktioniert Ihr Index nicht. Sie müssen die Schlüssel aus dem Export speichern und diese Schlüssel für den Import verwenden (die Reihenfolge der Schlüssel kann unterschiedlich sein).
Dies dient nur zur Demonstration und wird nicht empfohlen, da Ihr localStorage möglicherweise andere Schlüssel enthält, die nicht als Import unterstützt werden:
var keys = Object . keys ( localStorage ) ;
for ( let i = 0 , key ; i < keys . length ; i ++ ) {
key = keys [ i ] ;
index . import ( key , localStorage . getItem ( key ) ) ;
}
Sprachspezifische Definitionen werden in zwei Gruppen unterteilt:
function(string):string[]
boolean
{string: string}
{string: string}
string[]
Der Zeichensatz enthält die Kodierungslogik, die Sprache enthält Stemmer, Stoppwortfilter und Matcher. Mehrere Sprachdefinitionen können denselben Zeichensatz-Encoder verwenden. Außerdem ermöglicht diese Trennung die Verwaltung verschiedener Sprachdefinitionen für spezielle Anwendungsfälle (z. B. Namen, Städte, Dialekte/Slang usw.).
Um eine benutzerdefinierte Sprache im Handumdrehen vollständig zu beschreiben, müssen Sie Folgendes bestehen:
const index = FlexSearch ( {
// mandatory:
encode : ( content ) => [ words ] ,
// optionally:
rtl : false ,
stemmer : { } ,
matcher : { } ,
filter : [ ]
} ) ;
Wenn kein Parameter übergeben wird, wird standardmäßig das Schema latin:default
verwendet.
Feld | Kategorie | Beschreibung |
kodieren | Zeichensatz | Die Encoder-Funktion. Muss ein Array getrennter Wörter (oder eine leere Zeichenfolge) zurückgeben. |
RTL | Zeichensatz | Eine boolesche Eigenschaft, die die Codierung von rechts nach links angibt. |
Filter | Sprache | Filter werden auch als „Stoppwörter“ bezeichnet. Sie filtern Wörter vollständig aus der Indexierung heraus. |
Stemmer | Sprache | Stemmer entfernt Wortedungen und ist eine Art „partielle Normalisierung“. Eine Wortendung wurde gerade gefunden, wenn die Wortlänge größer als der übereinstimmende Teil ist. |
Matcher | Sprache | Matcher ersetzt alle Vorkommen einer bestimmten Zeichenfolge unabhängig von ihrer Position und ist auch eine Art „partielle Normalisierung“. |
Der einfachste Weg, eine zeichensatz-/sprachspezifische Kodierung über Module zuzuweisen, ist:
import charset from "./dist/module/lang/latin/advanced.js" ;
import lang from "./dist/module/lang/en.js" ;
const index = FlexSearch ( {
charset : charset ,
lang : lang
} ) ;
Importieren Sie einfach den Standardexport jedes Moduls und weisen Sie ihn entsprechend zu.
Das vollständig qualifizierte Beispiel von oben ist:
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
} ) ;
Das obige Beispiel ist die Standardschnittstelle, die zumindest aus jedem Zeichensatz/jeder Sprache exportiert wird.
Sie können den Encoder auch direkt definieren und alle anderen Optionen belassen:
import simple from "./dist/module/lang/latin/simple.js" ;
const index = FlexSearch ( {
encode : simple
} ) ;
Sie können einen Zeichensatz zuweisen, indem Sie den Zeichensatz während der Initialisierung übergeben, z. B. charset: "latin"
für den Standard-Zeichensatz-Encoder oder charset: "latin:soundex"
für eine Encoder-Variante.
Sprachdefinitionen (insbesondere Matcher) könnten auch verwendet werden, um Dialekt und Slang einer bestimmten Sprache zu normalisieren.
Sie müssen die Zeichensatz- und/oder Sprachdefinitionen verfügbar machen durch:
flexsearch.bundle.js
enthalten, es sind jedoch keine sprachspezifischen Definitionen enthalten/dist/lang/
befinden (Dateien beziehen sich auf Sprachen, Ordner sind Zeichensätze).Stellen Sie beim Laden von Sprachpaketen sicher, dass die Bibliothek zuvor geladen wurde:
< script src =" dist/flexsearch.light.js " > </ script >
< script src =" dist/lang/latin/default.min.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
Bei Verwendung der vollständigen "Bundle" -Version sind die integrierten lateinischen Encoder bereits enthalten und Sie müssen nur die Sprachdatei laden:
< script src =" dist/flexsearch.bundle.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
Da Sie Pakete als externe Pakete (Nicht-ES6-Moduls) laden, müssen Sie sie durch Verknüpfungen initialisieren:
const index = FlexSearch ( {
charset : "latin:soundex" ,
lang : "en"
} ) ;
Verwenden Sie das
charset:variant
-Notation, um Zeichenset und seine Varianten zuzuweisen. Wenn Sie das Zeichen ohne Variante gerade übergeben, wird automatisch alscharset:default
aufgelöst.
Sie können vorhandene Definitionen auch überschreiben, z. B.:
const index = FlexSearch ( {
charset : "latin" ,
lang : "en" ,
matcher : { }
} ) ;
Die bestehenden Definitionen erweitern keine Standarddefinitionen, sie ersetzen sie.
Wenn Sie eine Definition erweitern möchten, erstellen Sie einfach eine neue Sprachdatei und geben Sie die gesamte Logik ein.
Es ist ziemlich einfach, wenn eine Encoder -Variante verwendet wird:
< 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 >
Bei Verwendung der vollständigen "Bundle" -Version sind die integrierten lateinischen Encoder bereits enthalten und Sie müssen nur die Sprachdatei laden:
< 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"
} ) ;
In FlexSearch können Sie Ihren eigenen Teil -Tokenizer nicht zur Verfügung stellen, da es sich um eine direkte Abhängigkeit von der Kerneinheit handelt. Der eingebaute Tokenizer von FlexSearch spaltet jedes Wort durch verschiedene Muster in Fragmente auf:
Dies ist die Standardpipeline von FlexSearch:
Schauen Sie sich zunächst die Standardpipeline in src/common.js
an. Es ist sehr einfach und unkompliziert. Die Pipeline wird als eine Art von Kontrollinversion verarbeitet, die endgültige Encoder -Implementierung muss Charset und auch sprachspezifische Transformationen behandeln. Diese Problemumgehung hat aus vielen Tests übrig.
Injizieren Sie die Standardpipeline mit z. B.: zB:
this . pipeline (
/* string: */ str . toLowerCase ( ) ,
/* normalize: */ false ,
/* split: */ split ,
/* collapse: */ false
) ;
Verwenden Sie das Pipeline-Schema von oben, um die Iteration und den Unterschied zwischen Vorkodierung und Nachkodierung zu verstehen. Stemmer und Matcher müssen nach Charset -Normalisierung angewendet werden, aber auch vor Sprachtransformationen filtern.
Hier ist ein gutes Beispiel für die Erweiterung von Pipelines: src/lang/latin/extra.js
→ src/lang/latin/advanced.js
→ src/lang/latin/simple.js
.
Suchen Sie nach Ihrer Sprache in src/lang/
, wenn sie vorhanden ist, können Sie Varianten erweitern oder bereitstellen (wie Dialekt/Slang). Wenn die Sprache nicht vorhanden ist, erstellen Sie eine neue Datei und überprüfen Sie, ob eines der vorhandenen Zeichen (z. B. lateinisch) in Ihre Sprache passt. Wenn kein Zeichen vorhanden ist, müssen Sie eine Zeichenierung als Basis für die Sprache bereitstellen.
Ein neuer Charset sollte mindestens:
encode
eine Funktion, die das Zeichen eines übergebenen Textinhalts normalisieren (spezielle Zeichen, linguale Transformationen usw. entfernen) und gibt eine Reihe getrennter Wörter zurück . Auch Stemmer-, Match- oder Stoppword -Filter muss hier angewendet werden. Wenn die Sprache keine Worte hat, stellen Sie sicher, dass Sie etwas Ähnliches angeben, z. B. jedes chinesische Zeichen könnte auch ein "Wort" sein. Geben Sie den gesamten Textinhalt nicht ohne geteilt zurück.rtl
eine boolesche Flagge, die von rechts nach links kodiertGrundsätzlich muss der CharSet nur eine Encoderfunktion zusammen mit einem Indikator für die Codierung von rechts nach links bereitstellen:
export function encode ( str ) { return [ str ] }
export const rtl = false ;
Referenzzeichenfolge: "Björn-Phillipp Mayer"
Abfrage | Standard | einfach | fortschrittlich | Extra |
Björn | Ja | Ja | Ja | Ja |
Björ | Ja | Ja | Ja | Ja |
Bjorn | NEIN | Ja | Ja | Ja |
Bjoern | NEIN | NEIN | Ja | Ja |
Philipp | NEIN | NEIN | Ja | Ja |
Filip | NEIN | NEIN | Ja | Ja |
Björnphillip | NEIN | Ja | Ja | Ja |
Meier | NEIN | NEIN | Ja | Ja |
Björn Meier | NEIN | NEIN | Ja | Ja |
Meier Fhilip | NEIN | NEIN | Ja | Ja |
von Corn Mair | NEIN | NEIN | NEIN | Ja |
(falsch positive) | NEIN | NEIN | NEIN | Ja |
Das Buch "Gulliver's Travels Swift Jonathan 1726" wurde für die folgenden Beispiele vollständig indiziert.
Die am meisten speicheroptimierte aussagekräftige Einstellung vermittelt nur 1,2 MB für das gesamte indexierte Buch! Dies ist wahrscheinlich der winzigste Speicherpreis, den Sie aus einer Suchbibliothek erhalten.
import { encode } from "./lang/latin/extra.js" ;
index = new Index ( {
encode : encode ,
tokenize : "strict" ,
optimize : true ,
resolution : 1 ,
minlength : 3 ,
fastupdate : false ,
context : false
} ) ;
Das Buch "Gulliver's Travels" (Swift Jonathan 1726) wurde für diesen Test vollständig indiziert:
Standardmäßig ist ein lexikalischer Index sehr klein:
depth: 0, bidirectional: 0, resolution: 3, minlength: 0
=> 2,1 MB
Eine höhere Auflösung erhöht die Speicherzuweisung:
depth: 0, bidirectional: 0, resolution: 9, minlength: 0
=> 2,9 mb
Die Verwendung des Kontextindex erhöht die Speicherzuweisung:
depth: 1, bidirectional: 0, resolution: 9, minlength: 0
=> 12,5 MB
Eine höhere kontextbezogene Tiefe erhöht die Speicherzuweisung:
depth: 2, bidirectional: 0, resolution: 9, minlength: 0
=> 21,5 MB
Eine höhere Minenlänge verringert die Speicherzuweisung:
depth: 2, bidirectional: 0, resolution: 9, minlength: 3
=> 19,0 MB
Die Verwendung bidirektional wird die Speicherzuweisung verringern:
depth: 2, bidirectional: 1, resolution: 9, minlength: 3
=> 17,9 MB
Aktivieren Sie die Option "Fastupdate" erhöht die Speicherzuweisung:
depth: 2, bidirectional: 1, resolution: 9, minlength: 3
=> 6,3 mb
Jede Suchbibliothek befindet sich ständig im Wettbewerb mit diesen 4 Immobilien:
FlexSearch bietet Ihnen viele Parameter, mit denen Sie die optimale Balance für Ihren spezifischen Anwendungsfall einstellen können.
Modifikator | Speicherauswirkung * | Leistungsauswirkungen ** | Passende Auswirkungen ** | Bewertungswirkung ** |
Auflösung | +1 (pro Level) | +1 (pro Level) | 0 | +2 (pro Level) |
Tiefe | +4 (pro Level) | -1 (pro Level) | -10 + Tiefe | +10 |
Minlenlänge | -2 (pro Level) | +2 (pro Level) | -3 (pro Level) | +2 (pro Level) |
bidirektional | -2 | 0 | +3 | -1 |
Fastupdate | +1 | +10 (Update, entfernen) | 0 | 0 |
Optimieren: wahr | -7 | -1 | 0 | -3 |
Encoder: "iCase" | 0 | 0 | 0 | 0 |
Encoder: "Einfach" | -2 | -1 | +2 | 0 |
Encoder: "Fortgeschrittene" | -3 | -2 | +4 | 0 |
Encoder: "extra" | -5 | -5 | +6 | 0 |
Encoder: "Soundex" | -6 | -2 | +8 | 0 |
Tokenize: "Strenge" | 0 | 0 | 0 | 0 |
Tokenize: "Vorwärts" | +3 | -2 | +5 | 0 |
Tokenize: "Reverse" | +5 | -4 | +7 | 0 |
Tokenize: "voll" | +8 | -5 | +10 | 0 |
Dokumentindex | +3 (pro Feld) | -1 (pro Feld) | 0 | 0 |
Dokument -Tags | +1 (pro Tag) | -1 (pro Tag) | 0 | 0 |
Geschäft: wahr | +5 (pro Dokument) | 0 | 0 | 0 |
Geschäft: [Felder] | +1 (pro Feld) | 0 | 0 | 0 |
Cache: Richtig | +10 | +10 | 0 | 0 |
Cache: 100 | +1 | +9 | 0 | 0 |
Art der IDs: Nummer | 0 | 0 | 0 | 0 |
Art der IDs: String | +3 | -3 | 0 | 0 |
memory
(Primäroptimieren für den Speicher)performance
(Hauptoptimierung für die Leistung)match
(primär optimieren für die Übereinstimmung)score
(Hauptoptimierung für die Wertung)default
(das standardmäßige ausgeglichene Profil)Diese Profile decken Standard -Anwendungsfälle ab. Es wird empfohlen, benutzerdefinierte Konfiguration anzuwenden, anstatt Profile zu verwenden, um das Beste für Ihre Situation herauszuholen. Jedes Profil könnte weiter auf seine spezifische Aufgabe, z. B. extreme leistungsoptimierte Konfiguration oder extreme Speicher usw. optimiert werden.
Sie können während der Erstellung/Initialisierung des Index eine Voreinstellung übergeben.
Es wird empfohlen, numerische ID -Werte als Referenz zu verwenden, wenn Inhalte zum Index hinzugefügt werden. Die Byte -Länge der bestandenen IDs beeinflusst den Gedächtnisverbrauch erheblich. Wenn dies nicht möglich ist, sollten Sie in Betracht ziehen, um eine Indextabelle zu verwenden und die IDs mit Indizes zuzuordnen. Dies wird insbesondere dann wichtig, wenn kontextbezogene Indizes für eine große Menge an Inhalten verwendet werden.
Wenn Sie können, versuchen Sie, Inhalte nach Kategorien zu trennen, und fügen Sie sie zu einem eigenen Index hinzu, z. B.:
var action = new FlexSearch ( ) ;
var adventure = new FlexSearch ( ) ;
var comedy = new FlexSearch ( ) ;
Auf diese Weise können Sie auch unterschiedliche Einstellungen für jede Kategorie bereitstellen. Dies ist tatsächlich der schnellste Weg, um eine unscharfige Suche durchzuführen.
Um diese Problemumgehung länger zu gestalten, können Sie einen kurzen Helfer verwenden:
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 ) : [ ] ;
}
Fügen Sie dem Index Inhalte hinzu:
add ( 1 , "action" , "Movie Title" ) ;
add ( 2 , "adventure" , "Movie Title" ) ;
add ( 3 , "comedy" , "Movie Title" ) ;
Abfragen ausführen:
var results = search ( "action" , "movie title" ) ; // --> [1]
Split -Indizes nach Kategorien verbessert die Leistung erheblich.
Copyright 2018-2023 Thomas Wilkerling, moderiert von NextApps GmbH
Veröffentlicht unter der Apache 2.0 -Lizenz