• ElasticSearch基础3:全文搜索


    全文搜索

    所有查询会或多或少的执行相关度计算,但不是所有查询都有分析阶段。和一些特殊的完全不会对文本进行操作的查询(如 bool 或 function_score )不同,文本查询可以划分成两大家族:

    • 基于词项的查询

    如 term 或 fuzzy 这样的底层查询不需要分析阶段,它们对单个词项进行操作。用 term 查询词项 Foo 只要在倒排索引中查找 准确词项 ,并且用 TF/IDF 算法为每个包含该词项的文档计算相关度评分 _score 。
    记住 term 查询只对倒排索引的词项精确匹配,这点很重要,它不会对词的多样性进行处理(如, foo 或 FOO )。这里,无须考虑词项是如何存入索引的。如果是将 ["Foo","Bar"] 索引存入一个不分析的( not_analyzed )包含精确值的字段,或者将 Foo Bar 索引到一个带有 whitespace 空格分析器的字段,两者的结果都会是在倒排索引中有 Foo 和 Bar 这两个词。

    • 基于全文的查询

    像 match 或 query_string 这样的查询是高层查询,它们了解字段映射的信息:

    如果查询 日期(date) 或 整数(integer) 字段,它们会将查询字符串分别作为日期或整数对待。
    如果查询一个( not_analyzed )未分析的精确值字符串字段,它们会将整个查询字符串作为单个词项对待。
    但如果要查询一个( analyzed )已分析的全文字段,它们会先将查询字符串传递到一个合适的分析器,然后生成一个供查询的词项列表。
    一旦组成了词项列表,这个查询会对每个词项逐一执行底层的查询,再将结果合并,然后为每个文档生成一个最终的相关度评分。

    我们很少直接使用基于词项的搜索,通常情况下都是对全文进行查询,而非单个词项,这只需要简单的执行一个高层全文查询(进而在高层查询内部会以基于词项的底层查询完成搜索)。
    当我们想要查询一个具有精确值的 not_analyzed 未分析字段之前,需要考虑,是否真的采用评分查询,或者非评分查询会更好。
    单词项查询通常可以用是、非这种二元问题表示,所以更适合用过滤,而且这样做可以有效利用缓存:

    GET /_search
    {
        "query": {
            "constant_score": {
                "filter": {
                    "term": { "gender": "female" }
                }
            }
        }
    }

    匹配查询

    匹配查询 match 是个 核心 查询。无论需要查询什么字段, match 查询都应该会是首选的查询方式。它是一个高级 全文查询 ,这表示它既能处理全文字段,又能处理精确字段。

    这就是说, match 查询主要的应用场景就是进行全文搜索,我们以下面一个简单例子来说明全文搜索是如何工作的:

    POST /my_type/_bulk
    { "index": { "_id": 1 }}
    { "title": "The quick brown fox" }
    { "index": { "_id": 2 }}
    { "title": "The quick brown fox jumps over the lazy dog" }
    { "index": { "_id": 3 }}
    { "title": "The quick brown fox jumps over the quick dog" }
    { "index": { "_id": 4 }}
    { "title": "Brown fox brown dog" }

    单个词查询
    我们用第一个示例来解释使用 match 查询搜索全文字段中的单个词:

    GET /my_type/_search
    {
        "query": {
            "match": {
                "title": "QUICK!"
            }
        }
    }

    Elasticsearch 执行上面这个 match 查询的步骤是:

    • 检查字段类型 

    标题 title 字段是一个 string 类型( analyzed )已分析的全文字段,这意味着查询字符串本身也应该被分析。

    • 分析查询字符串 

    将查询的字符串 QUICK! 传入标准分析器中,输出的结果是单个项 quick 。因为只有一个单词项,所以 match 查询执行的是单个底层 term 查询。

    • 查找匹配文档 

    用 term 查询在倒排索引中查找 quick 然后获取一组包含该项的文档,本例的结果是文档:1、2 和 3 。

    • 为每个文档评分 

    用 term 查询计算每个文档相关度评分 _score ,这是种将词频(term frequency,即词 quick 在相关文档的 title 字段中出现的频率)和反向文档频率(inverse document frequency,即词 quick 在所有文档的 title 字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式。

    结果:

    • 文档 1 最相关,因为它的 title 字段更短,即 quick 占据内容的一大部分。
    • 文档 3 比 文档 2 更具相关性,因为在文档 3 中 quick 出现了两次。
    "hits" : [
          {
            "_index" : "my_type",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 0.8161564,
            "_source" : {
              "title" : "Quick brown rabbits",
              "body" : "Brown rabbits are commonly seen."
            }
          },
          {
            "_index" : "my_type",
            "_type" : "_doc",
            "_id" : "3",
            "_score" : 0.76146,
            "_source" : {
              "title" : "The quick brown fox jumps over the quick dog"
            }
          }
        ]

    多词查询

    如果我们一次只能搜索一个词,那么全文搜索就会不太灵活,幸运的是 match 查询让多词查询变得简单:

    GET /my_type/_search
    {
        "query": {
            "match": {
                "title": "BROWN DOG!"
            }
        }
    }


    上面这个查询返回所有四个文档:

    {
      "hits": [
         {
            "_id":      "4",
            "_score":   0.73185337, 
            "_source": {
               "title": "Brown fox brown dog"
            }
         },
         {
            "_id":      "2",
            "_score":   0.47486103, 
            "_source": {
               "title": "The quick brown fox jumps over the lazy dog"
            }
         },
         {
            "_id":      "3",
            "_score":   0.47486103, 
            "_source": {
               "title": "The quick brown fox jumps over the quick dog"
            }
         },
         {
            "_id":      "1",
            "_score":   0.11914785, 
            "_source": {
               "title": "The quick brown fox"
            }
         }
      ]
    }
    • 文档 4 最相关,因为它包含词 "brown" 两次以及 "dog" 一次。
    • 文档 2、3 同时包含 brown 和 dog 各一次,而且它们 title 字段的长度相同,所以具有相同的评分。
    • 文档 1 也能匹配,尽管它只有 brown 没有 dog 。

    用 任意 查询词项匹配文档可能会导致结果中出现不相关的长尾。这是种散弹式搜索。可能我们只想搜索包含 所有 词项的文档,也就是说,不去匹配 brown OR dog ,而通过匹配 brown AND dog 找到所有文档。
    match 查询还可以接受 operator 操作符作为输入参数,默认情况下该操作符是 or 。我们可以将它修改成 and 让所有指定词项都必须匹配:

    GET /my_type/_search
    {
        "query": {
            "match": {
                "title": {      
                    "query":    "BROWN DOG!",
                    "operator": "and"
                }
            }
        }
    }

    这个查询可以把文档 1 排除在外,因为它只包含两个词项中的一个。

    控制精度

    在 所有 与 任意 间二选一有点过于非黑即白。如果用户给定 5 个查询词项,想查找只包含其中 4 个的文档,该如何处理?将 operator 操作符参数设置成 and 只会将此文档排除。
    有时候这正是我们期望的,但在全文搜索的大多数应用场景下,我们既想包含那些可能相关的文档,同时又排除那些不太相关的。换句话说,我们想要处于中间某种结果。
    match 查询支持 minimum_should_match 最小匹配参数,这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。我们可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数,因为我们无法控制用户搜索时输入的单词数量:

    GET /my_type/_search
    {
      "query": {
        "match": {
          "title": {
            "query":                "quick brown dog",
            "minimum_should_match": "75%"
          }
        }
      }
    }

    当给定百分比的时候, minimum_should_match 会做合适的事情:在之前三词项的示例中, 75% 会自动被截断成 66.6% ,即三个里面两个词。无论这个值设置成什么,至少包含一个词项的文档才会被认为是匹配的。

    组合查询

    在 组合过滤器 中,我们讨论过如何使用 bool 过滤器通过 and 、 or 和 not 逻辑组合将多个过滤器进行组合。在查询中, bool 查询有类似的功能,只有一个重要的区别。

    过滤器做二元判断:文档是否应该出现在结果中?但查询更精妙,它除了决定一个文档是否应该被包括在结果中,还会计算文档的 相关程度 。

    与过滤器一样, bool 查询也可以接受 must 、 must_not 和 should 参数下的多个查询语句。比如:

    GET /my_type/_search
    {
      "query": {
        "bool": {
          "must":     { "match": { "title": "quick" }},
          "must_not": { "match": { "title": "lazy"  }},
          "should": [
                      { "match": { "title": "brown" }},
                      { "match": { "title": "dog"   }}
          ]
        }
      }
    }

    以上的查询结果返回 title 字段包含词项 quick 但不包含 lazy 的任意文档。目前为止,这与 bool 过滤器的工作方式非常相似。
    区别就在于两个 should 语句,也就是说:一个文档不必包含 brown 或 dog 这两个词项,但如果一旦包含,我们就认为它们 更相关

    {
      "hits": [
         {
            "_id":      "3",
            "_score":   0.70134366, 
            "_source": {
               "title": "The quick brown fox jumps over the quick dog"
            }
         },
         {
            "_id":      "1",
            "_score":   0.3312608,
            "_source": {
               "title": "The quick brown fox"
            }
         }
      ]
    }


    文档 3 会比文档 1 有更高评分是因为它同时包含 brown 和 dog 。


    控制精度

    所有 must 语句必须匹配,所有 must_not 语句都必须不匹配,但有多少 should 语句应该匹配呢?默认情况下,没有 should 语句是必须匹配的,只有一个例外:那就是当没有 must 语句的时候,至少有一个 should 语句必须匹配。
    就像我们能控制 match 查询的精度 一样,我们可以通过 minimum_should_match 参数控制需要匹配的 should 语句的数量,它既可以是一个绝对的数字,又可以是个百分比:

    GET /my_type/_search
    {
      "query": {
        "bool": {
          "should": [
            { "match": { "title": "brown" }},
            { "match": { "title": "fox"   }},
            { "match": { "title": "dog"   }}
          ],
          "minimum_should_match": 2 
        }
      }
    }

    minimum_should_match也可以用百分比表示。

    使用布尔匹配

    目前为止,可能已经意识到多词 match 查询只是简单地将生成的 term 查询包裹在一个 bool 查询中。如果使用默认的 or 操作符,每个 term 查询都被当作 should 语句,这样就要求必须至少匹配一条语句。以下两个查询是等价的:

    {
        "match": { "title": "brown fox"}
    } 
    {
      "bool": {
        "should": [
          { "term": { "title": "brown" }},
          { "term": { "title": "fox"   }}
        ]
      }
    }

    以上为or
    如果使用 and 操作符,所有的 term 查询都被当作 must 语句,所以 所有(all) 语句都必须匹配。以下两个查询是等价的:

    {
        "match": {
            "title": {
                "query":    "brown fox",
                "operator": "and"
            }
        }
    }
    
    {
      "bool": {
        "must": [
          { "term": { "title": "brown" }},
          { "term": { "title": "fox"   }}
        ]
      }
    }

    如果指定参数 minimum_should_match ,它可以通过 bool 查询直接传递,使以下两个查询等价:

    {
        "match": {
            "title": {
                "query":                "quick brown fox",
                "minimum_should_match": "75%"
            }
        }
    } 
    {
      "bool": {
        "should": [
          { "term": { "title": "brown" }},
          { "term": { "title": "fox"   }},
          { "term": { "title": "quick" }}
        ],
        "minimum_should_match": 2 
      }
    }

    查询语句提升权重
    当然 bool 查询不仅限于组合简单的单个词 match 查询,它可以组合任意其他的查询,以及其他 bool 查询。普遍的用法是通过汇总多个独立查询的分数,从而达到为每个文档微调其相关度评分 _score 的目的。

    假设想要查询关于 “full-text search(全文搜索)” 的文档,但我们希望为提及 “Elasticsearch” 或 “Lucene” 的文档给予更高的 权重 ,这里 更高权重 是指如果文档中出现 “Elasticsearch” 或 “Lucene” ,它们会比没有的出现这些词的文档获得更高的相关度评分 _score ,也就是说,它们会出现在结果集的更上面。

    一个简单的 bool 查询 允许我们写出如下这种非常复杂的逻辑:

    GET /_search
    {
        "query": {
            "bool": {
                "must": {
                    "match": {
                        "content": { 
                            "query":    "full text search",
                            "operator": "and"
                        }
                    }
                },
                "should": [ 
                    { "match": { "content": "Elasticsearch" }},
                    { "match": { "content": "Lucene"        }}
                ]
            }
        }
    }

    should 语句匹配得越多表示文档的相关度越高。目前为止还挺好。

    但是如果我们想让包含 Lucene 的有更高的权重,并且包含 Elasticsearch 的语句比 Lucene 的权重更高,该如何处理?

    我们可以通过指定 boost 来控制任何查询语句的相对的权重, boost 的默认值为 1 ,大于 1 会提升一个语句的相对权重。所以下面重写之前的查询:

    GET /_search
    {
        "query": {
            "bool": {
                "must": {
                    "match": {  
                        "content": {
                            "query":    "full text search",
                            "operator": "and"
                        }
                    }
                },
                "should": [
                    { "match": {
                        "content": {
                            "query": "Elasticsearch",
                            "boost": 3 
                        }
                    }},
                    { "match": {
                        "content": {
                            "query": "Lucene",
                            "boost": 2 
                        }
                    }}
                ]
            }
        }
    }

    最佳字段

    假设有个网站允许用户搜索博客的内容,以下面两篇博客内容文档为例:

    PUT /my_type/_doc/1
    {
        "title": "Quick brown rabbits",
        "body":  "Brown rabbits are commonly seen."
    }
    
    PUT /my_type/_doc/2
    {
        "title": "Keeping pets healthy",
        "body":  "My quick brown fox eats rabbits on a regular basis."
    }

    用户输入词组 “Brown fox” 然后点击搜索按钮。事先,我们并不知道用户的搜索项是会在 title 还是在 body 字段中被找到,但是,用户很有可能是想搜索相关的词组。用肉眼判断,文档 2 的匹配度更高,因为它同时包括要查找的两个词:

    现在运行以下 bool 查询:

    GET /_search
    {
        "query": {
            "bool": {
                "should": [
                    { "match": { "title": "Brown fox" }},
                    { "match": { "body":  "Brown fox" }}
                ]
            }
        }
    }

    我们发现7.5版本已经将分离最大化查询(Disjunction Max Query)做进去了,不需要专门指定dis_max

    {
      "took" : 500,
      "timed_out" : false,
      "_shards" : {
        "total" : 12,
        "successful" : 12,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 4,
          "relation" : "eq"
        },
        "max_score" : 0.8488448,
        "hits" : [
          {
            "_index" : "my_type",
            "_type" : "_doc",
            "_id" : "4",
            "_score" : 0.8488448,
            "_source" : {
              "title" : "Brown fox brown dog"
            }
          },
          {
            "_index" : "my_type",
            "_type" : "_doc",
            "_id" : "2",
            "_score" : 0.77041256,
            "_source" : {
              "title" : "Keeping pets healthy",
              "body" : "My quick brown fox eats rabbits on a regular basis."
            }
          },
          {
            "_index" : "my_type",
            "_type" : "_doc",
            "_id" : "3",
            "_score" : 0.53306913,
            "_source" : {
              "title" : "The quick brown fox jumps over the quick dog"
            }
          },
          {
            "_index" : "my_type",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 0.50484294,
            "_source" : {
              "title" : "Quick brown rabbits",
              "body" : "Brown rabbits are commonly seen."
            }
          }
        ]
      }
    }

    不需要专门指定dis_max,这个和上面查询的结果是一样的。

    GET /_search
    {
        "query": {
            "dis_max": {
                "queries": [
                    { "match": { "title": "Brown fox" }},
                    { "match": { "body":  "Brown fox" }}
                ]
            }
        }
    }


    multi_match 查询

    multi_match 查询为能在多个字段上反复执行相同查询提供了一种便捷方式。
    multi_match 多匹配查询的类型有多种,其中的三种恰巧与 了解我们的数据 中介绍的三个场景对应,即: best_fields 、 most_fields 和 cross_fields (最佳字段、多数字段、跨字段)。
    默认情况下,查询的类型是 best_fields ,这表示它会为每个字段生成一个 match 查询,然后将它们组合到 dis_max 查询的内部,如下:

    GET /_search
    {
      "dis_max": {
        "queries":  [
          {
            "match": {
              "title": {
                "query": "Quick brown fox",
                "minimum_should_match": "30%"
              }
            }
          },
          {
            "match": {
              "body": {
                "query": "Quick brown fox",
                "minimum_should_match": "30%"
              }
            }
          },
        ],
        "tie_breaker": 0.3
      }
    }
    上面这个查询用 multi_match 重写成更简洁的形式:
    GET /_search
    {
        "multi_match": {
            "query":                "Quick brown fox",
            "type":                 "best_fields", 
            "fields":               [ "title", "body" ],
            "tie_breaker":          0.3,
            "minimum_should_match": "30%" 
        }
    }

    best_fields 类型是默认值,可以不指定。
    如 minimum_should_match 或 operator 这样的参数会被传递到生成的 match 查询中。


    跨字段实体搜索

    现在讨论一种普遍的搜索模式:跨字段实体搜索(cross-fields entity search)。在如 person 、 product 或 address (人、产品或地址)这样的实体中,需要使用多个字段来唯一标识它的信息。 person 实体可能是这样索引的:

    PUT /user/_doc/1
    {
        "firstname":  "Peter",
        "lastname":   "Smith"
    }
    
    PUT /address/_doc/1
    {
        "street":   "5 Poland Street",
        "city":     "London",
        "country":  "United Kingdom",
        "postcode": "W1V 3DG"
    }

    这与之前描述的 多字符串查询 很像,但这存在着巨大的区别。在 多字符串查询 中,我们为每个字段使用不同的字符串,在本例中,我们想使用 单个 字符串在多个字段中进行搜索。

    我们的用户可能想搜索 “Peter Smith” 这个人,或 “Poland Street W1V” 这个地址,这些词出现在不同的字段中,所以如果使用 dis_max 或 best_fields 查询去查找 单个 最佳匹配字段显然是个错误的方式。

    简单的方式
    依次查询每个字段并将每个字段的匹配评分结果相加,听起来真像是 bool 查询:

    GET /_search
    {
      "query": {
        "bool": {
          "should": [
            { "match": { "street":    "Poland Street W1V" }},
            { "match": { "city":      "Poland Street W1V" }},
            { "match": { "country":   "Poland Street W1V" }},
            { "match": { "postcode":  "Poland Street W1V" }}
          ]
        }
      }
    }

    为每个字段重复查询字符串会使查询瞬间变得冗长,可以采用 multi_match 查询,将 type 设置成 most_fields 然后告诉 Elasticsearch 合并所有匹配字段的评分:

    GET /_search
    {
      "query": {
        "multi_match": {
          "query":       "Poland Street W1V",
          "type":        "most_fields",
          "fields":      [ "street", "city", "country", "postcode" ]
        }
      }
    }

    most_fields 方式的问题
    用 most_fields 这种方式搜索也存在某些问题,这些问题并不会马上显现:

    • 它是为多数字段匹配 任意 词设计的,而不是在 所有字段 中找到最匹配的。
    • 它不能使用 operator 或 minimum_should_match 参数来降低次相关结果造成的长尾效应。
    • 词频对于每个字段是不一样的,而且它们之间的相互影响会导致不好的排序结果。

    问题 1 :在多个字段中匹配相同的词

    回想一下 most_fields 查询是如何执行的:Elasticsearch 为每个字段生成独立的 match 查询,再用 bool 查询将他们包起来。

    可以通过 validate-query API 查看:

    GET /_validate/query?explain
    {
    "query": {
    "multi_match": {
    "query": "Poland Street W1V",
    "type": "most_fields",
    "fields": [ "street", "city", "country", "postcode" ]
    }
    }
    }

    生成 explanation 解释:

    (street:poland street:street street:w1v)
    (city:poland city:street city:w1v)
    (country:poland country:street country:w1v)
    (postcode:poland postcode:street postcode:w1v)

    可以发现, 两个 字段都与 poland 匹配的文档要比一个字段同时匹配 poland 与 street 文档的评分高。

    问题 2 :剪掉长尾

    在 匹配精度 中,我们讨论过使用 and 操作符或设置 minimum_should_match 参数来消除结果中几乎不相关的长尾,或许可以尝试以下方式:

    {
        "query": {
            "multi_match": {
                "query":       "Poland Street W1V",
                "type":        "most_fields",
                "operator":    "and", 
                "fields":      [ "street", "city", "country", "postcode" ]
            }
        }
    }

    所有词必须呈现,结果搜索结果为空。

    问题 3 :词频

    在 什么是相关 中,我们解释过每个词默认使用 TF/IDF 相似度算法计算相关度评分:

    • 词频

    一个词在单个文档的某个字段中出现的频率越高,这个文档的相关度就越高。

    • 逆向文档频率

    一个词在所有文档某个字段索引中出现的频率越高,这个词的相关度就越低。
    当搜索多个字段时,TF/IDF 会带来某些令人意外的结果。

    想想用字段 first_name 和 last_name 查询 “Peter Smith” 的例子, Peter 是个平常的名 Smith 也是平常的姓,这两者都具有较低的 IDF 值。但当索引中有另外一个人的名字是 “Smith Williams” 时, Smith 作为名来说很不平常,以致它有一个较高的 IDF 值!

    下面这个简单的查询可能会在结果中将 “Smith Williams” 置于 “Peter Smith” 之上,尽管事实上是第二个人比第一个人更为匹配。

    {
        "query": {
            "multi_match": {
                "query":       "Peter Smith",
                "type":        "most_fields",
                "fields":      [ "*_name" ]
            }
        }
    }


    这里的问题是 smith 在名字段中具有高 IDF ,它会削弱 “Peter” 作为名和 “Smith” 作为姓时低 IDF 的所起作用。
    存在这些问题仅仅是因为我们在处理着多个字段,如果将所有这些字段组合成单个字段,问题就会消失。可以为 person 文档添加 full_name 字段来解决这个问题:


    短语匹配

    就像 match 查询对于标准全文检索是一种最常用的查询一样,当你想找到彼此邻近搜索词的查询方法时,就会想到 match_phrase 查询。

    GET /_search
    {
    "query": {
    "match_phrase": {
    "title": "quick brown fox"
    }
    }
    }

    类似 match 查询, match_phrase 查询首先将查询字符串解析成一个词项列表,然后对这些词项进行搜索,但只保留那些包含 全部 搜索词项,且 位置 与搜索词项相同的文档。 比如对于 quick fox 的短语搜索可能不会匹配到任何文档,因为没有文档包含的 quick 词之后紧跟着 fox 。

    什么是短语
    一个被认定为和短语 quick brown fox 匹配的文档,必须满足以下这些要求:

    • quick 、 brown 和 fox 需要全部出现在域中。
    • brown 的位置应该比 quick 的位置大 1 。
    • fox 的位置应该比 quick 的位置大 2 。
    • 如果以上任何一个选项不成立,则该文档不能认定为匹配。

    精确短语匹配 或许是过于严格了。也许我们想要包含 “quick brown fox” 的文档也能够匹配 “quick fox,” , 尽管情形不完全相同。
    我们能够通过使用 slop 参数将灵活度引入短语匹配中:

    GET /_search
    {
        "query": {
            "match_phrase": {
                "title": {
                    "query": "quick fox",
                    "slop":  1
                }
            }
        }
    }

    slop 参数告诉 match_phrase 查询词条相隔多远时仍然能将文档视为匹配 。 相隔多远的意思是为了让查询和文档匹配你需要移动词条多少次?

    越近越好

    鉴于一个短语查询仅仅排除了不包含确切查询短语的文档, 而 邻近查询 — 一个 slop 大于 0— 的短语查询将查询词条的邻近度考虑到最终相关度 _score 中。 通过设置一个像 50 或者 100 这样的高 slop 值, 你能够排除单词距离太远的文档, 但是也给予了那些单词临近的的文档更高的分数。

    下列对 quick dog 的邻近查询匹配了同时包含 quick 和 dog 的文档, 但是也给了与 quick 和 dog 更加临近的文档更高的分数 :

    POST /my_index/my_type/_search
    {
       "query": {
          "match_phrase": {
             "title": {
                "query": "quick dog",
                "slop":  50 
             }
          }
       }
    }

    注意高 slop 值。

    {
      "hits": [
         {
            "_id":      "3",
            "_score":   0.75, 
            "_source": {
               "title": "The quick brown fox jumps over the quick dog"
            }
         },
         {
            "_id":      "2",
            "_score":   0.28347334, 
            "_source": {
               "title": "The quick brown fox jumps over the lazy dog"
            }
         }
      ]
    }

    分数较高因为 quick 和 dog 很接近
    分数较低因为 quick 和 dog 分开较远

  • 相关阅读:
    iOS app版本更新CheckVersion_Swift
    ios插件化开发
    开源框架RSA_Swift
    iOS SKStoreProductViewController的应用
    FMDB的使用
    iOS的MVP设计模式
    iOS UI帧率优化经验
    SKStoreReviewController之程序内评价
    Axure使用chrome插件
    修改每次《创建》项目maven仓库的默认路径
  • 原文地址:https://www.cnblogs.com/starcrm/p/12172918.html
Copyright © 2020-2023  润新知