Vamos discutir o próximo FlexSearch v0.8 aqui: #415
Início Básico • Referência de API • Índices de Documentos • Usando Worker • Changelog
Você pode me ajudar fazendo uma doação pessoal para manter este projeto vivo e também para dar toda a contribuição para solucionar suas necessidades.
Operações de Antítese LLC
Quando se trata de velocidade de pesquisa bruta, o FlexSearch supera todas as bibliotecas de pesquisa existentes e também fornece recursos de pesquisa flexíveis, como pesquisa em vários campos, transformações fonéticas ou correspondência parcial.
Dependendo das opções usadas, ele também fornece o índice com maior eficiência de memória. FlexSearch introduz um novo algoritmo de pontuação chamado "índice contextual" baseado em uma arquitetura de dicionário lexical pré-pontuada que realmente executa consultas até 1.000.000 de vezes mais rápido em comparação com outras bibliotecas. FlexSearch também fornece um modelo de processamento assíncrono sem bloqueio, bem como web workers para realizar quaisquer atualizações ou consultas no índice em paralelo por meio de threads balanceados dedicados.
Plataformas suportadas:
Comparação de bibliotecas "As Viagens de Gulliver":
Plugins (projetos externos):
Construir | Arquivo | CDN |
flexsearch.bundle.js | Download | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.bundle.js |
flexsearch.light.js | Download | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.light.js |
flexsearch.compact.js | Download | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.compact.js |
flexsearch.es5.js * | Download | https://rawcdn.githack.com/nextapps-de/flexsearch/0.7.31/dist/flexsearch.es5.js |
Módulos ES6 | Download | A pasta /dist/module/ deste repositório Github |
* O pacote "flexsearch.es5.js" inclui polyfills para suporte ao EcmaScript 5.
npm install flexsearch
O pacote Node.js inclui todos os recursos de
flexsearch.bundle.js
.
Recurso | flexsearch.bundle.js | flexsearch.compact.js | flexsearch.light.js |
Predefinições | ✓ | ✓ | - |
Pesquisa assíncrona | ✓ | ✓ | - |
Trabalhadores (Web + Node.js) | ✓ | - | - |
Índices Contextuais | ✓ | ✓ | ✓ |
Indexar documentos (pesquisa de campo) | ✓ | ✓ | - |
Armazenamento de documentos | ✓ | ✓ | - |
Correspondência parcial | ✓ | ✓ | ✓ |
Pontuação de Relevância | ✓ | ✓ | ✓ |
Cache balanceado automaticamente por popularidade | ✓ | - | - |
Etiquetas | ✓ | - | - |
Sugestões | ✓ | ✓ | - |
Correspondência Fonética | ✓ | ✓ | - |
Charset/idioma personalizável (Matcher, Encoder, Tokenizer, Stemmer, Filter, Split, RTL) | ✓ | ✓ | ✓ |
Índices de exportação/importação | ✓ | - | - |
Tamanho do arquivo (gzip) | 6,8kb | 5,3 KB | 2,9kb |
Comparação de execução: benchmark de desempenho "As Viagens de Gulliver"
Operação por segundos, maior é melhor, exceto o teste “Memória” em que menor é melhor.
Classificação | Biblioteca | Memória | Consulta (termo único) | Consulta (vários termos) | Consulta (longa) | Consulta (Duplicados) | Consulta (não encontrada) |
1 | FlexSearch | 17 | 7084129 | 1586856 | 511585 | 2017142 | 3202006 |
2 | JSii | 27 | 6564 | 158149 | 61290 | 95098 | 534109 |
3 | Wade | 424 | 20471 | 78780 | 16693 | 225824 | 213754 |
4 | Pesquisa JS | 193 | 8221 | 64034 | 10377 | 95830 | 167605 |
5 | Elasticlunr.js | 646 | 5412 | 7573 | 2865 | 23786 | 13982 |
6 | Pesquisa em massa | 1021 | 3069 | 3141 | 3333 | 3265 | 21825569 |
7 | Minipesquisa | 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 | Fusível | 7641904 | 6 | 2 | 1 | 2 | 3 |
Existem 3 tipos de índices:
Index
é um índice plano de alto desempenho que armazena pares id-conteúdo.Worker
/ WorkerIndex
também é um índice simples que armazena pares de id-conteúdo, mas é executado em segundo plano como um thread de trabalho dedicado.Document
é um índice de vários campos que pode armazenar documentos JSON complexos (também pode existir índices de trabalho).A maioria de vocês provavelmente precisará de apenas um deles de acordo com o seu cenário.
< script src =" node_modules/flexsearch/dist/flexsearch.bundle.min.js " > </ script >
< script >
// FlexSearch is available on window.FlexSearch
// Access FlexSearch static methods via bundled export (static class methods of FlexSearch)
const index = FlexSearch . Index ( options ) ;
const document = FlexSearch . Document ( options ) ;
const worker = FlexSearch . Worker ( options ) ;
</ script >
< script type =" module " >
// FlexSearch is NOT available on window.FlexSearch
// Access FlexSearch static methods by importing them explicitly
import Index from "./node_modules/flexsearch/dist/module/index" ;
import Document from "./node_modules/flexsearch/dist/module/document" ;
import Worker from "./node_modules/flexsearch/dist/module/worker" ;
const index = new Index ( options ) ;
const document = new Document ( options ) ;
const worker = new Worker ( options ) ;
</ script >
< script type =" module " >
// FlexSearch is NOT available on window.FlexSearch
// Access FlexSearch static methods via bundled export (static class methods of FlexSearch)
import FlexSearch from "./node_modules/flexsearch/dist/flexsearch.bundle.module.min.js" ;
const index = FlexSearch . Index ( options ) ;
const document = FlexSearch . Document ( options ) ;
const worker = FlexSearch . Worker ( options ) ;
</ script >
Ou via CDN:
< script src =" https://cdn.jsdelivr.net/gh/nextapps-de/[email protected]/dist/flexsearch.bundle.min.js " > </ script >
AMD/CommonJS:
var FlexSearch = require ( "./node_modules/flexsearch/dist/flexsearch.bundle.min.js" ) ;
npm install flexsearch
No seu código inclua o seguinte:
const { Index , Document , Worker } = require ( "flexsearch" ) ;
const index = new Index ( options ) ;
const document = new Document ( options ) ;
const worker = new Worker ( options ) ;
Ou:
const FlexSearch = require ( "flexsearch" ) ;
const index = new FlexSearch . Index ( options ) ;
const document = new FlexSearch . Document ( options ) ;
const worker = new FlexSearch . Worker ( options ) ;
index . add ( id , text ) ;
index . search ( text ) ;
index . search ( text , limit ) ;
index . search ( text , options ) ;
index . search ( text , limit , options ) ;
index . search ( options ) ;
document . add ( doc ) ;
document . add ( id , doc ) ;
document . search ( text ) ;
document . search ( text , limit ) ;
document . search ( text , options ) ;
document . search ( text , limit , options ) ;
document . search ( options ) ;
worker . add ( id , text ) ;
worker . search ( text ) ;
worker . search ( text , limit ) ;
worker . search ( text , options ) ;
worker . search ( text , limit , options ) ;
worker . search ( text , limit , options , callback ) ;
worker . search ( options ) ;
O worker
herda do tipo Index
e não herda do tipo Document
. Portanto, um WorkerIndex funciona basicamente como um índice FlexSearch padrão. O suporte ao trabalhador em documentos precisa ser habilitado apenas passando a opção apropriada durante a criação { worker: true }
.
Cada método chamado em um índice
Worker
é tratado como assíncrono. Você receberá de volta umaPromise
ou poderá fornecer uma função de retorno de chamada como último parâmetro, alternativamente.
Métodos globais:
Métodos de índice:
Métodos WorkerIndex:
Métodos de documento:
* Para cada um desses métodos existe um equivalente assíncrono:
Versão assíncrona:
Os métodos assíncronos retornarão um Promise
; alternativamente, você pode passar uma função de retorno de chamada como último parâmetro.
export
e import
de métodos são sempre assíncronas, assim como todos os métodos que você chama em um índice baseado em Worker.
FlexSearch é altamente personalizável. Usar as opções certas pode realmente melhorar seus resultados, bem como a economia de memória e o tempo de consulta.
Opção | Valores | Descrição | Padrão |
predefinido | "memória" "desempenho" "corresponder" "pontuação" "padrão" | O perfil de configuração como atalho ou base para suas configurações personalizadas. | "padrão" |
tokenizar | "estrito" "avançar" "reverter" "completo" | O modo de indexação (tokenizer). Escolha um dos integrados ou passe uma função de tokenizer personalizada. | "estrito" |
esconderijo | Booleano Número | Ativar/desativar e/ou definir a capacidade das entradas em cache. Ao passar um número como limite o cache equilibra automaticamente as entradas armazenadas relacionadas à sua popularidade . Nota: Ao usar apenas "true" o cache não tem limites e o crescimento é ilimitado. | falso |
resolução | Número | Define a resolução da pontuação (padrão: 9). | 9 |
contexto | Booleano Opções de contexto | Habilite/desabilite a indexação contextual. Ao passar "true" como valor, serão considerados os valores padrão do contexto. | falso |
otimizar | Booleano | Quando ativado, ele usa um fluxo de pilha com otimização de memória para o índice. | verdadeiro |
impulsionar | função(arr,str,int) => float | Uma função de reforço personalizada usada ao indexar conteúdo no índice. A função tem esta assinatura: Function(words[], term, index) => Float . Possui 3 parâmetros onde você obtém um array de todas as palavras, o termo atual e o índice atual onde o termo é colocado no array de palavras. Você pode aplicar seu próprio cálculo, por exemplo, as ocorrências de um termo e retornar esse fator (<1 significa que a relevância é reduzida, >1 significa que a relevância é aumentada).Nota: este recurso está atualmente limitado ao uso apenas do tokenizer "estrito". | nulo |
Opções e codificação específicas do idioma: | |||
conjunto de caracteres | Carga útil do conjunto de caracteres String (chave) | Forneça uma carga útil de conjunto de caracteres personalizada ou passe uma das chaves dos conjuntos de caracteres integrados. | "latino" |
linguagem | Carga útil do idioma String (chave) | Forneça uma carga útil de idioma personalizada ou passe o sinalizador abreviado de idioma (ISO-3166) de idiomas integrados. | nulo |
codificar | falso "padrão" "simples" "equilíbrio" "avançado" "extra" função(str) => [palavras] | O tipo de codificação. Escolha um dos integrados ou passe uma função de codificação personalizada. | "padrão" |
lematizador | falso Corda Função | falso | |
filtro | falso Corda Função | falso | |
combinador | falso Corda Função | falso | |
Opções adicionais para índices de documentos: | |||
trabalhador | Booleano | Ativar/desativar e definir a contagem de threads de trabalho em execução. | falso |
documento | Descritor de documento | Inclui definições para índice e armazenamento de documentos. |
Opção | Valores | Descrição | Padrão |
resolução | Número | Define a resolução de pontuação para o contexto (padrão: 1). | 1 |
profundidade | falso Número | Habilite/desabilite a indexação contextual e também defina a distância contextual de relevância. Profundidade é o número máximo de palavras/tokens distantes de um termo para ser considerado relevante. | 1 |
bidirecional | Booleano | Define o resultado da pesquisa bidirecional. Se habilitado e o texto fonte contiver "red hat", ele será encontrado para as consultas "red hat" e "hat red". | verdadeiro |
Opção | Valores | Descrição | Padrão |
eu ia | Corda | "eu ia"" | |
marcação | falso Corda | "marcação" | |
índice | Corda Matriz<String> Matriz<Objeto> | ||
loja | Booleano Corda Matriz<String> | falso |
Opção | Valores | Descrição | Padrão |
dividir | falso ExpReg Corda | A regra para dividir palavras ao usar tokenizador não personalizado (integrado, por exemplo, "encaminhar"). Use uma string/char ou use uma expressão regular (padrão: /W+/ ). | /[W_]+/ |
RTL | Booleano | Ativa a codificação da direita para a esquerda. | falso |
codificar | função(str) => [palavras] | A função de codificação personalizada. | /lang/latin/default.js |
Opção | Valores | Descrição |
lematizador | falso Corda Função | Desative ou transmita o sinalizador abreviado de idioma (ISO-3166) ou um objeto personalizado. |
filtro | falso Corda Função | Desative ou passe um sinalizador abreviado de idioma (ISO-3166) ou uma matriz personalizada. |
combinador | falso Corda Função | Desative ou passe um sinalizador abreviado de idioma (ISO-3166) ou uma matriz personalizada. |
Opção | Valores | Descrição | Padrão |
limite | número | Define o limite de resultados. | 100 |
desvio | número | Aplicar deslocamento (pular itens). | 0 |
sugerir | Booleano | Ativa sugestões nos resultados. | falso |
Opção | Valores | Descrição | Padrão |
índice | Corda Matriz<String> Matriz<Objeto> | Define os campos do documento que devem ser pesquisados. Quando nenhum campo for definido, todos os campos serão pesquisados. Opções personalizadas por campo também são suportadas. | |
marcação | Corda Matriz<String> | Define os campos do documento que devem ser pesquisados. Quando nenhum campo for definido, todos os campos serão pesquisados. Opções personalizadas por campo também são suportadas. | falso |
enriquecer | Booleano | Enriqueça os IDs dos resultados com os documentos correspondentes. | falso |
bool | "e" "ou" | Define o operador lógico usado ao pesquisar vários campos ou tags. | "ou" |
O Tokenizer afeta a memória necessária também como tempo de consulta e flexibilidade de correspondências parciais. Tente escolher o tokenizador mais superior que atenda às suas necessidades:
Opção | Descrição | Exemplo | Fator de memória (n = comprimento da palavra) |
"estrito" | indexar palavras inteiras | foobar | * 1 |
"avançar" | palavras de índice incremental na direção direta | fo obarfoob ar | *n |
"reverter" | palavras de índice incremental em ambas as direções | foob ar fo obar | * 2n - 1 |
"completo" | indexe todas as combinações possíveis | fo oba roob ar | * n * (n - 1) |
A codificação afeta a memória necessária também como tempo de consulta e correspondências fonéticas. Tente escolher o codificador mais superior que atenda às suas necessidades ou passe um codificador personalizado:
Opção | Descrição | Falsos-positivos | Compressão |
falso | Desative a codificação | não | 0% |
"padrão" | Codificação que diferencia maiúsculas de minúsculas | não | 0% |
"simples" | Codificação que diferencia maiúsculas de minúsculas Normalizações de conjunto de caracteres | não | ~ 3% |
"equilíbrio" | Codificação que diferencia maiúsculas de minúsculas Normalizações de conjunto de caracteres Transformações literais | não | ~ 30% |
"avançado" | Codificação que diferencia maiúsculas de minúsculas Normalizações de conjunto de caracteres Transformações literais Normalizações fonéticas | não | ~ 40% |
"extra" | Codificação que diferencia maiúsculas de minúsculas Normalizações de conjunto de caracteres Transformações literais Normalizações fonéticas Transformações Soundex | sim | ~ 65% |
função() | Passe a codificação personalizada via function(string):[words] |
var index = new Index ( ) ;
Crie um novo índice e escolha uma das predefinições:
var index = new Index ( "performance" ) ;
Crie um novo índice com opções personalizadas:
var index = new Index ( {
charset : "latin:extra" ,
tokenize : "reverse" ,
resolution : 9
} ) ;
Crie um novo índice e estenda uma predefinição com opções personalizadas:
var index = new FlexSearch ( {
preset : "memory" ,
tokenize : "forward" ,
resolution : 5
} ) ;
Veja todas as opções personalizadas disponíveis.
Todo conteúdo que deve ser adicionado ao índice precisa de um ID. Quando o seu conteúdo não tem ID, você precisa criar um passando um índice ou contagem ou qualquer outra coisa como ID (um valor do tipo number
é altamente recomendado). Esses IDs são referências exclusivas a um determinado conteúdo. Isso é importante quando você atualiza ou adiciona conteúdo por meio de IDs existentes. Quando a referência não for uma preocupação, você pode simplesmente usar algo simples como count++
.
Índice. adicionar(id, string)
index . add ( 0 , "John Doe" ) ;
Índice. pesquisar(string | opções, <limite>, <opções>)
index . search ( "John" ) ;
Limite o resultado:
index . search ( "John" , 10 ) ;
Você pode verificar se um ID já foi indexado por:
if ( index . contain ( 1 ) ) {
console . log ( "ID is already in index" ) ;
}
Você pode chamar cada método em sua versão assíncrona, por exemplo, index.addAsync
ou index.searchAsync
.
Você pode atribuir retornos de chamada a cada função assíncrona:
index . addAsync ( id , content , function ( ) {
console . log ( "Task Done" ) ;
} ) ;
index . searchAsync ( query , function ( result ) {
console . log ( "Results: " , result ) ;
} ) ;
Ou não passe uma função de retorno de chamada e receba uma Promise
:
index . addAsync ( id , content ) . then ( function ( ) {
console . log ( "Task Done" ) ;
} ) ;
index . searchAsync ( query ) . then ( function ( result ) {
console . log ( "Results: " , result ) ;
} ) ;
Ou use async
e 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 ) ;
}
Você pode anexar conteúdo a um índice existente como:
index . append ( id , content ) ;
Isso não substituirá o conteúdo indexado antigo, como acontecerá ao executar index.update(id, content)
. Tenha em mente que index.add(id, content)
também realizará uma "atualização" nos bastidores quando o id já estiver sendo indexado.
Os conteúdos anexados terão seu próprio contexto e também sua própria resolution
completa. Portanto, a relevância não é empilhada, mas ganha seu próprio contexto.
Tomemos este exemplo:
index . add ( 0 , "some index" ) ;
index . append ( 0 , "some appended content" ) ;
index . add ( 1 , "some text" ) ;
index . append ( 1 , "index appended content" ) ;
Quando você consulta index.search("index")
então você obterá o ID do índice 1 como a primeira entrada no resultado, porque o contexto começa do zero para os dados anexados (não é empilhado no contexto antigo) e aqui "index " é o primeiro termo.
Se você não deseja esse comportamento, basta usar o index.add(id, content)
padrão e fornecer o comprimento completo do conteúdo.
Índice. atualizar(id, string)
index . update ( 0 , "Max Miller" ) ;
Índice. remover (id)
index . remove ( 0 ) ;
Um tokenizer divide palavras/termos em componentes ou parciais.
Defina um tokenizer personalizado privado durante a criação/inicialização:
var index = new FlexSearch ( {
tokenize : function ( str ) {
return str . split ( / s-/ / g ) ;
}
} ) ;
A função tokenizer obtém uma string como parâmetro e deve retornar uma matriz de strings representando uma palavra ou termo. Em alguns idiomas, cada caractere é um termo e também não é separado por espaços em branco.
Stemmer: várias mutações linguísticas da mesma palavra (por exemplo, "correr" e "correr")
Filtro: uma lista negra de palavras a serem filtradas da indexação (por exemplo, "e", "para" ou "ser")
Atribua um lematizador ou filtro personalizado privado durante a criação/inicialização:
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 um filtro personalizado, por exemplo:
var index = new FlexSearch ( {
filter : function ( value ) {
// just add values with length > 1 to the index
return value . length > 1 ;
}
} ) ;
Ou atribua lematizadores/filtros globalmente a um idioma:
Stemmer são passados como um objeto (par chave-valor), filter como uma matriz.
FlexSearch . registerLanguage ( "us" , {
stemmer : { /* ... */ } ,
filter : [ /* ... */ ]
} ) ;
Ou use algum lematizador ou filtro predefinido de seus 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 >
...
Agora você pode atribuir lematizador integrado durante a criação/inicialização:
var index_en = new FlexSearch . Index ( {
language : "en"
} ) ;
var index_de = new FlexSearch . Index ( {
language : "de"
} ) ;
No Node.js, todos os arquivos de pacotes de idiomas integrados estão disponíveis:
const { Index } = require ( "flexsearch" ) ;
var index_en = new Index ( {
language : "en"
} ) ;
Defina o tokenizer pelo menos como "reverso" ou "completo" ao usar RTL.
Basta definir o campo "rtl" como verdadeiro e usar um tokenizer compatível:
var index = new Index ( {
encode : str => str . toLowerCase ( ) . split ( / [^a-z]+ / ) ,
tokenize : "reverse" ,
rtl : true
} ) ;
Defina um tokenizer personalizado que atenda às suas necessidades, por exemplo:
var index = FlexSearch . create ( {
encode : str => str . replace ( / [x00-x7F] / g , "" ) . split ( "" )
} ) ;
Você também pode passar uma função de codificador personalizada para aplicar algumas transformações linguísticas.
index . add ( 0 , "一个单词" ) ;
var results = index . search ( "单词" ) ;
Supondo que nosso documento tenha uma estrutura de dados como esta:
{
"id" : 0 ,
"content" : " some text "
}
Sintaxe antiga FlexSearch v0.6.3 ( não mais suportada! ):
const index = new Document ( {
doc : {
id : "id" ,
field : [ "content" ]
}
} ) ;
O descritor do documento mudou ligeiramente, não há mais ramificação
field
; em vez disso, basta aplicar um nível acima, para quekey
se torne um membro principal das opções.
Para a nova sintaxe o campo "doc" foi renomeado para document
e o campo "field" foi renomeado para index
:
const index = new Document ( {
document : {
id : "id" ,
index : [ "content" ]
}
} ) ;
index . add ( {
id : 0 ,
content : "some text"
} ) ;
O campo id
descreve onde o ID ou chave exclusiva reside em seus documentos. A chave padrão obtém o id
do valor por padrão quando não é passada, então você pode encurtar o exemplo acima para:
const index = new Document ( {
document : {
index : [ "content" ]
}
} ) ;
O index
de membros possui uma lista de campos que você deseja indexar em seus documentos. Ao selecionar apenas um campo, você pode passar uma string. Ao usar também id
da chave padrão, isso é reduzido para apenas:
const index = new Document ( { document : "content" } ) ;
index . add ( { id : 0 , content : "some text" } ) ;
Supondo que você tenha vários campos, você pode adicionar vários campos ao í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" ]
} ) ;
Você pode passar opções 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
}
} ]
} ) ;
As opções de campo são herdadas quando também as opções globais são passadas, por exemplo:
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: As opções de contexto do campo "conteúdo" também são herdadas pelas opções de campo correspondentes, enquanto estas opções de campo foram herdadas pela opção global.
Suponha que a matriz do documento pareça mais complexa (possui ramificações aninhadas, etc.), por exemplo:
{
"record" : {
"id" : 0 ,
"title" : " some title " ,
"content" : {
"header" : " some text " ,
"footer" : " some text "
}
}
}
Em seguida, use a notação separada por dois pontos root:child:child
para definir a hierarquia dentro do descritor do documento:
const index = new Document ( {
document : {
id : "record:id" ,
index : [
"record:title" ,
"record:content:header" ,
"record:content:footer"
]
}
} ) ;
Basta adicionar os campos que você deseja consultar. Não adicione campos ao índice, você só precisa do resultado (mas não consultou). Para isso você pode armazenar documentos independentemente do seu índice (leia abaixo).
Quando você deseja consultar um campo, você deve passar a chave exata do campo que você definiu no doc
como nome do campo (com sintaxe de dois pontos):
index . search ( query , {
index : [
"record:title" ,
"record:content:header" ,
"record:content:footer"
]
} ) ;
O mesmo que:
index . search ( query , [
"record:title" ,
"record:content:header" ,
"record:content:footer"
] ) ;
Usando opções 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
} ] ) ;
Você pode realizar uma pesquisa no mesmo campo com consultas diferentes.
Ao passar opções específicas de campo, você precisa fornecer a configuração completa de cada campo. Eles não são herdados como o descritor do documento.
Você precisa seguir 2 regras para seus documentos:
[ // <-- not allowed as document start!
{
"id" : 0 ,
"title" : "title"
}
]
{
"records" : [ // <-- not allowed when ID or tag lives inside!
{
"id" : 0 ,
"title" : "title"
}
]
}
Aqui está um exemplo de um documento complexo suportado:
{
"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 " ]
}
]
}
O descritor do documento correspondente (quando todos os campos devem ser indexados) é semelhante a:
const index = new Document ( {
document : {
id : "meta:id" ,
tag : "meta:tag" ,
index : [
"contents[]:body:title" ,
"contents[]:body:footer" ,
"contents[]:keywords"
]
}
} ) ;
Novamente, ao pesquisar, você deve usar a mesma string separada por dois pontos da definição do campo.
index . search ( query , {
index : "contents[]:body:title"
} ) ;
Este exemplo quebra ambas as regras acima:
[ // <-- 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" ]
}
]
}
]
Você precisa aplicar algum tipo de normalização de estrutura.
Uma solução alternativa para essa estrutura de dados é semelhante a esta:
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
} ] ) ;
Você pode pular o primeiro loop quando os dados do documento tiverem apenas um índice como matriz externa.
Adicione um documento ao índice:
index . add ( {
id : 0 ,
title : "Foo" ,
content : "Bar"
} ) ;
Atualize o índice com um único objeto ou uma matriz de objetos:
index . update ( {
data : {
id : 0 ,
title : "Foo" ,
body : {
content : "Bar"
}
}
} ) ;
Remova um único objeto ou uma matriz de objetos do índice:
index . remove ( docs ) ;
Quando o ID for conhecido, você também pode simplesmente removê-lo (mais rápido):
index . remove ( id ) ;
No exemplo complexo acima, o campo keywords
é uma matriz, mas aqui a marcação não tinha colchetes como keywords[]
. Isso também detectará o array, mas em vez de anexar cada entrada a um novo contexto, o array será unido em uma string grande e adicionado ao índice.
A diferença entre os dois tipos de adição de conteúdo de array é a relevância na pesquisa. Ao adicionar cada item de um array via append()
ao seu próprio contexto usando a sintaxe field[]
, então a relevância da última entrada simultânea com a primeira entrada. Quando você deixou os colchetes na notação, ele unirá o array a uma string separada por espaços em branco. Aqui a primeira entrada tem a maior relevância, enquanto a última entrada tem a menor relevância.
Portanto, supondo que as palavras-chave do exemplo acima sejam pré-classificadas por relevância para sua popularidade, você deseja manter esta ordem (informações de relevância). Para este propósito, não adicione colchetes à notação. Caso contrário, as entradas seriam levadas a um novo contexto de pontuação (a ordem antiga está se perdendo).
Além disso, você pode usar a notação de colchete esquerdo para melhor desempenho e menor consumo de memória. Use-o quando não precisar da granularidade de relevância das entradas.
Pesquise em todos os campos:
index . search ( query ) ;
Pesquise em um campo específico:
index . search ( query , { index : "title" } ) ;
Pesquise em um determinado conjunto de campos:
index . search ( query , { index : [ "title" , "content" ] } ) ;
O mesmo que:
index . search ( query , [ "title" , "content" ] ) ;
Passe modificadores e consultas personalizados para cada campo:
index . search ( [ {
field : "content" ,
query : "some query" ,
limit : 100 ,
suggest : true
} , {
field : "content" ,
query : "some other query" ,
limit : 100 ,
suggest : true
} ] ) ;
Você pode realizar uma pesquisa no mesmo campo com consultas diferentes.
Veja todas as opções de pesquisa de campo disponíveis.
Esquema do conjunto de resultados:
fields[] => { field, result[] => { document }}
O primeiro índice é uma matriz de campos aos quais a consulta foi aplicada. Cada um deste campo possui um registro (objeto) com 2 propriedades “campo” e “resultado”. O “resultado” também é uma matriz e inclui o resultado deste campo específico. O resultado pode ser uma matriz de IDs ou enriquecido com dados de documentos armazenados.
Um conjunto de resultados não enriquecido agora se parece com:
[ {
field : "title" ,
result : [ 0 , 1 , 2 ]
} , {
field : "content" ,
result : [ 3 , 4 , 5 ]
} ]
Um conjunto de resultados enriquecido agora se parece com:
[ {
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 */ } }
]
} ]
Ao usar pluck
em vez de "field", você pode selecionar explicitamente apenas um campo e obter uma representação plana:
index . search ( query , { pluck : "title" , enrich : true } ) ;
[
{ id : 0 , doc : { /* document */ } } ,
{ id : 1 , doc : { /* document */ } } ,
{ id : 2 , doc : { /* document */ } }
]
Este conjunto de resultados é uma substituição da "pesquisa booleana". Em vez de aplicar sua lógica bool a um objeto aninhado, você pode aplicar sua lógica sozinho sobre o conjunto de resultados dinamicamente. Isso abre enormes capacidades sobre como você processa os resultados. Portanto, os resultados dos campos não ficam mais comprimidos em um único resultado. Isso mantém algumas informações importantes, como o nome do campo e também a relevância dos resultados de cada campo que não se misturam mais.
Uma pesquisa de campo aplicará uma consulta com a lógica booleana "ou" por padrão. Cada campo tem seu próprio resultado para a consulta fornecida.
Há uma situação em que a propriedade bool
ainda está sendo suportada. Quando você deseja mudar a lógica padrão "ou" do campo de pesquisa para "e", por exemplo:
index . search ( query , {
index : [ "title" , "content" ] ,
bool : "and"
} ) ;
Você obterá apenas resultados que contêm a consulta em ambos os campos. É isso.
Assim como a key
do ID, basta definir o caminho para a tag:
const index = new Document ( {
document : {
id : "id" ,
tag : "tag" ,
index : "content"
}
} ) ;
index . add ( {
id : 0 ,
tag : "cat" ,
content : "Some content ..."
} ) ;
Seus dados também podem ter diversas tags como uma matriz:
index . add ( {
id : 1 ,
tag : [ "animal" , "dog" ] ,
content : "Some content ..."
} ) ;
Você pode realizar uma pesquisa específica de tag:
index . search ( query , {
index : "content" ,
tag : "animal"
} ) ;
Isso apenas fornece o resultado que foi marcado com a tag fornecida.
Use várias tags ao pesquisar:
index . search ( query , {
index : "content" ,
tag : [ "cat" , "dog" ]
} ) ;
Isso fornece resultados marcados com uma das tags fornecidas.
Várias tags serão aplicadas como o booleano "ou" por padrão. Basta que uma das tags exista.
Esta é outra situação em que a propriedade bool
ainda é suportada. Quando você deseja mudar a lógica padrão "ou" da pesquisa de tags para "e", por exemplo:
index . search ( query , {
index : "content" ,
tag : [ "dog" , "animal" ] ,
bool : "and"
} ) ;
Você obterá apenas resultados que contêm ambas as tags (neste exemplo, há apenas um registro que contém as tags "cachorro" e "animal").
Você também pode buscar resultados de uma ou mais tags quando nenhuma consulta foi passada:
index . search ( { tag : [ "cat" , "dog" ] } ) ;
Neste caso, o conjunto de resultados se parece com:
[ {
tag : "cat" ,
result : [ /* all cats */ ]
} , {
tag : "dog" ,
result : [ /* all dogs */ ]
} ]
Por padrão, cada consulta é limitada a 100 entradas. Consultas ilimitadas levam a problemas. Você precisa definir o limite como uma opção para ajustar o tamanho.
Você pode definir o limite e o deslocamento para cada consulta:
index . search ( query , { limit : 20 , offset : 100 } ) ;
Você não pode pré-contar o tamanho do conjunto de resultados. Esse é um limite definido pelo design do FlexSearch. Quando você realmente precisar de uma contagem de todos os resultados que puder percorrer, basta atribuir um limite alto o suficiente, recuperar todos os resultados e aplicar o deslocamento de paginação manualmente (isso também funciona no lado do servidor). FlexSearch é rápido o suficiente para que isso não seja um problema.
Somente um índice de documento pode ter um armazenamento. Você pode usar um índice de documento em vez de um índice simples para obter essa funcionalidade também ao armazenar apenas pares de conteúdo de ID.
Você pode definir independentemente quais campos devem ser indexados e quais campos devem ser armazenados. Desta forma você pode indexar campos que não devem ser incluídos no resultado da pesquisa.
Não use um armazenamento quando: 1. uma matriz de IDs como resultado for boa o suficiente, ou 2. você já tiver o conteúdo/documentos armazenados em outro lugar (fora do índice).
Quando o atributo
store
foi definido, você deve incluir todos os campos que devem ser armazenados explicitamente (funciona como uma lista de permissões).
Quando o atributo
store
não foi definido, o documento original é armazenado como substituto.
Isso adicionará todo o conteúdo original à loja:
const index = new Document ( {
document : {
index : "content" ,
store : true
}
} ) ;
index . add ( { id : 0 , content : "some text" } ) ;
Você pode obter documentos indexados na loja:
var data = index . get ( 1 ) ;
Você pode atualizar/alterar o conteúdo da loja diretamente sem alterar o índice:
index . set ( 1 , data ) ;
Para atualizar a loja e também atualizar o índice basta usar index.update
, index.add
ou index.append
.
Quando você executa uma consulta, seja um índice de documento ou um índice simples, você sempre receberá de volta uma matriz de IDs.
Opcionalmente, você pode enriquecer automaticamente os resultados da consulta com conteúdos armazenados:
index . search ( query , { enrich : true } ) ;
Seus resultados agora se parecem com:
[ {
id : 0 ,
doc : { /* content from store */ }
} , {
id : 1 ,
doc : { /* content from store */ }
} ]
Isso adicionará apenas campos específicos de um documento à loja (o ID não é necessário para manter na loja):
const index = new Document ( {
document : {
index : "content" ,
store : [ "author" , "email" ]
}
} ) ;
index . add ( id , content ) ;
Você pode configurar de forma independente o que deve ser indexado e o que deve ser armazenado. É altamente recomendável fazer uso disso sempre que puder.
Aqui está um exemplo útil de configuração de documento e loja:
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 ..."
} ) ;
Você pode consultar o conteúdo e recuperar os valores armazenados:
index . search ( "some content" , { enrich : true } ) ;
Seus resultados agora estão parecidos com:
[ {
field : "content" ,
result : [ {
id : 0 ,
doc : {
author : "Jon Doe" ,
email : "[email protected]" ,
}
} ]
} ]
Os campos "autor" e "e-mail" não são indexados.
Simplesmente encadeie 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: Este recurso está desabilitado por padrão devido ao uso estendido de memória. Leia aqui para obter mais informações sobre e como ativar.
FlexSearch introduz um novo mecanismo de pontuação chamado Pesquisa Contextual que foi inventado por Thomas Wilkerling, o autor desta biblioteca. Uma pesquisa contextual aumenta incrivelmente as consultas para um nível totalmente novo, mas também requer alguma memória adicional (dependendo da profundidade ). A ideia básica deste conceito é limitar a relevância pelo seu contexto em vez de calcular a relevância através de toda a distância do seu documento correspondente. Dessa forma, a pesquisa contextual também melhora os resultados de consultas baseadas em relevância em uma grande quantidade de dados de texto.
Crie um índice e use o contexto padrão:
var index = new FlexSearch ( {
tokenize : "strict" ,
context : true
} ) ;
Crie um índice e aplique opções personalizadas para o contexto:
var index = new FlexSearch ( {
tokenize : "strict" ,
context : {
resolution : 5 ,
depth : 3 ,
bidirectional : true
}
} ) ;
Apenas o tokenizer "estrito" é realmente suportado pelo índice contextual.
O índice contextual requer quantidade adicional de memória dependendo da profundidade.
Você precisa inicializar o cache e seu limite durante a criação do índice:
const index = new Index ( { cache : 100 } ) ;
const results = index . searchCache ( query ) ;
Um cenário comum para usar um cache é o preenchimento automático ou a pesquisa instantânea durante a digitação.
Ao passar um número como limite o cache equilibra automaticamente as entradas armazenadas relacionadas à sua popularidade.
Ao usar apenas "true", o cache é ilimitado e funciona de 2 a 3 vezes mais rápido (porque o balanceador não precisa ser executado).
O novo modelo de trabalhador da v0.7.0 é dividido em "campos" do documento (1 trabalhador = 1 índice de campo). Desta forma o trabalhador torna-se capaz de resolver tarefas (subtarefas) de forma completa. A desvantagem desse paradigma é que eles podem não ter sido perfeitamente equilibrados no armazenamento de conteúdo (os campos podem ter comprimentos de conteúdo diferentes). Por outro lado, não há indicação de que o equilíbrio do armazenamento proporcione alguma vantagem (todos exigem a mesma quantidade no total).
Ao utilizar um índice de documento, basta aplicar a opção "trabalhador":
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" }
Quando você realiza uma pesquisa de campo em todos os campos, esta tarefa está sendo perfeitamente equilibrada entre todos os trabalhadores, que podem resolver suas subtarefas de forma independente.
Acima vimos que os documentos criarão trabalhadores automaticamente para cada campo. Você também pode criar um WorkerIndex diretamente (da mesma forma que usar Index
em vez de Document
).
Use 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" ) ;
Ou quando a versão empacotada foi usada:
var index = new FlexSearch . Worker ( options ) ;
index . add ( 1 , "some" )
. add ( 2 , "content" )
. add ( 3 , "to" )
. add ( 4 , "index" ) ;
Tal WorkerIndex funciona praticamente da mesma forma que uma instância criada de Index
.
Um WorkerIndex oferece suporte apenas à variante
async
de todos os métodos. Isso significa que quando você chamaindex.search()
em um WorkerIndex, isso também funcionará de forma assíncrona, da mesma forma queindex.searchAsync()
.
O modelo de trabalho para Node.js é baseado em "threads de trabalho" e funciona exatamente da mesma maneira:
const { Document } = require ( "flexsearch" ) ;
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
Ou crie uma única instância de trabalho para um índice que não seja de documento:
const { Worker } = require ( "flexsearch" ) ;
const index = new Worker ( { options } ) ;
Um trabalhador sempre funcionará como assíncrono. Em uma chamada de método de consulta você sempre deve tratar a promessa retornada (por exemplo, usar await
) ou passar uma função de retorno de chamada como último parâmetro.
const index = new Document ( {
index : [ "tag" , "name" , "title" , "text" ] ,
worker : true
} ) ;
Todas as solicitações e subtarefas serão executadas em paralelo (priorize "todas as tarefas concluídas"):
index . searchAsync ( query , callback ) ;
index . searchAsync ( query , callback ) ;
index . searchAsync ( query , callback ) ;
Além disso (priorize "todas as tarefas concluídas"):
index . searchAsync ( query ) . then ( callback ) ;
index . searchAsync ( query ) . then ( callback ) ;
index . searchAsync ( query ) . then ( callback ) ;
Ou quando você tiver apenas um retorno de chamada quando todas as solicitações forem concluídas, basta usar Promise.all()
que também prioriza "todas as tarefas concluídas":
Promise . all ( [
index . searchAsync ( query ) ,
index . searchAsync ( query ) ,
index . searchAsync ( query )
] ) . then ( callback ) ;
Dentro do retorno de chamada de Promise.all()
você também obterá uma matriz de resultados como o primeiro parâmetro, respectivamente, para cada consulta realizada.
Ao usar await
você pode priorizar a ordem (priorizar "primeira tarefa concluída") e resolver as solicitações uma por uma e apenas processar as subtarefas em paralelo:
await index . searchAsync ( query ) ;
await index . searchAsync ( query ) ;
await index . searchAsync ( query ) ;
O mesmo para index.add()
, index.append()
, index.remove()
ou index.update()
. Aqui há um caso especial que não é desabilitado pela biblioteca, mas que você precisa ter em mente ao usar Workers.
Quando você chama a versão "sincronizada" em um índice de trabalho:
index . add ( doc ) ;
index . add ( doc ) ;
index . add ( doc ) ;
// contents aren't indexed yet,
// they just queued on the message channel
Claro, você pode fazer isso, mas lembre-se de que o thread principal não possui uma fila adicional para tarefas de trabalho distribuídas. Executá-los em um loop longo dispara conteúdo massivamente para o canal de mensagens via worker.postMessage()
internamente. Felizmente, o navegador e o Node.js lidarão com essas tarefas recebidas automaticamente (desde que haja RAM livre suficiente disponível). Ao usar a versão "sincronizada" em um índice de trabalho, o conteúdo não é indexado uma linha abaixo, porque todas as chamadas são tratadas como assíncronas por padrão.
Ao adicionar/atualizar/remover grandes volumes de conteúdo do índice (ou alta frequência), é recomendado usar a versão assíncrona junto com
async/await
para manter um baixo consumo de memória durante processos longos.
A exportação mudou ligeiramente. A exportação agora consiste em várias peças menores, em vez de apenas um grande volume. Você precisa passar uma função de retorno de chamada que possui 2 argumentos "chave" e "dados". Esta função de retorno de chamada é chamada por cada parte, por exemplo:
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 dados para o localStorage não é realmente uma boa prática, mas se o tamanho não for uma preocupação, use-o, se desejar. A exportação existe principalmente para uso em Node.js ou para armazenar índices que você deseja delegar de um servidor para o cliente.
O tamanho da exportação corresponde ao consumo de memória da biblioteca. Para reduzir o tamanho da exportação você deve usar uma configuração que tenha menos consumo de memória (use a tabela na parte inferior para obter informações sobre configurações e sua alocação de memória).
Quando sua rotina de salvamento é executada de forma assíncrona, você deve retornar uma promessa:
index . export ( function ( key , data ) {
return new Promise ( function ( resolve ) {
// do the saving as async
resolve ( ) ;
} ) ;
} ) ;
Você não pode exportar a tabela adicional para o recurso "fastupdate". Essas tabelas existem de referências e quando armazenadas ficam totalmente serializadas e ficam muito grandes. A lib irá lidar com isso automaticamente para você. Ao importar dados, o índice desativa automaticamente o "fastupdate".
Antes de importar dados, você precisa primeiro criar seu índice. Para índices de documentos, forneça o mesmo descritor de documento usado ao exportar os dados. Esta configuração não é armazenada na exportação.
var index = new Index ( { ... } ) ;
Para importar os dados basta passar uma chave e dados:
index . import ( key , localStorage . getItem ( key ) ) ;
Você precisa importar todas as chaves! Caso contrário, seu índice não funcionará. Você precisa armazenar as chaves da exportação e usá-las para a importação (a ordem das chaves pode ser diferente).
Isto é apenas para demonstração e não é recomendado, porque você pode ter outras chaves em seu localStorage que não são suportadas como importação:
var keys = Object . keys ( localStorage ) ;
for ( let i = 0 , key ; i < keys . length ; i ++ ) {
key = keys [ i ] ;
index . import ( key , localStorage . getItem ( key ) ) ;
}
As definições específicas do idioma estão sendo divididas em dois grupos:
function(string):string[]
boolean
{string: string}
{string: string}
string[]
O conjunto de caracteres contém a lógica de codificação, a linguagem contém lematizador, filtro de palavras irrelevantes e correspondentes. Várias definições de linguagem podem usar o mesmo codificador de conjunto de caracteres. Além disso, esta separação permite gerenciar diferentes definições de idioma para casos de uso especiais (por exemplo, nomes, cidades, dialetos/gírias, etc.).
Para descrever completamente um idioma personalizado rapidamente, você precisa passar:
const index = FlexSearch ( {
// mandatory:
encode : ( content ) => [ words ] ,
// optionally:
rtl : false ,
stemmer : { } ,
matcher : { } ,
filter : [ ]
} ) ;
Ao não passar nenhum parâmetro, ele usa o esquema latin:default
por padrão.
Campo | Categoria | Descrição |
codificar | conjunto de caracteres | A função do codificador. Tem que retornar um array de palavras separadas (ou uma string vazia). |
RTL | conjunto de caracteres | Uma propriedade booleana que indica codificação da direita para a esquerda. |
filtro | linguagem | Os filtros também são conhecidos como "palavras irrelevantes", pois filtram completamente a indexação das palavras. |
lematizador | linguagem | Stemmer remove terminações de palavras e é uma espécie de "normalização parcial". Uma terminação de palavra correspondida apenas quando o comprimento da palavra é maior que a parcial correspondente. |
combinador | linguagem | O Matcher substitui todas as ocorrências de uma determinada string independente de sua posição e também é uma espécie de “normalização parcial”. |
A maneira mais simples de atribuir codificação específica de conjunto de caracteres/idioma por meio de módulos é:
import charset from "./dist/module/lang/latin/advanced.js" ;
import lang from "./dist/module/lang/en.js" ;
const index = FlexSearch ( {
charset : charset ,
lang : lang
} ) ;
Basta importar a exportação padrão de cada módulo e atribuí-los de acordo.
O exemplo completo qualificado acima é:
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
} ) ;
O exemplo acima é a interface padrão que é pelo menos exportada de cada conjunto de caracteres/idioma.
Você também pode definir o codificador diretamente e deixar todas as outras opções:
import simple from "./dist/module/lang/latin/simple.js" ;
const index = FlexSearch ( {
encode : simple
} ) ;
Você pode atribuir um conjunto de caracteres passando o conjunto de caracteres durante a inicialização, por exemplo, charset: "latin"
para o codificador padrão do conjunto de caracteres ou charset: "latin:soundex"
para uma variante do codificador.
As definições de linguagem (especialmente os matchers) também podem ser usadas para normalizar o dialeto e a gíria de uma linguagem específica.
Você precisa disponibilizar o conjunto de caracteres e/ou definições de idioma:
flexsearch.bundle.js
por padrão, mas nenhuma definição específica de idioma está incluída/dist/lang/
(arquivos referem-se a idiomas, pastas são conjuntos de caracteres)Ao carregar os pacotes de idiomas, verifique se a biblioteca foi carregada 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 >
Ao usar a versão completa do "pacote", os codificadores latinos embutidos já estão incluídos e você só precisa carregar o arquivo de idiomas:
< script src =" dist/flexsearch.bundle.js " > </ script >
< script src =" dist/lang/en.min.js " > </ script >
Como você carrega pacotes como pacotes externos (não-módulos de 6-Modules), você precisa inicializá-los por atalhos:
const index = FlexSearch ( {
charset : "latin:soundex" ,
lang : "en"
} ) ;
Use o
charset:variant
para atribuir o Charset e suas variantes. Ao passar o charset sem uma variante, resolverá automaticamente comocharset:default
.
Você também pode substituir as definições existentes, por exemplo:
const index = FlexSearch ( {
charset : "latin" ,
lang : "en" ,
matcher : { }
} ) ;
As definições aprovadas não estenderão as definições padrão, elas as substituirão.
Quando você gosta de estender uma definição, crie um novo arquivo de idiomas e coloque toda a lógica.
É bastante direto ao usar uma variante do 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 >
Ao usar a versão completa do "pacote", os codificadores latinos embutidos já estão incluídos e você só precisa carregar o arquivo de idiomas:
< 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"
} ) ;
No FlexSearch, você não pode fornecer seu próprio tokenizador parcial, porque é uma dependência direta para a unidade principal. O tokenizer embutido da FlexSearch divide cada palavra em fragmentos por diferentes padrões:
Este é o pipeline padrão fornecido pela FlexSearch:
A princípio, dê uma olhada no pipeline padrão no src/common.js
. É muito simples e direto. O pipeline processará como algum tipo de inversão de controle, a implementação final do codificador deve lidar com o CHARSET e também transformações específicas de idiomas. Esta solução alternativa sobrou de muitos testes.
Injete o pipeline padrão por exemplo:
this . pipeline (
/* string: */ str . toLowerCase ( ) ,
/* normalize: */ false ,
/* split: */ split ,
/* collapse: */ false
) ;
Use o esquema de oleoduto de cima para entender a iteração e a diferença de pré-codificação e pós-codificação. Stemmer e Matchers precisam ser aplicados após a normalização do charset, mas antes das transformações do idioma, também os filtros.
Aqui está um bom exemplo de extensão de pipelines: src/lang/latin/extra.js
→ src/lang/latin/advanced.js
→ src/lang/latin/simple.js
.
Pesquise seu idioma no src/lang/
, se existir, você pode estender ou fornecer variantes (como dialeto/gíria). Se o idioma não existir, crie um novo arquivo e verifique se algum dos charsets existentes (por exemplo, latim) se encaixa no seu idioma. Quando não existe um charset, você precisa fornecer um charset como base para o idioma.
Um novo charset deve fornecer pelo menos:
encode
uma função que normalize o conjunto de charnetas de um conteúdo de texto passado (remova os caracteres especiais, transformações linguais, etc.) e retorna uma matriz de palavras separadas . Também o filtro Stemmer, Matcher ou Stopword precisa ser aplicado aqui. Quando o idioma não tiver palavras, forneça algo semelhante, por exemplo, cada sinal chinês também pode ser uma "palavra". Não devolva todo o conteúdo do texto sem divisão.rtl
Uma bandeira booleana que indica a codificação da direita para a esquerdaBasicamente, o charset precisa apenas para fornecer uma função do codificador junto com um indicador para a codificação da direita para a esquerda:
export function encode ( str ) { return [ str ] }
export const rtl = false ;
String de referência: "Björn-Phillipp Mayer"
Consulta | padrão | simples | avançado | extra |
Björn | sim | sim | sim | sim |
Björ | sim | sim | sim | sim |
Bjorn | não | sim | sim | sim |
BJOERN | não | não | sim | sim |
Philipp | não | não | sim | sim |
Filipe | não | não | sim | sim |
Björnphillip | não | sim | sim | sim |
mais | não | não | sim | sim |
Björn Meier | não | não | sim | sim |
Meier Fhilip | não | não | sim | sim |
Byorn Mair | não | não | não | sim |
(falsos positivos) | não | não | não | sim |
O livro "Viagens de Gulliver Swift Jonathan 1726" foi totalmente indexado para os exemplos abaixo.
A configuração significativa mais otimizada da memória alocará apenas 1,2 MB para todo o livro indexado! Esta é provavelmente a pegada de memória mais pequena que você receberá de uma biblioteca de pesquisa.
import { encode } from "./lang/latin/extra.js" ;
index = new Index ( {
encode : encode ,
tokenize : "strict" ,
optimize : true ,
resolution : 1 ,
minlength : 3 ,
fastupdate : false ,
context : false
} ) ;
O livro "Gulliver's Travels" (Swift Jonathan 1726) foi completamente indexado para este teste:
Por padrão, um índice lexical é muito pequeno:
depth: 0, bidirectional: 0, resolution: 3, minlength: 0
=> 2,1 Mb
Uma resolução mais alta aumentará a alocação de memória:
depth: 0, bidirectional: 0, resolution: 9, minlength: 0
=> 2,9 Mb
O uso do índice contextual aumentará a alocação de memória:
depth: 1, bidirectional: 0, resolution: 9, minlength: 0
=> 12,5 Mb
Uma profundidade contextual mais alta aumentará a alocação de memória:
depth: 2, bidirectional: 0, resolution: 9, minlength: 0
=> 21,5 Mb
Um MinLen Comnn mais alto diminuirá a alocação de memória:
depth: 2, bidirectional: 0, resolution: 9, minlength: 3
=> 19,0 Mb
O uso do bidirecional diminuirá a alocação de memória:
depth: 2, bidirectional: 1, resolution: 9, minlength: 3
=> 17,9 Mb
Ativar a opção "fastupdate" aumentará a alocação de memória:
depth: 2, bidirectional: 1, resolution: 9, minlength: 3
=> 6,3 Mb
Toda biblioteca de pesquisa está constantemente em competição com estas 4 propriedades:
O FlexSearch fornece muitos parâmetros que você pode usar para ajustar o equilíbrio ideal para o seu caso de uso específico.
Modificador | Impacto da memória * | Impacto de desempenho ** | Impacto correspondente ** | Impacto de pontuação ** |
resolução | +1 (por nível) | +1 (por nível) | 0 | +2 (por nível) |
profundidade | +4 (por nível) | -1 (por nível) | -10 + profundidade | +10 |
comprimento mínimo | -2 (por nível) | +2 (por nível) | -3 (por nível) | +2 (por nível) |
bidirecional | -2 | 0 | +3 | -1 |
fastUpdate | +1 | +10 (atualização, remova) | 0 | 0 |
Otimizar: Verdadeiro | -7 | -1 | 0 | -3 |
codificador: "ICase" | 0 | 0 | 0 | 0 |
codificador: "simples" | -2 | -1 | +2 | 0 |
codificador: "avançado" | -3 | -2 | +4 | 0 |
codificador: "extra" | -5 | -5 | +6 | 0 |
Encoder: "SoundEx" | -6 | -2 | +8 | 0 |
tokenize: "rigoroso" | 0 | 0 | 0 | 0 |
Tokenize: "Forward" | +3 | -2 | +5 | 0 |
tokenize: "reverso" | +5 | -4 | +7 | 0 |
tokenize: "completo" | +8 | -5 | +10 | 0 |
Índice de documentos | +3 (por campo) | -1 (por campo) | 0 | 0 |
Tags de documentos | +1 (por tag) | -1 (por tag) | 0 | 0 |
Loja: Verdadeiro | +5 (por documento) | 0 | 0 | 0 |
Loja: [Campos] | +1 (por campo) | 0 | 0 | 0 |
cache: true | +10 | +10 | 0 | 0 |
Cache: 100 | +1 | +9 | 0 | 0 |
Tipo de IDS: Número | 0 | 0 | 0 | 0 |
Tipo de IDS: String | +3 | -3 | 0 | 0 |
memory
(otimizar primário para memória)performance
(otimizar primário para desempenho)match
(otimizar primário para correspondência)score
(otimizar primário para pontuação)default
(o perfil equilibrado padrão)Esses perfis estão cobrindo casos de uso padrão. Recomenda -se aplicar configuração personalizada em vez de usar perfis para aproveitar o melhor para a sua situação. Todo perfil pode ser otimizado ainda mais para sua tarefa específica, por exemplo, configuração otimizada de desempenho extremo ou memória extrema e assim por diante.
Você pode passar por uma predefinição durante a criação/inicialização do índice.
Recomenda -se usar valores de ID numéricos como referência ao adicionar conteúdo ao índice. O comprimento do byte dos IDs passados influencia significativamente o consumo de memória. Se isso não for possível, você deve considerar usar uma tabela de índice e mapear os IDs com índices, isso se torna importante, especialmente ao usar índices contextuais em uma grande quantidade de conteúdo.
Sempre que puder, tente dividir o conteúdo por categorias e adicione -os ao seu próprio índice, por exemplo:
var action = new FlexSearch ( ) ;
var adventure = new FlexSearch ( ) ;
var comedy = new FlexSearch ( ) ;
Dessa forma, você também pode fornecer configurações diferentes para cada categoria. Esta é realmente a maneira mais rápida de realizar uma pesquisa difusa.
Para tornar essa solução alternativa mais extensível, você pode usar um ajudante curto:
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 ) : [ ] ;
}
Adicione conteúdo ao índice:
add ( 1 , "action" , "Movie Title" ) ;
add ( 2 , "adventure" , "Movie Title" ) ;
add ( 3 , "comedy" , "Movie Title" ) ;
Execute consultas:
var results = search ( "action" , "movie title" ) ; // --> [1]
Os índices divididos por categorias melhoram significativamente o desempenho.
Copyright 2018-2023 Thomas Wilkerling, hospedado por NextApps GmbH
Liberado sob a licença Apache 2.0