ElasticSearch 2 (22)

ElasticSearch 2 (22) - 语言处理系列之标记规范化

摘要

将文本拆解成标记只是工作的一半。为了使这些标记更容易被搜索到,它们需要经过一个规范化的处理过程,以移除相同单词间不重要的差异(比如:大小写)。或许我们还需要移除一些重要的差异,让estaéstaestá 可以作为相同的词被搜索。是会搜索 déjà vu 还是 deja vu 呢?

这是标记过滤器的工作,它从标记器接收一个标记流。我们可以有多个标记过滤器,他们各司其则,每个都将它们前一个标记过滤器的输出作为自己的新标记流输入。

版本

elasticsearch版本: elasticsearch-2.x

内容

既然如此(In That Case)

使用最频繁的标记过滤器要数 lowercase 小写过滤器,正如我们的期望,它将每个标记转换为与之对应的小写形式:

GET /_analyze?tokenizer=standard&filters=lowercase
The QUICK Brown FOX! #1

#1 输出标记:thequickbrownfox

用户是用 fox 还是 FOX 并不重要,只要查询时和搜索时使用的是同一套分析过程。lowercase 分析器会将查询里的 FOX 转换成 fox,它与我们在倒排索引中存储的标记一致。

将标记过滤器作为分析过程的一部分来使用,我们可以创建一个自定义分析器:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_lowercaser": {
          "tokenizer": "standard",
          "filter":  [ "lowercase" ]
        }
      }
    }
  }
}

我们可以用 analyze API 对其进行测试:

GET /my_index/_analyze?analyzer=my_lowercaser
The QUICK Brown FOX! #1

#1 输出标记 thequickbrownfox

口音(You Have an Accent)

英语只对舶来词(如:rôledéjàdäis)使用变音符号(´^¨),但通常这不是必须的。其他语言要求使用变音符以确保正确性。当然,即使因为索引中的词都是拼写正确的,也并不意味着用户在搜索时也会使用正确的拼写形式。

将变音符从词语中剔除通常会比较有用,让 rôle 可以匹配 role,亦或反之。西方的语言可以通过使用 asciifolding 字符过滤器来完成。实际上,它做的事情不只是剔除变音符号,它试图将许多 Unicode 字符转换成更简单的 ASCII 形式:

  • ßss
  • æae
  • łl
  • ɰm
  • ??
  • 2
  • 6

lowercase 过滤器一样,asciifolding 过滤器无须任何配置,可以被直接包括在一个自定义分析器中:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "folding": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "asciifolding" ]
        }
      }
    }
  }
}

GET /my_index?analyzer=folding
My œsophagus caused a débâcle #1

#1 输出 myoesophaguscausedadebacle

保留词意(Retaining Meaning)

当然,在我们剔除变音符号的时候,也会丢失词语的意思。例如,考虑以下三个西班牙语的单词:

  • esta

    阴性形式的 this 形容词,如:esta silla (this chair) 或 esta (this one)。(注:在西班牙语中,silla是阴性词,esta做代词时也代指阴性对象)。

  • ésta

    esta 的旧式写法。

  • está

    第三人称是动词形式 estar(to be,中文意思为:是),如:está feliz(he is happy,中文意思为:他高兴)。

但我们会将前两个形式合并,它们与第三个形式的意思是有区别的,我们会将第三个形式独立处理。类似:

  • 动词 saber 的第一人称形式(to know,中文意思为:知道),如:Yo sé(I know,中文意思为:我知道).

  • se

    第三人称反身代词,可以与很多动词联用,如:se sabe(it is known,中文意思为:众所周知)。

不幸的是,想要将应该剔除与不应剔除变音的词区分开并非易事。很有可能是连我们用户自己也都搞不清楚。

取而代之的是我们对文本索引两次:一次以原始形式,一次剔除变音形式:

PUT /my_index/_mapping/my_type
{
  "properties": {
    "title": { #1
      "type":           "string",
      "analyzer":       "standard",
      "fields": {
        "folded": { #2
          "type":       "string",
          "analyzer":   "folding"
        }
      }
    }
  }
}

#1 title 字段使用 standard 分析器,会包含原始单词的变音形式。

#2 title.folded 字段使用 folding 分析器,会剔除变音符号。

analyze API 对字段映射进行测试,测试的句子是 Esta está loca(This woman is crazy 这个女人疯了):

