從零構建搜索引擎 build demo search engine from scratch

image

從零構建搜索引擎 build demo search engine from scratch

image

我們每天都會使用搜索引擎:打開google等搜索引擎,輸入關鍵詞,檢索出結果,這是一次搜索;當打開歷史記錄旁邊的🔍按鈕,輸入關鍵詞,檢索出結果,這也是一次搜索。
本文將從原理開始到最終實現一個搜索demo,探究搜索引擎背后是如何實現“一瞬間”從海量數據中檢索出想要的結果的。

配合本文,實現了一個本文講述的搜索引擎的demo用于檢索內容,支持AND、包含、排除查詢等常見的查詢,也附帶上簡單的benchmark對比,可見我的github倉庫:https://github.com/578223592/go-small-practices/tree/main/search_engine

全文搜索

全文搜索可以簡單的定義為:根據輸入的查詢語句(query),在一組文檔(document)中,篩選出符合條件的文檔。

過程

要實現全文搜索,我們可以想象到最簡單的方式就是循環去匹配,like如下的偽代碼,想象一下這種循環會在文檔數量增加和查詢復雜后產生多少次循環匹配計算:

    a = ["just", "do"] #查詢條件count = 0  #匹配數量for d in tokenized_documents:  #遍歷文檔for t in d:  #遍歷文檔中的每個語句for c in a: # 遍歷查詢條件if c == t: # 如果匹配,則count++count += 1return count

因此在現實世界,會對文檔進行預處理(分析),分析之后產生索引。查詢的時候根據索引去匹配定位到源文件,比如下圖這樣的管道:

image?

實際上索引是不同查詢引擎里面影響性能的關鍵,我們通常可以用如下指標來評價一個搜索索引的好壞:

  • 計算索引的速度
  • 壓縮率
  • 可拓展性
  • 查詢性能

由于本人目前對搜索索引的了解也不是特別深入,因此這里就不賣弄了。

關鍵詞

?document?:一組單詞。

Do it. Just do it. Don’t let your dreams be dreams. Yesterday, you said tomorrow. So just do it. Make your dreams come true. Just do it. Some people dream of success, while you’re gonna wake up and work hard at it. Nothing is impossible. You should get to the point where anyone else would quit, and you’re not gonna stop there. No, what are you waiting for? Do it! Just do it! Yes you can. Just do it.

?term?:詞。

just

?token?:用戶輸入的查詢詞(或其變體)在被檢索文檔中出現的每一個具體匹配實例。

Just do it. .... So just do it. Make your dreams come true. Just do it. ... Just do .... Just do it.

?query?:指定特定格式的查詢語句

以下舉例的是本文構建出來的搜索引擎支持的查詢格式,不同引擎的語句可能是不同的。

just:查詢包含just的文檔

just do:查詢包含just或者包含do的文檔

just AND do:查詢同時包含just和do的文檔

"just do":查詢順序出現just和do的文檔

just do+it:出現it的文檔中,查詢包含just或者包含do的文檔

just do -it:查詢包含just或者包含do的文檔,再剔除掉包含it的文檔。

?score?:文檔匹配的分數,如果多個文檔都符合查詢條件,那么文檔的列表會按照分數從高到低給出。本文搜索引擎是按照文檔中匹配到的詞的個數來評分,現實世界中的評分會參考更多因素, 比如說地區、性別等等。

?index?:索引,搜索的索引為inverted-index?(倒序索引),其可以根據關鍵詞尋找關鍵詞出現的位置,通常的結構如下。這是搜索引擎如此之快的關鍵奧秘!通過inverted-index?,我們尋找關鍵詞不再需要通過遍歷文檔,而是可以直接通過類似key->value查找的方式,速度非常快。

just -> [(1, 0), (1, 5), (1, 20), (1, 28), (1, 76), (1, 82)] #含義是just出現的位置是document1的第0個位置,document1的第5個位置,document1的第20個位置。。。
do -> [(0, 94), (0, 399), (0, 455), (0, 493), (1, 1), (1, 3), (1, 6), (1, 21), (1, 29), (1, 74), (1, 77), (1, 83), (2, 15), (2, 33), (2, 44)] #含義是do出現的位置是。。。

