Форум программистов, компьютерный форум, киберфорум
arni
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Форматирование окончания числа в зависимости от числительного (Delphi)

Запись от arni размещена 21.10.2012 в 16:39
Показов 6370 Комментарии 8

Решил причесать вывод логов, в которых зачастую попадаются несогласованные окончания существительных, стоящих в паре с числительным. Например, можно было видеть такое:
Code
1
2
3
Слито 37 эскизов в резервную папку.
Слито 22 эскизов в резервную папку.
Слито 1 эскизов в резервную папку.
В предложениях 2 и 3 окончание явно не согласуется с числом. Должно было быть "22 эскиза" и "1 эскиз" соответственно.
Размышления привели к выделению трех групп окончаний:
Для 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 и для всех остальных числительных.
Для исправления ситуации можно написать функцию:
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function FormatNumAtStr(const Num: Integer; const Words: array of string;
  const Template: string='%0:d %1:s'): string;
var
  d: Integer;
begin
  if Length(Words)<>3 then
    raise Exception.Create('Ожидается 3 строковых значения.');
  d:=Num mod 100;
  if (d>=11) and (d<=14) then
    Result:=Format(Template, [Num, Words[2]])
  else
    case d mod 10 of
      1: Result:=Format(Template, [Num, Words[0]]);
      2..4: Result:=Format(Template, [Num, Words[1]]);
    else
      Result:=Format(Template, [Num, Words[2]]);
    end;