GET /my_index/_analyze?field=title #1
Esta está loca

GET /my_index/_analyze?field=title.folded #2
Esta está loca

#1 输出 estaestáloca
#2 输出 estaestaloca

让我们索引一些供测试的文档:

PUT /my_index/my_type/1
{ "title": "Esta loca!" }

PUT /my_index/my_type/2
{ "title": "Está loca!" }

现在我们可以使用 multi_match 跨字段搜索,并在 most_fields 模式下合并每个字段的评分结果:

GET /my_index/_search
{
  "query": {
    "multi_match": {
      "type":     "most_fields",
      "query":    "esta loca",
      "fields": [ "title", "title.folded" ]
    }
  }
}

validate-query API 查看查询的解释工具:

GET /my_index/_validate/query?explain
{
  "query": {
    "multi_match": {
      "type":     "most_fields",
      "query":    "está loca",
      "fields": [ "title", "title.folded" ]
    }
  }
}

multi-match 查询在 title 字段中搜索词的原始形式(está),在 title.folded 中搜索词的去变音形式(esta):

(title:está        title:loca       )
(title.folded:esta title.folded:loca)

用户是在搜索 esta 还是在搜索 está 并不重要,因为两种形式分别同时存在于 titletitle.folded 字段,两个文档都能被搜出。但在 title 字段中只存有原始形式,这个额外的匹配会使包含原始单词的文档处于结果列表的顶部。

我们用 title.folded 字段来广撒网,希望能匹配更多文档,用原 title 字段将最相关的文档推到结果列表的顶部。同样的技术可以在使用分析器时应用,通过牺牲词语的含义来提升匹配的数量。

小贴士

asciifolding 过滤器确实有一个配置选项叫做 preserve_original 让我们可以将原始标记和经剔除的标记放在同一字段的同一位置。如果开启这个配置选项,我们会得到如下结果:

  Position 1     Position 2
  --------------------------
  (ésta,esta)    loca
  --------------------------

尽管这看上去是一个节省空间的不错方法,但这也意味着我们无从要求过滤器对原始词精确匹配。将原始标记和剔除变音的标记混在一起还会影响到词频计数,进而导致相关度计算的可靠性会被弱化。

这里有一个规则:将每个字段及其变体分开索引。正如我们在本部分里做的一样。

统一字符编码标准的世界(Living in a Unicode World)

当 Elasticsearch 比较标记时,它是在字节级别处理这个问题的。换句话说,两个被认为相同的标记,须要由完全相同的字节组成。但 Unicode 允许我们用不同方式来表示同一个字符。

例如, é 之间有什么区别?答案取决于问题的对象。在Elasticsearch里,第一个由两个字节组成0xC3 0xA9,而第二个由三个字节组成 0x65 0xCC 0x81

在 Unicode 中,它们作为字节如何表达的差异无关紧要,它们是相同的字符,第一个是单个字符 é,而第二个只是个 e 和重音符号 ´ 的组合。

如果我们获取数据的来源不止一个,我们可能会碰到相同的字符的不同编码形式,这会导致一种形式的 déjà 无法匹配一种。

幸运的是,我们有现成的解决方案。Unicode 的规范化有四种形式,所有都是将 Unicode 字符转换成标准格式,让所有字符都能在字节层进行比较:nfcnfdnfkcnfkd

Unicode 规范化的形式(Unicode Normalization Forms)

整理型(nfcnfkc)以最少字节形式表示字符,所以 é 是作为单个字母表示的;分解型(nfdnfkd)以字符的各自组成部分来表示,所以 é 表示为 e´

规范型(nfcnfd)以印刷形式表示字符,所以如 œ 是作为单个字符表示的;兼容型(nfkcnfkd)以拆解的简单多字母形式表示字符,对应表示为:f + f + io + e

我们选择何种规则形式并不十分重要,只要我们所有的文本都是以相同形式表示的。这样,相同的标记由相同的字节组成,也就是说,兼容模式允许我们可以将印刷形式()与简单字母表示(ffi)相比较。

我们可以用 icu_normalizer 标记过滤器来确保所有的标记都是相同的形式:

PUT /my_index
{
  "settings": {
    "analysis": {
      "filter": {
        "nfkc_normalizer": { #1
          "type": "icu_normalizer",
          "name": "nfkc"
        }
      },
      "analyzer": {
        "my_normalizer": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "nfkc_normalizer" ]
        }
      }
    }
  }
}

