Комментарии: (13)

Русский Язык. Выбор окончаний

Рубрика : Наше ПО

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

Например: "Комментариев: 4", "Новых писем: 1", "Всего сотрудников: 19" и т.д.

Пользователь - существо, как правило, неприхотливое. Для пользователя программа - магия. И если она работает, то уже хорошо, главное - покажите куда нажимать.

Но если вы все же найдете свободную минутку, чтобы его порадовать, то почему бы не сделать так: "4 комментария", "1 новое письмо", "Всего 19 сотрудников" и т.д.

В английском языке все просто: "1 comment", "N comments" (N > 1). Богатство же Русского Языка не дает скучать хмурым программистам. Статья посвящена правильному выбору окончаний.

Давайте возьмем слово "сообщение" и пробежимся по первой десятке чисел.

  • 0 сообщений
  • 1 сообщение
  • 2 сообщения
  • 3 сообщения
  • 4 сообщения
  • 5 сообщений
  • 6 сообщений
  • 7 сообщений
  • 8 сообщений
  • 9 сообщений

Можно выделить три группы, я назову их w1, w2 и w5.

Группа w1:

  • 1 сообщение

Группа w2:

  • 2 сообщения
  • 3 сообщения
  • 4 сообщения

Группа w5:

  • 5 сообщений
  • 6 сообщений
  • 7 сообщений
  • 8 сообщений
  • 9 сообщений
  • 0 сообщений

Для любого числа подходящая пара будет относится к одной из этих трех групп. Но как обобщить правило для всех чисел?

Первая мысль, которая приходит в голову - брать последнюю цифру. Действительно: "1021 сообщение", "542 сообщения", "35 сообщений". Но эта мысль верна ровно на 90%.

Дело в том, что все числа, предпоследняя цифра которых равна единице, отностяся к группе w5: "11 сообщений", "1012 сообщений".

Теперь можно сформулировать правило выбора группы для любого числа:

  • w5, если предпоследняя цифра равна единице
  • или w5, если последняя цифра больше или равна пяти
  • или w5, если последняя цифра равна нулю
  • или w1, если последняя цифра равна единице
  • иначе w2

Ниже несколько реализаций функции.

PHP:

//
// Функция выбирает нужное слово для конкретного числа.
// Например: 1 сообщение, 2 сообщения, 5 сообщений.
//
function NumberOf($number, $w1, $w2, $w5, $appendNumber = true)
{
	$n1 = $number % 10;   // 123 % 10 = 3
	$n12 = $number % 100; // 123 % 100 = 23
 
	if ($n1 >= 5 || $n1 == 0 || ($n12 >= 11 && $n12 <= 19))
		$w = $w5;
	else if ($n1 == 1)
		$w = $w1;
	else
		$w = $w2;
 
	return $appendNumber ? "$number $w" : $w;
}

Пример использования:

$n = 10;
 
echo NumberOf($n, 'новое сообщение', 'новых сообщения', 'новых сообщений');
 
echo "<b>$n</b>" . 
       NumberOf($n, 'новое сообщение', 'новых сообщения', 'новых сообщений', false);

Результат:

10 новых сообщений
<b>10</b> новых сообщений

Java Script:

//
// Функция выбирает нужное слово для конкретного числа.
// Например: 1 сообщение, 2 сообщения, 5 сообщений.
//
function numberOf(number, w1, w2, w5, appendNumber)
{
	var w = '';	
	var n1 = number % 10;   // 123 % 10 = 3
	var n12 = number % 100; // 123 % 100 = 23
 
	if (n1 >= 5 || n1 == 0 || (n12 >= 11 && n12 <= 19))
		w = w5;
	else if (n1 == 1)
		w = w1;
	else
		w = w2;
 
	if (appendNumber == null)
		appendNumber = true;
 
	return appendNumber ? (number + ' ' + w) : w;
}

Пример использования:

