Como a ordenación de Linux ordena as cadeas

Introdución

Todo comezou cun guión curto que debía combinar información de enderezos e-mail empregados obtidos da lista de usuarios da lista de correo, con postos de empregados obtidos da base de datos do departamento de RRHH. Ambas listas exportáronse a ficheiros de texto Unicode UTF-8 e gardado con finais de liña Unix.

Contido correo.txt

Иванов Андрей;[email protected]

Contido buhg.txt

Иванова Алла;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Абаканов Михаил;маляр

Para combinar, os ficheiros foron ordenados polo comando Unix especie e enviado á entrada do programa Unix unirse, que fallou inesperadamente cun erro:

$> sort buhg.txt > buhg.srt
$> sort mail.txt > mail.srt
$> join buhg.srt mail.srt > result
join: buhg.srt:4: is not sorted: Иванов Андрей;слесарь

Ao ver o resultado da clasificación coa vista demostrou que, en xeral, a ordenación é correcta, pero no caso das coincidencias de apelidos masculinos e femininos, os femininos van antes que os masculinos:

$> sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванова Алла;маляр
Иванов Андрей;слесарь

Parece un fallo de clasificación en Unicode ou unha manifestación de feminismo no algoritmo de clasificación. O primeiro é, por suposto, máis plausible.

Poñemos por agora unirse e céntrase en especie. Imos tentar resolver o problema usando un pinchazo científico. En primeiro lugar, imos cambiar a configuración rexional de en_US en ru_RU. Para ordenar, bastaría con establecer a variable de ambiente LC_COLLATE, pero non perderemos o tempo en bagatelas:

$> LANG=ru_RU.UTF-8 sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванова Алла;маляр
Иванов Андрей;слесарь

Non cambiou nada.

Imos tentar recodificar os ficheiros nunha codificación dun só byte:

$> iconv -f UTF-8 -t KOI8-R buhg.txt 
 | LANG=ru_RU.KOI8-R sort 
 | iconv -f KOI8-R -t UTF8

De novo nada cambiou.

Non hai nada que poidas facer, terás que buscar unha solución en Internet. Non hai nada directamente sobre os apelidos rusos, pero hai preguntas sobre outras peculiaridades de clasificación. Por exemplo, aquí hai un problema: Unix sort trata os caracteres '-' (guión) como invisibles. En resumo, as cadeas "ab", "aa", "ac" ordénanse como "aa", "ab", "ac".

A resposta é estándar en todas partes: use a configuración rexional do programador "C" e serás feliz. Imos probar:

$> LANG=C sort buhg.txt
Ёлкина Элла;крановщица
Абаканов Михаил;маляр
Иванов Андрей;слесарь
Иванова Алла;адвокат

Algo cambiou. Os Ivanov aliñaron na orde correcta, aínda que Yolkina caeu nalgún lugar. Volvamos ao problema orixinal:

$> LANG=C sort buhg.txt > buhg.srt
$> LANG=C sort mail.txt > mail.srt
$> LANG=C join buhg.srt mail.srt > result

Funcionou sen erros, como prometía Internet. E iso a pesar de Yolkina na primeira liña.

O problema parece estar resolto, pero por se acaso, probemos outra codificación rusa: Windows CP1251:

$> iconv -f UTF-8 -t CP1251 buhg.txt 
 | LANG=ru_RU.CP1251 sort 
 | iconv -f CP1251 -t UTF8 

O resultado da clasificación, curiosamente, coincidirá coa localización "C", e todo o exemplo, en consecuencia, execútase sen erros. Algún tipo de misticismo.

Non me gusta o misticismo na programación porque normalmente enmascara os erros. Teremos que ver seriamente como funciona. especie e que afecta? LC_COLLATE .

Ao final intentarei responder ás preguntas:

  • por que se ordenaron incorrectamente os apelidos femininos?
  • por que LANG=ru_RU.CP1251 resultou ser equivalente LANG=C
  • por que facer especie и unirse diferentes ideas sobre a orde das cadeas ordenadas
  • por que hai erros en todos os meus exemplos?
  • finalmente como ordenar as cadeas ao teu gusto

Ordenación en Unicode

A primeira parada será o informe técnico no 10 titulado Algoritmo de clasificación Unicode on-line unicode.org. O informe contén moitos detalles técnicos, así que permítanme facer un breve resumo das ideas principais.