#1 将所有标记都规范化 nfkc 形式。

小贴士

除了前面提到的 icu_normalizer 标记过滤器,还有一个 icu_normalizer 字符过滤器可以做同样的事情,但处理是发生在文本到达标记器之前。当使用 standardicu_tokenizer 标记器的时候,这个差异不是十分重要。这些标记器都知道如何正确处理 Unicode 的所有形式。

尽管如此,如果我们打算使用不同的标记器,如:ngramedge_ngrampattern 标记器,最好根据标记过滤器的偏好使用 icu_normalizer 字符过滤器。

很多时候,我们会想同时完成将其规范化和将其小写的工作,可以通过 icu_normalizer 来完成,使用自定义的规范化形式 nfkc_cf,我们将在下一部分中讨论它。

Unicode 大小写转换(Unicode Case Folding)

如果没哟发明才能人类什么都不是,人类的语言正能体现这点。直到处理多语言时,都还认为转换一个词的大小写看似一个简单的任务。

举个例子,德语里的小写字母 ß,转换成大写会是 SS,再转换回小写是 ss。或者考虑希腊语里的字母 ς (西格玛,通常在词的末尾使用),将其转换成大写会是 Σ,再转换回小写是 σ

The whole point of lowercasing terms is to make them more likely to match, not less! In Unicode, this job is done by case folding rather than by lowercasing. Case folding is the act of converting words into a (usually lowercase) form that does not necessarily result in the correct spelling, but does allow case-insensitive comparisons.
将词项小写的目的是为了让它们更易于匹配,而不是更难!在 Unicode 中,这个工作通过大小写转换完成而不只是小写化。大小写转换是个将单词转换为一种可以不区分大小写而能比较形式(通常是小写形式)的动作,它并不要求结果里拼写的正确性。

例如,字母 ß 已经是小写了,经转换成了 ss。类似的,小写 ς 经转换变成 σ,无论它们在单词的什么地方出现,都让 σςΣ 可以相互比较。

icu_normalizer 标记过滤器使用的默认规范化形式是 nfkc_cf,和 nfkc 形式一样,它做下列事情:

  • 将字符 整理 以最短字节表示。
  • 兼容 模式将字符转换,如 变成 ffi

它还会做下面事情:

  • 将字符 大小写转换 成适合比较的形式。

换句话说,nfkc_cflowercase 小写标记过滤器的等价形式,但它适于所有语言。on-steroidsstandard 分析器等价:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_lowercaser": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "icu_normalizer" ] #1
        }
      }
    }
  }
}

#1 icu_normalizer 默认使用 nfkc_cf 形式。

我们可以通过 standardUnicode-aware 分析器运行比较 WeißkopfseeadlerWEISSKOPFSEEADLER (等价的大写形式)的结果:

GET /_analyze?analyzer=standard #1
Weißkopfseeadler WEISSKOPFSEEADLER

GET /my_index/_analyze?analyzer=my_lowercaser #2
Weißkopfseeadler WEISSKOPFSEEADLER

#1 输出标记 weißkopfseeadlerweisskopfseeadler

#2 输出标记 weisskopfseeadlerweisskopfseeadler

standard 分析器输出了两个不同且不可比的标记,而我们自定义的分析器输出的标记是可以比较的,无乱原始的形式如何。

Unicode 字符转换(Unicode Character Folding)

同样,使用 lowercase 标记过滤器对很多语言来说都是一个不错的开始,但当被暴露在巴别塔下的时候,它的短处就会立刻突显出来,asciifolding 标记过滤器要求与更有效的 Unicode 字符转换 配合使用来应对多语言的世界。

icu_folding 标记过滤器(icu 插件内提供)与 asciifolding 过滤器做的事情一样,但扩展了对不基于 ASCII 书写形式的转换支持,如:希腊语、希伯来语、汉语,将数字以它们等价的拉丁书写形式表示,以及其他各种数字、符号和标点的转换。

icu_folding 标记过滤器自动应用 Unicode 规则化和 nfkc_cf 大小写转换方式,所以 icu_normalizer 不是必须的:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_folder": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "icu_folding" ]
        }
      }
    }
  }
}

GET /my_index/_analyze?analyzer=my_folder
١٢٣٤٥ #1

