Как стать автором
Обновить

Комментарии 28

Мне кажется, что с Вашим последним примером что-то не так. Насколько корректно использовать модифицирующий символ перед латинской буквой?

Вот что выдаёт perl 5.16.3:
$ perl ./reverse.pl 
ok 1 - '' -> ''
ok 2 - 'a' -> 'a'
ok 3 - 'ab' -> 'ba'
ok 4 - 'a b' -> 'b a'
ok 5 - 'ф' -> 'ф'
ok 6 - 'xф' -> 'фx'
ok 7 - 'yфz' -> 'zфy'
ok 8 - 'фх' -> 'хф'
ok 9 - 'Й' -> 'Й'
ok 10 - 'iЙ' -> 'Йi'
ok 11 - 'Йi' -> 'iЙ'
ok 12 - 'Йф' -> 'фЙ'
not ok 13 - 'zЙ̈y' -> 'yЙ̈z'
#   Failed test ''zЙ̈y' -> 'yЙ̈z''
#   at ./reverse.pl line 45.
#          got: 'ÿЙz'
#     expected: 'yЙ̈z'
1..13
# Looks like you failed 1 test of 13.

код reverse.pl:
#!/usr/bin/perl
use warnings;
use strict;
use 5.012;
use open ':utf8', ':std';
use Test::More;
use Unicode::Normalize;

my @s = (
  # тривиальные ascii-случаи
  "", "",
  "a", "a",
  "ab", "ba",
  "a b", "b a",

  # одна русская буква, записанная в UTF-8
  "\xd1\x84", "\xd1\x84",

  # смесь русских и латинских букв
  "x\xd1\x84", "\xd1\x84x",
  "y\xd1\x84z", "z\xd1\x84y",
  "\xd1\x84\xd1\x85", "\xd1\x85\xd1\x84",

  # одна русская буква, записанная в декомпозированной форме
  "\xd0\x98\xcc\x86", "\xd0\x98\xcc\x86",

  # смесь русских декомпозированных и латинских букв
  "i\xd0\x98\xcc\x86", "\xd0\x98\xcc\x86i",
  "\xd0\x98\xcc\x86i", "i\xd0\x98\xcc\x86",
  "\xd0\x98\xcc\x86\xd1\x84", "\xd1\x84\xd0\x98\xcc\x86",

  # забавы ради: zЙ̈y
  "z\xd0\x98\xcc\x86\xcc\x88y", "y\xd0\x98\xcc\x86\xcc\x88z"
);

# преобразуем значения @s из последовательностей байт в Unicode-строки
utf8::decode $_ for @s;

while (@s) {
    my ($from, $to) = splice @s, 0, 2;
    # NFC() возвращает Normalization Form C (formed by canonical
    #   decomposition followed by canonical composition)
    # NFD() возвращает Normalization Form D (formed by canonical
    #   decomposition)
    is NFD(reverse(NFC($from))), $to,   "'$from' -> '$to'";
}
done_testing();

Так это не перед, это «И» и два модифицирующих символа после. Если уж честно, то я не знаю, можно ли так делать, но моя программа этот тест проходит.
Википедия говорит, что можно
In Unicode, diacritics are always added after the main character, so it is possible to add several diacritics to the same character
Несколько-то добавлять можно, не вопрос. Вопрос в том, корректно ли добавлять конкретно эти два после конкретно этого символа.
А почему нельзя-то? Символы-модификаторы могут вешаться куда угодно. Это неважно совершенно.
Ненормальным программированием это бы было в случае отказа от использования ICU и трешоугарным переворачиванием без перекодирования.
Это был бы велосипедизм. Размещая в хабе «Ненормальное программирование» хотел подчеркнуть нетривиальное правильное решение школьной задачи.
А символы смены направления текста у вас обрабатываются?
Получается вот так: "qu\xe2\x80\xaewz" -> "zw\xe2\x80\xaeuq". С моей точки зрения, это правильно: ведь задача формулировалась как «записать Unicode-символы в обратном порядке».
А если сформулировать задачу «записать байты в обратном порядке» — будет ещё проще. Я думал мы в этом топике пытаемся усложнять задачу?

Был валидный юникод: «foo{RLE=U+202B}bar{PDF=U+202C}baz», после вашего инвертирования становится невалидный «zab{PDF=U+202C}rab{RLE=U+202B}oof».

