zoukankan      html  css  js  c++  java
  • 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的游标做一个即省内存又靠谱的实现。

  • 相关阅读:
    CodeForces 734F Anton and School
    CodeForces 733F Drivers Dissatisfaction
    CodeForces 733C Epidemic in Monstropolis
    ZOJ 3498 Javabeans
    ZOJ 3497 Mistwald
    ZOJ 3495 Lego Bricks
    CodeForces 732F Tourist Reform
    CodeForces 732E Sockets
    CodeForces 731E Funny Game
    CodeForces 731D 80-th Level Archeology
  • 原文地址:https://www.cnblogs.com/AllenDang/p/3335396.html
Copyright © 2011-2022 走看看