#1 阿拉伯数字 ١٢٣٤٥ 被转换成了它们的拉丁等价形式:12345。(注:通常我们汉语中说的阿拉伯数字实际上是拉丁数字)

如果需要对某些特殊字符禁止转换,我们可以通过 UnicodeSet (非常像一个用正则式表示的一系列字符)来指定具体需要转换的 Unicode 字符。例如,为了排除瑞典语里的字符 åäöÅÄÖ, 我们需要指定一个字符类来表示所有 Unicode 字符,除了字母:[^åäöÅÄÖ]^ 表示排除)

PUT /my_index
{
  "settings": {
    "analysis": {
      "filter": {
        "swedish_folding": { #1
          "type": "icu_folding",
          "unicodeSetFilter": "[^åäöÅÄÖ]"
        }
      },
      "analyzer": {
        "swedish_analyzer": { #2
          "tokenizer": "icu_tokenizer",
          "filter":  [ "swedish_folding", "lowercase" ]
        }
      }
    }
  }
}

#1 swedish_folding 标记过滤自定义了 icu_folding 标记过滤器来排除所有瑞典语字母(同时排除大写和小写)。

#2 swedish 分析器先标记单词,再用 swedish_folding 过滤器进行大小写转换,然后将每个标记小写以防它包括了某些应该被排除的大写字母:ÅÄÖ

排序与排序规则(Sorting and Collations)

本章到目前为止,我们介绍了如何对标记进行规范化处理。本章最后需要考虑的一个应用场景是字符串排序。

字符串排序和多字段(String Sorting and Multifields) 中,我们解释过 Elasticsearch 无法对一个 analyzed 字符串字段进行排序,还展示说明如何使用 multifields 对同一字段建立多个索引,一个是 analyzed 字段供搜索使用,一个是 not_analyzed 字段供排序使用。

analyzed 字段的排序问题不在于它使用分析器,而在于分析器会将字符串标记成多个标记,就如同一袋子单词,这使 Elasticsearch 不知道使用哪个标记来排序。

依赖 not_analyzed 字段排序是不灵活的,它只允许我们对原始字符串的精确值进行排序。但是,我们 可以 使用分析器来达到其他方式排序的目的,只要我们选择的分析器为每个字符串始终输出单个标记即可。

Case-Insensitive Sorting

假设我们有三个 user 文档,它们的 name 字段分别包含:BoffeyBROWNbailey。首先我们应用在 字符串排序和多字段(String Sorting and Multifields) 里描述的技术,用一个 not_analyzed 字段来排序:

PUT /my_index
{
  "mappings": {
    "user": {
      "properties": {
        "name": { #1
          "type": "string",
          "fields": {
            "raw": { #2
              "type":  "string",
              "index": "not_analyzed"
            }
          }
        }
      }
    }
  }
}

#1 analyzed name 字段是供搜索使用的。

#2 not_analyzed name.raw 字段是供排序使用的。

我们可以索引一些文档并尝试排序:

PUT /my_index/user/1
{ "name": "Boffey" }

PUT /my_index/user/2
{ "name": "BROWN" }

PUT /my_index/user/3
{ "name": "bailey" }

GET /my_index/user/_search?sort=name.raw

前面的搜索请求会按以下顺序返回文档:BROWNBoffeybailey。这是字典顺序,与字母顺序正好相反。实际上,用来表示大写字母的字节值比用来表示小写的要小,所以字节最小的排在最前。

这对计算机来说是合理的,但是对人类来说这却不合常理,因为我们希望名字是按字母顺序排列的,无乱大小写如何。为了做到这点,需要对每个名字的字节索引排序方式与我们期望的顺序一致。

换句话说,需要一个能输出单个小写形式的标记的分析器:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "case_insensitive_sort": {
          "tokenizer": "keyword",    #1
          "filter":  [ "lowercase" ] #2
        }
      }
    }
  }
}

#1 keyword 标记器将原始未改变的字符串作为单个标记输出。

#2 lowercase 标记过滤器将标记以小写形式输出。

有了 case_insentive_sort 分析器,我们可以在 multifield 使用它:

PUT /my_index/_mapping/user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "lower_case_sort": { #1
          "type":     "string",
          "analyzer": "case_insensitive_sort"
        }
      }
    }
  }
}

PUT /my_index/user/1
{ "name": "Boffey" }

PUT /my_index/user/2
{ "name": "BROWN" }

PUT /my_index/user/3
{ "name": "bailey" }