Ordenación — "Comparar" cadeas é a base de calquera algoritmo de clasificación. Os propios algoritmos poden diferir ("burbulla", "fusión", "rápido"), pero todos usarán unha comparación dun par de cadeas para determinar a orde na que aparecen.

Ordenar cadeas en linguaxe natural é un problema bastante complexo. Mesmo nas codificacións dun só byte máis sinxelas, a orde das letras do alfabeto, aínda que sexa dalgún xeito diferente do alfabeto latino inglés, xa non coincidirá coa orde dos valores numéricos cos que se codifican estas letras. Así, no alfabeto alemán a letra Ö sitúase entre О и P, e na codificación CP850 ela ponse entre ÿ и Ü.

Podes tentar abstraer dunha codificación específica e considerar letras "ideais" que están dispostas nalgunha orde, como se fai en Unicode. Codificacións UTF8, UTF16 ou un byte KOI8-R (se se necesita un subconxunto limitado de Unicode) dará representacións numéricas diferentes das letras, pero referirase aos mesmos elementos da táboa base.

Resulta que aínda que construímos unha táboa de símbolos desde cero, non poderemos asignarlle unha orde de símbolos universal. En diferentes alfabetos nacionais que usan as mesmas letras, a orde destas letras pode diferir. Por exemplo, en francés Æ considerarase unha ligadura e ordenarase como unha corda AE. En noruegués Æ será unha letra separada, que se atopa despois Z. Por certo, ademais de ligaduras como Æ Hai letras escritas con varios símbolos. Entón, no alfabeto checo hai unha letra Ch, que se sitúa entre H и I.

Ademais das diferenzas nos alfabetos, hai outras tradicións nacionais que inflúen na clasificación. En particular, xorde a pregunta: en que orde deberían aparecer no dicionario as palabras formadas por letras maiúsculas e minúsculas? A clasificación tamén pode verse afectada polo uso de signos de puntuación. En castelán úsase un signo de interrogación invertido ao comezo dunha oración interrogativa (Gústache a música?). Neste caso, é obvio que as oracións interrogativas non deben agruparse nun grupo separado fóra do alfabeto, pero como ordenar as liñas con outros signos de puntuación?

Non vou determe na clasificación das cadeas en linguas moi diferentes ás europeas. Teña en conta que nas linguas cunha dirección de escritura de dereita a esquerda ou de arriba a abaixo, os caracteres das liñas probablemente se almacenen en orde de lectura, e mesmo os sistemas de escritura non alfabéticos teñen as súas propias formas de ordenar liñas carácter por carácter. . Por exemplo, os xeroglíficos pódense ordenar por estilo (Teclas de caracteres chineses) ou pola pronuncia. Para ser honesto, non teño idea de como deberían organizarse os emojis, pero tamén podes inventar algo para eles.

En función das características enumeradas anteriormente, formuláronse os requisitos básicos para comparar cadeas baseadas en táboas Unicode:

  • a comparación de cadeas non depende da posición dos caracteres na táboa de códigos;
  • as secuencias de caracteres que forman un só carácter redúcense á forma canónica (A + o círculo superior é o mesmo que Å);
  • Ao comparar cadeas, considérase un carácter no contexto da cadea e, se é necesario, combínase cos seus veciños nunha unidade de comparación (Ch en checo) o se divide en varios (Æ en francés);
  • todas as características nacionais (alfabeto, maiúsculas/minúsculas, puntuación, tipos de orde de escritura) deben configurarse ata a asignación manual da orde (emoji);
  • a comparación é importante non só para ordenar, senón tamén en moitos outros lugares, por exemplo para especificar intervalos de filas (substituíndo {A... z} en bater);
  • a comparación debe facerse con bastante rapidez.

Ademais, os autores do informe formularon propiedades de comparación nas que os desenvolvedores de algoritmos non deberían confiar:

  • o algoritmo de comparación non debería requirir un conxunto separado de caracteres para cada lingua (as linguas rusas e ucraínas comparten a maioría dos caracteres cirílicos);
  • a comparación non debe depender da orde dos caracteres nas táboas Unicode;
  • o peso da cadea non debe ser un atributo da cadea, xa que a mesma cadea en diferentes contextos culturais pode ter diferentes pesos;
  • os pesos das filas poden cambiar ao combinar ou dividir (de x < y iso non segue xz < yz);
  • diferentes cadeas que teñen os mesmos pesos considéranse iguais desde o punto de vista do algoritmo de clasificación. É posible introducir unha orde adicional de tales cadeas, pero pode degradar o rendemento;
  • Durante a clasificación repetida, pódense intercambiar filas co mesmo peso. A robustez é unha propiedade dun algoritmo de clasificación específico, e non unha propiedade dun algoritmo de comparación de cadeas (ver o parágrafo anterior);
  • As regras de clasificación poden cambiar co paso do tempo a medida que as tradicións culturais se refinan/cambian.