Представьте себе что у вас форум, и вы где-то с помощью этой функции отображаете на странице реверсированный ник участника. Если у него в нике были символы смены направления текста (может быть они даже были провалидированы движком на парность), после вашего преобразования они становятся непарными и вксь форум становится RTL.

Юникод каждый раз найдёт способ вынести мозг :D
Да, засада. Постараюсь исправить.

-webkit-transform: rotateY(180deg); кажется самым универсальным способом :)
Юникод каждый раз найдёт способ вынести мозг :D
Вот поэтому важно чётко различать всё три сущности:
  • текст — абстрактная штука, которую видят и читают люди;
  • строки — представление текста в программе;
    для Юникода — последовательность code points, сформированных определённым образом;
  • байты — представление строк в памяти, файлах, сети, и т. д.;
    для Юникода — последовательность code units, полученная в соответствии с определённой кодировкой.
Некоторые последовательности байт могут не быть корректным представлением символов. Некоторые последовательности символов и модификаторов могут не быть корректным текстом.
Раз уж мы нашли эксперта по юникоду в этом треде, то ответьте пожалуйста на пару вопросов:

1) Символы смены направления (RLE, LRE, PDF и иже с ними) — это code points, или же текст? Предположу что code points.
2) Имеет ли смысл задача обращения строки в юникоде (обращение порядка code points)? Кажется нет.
3) Имеет ли смысл задача обращения текста (вот той самой абстрактной штуки которую видят люди)? Разрешима ли она? Если зачем-то хочется её решать, то нужно группировать code points в видимые сущности типа символов, и инвертировать порядок символов. И как-то особо обращаться с невидимыми code points. Хотя
webkit-transform: rotateY(180deg);
конечно лучше всего :)
1. Да, это code points. Так в Юникоде представляется смена направления письма в тексте. От появления этих штук не меняется порядок следующих символов в строке, но должно меняться представление этой строки на экране в виде текста.

2. Почему бы и нет? Вдруг у вас есть какая-то функция, которая (по какой-то причине) ожидает как раз обращённые строки. Тогда вам надо будет перевернуть строку именно как строку. Это уже другое дело, что под обращением строки чаще всего понимают обращение текста (слава богу, что уже никто в здравом уме не предлагает обращать порядок байтов).

3. Ну вот кому-то же надо перевернуть имена на форуме :) Разрешима, конечно, но решение зависит от локали — языка, на котором написан текст. Например, если в тексте не могут встречаться модификаторы и прочее, то переворачивание строки идентично переворачиванию текста. (Аналогично, если текст написан исключительно на английском языке и закодирован UTF-8, то даже переворачивание байтов правильно перевернёт и текст.)

Иначе же обращение строки надо выполнять с учётом того, в какие видимые символы преобразуются последовательности code points. Символы с комбинируемой диакритикой надо оставлять как есть (e + ´). Лигатуры — разбивать и переворачивать (ffi → iff). Оконечные символы — заменять на альтернативные написания (Ὀδυσσεύς → σύεσσυδὈ). И так далее, и так далее, и так далее…

Юникод сложен, потому что сложны естественные языки, текст на которых он призван представлять.
Спасибо за такой развернутый ответ!
Кроме как «обратить строку» задача никак не формулировалась. А под обращением строки обычно понимают другое: "\xe2\x80\xaezw\xe2\x80\xaduq"
Почесав в затылке: А если добавить символ обращения направления текста в начале, а потом заменить все подобные штуки на противоположные в остальном тексте? Формально текст будет обращён, не?
Визуально да. Но обращение текста может быть полезным в ряде задач поиска, и там такими уловками не обойдешься.
А у нас разве есть символ обращения направления текста? Вроде только для форсинга RLE/LRE, но не для обращения.
При выводе смене RLE на LRE в результате как раз обращение и происходит.
Но текст мог быть не обрамлён в RLE/LRE, тогда его обратить так легко не получится, т.к. неясно какой тег добавлять.

Да и вообще тег RLE/LRE например вовсе не означает что текст внутри будет написан именно в таком порядке. Он лишь задаёт основное направление текста — что по сути влияет на знаки препинания на границе текста, а также на то в каком порядке будут идти блоки LTR и RTL символов, порядок символов внутри блока зависит только от самих символов.