GET /my_index/user/_search?sort=name.lower_case_sort

#1 name.lower_case_sort 字段为我们提供不区分大小写的排序。

前面的搜索请求会按以下顺序返回文档:baileyBoffeyBROWN

但这个顺序正确吗?这看上去是正确的是因为它正如我们的期望一样,但是我们的期望很有可能是受了本书是用英语的而且示例文档中所有字母都是英语字母。

如果我们将德语名字 Böhm 加入结果会怎样?

现在返回的名字顺序会是:baileyBoffeyBROWNBöhmBöhm 排在 BROWN 之后出现的原因还是由于结果是按照表示词的字节值来排序的,r 以字节 0x72 形式存储,ö 以字节 0xF6 来存储,所以排在最后。每个字符的字节值是历史偶然所造成的。

显然,默认的排序方式除了适用英语,对其他语言毫无意义,实际上,没有“正确”的排序方式,只有适于语言的排序方式。

语言的差异(Differences Between Languages)

Every language has its own sort order, and sometimes even multiple sort orders. Here are a few examples of how our four names from the previous section would be sorted in different contexts:
每种语言都有它们自己的顺序,有时甚至可以有多种排序方式。这里以我们之前四个名字为例,它们在不同的语境下有着不同的排序:

  • 英语(English): baileyboffeyböhmbrown
  • 德语(German): baileyboffeyböhmbrown
  • 德语电话簿(German phonebook): baileyböhmboffeybrown
  • 瑞典语(Swedish): baileyboffeybrownböhm
注意

之所以 böhm 排在 boffey 之前是因为在德语电话簿中,当处理名字和地址时,öoe 被认为是同义词,所以 böhm 就像作为 boehm 排序一样。

Unicode 预定义排序算法(Unicode Collation Algorithm)

预定义排序是将文本按照预定顺序进行排序的过程。Unicode Collation AlgorithmUCA (参见 www.unicode.org/reports/tr10) 定义了一个按照 排序元素表(Collation Element Table) 中预定义顺序进行排序的方法。(通常 排序元素表 也被简单称为 排序表(collation)

UCA 也定义了一个 默认 Unicode 排序元素表(Default Unicode Collation Element Table) 简称 DUCET,它为所有的 Unicode 字符定义了默认的排序顺序,与语言无关。正如我们已经看到的,正确的排序顺序并不唯一,所以 DUCET 的设计是为了尽可能减少用户的烦恼,但它远不足以在所有排序困境下都能成为灵丹妙药。

取而代之的是,因为世界上几乎每种语言都有一个与语言相关的排序表,最通用的做法是先以 DUCET 作为基准,再加入一些自定义规则来处理每种语言的独特特性。

UCA 将字符串和排序表作为输入并输出一个二进制排序键值,这样让字符串集合按照指定排序表来排序的过程就简化成了比较它们的二进制键值。

Unicode 字符串(Unicode Sorting)

小贴士

本部分描述的一些方法可能会在 Elasticsearch 的将来版本发生变化,最新的信息请参考 icu plugin 文档。

icu_collation 标记过滤器默认使用 DUCET 排序规则进行排序,这是对默认排序的一个改进。要想使用它,我们只需要创建一个使用默认 icu_collation 的分析器:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ducet_sort": {
          "tokenizer": "keyword",
          "filter": [ "icu_collation" ] #1
        }
      }
    }
  }
}

#1 使用默认 DUCET 排序规则。

一般来说,我们想要排序的字段同样也是我们想要搜索的字段,所以我们使用与 无大小写区分的排序(Case-Insensitive Sorting) 中相同的 multifield 方法:

PUT /my_index/_mapping/user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "sort": {
          "type": "string",
          "analyzer": "ducet_sort"
        }
      }
    }
  }
}

有了这个映射, the name.sort 字段会包含一个排序键值仅供排序时使用。我们没有指定任何语言,所以它默认使用 DUCET 排序规则

现在我们对示例中的文档重新索引再测试排序:

PUT /my_index/user/_bulk
{ "index": { "_id": 1 }}
{ "name": "Boffey" }
{ "index": { "_id": 2 }}
{ "name": "BROWN" }
{ "index": { "_id": 3 }}
{ "name": "bailey" }
{ "index": { "_id": 4 }}
{ "name": "Böhm" }

GET /my_index/user/_search?sort=name.sort

注意