搜索索引稱為inverted-index?的原因是通常的索引是根據位置去找內容,而inverted-index是根據內容去找位置,是inverted版本的普通索引。

關鍵算法實現

創建倒序索引:

func (a *Analyser) AnalyseAndIndex(documents []string) {for docId, oneDocument := range documents {tokens := regexp.MustCompile(`\w+`).FindAllString(oneDocument, -1) // 使用正則將文檔拆解成termfor index, token := range tokens {token = strings.ToLower(token)local_ := local{DocId: int64(docId), Indexes: int64(index)} //use inverted index to index termsif a.index == nil {a.index = make(map[string][]local)}a.index[token] = append(a.index[token], local_)}}
}

?

搜索:

我們將文檔檢索分成四種模式,如下代碼塊所示,默認來說是OR模式,如果出現其他模式的關鍵詞,那么就會調整對應的搜索模式:

const (AND     searchModeExp = "AND"OR      searchModeExp = "OR"INCLUDE searchModeExp = "+"EXCLUDE searchModeExp = "-"
)

我們創建了兩個結果數組,分別用于記錄結果和記錄必須要排除掉的文檔(EXCLUDE searchModeExp = "-"?):

includeDocScoresMap := make(map[int64]float64)excludeDocMap := make(map[int64]any)

此外,還需要注意的是,為了支持"just do"?這樣保證順序的查找,我們需要匹配成對出現的"?,然后根據其中的內容去文檔中固定匹配內容,而不能直接簡單組合不同文檔的內容。

整個搜索過程關鍵算法如下:

func (a *Analyser) Search(keyWords string) []SearchRes {includeDocScoresMap := make(map[int64]float64)excludeDocMap := make(map[int64]any)queryExpressions := a.parseQuery(keyWords)var searchMode = OR        // for i := 0; i < len(queryExpressions); i++ {var locals []localif isSearchMode(queryExpressions[i]) { //調整搜索模式searchMode = searchModeExp(queryExpressions[i])continue} else if queryExpressions[i] == Quote {endIndex := stringsIndex(queryExpressions[i+1:], Quote)if endIndex == -1 {continue // 缺少結束引號,忽略}if endIndex == 0 {continue // 引號中間沒有任何exp,忽略}quoteQueryExpressions := queryExpressions[i+1 : i+1+endIndex]i += endIndex + 1for i2 := range quoteQueryExpressions {quoteQueryExpressions[i2] = strings.ToLower(quoteQueryExpressions[i2])}firstExp, otherExp := quoteQueryExpressions[0], quoteQueryExpressions[1:]var ok boollocals, ok = a.index[firstExp]if !ok {// not found,early continuecontinue}for _, exp := range otherExp {locals = a.findExpInIndex(locals, exp)}} else {//	searchlocals = a.index[queryExpressions[i]]}switch searchMode {case AND:for _, l := range locals {if _, ok := includeDocScoresMap[l.DocId]; ok {includeDocScoresMap[l.DocId] += 1}}case OR:for _, l := range locals {includeDocScoresMap[l.DocId] += 1}case INCLUDE:locsMap := make(map[int64]float64, len(includeDocScoresMap))for _, l := range locals {locsMap[l.DocId] = includeDocScoresMap[l.DocId] + 1}includeDocScoresMap = locsMapcase EXCLUDE:for _, l := range locals {excludeDocMap[l.DocId] = nil}}searchMode = OR}res := make([]SearchRes, 0)for k, v := range includeDocScoresMap {if _, ok := excludeDocMap[k]; ok {continue}res = append(res, SearchRes{DocId: k, Score: v})}slices.SortFunc(res, func(a, b SearchRes) int {return int(b.Score - a.Score) // order by score desc})return res
}

?

真正的搜索引擎考慮更多內容,比如說搜索計劃、ast、黑客、錯誤糾正、類型匹配(比如說搜索攀枝花,推薦攀枝花城市、攀枝花花朵等)。

?

總結

本文通過講解搜索中analyze-》index—》query步驟,其中出現的關鍵名詞,和一個簡單的demo代碼,講解了搜索引擎實現的關鍵技術, 希望對你有所幫助!

?

參考:

  • strong thanks :Building a search engine from scratch | by Halvor Fladsrud B? | Medium

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914485.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914485.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914485.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

pytorch小記(二十九):深入解析 PyTorch 中的 `torch.clip`(及其別名 `torch.clamp`)

pytorch小記&#xff08;二十九&#xff09;&#xff1a;深入解析 PyTorch 中的 torch.clip&#xff08;及其別名 torch.clamp&#xff09;深入解析 PyTorch 中的 torch.clip&#xff08;及其別名 torch.clamp&#xff09;一、函數簽名二、簡單示例三、廣播支持四、與 Autograd…

快速分頁wpf

/*沒有在xaml設置上下文window.context是因為 命名空間一直對應不上 所以在xaml.cs 里面綁定*/ <Window x:Class"DataGrid.views.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft…

如何徹底禁用 Chrome 自動更新

如何徹底禁用 Chrome 自動更新 隨著谷歌將 Chrome 瀏覽器版本升級至 138&#xff0c;它即將徹底拋棄對 Manifest V2 擴展的支持。許多用戶希望將瀏覽器版本鎖定在 138&#xff0c;以繼續使用 uBlock Origin、Tampermonkey 等常用擴展。 本文總結了四種有效方法&#xff0c;幫助…

流批一體的“奧卡姆剃刀”:Apache Cloudberry 增量物化視圖應用解析

引言&#xff1a;流批一體&#xff0c;理想與現實的鴻溝 在數據驅動的今天&#xff0c;“實時”二字仿佛擁有魔力&#xff0c;驅使著無數企業投身于流批一體架構的建設浪潮中。我們渴望實時洞察業務變化&#xff0c;實時響應用戶需求。以 Apache Flink 為代表的流處理引擎&…

C# 入門教程(三):詳解字段、屬性、索引器及各類參數與擴展方法

文章目錄一、字段、屬性、索引器、常量1.字段2.屬性2.1 什么是屬性2.2 屬性的聲明2.3 屬性與字段的關系3 索引器4. 常量二、傳值 輸出 引用 數組 具名 可選參數&#xff0c;擴展方法2.1 傳值參數2.1.1 值類型 傳參2.1.2 引用類型 傳參2.2 引用參數2.2.1 引用參數-值類型 傳參2.…

《美術教育研究》是什么級別的期刊?是正規期刊嗎?能評職稱嗎?

?問題解答&#xff1a;問&#xff1a;《美術教育研究》是不是核心期刊&#xff1f;答&#xff1a;不是&#xff0c;是知網收錄的第一批認定學術期刊。問&#xff1a;《美術教育研究》級別&#xff1f;答&#xff1a;省級。主管單位&#xff1a; 安徽出版集團有限責任公司 主辦…

每日算法刷題Day47:7.13:leetcode 復習完滑動窗口一章,用時2h30min

思考: 遇到子數組/子字符串可以考慮能不能用滑動窗口&#xff0c; 定長:逆向思維,答案不定 最大長度/最小長度:一般求長度 越長越合法/越短越合法/恰好:一般求數量 主要思考窗口條件成立&#xff0c; 判斷條件是符合窗口條件(最小長度/越長越合法還是不符合(最大長度/越短越合法…

電流驅動和電壓驅動的區別

理解電流驅動和電壓驅動的區別對電路設計至關重要&#xff0c;尤其在高速、高抗噪要求的場景&#xff08;如LVDS&#xff09;。以下是兩者的核心對比&#xff1a;一、電壓驅動 (Voltage Drive) 核心原理&#xff1a; 驅動器輸出一個受控的電壓&#xff08;與負載阻抗無關&#…

宿舍電費查詢——以ZUA為例

宿舍電費查詢——以ZUA為例0. 安裝抓包環境手機端桌面端1. 登錄1.1 開啟抓包后進入繳費頁面&#xff1a;1.2 分析請求1.3 編寫登錄代碼2. 獲取樓棟及房間ID2.1 獲取樓棟ID2.2 編寫獲取樓棟ID代碼2.3 獲取房間ID2.4 編寫獲取房間ID代碼3. 獲取剩余電費&#xff1a;3.1 選擇房間號…

vue中計算屬性的介紹

Vue.js 中的計算屬性是基于它的響應式系統來實現的&#xff0c;它可以根據 Vue 實例的數據狀態來動態計算出新的屬性值。在 Vue 組件中&#xff0c;計算屬性常用于對數據進行處理和轉換&#xff0c;以及動態生成一些需要的數據。一、使用方式1.定義計算屬性&#xff1a; 在Vue組…

MFC UI控件CheckBox從專家到小白

文章目錄CheckBox勾選框控件控件與變量綁定控件點擊消息映射互斥CheckBox勾選框控件 控件與變量綁定 方案一&#xff1a; BOOL m_bEnable1; BOOL m_bEnable2; void A::DoDataExchange(CDataExchange* pDX) {DDX_Check(pDX, IDC_CK_1, m_bEnable1);DDX_Check(pDX, IDC_CK_2, …

阿爾卡特ACT 250 ATP 150 AND ATP 400 分子泵控制器TURBOMOLECULAR PUMP CONTROLLER ALCATEL

阿爾卡特ACT 250 ATP 150 AND ATP 400 分子泵控制器TURBOMOLECULAR PUMP CONTROLLER ALCATEL

python的小學課外綜合管理系統

前端開發框架:vue.js 數據庫 mysql 版本不限 后端語言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 數據庫工具&#xff1a;Navicat/SQLyog等都可以 摘要 隨著…

實用技巧 Excel 與 XML互轉

一 概述 在android多語言適配中&#xff0c;可能提供的是excel格式的多語言翻譯&#xff0c;而且翻譯數量非常龐大。那手動一個一個往xml里面添加效率非常低&#xff0c;這時候就需要把excel快速轉為android可以直接用的資源文件string.xml二 轉換流程2.1 第一步任意文件夾或者…

云原生技術與應用-Containerd容器技術詳解

目錄 一.Containerd概述 1.什么是containerd 2.Containerd的起源與背景 二.Containerd架構 1.Containerd架構概述 2.核心組件解析 三.安裝配置Containerd 1.安裝Containerd 2.配置Containerd 四.Containerd基本操作 1.鏡像類操作 2.容器類操作 3.任務類操作 4.其他操作 一.…

LINUX714 自動掛載/nfs;物理卷

開機自動掛載 /etc/fstab vim /etc/fstab /dev/sdb2 /u2 ext4 defaults 0 0 mount -a [rootweb ~]# vim /etc/fstab [rootweb ~]# cat /etc/fstab# # /etc/fstab # Created by anaconda on Sat Apr 19 17:11:28 2025 # # Accessible filesystems, by reference, are maintai…

系統性學習C語言-第十六講-深入理解指針(6)

系統性學習C語言-第十六講-深入理解指針&#xff08;6&#xff09;1. sizeof 和 strlen 的對比1.1 sizeof 1.2 strlen 1.3 sizeof 和 strlen 的對比2. 數組和指針筆試題解析2.1 一維數組2.2 字符數組2.3 二維數組3. 指針運算筆試題解析3.1 題目1&#xff1a;3.2 題目…

8:從USB攝像頭把聲音拿出來--ALSA大佬登場!

前言前面的章節我們從認識攝像頭開始&#xff0c;逐漸認識的YCbCr&#xff0c;并對其進行了H264的編碼以及MP4封裝。整個過程中&#xff0c;我們大致使用了V4L2和FFmpeg這兩個重量級工具&#xff0c;就像我們前面章節所講&#xff0c;V4L2只是給圖像做服務的&#xff0c;并不參…

Linux 命令:useradd

Linux useradd 命令詳細教程 useradd 是 Linux 系統中用于創建新用戶賬戶的基礎命令&#xff0c;它通過配置文件&#xff08;如 /etc/passwd、/etc/shadow&#xff09;和默認設置自動完成用戶創建流程。本文將詳細介紹其用法、參數及相關配置。資料已經分類整理好&#xff1a;h…

Pytest之收集用例規則與運行指定用例

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 小伙伴們大家好呀&#xff0c;今天筆者會給大家講解一下pytest是如何收集我們寫好的用例&#xff1f;我們又有哪些方式來運行單個用例或者批量運行用例呢&#xff…