Tamén se estipula que o algoritmo de comparación non sabe nada sobre a semántica das cadeas que se están procesando. Así, as cadeas formadas só por díxitos non deben compararse como números, e nas listas de nomes en inglés o artigo (Beatles, The).

Para satisfacer todos os requisitos especificados, proponse un algoritmo de clasificación de táboas de varios niveis (en realidade de catro niveis).

Anteriormente, os caracteres da cadea redúcense á forma canónica e agrúpanse en unidades de comparación. A cada unidade de comparación atribúeselles varios pesos correspondentes a varios niveis de comparación. Os pesos das unidades de comparación son elementos de conxuntos ordenados (neste caso, números enteiros) que se poden comparar por máis ou menos. Significado especial IGNORADO (0x0) significa que no nivel de comparación correspondente esta unidade non está implicada na comparación. A comparación de cordas pódese repetir varias veces, utilizando os pesos dos niveis correspondentes. En cada nivel, os pesos das unidades de comparación de dúas filas compáranse secuencialmente entre si.

En diferentes implementacións do algoritmo para diferentes tradicións nacionais, os valores dos coeficientes poden diferir, pero o estándar Unicode inclúe unha táboa básica de pesos - "Táboa de elementos de intercalación Unicode predeterminada" (DUCITO). Gustaríame sinalar que establecer a variable LC_COLLATE é en realidade unha indicación da selección da táboa de pesos na función de comparación de cadeas.

Coeficientes de ponderación DUCITO dispostos do seguinte xeito:

  • no primeiro nivel, todas as letras redúcense ao mesmo caso, descartanse os diacríticos, ignóranse os signos de puntuación (non todos);
  • no segundo nivel só se teñen en conta os diacríticos;
  • no terceiro nivel, só se ten en conta o caso;
  • no cuarto nivel só se teñen en conta os signos de puntuación.

A comparación realízase en varias pasadas: en primeiro lugar, compáranse os coeficientes do primeiro nivel; se os pesos coinciden, realízase unha comparación repetida cos pesos do segundo nivel; entón quizais un terceiro e un cuarto.

A comparación remata cando as filas conteñen unidades de comparación coincidentes con diferentes pesos. As filas que teñen o mesmo peso nos catro niveis considéranse iguais entre si.

Este algoritmo (cunha morea de detalles técnicos adicionais) deu o nome ao informe número 10 - "Algoritmo de clasificación Unicode" (ACU).

Aquí é onde o comportamento de clasificación do noso exemplo faise un pouco máis claro. Sería bo comparalo co estándar Unicode.

Para probar implementacións ACU hai un especial proba, utilizando arquivo de pesos, implementando DUCITO. Podes atopar todo tipo de cousas divertidas no ficheiro de escalas. Por exemplo, existe a orde de mahjong e dominó europeo, así como a orde dos traxes nunha baralla de cartas (símbolo 1F000 e máis aló). Os traxes de cartas colócanse segundo as regras da ponte - PCBT, e as cartas do traxe están na secuencia T, 2,3, XNUMX... K.

Comprobando manualmente que as filas estean ordenadas correctamente segundo DUCITO sería bastante tedioso, pero, afortunadamente para nós, existe unha implementación exemplar da biblioteca para traballar con Unicode - "Compoñentes internacionais para Unicode"(UTI).

Na páxina web desta biblioteca, desenvolvida en IBM, hai páxinas de demostración, incluíndo páxina do algoritmo de comparación de cadeas. Introducimos as nosas liñas de proba coa configuración predeterminada e, velaquí, obtemos unha clasificación rusa perfecta.

Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Иванова Алла;адвокат

Por certo, na páxina web UTI Podes atopar unha aclaración do algoritmo de comparación ao procesar os signos de puntuación. Nos exemplos Preguntas frecuentes sobre colación o apóstrofo e o guión son ignorados.

Unicode axudounos, pero busca razóns para un comportamento estraño especie в Linux terá que ir a outro sitio.

Ordenando en glibc