注意排序键值与每个文档一起返回,所以之前例子中的 brownböhm 会看上去令人费解:ᖔ乏昫တ倈⠀u0001。原因是 icu_collation 过滤器输出的键值只是为了排序的高效,而没有其他任何目的。

前面搜索返回文档的顺序是:baileyBoffeyBöhmBROWN。这已经是一个进步了,因为现在的顺序对于英语和德语来说都是对的,但对于德语电话簿和瑞典语来说还是不正确。下一步要做的是对不同语言自定义我们的映射。

指定语言(Specifying a Language)

icu_collation 过滤器可以为特定语言配置不同的排序表,无论是一个国家的语言,还是其他某些语言的子集(如:德语电话簿)。这可以通过创建一个自定义版本的标记过滤器并使用参数 languagecountryvariant 来做到:

  • 英语(English)

      { "language": "en" }
    
  • 德语(German)

      { "language": "de" }
    
  • 奥地利德语(Austrian German)

      { "language": "de", "country": "AT" }
    
  • 德语电话簿(German phonebooks)

      { "language": "de", "variant": "@collation=phonebook" }
    

小贴士

更多关于地域支持的信息,请参见:http://userguide.icu-project.org/locale

这个示例为我们展示了如何给德语电话簿设置排序顺序:

PUT /my_index
{
  "settings": {
    "number_of_shards": 1,
    "analysis": {
      "filter": {
        "german_phonebook": { #1
          "type":     "icu_collation",
          "language": "de",
          "country":  "DE",
          "variant":  "@collation=phonebook"
        }
      },
      "analyzer": {
        "german_phonebook": { #2
          "tokenizer": "keyword",
          "filter":  [ "german_phonebook" ]
        }
      }
    }
  },
  "mappings": {
    "user": {
      "properties": {
        "name": {
          "type": "string",
          "fields": {
            "sort": { #3
              "type":     "string",
              "analyzer": "german_phonebook"
            }
          }
        }
      }
    }
  }
}

#1 首先我们为德语电话簿创建一个自定义的 icu_collation

#2 然后将其包在一个自定义分析器中。

#3 最后在 name.sort 字段应用。

重新索引数据再次执行前面的搜索:

PUT /my_index/user/_bulk
{ "index": { "_id": 1 }}
{ "name": "Boffey" }
{ "index": { "_id": 2 }}
{ "name": "BROWN" }
{ "index": { "_id": 3 }}
{ "name": "bailey" }
{ "index": { "_id": 4 }}
{ "name": "Böhm" }

GET /my_index/user/_search?sort=name.sort

现在返回文档的顺序是:baileyBöhmBoffeyBROWN。在德语电话簿中,BöhmBoehm 是等价的,所以它应该出现在 Boffey 之前。

多重排序顺序(Multiple sort orders)

相同的字段也可以支持多个排序顺序,只要为每种语言使用 multifield 就能做到:

PUT /my_index/_mapping/_user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "default": {
          "type":     "string",
          "analyzer": "ducet" #1
        },
        "french": {
          "type":     "string",
          "analyzer": "french" #2
        },
        "german": {
          "type":     "string",
          "analyzer": "german_phonebook" #3
        },
        "swedish": {
          "type":     "string",
          "analyzer": "swedish" #4
        }
      }
    }
  }
}

#1 #2 #3 #4 我们需要为每个排序规则创建相应的分析器。

有了这个映射,结果的顺序对于法语、德语和瑞典语的用户都是正确的,只需根据字段 name.frenchname.germanname.swedish 排序即可。对于不支持的语言会根据默认字段 name.default 使用 DUCET 排序规则。

自定义排序规则(Customizing Collations)

icu_collation 标记过滤器除了 languagecountryvariant 还有其他很多配置选项,它们都可以被用以对排序算法进行裁剪。这些选项可以提供以下功能:

  • 忽略变音词
  • 将大写排在最前或最后,或者忽略大小写
  • 考虑标点符号和空格,或忽略它们
  • 将数字当作字符串进行排序,或当作数值进行排序
  • 配置定义现有的排序规则,或使用自己定义的排序规则

这些配置选项的详细使用方法超出本书的范围,更多的内容可以在 ICU plug-in 文档ICU project collation 文档 中找到。

参考

elastic.co: Normalizing Tokens

原文地址:https://www.cnblogs.com/richaaaard/p/5282472.html