То есть если у нас текст тупо из символов английского алфавита — то есть это один блок, то оборачивание его в RLE-тег не изменит ровным счетом ничего.
А в юникоде вообще есть какое-нибудь (универсальное) понятие, обозначающее атомарные элементы текста, к последовательности которых можно было бы применить термин «обращение»?

Типа буква + модифицирующие символы.
Нет. Потому что, как уже отмечалось юникод сложен, потому что сложны естественные языки, текст на которых он призван представлять.

Есть арабский, где каждая буква существует в четырёх вариантах: для записи в начале слова, в конце слова, в середине и когда она стоит отдельно. После переворачивания мы проде как должны поменять первые два варианта местами… а может всё превратить в отдельно стоящие буквы?

Есть хангыль, вы его, наверное видели на разных этикетках с надписями по корейски. Там такие характерные «иероглифы», которые вроде как состоят из отдельных маленьких штучек. Так вот: это слова — и они действительно состоят из отдельных слогов! Называется «чамо». Вот когда мы строку переворачиваем — мы хотим поменять местами слоги или таки целые слова?

Есть, наконец, японский, где есть аж три способа записи слов: кандзи, хирагана, и катакана. Всё бы ничего, но проблема в том, что кандзи не позволяют менять слова (они пришли из китайского, где это не нужно), потому если слово получает суффикс (все эти -сан и -сама), то к кандзи приписывается пара символов хираганой.

И у тех же японцев есть ещё и фуригана — это когда к кандзи приписывается маленькими буквами то же слово каной. С этим мы что будем делать?

В общем начав описанную в топике задачу можно обсуждать неделями — и всё равно так и не придти к «единственно правильному» варианту.
Таки есть правила для расстановки границ «графемных кластеров» (в NFD-нормализованном тексте):
www.unicode.org/reports/tr29/tr29-23.html

А варианты написания арабского — это, по идее, задача рендеринга шрифтов, а не представления в строке. Там же где кернинг и антиалиасинг.
Также, как и греческая сигма.
Разные формы имеют собственные кодировки (у арабского — в отдельном диапазоне), но это должно нивелироваться нормализацией.

Есть ещё кластеры в деванагари, представляющие сочетания согласных. Но у них нет даже отдельных представлений в юникоде.
Тоесть тут чистейшей воды задача рендеринга.
Я правда не понял, что с деванагари буквой ssa (из примера в документе)

Кроме хангыля, немного похожая ситуация в тибетском и всяких тайских-кхмерских (элементы слога пишутся сильно нелинейно),
В этих случаях графемный кластер определяется, как базовый символ + расширяющие символы.
А для хангыля — цепочка символов, образующая «слог» и есть графемный кластер.

Но таки да, в любом случае, в условиях задачи надо указывать «обращение порядка графемных кластеров»
Да и вообще, указанная в топике формулировка «не-ascii» никак не подразумевает, что это utf.
Да, это так же может быть и Shift JIS. И я не понимаю — в чем причина недовольства — что в топике рассматривается какой-то частный случай? Может быть, мне еще надо написать как правильно обратить
int data[] = {1, 2, 3, 4, 5};
char* data_to_reverse = reinterpret_cast<char*>(data);

?
Причина недовольства в недостаточности формулировки задачи.
Кроме того, даже если обозначить, что переворачивается юникод-строка, она может быть:
— в ненормализованной форме (с лигатурами, встроенными диакритиками, отдельными кодами для форм написания оконечных/начальных букв, и всё что угодно)
— в канонической декомпозиции
— в канонической композиции
— в совместимой декомпозиции
— в совместимой композиции

Понятия «строки» в юникоде нет.
Юникод «строка» — это последовательность code-point'ов.
Их свойства типа модификаторов или обозначения направления — это уже семантика символов, на уровне сбалансированности скобок.
«обращение» такой строки не имеет смысла (интерпретации в виде человекопонятного текста).

Если пытаться «обращать» в контексе человекопонимаемого текста, то есть варианты обращения:
— последовательности «графемных кластеров», определённых в стандарте юникода
— написания элементов слов (с учётом особенностй начертания начальных/конечных букв)
— написания элементов слогов (в слого-ориентированных писменностях)
— произношения (с учётом всей фонологии языка)

А вы смело обозначили, что тут существует всего две проблемы, половина из которых решается библиотечными вызовами.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации