YC Vibe Check、linus.zone/entr、個人用生産性ソフトウェアなどの個人プロジェクト全体で使用される、JavaScript 用のシンプルでインデックス不要のテキスト検索。注釈付きのソースを読んで、内部でどのように動作するかを理解してください。
いくつかの簡単な例から始めましょう。
import { search } from 'libsearch' ; // on Node.js
const { search } = window . libsearch ; // in the browser
const articles = [
{ title : 'Weather in Berkeley, California' } ,
{ title : 'University report: UC Berkeley' } ,
{ title : 'Berkeley students rise in solidarity...' } ,
{ title : 'Californian wildlife returning home' } ,
] ;
// basic usage
search ( articles , 'berkeley cali' , a => a . title ) ;
// => [{ title: 'Weather in Berkeley, California' }]
search ( articles , 'california' , a => a . title ) ;
// => [
// { title: 'Weather in Berkeley, California' },
// { title: 'Californian wildlife returning home' },
// ]
// mode: 'word' only returns whole-word matches
search ( articles , 'california' , a => a . title , { mode : 'word' } ) ;
// => [{ title: 'Weather in Berkeley, California' }]
// case sensitivity
search ( articles , 'W' , a => a . title , { caseSensitive : true } ) ;
// => [{ title: 'Weather in Berkeley, California' }]
// empty query returns the full list, unmodified
search ( articles , '' , a => a . title ) ;
// => [{...}, {...}, {...}, {...}]
より正式には、libsearch は単一の API、 search
関数を公開します。この関数は、2 つの必須引数と 2 つのオプションの引数を受け取ります。
function search < T > (
items : T [ ] ,
query : string ,
by ?: ( it : T ) => string ,
options ?: {
caseSensitive : boolean ,
mode : 'word' | 'prefix' | 'autocomplete' ,
} ,
) : T [ ]
items
検索する項目のリストです。通常、 items
文字列の配列、または文字列プロパティを持つオブジェクトの配列になります。query
、項目のリストを検索するための文字列クエリです。by
(オプション) は、 items
から項目を取得し、その項目を検索するための文字列値を返す述語関数です。たとえば、 items
{ name: 'Linus' }
のようなオブジェクトのリストである場合、 by
関数x => x.name
である必要があります。これにはデフォルトで値x => String(x)
があり、 string[]
型のitems
に対して機能します。options
( Optional ) はオプションの辞書です。caseSensitive
大文字と小文字を区別して検索します。デフォルトではfalse
です。mode
不完全なクエリ単語を照合する方法を制御します。mode: 'word'
すべてのクエリ単語が単語の一部ではなく、完全に完全に一致する単語のみに一致する必要があります。たとえば、クエリ「California」は「University of California 」には一致しますが、「 California n University」には一致しません。mode: 'prefix'
、すべてのクエリ単語が一致した単語の不完全な「プレフィックス」である可能性があることを意味します。 「Uni Cali」は「 Uni versity of Cali fornia」と「 Cali fornian Uni versity」の両方に一致します。このモードでも、すべてのクエリ単語はどこかに一致する必要があります。「 Cali fornia」はクエリと一致しないため、一致しません。 「ユニ」という言葉。mode: 'autocomplete'
は他の 2 つのモードのハイブリッドで、検索結果が返されるときにユーザーがクエリを入力し続けるオートコンプリート スタイルの検索で使用すると便利です。このモードは、最後のクエリ単語がmode: 'prefix'
のように不完全である可能性があることを除いて、 mode: 'word'
と同じです。これは、「University of Cali」が「 University of Cali fornia」と一致することを意味します。これは、ユーザーが完全なクエリを入力する前に一致するものを見つけることができるため便利です。単体テストでこれらのオプションをどのように組み合わせるかの例をさらに見つけることができます。
<script>
を使用してこれを HTML にドロップします。
< script src =" https://unpkg.com/libsearch/dist/browser.js " > </ script >
これにより、 search
関数がwindow.libsearch.search
として公開されます。
npm install libsearch
# or
yarn add libsearch
コードで使用します。
import { search } from 'libsearch' ;
// search(...);
libsearch には、ソース ファイルから生成された TypeScript 型定義が付属しています。 NPM から libsearch を使用すると、TypeScript コンパイラーによってそれらが取得されるはずです。
libsearch を使用すると、事前に構築された検索インデックスを必要とせずに、JavaScript オブジェクトのリストに対して基本的な全文検索を迅速に実行でき、適度に良好な結果の TF-IDF ランキングを提供します。 FlexSearch や lunr.js などのライブラリに付属する幅広い機能は提供しませんが、 text.indexOf(query) > -1
よりも大幅に優れており、数千のドキュメントを検索するのに十分な速度を備えています。私の経験ではすべてのキーストローク。
libsearch がこれを実現する方法には 2 つの重要なアイデアがあります。
最新の JavaScript エンジンには高度に最適化された正規表現エンジンが付属しており、libsearch はこれを利用して、検索時にクエリ文字列を正規表現フィルターに変換することで、インデックス不要の高速テキスト検索を実現します。
ほとんどの全文検索ライブラリは、まず開発者に、検索用語をその検索用語が出現するドキュメントにマッピングする「インデックス」データ構造を構築することを要求することによって機能します。これは通常、「検索」の計算作業の一部を事前に実行するため、検索自体の高速性と正確性を維持できるため、良いトレードオフです。また、検索速度を損なうことなく、インデックス付きデータの見出し語化などの高度な変換やデータ クリーンアップも可能になります。しかし、プロトタイプや単純な Web アプリを構築する場合、「十分な」検索ソリューションを得るために別の「インデックス作成」ステップが必要になるという複雑さは避けたいと思うことがよくありました。インデックスはどこかに保存し、基盤となるデータセットの変化や成長に合わせて常に維持する必要があります。
検索インデックスの主なタスクは、データセット内に出現する「トークン」またはキーワードを、それらが出現するドキュメントにマッピングすることです。これにより、「どのドキュメントに単語 X が含まれているか?」という質問が得られるようになります。検索時の応答が速い ( O(1)
)。インデックスがないと、すべての文書をスキャンしてキーワードを探す必要があるため、これはO(n)
質問になります。しかし、多くの場合、最新のハードウェアでは、クライアント側の Web アプリで一般的な (数 MB の) 十分に小さいデータセットの場合、 n
は非常に小さく、キーストロークごとのO(n)
が目立たないほど十分に小さいです。
libsearch は、「Uni of California」のようなクエリを正規表現フィルターのリスト(^|W)Uni($|W)
、 (^|W)of($|W)
、 (^|W)California
変換します。 (^|W)California
。次に、これらの各正規表現でコーパスをフィルタリングすることにより、インデックスを必要とせずに「検索」します。
従来の TF-IDF メトリックは、単語ごとに次のように計算されます。
( # matches ) / ( # words in the doc ) * log ( # total docs / # docs that matched )
ドキュメント内の単語数を取得するには、ドキュメントをトークン化するか、少なくともドキュメントを空白で分割する必要があり、これには計算コストがかかります。したがって、libsearch は、代わりにドキュメントの長さ (文字数) を使用してこれを近似します。
上記の正規表現クエリを使用すると、libsearch の TF-IDF 式は次のようになります。
( # RegExp matches ) / ( doc . length ) * log ( # docs / # docs that matched RegExp )
これは検索の実行時に単語ごとに計算され、最後に並べ替えのために集計されます。
libsearch のソース コードは TypeScript で書かれています。 TypeScript、バニラ Node.js、Web 間でライブラリを使用できるようにするために、2 つのビルドをコンパイルします。
search.ts
型チェックと型の削除だけです。これはNode.jsにlibsearch
インポートするときにインポートされるコードですsearch
機能をwindow.libsearch
グローバルにエクスポートします。 ES モジュールのビルドは TypeScript コンパイラーであるtsc
で生成され、さらに縮小されたブラウザーのビルドは Webpack で生成されます。
NPM/Yarn コマンド:
lint
およびfmt
: lint を実行し、リポジトリ内のソース コードを自動的にフォーマットします。test
ライブラリの最新ビルドで単体テストを実行します。 test
を実行する前にbuild:tsc
実行する必要がありますbuild:*
コマンドが、さまざまなタイプのライブラリ ビルドの生成を調整します。build:tsc
ES モジュールのビルドをビルドしますbuild:w
ファイルの書き込みごとにbuild:tsc
を実行しますbuild:cjs
ES モジュールのビルドからブラウザのビルドをビルドしますbuild:all
両方のビルドを順番にビルドしますclean
dist/
内の生成/ビルド ファイルをすべて削除します。docs
、thethephist.github.io/libsearch にある Litterate ベースのドキュメントを構築します。メインにプッシュしたり公開したりする前に、通常は次のコマンドを実行します。
yarn fmt && yarn build:all && yarn test && yarn docs
何も忘れていないことを確認するために。