Analicemos el próximo FlexSearch v0.8 aquí: #415
Inicio básico • Referencia de API • Índices de documentos • Uso de Worker • Registro de cambios
Puedes ayudarme haciendo una donación personal para mantener vivo este proyecto y también brindar todo el aporte para resolver tus necesidades.
Operaciones de antítesis LLC
Cuando se trata de velocidad de búsqueda bruta, FlexSearch supera a todas las bibliotecas de búsqueda que existen y también proporciona capacidades de búsqueda flexibles como búsqueda de múltiples campos, transformaciones fonéticas o coincidencias parciales.
Dependiendo de las opciones utilizadas, también proporciona el índice con mayor eficiencia de memoria. FlexSearch introduce un nuevo algoritmo de puntuación llamado "índice contextual" basado en una arquitectura de diccionario léxico prepuntuada que en realidad realiza consultas hasta 1.000.000 de veces más rápido en comparación con otras bibliotecas. FlexSearch también le proporciona un modelo de procesamiento asincrónico sin bloqueo, así como trabajadores web para realizar actualizaciones o consultas en el índice en paralelo a través de subprocesos equilibrados dedicados.
Plataformas compatibles:
Comparación de bibliotecas "Los viajes de Gulliver":
Complementos (proyectos externos):
Construir | Archivo | CDN |
flexsearch.bundle.js | Descargar | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.bundle.js |
flexsearch.light.js | Descargar | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.light.js |
flexsearch.compact.js | Descargar | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.compact.js |
flexsearch.es5.js * | Descargar | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.es5.js |
Módulos ES6 | Descargar | La carpeta /dist/module/ de este repositorio de Github |
* El paquete "flexsearch.es5.js" incluye polyfills para compatibilidad con EcmaScript 5.
npm install flexsearch
El paquete Node.js incluye todas las funciones de
flexsearch.bundle.js
.
Característica | flexsearch.bundle.js | flexsearch.compact.js | flexsearch.light.js |
Preajustes | ✓ | ✓ | - |
Búsqueda asíncrona | ✓ | ✓ | - |
Trabajadores (Web + Node.js) | ✓ | - | - |
Índices contextuales | ✓ | ✓ | ✓ |
Documentos de índice (búsqueda de campo) | ✓ | ✓ | - |
Almacén de documentos | ✓ | ✓ | - |
Coincidencia parcial | ✓ | ✓ | ✓ |
Puntuación de relevancia | ✓ | ✓ | ✓ |
Caché equilibrado automáticamente por popularidad | ✓ | - | - |
Etiquetas | ✓ | - | - |
Sugerencias | ✓ | ✓ | - |
Coincidencia fonética | ✓ | ✓ | - |
Conjunto de caracteres/idioma personalizable (Matcher, codificador, tokenizador, Stemmer, filtro, división, RTL) | ✓ | ✓ | ✓ |
Índices de Exportación/Importación | ✓ | - | - |
Tamaño de archivo (gzip) | 6,8 KB | 5,3 KB | 2,9 KB |
Comparación de ejecuciones: Punto de referencia de rendimiento "Los viajes de Gulliver"
Funcionamiento por segundos, cuanto más alto es mejor, excepto en el test "Memoria" en el que cuanto más bajo es mejor.
Rango | Biblioteca | Memoria | Consulta (término único) | Consulta (varios términos) | Consulta (larga) | Consulta (duplicados) | Consulta (no encontrada) |
1 | Búsqueda flexible | 17 | 7084129 | 1586856 | 511585 | 2017142 | 3202006 |
2 | jsii | 27 | 6564 | 158149 | 61290 | 95098 | 534109 |
3 | Vadear | 424 | 20471 | 78780 | 16693 | 225824 | 213754 |
4 | Búsqueda JS | 193 | 8221 | 64034 | 10377 | 95830 | 167605 |
5 | Elasticlunr.js | 646 | 5412 | 7573 | 2865 | 23786 | 13982 |
6 | Búsqueda masiva | 1021 | 3069 | 3141 | 3333 | 3265 | 21825569 |
7 | Minibúsqueda | 24348 | 4406 | 10945 | 72 | 39989 | 17624 |
8 | bm25 | 15719 | 1429 | 789 | 366 | 884 | 1823 |
9 | lunr.js | 2219 | 255 | 271 | 272 | 266 | 267 |
10 | Búsqueda difusa | 157373 | 53 | 38 | 15 | 32 | 43 |
11 | Fusible | 7641904 | 6 | 2 | 1 | 2 | 3 |
Hay 3 tipos de índices:
Index
es un índice plano de alto rendimiento que almacena pares de contenido de identificación.Worker
/ WorkerIndex
también es un índice plano que almacena pares de contenido de identificación pero se ejecuta en segundo plano como un subproceso de trabajo dedicado.Document
es un índice de múltiples campos que puede almacenar documentos JSON complejos (también podrían existir índices de trabajadores).La mayoría de ustedes probablemente necesite solo uno de ellos según su situación.
< 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 >
O vía CDN:
< script src =" https://cdn.jsdelivr.net/gh/nextapps-de/[email protected]/dist/flexsearch.bundle.min.js " > </ script >
AMD/ComúnJS:
var FlexSearch = require ( "./node_modules/flexsearch/dist/flexsearch.bundle.min.js" ) ;
npm install flexsearch
En su código incluya lo siguiente:
const { Index , Document , Worker } = require ( "flexsearch" ) ;
const index = new Index ( options ) ;
const document = new Document ( options ) ;
const worker = new Worker ( options ) ;
O:
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 ) ;
El worker
hereda del tipo Index
y no hereda del tipo Document
. Por lo tanto, un WorkerIndex básicamente funciona como un índice FlexSearch estándar. El soporte para trabajadores en los documentos debe habilitarse simplemente pasando la opción adecuada durante la creación { worker: true }
.
Cada método llamado en un índice
Worker
se trata como asíncrono. Recibirá unaPromise
o, alternativamente, puede proporcionar una función de devolución de llamada como último parámetro.
Métodos globales:
Métodos de índice:
Métodos de índice de trabajador:
Métodos de documento:
* Para cada uno de esos métodos existe un equivalente asincrónico:
Versión asíncrona:
Los métodos asíncronos devolverán una Promise
; alternativamente, puede pasar una función de devolución de llamada como último parámetro.
Los métodos export
y también import
son siempre asíncronos, al igual que todos los métodos que invoca en un índice basado en trabajadores.
FlexSearch es altamente personalizable. Hacer uso de las opciones correctas realmente puede mejorar sus resultados, así como la economía de memoria y el tiempo de consulta.
Opción | Valores | Descripción | Por defecto |
programar | "memoria" "actuación" "fósforo" "puntaje" "por defecto" | El perfil de configuración como acceso directo o como base para sus configuraciones personalizadas. | "por defecto" |
tokenizar | "estricto" "adelante" "contrarrestar" "lleno" | El modo de indexación (tokenizador). Elija uno de los integrados o pase una función de tokenizador personalizada. | "estricto" |
cache | booleano Número | Habilitar/deshabilitar y/o establecer la capacidad de las entradas almacenadas en caché. Al pasar un número como límite, la caché equilibra automáticamente las entradas almacenadas relacionadas con su popularidad . Nota: Cuando se usa "true", el caché no tiene límites y el crecimiento es ilimitado. | FALSO |
resolución | Número | Establece la resolución de puntuación (predeterminada: 9). | 9 |
contexto | booleano Opciones de contexto | Activar/desactivar la indexación contextual. Al pasar "verdadero" como valor, se tomarán los valores predeterminados para el contexto. | FALSO |
optimizar | booleano | Cuando está habilitado, utiliza un flujo de pila optimizado para memoria para el índice. | verdadero |
aumentar | función(arr, str, int) => flotante | Una función de impulso personalizada que se utiliza al indexar contenidos en el índice. La función tiene esta firma: Function(words[], term, index) => Float . Tiene 3 parámetros donde obtienes una matriz de todas las palabras, el término actual y el índice actual donde se coloca el término en la matriz de palabras. Puede aplicar su propio cálculo, por ejemplo, las apariciones de un término y devolver este factor (<1 significa que la relevancia disminuye, >1 significa que la relevancia aumenta).Nota: esta característica actualmente está limitada al usar el tokenizador "estricto" únicamente. | nulo |
Opciones y codificación específicas del idioma: | |||
juego de caracteres | Carga útil del juego de caracteres Cadena (clave) | Proporcione una carga útil de juego de caracteres personalizado o pase una de las claves de los juegos de caracteres integrados. | "latín" |
idioma | Carga útil de idioma Cadena (clave) | Proporcione una carga útil de idioma personalizada o pase el indicador abreviado de idioma (ISO-3166) de los idiomas integrados. | nulo |
codificar | FALSO "por defecto" "simple" "balance" "avanzado" "extra" función(cadena) => [palabras] | El tipo de codificación. Elija una de las funciones integradas o pase una función de codificación personalizada. | "por defecto" |
destilador | FALSO Cadena Función | FALSO | |
filtrar | FALSO Cadena Función | FALSO | |
igualador | FALSO Cadena Función | FALSO | |
Opciones adicionales para índices de documentos: | |||
obrero | booleano | Habilite/Deshabilite y establezca el recuento de subprocesos de trabajo en ejecución. | FALSO |
documento | Descriptor del documento | Incluye definiciones para el índice y almacenamiento de documentos. |
Opción | Valores | Descripción | Por defecto |
resolución | Número | Establece la resolución de puntuación para el contexto (predeterminado: 1). | 1 |
profundidad | FALSO Número | Activa/desactiva la indexación contextual y también establece la distancia contextual de relevancia. La profundidad es el número máximo de palabras/tokens de un término para que se considere relevante. | 1 |
bidireccional | booleano | Establece el resultado de la búsqueda bidireccional. Si está habilitado y el texto fuente contiene "sombrero rojo", se encontrará para las consultas "sombrero rojo" y "sombrero rojo". | verdadero |
Opción | Valores | Descripción | Por defecto |
identificación | Cadena | "identificación"" | |
etiqueta | FALSO Cadena | "etiqueta" | |
índice | Cadena Matriz<Cadena> Matriz<Objeto> | ||
almacenar | booleano Cadena Matriz<Cadena> | FALSO |
Opción | Valores | Descripción | Por defecto |
dividir | FALSO ExpReg Cadena | La regla para dividir palabras cuando se utiliza un tokenizador no personalizado (integrado, por ejemplo, "reenviar"). Utilice una cadena/carácter o utilice una expresión regular (predeterminada: /W+/ ). | /[W_]+/ |
rtl | booleano | Habilita la codificación de derecha a izquierda. | FALSO |
codificar | función(cadena) => [palabras] | La función de codificación personalizada. | /lang/latin/default.js |
Opción | Valores | Descripción |
destilador | FALSO Cadena Función | Deshabilite o pase el indicador taquigráfico del idioma (ISO-3166) o un objeto personalizado. |
filtrar | FALSO Cadena Función | Deshabilite o pase el indicador de taquigrafía del idioma (ISO-3166) o una matriz personalizada. |
igualador | FALSO Cadena Función | Deshabilite o pase el indicador de taquigrafía del idioma (ISO-3166) o una matriz personalizada. |
Opción | Valores | Descripción | Por defecto |
límite | número | Establece el límite de resultados. | 100 |
compensar | número | Aplicar desplazamiento (omitir elementos). | 0 |
sugerir | booleano | Permite sugerencias en los resultados. | FALSO |
Opción | Valores | Descripción | Por defecto |
índice | Cadena Matriz<Cadena> Matriz<Objeto> | Establece los campos del documento en los que se debe buscar. Cuando no se establece ningún campo, se buscarán todos los campos. También se admiten opciones personalizadas por campo. | |
etiqueta | Cadena Matriz<Cadena> | Establece los campos del documento en los que se debe buscar. Cuando no se establece ningún campo, se buscarán todos los campos. También se admiten opciones personalizadas por campo. | FALSO |
enriquecer | booleano | Enriquezca las identificaciones a partir de los resultados con los documentos correspondientes. | FALSO |
booleano | "y" "o" | Establece el operador lógico utilizado al buscar en varios campos o etiquetas. | "o" |
Tokenizer afecta la memoria requerida también como el tiempo de consulta y la flexibilidad de coincidencias parciales. Intente elegir el tokenizador más superior que se ajuste a sus necesidades:
Opción | Descripción | Ejemplo | Factor de memoria (n = longitud de la palabra) |
"estricto" | indexar palabras completas | foobar | * 1 |
"adelante" | indexar palabras de forma incremental en dirección hacia adelante | fo obarfoob ar | * norte |
"contrarrestar" | indexar palabras de forma incremental en ambas direcciones | foob ar fo obar | * 2norte - 1 |
"lleno" | indexar todas las combinaciones posibles | fo oba roob ar | *n* (n-1) |
La codificación afecta la memoria requerida también como el tiempo de consulta y las coincidencias fonéticas. Intente elegir el codificador más superior que se ajuste a sus necesidades o pase un codificador personalizado:
Opción | Descripción | Falsos positivos | Compresión |
FALSO | Desactivar la codificación | No | 0% |
"por defecto" | Codificación que no distingue entre mayúsculas y minúsculas | No | 0% |
"simple" | Codificación que no distingue entre mayúsculas y minúsculas Normalizaciones de conjuntos de caracteres | No | ~ 3% |
"balance" | Codificación que no distingue entre mayúsculas y minúsculas Normalizaciones de conjuntos de caracteres Transformaciones literales | No | ~ 30% |
"avanzado" | Codificación que no distingue entre mayúsculas y minúsculas Normalizaciones de conjuntos de caracteres Transformaciones literales Normalizaciones fonéticas | No | ~ 40% |
"extra" | Codificación que no distingue entre mayúsculas y minúsculas Normalizaciones de conjuntos de caracteres Transformaciones literales Normalizaciones fonéticas Transformaciones de Soundex | Sí | ~ 65% |
función() | Pase codificación personalizada mediante función (cadena): [palabras] |
var index = new Index ( ) ;
Cree un nuevo índice y elija uno de los ajustes preestablecidos:
var index = new Index ( "performance" ) ;
Cree un nuevo índice con opciones personalizadas:
var index = new Index ( {
charset : "latin:extra" ,
tokenize : "reverse" ,
resolution : 9
} ) ;
Cree un nuevo índice y amplíe un ajuste preestablecido con opciones personalizadas:
var index = new FlexSearch ( {
preset : "memory" ,
tokenize : "forward" ,
resolution : 5
} ) ;
Vea todas las opciones personalizadas disponibles.
Cada contenido que deba agregarse al índice necesita una identificación. Cuando su contenido no tiene ID, debe crear uno pasando un índice o recuento u otra cosa como ID (se recomienda encarecidamente un valor del tipo number
). Esas identificaciones son referencias únicas a un contenido determinado. Esto es importante cuando actualiza o agrega contenido a través de ID existentes. Cuando hacer referencia no es una preocupación, simplemente puede usar algo simple como count++
.
Índice. agregar (identificación, cadena)
index . add ( 0 , "John Doe" ) ;
Índice. buscar(cadena | opciones, <límite>, <opciones>)
index . search ( "John" ) ;
Limitar el resultado:
index . search ( "John" , 10 ) ;
Puede verificar si una identificación ya fue indexada por:
if ( index . contain ( 1 ) ) {
console . log ( "ID is already in index" ) ;
}
Puede llamar a cada método en su versión asíncrona, por ejemplo, index.addAsync
o index.searchAsync
.
Puede asignar devoluciones de llamada a cada función asíncrona:
index . addAsync ( id , content , function ( ) {
console . log ( "Task Done" ) ;
} ) ;
index . searchAsync ( query , function ( result ) {
console . log ( "Results: " , result ) ;
} ) ;
O no pase una función de devolución de llamada y obtenga una Promise
en su lugar:
index . addAsync ( id , content ) . then ( function ( ) {
console . log ( "Task Done" ) ;
} ) ;
index . searchAsync ( query ) . then ( function ( result ) {
console . log ( "Results: " , result ) ;
} ) ;
O use async
y 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 ) ;
}
Puede agregar contenidos a un índice existente como:
index . append ( id , content ) ;
Esto no sobrescribirá los contenidos indexados antiguos como lo hará cuando se realice index.update(id, content)
. Tenga en cuenta que index.add(id, content)
también realizará una "actualización" interna cuando la identificación ya se esté indexando.
Los contenidos adjuntos tendrán su propio contexto y también su propia resolution
completa. Por lo tanto, la relevancia no se acumula sino que adquiere su propio contexto.
Tomemos este ejemplo:
index . add ( 0 , "some index" ) ;
index . append ( 0 , "some appended content" ) ;
index . add ( 1 , "some text" ) ;
index . append ( 1 , "index appended content" ) ;
Cuando consulta index.search("index")
obtendrá el ID de índice 1 como primera entrada en el resultado, porque el contexto comienza desde cero para los datos agregados (no está apilado en el contexto anterior) y aquí "index" " es el primer término.
Si no desea este comportamiento, simplemente use el index.add(id, content)
estándar y proporcione la longitud completa del contenido.
Índice. actualizar (id, cadena)
index . update ( 0 , "Max Miller" ) ;
Índice. eliminar (identificación)
index . remove ( 0 ) ;
Un tokenizador divide palabras/términos en componentes o parciales.
Defina un tokenizador personalizado privado durante la creación/inicialización:
var index = new FlexSearch ( {
tokenize : function ( str ) {
return str . split ( / s-/ / g ) ;
}
} ) ;
La función tokenizer obtiene una cadena como parámetro y debe devolver una matriz de cadenas que representan una palabra o término. En algunos idiomas, cada carácter es un término y tampoco está separado por espacios en blanco.
Stemmer: varias mutaciones lingüísticas de la misma palabra (por ejemplo, "correr" y "correr")
Filtro: una lista negra de palabras que se excluirán de la indexación (por ejemplo, "y", "para" o "ser")
Asigne un lematizador o filtro personalizado privado durante la creación/inicialización:
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"
]
} ) ;
Usando un filtro personalizado, por ejemplo:
var index = new FlexSearch ( {
filter : function ( value ) {
// just add values with length > 1 to the index
return value . length > 1 ;
}
} ) ;
O asignar lematizadores/filtros globalmente a un idioma:
Los Stemmer se pasan como un objeto (par clave-valor-par), se filtran como una matriz.
FlexSearch . registerLanguage ( "us" , {
stemmer : { /* ... */ } ,
filter : [ /* ... */ ]
} ) ;
O utilice algún lematizador o filtro predefinido de sus idiomas preferidos:
< 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 >
...
Ahora puede asignar el lematizador incorporado durante la creación/inicialización:
var index_en = new FlexSearch . Index ( {
language : "en"
} ) ;
var index_de = new FlexSearch . Index ( {
language : "de"
} ) ;
En Node.js, todos los archivos de paquetes de idiomas integrados están disponibles:
const { Index } = require ( "flexsearch" ) ;
var index_en = new Index ( {
language : "en"
} ) ;
Configure el tokenizador al menos en "inverso" o "completo" cuando utilice RTL.
Simplemente configure el campo "rtl" en verdadero y use un tokenizador compatible:
var index = new Index ( {
encode : str => str . toLowerCase ( ) . split ( / [^a-z]+ / ) ,
tokenize : "reverse" ,
rtl : true
} ) ;
Configure un tokenizador personalizado que se ajuste a sus necesidades, por ejemplo:
var index = FlexSearch . create ( {
encode : str => str . replace ( / [x00-x7F] / g , "" ) . split ( "" )
} ) ;
También puede pasar una función de codificador personalizada para aplicar algunas transformaciones lingüísticas.
index . add ( 0 , "一个单词" ) ;
var results = index . search ( "单词" ) ;
Suponiendo que nuestro documento tiene una estructura de datos como esta:
{
"id" : 0 ,
"content" : " some text "
}
Sintaxis antigua FlexSearch v0.6.3 ( ¡ya no es compatible! ):
const index = new Document ( {
doc : {
id : "id" ,
field : [ "content" ]
}
} ) ;
El descriptor del documento ha cambiado ligeramente, ya no hay rama
field
; en su lugar, simplemente aplique un nivel superior, por lo quekey
se convierte en un miembro principal de las opciones.
Para la nueva sintaxis, se cambió el nombre del campo "doc" a document
y el campo "campo" a index
:
const index = new Document ( {
document : {
id : "id" ,
index : [ "content" ]
}
} ) ;
index . add ( {
id : 0 ,
content : "some text"
} ) ;
El campo id
describe dónde se encuentra el ID o la clave única dentro de sus documentos. La clave predeterminada obtiene el valor id
de forma predeterminada cuando no se pasa, por lo que puedes acortar el ejemplo anterior a:
const index = new Document ( {
document : {
index : [ "content" ]
}
} ) ;
El index
de miembros tiene una lista de campos que desea indexar en sus documentos. Al seleccionar solo un campo, puede pasar una cadena. Cuando también se utiliza id
de clave predeterminada, esto se reduce a solo:
const index = new Document ( { document : "content" } ) ;
index . add ( { id : 0 , content : "some text" } ) ;
Suponiendo que tiene varios campos, puede agregar varios campos al índice:
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" ]
} ) ;
Puede pasar opciones personalizadas para cada campo:
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
}
} ]
} ) ;
Las opciones de campo se heredan cuando también se pasan opciones globales, por ejemplo:
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
}
} ]
}
} ) ;
Nota: Las opciones de contexto del campo "contenido" también las heredan las opciones de campo correspondientes, mientras que las opciones de este campo fueron heredadas por la opción global.
Supongamos que la matriz de documentos parece más compleja (tiene ramas anidadas, etc.), por ejemplo:
{
"record" : {
"id" : 0 ,
"title" : " some title " ,
"content" : {
"header" : " some text " ,
"footer" : " some text "
}
}
}
Luego use la notación separada por dos puntos root:child:child
para definir la jerarquía dentro del descriptor del documento:
const index = new Document ( {
document : {
id : "record:id" ,
index : [
"record:title" ,
"record:content:header" ,
"record:content:footer"
]
}
} ) ;
Simplemente agregue los campos que desea consultar. No agregue campos al índice, solo necesita el resultado (pero no realizó la consulta). Para ello, puede almacenar documentos independientemente de su índice (lea más abajo).
Cuando desee realizar una consulta a través de un campo, debe pasar la clave exacta del campo que ha definido en el doc
como nombre de campo (con sintaxis de dos puntos):
index . search ( query , {
index : [
"record:title" ,
"record:content:header" ,
"record:content:footer"
]
} ) ;
Igual que:
index . search ( query , [
"record:title" ,
"record:content:header" ,
"record:content:footer"
] ) ;
Usando opciones específicas de campo:
index . search ( [ {
field : "record:title" ,
query : "some query" ,
limit : 100 ,
suggest : true
} , {
field : "record:title" ,
query : "some other query" ,
limit : 100 ,
suggest : true
} ] ) ;
Puedes realizar una búsqueda a través del mismo campo con diferentes consultas.
Al pasar opciones específicas de un campo, debe proporcionar la configuración completa para cada campo. No se heredan como el descriptor del documento.
Debes seguir 2 reglas para tus documentos:
[ // <-- not allowed as document start!
{
"id" : 0 ,
"title" : "title"
}
]
{
"records" : [ // <-- not allowed when ID or tag lives inside!
{
"id" : 0 ,
"title" : "title"
}
]
}
A continuación se muestra un ejemplo de un documento complejo compatible:
{
"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 " ]
}
]
}
El descriptor de documento correspondiente (cuando todos los campos deben estar indexados) tiene este aspecto:
const index = new Document ( {
document : {
id : "meta:id" ,
tag : "meta:tag" ,
index : [
"contents[]:body:title" ,
"contents[]:body:footer" ,
"contents[]:keywords"
]
}
} ) ;
Nuevamente, cuando busque, debe usar la misma cadena separada por dos puntos de la definición de campo.
index . search ( query , {
index : "contents[]:body:title"
} ) ;
Este ejemplo rompe ambas reglas anteriores:
[ // <-- 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" ]
}
]
}
]
Es necesario aplicar algún tipo de normalización de estructura.
Una solución alternativa a dicha estructura de datos es la siguiente:
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
} ] ) ;
Puede omitir el primer bucle cuando los datos de su documento tengan solo un índice como matriz externa.
Agregar un documento al índice:
index . add ( {
id : 0 ,
title : "Foo" ,
content : "Bar"
} ) ;
Actualizar índice con un solo objeto o una serie de objetos:
index . update ( {
data : {
id : 0 ,
title : "Foo" ,
body : {
content : "Bar"
}
}
} ) ;
Elimine un solo objeto o una serie de objetos del índice:
index . remove ( docs ) ;
Cuando se conoce la identificación, también puede simplemente eliminarla (más rápido):
index . remove ( id ) ;
En el ejemplo complejo anterior, el campo keywords
es una matriz, pero aquí el marcado no tenía corchetes como keywords[]
. Eso también detectará la matriz, pero en lugar de agregar cada entrada a un nuevo contexto, la matriz se unirá en una cadena grande y se agregará al índice.
La diferencia entre ambos tipos de agregar contenidos a una matriz es la relevancia al realizar la búsqueda. Al agregar cada elemento de una matriz mediante append()
a su propio contexto usando la sintaxis field[]
, la relevancia de la última entrada coincide con la primera entrada. Cuando dejó los corchetes en la notación, unirá la matriz a una cadena separada por espacios en blanco. Aquí la primera entrada tiene la mayor relevancia, mientras que la última entrada tiene la menor relevancia.
Entonces, suponiendo que las palabras clave del ejemplo anterior estén ordenadas previamente por relevancia según su popularidad, entonces querrás mantener este orden (información de relevancia). Para este propósito no agregue paréntesis a la notación. De lo contrario, se tomarían las entradas en un nuevo contexto de puntuación (el orden anterior se está perdiendo).
También puede dejar la notación entre corchetes para obtener un mejor rendimiento y una menor huella de memoria. Úselo cuando no necesite la granularidad de relevancia de las entradas.
Busque en todos los campos:
index . search ( query ) ;
Buscar a través de un campo específico:
index . search ( query , { index : "title" } ) ;
Busque en un conjunto determinado de campos:
index . search ( query , { index : [ "title" , "content" ] } ) ;
Igual que:
index . search ( query , [ "title" , "content" ] ) ;
Pase modificadores y consultas personalizados a cada campo:
index . search ( [ {
field : "content" ,
query : "some query" ,
limit : 100 ,
suggest : true
} , {
field : "content" ,
query : "some other query" ,
limit : 100 ,
suggest : true
} ] ) ;
Puedes realizar una búsqueda a través del mismo campo con diferentes consultas.
Vea todas las opciones de búsqueda de campo disponibles.
Esquema del conjunto de resultados:
fields[] => { field, result[] => { document }}
El primer índice es una matriz de campos a los que se aplicó la consulta. Cada uno de estos campos tiene un registro (objeto) con 2 propiedades "campo" y "resultado". El "resultado" también es una matriz e incluye el resultado de este campo específico. El resultado podría ser una serie de ID o enriquecido con datos de documentos almacenados.
Un conjunto de resultados no enriquecido ahora tiene el siguiente aspecto:
[ {
field : "title" ,
result : [ 0 , 1 , 2 ]
} , {
field : "content" ,
result : [ 3 , 4 , 5 ]
} ]
Un conjunto de resultados enriquecido ahora tiene el siguiente aspecto:
[ {
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 */ } }
]
} ]
Al utilizar pluck
en lugar de "campo", puede seleccionar explícitamente solo un campo y obtener una representación plana:
index . search ( query , { pluck : "title" , enrich : true } ) ;
[
{ id : 0 , doc : { /* document */ } } ,
{ id : 1 , doc : { /* document */ } } ,
{ id : 2 , doc : { /* document */ } }
]
Este conjunto de resultados reemplaza la "búsqueda booleana". En lugar de aplicar su lógica bool a un objeto anidado, puede aplicar su lógica usted mismo sobre el conjunto de resultados de forma dinámica. Esto abre enormes posibilidades sobre cómo procesar los resultados. Por lo tanto, los resultados de los campos ya no se agrupan en un solo resultado. Eso mantiene información importante, como el nombre del campo y la relevancia de los resultados de cada campo, que ya no se mezclan.
Una búsqueda de campo aplicará una consulta con la lógica booleana "o" de forma predeterminada. Cada campo tiene su propio resultado para la consulta dada.
Hay una situación en la que todavía se admite la propiedad bool
. Cuando desee cambiar la lógica predeterminada "o" del campo de búsqueda a "y", por ejemplo:
index . search ( query , {
index : [ "title" , "content" ] ,
bool : "and"
} ) ;
Simplemente obtendrá resultados que contengan la consulta en ambos campos. Eso es todo.
Al igual que la key
para la ID, simplemente defina la ruta a la etiqueta:
const index = new Document ( {
document : {
id : "id" ,
tag : "tag" ,
index : "content"
}
} ) ;
index . add ( {
id : 0 ,
tag : "cat" ,
content : "Some content ..."
} ) ;
Sus datos también pueden tener múltiples etiquetas como una matriz:
index . add ( {
id : 1 ,
tag : [ "animal" , "dog" ] ,
content : "Some content ..."
} ) ;
Puede realizar una búsqueda específica de etiqueta mediante:
index . search ( query , {
index : "content" ,
tag : "animal"
} ) ;
Esto solo le da el resultado que fue etiquetado con la etiqueta dada.
Utilice varias etiquetas al buscar:
index . search ( query , {
index : "content" ,
tag : [ "cat" , "dog" ]
} ) ;
Esto le proporciona resultados que están etiquetados con una de las etiquetas dadas.
Se aplicarán varias etiquetas como booleano "o" de forma predeterminada. Sólo necesita que una de las etiquetas exista.
Esta es otra situación en la que todavía se admite la propiedad bool
. Cuando desee cambiar la lógica predeterminada "o" de la búsqueda de etiquetas a "y", por ejemplo:
index . search ( query , {
index : "content" ,
tag : [ "dog" , "animal" ] ,
bool : "and"
} ) ;
Simplemente obtendrá resultados que contienen ambas etiquetas (en este ejemplo solo hay un registro que tiene la etiqueta "perro" y "animal").
También puede obtener resultados de una o más etiquetas cuando no se pasó ninguna consulta:
index . search ( { tag : [ "cat" , "dog" ] } ) ;
En este caso, el conjunto de resultados se ve así:
[ {
tag : "cat" ,
result : [ /* all cats */ ]
} , {
tag : "dog" ,
result : [ /* all dogs */ ]
} ]
De forma predeterminada, cada consulta está limitada a 100 entradas. Las consultas ilimitadas generan problemas. Debe establecer el límite como una opción para ajustar el tamaño.
Puede establecer el límite y el desplazamiento para cada consulta:
index . search ( query , { limit : 20 , offset : 100 } ) ;
No se puede contar previamente el tamaño del conjunto de resultados. Ese es un límite por el diseño de FlexSearch. Cuando realmente necesite un recuento de todos los resultados que puede recorrer, simplemente asigne un límite lo suficientemente alto y recupere todos los resultados y aplique su desplazamiento de paginación manualmente (esto también funciona en el lado del servidor). FlexSearch es lo suficientemente rápido como para que esto no sea un problema.
Sólo un índice de documentos puede tener un almacén. Puede utilizar un índice de documento en lugar de un índice plano para obtener esta funcionalidad también cuando solo almacena pares de ID-contenido.
Puede definir de forma independiente qué campos deben indexarse y qué campos deben almacenarse. De esta manera puede indexar campos que no deberían incluirse en el resultado de la búsqueda.
No utilice una tienda cuando: 1. una serie de ID ya que el resultado es lo suficientemente bueno, o 2. ya tiene los contenidos/documentos almacenados en otro lugar (fuera del índice).
Cuando se configuró el atributo
store
, debe incluir todos los campos que deben almacenarse explícitamente (actúa como una lista blanca).
Cuando no se estableció el atributo
store
, el documento original se almacena como respaldo.
Esto agregará todo el contenido original a la tienda:
const index = new Document ( {
document : {
index : "content" ,
store : true
}
} ) ;
index . add ( { id : 0 , content : "some text" } ) ;
Puede obtener documentos indexados en la tienda:
var data = index . get ( 1 ) ;
Puede actualizar/cambiar el contenido de la tienda directamente sin cambiar el índice de la siguiente manera:
index . set ( 1 , data ) ;
Para actualizar la tienda y también actualizar el índice, simplemente use index.update
, index.add
o index.append
.
Cuando realiza una consulta, ya sea que sea un índice de documento o un índice plano, siempre obtendrá una serie de ID.
Opcionalmente, puede enriquecer los resultados de la consulta automáticamente con contenidos almacenados mediante:
index . search ( query , { enrich : true } ) ;
Tus resultados ahora se ven así:
[ {
id : 0 ,
doc : { /* content from store */ }
} , {
id : 1 ,
doc : { /* content from store */ }
} ]
Esto agregará solo campos específicos de un documento a la tienda (no es necesario mantener la identificación en la tienda):
const index = new Document ( {
document : {
index : "content" ,
store : [ "author" , "email" ]
}
} ) ;
index . add ( id , content ) ;
Puede configurar de forma independiente qué se debe indexar y qué se debe almacenar. Se recomienda encarecidamente hacer uso de esto siempre que sea posible.
Aquí un ejemplo útil de configuración de doc y 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 ..."
} ) ;
Puede consultar el contenido y, en su lugar, recuperará los valores almacenados:
index . search ( "some content" , { enrich : true } ) ;
Sus resultados ahora se ven así:
[ {
field : "content" ,
result : [ {
id : 0 ,
doc : {
author : "Jon Doe" ,
email : "[email protected]" ,
}
} ]
} ]
Tanto el campo "autor" como el "correo electrónico" no están indexados.
Simplemente encadene métodos como:
var index = FlexSearch . create ( )
. addMatcher ( { 'â' : 'a' } )
. add ( 0 , 'foo' )
. add ( 1 , 'bar' ) ;
index . remove ( 0 ) . update ( 1 , 'foo' ) . add ( 2 , 'foobar' ) ;
Nota: Esta función está desactivada de forma predeterminada debido al uso extendido de memoria. Lea aquí para obtener más información sobre cómo habilitarlo.
FlexSearch introduce un nuevo mecanismo de puntuación llamado Búsqueda contextual que fue inventado por Thomas Wilkerling, el autor de esta biblioteca. Una búsqueda contextual impulsa increíblemente las consultas a un nivel completamente nuevo, pero también requiere algo de memoria adicional (según la profundidad ). La idea básica de este concepto es limitar la relevancia por su contexto en lugar de calcular la relevancia a lo largo de toda la distancia de su documento correspondiente. De esta manera, la búsqueda contextual también mejora los resultados de las consultas basadas en relevancia sobre una gran cantidad de datos de texto.
Cree un índice y use el contexto predeterminado:
var index = new FlexSearch ( {
tokenize : "strict" ,
context : true
} ) ;
Cree un índice y aplique opciones personalizadas para el contexto:
var index = new FlexSearch ( {
tokenize : "strict" ,
context : {
resolution : 5 ,
depth : 3 ,
bidirectional : true
}
} ) ;
Sólo el tokenizador "estricto" es realmente compatible con el índice contextual.
El índice contextual requiere una cantidad adicional de memoria según la profundidad.
Debe inicializar el caché y su límite durante la creación del índice:
const index = new Index ( { cache : 100 } ) ;
const results = index . searchCache ( query ) ;
Un escenario común para usar un caché es una búsqueda automática o instantánea al escribir.
Al pasar un número como límite, el caché equilibra automáticamente las entradas almacenadas relacionadas con su popularidad.
Cuando solo se usa "true", el caché es ilimitado y funciona entre 2 y 3 veces más rápido (porque no es necesario ejecutar el equilibrador).
El nuevo modelo de trabajador de v0.7.0 se divide en "campos" del documento (1 trabajador = 1 índice de campo). De esta manera el trabajador puede resolver tareas (subtareas) por completo. La desventaja de este paradigma es que es posible que no hayan estado perfectamente equilibrados en el almacenamiento de contenidos (los campos pueden tener diferentes longitudes de contenido). Por otro lado, no hay indicios de que equilibrar el almacenamiento proporcione alguna ventaja (todos requieren la misma cantidad en total).
Cuando utilice un índice de documento, simplemente aplique la opción "trabajador":
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" }
Cuando realiza una búsqueda de campo en todos los campos, esta tarea se equilibra perfectamente entre todos los trabajadores, que pueden resolver sus subtareas de forma independiente.
Arriba hemos visto que los documentos crearán trabajadores automáticamente para cada campo. También puede crear un WorkerIndex directamente (igual que usar Index
en lugar de Document
).
Usar como módulo 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" ) ;
O cuando se usó la versión incluida en su lugar:
var index = new FlexSearch . Worker ( options ) ;
index . add ( 1 , "some" )
. add ( 2 , "content" )
. add ( 3 , "to" )
. add ( 4 , "index" ) ;
Un WorkerIndex de este tipo funciona prácticamente igual que una instancia creada de Index
.
Un WorkerIndex solo admite la variante
async
de todos los métodos. Eso significa que cuando llamasindex.search()
en un WorkerIndex, esto también funcionará de forma asíncrona de la misma manera que lo haráindex.searchAsync()
.
El modelo de trabajo para Node.js se basa en "hilos de trabajo" y funciona exactamente de la misma manera:
const { Document } = require ( "flexsearch" ) ;
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
O cree una única instancia de trabajador para un índice que no sea un documento:
const { Worker } = require ( "flexsearch" ) ;
const index = new Worker ( { options } ) ;
Un trabajador siempre funcionará como asíncrono. En una llamada a un método de consulta, siempre debe manejar la promesa devuelta (por ejemplo, usar await
) o pasar una función de devolución de llamada como último parámetro.
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
Todas las solicitudes y subtareas se ejecutarán en paralelo (priorice "todas las tareas completadas"):
index . searchAsync ( query , callback ) ;
index . searchAsync ( query , callback ) ;
index . searchAsync ( query , callback ) ;
Además (priorice "todas las tareas completadas"):
index . searchAsync ( query ) . then ( callback ) ;
index . searchAsync ( query ) . then ( callback ) ;
index . searchAsync ( query ) . then ( callback ) ;
O cuando tenga solo una devolución de llamada cuando se hayan completado todas las solicitudes, simplemente use Promise.all()
que también prioriza "todas las tareas completadas":
Promise . all ( [
index . searchAsync ( query ) ,
index . searchAsync ( query ) ,
index . searchAsync ( query )
] ) . then ( callback ) ;
Dentro de la devolución de llamada de Promise.all()
también obtendrá una serie de resultados como primer parámetro respectivamente para cada consulta que realice.
Al usar await
puede priorizar el orden (priorizar "primera tarea completada") y resolver las solicitudes una por una y simplemente procesar las subtareas en paralelo:
await index . searchAsync ( query ) ;
await index . searchAsync ( query ) ;
await index . searchAsync ( query ) ;
Lo mismo para index.add()
, index.append()
, index.remove()
o index.update()
. Aquí hay un caso especial que la biblioteca no deshabilita, pero que debes tener en cuenta al usar Workers.
Cuando llamas a la versión "sincronizada" en un índice de trabajadores:
index . add ( doc ) ;
index . add ( doc ) ;
index . add ( doc ) ;
// contents aren't indexed yet,
// they just queued on the message channel
Por supuesto, puede hacerlo, pero tenga en cuenta que el hilo principal no tiene una cola adicional para tareas de trabajadores distribuidos. Ejecutarlos en un bucle largo envía contenido masivamente al canal de mensajes a través de worker.postMessage()
internamente. Afortunadamente, el navegador y Node.js manejarán dichas tareas entrantes automáticamente (siempre que haya suficiente RAM libre disponible). Cuando se utiliza la versión "sincronizada" en un índice de trabajadores, el contenido no se indexa una línea más abajo, porque todas las llamadas se tratan como asíncronas de forma predeterminada.
Al agregar/actualizar/eliminar grandes cantidades de contenido al índice (o alta frecuencia), se recomienda usar la versión asíncrona junto con
async/await
para mantener un consumo de memoria bajo durante procesos largos.
La exportación ha cambiado ligeramente. La exportación ahora consta de varias partes más pequeñas, en lugar de sólo un gran volumen. Debe pasar una función de devolución de llamada que tenga 2 argumentos "clave" y "datos". Esta función de devolución de llamada es llamada por cada parte, por ejemplo:
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 ) ;
} ) ;
Exportar datos al almacenamiento local no es realmente una buena práctica, pero si el tamaño no es una preocupación, úselo si lo desea. La exportación existe principalmente para su uso en Node.js o para almacenar índices que desea delegar de un servidor al cliente.
El tamaño de la exportación corresponde al consumo de memoria de la biblioteca. Para reducir el tamaño de la exportación, debe usar una configuración que ocupe menos memoria (use la tabla en la parte inferior para obtener información sobre las configuraciones y su asignación de memoria).
Cuando su rutina de guardado se ejecuta de forma asincrónica, debe devolver una promesa:
index . export ( function ( key , data ) {
return new Promise ( function ( resolve ) {
// do the saving as async
resolve ( ) ;
} ) ;
} ) ;
No puede exportar la tabla adicional para la función "actualización rápida". Estas tablas existen de referencias y, cuando se almacenan, se serializan por completo y se vuelven demasiado grandes. La biblioteca los manejará automáticamente. Al importar datos, el índice desactiva automáticamente la "actualización rápida".
Antes de poder importar datos, primero debe crear su índice. Para los índices de documentos, proporcione el mismo descriptor de documento que utilizó al exportar los datos. Esta configuración no se almacena en la exportación.
var index = new Index ( { ... } ) ;
Para importar los datos simplemente pasa una clave y datos:
index . import ( key , localStorage . getItem ( key ) ) ;
¡Necesitas importar todas las claves! De lo contrario, su índice no funciona. Debe almacenar las claves de la exportación y usarlas para la importación (el orden de las claves puede variar).
Esto es solo para demostración y no se recomienda, porque es posible que tenga otras claves en su almacenamiento local que no sean compatibles como importación:
var keys = Object . keys ( localStorage ) ;
for ( let i = 0 , key ; i < keys . length ; i ++ ) {
key = keys [ i ] ;
index . import ( key , localStorage . getItem ( key ) ) ;
}
Las definiciones específicas del idioma se dividen en dos grupos:
function(string):string[]
boolean
{string: string}
{string: string}
string[]
El juego de caracteres contiene la lógica de codificación, el lenguaje contiene lematizadores, filtros de palabras vacías y comparadores. Varias definiciones de idiomas pueden utilizar el mismo codificador de juego de caracteres. Además, esta separación le permite administrar diferentes definiciones de idiomas para casos de uso especiales (por ejemplo, nombres, ciudades, dialectos/jerga, etc.).
Para describir completamente un idioma personalizado sobre la marcha, debe aprobar:
const index = FlexSearch ( {
// mandatory:
encode : ( content ) => [ words ] ,
// optionally:
rtl : false ,
stemmer : { } ,
matcher : { } ,
filter : [ ]
} ) ;
Cuando no se pasa ningún parámetro, utiliza el esquema latin:default
de forma predeterminada.
Campo | Categoría | Descripción |
codificar | juego de caracteres | La función del codificador. Tiene que devolver una serie de palabras separadas (o una cadena vacía). |
RTL | juego de caracteres | Una propiedad booleana que indica codificación de derecha a izquierda. |
filtrar | idioma | Los filtros también se conocen como "palabras vacías", filtran completamente las palabras para que no se indexen. |
destilador | idioma | Stemmer elimina las terminaciones de palabras y es una especie de "normalización parcial". Una terminación de palabra simplemente coincide cuando la longitud de la palabra es mayor que el parcial coincidente. |
igualador | idioma | Matcher reemplaza todas las apariciones de una cadena determinada independientemente de su posición y también es una especie de "normalización parcial". |
La forma más sencilla de asignar codificación específica de juego de caracteres/idioma a través de módulos es:
import charset from "./dist/module/lang/latin/advanced.js" ;
import lang from "./dist/module/lang/en.js" ;
const index = FlexSearch ( {
charset : charset ,
lang : lang
} ) ;
Simplemente importe la exportación predeterminada de cada módulo y asígnelas en consecuencia.
El ejemplo completo completo de arriba es:
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
} ) ;
El ejemplo anterior es la interfaz estándar que al menos se exporta desde cada juego de caracteres/idioma.
También puedes definir el codificador directamente y dejar todas las demás opciones:
import simple from "./dist/module/lang/latin/simple.js" ;
const index = FlexSearch ( {
encode : simple
} ) ;
Puede asignar un juego de caracteres pasándolo durante la inicialización, por ejemplo, charset: "latin"
para el codificador de juego de caracteres predeterminado o charset: "latin:soundex"
para una variante del codificador.
Las definiciones de idiomas (especialmente comparadores) también podrían usarse para normalizar el dialecto y la jerga de un idioma específico.
Debe hacer que el juego de caracteres y/o las definiciones de idioma estén disponibles mediante:
flexsearch.bundle.js
de forma predeterminada, pero no se incluyen definiciones específicas del idioma./dist/lang/
(los archivos se refieren a idiomas, las carpetas son conjuntos de caracteres)Al cargar paquetes de idiomas, asegúrese de que la biblioteca se haya cargado antes:
< script src =" dist/flexsearch.light.js " > </ script >
< script src =" dist/lang/latin/default.min.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
Cuando se usa la versión completa del "paquete", los codificadores latinos incorporados ya están incluidos y solo tiene que cargar el archivo de idioma:
< script src =" dist/flexsearch.bundle.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
Debido a que carga paquetes como paquetes externos (módulos no 6), debe inicializarlos mediante accesos directos:
const index = FlexSearch ( {
charset : "latin:soundex" ,
lang : "en"
} ) ;
Utilice la notación
charset:variant
para asignar Charset y sus variantes. Cuando solo pasar el charset sin una variante se resolverá automáticamente comocharset:default
.
También puede anular las definiciones existentes, por ejemplo:
const index = FlexSearch ( {
charset : "latin" ,
lang : "en" ,
matcher : { }
} ) ;
Las definiciones aprobadas no extenderán las definiciones predeterminadas, las reemplazarán.
Cuando le gusta extender una definición, simplemente cree un nuevo archivo de idioma y ponga toda la lógica.
Es bastante sencillo cuando se usa una variante del codificador:
< 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 >
Cuando se usa la versión completa del "paquete", los codificadores latinos incorporados ya están incluidos y solo tiene que cargar el archivo de idioma:
< 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"
} ) ;
En FlexSearch, no puede proporcionar su propio tokenizador parcial, porque es una dependencia directa de la unidad central. El tokenizador incorporado de FlexSearch divide cada palabra en fragmentos por diferentes patrones:
Esta es la tubería predeterminada proporcionada por FlexSearch:
Al principio, eche un vistazo a la tubería predeterminada en src/common.js
. Es muy simple y directo. La tubería se procesará como algún tipo de inversión de control, la implementación final del codificador debe manejar Charset y también transformaciones específicas del lenguaje. Esta solución ha quedado de muchas pruebas.
Inyectar la tubería predeterminada por EG:
this . pipeline (
/* string: */ str . toLowerCase ( ) ,
/* normalize: */ false ,
/* split: */ split ,
/* collapse: */ false
) ;
Use el esquema de la tubería de arriba para comprender la iteración y la diferencia de pre-codificación y post-codificación. Stemmer y Matchers deben aplicarse después de la normalización del charset, pero antes de las transformaciones del lenguaje, los filtros también.
Aquí hay un buen ejemplo de extensión de tuberías: src/lang/latin/extra.js
→ src/lang/latin/advanced.js
→ src/lang/latin/simple.js
.
Busque su idioma en src/lang/
, si existe, puede extender o proporcionar variantes (como dialecto/jerga). Si el idioma no existe, cree un archivo nuevo y verifique si alguno de los Charsets existentes (por ejemplo, latín) se ajusta a su idioma. Cuando no existe un charset, debe proporcionar un charset como base para el idioma.
Un nuevo charset debe proporcionar al menos:
encode
una función que normalice el carro de un contenido de texto aprobado (elimine las características especiales, transformaciones linguales, etc.) y devuelve una variedad de palabras separadas . También se debe aplicar aquí Stemmer, Matcher o Stopword Filter. Cuando el idioma no tiene palabras, asegúrese de proporcionar algo similar, por ejemplo, cada signo chino también podría ser una "palabra". No devuelva el contenido de texto completo sin dividir.rtl
una bandera booleana que indica la codificación de derecha a izquierdaBásicamente, el charset necesita solo proporcionar una función de codificador junto con un indicador para la codificación de derecha a izquierda:
export function encode ( str ) { return [ str ] }
export const rtl = false ;
Cadena de referencia: "Björn-Phillipp Mayer"
Consulta | por defecto | simple | avanzado | extra |
björn | Sí | Sí | Sí | Sí |
björ | Sí | Sí | Sí | Sí |
björn | No | Sí | Sí | Sí |
bJOERN | No | No | Sí | Sí |
felipe | No | No | Sí | Sí |
filip | No | No | Sí | Sí |
björnphillip | No | Sí | Sí | Sí |
meier | No | No | Sí | Sí |
björn meier | No | No | Sí | Sí |
meier fhilip | No | No | Sí | Sí |
Byorn Mair | No | No | No | Sí |
(falsos positivos) | No | No | No | Sí |
El libro "Gulliver's Travels Swift Jonathan 1726" estaba completamente indexado para los ejemplos a continuación.
¡La configuración significativa más optimizada de la memoria asignará solo 1.2 MB para todo el libro indexado! Esta es probablemente la huella de memoria más pequeña que obtendrá de una biblioteca de búsqueda.
import { encode } from "./lang/latin/extra.js" ;
index = new Index ( {
encode : encode ,
tokenize : "strict" ,
optimize : true ,
resolution : 1 ,
minlength : 3 ,
fastupdate : false ,
context : false
} ) ;
El libro "Gulliver's Travels" (Swift Jonathan 1726) estaba completamente indexado para esta prueba:
Por defecto, un índice léxico es muy pequeño:
depth: 0, bidirectional: 0, resolution: 3, minlength: 0
=> 2.1 MB
Una resolución más alta aumentará la asignación de memoria:
depth: 0, bidirectional: 0, resolution: 9, minlength: 0
=> 2.9 Mb
El uso del índice contextual aumentará la asignación de memoria:
depth: 1, bidirectional: 0, resolution: 9, minlength: 0
=> 12.5 Mb
Una mayor profundidad contextual aumentará la asignación de memoria:
depth: 2, bidirectional: 0, resolution: 9, minlength: 0
=> 21.5 Mb
Una MinLength más alta disminuirá la asignación de memoria:
depth: 2, bidirectional: 0, resolution: 9, minlength: 3
=> 19.0 MB
El uso de bidireccional disminuirá la asignación de memoria:
depth: 2, bidirectional: 1, resolution: 9, minlength: 3
=> 17.9 MB
Habilite la opción "FastUpdate" aumentará la asignación de memoria:
depth: 2, bidirectional: 1, resolution: 9, minlength: 3
=> 6.3 Mb
Cada biblioteca de búsqueda está constantemente en competencia con estas 4 propiedades:
FlexSearch le proporciona muchos parámetros que puede usar para ajustar el equilibrio óptimo para su caso de uso específico.
Modificador | Impacto de la memoria * | Impacto del rendimiento ** | Impacto coincidente ** | Impacto de puntuación ** |
resolución | +1 (por nivel) | +1 (por nivel) | 0 | +2 (por nivel) |
profundidad | +4 (por nivel) | -1 (por nivel) | -10 + profundidad | +10 |
longitud mínima | -2 (por nivel) | +2 (por nivel) | -3 (por nivel) | +2 (por nivel) |
bidireccional | -2 | 0 | +3 | -1 |
fastupdate | +1 | +10 (actualizar, eliminar) | 0 | 0 |
Optimizar: verdadero | -7 | -1 | 0 | -3 |
codificador: "icase" | 0 | 0 | 0 | 0 |
Encoder: "Simple" | -2 | -1 | +2 | 0 |
codificador: "avanzado" | -3 | -2 | +4 | 0 |
codificador: "extra" | -5 | -5 | +6 | 0 |
Encoder: "SoundEx" | -6 | -2 | +8 | 0 |
Tokenize: "estricto" | 0 | 0 | 0 | 0 |
Tokenize: "Forward" | +3 | -2 | +5 | 0 |
Tokenize: "reverso" | +5 | -4 | +7 | 0 |
Tokenize: "lleno" | +8 | -5 | +10 | 0 |
índice de documentos | +3 (por campo) | -1 (por campo) | 0 | 0 |
etiquetas de documento | +1 (por etiqueta) | -1 (por etiqueta) | 0 | 0 |
Almacén: Verdadero | +5 (por documento) | 0 | 0 | 0 |
Tienda: [Campos] | +1 (por campo) | 0 | 0 | 0 |
Cache: verdadero | +10 | +10 | 0 | 0 |
Cache: 100 | +1 | +9 | 0 | 0 |
Tipo de IDS: Número | 0 | 0 | 0 | 0 |
Tipo de IDS: cadena | +3 | -3 | 0 | 0 |
memory
(Optimización primaria para la memoria)performance
(Optimización primaria para el rendimiento)match
(Optimización primaria para la coincidencia)score
(optimización primaria para la puntuación)default
(el perfil equilibrado predeterminado)Estos perfiles cubren casos de uso estándar. Se recomienda aplicar la configuración personalizada en lugar de usar perfiles para obtener lo mejor para su situación. Cada perfil podría optimizarse aún más a su tarea específica, por ejemplo, la configuración optimizada de rendimiento extremo o la memoria extrema, etc.
Puede pasar un preajuste durante la creación/inicialización del índice.
Se recomienda utilizar los valores de ID numéricos como referencia al agregar contenido al índice. La longitud de los bytes de las ID pasadas influye significativamente en el consumo de memoria. Si esto no es posible, debe considerar usar una tabla de índice y asignar los ID con índices, esto se vuelve importante, especialmente cuando se usa índices contextuales en una gran cantidad de contenido.
Siempre que pueda, intente dividir el contenido por categorías y agréguelos a su propio índice, por ejemplo:
var action = new FlexSearch ( ) ;
var adventure = new FlexSearch ( ) ;
var comedy = new FlexSearch ( ) ;
De esta manera, también puede proporcionar diferentes configuraciones para cada categoría. Esta es en realidad la forma más rápida de realizar una búsqueda difusa.
Para que esta solución sea más extensible, puede usar un ayudante corto:
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 ) : [ ] ;
}
Agregue contenido al índice:
add ( 1 , "action" , "Movie Title" ) ;
add ( 2 , "adventure" , "Movie Title" ) ;
add ( 3 , "comedy" , "Movie Title" ) ;
Realizar consultas:
var results = search ( "action" , "movie title" ) ; // --> [1]
Los índices divididos por categorías mejoran significativamente el rendimiento.
Copyright 2018-2023 Thomas Wilkerling, organizado por NextApps GMBH
Liberado bajo la licencia Apache 2.0