Форматирование окончания числа в зависимости от числительного (Delphi)
Запись от arni размещена 21.10.2012 в 16:39
Показов 6370
Комментарии 8
Решил причесать вывод логов, в которых зачастую попадаются несогласованные окончания существительных, стоящих в паре с числительным. Например, можно было видеть такое:
Размышления привели к выделению трех групп окончаний: Для 1 шт.: "1 файл", "1 банка". Сюда же относятся 1 + десятки/сотни/тысячи/т.д., "21 телефон", "301 файл", "4001 банка". Исключение: число 11. Для 2..4 шт: "2 файла", "4 банки". Сюда же относятся 2..4 + десятки/сотни/тысячи/т.д., "23 телефона", "803 файла", "19002 банки". Исключение: числа 12-14. Для остальных форм множественного числа (а также для нуля): "0 телефонов", "9 файлов", "87 банок". К этому же, третьему типу относятся числа 11..14: "11 телефонов", "12 файлов", "13 банок". Поиск по RTL, в частности по модулям SysUtils и StrUtils, не дал встроенного решения проблемы, что не удивительно: в английском языке всего два окончания: для 1 и для всех остальных числительных. Для исправления ситуации можно написать функцию:
| |||||||||||||||||||||||||||||||||||
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 8
Комментарии
-
Проблема выбрана актуальная, только подход к ней, на мой взгляд, выбран неверно. Ты написал целую функцию, которая занимается форматированием полной строки вывода. В то время как надо писать функцию, которая всего лишь выбирает один вариант из трёх, но не занимается полным форматированием строки. Паскалём не владею, а просто схематично напишу на Си то, как я вижу правильное решение:
Другими словами, в твоём варианте функция FormatNumAtStr организует печать только для пары целое-строка, а в моём - отаётся форматирование произвольного количества параметровCconst char* my_select (unsigned val, const char *str1, const char *str2, const char *str3) { /* В зависимости от val выберем один из трёх элементов массива */ } /* далее используем стандартный printf абсолютно произвольным образом */ printf ("Выбрано %d %s\n", val, my_select (val, "эскиз", "эскиза", "эскизов")); printf ("Товарищ %s пока работал в %s выбрал %d %s\n", str1, str2, val, my_select (val, "эскиз", "эскиза", "эскизов"));
Запись от Evg размещена 23.10.2012 в 10:10
-
Это вопрос личного предпочтения.
Первый вариант функции возвращал только слово.
После внедрения в исходник проекта, мне показалось, что лишняя конкатенация мне не к чему, и переделал функцию именно в подобие Format(), чтобы разом передавать не только смысловые аргументы, но и контекст. Так код почище выглядит. Но еще раз - это дело вкуса.Запись от arni размещена 23.10.2012 в 10:17
-
Запись от Evg размещена 23.10.2012 в 18:07
-
Боюсь, что понял неправильно ваш вопрос. Но если вы о том, чтобы передать в параметр Template еще один маркер подстановки (%s например для строкового значения), то да, он не будет обработан, либо даже внутренний вызов Format() вернет исключение, не найдя аргумента для подстановки.
Пожалуй тут можно было бы доработать функцию, используя массив параметров, принимающий неопределение кол-во аргументов, но пожалуй это будет уже слишком усложненым. В случае, если потребует дополнительное форматирование, я предпочту обернуть в еще один вызов Format() уже по месту.Запись от arni размещена 23.10.2012 в 20:21
-
Вопрос понял правильно. Твой выбор - дело хозяйское. Правда я не понимаю, чем универсальное решение:
чем-то принципиально (т.е. что-то более существенное, чем количество букв) хуже частного неуниверсального:Delphi 1
ShowMessage(Format('Было перемещено %0:d %1:s', [val, Select (val, 'эскиз', 'эскиза', 'эскизов')]));
Возможно, на паскале свои сложности, просто мне, как программисту на Си, не видно ни одной причины, зачем надо было бы на ровном месте усложнять себе жизнь промышленным выпуском велосипедовDelphi 1
ShowMessage(FormatNumAtStr(13999, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
Запись от Evg размещена 23.10.2012 в 23:03
-
Ок, давайте меряться
Я приведу оба примера к единым условиям, а именно дам именам функции одно имя (суффикс А-мой, В-ваш) и буду использовать одну числовую константу. ShowMessage() отбросим, как не относящийся к делу. Индексы параметров подстановки также в топку для чистоты примера.
Мой вызов:
Ваш вызов:Delphi 1
FormatNumAtStrA(13999, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %d %s')
Дело вкуса, но я при прочих равных условиях голосую за короткую запись с меньшим кол-вом скобок, как более простую для понимания.Delphi 1
Format('Было перемещено %d %s', [13999, NumAtStrB(13999, 'эскиз', 'эскиза', 'эскизов')])
Пример 2. Нужно переставить параметры местами.
Мой пример чуть усложнится (добавятся индексы к маркерам подстановки):
Ваш вызов останется неизменным, только поменяются местами маркеры:Delphi 1
FormatNumAtStrA(13999, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d')
При этом мой пример все равно оказался короче, и я бы сказал, что нагляднее (кроме концовки: индексы у параметров подстановки могут смутить).Delphi 1
Format('Было перемещено %s: %d', [NumAtStrB(13999, 'эскиз', 'эскиза', 'эскизов'), 13999])
Пример 3: Нужно добавить третий параметр:
Мой вызов:
Ваш вызов:Delphi 1
Format('Было перемещено %s на %s', [FormatNumAtStrA(13999, ['эскиз', 'эскиза', 'эскизов']), 'склад'])
В моем случае пришлось завести Format() для вставки третьего параметра, но запись по прежнему оказалась короче, хотя и на мизер (экономия на пропуске дефолтного шаблона, раз уж мы не передаем контекст в FormatNumAtStrA).Delphi 1
Format('Было перемещено %d %s на %s', [13999, NumAtStrB(13999, 'эскиз', 'эскиза', 'эскизов'), 'склад'])
Пример 4: Наипростейший случай.
Мой вызов:
Ваш вызов:Delphi 1
FormatNumAtStrA(13999, ['эскиз', 'эскиза', 'эскизов'])
Мой пример смотрится выйгрышнее, т.к. содержит меньше вызовов, меньше скобок, меньше параметров, а поэтому на четверть короче.Delphi 1
Format('%d %s', [13999, NumAtStrB(13999, 'эскиз', 'эскиза', 'эскизов')])
Итого, я делаю такой вывод: при большем кол-ве параметров, при более сложном форматировании ваш пример возможно станет эффективнее, если судить по длине записи и её "обзорности". Но в простых случаях мой вариант короче, хотя и жертвует универсальностью. Ваш вариант длинее, обильно пересыпан скобками, но не спорю - нагляднее для понимания, если смотрит посторонний человек (не автор).
Исходя из принципа Парето 80/20 я выбираю эффективность в 80% случаев, жертвуя 20%.Запись от arni размещена 24.10.2012 в 08:28
-
Пр эффективность речь не идёт вообще (поскольку разницы между этими вариантами по скорости практически нету), речь идёт только об универсальности. Для программы на одну страницу, которая выводит, грубо говоря, только один тип сообщений, твой вариант конечно сойдёт, поскольку ты пока руководствуешься принципом наименьшего количества букв и чтоб не дай боже не было пары лишних скобок (и это ты называешь "обильно пересыпан скобками"). Я же руководствуюсь тем, что программа пишется большой командой и состоит из тысяч исходных файлов, а потому построение программы должно быть в виде универсальных интерфейсов с небольшими прокладками под частные случаи. Потому мы и смотрим на проблему с разных углов зренияЗапись от Evg размещена 24.10.2012 в 09:07
-
Запись от arni размещена 24.10.2012 в 09:20