Vista rápida dos códigos fonte da utilidade especie de GNU Core Utils mostrou que na propia utilidade, a localización redúcese a imprimir o valor actual da variable LC_COLLATE cando se executa no modo de depuración:

$ sort --debug buhg.txt > buhg.srt
sort: using ‘en_US.UTF8’ sorting rules

As comparacións de cadeas realízanse mediante a función estándar strcoll, o que significa que todo o interesante está na biblioteca glibc.

En wiki o proxecto glibc dedicado á comparación de cadeas un parágrafo. Desde este parágrafo pódese entender que en glibc a ordenación baséase nun algoritmo xa coñecido por nós ACU (Algoritmo de clasificación Unicode) e/ou nun estándar próximo ISO 14651 (Ordenación e comparación de cadeas internacionais). En canto ao último estándar, hai que ter en conta que no sitio standards.iso.org ISO 14651 declarado oficialmente dispoñible para o público, pero a ligazón correspondente conduce a unha páxina inexistente. Google devolve varias páxinas con ligazóns a sitios oficiais que ofrecen a compra dunha copia electrónica do estándar por cen euros, pero na terceira ou cuarta páxina de resultados de busca tamén hai ligazóns directas a PDF. En xeral, o estándar practicamente non é diferente ACU, pero é máis aburrido de ler porque non contén exemplos claros de características nacionais da clasificación de cadeas.

A información máis interesante sobre wiki había unha ligazón a rastreador de erros cunha discusión sobre a implementación da comparación de cadeas en glibc. Da discusión pódese aprender que glibc usado para comparar cadeas ISOmesa persoal A táboa de modelos comúns (CTT), cuxo enderezo se pode consultar na solicitude A estándar ISO 14651. Entre 2000 e 2015 esta táboa en glibc non tiña mantedor e era bastante diferente (polo menos externamente) da versión actual do estándar. De 2015 a 2018 tivo lugar a adaptación á nova versión da táboa, e agora tes a oportunidade de coñecer na vida real unha nova versión da táboa (CentOS 8), e vello (CentOS 7).

Agora que temos toda a información sobre o algoritmo e as táboas auxiliares, podemos volver ao problema orixinal e comprender como ordenar correctamente as cadeas na configuración regional rusa.

ISO 14651/14652

Código fonte da táboa que nos interesa CTT na maioría das distribucións Linux está no catálogo /usr/share/i18n/locales/. A propia táboa está no ficheiro iso14651_t1_común. Entón esta é a directiva do ficheiro copia iso14651_t1_common incluído no ficheiro iso14651_t1, que, á súa vez, se inclúe en expedientes nacionais, incluíndo en_US и ru_RU. Na maioría das distribucións Linux todos os ficheiros fonte están incluídos na instalación básica, pero se non están presentes, terás que instalar un paquete adicional da distribución.

Estrutura do ficheiro iso14651_t1 pode parecer terriblemente verboso, con regras non obvias para construír nomes, pero se o miras, todo é bastante sinxelo. A estrutura descríbese na norma ISO 14652, cuxa copia se pode descargar da páxina web open-std.org. Pódese ler outra descrición do formato do ficheiro especificacións POSIX de Grupo Aberto. Como alternativa á lectura do estándar, pode estudar o código fonte da función cotear_ler в glibc/locale/programs/ld-collate.c.

A estrutura do ficheiro ten o seguinte aspecto:

Por defecto, o carácter utilízase como carácter de escape e o final da liña despois do carácter # é un comentario. Ambos símbolos pódense redefinir, que é o que se fai na nova versión da táboa:

escape_char /
comment_char %

O ficheiro conterá fichas no formato ou (onde x - díxito hexadecimal). Esta é a representación hexadecimal dos puntos de código Unicode na codificación UCS-4 (UTF-32). Todos os demais elementos entre corchetes angulares (incluíndo , <2> e similares) considéranse simples constantes de cadea que teñen pouco significado fóra do contexto.

Liña LC_COLLATE dinos que a continuación comezan os datos que describen a comparación de cadeas.

En primeiro lugar, especifícanse os nomes para os pesos na táboa de comparación e os nomes para as combinacións de símbolos. En xeral, os dous tipos de nomes pertencen a dúas entidades diferentes, pero no ficheiro real mestúranse. Os nomes dos pesos especifícanse mediante a palabra clave símbolo de cotexo (carácter de comparación) porque ao comparar, os caracteres Unicode que teñan o mesmo peso consideraranse caracteres equivalentes.

