付费下载
下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
12-面向对象(下):如何实现一个搜索引擎 个Python的搜索引擎(searchengine)承接上文,今天这节课的主要目的是,带你模拟敏捷开发过程中的迭发流程,巩固面向对象的程序设计思想。从最简单最直接的搜索做起,步步优化,这其中,我不会涉及到过多的超纲算法,但不可避免会介绍些现代搜索引擎中的基础概念,例如语料(corpus)、倒序索引(invertedindex)等。如果你对这方面本身有些了解,自然可以轻松理解;即使你之前完全没接触过搜索引擎,也不用过分担心,我会力求简洁清晰,降低学习难度。同时,我希望你把的精力放在面向对象的建模思。 告,则成了硅谷和中国巨头的商业模式;而搜索本身,也在持续进步着,和 门课程叫做Thelifeofaquery,内容是讲用户在浏览器中键入串文字,按下回车后发生了什么。今天我也按照这个思路,来简单介绍下。 搜索器,通俗来讲就是我们常提到的爬虫(crawe),它能在互联网上大量爬取各类的内容,送给索引器。索引器拿到网页和内容后,会对内容进行处理,形成索引(index),于内部的数据库等待检索。最后的用户接口很好理解,是指网页和App前端界面,例如和谷歌的搜索页面。用户通过用户接口,##haveadreamthatmyfourlittlechildrenwillonedayliveinanationwheretheywillnotbejudged#haveadreamthatonedaydowninAlabama,withitsviciousracists,...onedayrighttherein#haveadreamthatonedayeveryvalleyshallbeexalted,everyhillandmountainshallbemadelow,the##hisisourhope...Withthisfaithwewillbeabletohewoutofthemountainofdespairastoneof#Andwhenthishappens,andwhenweallowfreedomring,whenweletitringfromeveryvillageandevery我们先来定义Seahnginease基类。这里我先给出了具体的代码,你不必着急操作,还是那句话,跟着节奏慢慢学,再难的东西也可以啃得下来。classclassSearchEngineBase(object): init(self):defaddcorpus(self,filepath):withopen(filepath,'r')asfin:text=cesscorpus(filepath,defprocesscorpus(self,id,raiseException('processcorpusnotdefsearch(self,raiseException('searchnotdefmain(searchforfilepathin['1.txt','2.txt','3.txt','4.txt','5.txt']:searchengine.addcorpus(filepath)whilequery=results=searchengine.search(query)print('found{}result(s):'.format(len(results)))forresultinresults:Seachnginease可以被继承,继承的类分别代表不同的算法引擎。每 个引擎都应该实现pocessopus()sah两个函数,对应我们刚刚提到的索引器和检索器。main函数提供搜索器和用户接口,于是 个简单的包装界面就有了。 起送到process_corpus中search则给定 classclassSimpleEngine(SearchEngineBase): init(self):super(SimpleEngine,self).init()self.idtotexts={}defprocesscorpus(self,id,text):self.idtotexts[id]=textdefsearch(self,query):results=[]forid,textinself.idtotexts.items():ifqueryintext:returnresultssearchengine=SimpleEngine()main(searchengine)输出found0result(s):found2result(s): SimpleEngine实现了个继承SearchEngineBase的子类,继承并实现了process_corpus和search接口,add_corpus(当然你想重写也是可行的),main在我们新的构造函数中,self.id_to_texts={}初始化了自己的私有变量,也就是这个用来文process_corpus()函数则非常直白地将文件内容插入到字典中。这里注意,ID需要是唯 search直接枚举字典,从中找到要搜索的字符串。如果能够找到,则将ID放到结果列表中,最后返回。你看,是不是非常简单呢?这个过程始终贯穿着面向对象的思想,这里我为你梳理成了几个问题,你可以自 下相信你也能看得出来,这种实现方式简单,但显然是种很低效的方式:每次索引后需要占用大量空间,因为索引函数并没有做任何事情;每次检索需要占用大量时间,因为所有索引库的文件都要被重新搜索遍。如果把语料的信息量视为n,那么这里的时间复杂度和空间复杂度都应该是O(n)级别的。而且,还有个问题:这里的query只能是 最直接的 个想法,就是把语料分词,看成 个个的词汇,这样就只需要对每篇文章它所有词汇的set即可。根据定律(ipf’slaw,),在自然语言的语料库里, 个单词出现的频率与它在频率表里的成反比,呈现幂律分布。因此,语料分词的做法可以大大提升我们的和搜索效率。BagofWordsInverted 个名叫BagofWords的搜索模型。请看下面的代码importimportclassBOWEngine(SearchEngineBase): init(self):super(BOWEngine,self).initself.idtowords=defprocesscorpus(self,id,self.idtowords[id]=self.parsetexttodefsearch(self,querywords=self.parsetexttowords(query)results=[]forid,wordsinself.idtowords.items():ifself.querymatch(querywords,words):returndefquerymatch(querywords,words):forquerywordinquerywords:ifquerywordnotinwords:returnFalsereturndefparsetextto#texttextre.sub(r'[^\wtext)#转为⼩写texttext.lower()#⽣成所有单词的列表wordlisttext.split#去除空⽩单词wordlistfilter(Nonewordlist)#返回单词的setreturnset(wordsearchengine=BOWEngine()main(searchengine)输出ihaveadreamfound3result(s):freedomfound1result(s):这里我们先来理解个概念,BOWModel,即BagofWordsModel,中文叫做词袋模型。这是NLP领域最常见最简单的模型之。假设个文本,不考虑语法、句法、段落,也不考虑词汇出现的顺序,只将这个文本看成这些词汇的集合。于是相应的,我们把id_to_texts替换成id_to_wods,这样就只需要存这些单词,而不是全部文章,也不需要考虑顺序。其中,process_corpus()函数调用类静态函数parse_text_to_words,将文章打碎形成词袋,放入set之后再search()函数则稍微复杂些。这里我们假设,想得到的结果,是所有的搜索都要出现在同篇文章中。那么,我们需要同样打碎query得到个set,然后把set中的每个词,和我们的索引中每篇文章进行核对,看下要找的词是否在其中。而这个过程由静态函数query_match负责。你可以回顾下上节课学到的静态函数,我们看到,这两个函数都是没有状态的,它们不涉及对象的私有变量(没有self作为参数),相同的输入能够得到完全相同的输出结果。因此设置为静态,可以方便其他的类可是,即使这样做,每次查询时依然需要遍历所有ID,虽然比起Simple模型已经节约了大量时间,但是互你可能想到了,我们每次查询的query的单词量不会很多,般也就几个、最多十几个的样子。那可不可些,这种情况下词袋模型现任就为力了。importclassBOWnvertedndexEngine(SearchEngineBase): init(self):super(BOWnvertedndexEngine,self).init()self.invertedindex={}defprocesscorpus(self,id,words=self.parsetexttowords(text)forwordinwords:ifwordnotinself.invertedindex:self.invertedindex[word]=[]self.inverteddefsearch(self,querywords=list(self.parsetexttowords(query))querywordsindex=list()forquerywordinquerywords:querywordsindex.append(0)#如果某个查询单词的倒序索引为空,我们就⽴刻forquerywordinqueryifquerywordnotinself.invertedindex:return[]result=[]while⾸先,获得当前状态下所有倒序索引的indexcurrentids=[]foridx,querywordinenumerate(querywords):currentindex=querywordsindex[idx]currentinvertedlist=self.invertedindex[query#已经遍历到了某个倒序索引的末尾,结束ifcurrentindex>=len(currentinvertedlist):returnresultcurrentids.append(currentinvertedlist[current#然后,如果currentids的所有元素都样,那么表明这个单词在这个元素对应的⽂档中ifall(x==currentids[0]forxincurrentids):result.append(currentids[0])querywordsindex=[x+1forxinquerywordsindex]#如果不是,我们就把最⼩的元minval=min(currentminvalpos=currentids.index(minval)querywordsindex[minvalpos]+=1defparsetextto#使⽤正则表达式去除标点符号和text=re.sub(r'[^\w]','',texttext.lower()#⽣成所有单词的列表wordlisttext.split#去除空⽩单词wordlistfilter(Nonewordlist)#返回单词的setreturnset(wordsearchengine=BOWnvertedndexEngine()main(searchengine)输出found2result(s):littleviciousfound1result(s): init()、process_corpus()和search()这其实也是大公司里团队协作的种方式,在合理的分层设计后,每层的逻辑只需要处理好分内的事情即可。在迭代升级我们的搜索引擎内核时,main函数、用户接口没有任何改变。当然,如果公司招了新的前继续看代码,你可能注意到了开头的InvertedIndex。InvertedIndexModel,即倒序索引,是非常有名的搜索引擎方法,接下来我简单介绍下。倒序索引,如其名,也就是说这次反过来,我们保留的是word->id的字典。于是情况就豁然开朗了,在searchquery_word的几个倒序索引单独拎出来,然后从这几个列表中找共有的元素,那些共有的元素,即ID,就是我们想要的查询结果。这样,我们就避免了将所有的index过遍的尴 个unique,来对每IDunique_id至于searchquery_wordsqueryword不存在于任何文章中,直接返回空;拿到之后,运行个“合并K个有序数组”的算法,从中拿到我们想要的ID,并返回。注意,这里用到的算法并不是最优的,最优的写法需要用最小堆来index。这 道有的leetcodehard题,有请考:遍历的问题解决了,那第二个问题,如果我们想要实现搜索单词按顺序出现,或者希望搜索的单词在文中离 我们需要在InvertedIndex上,对于每篇文章也保留单词的位置信息,这样 倒序索引我就介绍到这里了,如果你感可以自行查阅资料。还是那句话,我们的重点是面向对象的抽 LRU和多重继承到这步,终于,你的搜索引擎上线了,有了越来越多的量(QPS)。欣喜骄傲的同时,你却发现服务器有些“重负”了。经过段时间的调研,你发现大量重复性搜索占据了90%以上的流量,于是,你想到了个大杀器——给搜索引擎加个缓存。importimportclass init(self,size=32):self.cache=pylru.lrucache(size)defhas(self,returnkeyindefget(self,returndefset(self,key,self.cache[key]=classBOWnvertedndexEngineWithCache(BOWnvertedndexEngine,LRUCache): init(self):super(BOWnvertedndexEngineWithCache,self).initLRUCache.initdefsearch(self,query):ifprint('cachereturnresult=super(BOWnvertedndexEngineWithCache,self.set(query,returnsearchengine=BOWnvertedmain(search输出found2result(s):found2result(s):它的代码很简单,LRUCache定义了 个缓存类,你可以通过继承这个类来调用其方法。LRU缓存是 经典的缓存(同时,LRU的实现也是硅谷大厂常考的算法面试题,这里为了简单,我直接使用pylru这个因此,这里的缓存使用起来也很简单,调用has()函数判断是否在缓存中,如果在,调用get函数直接返回 第种,super(BOWInvertedIndexEngineWithCache,self). 个父类,不过使用这种方法时,要求继承链的最顶层父类必须要继承object;LRUCache.init(self第二个需要注意,query()函数被子类BOWInvertedIndexEngineWithCache再次重载,但是我还需要调用的super(BOWsuper(BOWnvertedndexEngineWithCache,这样 来,我们就简洁地实现了缓存,而且还是在不影响Onveednexngine代码的情况下。这部分内容希望你多读几遍,自己揣摩清楚,通过这个例子多多体会继承的优势。今天这节课是面向对象的实战应用,相比起前面的理论知识,内容其实不那么友好。不过,若你能静下心来,仔细学习,理清楚整个过程的要点,对你理解面向对象必将有所裨益。比如,你可以根据下面两个问题,来检验今天这节课的收获。 最后给你留 道思考题。私有变量能被继承吗?如果不能,你想继承应该怎么去做呢?欢迎留言与我、讨论,也欢迎你把这篇文章给你的同事、朋友, 起交流与进步。益达2019-06-0511Danpier2019-06-05BOWInvertedIndexEnginesearch函数后半部分少了缩进,第40行开始[3Wing·三金2019-06-05get/set_父类名私有变量名来直接调用,所以事实上python迭发的流pylru的基本用法# 的索#迭发的流构建个通用的基本框架从最简单的情况考虑起,搭建个能运行的极简版本按照实际需要不断对具体的实现过程进行优化:如在本讲的例子中,先考虑了单个搜索词+小搜索量的情况,构建了ver1;然后考虑了多个搜索词,构建了词袋的ver2;再考虑了大搜索量,构建了词袋+逆序索引的ver3(提了搜索词排序与间隔情况下的处理思路);最后考虑了负载与重复性搜索问题,构建了使用LRU缓存策略的ver4如果回过头来看最初的框架,还能发现add_corpus讲的内容可以做些改进;以及main函数直接用了for循环来找所有的文件,实际使用时用的是诸如os.walk的方法[2赞]fly2019-06-06是search()又被重载了,不是query()1程序员人生2019-06-05classA():defgetPrivateVar(self):returnclassdefb=modify[1赞]tux2019-06-05defwhilequeryinput()JohnSi2019-06-05关于思考题,子类不能继承父类私有属性,只可透过el._arntanae去该私有属性的值,或在父类创建方法返回私有属性的值,然后子类调用父类方法去取得该私有属性的值class init(self,sex,height,self.sex=sexself.height=heightself.weight=defsay_raise'sayonotdefprint('Achievesexinformationforparentmethod:{}'.format(self.sex))class(Animal): initsuper().init('M',172,70)=nameself.age=agedefsay_print('o,{},age:{},weight:{}'.format(,self.age,self.weight))print('Sex:{}'.format(self._Animalsex))john=o,John,age:35,weight:70Sex:MAchievesexinformationforparentmethodMfarFlight2019-06-05按照我之前的理解,Python是没有严格的private变量的。子类确实无法直接父类"self.var"变量,但是可以通过"self._Superclassvar"。要想比较好地或者修改这些变量,可以像JAVA样写gett以搜索引擎为例,如何规划各个类以及函数的功能,是否是设计模式方面的知识呢?[1KaitoShy2019-06-06class initself.a_private_paramself.a_param='我是父类变量'defreturnself.class initsuper(B,self).initB.private_paramself.a_priv
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 公共资金有效管理与使用保证承诺书(5篇)
- 项目安全有序开展承诺书范文5篇
- 产品生命周期管理与规划模板
- 自然健康食品加工生产手册
- 健身中心会员管理标准化操作手册
- 青少年沟通技巧提升指导书
- 合作意向落实确认函6篇
- 食品饮料行业质量追溯系统建设方案
- 公共安全紧急预案实施承诺书(6篇)
- 市场营销策划案编写标准流程解析
- 乡村振兴背景下农村教育发展路径研究
- 2025年福建省初中学业水平考试中考(会考)生物试卷(真题+答案)
- 小学英语三年级家长会课件
- 广西幼师学前专业儿童文学课件第8章 儿童诗
- 国家能源集团陆上风电项目通 用造价指标(2024年)
- 项目工程检测培训
- 儿童哲学论-高振宇著
- TOPCon 电池无银化进展-蒋秀林
- 十岁生日模板
- JT-T-496-2018公路地下通信管道高密度聚乙烯硅芯塑料管
- 医疗保健保密知识培训
评论
0/150
提交评论