Pencarian fuzzy kecil dan efisien yang tidak membosankan. Ini kaburku?. Ada banyak yang seperti itu, tapi yang ini milikku.¹
uFuzzy adalah perpustakaan pencarian fuzzy yang dirancang untuk mencocokkan frasa pencarian yang relatif pendek (jarum) dengan daftar besar frasa pendek hingga menengah (tumpukan jerami). Ini mungkin paling tepat digambarkan sebagai String.includes() yang lebih pemaaf. Aplikasi umum mencakup pemfilteran daftar, pelengkapan/saran otomatis, dan pencarian judul, nama, deskripsi, nama file, dan fungsi.
Dalam mode MultiInsert
default uFuzzy, setiap kecocokan harus berisi semua karakter alfanumerik dari jarum dalam urutan yang sama; dalam mode SingleError
, kesalahan ketik tunggal ditoleransi di setiap istilah (jarak Damerau–Levenshtein = 1). API .search()
nya dapat secara efisien mencocokkan istilah yang tidak berurutan, mendukung beberapa pengecualian substring (mis. fruit -green -melon
), dan istilah yang tepat dengan karakter non-alphanum (mis. "C++"
, "$100"
, "#hashtag"
). Jika dipegang dengan benar , ia juga dapat mencocokkan beberapa properti objek secara efisien.
Array.sort()
sederhana yang mendapatkan akses ke statistik/penghitung setiap pertandingan. Tidak ada "skor" kotak hitam gabungan yang perlu dipahami.uFuzzy dioptimalkan untuk alfabet Latin/Romawi dan mengandalkan secara internal pada ekspresi reguler non-unicode.
Dukungan untuk lebih banyak bahasa berfungsi dengan menambah regexp Latin bawaan dengan karakter tambahan atau dengan menggunakan varian {unicode: true}
yang lebih lambat dan universal. Alternatif {alpha: "..."}
yang lebih sederhana, namun kurang fleksibel menggantikan bagian AZ
dan az
dari regexps Latin bawaan dengan karakter pilihan Anda (huruf besar akan dicocokkan secara otomatis selama penggantian).
Fungsi util uFuzzy.latinize()
dapat digunakan untuk menghilangkan aksen/diakritik umum dari tumpukan jerami dan jarum sebelum melakukan pencarian.
// Latin (default)
let opts = { alpha : "a-z" } ;
// OR
let opts = {
// case-sensitive regexps
interSplit : "[^A-Za-z\d']+" ,
intraSplit : "[a-z][A-Z]" ,
intraBound : "[A-Za-z]\d|\d[A-Za-z]|[a-z][A-Z]" ,
// case-insensitive regexps
intraChars : "[a-z\d']" ,
intraContr : "'[a-z]{1,2}\b" ,
} ;
// Latin + Norwegian
let opts = { alpha : "a-zæøå" } ;
// OR
let opts = {
interSplit : "[^A-Za-zæøåÆØÅ\d']+" ,
intraSplit : "[a-zæøå][A-ZÆØÅ]" ,
intraBound : "[A-Za-zæøåÆØÅ]\d|\d[A-Za-zæøåÆØÅ]|[a-zæøå][A-ZÆØÅ]" ,
intraChars : "[a-zæøå\d']" ,
intraContr : "'[a-zæøå]{1,2}\b" ,
} ;
// Latin + Russian
let opts = { alpha : "a-zа-яё" } ;
// OR
let opts = {
interSplit : "[^A-Za-zА-ЯЁа-яё\d']+" ,
intraSplit : "[a-z][A-Z]|[а-яё][А-ЯЁ]" ,
intraBound : "[A-Za-zА-ЯЁа-яё]\d|\d[A-Za-zА-ЯЁа-яё]|[a-z][A-Z]|[а-яё][А-ЯЁ]" ,
intraChars : "[a-zа-яё\d']" ,
intraContr : "'[a-z]{1,2}\b" ,
} ;
// Unicode / universal (50%-75% slower)
let opts = {
unicode : true ,
interSplit : "[^\p{L}\d']+" ,
intraSplit : "\p{Ll}\p{Lu}" ,
intraBound : "\p{L}\d|\d\p{L}|\p{Ll}\p{Lu}" ,
intraChars : "[\p{L}\d']" ,
intraContr : "'\p{L}{1,2}\b" ,
} ;
Semua penelusuran saat ini tidak peka huruf besar-kecil; tidak mungkin melakukan penelusuran peka huruf besar-kecil.
CATATAN: File testdata.json adalah kumpulan data 162.000 string/frasa yang beragam dan berukuran 4 MB, jadi pemuatan pertama mungkin lambat karena transfer jaringan. Coba segarkan setelah di-cache oleh browser Anda.
Pertama, uFuzzy secara terpisah untuk menunjukkan kinerjanya.
https://leeoniya.github.io/uFuzzy/demos/compare.html?libs=uFuzzy&search=super%20ma
Sekarang halaman perbandingan yang sama, di-boot dengan fuzzysort, QuickScore, dan Fuse.js:
https://leeoniya.github.io/uFuzzy/demos/compare.html?libs=uFuzzy,fuzzysort,QuickScore,Fuse&search=super%20ma
Berikut adalah daftar perpustakaan lengkap tetapi dengan kumpulan data yang dikurangi (hanya hearthstone_750
, urls_and_titles_600
) untuk menghindari kerusakan pada browser Anda:
https://leeoniya.github.io/uFuzzy/demos/compare.html?lists=hearthstone_750,urls_and_titles_600&search=moo
Jawaban:
Lainnya: https://github.com/leeoniya/uFuzzy/issues
npm i @leeoniya/ufuzzy
const uFuzzy = require ( '@leeoniya/ufuzzy' ) ;
< script src = "./dist/uFuzzy.iife.min.js" > < / script >
let haystack = [
'puzzle' ,
'Super Awesome Thing (now with stuff!)' ,
'FileName.js' ,
'/feeding/the/catPic.jpg' ,
] ;
let needle = 'feed cat' ;
let opts = { } ;
let uf = new uFuzzy ( opts ) ;
// pre-filter
let idxs = uf . filter ( haystack , needle ) ;
// idxs can be null when the needle is non-searchable (has no alpha-numeric chars)
if ( idxs != null && idxs . length > 0 ) {
// sort/rank only when <= 1,000 items
let infoThresh = 1e3 ;
if ( idxs . length <= infoThresh ) {
let info = uf . info ( idxs , haystack , needle ) ;
// order is a double-indirection array (a re-order of the passed-in idxs)
// this allows corresponding info to be grabbed directly by idx, if needed
let order = uf . sort ( info , haystack , needle ) ;
// render post-filtered & ordered matches
for ( let i = 0 ; i < order . length ; i ++ ) {
// using info.idx here instead of idxs because uf.info() may have
// further reduced the initial idxs based on prefix/suffix rules
console . log ( haystack [ info . idx [ order [ i ] ] ] ) ;
}
}
else {
// render pre-filtered but unordered matches
for ( let i = 0 ; i < idxs . length ; i ++ ) {
console . log ( haystack [ idxs [ i ] ] ) ;
}
}
}
uFuzzy menyediakan uf.search(haystack, needle, outOfOrder = 0, infoThresh = 1e3) => [idxs, info, order]
yang menggabungkan langkah filter
, info
, sort
di atas. Metode ini juga menerapkan logika efisien untuk mencocokkan istilah pencarian yang tidak berurutan dan mendukung beberapa pengecualian substring, misalnya fruit -green -melon
.
Dapatkan kecocokan pesanan Anda terlebih dahulu:
let haystack = [
'foo' ,
'bar' ,
'cowbaz' ,
] ;
let needle = 'ba' ;
let u = new uFuzzy ( ) ;
let idxs = u . filter ( haystack , needle ) ;
let info = u . info ( idxs , haystack , needle ) ;
let order = u . sort ( info , haystack , needle ) ;
Penyorot innerHTML dasar ( <mark>
-rentang terbungkus):
let innerHTML = '' ;
for ( let i = 0 ; i < order . length ; i ++ ) {
let infoIdx = order [ i ] ;
innerHTML += uFuzzy . highlight (
haystack [ info . idx [ infoIdx ] ] ,
info . ranges [ infoIdx ] ,
) + '<br>' ;
}
console . log ( innerHTML ) ;
penyorot innerHTML dengan fungsi penandaan khusus ( <b>
-rentang terbungkus):
let innerHTML = '' ;
const mark = ( part , matched ) => matched ? '<b>' + part + '</b>' : part ;
for ( let i = 0 ; i < order . length ; i ++ ) {
let infoIdx = order [ i ] ;
innerHTML += uFuzzy . highlight (
haystack [ info . idx [ infoIdx ] ] ,
info . ranges [ infoIdx ] ,
mark ,
) + '<br>' ;
}
console . log ( innerHTML ) ;
Penyorot elemen DOM/JSX dengan penandaan khusus dan fungsi penambahan:
let domElems = [ ] ;
const mark = ( part , matched ) => {
let el = matched ? document . createElement ( 'mark' ) : document . createElement ( 'span' ) ;
el . textContent = part ;
return el ;
} ;
const append = ( accum , part ) => { accum . push ( part ) ; } ;
for ( let i = 0 ; i < order . length ; i ++ ) {
let infoIdx = order [ i ] ;
let matchEl = document . createElement ( 'div' ) ;
let parts = uFuzzy . highlight (
haystack [ info . idx [ infoIdx ] ] ,
info . ranges [ infoIdx ] ,
mark ,
[ ] ,
append ,
) ;
matchEl . append ( ... parts ) ;
domElems . push ( matchEl ) ;
}
document . getElementById ( 'matches' ) . append ( ... domElems ) ;
uFuzzy memiliki dua mode operasional yang berbeda dalam strategi pencocokannya:
example
- tepatexamplle
- penyisipan tunggal (tambahan)exemple
- substitusi tunggal (penggantian)exmaple
- transposisi tunggal (swap)exmple
- penghapusan tunggal (penghilangan)xamp
- sebagianxmap
- parsial dengan transposisiAda 3 fase pencarian:
haystack
penuh dengan RegExp cepat yang dikompilasi dari needle
Anda tanpa melakukan operasi tambahan apa pun. Ini mengembalikan array indeks yang cocok dalam urutan asli.needle
menjadi dua RegExps yang lebih mahal yang dapat mempartisi setiap kecocokan. Oleh karena itu, ini harus dijalankan pada subset tumpukan jerami yang lebih sedikit, biasanya dikembalikan oleh fase Filter. Demo uFuzzy dibatasi pada <= 1.000 item yang difilter, sebelum melanjutkan fase ini.Array.sort()
untuk menentukan urutan hasil akhir, memanfaatkan objek info
yang dikembalikan dari fase sebelumnya. Fungsi pengurutan khusus dapat disediakan melalui opsi uFuzzy: {sort: (info, haystack, needle) => idxsOrder}
.File 200 LoC uFuzzy.d.ts yang dikomentari secara bebas.
Opsi dengan awalan antar berlaku untuk kelonggaran di antara istilah pencarian, sedangkan pilihan dengan awalan intra berlaku untuk kelonggaran dalam setiap istilah pencarian.
Pilihan | Keterangan | Bawaan | Contoh |
---|---|---|---|
intraMode | Bagaimana pencocokan istilah harus dilakukan | 0 | 0 MultiSisipkan1 Kesalahan TunggalLihat Cara Kerjanya |
intraIns | Jumlah maksimum karakter tambahan yang diperbolehkan antara setiap karakter dalam suatu istilah | Cocok dengan nilai intraMode ( 0 atau 1 ) | Mencari "kucing"...0 dapat mencocokkan: cat , s cat , cat ch, va cat e1 juga cocok: car t , cha p t er, out cast |
interIns | Jumlah maksimum karakter tambahan yang diperbolehkan antar persyaratan | Infinity | Mencari "di mana"...Infinity bisa cocok: di mana , di mana bla w adalah dom5 tidak bisa menandingi: di mana ada kebijaksanaan bla |
intraSub intraTrn intraDel | Untuk intraMode: 1 saja,Jenis kesalahan yang harus ditoleransi sesuai ketentuan | Cocok dengan nilai intraMode ( 0 atau 1 ) | 0 Tidak1 Ya |
intraChars | Regexp parsial untuk penyisipan yang diizinkan karakter di antara setiap karakter dalam suatu istilah | [azd'] | [azd] hanya cocok dengan alfanumerik (tidak peka huruf besar-kecil)[w-] akan cocok dengan alfanumerik, garis bawah, dan tanda hubung |
intraFilt | Panggilan balik untuk mengecualikan hasil berdasarkan istilah & kecocokan | (term, match, index) => true | Lakukan sesukamu, mungkin... - Panjangnya berbeda ambang batas - Jarak Levenshtein - Istilah offset atau konten |
interChars | Regexp parsial untuk karakter yang diizinkan antar istilah | . | . cocok dengan semua karakter[^azd] hanya akan cocok dengan spasi dan tanda baca |
interLft | Menentukan batas kiri istilah yang diijinkan | 0 | Mencari "mania"...0 mana saja - dimana saja: ro mania n1 longgar - spasi putih, tanda baca, angka alfa, transisi perubahan huruf besar-kecil: Track Mania , mania c2 ketat - spasi, tanda baca: mania cally |
interRgt | Menentukan batas kanan istilah yang diijinkan | 0 | Mencari "mania"...0 mana saja - dimana saja: ro mania n1 longgar - spasi, tanda baca, angka alfa, transisi perubahan huruf besar-kecil: Mania Star2 ketat - spasi, tanda baca: mania _foo |
sort | Fungsi penyortiran hasil khusus | (info, haystack, needle) => idxsOrder | Default: Pengurutan pencarian, memprioritaskan kecocokan istilah penuh dan kepadatan karakter Demo: Pengurutan typeahead, memprioritaskan offset awal dan panjang kecocokan |
Penilaian ini sangat sempit dan, tentu saja, bias terhadap kasus penggunaan saya, korpus teks, dan keahlian lengkap saya dalam mengoperasikan perpustakaan saya sendiri. Kemungkinan besar saya tidak memanfaatkan sepenuhnya beberapa fitur di perpustakaan lain yang dapat meningkatkan hasil secara signifikan pada beberapa sumbu; Saya menyambut peningkatan PR dari siapa pun yang memiliki pengetahuan perpustakaan lebih dalam daripada yang diperoleh dari penelusuran 10 menit saya yang tergesa-gesa mengenai contoh "Penggunaan dasar" dan dokumen README.
Kaleng cacing #1.
Sebelum kita membahas kinerja, mari kita bahas kualitas penelusuran, karena kecepatan tidak relevan jika hasil Anda berupa "Oh ya!" dan "Apa?".
Kualitas pencarian sangat subyektif. Apa yang termasuk dalam pencocokan teratas yang baik dalam kasus "typeahead/sugesti otomatis" bisa menjadi pencocokan yang buruk dalam skenario "penelusuran/temukan semua". Beberapa solusi mengoptimalkan untuk yang kedua, beberapa untuk yang pertama. Merupakan hal yang umum untuk menemukan tombol-tombol yang mengarahkan hasil ke salah satu arah, namun tombol-tombol ini sering kali tidak terlihat dan tidak sempurna, tidak lebih dari sekadar proksi untuk menghasilkan "skor" pertandingan gabungan tunggal.
PEMBARUAN (2024): Kritik di bawah mengenai kecocokan aneh hanya berlaku untuk konfigurasi default Fuse.js. Berlawanan dengan intuisi, pengaturan ignoreFieldNorm: true
meningkatkan hasil secara signifikan, namun pemesanan kecocokan berkualitas tinggi tetap tidak bagus.
Mari kita lihat beberapa kecocokan yang dihasilkan oleh perpustakaan pencarian fuzzy paling populer, Fuse.js dan beberapa lainnya yang penyorotan kecocokannya diterapkan dalam demo.
Saat menelusuri sebagian istilah "twili" , kami melihat hasil berikut muncul di atas banyak hasil "twilight" yang jelas:
https://leeoniya.github.io/uFuzzy/demos/compare.html?libs=uFuzzy,fuzzysort,QuickScore,Fuse&search=twili
Kecocokan buruk ini tidak hanya terjadi secara terpisah, tetapi peringkatnya sebenarnya lebih tinggi daripada substring literal.
Menyelesaikan istilah pencarian menjadi "twilight" , masih mendapatkan hasil yang lebih aneh:
https://leeoniya.github.io/uFuzzy/demos/compare.html?libs=uFuzzy,fuzzysort,QuickScore,Fuse&search=twilight
Beberapa mesin bekerja lebih baik dengan pencocokan awalan parsial, dengan mengorbankan biaya permulaan/pengindeksan yang lebih tinggi:
https://leeoniya.github.io/uFuzzy/demos/compare.html?libs=uFuzzy,FlexSearch,penyortir pertandingan,MiniSearch&search=twili
Di sini, match-sorter
menghasilkan 1.384 hasil, tetapi hanya 40 hasil pertama yang relevan. Bagaimana kita tahu di mana batasnya?
Kaleng cacing #2.
Semua tolok ukur jelek, tapi yang ini mungkin lebih jelek dari yang lain.
Tetap saja, ada sesuatu yang lebih baik daripada pemecatan YMMV/lakukan sendiri dan tentu saja lebih baik daripada tidak sama sekali.
Lingkungan
Tanggal | 2023-10 |
---|---|
Perangkat keras | CPU: Ryzen 7 PRO 5850U (1,9GHz, 7nm, TDP 15W) RAM: 48GB SSD: Samsung SSD 980 PRO 1TB (NVMe) |
sistem operasi | EndeavourOS (Linux Lengkungan) v6.5.4-arch2-1 x86_64 |
krom | v117.0.5938.132 |
libs
ke nama perpustakaan yang diinginkan: https://leeoniya.github.io/uFuzzy/demos/compare.html?bench&libs=uFuzzybench
untuk menghindari benchmarking pada DOM.test
, chest
, super ma
, mania
, puzz
, prom rem stor
, twil
. Untuk mengevaluasi hasil setiap perpustakaan, atau untuk membandingkan beberapa, cukup kunjungi halaman yang sama dengan lebih banyak libs
dan tanpa bench
: https://leeoniya.github.io/uFuzzy/demos/compare.html?libs=uFuzzy,fuzzysort,QuickScore ,Sekering&pencarian=super%20mA.
Ada beberapa metrik yang dievaluasi:
Lib | Bintang | Ukuran (min) | Init | Mencari (x 86) | Tumpukan (puncak) | Dipertahankan | hal |
---|---|---|---|---|---|---|---|
uFuzzy (coba) | ★ 2,3k | 7.6KB | 0,5 ms | 434 md | 28,4MB | 7,4MB | 18 md |
uFuzzy (coba) (cache awalan eksternal) | 210 md | 27,8MB | 7,4MB | 18 md | |||
uFuzzy (coba) (outOfOrder, lebih kabur) | 545 md | 29,5MB | 7,4MB | 18 md | |||
uFuzzy (coba) (outOfOrder, fuzzier, SingleError) | 508ms | 30,0MB | 7,4MB | 18 md | |||
------- | |||||||
Fuse.js (coba) | ★ 16.6k | 24.2KB | 31 md | 33875ms | 245MB | 13,9MB | 25 md |
FlexSearch (Ringan) (coba) | ★ 10.7k | 6.2KB | 3210ms | 83 md | 670MB | 316MB | 553ms |
Lunr.js (coba) | ★ 8.7k | 29.4KB | 1704ms | 996 md | 380MB | 123MB | 166 md |
Orama (sebelumnya Lyra) (coba) | ★ 6.4k | 41.5KB | 2650 ms | 225 md | 313MB | 192MB | 180 md |
MiniSearch (coba) | ★ 3,4k | 29.1KB | 504ms | 1453ms | 438MB | 67MB | 105 md |
penyortir kecocokan (coba) | ★ 3,4k | 7.3KB | 0,1 ms | 6245ms | 71MB | 7,3MB | 12 md |
fuzzysort (coba) | ★ 3,4k | 6.2KB | 50 md | 1321 md | 175MB | 84MB | 63 md |
Wade (coba) | ★ 3k | 4KB | 781 md | 194 md | 438MB | 42MB | 130 md |
pencarian fuzzy (coba) | ★ 2,7k | 0,2 KB | 0,1 ms | 529 md | 26,2MB | 7,3MB | 18 md |
js-pencarian (coba) | ★ 2,1k | 17.1KB | 5620ms | 1190 md | 1740MB | 734MB | 2600 ms |
Elasticlunr.js (coba) | ★ 2k | 18.1KB | 933ms | 1330 md | 196MB | 70MB | 135 md |
Fuzzyset (coba) | ★ 1,4k | 2.8KB | 2962 ms | 606ms | 654MB | 238MB | 239 md |
indeks pencarian (coba) | ★ 1,4k | 168KB | RangeError: Ukuran tumpukan panggilan maksimum terlampaui | ||||
ayakan.js (coba) | ★ 1,1k | 7.5KB | 3 ms | 1070 md | 46,2MB | 10,6MB | 18 md |
fzf-for-js (coba) | ★831 | 15.4KB | 50 md | 6290ms | 153MB | 25MB | 18 md |
kabur (coba) | ★819 | 1.4KB | 0,1 ms | 5427ms | 72MB | 7,3MB | 14 md |
cepat-fuzzy (coba) | ★ 346 | 18.2KB | 790 md | 19266ms | 550MB | 165MB | 140 md |
ItemsJS (coba) | ★ 305 | 109KB | 2400 md | 11304ms | 320MB | 88MB | 163 md |
LiquidMetal (coba) | ★ 292 | 4.2KB | (menabrak) | ||||
Pencarian Fuzzy (coba) | ★209 | 3,5 KB | 2 ms | 3948ms | 84MB | 10,5MB | 18 md |
FuzzySearch2 (coba) | ★186 | 19.4KB | 93 md | 4189ms | 117MB | 40,3MB | 40 md |
Skor Cepat (coba) | ★153 | 9,5 KB | 10 md | 6915ms | 133MB | 12.1MB | 18 md |
ndx (coba) | ★ 142 | 2.9KB | 300 md | 581 md | 308MB | 137MB | 262 md |
fzy (coba) | ★ 133 | 1,5 KB | 0,1 ms | 3932 ms | 34MB | 7,3MB | 10 md |
alat fuzzy (coba) | ★ 13 | 3KB | 0,1 ms | 5138ms | 164MB | 7,5MB | 18 md |
fuzzyMatch (coba) | ★ 0 | 1KB | 0,1 ms | 2415ms | 83,5MB | 7,3MB | 13 md |