2.X/3. Dealing with Human Language

3-3-6. Sorting and Collations

drscg 2017. 9. 24. 17:14

So far in this chapter, we have looked at how to normalize tokens for the purposes of search. The final use case to consider in this chapter is that of string sorting.

지금까지 이 장에서, 검색을 목적으로, token을 정규화하는 방법을 살펴보았다. 이 장에서 고려할 마지막 사용 사례는 문자열의 정렬 사례이다.

In String Sorting and Multifields, we explained that Elasticsearch cannot sort on an analyzed string field, and demonstrated how to use multifields to index the same field once as an analyzed field for search, and once as a not_analyzed field for sorting.

String Sorting and Multifields에서, Elasticsearch는 analyzed string field는 정렬할 수 없다라고 설명한 바 있다. 그리고 검색을 위한 analyzed field로 한번, 정렬을 위해 not_analyzed field로 한번, 동일한 field에 색인 하기 위해, multifields 를 사용하는 방법을 시연했었다.

The problem with sorting on an analyzed field is not that it uses an analyzer, but that the analyzer tokenizes the string value into multiple tokens, like a bag of words, and Elasticsearch doesn’t know which token to use for sorting.

analyzed field에서의 정렬 문제는 그것이 analyzer를 사용한다는 사실이 아니라, analyzer가 문자열 값을 단어의 가방(bag of words) 같은 다수의 token으로 만든다는 사실이다. 그리고, Elasticsearch는 정렬을 위해 어떤 token을 사용해야 하는지를 알지 못한다.

Relying on a not_analyzed field for sorting is inflexible: it allows us to sort on only the exact value of the original string. However, we can use analyzers to achieve other sort orders, as long as our chosen analyzer always emits only a single token for each string value.

정렬을 위해 not_analyzed field에 의존하면 유연하지 않다. 원래의 문자열의 exact value로만 정렬할 수 있다. 그러나, 각각의 문자열 값에 대해 항상 하나의 token을 출력하는 analyzer를 선택하는 한, 다른 정렬 순서를 만들기 위해 analyzer를 사용 할 수 있다.

Case-Insensitive Sortingedit

Imagine that we have three user documents whose name fields contain Boffey, BROWN, and bailey, respectively. First we will apply the technique described in String Sorting and Multifields of using a not_analyzed field for sorting:

각각 BoffeyBROWNbailey 를 name field에 포함하고 있는, 3개의 user document를 가지고 있다고 가정해 보자. 먼저, 정렬을 위해 not_analyzed field를 사용해, String Sorting and Multifields에서 언급한 기술을 적용해 보자.

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

analyzed name field는 검색을 위해 사용된다.

not_analyzed name.raw field는 정렬을 위해 사용된다.

We can index some documents and try sorting:

몇 개의 document를 색인하고, 정렬을 해 보자.

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

The preceding search request would return the documents in this order: BROWNBoffeybailey. This is known as lexicographical order as opposed to alphabetical order. Essentially, the bytes used to represent capital letters have a lower value than the bytes used to represent lowercase letters, and so the names are sorted with the lowest bytes first.

위의 검색 request는, BROWNBoffeybailey 의 순서로, document를 반환한다. 이것은 알파벳 순서(alphabetical order) 와는 다른 사전식 순서(lexicographical order) 라 알려져 있다. 근본적으로, 대문자를 나타내는데 사용되는 byte는, 소문자를 나타내는데 사용되는 byte보다, 더 낮은 값을 가진다. 그러므로, name은 가장 낮은 byte가 먼저 나오도록 정렬된다.

That may make sense to a computer, but does not make much sense to human beings who would reasonably expect these names to be sorted alphabetically, regardless of case. To achieve this, we need to index each name in a way that the byte ordering corresponds to the sort order that we want.

이것은 컴퓨터로서는 합리적이다. 그러나, 대소문자에 관계없이, name이 알파벳순으로 정렬되는 것이 타당하다고 기대하는 인간에게는 합리적이지 않다. 이를 위해, 원하는 정렬 순서에 상응하는 byte 순서로, 각각의 name을 색인해야 한다.

In other words, we need an analyzer that will emit a single lowercase token:

즉, 하나의 소문자 token을 출력하는 analyzer가 필요하다.

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

