Почему добавление общего суффикса меняет порядок сортировки в локали en_US? - PullRequest
5 голосов
/ 20 октября 2010

Следующий код

#!/usr/bin/perl

use strict;
use warnings;

my $s1 = 'aaa2000@yahoo.com';
my $s2 = 'aaa_2000@yahoo.com';
my $s3 = 'aaa2000';
my $s4 = 'aaa_2000';

no locale;

print "\nNO Locale:\n\n";

if ($s1 gt $s2) {print "$s1 is > $s2\n";}
if ($s1 lt $s2) {print "$s1 is < $s2\n";}
if ($s1 eq $s2) {print "$s1 is = $s2\n";}

if ($s3 gt $s4) {print "$s3 is > $s4\n";}
if ($s3 lt $s4) {print "$s3 is < $s4\n";}
if ($s3 eq $s4) {print "$s3 is = $s4\n";}

use locale;

print "\nWith 'use locale;':\n\n";

if ($s1 gt $s2) {print "$s1 is > $s2\n";}
if ($s1 lt $s2) {print "$s1 is < $s2\n";}
if ($s1 eq $s2) {print "$s1 is = $s2\n";}

if ($s3 gt $s4) {print "$s3 is > $s4\n";}
if ($s3 lt $s4) {print "$s3 is < $s4\n";}
if ($s3 eq $s4) {print "$s3 is = $s4\n";}

распечатывает

NO Locale:

aaa2000@yahoo.com is < aaa_2000@yahoo.com
aaa2000 is < aaa_2000

With 'use locale;':

aaa2000@yahoo.com is > aaa_2000@yahoo.com
aaa2000 is < aaa_2000

, за которым я не могу следовать: в в то же время , при использовании языка,AND a@yahoo.com> b@yahoo.com? !!

Я упускаю что-то более или менее очевидное, или это ошибка?Могут ли другие подтвердить то же поведение?

Locale is $ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

Заранее спасибо.

Ответы [ 2 ]

4 голосов
/ 27 августа 2011

При включенных локалях сопоставление выполняется за несколько проходов. Каждый персонаж имеет четыре веса, которые сравниваются в последовательных проходах. Знаки @ и _, как и большинство знаков препинания, не имеют первичного, вторичного или третичного веса, поэтому они вступают в игру только на четвертом проходе. Итак, для вашего примера

aaa2000@yahoo.com > aaa_2000@yahoo.com

в первом проходе, это действительно сравнение

aaa2000yahoocom = aaa2000yahoocom

, а затем на четвертом проходе (во втором и третьем проходах нет дифференцирующих факторов)

@. > _@.

потому что @ оказывается больше, чем _ в этой локали. (Это просто выбор, который делает определение локали, предположительно на основе какого-либо стандарта ISO или другого.)

Вы можете заглянуть в детали реализации этого. Сравнение с поддержкой локали в конечном итоге будет реализовано в библиотеке C как strxfrm(A) cmp strxfrm(B). Запустите эту программу:

use POSIX;

my $s1 = 'aaa2000@yahoo.com';
my $s2 = 'aaa_2000@yahoo.com';

foreach ($s1, $s2) {
    printf "%s =>\t%v02x\n", $_, POSIX::strxfrm($_);
}

Я получаю:

aaa2000@yahoo.com =>    0c.0c.0c.04.02.02.02.24.0c.13.1a.1a.0e.1a.18.01.08.08.08.08.08.08.08.08.08.08.08.08.08.08.08.01.02.02.02.02.02.02.02.02.02.02.02.02.02.02.02.01.08.5d.06.44
# explanation:           a  a  a  2  0  0  0  y  a  h  o  o  c  o  m DIV secondary weights ...                       DIV tertiary weights ...                        DIV  @     .
aaa_2000@yahoo.com =>   0c.0c.0c.04.02.02.02.24.0c.13.1a.1a.0e.1a.18.01.08.08.08.08.08.08.08.08.08.08.08.08.08.08.08.01.02.02.02.02.02.02.02.02.02.02.02.02.02.02.02.01.04.36.05.5d.06.44
# explanation:           a  a  a  2  0  0  0  y  a  h  o  o  c  o  m DIV secondary weights ...                       DIV tertiary weights ...                        DIV  _     @     .

Способ получения этих чисел - деталь реализации; они просто должны выйти так, чтобы сравнение байтов дало желаемый конечный результат. Но концепция одинакова во всех современных средах программирования с сортировкой с поддержкой локали.

2 голосов
/ 21 октября 2010

Я получаю те же результаты на моей 32-битной системе Linux с языком en_US.utf8. Это не ошибка Perl, как показано на этой программе на C:

#include <locale.h>
#include <string.h>
#include <stdio.h>

void transformed(const char* str)
{
  char dest[256];
  const char* c;

  strxfrm(dest, str, sizeof(dest));
  printf("%18s =", str);
  for (c = dest; *c; ++c) printf(" %02x", *c);
  puts("");
} /* end transformed */

void test_strings(const char* s1, const char* s2)
{
  int c = strcoll(s1, s2);

  printf("%s is %s %s\n", s1, ((c < 0) ? "<" : ((c == 0) ? "=" : ">")), s2);
} /* end test_strings */

int main(int argc, char* argv[])
{
  puts("with C locale:");

  test_strings("aaa2000@yahoo.com", "aaa_2000@yahoo.com");
  test_strings("aaa2000", "aaa_2000");

  setlocale(LC_ALL, "");
  puts("\nwith your locale:");

  test_strings("aaa2000@yahoo.com", "aaa_2000@yahoo.com");
  test_strings("aaa2000", "aaa_2000");
  puts("");
  transformed("aaa2000@yahoo.com");
  transformed("aaa_2000@yahoo.com");
  transformed("aaa2000");
  transformed("aaa_2000");
  return 0;
} /* end main */

С LANG=en_US.utf8 генерируется:

with C locale:
aaa2000@yahoo.com is < aaa_2000@yahoo.com
aaa2000 is < aaa_2000

with your locale:
aaa2000@yahoo.com is > aaa_2000@yahoo.com
aaa2000 is < aaa_2000

 aaa2000@yahoo.com = 0c 0c 0c 04 02 02 02 24 0c 13 1a 1a 0e 1a 18 01 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 01 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 01 08 5d 06 44
aaa_2000@yahoo.com = 0c 0c 0c 04 02 02 02 24 0c 13 1a 1a 0e 1a 18 01 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 01 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 01 04 36 05 5d 06 44
           aaa2000 = 0c 0c 0c 04 02 02 02 01 08 08 08 08 08 08 08 01 02 02 02 02 02 02 02
          aaa_2000 = 0c 0c 0c 04 02 02 02 01 08 08 08 08 08 08 08 01 02 02 02 02 02 02 02 01 04 36

Функция strxfrm (к которой вы можете обратиться в Perl через модуль POSIX ) возвращает строку, которая указывает порядок сопоставления. Когда вы сравниваете две такие преобразованные строки побайтно, первая строка с меньшим байтом появляется первой в порядке сортировки.

Я не уверен, является ли это ошибкой или нет. Я не могу найти какую-либо документацию о том, как порядок сортировки en_US должен работать. Если является ошибкой, значит, это в вашей библиотеке C или базе данных локали.

...