A lonxitude total da sección na revisión do ficheiro actual é dunhas 900 liñas. Tirei exemplos de varios lugares para mostrar a arbitrariedade dos nomes e varios tipos de sintaxe.

LC_COLLATE

collating-symbol <RES-1>
collating-symbol <BLK>
collating-symbol <MIN>
collating-symbol <WIDE>
...
collating-symbol <ARABIC>
collating-symbol <ETHPC>
collating-symbol <OSMANYA>
...
collating-symbol <S1D000>..<S1D35F>
collating-symbol <SFFFF> % Guaranteed largest symbol value. Keep at end of this list
...
collating-element <U0413_0301> from "<U0413><U0301>"
collating-element <U0413_0341> from "<U0413><U0341>"

  • símbolo de cotexo rexistra unha cadea OSMANIA na táboa de nomes de escalas
  • símbolo de cotexo .. rexistra unha secuencia de nomes formada por un prefixo S e sufixo numérico hexadecimal de 1D000 para 1D35F.
  • FFFF в símbolo de cotexo parece un enteiro grande sen signo en hexadecimal, pero é só un nome que pode parecer
  • nome significa punto de código na codificación UCS-4
  • elemento de cotexo de " " rexistra un novo nome para un par de puntos Unicode.

Unha vez que se definen os nomes dos pesos, especifícanse os pesos reais. Dado que só importan relacións de maior que menos en comparación, os pesos están determinados por unha simple secuencia de nomes de lista. Os pesos "máis lixeiros" aparecen primeiro, despois os "máis pesados". Permíteme recordarche que a cada carácter Unicode se lle asignan catro pesos diferentes. Aquí combínanse nunha única secuencia ordenada. En teoría, calquera nome simbólico podería usarse en calquera dos catro niveis, pero os comentarios indican que os desenvolvedores separan mentalmente os nomes en niveis.

% Symbolic weight assignments

% Third-level weight assignments
<RES-1>
<BLK>
<MIN>
<WIDE>
...
% Second-level weight assignments
<BASE>
<LOWLINE> % COMBINING LOW LINE
<PSILI> % COMBINING COMMA ABOVE
<DASIA> % COMBINING REVERSED COMMA ABOVE
...
% First-level weight assignments
<S0009> % HORIZONTAL TABULATION 
<S000A> % LINE FEED
<S000B> % VERTICAL TABULATION
...
<S0434> % CYRILLIC SMALL LETTER DE
<S0501> % CYRILLIC SMALL LETTER KOMI DE
<S0452> % CYRILLIC SMALL LETTER DJE
<S0503> % CYRILLIC SMALL LETTER KOMI DJE
<S0453> % CYRILLIC SMALL LETTER GJE
<S0499> % CYRILLIC SMALL LETTER ZE WITH DESCENDER
<S0435> % CYRILLIC SMALL LETTER IE
<S04D7> % CYRILLIC SMALL LETTER IE WITH BREVE
<S0454> % CYRILLIC SMALL LETTER UKRAINIAN IE
<S0436> % CYRILLIC SMALL LETTER ZHE

Finalmente, a táboa de peso real.

A sección de pesos está encerrada en liñas de palabras clave orde_inicio и fin_orde. Opcións extra orde_inicio determinar en que dirección se exploran as filas en cada nivel de comparación. A configuración predeterminada é á fronte. O corpo da sección está formado por liñas que conteñen o código do símbolo e os seus catro pesos. O código de carácter pode representarse polo propio carácter, un punto de código ou un nome simbólico definido anteriormente. Tamén se poden dar pesos a nomes simbólicos, puntos de código ou aos propios símbolos. Se se utilizan puntos de código ou caracteres, o seu peso é o mesmo que o valor numérico do punto de código (posición na táboa Unicode). Os caracteres non especificados explícitamente (segundo entendo) considéranse asignados á táboa cun peso principal que coincide coa posición na táboa Unicode. Valor de peso especial IGNORAR significa que o símbolo é ignorado no nivel de comparación adecuado.

Para demostrar a estrutura das escalas, escollín tres fragmentos bastante obvios:

  • personaxes que son completamente ignorados
  • símbolos equivalentes ao número tres nos dous primeiros niveis
  • o inicio do alfabeto cirílico, que non contén signos diacríticos e, polo tanto, está clasificado principalmente polo primeiro e terceiro niveis.