var n = 10;
alert numberOf(n, 'новое сообщение', 'новых сообщения', 'новых сообщений');
alert numberOf(n, 'новое сообщение', 'новых сообщения', 'новых сообщений', false);

C#:

//
// Функция выбирает нужное слово для конкретного числа.
// Например: 1 сообщение, 2 сообщения, 5 сообщений.
//
static string NumberOf(int number, 
                       string w1, 
                       string w2, 
                       string w5, 
                       bool appendNumber)
{
	string w = "";	
	int n1 = number % 10;   // 123 % 10 = 3
	int n12 = number % 100; // 123 % 100 = 23
 
	if (n1 >= 5 || n1 == 0 || (n12 >= 11 && n12 <= 19))
		w = w5;
	else if (n1 == 1)
		w = w1;
	else
		w = w2;
 
	return appendNumber ? (number.ToString() + ' ' + w) : w;
}

Пример использования:

string s1, s2;
int n = 10;
 
s1 = NumberOf(n, "новое сообщение", "новых сообщения", "новых сообщений", true);
s2 = NumberOf(n, "новое сообщение", "новых сообщения", "новых сообщений", false);
 
Console.WriteLine(s1);
Console.WriteLine(s2);

Материал не претендует на революционность, хотя и является собственной разработкой. Я не искал решений в Интеренете, так как люблю изобретать велосипеды )

Комментарии (13)

Дим, просто хотел сказать спасибо за то, что этой статьёй ты поднял такой вопрос. Действительно, в русском языке из-за большого количество флексий (частей слова, изменяющихся при склонении/спряжении или другом словоизменении) сделать UI с учётом всех особенностей очень сложно, и в общем виде эта задача, скорее всего, не решается. Однако, надеюсь, что понемногу такие приятные мелочи будут всё больше и больше проникать во все приложения, как веб- так и десктопные.

Согласен, Андрей. Даже такая простая задача как вывести по-русски:

«$username1 выиграл у $username2″

кажется не решаемой. Если запрашивать пол при регистрации, то можно правильно поставить окончание у глагола, но не будешь же просить пользователя ввести свое имя во всех падежах! ))

@Дмитрий
в WoW просят:)

@Дмитрий
Вообще-то есть функция взятия по модулю – здесь она больше подойдет.

хотелось бы оптимизировать один момент
number – Math.floor(number / 10) * 10 == number % 10
number – Math.floor(number / 100) * 100 == number % 100

> в WoW просят:)
А если пользователь не знаком с падежами)

> %
Hazzik, Kutu, вы чертовски правы! Поправил в статье. Спасибо.

У Контакта неплохо получается склонять имена, кстати. Но там, скорее всего, просто база.

Сегодня добавил подобную функцию в один из своих проектов на работе. Мелочь, а приятно; плюс в карму :-)

Андрюха, на Objective-C, написал?) В общем, если не на PHP/JS/C# будет круто, если ты ее здесь запостишь. Будем собирать коллекцию)

* Исходники можно подсвечивать с помощью тега pre вот так.

Да не, на работе я больше на джаве пишу :-) Вот исходник:

private static final String[] _pageWordForms =
    {"лист", "листа", "листов"};
 
private static String getPageWordForm(Long pageCount)
{
    Long pageCountModulo10 = pageCount % 10;
    Long pageCountModulo100 = pageCount % 100;
 
    if (pageCountModulo10 >= 5 || pageCountModulo10 == 0 ||
            (pageCountModulo100 >= 11 && pageCountModulo100 <= 19))
    {
        return _pageWordForms[2];
    }
    else if (pageCountModulo10 == 1)
        return _pageWordForms[0];
    else
        return _pageWordForms[1];
}

Спасибо за вариант на Java Script, добавлю в колекцию)

когда уже люди поймут, что Java и JavaScript это разные вещи???!!!

Namreg, выше был как вариант на Java, так и на Java Script. Так что внимательнее читайте, что комментируете.

Комментировать