Dubrowsky
Хроники одного дупла
Блогово  →  WebDev  → 

Склонение (согласование) существительных с числом на PHP

09 Июня 2012 года

Задачка древняя, что твой дедушка Ленин, однако пыхари всех стран не унимаются до сих пор. Возможно, из-за того, что до сих пор здесь и там можно встретить надпись в духе "Пользователи оставили 21 комментариев(я)". Внесу свою лепту и я, код и некоторые рассуждения - под катом.

Собственно, функция, принимающая 2 аргумента (первый из которых число, а второй - массив со словоформами), уже давно перестала быть диковинкой и есть в запаснике у многих. Например приблизительно в таком виде. Кстати, интересно, что эта задача почему-то традиционно называется "склонение числительных", хотя с числительным как раз ничего не делается, оно вообще выводится цифрами :)

Так вот, подход верный и работает, но беда в том, что таскать везде массив словоформ - это таки неудобно. Особенно если шаблонная система, где обычно и происходит вывод, не знает ничего про массивы. Ага, я про XSLT например :)

Кроме того, часто нужно строки доставать из какого-то хранилища данных для локализации ("словаря"), а механизм словарей в большинстве случаев рассчитывает, что в нем будут хранить строки, а не массивы.

Нижеследующий код умеет делать все то же самое, но вторым аргументом получает строку. Для наглядности, сначала пример работы.

$nums = array(1, 3, 5);
$strings = array(
	'кенгуру', // не склоняется 
	'hour%s', // мн. ч. всегда одинаковое 
	'час%а%ов', // ед. ч. == основа
	'комментари%й%я%ев', // ух, сколько вариантов!
	'%ребенок%ребенка%детей' // начинается с %, берем "как есть"
);
foreach ( $strings as $str) {
	foreach ($nums as $num) {
		echo $num." ".decl($num, $str)."; ";
	}
	echo "<hr />";
}

Посмотреть вывод можно тут. Кстати, в этом примере видно, как функцию можно использовать для локализации - см. "часы" и "hours". Да, и массив вторым аргументом оно тоже поддерживает, если кому надо :)

А вот и сниппет со всей функцией:

function decl($num,$words) {
	if (is_string($words)) {
		$parts = explode("%", $words);
		$r = array_shift($parts);
		if ($r == '') {
			$words = $parts;
			if (!isset($words[2])) {
				$words[2] = $parts[1];
			}
		} else {
			$count_parts = count($parts);
			// кенгуру
			$words = array($r, $r, $r);
			switch ($count_parts) {
				case 1:
					// hour%s
					$words[1] .= $parts[0];
					$words[2] .= $parts[0];
					break;
				case 2:
					// час%а%ов
					// глаз%а%
					$words[1] .= $parts[0];
					$words[2] .= $parts[1];
					break;
				case 3: 
					// комментари%й%я%ев
					$words[0] .= $parts[0];
					$words[1] .= $parts[1];
					$words[2] .= $parts[2];
					break;
			}
		}
	}
	$num=$num%100;
	if ($num>19) {
		$num=$num%10;
	}
	switch ($num) {
		case 1:
			return($words[0]);
		case 2: case 3: case 4:  
			return($words[1]);
		default: 
			return($words[2]);
	}
}

Пользуйтесь невозбранно!

----

Заядлым игроманам (и просто любителям скоротать время, тупя в квестик) будет любопытно взглянуть на скриншоты final fantasy - весьма симпатичной игрушки.

Камменты

Евгений12.07.2012, 13:29#
Чет такая-то бооольшая функция :)) Так гораздо проще:

function lexica($n,$a,$b,$c,$m=1)
{
$n=sprintf("%.0f",$n);
$x=abs($n>9?substr($n,-2):$n);
return ($m?($m==2?number_format($n,0,' ',' '):$n).' ':'').(($x%=100)>9&&$x<20||($x%=10)>4||$x==0?$c:($x==1?$a:$b));
}
Дуброн самый12.07.2012, 23:41#
Евгений, чего она такая большая - см. способ применения и текст поста.

Кроме того, так не "гораздо проще", а "хуй чего поймешь", откровенно говоря :)
Евгений19.07.2012, 15:39#
Прошу прощения, забыл указать как пользоваться функцией:

примеры:

lexica(123, "стул", "стула", "стульев"); // стула

lexica(123451, "стул", "стула", "стульев"); // 123451 стул

lexica(123456, "стул", "стула", "стульев", 2); // 123 456 стульев


dude25.10.2012, 16:21#
gettext? не, не слышал
Дуброн самый25.10.2012, 16:46#
gettext не для этого, и пост не про это. сторить языковые данные можно как угодно, а тут про то, чо с ними потом делать.

Написать коммент: памятка постеру

 

Крутые посты wtf??? →

02.10.2012 · 94 каммента · рейтинг 13.02
27.06.2012 · 37 камментов · рейтинг 8.07
15.02.2013 · 24 каммента · рейтинг 6.69
23.01.2013 · 21 каммент · рейтинг 6.24
29.08.2007 · 28 камментов · рейтинг 5.87

Последне камменты

16.05.2023  8DC0WIM www.yandex.ruРеклама паблика вконтакте, как меня модерировали: 8DC0WIM www.yandex.ru
16.05.2023  EWDOLQG7E28H www.yandex.ruНакрутка сердечек и групп вконтакте через V-Like.ru: EWDOLQG7E28H www.yandex.ru

Статсы