order_start forward;forward;forward;forward,position
<U0000> IGNORE;IGNORE;IGNORE;IGNORE % NULL (in 6429)
<U0001> IGNORE;IGNORE;IGNORE;IGNORE % START OF HEADING (in 6429)
<U0002> IGNORE;IGNORE;IGNORE;IGNORE % START OF TEXT (in 6429)
...
<U0033> <S0033>;<BASE>;<MIN>;<U0033> % DIGIT THREE
<UFF13> <S0033>;<BASE>;<WIDE>;<UFF13> % FULLWIDTH DIGIT THREE
<U2476> <S0033>;<BASE>;<COMPAT>;<U2476> % PARENTHESIZED DIGIT THREE
<U248A> <S0033>;<BASE>;<COMPAT>;<U248A> % DIGIT THREE FULL STOP
<U1D7D1> <S0033>;<BASE>;<FONT>;<U1D7D1> % MATHEMATICAL BOLD DIGIT THREE
...
<U0430> <S0430>;<BASE>;<MIN>;<U0430> % CYRILLIC SMALL LETTER A
<U0410> <S0430>;<BASE>;<CAP>;<U0410> % CYRILLIC CAPITAL LETTER A
<U04D1> <S04D1>;<BASE>;<MIN>;<U04D1> % CYRILLIC SMALL LETTER A WITH BREVE
<U0430_0306> <S04D1>;<BASE>;<MIN>;<U04D1> % CYRILLIC SMALL LETTER A WITH BREVE
...
<U0431> <S0431>;<BASE>;<MIN>;<U0431> % CYRILLIC SMALL LETTER BE
<U0411> <S0431>;<BASE>;<CAP>;<U0411> % CYRILLIC CAPITAL LETTER BE
<U0432> <S0432>;<BASE>;<MIN>;<U0432> % CYRILLIC SMALL LETTER VE
<U0412> <S0432>;<BASE>;<CAP>;<U0412> % CYRILLIC CAPITAL LETTER VE
...
order_end

Agora podes volver a ordenar os exemplos desde o inicio do artigo. A emboscada atópase nesta parte da táboa de pesos:

<U0020> IGNORE;IGNORE;IGNORE;<U0020> % SPACE
<U0021> IGNORE;IGNORE;IGNORE;<U0021> % EXCLAMATION MARK
<U0022> IGNORE;IGNORE;IGNORE;<U0022> % QUOTATION MARK
...

Pódese ver que nesta táboa os signos de puntuación da táboa ASCII (incluíndo o espazo) case sempre se ignora ao comparar cadeas. As únicas excepcións son as liñas que coinciden en todo, excepto nos signos de puntuación que se atopan en posicións coincidentes. As liñas do meu exemplo (despois de ordenar) para o algoritmo de comparación ven así:

АбакановМихаилмаляр
ЁлкинаЭллакрановщица
ИвановаАлламаляр
ИвановАндрейслесарь

Tendo en conta que na táboa de escalas, as maiúsculas en ruso veñen despois das minúsculas (no terceiro nivel máis pesado que ), a clasificación parece absolutamente correcta.

Ao establecer unha variable LC_COLLATE=C cárgase unha táboa especial que especifica unha comparación byte por byte

static const uint32_t collseqwc[] =
{
  8, 1, 8, 0x0, 0xff,
  /* 1st-level table */
  6 * sizeof (uint32_t),
  /* 2nd-level table */
  7 * sizeof (uint32_t),
  /* 3rd-level table */
  L'x00', L'x01', L'x02', L'x03', L'x04', L'x05', L'x06', L'x07',
  L'x08', L'x09', L'x0a', L'x0b', L'x0c', L'x0d', L'x0e', L'x0f',

...
  L'xf8', L'xf9', L'xfa', L'xfb', L'xfc', L'xfd', L'xfe', L'xff'
};

Dado que en Unicode o punto de código Ё está antes de A, as cadeas ordénanse en consecuencia.

Táboas de texto e binarias

Obviamente, a comparación de cadeas é unha operación moi común e a análise de táboas CTT un procedemento bastante custoso. Para optimizar o acceso á táboa, compílase en forma binaria co comando localdef.

Equipo localdef acepta como parámetros un ficheiro cunha táboa de características nacionais (opción -i), no que todos os caracteres están representados por puntos Unicode e un ficheiro de correspondencia entre puntos Unicode e caracteres dunha codificación específica (opción -f). Como resultado do traballo, créanse ficheiros binarios para a configuración rexional co nome especificado no último parámetro.