end;
Простой пример вызова:
Delphi
1
2
3
4
5
6
7
8
ShowMessage(FormatNumAtStr(0, ['эскиз', 'эскиза', 'эскизов']));
ShowMessage(FormatNumAtStr(1, ['эскиз', 'эскиза', 'эскизов']));
ShowMessage(FormatNumAtStr(2, ['эскиз', 'эскиза', 'эскизов']));
ShowMessage(FormatNumAtStr(13, ['эскиз', 'эскиза', 'эскизов']));
ShowMessage(FormatNumAtStr(24, ['эскиз', 'эскиза', 'эскизов']));
ShowMessage(FormatNumAtStr(100, ['эскиз', 'эскиза', 'эскизов']));
ShowMessage(FormatNumAtStr(991, ['эскиз', 'эскиза', 'эскизов']));
ShowMessage(FormatNumAtStr(13999, ['эскиз', 'эскиза', 'эскизов']));
А теперь не просто словосочетание, а вставка в выражение по образцу функции Format():
Delphi
1
2
3
4
5
6
7
8
ShowMessage(FormatNumAtStr(0, ['эскиз', 'эскиза', 'эскизов'], 'Слито %d %s в резервную папку.'));
ShowMessage(FormatNumAtStr(1, ['эскиз', 'эскиза', 'эскизов'], 'Слито %d %s в резервную папку.'));
ShowMessage(FormatNumAtStr(2, ['эскиз', 'эскиза', 'эскизов'], 'Слито %d %s в резервную папку.'));
ShowMessage(FormatNumAtStr(13, ['эскиз', 'эскиза', 'эскизов'], 'Слито %d %s в резервную папку.'));
ShowMessage(FormatNumAtStr(24, ['эскиз', 'эскиза', 'эскизов'], 'Слито %d %s в резервную папку.'));
ShowMessage(FormatNumAtStr(100, ['эскиз', 'эскиза', 'эскизов'], 'Слито %d %s в резервную папку.'));
ShowMessage(FormatNumAtStr(991, ['эскиз', 'эскиза', 'эскизов'], 'Слито %d %s в резервную папку.'));
ShowMessage(FormatNumAtStr(13999, ['эскиз', 'эскиза', 'эскизов'], 'Слито %d %s в резервную папку.'));
Для параметра
Delphi
1
const Template: string='%0:d %1:s'
я неслучайно указал индексы для числового и строкового аргумента, хотя они здесь совершенно необязательны и достаточно будет
Delphi
1
const Template: string='%d %s'
Дело в том, что в ряде случаев понадобится числительное поставить после существительного. Явное указание индексов для аргументов подстановки позволит использовать функцию даже в этом случае:
Delphi
1
2
3
4
5
6
7
8
ShowMessage(FormatNumAtStr(0, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
ShowMessage(FormatNumAtStr(1, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
ShowMessage(FormatNumAtStr(2, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
ShowMessage(FormatNumAtStr(13, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
ShowMessage(FormatNumAtStr(24, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
ShowMessage(FormatNumAtStr(100, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
ShowMessage(FormatNumAtStr(991, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
ShowMessage(FormatNumAtStr(13999, ['эскиз', 'эскиза', 'эскизов'], 'Было перемещено %1:s: %0:d'));
пример стилистически не удачен, но случаев, подобных этому, тоже хватает.
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 8
Комментарии
  1. Старый комментарий
    Аватар для Evg
    Проблема выбрана актуальная, только подход к ней, на мой взгляд, выбран неверно. Ты написал целую функцию, которая занимается форматированием полной строки вывода. В то время как надо писать функцию, которая всего лишь выбирает один вариант из трёх, но не занимается полным форматированием строки. Паскалём не владею, а просто схематично напишу на Си то, как я вижу правильное решение:

    C
    const 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, "эскиз", "эскиза", "эскизов"));
    Другими словами, в твоём варианте функция FormatNumAtStr организует печать только для пары целое-строка, а в моём - отаётся форматирование произвольного количества параметров
    Запись от Evg размещена 23.10.2012 в 10:10 Evg вне форума
  2. Старый комментарий
    Аватар для arni
    Это вопрос личного предпочтения.
    Первый вариант функции возвращал только слово.
    После внедрения в исходник проекта, мне показалось, что лишняя конкатенация мне не к чему, и переделал функцию именно в подобие Format(), чтобы разом передавать не только смысловые аргументы, но и контекст. Так код почище выглядит. Но еще раз - это дело вкуса.
    Запись от arni размещена 23.10.2012 в 10:17 arni вне форума
  3. Старый комментарий
    Аватар для Evg
    Я правильно понимаю, что в твоей реализации третий параметр подсунуть нельзя без модификации функции?
    Запись от Evg размещена 23.10.2012 в 18:07 Evg вне форума
  4. Старый комментарий
    Аватар для arni
    Боюсь, что понял неправильно ваш вопрос. Но если вы о том, чтобы передать в параметр Template еще один маркер подстановки (%s например для строкового значения), то да, он не будет обработан, либо даже внутренний вызов Format() вернет исключение, не найдя аргумента для подстановки.
    Пожалуй тут можно было бы доработать функцию, используя массив параметров, принимающий неопределение кол-во аргументов, но пожалуй это будет уже слишком усложненым. В случае, если потребует дополнительное форматирование, я предпочту обернуть в еще один вызов Format() уже по месту.
    Запись от arni размещена 23.10.2012 в 20:21 arni вне форума
  5. Старый комментарий
    Аватар для Evg
    Вопрос понял правильно. Твой выбор - дело хозяйское. Правда я не понимаю, чем универсальное решение:

    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 Evg вне форума
  6. Старый комментарий
    Аватар для arni
    Ок, давайте меряться
    Я приведу оба примера к единым условиям, а именно дам именам функции одно имя (суффикс А-мой, В-ваш) и буду использовать одну числовую константу. 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, ['эскиз', 'эскиза', 'эскизов']), 'склад'])
    Ваш вызов:
    Delphi
    1
    
    Format('Было перемещено %d %s на %s', [13999, NumAtStrB(13999, 'эскиз', 'эскиза', 'эскизов'), 'склад'])
    В моем случае пришлось завести Format() для вставки третьего параметра, но запись по прежнему оказалась короче, хотя и на мизер (экономия на пропуске дефолтного шаблона, раз уж мы не передаем контекст в FormatNumAtStrA).

    Пример 4: Наипростейший случай.
    Мой вызов:
    Delphi
    1
    
    FormatNumAtStrA(13999, ['эскиз', 'эскиза', 'эскизов'])
    Ваш вызов:
    Delphi
    1
    
    Format('%d %s', [13999, NumAtStrB(13999, 'эскиз', 'эскиза', 'эскизов')])
    Мой пример смотрится выйгрышнее, т.к. содержит меньше вызовов, меньше скобок, меньше параметров, а поэтому на четверть короче.

    Итого, я делаю такой вывод: при большем кол-ве параметров, при более сложном форматировании ваш пример возможно станет эффективнее, если судить по длине записи и её "обзорности". Но в простых случаях мой вариант короче, хотя и жертвует универсальностью. Ваш вариант длинее, обильно пересыпан скобками, но не спорю - нагляднее для понимания, если смотрит посторонний человек (не автор).
    Исходя из принципа Парето 80/20 я выбираю эффективность в 80% случаев, жертвуя 20%.
    Запись от arni размещена 24.10.2012 в 08:28 arni вне форума
  7. Старый комментарий
    Аватар для Evg
    Пр эффективность речь не идёт вообще (поскольку разницы между этими вариантами по скорости практически нету), речь идёт только об универсальности. Для программы на одну страницу, которая выводит, грубо говоря, только один тип сообщений, твой вариант конечно сойдёт, поскольку ты пока руководствуешься принципом наименьшего количества букв и чтоб не дай боже не было пары лишних скобок (и это ты называешь "обильно пересыпан скобками"). Я же руководствуюсь тем, что программа пишется большой командой и состоит из тысяч исходных файлов, а потому построение программы должно быть в виде универсальных интерфейсов с небольшими прокладками под частные случаи. Потому мы и смотрим на проблему с разных углов зрения
    Запись от Evg размещена 24.10.2012 в 09:07 Evg вне форума
  8. Старый комментарий
    Аватар для arni
    Согласен, каждый останется при своих, исходя из своего личного опыта, экосистемы и прочих соображений.
    Запись от arni размещена 24.10.2012 в 09:20 arni вне форума
 
Новые блоги и статьи
Нейросеть на алгоритме "эстафета хвоста" как перспектива.
Hrethgir 06.05.2026
На десерт, когда запущу сервер. Статья тут https:/ / habr. com/ ru/ articles/ 1030914/ . Автор я сам, нейросеть только помогает в вопросах которые мне не известны - не знаю людей которые знали-бы. . .
Асинхронный приём данных из COM-порта
Argus19 01.05.2026
Асинхронный приём данных из COM-порта Купил на aliexpress термопринтер QR701. Он оказался странным. Поключил к Arduino Nano. Был очень удивлён. Наотрез отказывается печатать русские буквы. Чтобы. . .
попытка написать игровой сервер на C++
pyirrlicht 29.04.2026
попытка написать игровой сервер на плюсах с открытым бесконечным миром. возможно получится прикрутить интерпретатор питон для кастомизации игровой логики. что есть на текущий момент:. . .
Контроль уникальности выбранного документа-основания при изменении реквизита
Maks 28.04.2026
Алгоритм из решения ниже разработан на примере нетипового документа "ЗаявкаНаРемонтСпецтехники", разработанного в КА2. Задача: уведомлять пользователя, если указанная заявка (документ-основание). . .
Благородство как наказание
Maks 24.04.2026
У хорошего человека отношения с женщинами всегда складываются трудно. А я человек хороший. Заявляю без тени смущения, потому что гордиться тут нечем. От хорошего человека ждут соответствующего. . .
Валидация и контроль данных табличной части документа перед записью
Maks 22.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа, разработанного в КА2. Задача: контроль и валидация данных табличной части документа перед записью с учетом регламента компании. . .
Отчёт о затраченных материалах за определенный период с макетом печатной формы
Maks 21.04.2026
Отчёт из решения ниже размещён в конфигурации КА2. Задача: разработка отчёта по затраченным материалам за определённый период, с возможностью вывода печатной формы отчёта с шапкой и подвалом. В. . .
Отчёт о спецтехнике находящейся в ремонте
Maks 20.04.2026
Отчёт из решения ниже размещен в конфигурации КА2. Задача: отобразить спецтехнику, которая на данный момент находится в ремонте. Есть нетиповой документ "Заявка на ремонт спецтехники" который. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru