• Go语言实战


    山坡网的用户抱怨“为什么搜索‘二鬼子李富贵’找不到‘二鬼子汉奸李富贵’?我用百度搜都能找到。”

    当时我就滴汗了,用户说的有道理,应该要能搜索到。

    之前的方案很简单,用户输入的字串会在数据库里做正则表达式匹配,以便用“二鬼子”能搜到“二鬼子汉奸李富贵”。事实证明,我想当然了,即便是这么简单的一个书名搜索,也不能马虎。

    那就来分析一下怎么做吧,即便不是专业做搜索的,思路上也可以先YY一下。按照本能,先把问题大而化小。

    1. 先把搜索字符串进行中文分词

    2. 用词组在数据库里做 or 包含匹配。

    3. 搜索出来的结果按与搜索条件相关度排序。

    看起来也不难(玩笑话,每一条水都很深),一条一条来解决。

    1. 中文分词。

    我找了一下,免费的不多,选择了盘古分词Go语言Port。从Github看到,代码是四个月以前的了,字典文件有些老。所以我在盘古网站上下载了最新的字典,测试了一下Go的代码,运行结果良好。

    这个Port可能蛮久没有更新过,代码的结构不能直接go get,需要自己下载src里面的segment文件夹出来使用。

    我把新的字典文件全都放到了revel app的conf文件夹里,如下图所示。

    image

    然后在Controller.Init方法里加上初始化代码。

    revel.OnAppStart(func() {
       segHandler = segment.NewSegment()
       err := segHandler.Init(path.Join(revel.ConfPaths[0], "dicts"))
       if err != nil {
         glog.Fatalln("Failed to init segment handler", err)
       }

    然后在需要的地方就可以开始用它分词了。

    //对搜索的字符串进行分词
    searchKey := "(" + key + "|"

    segs := segHandler.DoSegment(key)
    for cur := segs.Front(); cur != nil; cur = cur.Next() {
      word := cur.Value.(*dict.WordInfo)
      if word.Word != "的" {
        searchKey += word.Word + "|"
      }
    }

    searchKey = strings.TrimRight(searchKey, "|") + ")"

    searchResults, pageSum, err := d.findBookBy(M{"$or": []M{
      M{"title": M{"$regex": searchKey}},
      M{"author": M{"$regex": key}},
      M{"category": M{"$regex": key}}}}, "-score", pageNum, numPerPage)
    if err != nil {
      return nil, pageSum, err
    }

    思路是把搜索条件分成词组,再组合成正则表达式,比如“二鬼子李富贵”变成“(二|鬼子|李富贵)”,然后使用mongodb的正则查询。

    这里我把“的”字去掉了,因为中文里面“的”字用的太多了,基本没有查询价值。

    2. 搜索出来的结果按与搜索条件相关度排序。

    搜索引擎里,这部分是技术含量最大的。我这边只是牛刀小试,所以方案简单很多,把搜索出来的书籍标题与搜索条件比对相似度。

    正好,字符串比对相似度的库我之前Port过一个,叫做simhash(当时为什么port我都忘了,哈,工具箱里东西多还是有好处的!)。算法具体就不多说了,免得跑题。看用法吧。

    needle := "Reading bytes into structs using reflection"
    hayStack := "Golang - mapping an variable length array to a struct"
    
    likeness := GetLikenessValue(needle, hayStack)
    fmt.Println("Likeness:", likeness)
    就一个函数,输入两个字符串,输出一个从0到1的浮点数,代表相似百分比。
    为了方便计算,我在SearchResult结构中加入了一个新的字段,OriginalQueryString,存储原始搜索条件,之后实现一下Sort接口。

    type SearchResult struct {
      Id                bson.ObjectId "_id"
      Title             string
      OriginQueryString string //原始的搜索条件,用于排序
    }

    type SearchResults []SearchResult

    func (srs SearchResults) Len() int {
      return len(srs)
    }

    func (srs SearchResults) Less(i, j int) bool {
      likenessI := simhash.GetLikenessValue(srs[i].Title, srs[i].OriginQueryString)
      likenessJ := simhash.GetLikenessValue(srs[j].Title, srs[j].OriginQueryString)

      return likenessI < likenessJ
    }

    func (srs SearchResults) Swap(i, j int) {
      srs[i], srs[j] = srs[j], srs[i]
    }

    就可以在搜索出来之后按照相关性排序了。

    //为searchResult的OriginQueryString赋值,以便按照搜索相关性排序
    for i, _ := range searchResults {
      searchResults[i].OriginQueryString = key
    }

    sort.Sort(sort.Reverse(SearchResults(searchResults)))

    我的实现到这里就完成了。

    但其实有一部分很重要的东西我取巧了。由于使用模糊搜索,结果集的大小是无法预料的,全部取的话随时可能把内存用完。分批的话怎么保证相关性排序的准确性呢?好问题,这里是非常关键又很难做的部分,我取巧的方式是把书籍按评分排序,然后取前20个出来,仅仅在这20本书中做相似度排序。这并不是完美的方案,仅仅只是够用。

    后期如果有时间,可以用mongodb的游标做一个即省内存又靠谱的实现。

  • 相关阅读:
    微信小程序——微信支付
    .Net 异步方法, await async 使用
    微信小程序路过——新手不要错过哦!
    关于文件的上传。
    网页源代码的获取方法
    PCPOP多功能外挂v1.0
    网站开发步骤
    关于系统的垃圾文件
    关于容器中的控件的使用
    POP气球机
  • 原文地址:https://www.cnblogs.com/AllenDang/p/3335396.html
Copyright © 2020-2023  润新知