glibc admite dous formatos de ficheiro binarios: "tradicional" e "moderno".

O formato tradicional significa que o nome da configuración rexional é o nome do subdirectorio no que se atopa /usr/lib/locale/. Este subdirectorio almacena ficheiros binarios LC_COLLATE, LC_CTYPE, LC_TIME etcétera. Arquivo LC_IDENTIFICACIÓN contén o nome formal da configuración rexional (que pode ser diferente do nome do directorio) e comentarios.

O formato moderno implica almacenar todos os locais nun único arquivo /usr/lib/locale/locale-archive, que se asigna á memoria virtual de todos os procesos que utilizan glibc. O nome da rexión no formato moderno está suxeito a algunha canonización; só quedan números e letras reducidos a minúsculas nos nomes de codificación. Entón ru_RU.KOI8-R, gardarase como ru_RU.koi8r.

Os ficheiros de entrada búscanse no directorio actual, así como nos directorios /usr/share/i18n/locales/ и /usr/share/i18n/charmaps/ para arquivos CTT e ficheiros de codificación, respectivamente.

Por exemplo, o comando

localedef -i ru_RU -f MAC-CYRILLIC ru_RU.MAC-CYRILLIC

compilará o ficheiro /usr/share/i18n/locales/ru_RU usando o ficheiro de codificación /usr/share/i18n/charmaps/MAC-CYRILLIC.gz e gardar o resultado en /usr/lib/locale/locale-archive baixo o nome ru_RU.maccyrillic

Se estableces a variable LANG = en_US.UTF-8 que glibc buscará binarios locais na seguinte secuencia de ficheiros e directorios:

/usr/lib/locale/locale-archive
/usr/lib/locale/en_US.UTF-8/
/usr/lib/locale/en_US/
/usr/lib/locale/enUTF-8/
/usr/lib/locale/en/

Se un lugar aparece en formatos tradicionais e modernos, dáselle prioridade ao moderno.

Podes ver a lista de locais compilados co comando local -a.

Preparando a súa táboa de comparación

Agora, armado co coñecemento, podes crear a túa propia táboa de comparación de cadeas ideal. Esta táboa debe comparar correctamente as letras rusas, incluída a letra Ё, e ao mesmo tempo ter en conta os signos de puntuación de acordo coa táboa. ASCII.

O proceso de preparación da súa propia táboa de clasificación consta de dúas etapas: editar a táboa de pesos e compilala en forma binaria co comando localdef.

Para que a táboa de comparación se axuste cuns custos mínimos de edición, no formato ISO 14652 Ofrécense seccións para axustar os pesos dunha táboa existente. A sección comeza cunha palabra clave reordenar despois e indicando a posición tras a que se realiza a substitución. A sección remata coa liña reordenar-fin. Se é necesario corrixir varias seccións da táboa, créase unha sección para cada sección.

Copiei novas versións dos ficheiros iso14651_t1_común и ru_RU dende o repositorio glibc ao meu directorio de inicio ~/.local/share/i18n/locales/ e editei lixeiramente a sección LC_COLLATE в ru_RU. As novas versións dos ficheiros son totalmente compatibles coa miña versión glibc. Se queres utilizar versións antigas de ficheiros, terás que cambiar os nomes simbólicos e o lugar onde comeza a substitución na táboa.

LC_COLLATE
% Copy the template from ISO/IEC 14651
copy "iso14651_t1"
reorder-after <U000D>
<U0020> <S0020>;<BASE>;<MIN>;<U0020> % SPACE
<U0021> <S0021>;<BASE>;<MIN>;<U0021> % EXCLAMATION MARK
<U0022> <S0022>;<BASE>;<MIN>;<U0022> % QUOTATION MARK
...
<U007D> <S007D>;<BASE>;<MIN>;<U007D> % RIGHT CURLY BRACKET
<U007E> <S007E>;<BASE>;<MIN>;<U007E> % TILDE
reorder-end
END LC_COLLATE

De feito, sería necesario cambiar os campos en LC_IDENTIFICACIÓN para que apunten á localidade ru_MEU, pero no meu exemplo isto non era necesario, xa que excluín o arquivo da busca de locais arquivo-local.

Que localdef traballou con ficheiros do meu cartafol mediante unha variable I18NPATH Podes engadir un directorio adicional para buscar ficheiros de entrada e o directorio para gardar ficheiros binarios pódese especificar como un camiño con barras:

