“http://npm.im/libsearch” rel=”nofollow”>”https://camo.githubusercontent.com/7e1f8fadcce70ef95d9ad0ef398ec77c0f8197deab7af6deae328a340346f955/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6c69627365617263682e737667″ البديل=”npm libsearch” data-canonical-src=”https://img.shields.io/npm/v/libsearch.svg”>“https://camo.githubusercontent.com/0e86c5d9aab13936a47f424efe24775cb4721fadc31f2549aaf84689127ad39e/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f74797065732f6c69627365617263682e737667″ البديل=”TypeScript types” data-canonical-src=”https://img.shields.io/npm/types/libsearch.svg”>“https://camo.githubusercontent.com/b9f13285d7e5f127e8a964e8dbf48d39dab6b97fbf65922830795c88edb0d8cf/68747470733a2f2f6170702e7472617669732d63692e636f6d2f746865736570686973742f6c69627365617263682e7376673f6272616e63683d6d61696e” البديل=”Build Status” data-canonical-src=”https://app.travis-ci.com/thesephist/libsearch.svg?branch=main”>
بحث نصي بسيط وخالي من الفهرس لـ JavaScript، يُستخدم عبر مشاريعي الشخصية مثل”https://ycvibecheck.com/” rel=”nofollow”> فحص YC فيبي,”https://linus.zone/entr” rel=”nofollow”>linus.zone/entr، وبرامجي الإنتاجية الشخصية. اقرأ”https://thesephist.github.io/libsearch/lib/search.ts.html” rel=”nofollow”> مصدر مشروح لفهم كيف يعمل تحت غطاء محرك السيارة.
“-1” دير=”auto”> واجهة برمجة التطبيقات
لنبدأ ببعض الأمثلة السريعة:
search
وظيفة. تأخذ هذه الدالة وسيطتين مطلوبتين ووسيطين اختياريين:
items
هي قائمة العناصر للبحث. عادة items
سيكون مصفوفة من السلاسل أو مصفوفة من الكائنات مع بعض خصائص السلسلة.query
عبارة عن استعلام سلسلة يمكن من خلاله البحث في قائمة العناصر.by
(خياري) هي دالة أصلية تأخذ عنصرًا منها items
وتقوم بإرجاع قيمة سلسلة يمكن من خلالها البحث عن هذا العنصر. على سبيل المثال، إذا items
هي قائمة من الكائنات مثل { name: 'Linus' }
, by
سوف تحتاج إلى أن تكون وظيفة x=> x.name
. هذا له قيمة x=> String(x)
بشكل افتراضي، والذي يعمل ل items
من النوع string[]
.options
(خياري) هو قاموس الخيارات:”auto”>caseSensitive
يجعل البحث حساسًا لحالة الأحرف. إنه false
بشكل افتراضي.mode
يتحكم في الطريقة التي تتم بها مطابقة كلمات الاستعلام غير المكتملة:”auto”>mode: 'word'
يتطلب أن تتطابق كل كلمة استعلام مع الكلمات الكاملة والدقيقة فقط وليس أجزاء من الكلمات. على سبيل المثال، الاستعلام “California” سوف تتطابق “University of California” ولكن لا “Californian University”.mode: 'prefix'
يعني أن كل كلمة استعلام قد تكون غير مكتملة “prefix” للكلمة المطابقة. “Uni Cali” سوف تتطابق مع كليهما “University of California” و “Californian University” حتى في هذا الوضع، يجب أن تتطابق كل كلمة استعلام في مكان ما — “California” ليست مطابقة، لأنها لا تتطابق مع كلمة الاستعلام “Uni”.mode: 'autocomplete'
هو مزيج من الوضعين الآخرين وهو مفيد عند استخدامه في عمليات البحث بنمط الإكمال التلقائي، حيث يقوم المستخدم بكتابة استعلام بشكل مستمر أثناء إرجاع نتائج البحث. هذا الوضع مطابق ل mode: 'word'
، باستثناء أن كلمة الاستعلام الأخيرة قد تكون غير مكتملة كما في mode: 'prefix'
. يعني “University of Cali” سوف تتطابق “University of California”، وهو أمر مفيد لأن المستخدم قد يجد المطابقة الخاصة به قبل كتابة الاستعلام الكامل الخاص به.يمكنك العثور على المزيد من الأمثلة حول كيفية دمج هذه الخيارات معًا في”http://github.com/thesephist/libsearch/blob/main/test/search.js”> اختبارات الوحدة.
“-1” دير=”auto”> التثبيت والاستخدام
“-1” دير=”auto”> على شبكة الإنترنت، مع قم بإسقاط هذا في HTML الخاص بك:
<البرنامج النصي src="https://unpkg.com/libsearch/dist/browser.js">البرنامج النصي>
هذا سوف يفضح search
تعمل ك window.libsearch.search
.
npm install libsearch# oryarn add libsearch
واستخدمه في الكود الخاص بك:
import { search } from 'libsearch';// search(...);
“-1” دير=”auto”>استخدام أنواع TypeScript
يأتي libsearch مع تعريفات نوع TypeScript التي تم إنشاؤها من الملف المصدر. إن استخدام libsearch من NPM يجب أن يلتقطها مترجم TypeScript.
يتيح لك libsearch إجراء بحث أساسي عن النص الكامل عبر قائمة كائنات JavaScript بسرعة، دون الحاجة إلى فهرس بحث مُعد مسبقًا، مع تقديم عرض جيد بشكل معقول”https://en.wikipedia.org/wiki/Tf%E2%80%93idf” rel=”nofollow”> قوات الدفاع الإسرائيلية ترتيب النتائج. لا يقدم مجموعة واسعة من الميزات التي تأتي مع المكتبات مثل”https://github.com/nextapps-de/flexsearch”> البحث المرن و”https://lunrjs.com/” rel=”nofollow”>lunr.js، ولكنها خطوة كبيرة أعلاه text.indexOf(query)> -1
، وهو سريع بما يكفي ليكون قابلاً للاستخدام للبحث في آلاف المستندات عند كل ضغطة مفتاح في تجربتي.
هناك فكرتان رئيسيتان حول كيفية تقديم libsearch لهذا:
“-1” دير=”auto”>1. تحويل الاستعلامات إلى تعبيرات عادية
يتم شحن محركات JavaScript الحديثة مع”https://v8.dev/blog/non-backtracking-regexp” rel=”nofollow”> محركات التعبير العادي المحسنة للغاية، ويستفيد libsearch من ذلك لإجراء بحث نصي سريع وخالي من الفهرس عن طريق تحويل سلاسل الاستعلام إلى مرشحات تعبير عادية في وقت البحث.
تعمل معظم مكتبات البحث عن النص الكامل من خلال مطالبة المطور أولاً بإنشاء ملف “index” تقوم بنية البيانات بتعيين مصطلحات البحث إلى المستندات التي تظهر فيها. عادةً ما تكون هذه مقايضة جيدة، لأنها تحرك بعض الأعمال الحسابية “searching” يجب إجراؤه مسبقًا، بحيث يظل البحث نفسه سريعًا ودقيقًا. كما أنه يسمح بإجراء تحويلات خيالية وتنظيف البيانات مثل”https://nlp.stanford.edu/IR-book/html/htmledition/stemming-and-lemmatization-1.html” rel=”nofollow”> lemmatization على البيانات المفهرسة دون تدمير سرعة البحث. ولكن عند إنشاء نماذج أولية وتطبيقات ويب بسيطة، لم أرغب في كثير من الأحيان في تحمل التعقيد الناتج عن وجود جهاز منفصل “indexing” خطوة للحصول على “good enough” حل البحث. يجب تخزين الفهرس في مكان ما وصيانته باستمرار مع تغير مجموعة البيانات الأساسية ونموها.
المهمة الرئيسية لفهرس البحث هي رسم الخرائط “tokens” أو الكلمات الرئيسية التي تظهر في مجموعة البيانات إلى المستندات التي تظهر فيها، بحيث يكون السؤال “which documents contain the word X?” سريع (O(1)
) للإجابة في وقت البحث. بدون فهرس، يتحول هذا إلى ملف O(n)
سؤال، حيث يجب مسح كل مستند ضوئيًا بحثًا عن الكلمة الأساسية. ولكن في كثير من الأحيان، على الأجهزة الحديثة، بالنسبة لمجموعات البيانات الصغيرة بما يكفي (بضعة ميغابايت) النموذجية في تطبيق الويب من جانب العميل، n
صغيرة جدًا، صغيرة بما يكفي لذلك O(n)
في كل ضغطة مفتاح ليست ملحوظة.
يقوم libsearch بتحويل استعلام مثل “Uni of California” في قائمة مرشحات التعبير العادي، (^|W)Uni($|W)
, (^|W)of($|W)
, (^|W)California
. ثم ذلك “searches” دون الحاجة إلى فهرس عن طريق تصفية المجموعة من خلال كل من تلك التعبيرات العادية.
“-1” دير=”auto”>2. “Good enough” يعتمد تصنيف TF-IDF على تطابقات RegExp وطول المستند
يتم حساب مقياس TF-IDF التقليدي لكل كلمة على النحو التالي:
(# matches) / (# words in the doc) * log(# total docs / # docs that matched)
يتطلب الحصول على عدد الكلمات في مستند ترميز المستند، أو على الأقل تقسيم المستند بمسافات بيضاء، وهو أمر مكلف من الناحية الحسابية. لذلك يقوم libsearch بتقريب ذلك باستخدام طول المستند (عدد الأحرف) بدلاً من ذلك.
باستخدام استعلامات التعبير العادي الموضحة أعلاه، تكون صيغة TF-IDF الخاصة بـ libsearch هي:
(# RegExp matches) / (doc.length) * log(# docs / # docs that matched RegExp)
والتي يتم حسابها لكل كلمة أثناء إجراء البحث، ثم يتم تجميعها في النهاية للفرز.
الكود المصدري لـ libsearch هو”http://github.com/thesephist/libsearch/blob/main/lib/search.ts”>مكتوب بلغة TypeScript. للسماح باستخدام المكتبة عبر TypeScript وVilla Node.js والويب، قمنا بتجميع نسختين:
- ال بناء وحدة ES، وهو مجرد
search.ts
تم فحص النوع وإزالة الأنواع. هذا هو الرمز الذي تم استيراده عندماlibsearch
يتم استيراده في Node.js - ال بناء المتصفح، الذي يصدر الرئيسي
search
وظيفة إلىwindow.libsearch
عالمي
يتم إنتاج بناء الوحدة النمطية ES باستخدام tsc
ويتم إنتاج برنامج التحويل البرمجي TypeScript والبنية المصغرة للمتصفح باستخدام Webpack.
أوامر NPM/الغزل:
lint
وfmt
، والذي يقوم بتنسيق كود المصدر تلقائيًا في المستودعtest
إجراء اختبارات الوحدة على أحدث إصدار للمكتبة؛ يجب عليك تشغيلbuild:tsc
قبل الجريtest
- متنوع
build:*
تقوم الأوامر بتنسيق إنتاج الأنواع المختلفة من بنيات المكتبة:”auto”> build:tsc
يبني بناء وحدة ESbuild:w
يديرbuild:tsc
على كل ملف الكتابةbuild:cjs
يبني بناء المتصفح من بناء وحدة ESbuild:all
يبني كلا البناءين بالترتيب
clean
يزيل جميع الملفات التي تم إنشاؤها/إنشاءها dist/
docs
يبني”https://github.com/thesephist/litterate”> متعلمالوثائق المستندة إلى، والتي تعيش في”https://thesephist.github.io/libsearch/lib/search.ts.html” rel=”nofollow”>thesephist.github.io/libsearch.قبل الانتقال إلى الصفحة الرئيسية أو النشر، عادةً ما أقوم بالتشغيل
yarn fmt && yarn build:all && yarn test && yarn docs
للتأكد من أنني لم أنس أي شيء.