keyword tokenizer는 원래의 입력 문자열을 하나의 변경되지 않은 token으로 출력한다.

lowercase token filter는 소문자 token을 출력한다.

With the case_insensitive_sort analyzer in place, we can now use it in our multifield:

이제, case_insensitive_sort analyzer를 다중 field에 사용할 수 있다.

PUT /my_index/_mapping/user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "lower_case_sort": { 
          "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

name.lower_case_sort field는 대소문자 구분 없는 정렬을 제공한다.

The preceding search request returns our documents in the order that we expect: baileyBoffeyBROWN.

위의 검색 request는, 기대했던 순서인, baileyBoffeyBROWN 순으로, document를 반환한다.

But is this order correct? It appears to be correct as it matches our expectations, but our expectations have probably been influenced by the fact that this book is in English and all of the letters used in our example belong to the English alphabet.

그런데, 이 순서가 올바를까? 기대대로 올바르게 나타난다. 그러나 그 기대는, 아마도 이 책이 영어로 되어 있고, 예제에 사용된 모든 문자가 영어 알파벳에 속한다는 사실에 영향을 받았을 것이다.

What if we were to add the German name Böhm?

독일어 이름 Böhm 을 추가하면 어떻게 될까?

Now our names would be returned in this order: baileyBoffeyBROWNBöhm. The reason that Böhmcomes after BROWN is that these words are still being sorted by the values of the bytes used to represent them, and an r is stored as the byte 0x72, while ö is stored as 0xF6 and so is sorted last. The byte value of each character is an accident of history.

이제 이름은 baileyBoffeyBROWNBöhm 의 순으로 반환될 것이다. BROWN 다음에 Böhm 이 오는 이유는, 해당 단어들이, 여전히 그들을 나타내는데 사용되는 byte의 값에 의해 정렬되기 때문이다. 그리고 r 은 byte 0x72 로 저장되고, 반면에, ö 는 0xF6 으로 저장되어, 마지막에 정렬된다. 각 문자의 byte값은 역사적인 우연이다.

Clearly, the default sort order is meaningless for anything other than plain English. In fact, there is no "right" sort order. It all depends on the language you speak.

분명히, 기본적인 정렬 순서는 일반적인 영어 이외에는 의미가 없다. 사실 "올바른" 정렬 순서는 없다. 그것은 여러분들이 말하는 언어에 달려 있다.

Differences Between Languagesedit

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:

모든 언어는 자신만의 정렬 순서를 가지고 있고, 심지어 다수의 정렬 순서를 가진 경우도 있다. 다음은, 이전의 4개의 이름이, 각기 다른 언어에서, 정렬되는 방법의 몇 가지 예이다.

  • English: baileyboffeyböhmbrown
  • German: baileyboffeyböhmbrown
  • German phonebook: baileyböhmboffeybrown
  • Swedish: baileyboffeybrownböhm
Note

The reason that the German phonebook sort order places böhm before boffey is that ö and oe are considered synonyms when dealing with names and places, so böhm is sorted as if it had been written as boehm.

독일 전화번호부 정렬 순서가 boffey 전에 böhm 을 두는 이유는, ö 와 oe 가 이름과 장소를 다룰 경우, 동의어로 간주되기 때문이다. 그래서, böhm 은 boehm 으로 쓰여져 정렬된다.

Unicode Collation Algorithmedit

Collation is the process of sorting text into a predefined order. The Unicode Collation Algorithm, or UCA (see www.unicode.org/reports/tr10) defines a method of sorting strings into the order defined in a Collation Element Table (usually referred to just as a collation).

정렬(collation) 은 텍스트를 미리 정의한 순서로 정렬하는 프로세스이다. UCA(Unicode Collation Algorithm, www.unicode.org/reports/tr10)는 Collation Element Table(일반적으로 collation 라 함)에서 정의한 순서로, 문자열을 정렬하는 방법을 정의한다

The UCA also defines the Default Unicode Collation Element Table, or DUCET, which defines the default sort order for all Unicode characters, regardless of language. As you have already seen, there is no single correct sort order, so DUCET is designed to annoy as few people as possible as seldom as possible, but it is far from being a panacea for all sorting woes.

UCA는 또한, 언어에 관계없이, 모든 unicode 문자의 기본적인 정렬 순서를 정의한, DUCET(Default Unicode Collation Element Table)를 정의한다. 이미 알겠지만, 하나의 올바른 정렬 순서는 없다. 그래서 DUCET는 가능한 한 성가시지 않도록, 가능한 한 적은 수의 사람이 성가시도록 설계했다. 하지만, 그것은 모든 정렬 문제에 대한 만병통치약과는 거리가 있다.

Instead, language-specific collations exist for pretty much every language under the sun. Most use DUCET as their starting point and add a few custom rules to deal with the peculiarities of each language.

대신, 언어별 정렬은 태양 아래의 모든 언어에 많이 존재한다. 대부분은 DUCET를 출발점으로 하고, 각 언어의 특수성을 다루기 위해, 몇 가지 사용자 정의 규칙을 추가한다.

The UCA takes a string and a collation as inputs and outputs a binary sort key. Sorting a collection of strings according to the specified collation then becomes a simple comparison of their binary sort keys.

UCA는 입력으로 문자열과 정렬을 받아, 이진 정렬 key를 출력한다. 지정된 정렬에 따라, 문자열의 집합을 정렬하는 것은 이진 정렬 key의 단순한 비교이다.

Unicode Sortingedit

Tip

The approach described in this section will probably change in a future version of Elasticsearch. Check the icu plugin documentation for the latest information.

아래에 언급된 방법은, Elasticsearch의 미래 버전에서는 아마 변경될 것이다. 최신 정보를 위해, icu plugin을 참조하자.

The icu_collation token filter defaults to using the DUCET collation for sorting. This is already an improvement over the default sort. To use it, all we need to do is to create an analyzer that uses the default icu_collation filter:

icu_collation token filter는 정렬을 위해 DUCET 정렬을 사용하는 것이 기본이다. 이것은 이미 기본 정렬 이상으로 개선한 것이다. 그것을 사용하기 위해, 기본 icu_collation filter를 사용하는 analyzer를 생성해야 한다.

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

기본 DUCET 정렬을 사용한다.

Typically, the field that we want to sort on is also a field that we want to search on, so we use the same multifield approach as we used in Case-Insensitive Sorting:

일반적으로, 정렬하려는 field는 또한 검색하려는 field이다. 따라서, Case-Insensitive Sorting에서 사용된, 다중 field 방식을 사용한다.

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

With this mapping, the name.sort field will contain a sort key that will be used only for sorting. We haven’t specified a language, so it defaults to using the DUCET collation.

이 mapping에서, name.sort field는, 정렬에만 사용되는, 정렬 key를 가지고 있다. 언어를 지정하지 않았기 때문에, DUCET collation을 사용하는 것이 기본이다.

Now, we can reindex our example docs and test the sorting:

이제, 예제 document를 다시 색인 하고, 정렬을 테스트해 보자.

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
Note

Note that the sort key returned with each document, which in earlier examples looked like brown and böhm, now looks like gobbledygook: ᖔ乏昫တ倈⠀\u0001. The reason is that the icu_collation filter emits keys intended only for efficient sorting, not for any other purposes.

sort key가 각 document와 함께 반환된다는 점을 기억하자. 이전 예제에서는, sort Key가,brown 과 böhm 처럼 보이지만, 지금은 이해하기 힘든 표현(ᖔ乏昫တ倈⠀\u0001)으로 보인다. 그 이유는 icu_collation filter가 다른 목적은 전혀 없이, 단지 효율적인 정렬만을 위해 의도된 key를 출력하기 때문이다.

The preceding search returns our docs in this order: baileyBoffeyBöhmBROWN. This is already an improvement, as the sort order is now correct for English and German, but it is still incorrect for German phonebooks and Swedish. The next step is to customize our mapping for different languages.

위의 검색은, baileyBoffeyBöhmBROWN 의 순서로, document를 반환한다. 영어(English)와 독일어(German)에 대해 정렬 순서가 이제 올바르다. 이미 개선된 것이다. 그러나 독일 전화번호부(German phonebooks), 스웨덴어(Swedish)에 대해서는 여전히 올바르지 않다. 다음 단계는 다른 언어를 위해 mapping을 변경하는 것이다.

Specifying a Languageedit

The icu_collation filter can be configured to use the collation table for a specific language, a country-specific version of a language, or some other subset such as German phonebooks. This can be done by creating a custom version of the token filter by using the languagecountry, and variant parameters as follows:

icu_collation filter는 특정 언어, 언어의 국가별 버전, 독일 전화번호부 같은 일부 다른 하위 집합에, 정렬 테이블의 사용을 설정할 수 있다. 다음과 같이, languagecountryvariant 같은 매개변수를 사용하여, token filter의 사용자 정의 버전을 생성할 수 있다.

English
{ "language": "en" }
German
{ "language": "de" }
Austrian German
{ "language": "de", "country": "AT" }
German phonebooks
{ "language": "de", "variant": "@collation=phonebook" }
Tip

You can read more about the locales supported by ICU at: http://userguide.icu-project.org/locale.

ICU에 의해 지원되는 locale은 http://userguide.icu-project.org/locale 에서 읽을 수 있다.

This example shows how to set up the German phonebook sort order:

아래 예제는, 독일 전화번호부 정렬 순서를 설정하는 방법을 보여준다.

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

먼저, 독일 전화번호부 정렬을 위해, 사용자 정의 icu_collation 버전을 생성한다.

그리고, 사용자 정의 analyzer에서 그것을 감싼다.

그리고, name.sort field에 그것을 적용한다.

Reindex the data and repeat the same search as we used previously:

데이터를 다시 색인 하고, 위에서 사용했던 것과 동일한 검색을 반복해 보자.

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

This now returns our docs in this order: baileyBöhmBoffeyBROWN. In the German phonebook collation, Böhm is the equivalent of Boehm, which comes before Boffey.

이제, baileyBöhmBoffeyBROWN 의 순서로 document를 반환할 것이다. 독일 전화번호부 정렬에서, Böhm 은 Boehm 과 동일하고, Boffey 이전에 나타난다.

Multiple sort ordersedit

The same field can support multiple sort orders by using a multifield for each language:

동일한 field는, 각 언어별로 다중 field를 사용하여, 다수의 정렬 순서를 지원할 수 있다.

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

   

각각의 정렬에 상응하는 analyzer를 생성해야 한다.

With this mapping in place, results can be ordered correctly for French, German, and Swedish users, just by sorting on the name.frenchname.german, or name.swedish fields. Unsupported languages can fall back to using the name.default field, which uses the DUCET sort order.

이 mapping을 사용하면, name.frenchname.germanname.swedish field로 정렬함으로써, 프랑스(French), 독일(German), 스웨덴(Swedish) user가 올바르게 정렬된다. 지원되지 않는 언어는, DUCET 정렬 순서를 사용하는 name.default field를 사용하여, 대비할 수 있다.

Customizing Collationsedit

The icu_collation token filter takes many more options than just languagecountry, and variant, which can be used to tailor the sorting algorithm. Options are available that will do the following:

icu_collation token filter는 languagecountryvariant 이상의 많은 옵션을 가지며, 이들은 정렬 알고리즘의 재단사로 사용될 수 있다. 이용할 수 있는 옵션은 다음과 같다

  • Ignore diacritics

    발음 구별 부호 무시

  • Order uppercase first or last, or ignore case

    대문자를 먼저/나중에 정렬, 또는 대소문자를 무시

  • Take punctuation and whitespace into account or ignore it

    문장 부호나 공백을 고려하거나 무시

  • Sort numbers as strings or by their numeric value

    숫자를 문자열 혹은 숫자 값으로 정렬

  • Customize existing collations or define your own custom collations

    기존의 정렬을 재정의하거나, 사용자 정의 정렬을 정의

Details of these options are beyond the scope of this book, but more information can be found in the ICU plug-in documentation and in the ICU project collation documentation.

이러한 옵션의 자세한 사항은 이 책의 범위를 벗어난다. 그러나, 더 많은 정보는 ICU plug-in documentation이나 ICU project collation documentation에서 찾을 수 있다.


'2.X > 3. Dealing with Human Language' 카테고리의 다른 글

3-3-4. Unicode Case Folding  (0) 2017.09.24
3-3-5. Unicode Character Folding  (0) 2017.09.24
3-4. Reducing Words to Their Root Form  (0) 2017.09.24
3-4-1. Algorithmic Stemmers  (0) 2017.09.24
3-4-2. Dictionary Stemmers  (0) 2017.09.24