$> I18NPATH=~/.local/share/i18n localedef -i ru_RU -f UTF-8 ~/.local/lib/locale/ru_MY.UTF-8

POSIX asume que en SOLO pode escribir camiños absolutos a directorios con ficheiros de configuración rexional, comezando cunha barra inclinada, pero glibc в Linux todos os camiños cóntanse desde o directorio base, que se pode anular a través dunha variable LOCPATH. Despois da instalación LOCPATH=~/.local/lib/locale/ todos os ficheiros relacionados coa localización buscaranse só no meu cartafol. Arquivo de locais co conxunto de variables LOCPATH ignorado.

Velaquí a proba decisiva:

$> LANG=ru_MY.UTF-8 LOCPATH=~/.local/lib/locale/ sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Иванова Алла;адвокат

Hurra! Fixémolo!

Algúns erros

Xa respondín ás preguntas sobre a clasificación de cadeas formuladas ao principio, pero aínda hai un par de preguntas sobre erros: visibles e invisibles.

Volvamos ao problema orixinal.

E o programa especie e programa unirse use as mesmas funcións de comparación de cadeas de glibc. Como pasou iso unirse deu un erro de clasificación nas filas ordenadas polo comando especie en local gl_US.UTF-8? A resposta é sinxela: especie compara a cadea enteira e unirse compara só a clave, que por defecto é o inicio da cadea ata o primeiro espazo en branco. No meu exemplo, isto deu lugar a unha mensaxe de erro porque a ordenación das primeiras palabras nas liñas non coincidía coa ordenación das liñas completas.

Local "C" garante que nas cadeas ordenadas tamén se ordenarán as subcadeas iniciais ata o primeiro espazo, pero isto só enmascara o erro. É posible seleccionar datos (persoas co mesmo apelido, pero nomes diferentes) que, sen a mensaxe de erro, darían un resultado de combinación de ficheiros incorrecto. Se queremos unirse liñas de ficheiro combinadas polo nome completo, entón a forma correcta sería especificar explícitamente o separador de campos e ordenar polo campo clave, e non pola liña completa. Neste caso, a combinación procederá correctamente e non haberá erros en ningunha configuración rexional:

$> sort -t ; -k 1 buhg.txt > buhg.srt
$> sort -t ; -k 1 mail.txt > mail.srt
$> join -t ; buhg.srt mail.srt > result

Exemplo executado correctamente na codificación CP1251 contén outro erro. O caso é que en todas as distribucións que coñezo Linux aos paquetes faltan os locais compilados ru_RU.CP1251. Se non se atopa a configuración rexional compilada, entón especie utiliza silenciosamente unha comparación byte por byte, que é o que observamos.

Por certo, hai outra pequena falla relacionada coa inaccesibilidade dos locais compilados. Equipo LOCPATH=/tmp local -a dará unha lista de todos os lugares en arquivo-local, pero co conxunto de variables LOCPATH para todos os programas (incluído o máis localidade) estes locais non estarán dispoñibles.

$> LOCPATH=/tmp locale -a | grep en_US
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_COLLATE to default locale: No such file or directory
en_US
en_US.iso88591
en_US.iso885915
en_US.utf8

$> LC_COLLATE=en_US.UTF-8 sort --debug
sort: using ‘en_US.UTF-8’ sorting rules

$> LOCPATH=/tmp LC_COLLATE=en_US.UTF-8 sort --debug
sort: using simple byte comparison

Conclusión

Se es un programador que está afeito a pensar que as cadeas son un conxunto de bytes, entón a túa elección LC_COLLATE=C.

Se es un lingüista ou un compilador de dicionarios, é mellor que compile na súa configuración rexional.

Se es un usuario sinxelo, só tes que acostumarte ao feito de que o comando ls -a sae ficheiros que comezan por un punto mesturados con ficheiros que comezan por letra e Comandante de medianoite, que utiliza as súas propias funcións internas para ordenar os nomes, pon os ficheiros que comezan cun punto ao principio da lista.

referencias

Informe n.o 10 Algoritmo de clasificación Unicode

Pesos de caracteres en unicode.org

UTI — implementación dunha biblioteca para traballar con Unicode de IBM.

Proba de clasificación usando UTI

Pesos dos personaxes ISO 14651

Descrición do formato do ficheiro con escalas ISO 14652

Discusión sobre a comparación de cadeas en glibc

Fonte: www.habr.com

Engadir un comentario