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

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

А ещё можно учесть translate.sourceforge.net/wiki/l10n/pluralforms и сделать более модную ф-ию.
Только внимание, там код для C, а в C несколько иная интерпретация тренарного оператора (?:) — придётся к тем формулам скобочки добавить, чтобы правильно работало.
Сравните размер функций и реализацию.
Мои модификаторы по ссылке выше расчитаны на работу с многоязычным контентом. И для данного модификатора подразумевается использование не только русского языка. Работает, как минимум, с английским.

Так, в оправдание размера :)
$int = round(1,999);
$text = declension($int, array('попугай','попугая','попугаев');
 
 
function declension($digit, $expr, $onlyword = false) {
  if(empty($expr[2])) $expr[2]=$expr[1];
 
    $i = preg_replace('/[^0-9]+/s','',$digit)%100; //intval не всегда корректно работает
    if ( $onlyword ) $digit='';
    if ( $i >= 5 AND $i <= 20 ) $res=$digit.' '.$expr[2];
    else {
    $i%=10;
        if ($i==1) $res=$digit.' '.$expr[0];
        elseif ( $i >= 2 && $i <= 4 ) $res = $digit .' '. $expr[1];
        else $res = $digit.' '.$expr[2];
    }
    return trim($res);
}

(с) где-то с интенета
Это dklab'овский код. Далеко не самый оптимальный, надо вам сказать.
не оптимальный по скорости?
Да. И вообще, как по мне он не очень удобен и для программиста.
Вас беспокоит скорость этого куска кода? Вы привыкли экономить на спичках?
Да :)

ИМХО, использовать регулярки там, где они не нужны как-то моветон.

И вызовов функции склонения может быть несколько сотен на одной странице.
а что будет удобней для «программиста»? $bananes = format_num(100);? это самый оптимальный вариант, но боюсь функция не угадает в чем мерять переданные единицы — в бананах или попугаях…
Например, необходимость оборачивать тексты в array, что не всегда нужно и удобно, ну и дублирование кода по тексту самой функции.
на вкус и цвет…

массив можно за 20 секунд переписать на аргументы, а регулярка для подстраховки — и написано почему :)
Можно сделать еще проще и мучать смарти

function okonchanie($c, $m, $o, $t)
{
$c = abs($c);
if ($c % 100 == '1' or ($c % 100 > '20') and ($c % 10 == '1')) return $o;
else if ($c % 100 == '2' or ($c % 100 > '20') and ($c % 10 == '2')) return $t;
else if ($c % 100 == '3' or ($c % 100 > '20') and ($c % 10 == '3')) return $t;
else if ($c % 100 == '4' or ($c % 100 > '20') and ($c % 10 == '4')) return $t;
else return $m;
}

пример
okonchanie($count, 'комментариев', 'комментарий', 'комментария')
Отделяем представление от логики, отделяем. Если же очень необходимо:
Copy Source | Copy HTML
  1. /**
  2. * @desc Функция получения русскоязычного варианта окончания для переданного числа
  3. * @param float $value Число, для которого надо подобрать окончание
  4. * @param array $names Массив имён вида [0]имя одного; [1]имя от 2 до 4; [2]имя 0 и от 5 до 20
  5. * @return string Возвращает соответствующее числу слово
  6. */
  7. function getNumberWord($value,$names){
  8.     $temp = strval($value);
  9.     $temp = $temp[utf8_strlen($temp)-1];
  10.     return (($temp>1 and $temp <5 and (intval($value)>19 or intval($value)<10))?$names[1]:($temp==1?$names[0]:$names[2]));
  11. }
И еще незабываем что код пишем для людей.
Nice guide — people should be able to recognise ternary operators even if they choose not to use them.

А кроме всего прочего по скромному ИМХО вашего покорного слуги, код с тернарными сравнениями выглядит намного чище и понятнее, чем простыни условий. Но это, как я уже сказал, ИМХО.
Я лично предпочитаю рубить строки с длинными условиями с тернарным оператором.
Опять же имхо, запись этой строки как
Copy Source | Copy HTML
  1. if ($temp>1 and $temp <5 and (intval($value)>19 or intval($value)<10)){
  2. return $names[1];
  3. }elseif ($temp==1){
  4. return $names[0];
  5. } else {
  6. return $names[2];
  7. }
для меня выглядит так:
  1. — операции определения значения переменной ИМХО должны быть записаны с использованием тернарных условий (потому как в строку писать if — это некомпетентность). Если применяются операторы или производится несколько действий — тогда да, тернарки неприменимы
  2. — некрасиво ибо простыня
  3. — если «экономить на спичках», то такая запись менее производительна (~0.5 секунды при выполнении 5000000 операций)
Я, видимо, был неправильно понят. Под фразой «рубить строку», я подразумевал использовать переносы, размещая тернарный оператор (и знак вопроса и двоеточие с новой строки). Как-то так:
return (($temp>1 and $temp <5 
  and (intval($value)>19 or intval($value)<10))
    ? $names[1]
    : ($temp==1
        ? $names[0]
        : $names[2]));
Это просто для примера, обычно условия внутри тернарного оператора бывают длиннее, смотрится лучше.
Да, я понял вас не правильно.
Что касается записи — согласен, так читабельнее.
Вот теперь я смог прочитать последнюю строчку в его примере :)
я смотрю немногие тут сталкивались с числами больше ста
11 комментарий
Мда, боян, и все тут же принялись постить свои функции… Странно, пятница, наверное.
Даже не просто пятница, а пятница, 13-е
пару дней назад была тема о подобных же рассуждениях. там же приводились правильные ссылки по расчёту множественных форм.
повторю ссылку ещё раз: translate.sourceforge.net/wiki/l10n/pluralforms
ps: в mzz используем примерно такую нотацию:

файл интернационализации:
[count]
0 = "? комментарий"
1 = "? комментария"
2 = "? комментариев"

шаблон:
{_ count $num}
Кстати, да. И хранить код выбора и набор форм вместе.

Привет!
Да, кстати, забыл прокомментировать utf8_strlen — это функция из пакета UTF8 для замены multibyte взята отсюда
а как же mbsring?
Он использует в том числе mb_string (если есть), но в то же время при отсутствии оного система не заявляет, что она fatal error.
Не могу не заметить, что у вас ошибка в логике функции.
Например, для 112 результат будет неверным.
Спасибо, ценное замечание. Исправлю функцию.
делал такое для XSLT (вдруг кому нужно):
Copy Source | Copy HTML
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  3.     <xsl:output method="xml" indent="no" encoding="utf-8" />
  4.  
  5.     <xsl:template name="words">
  6.         <xsl:param name="count">0</xsl:param>
  7.         <xsl:param name="wForm1" /> <!-- 1 предмет -->
  8.         <xsl:param name="wForm2" /> <!-- 2 предмета-->
  9.         <xsl:param name="wForm3" /> <!-- 10 предметов -->
  10.  
  11.         <xsl:variable name="final" select="number(substring($count, string-length($count)-1))" />
  12.         <xsl:choose>
  13.             <xsl:when test="$final mod 10 = 0">
  14.                 <xsl:value-of select="$wForm3" />
  15.             </xsl:when>
  16.             <xsl:when test="20 > $final and $final > 10">
  17.                 <xsl:value-of select="$wForm3" />
  18.             </xsl:when>
  19.             <xsl:when test="$final = 1 or $final mod 10 = 1">
  20.                 <xsl:value-of select="$wForm1" />
  21.             </xsl:when>
  22.             <xsl:when test="$final mod 10 > 4">
  23.                 <xsl:value-of select="$wForm3" />
  24.             </xsl:when>
  25.             <xsl:otherwise>
  26.                 <xsl:value-of select="$wForm2" />
  27.             </xsl:otherwise>
  28.         </xsl:choose>
  29.     </xsl:template>
  30.  
  31. </xsl:stylesheet>


Для symfony было бы приятно иметь такое
Мое решение: модификатор.

Пример: {$total_pages|incline_word:«всего %d страница»:«всего %d страницы»:«всего %d страниц»:«нет ни одной страницы»}

Решение как в gettext конечно правильнее в плане интернационализации, но если мы целимся только на русскоязычную аудиторию, то можно и так, тем более что так удобно все прописывать в одном шаблоне)
Мне удобнее всего использовать следующую функцию:

function plural_word($first_word, $second_word, $third_word, $number)
{
    if ($number > 100) $number = $number % 100;
    if ($number > 20) $number = $number % 10;
    switch ($number)
    {
        case 1:
            return $first_word; // «год»
        case 2:
        case 3:
        case 4:
            return $second_word; // «года»
        default:
            return $third_word; // «лет»
    }
}
Русский без роботизма — это когда у тебя 458 комментариев, а ты пишешь «несколько сотен комментариев», когда ты автоматически округляешь размерфайла в списке файлов в несколько тысяч байт до килобайтов, а в несколько тысяч килобайтов до десятых мегабайта, когда ты пишешь «последний коммент был добавлен несколько минут назад», а не «12 января, 12:35» и так далее.

А писать про банальное правило 1-2-5 — это, простите, кармадроч.
Лишать пользователя потенциально полезной информации (иногда интересна точность) — тоже не стоит.
Согласен, необходимо предоставлять альтернативный вариант доступа к точной информации (если кому-то она будет интересна). Однако необходимо разумно подходить к выбору, что будет интересно подавляющему большинству в первую очередь — «точные данные» или «ориентирование в ситуации».
Как, например: «Страница недавно обновлена (в 18:47)»?..
Переменная temp с 7-ю употреблениям, это круто, конечно.
предложу свой вариант (конечно не идеал, но коротко и ясно — берется последняя цифра и уже исходя из нее подставляется слово в нужном падеже):

function get_number($n,$w1,$w2,$w3){
$n = (string)$n;
$s = (int)$n[strlen($n)-1];

if ($s==1 && $s!=0){
return $w1;
} elseif ($s
блин как увижу какой-нибудь такой гид — аж волосы дыбом встают — а ведь когда-то лет эдак 7-8 назад я был такой же юнец… )))

сейчас же — перевод на 30-40 языков, прекрасное понимае что числительных форм может быть 2, 3 (рус.) и даже 4 штуки. А ещё числительную форму для каждого языка можно выразить формулой.

Пример для русского языка:

n%10==1 && n%100!=11? 0: n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20)? 1: 2

А ещё — существует gettext, который кушает эту формулу и сам всё сотворит. Ну на худой конец можно лингвиста от кьют, если на кьюте писать что-нибудь.

К слову, под пхп gettext встроенный. И ОЧЕНЬ (!!!!!!!!!!!!!!!!!) простой. Делайте сразу правильно, не повторяйте чужих ошибок)
Всё бы хорошо, но даже у Мастерхоста, например, gettext в PHP отсутствует.
мои 5 копеек:
/**
* Возвращает склонение числа
*
* @param integer $number
* @param array $titles - массив значений. Например, array("гривна", "гривны", "гривен")
* @return string
*/
function declOfNum( $number, $titles ) {
    $cases = array( 2, 0, 1, 1, 1, 2 );
    return $titles[ ( $number % 100 > 4 && $number % 100 < 20 ) ? 2 : $cases[ min( $number % 10, 5 ) ] ];
}


и не стоит в этом случае говорить, что «код пишем для человека», ибо эта функция реализует 100% требований, а так же является минимальной неделимой частью — внутри раскапывать нечего
>{number2word number=$your_number name1=Строка name2=Строки name3=Строк}

Очень громоздко :)

Плагин, hg.balancer.ru/hgwebdir/bors-core/file/1a7622f87584/engines/smarty/plugins/modifier.sklon.php:
<?php
     2 function smarty_modifier_sklon($n, $s1, $s2=NULL, $s5=NULL) // 1 нож 2 ножа 5 ножей
     3 {
     4 	if($s2 === NULL)
     5 		list($s1, $s2, $s5) = explode(',', $s1);
     6 			
     7     $ns=intval(substr($n,-1));
     8     $n2=intval(substr($n,-2));
     9 
    10     if($n2>=10 && $n2<=19) return $s5;
    11     if($ns==1) return $s1;
    12     if($ns>=2&&$ns<=4) return $s2;
    13     return $s5;
    14 }


Вызывается так:

Всего в БД {$n} {$n|sklon:'сообщение':'сообщения':'сообщений'}.

Или:

Всего в БД {$n} сообщени{$n|sklon:'е':'я':'й'}.

Или:

Всего в БД {$n} сообщени{$n|sklon:'е, я, й'}.

Плюс просто функция sklon: hg.balancer.ru/hgwebdir/bors-core/file/15136adfd8fb/inc/strings.php

(да, лучше бы обозвать declension, но sklon — запомнить проще ;) )
Числительными все впорядке, задача стара как мир, и решений море.
А вот как обстоит дело с падежами?
Есть ли реальный способ склонять слово по падежам? Ну или хотя бы по роду.
Например, создана страница, создан материал.
Может быть там можно с окончаниями помудрить?
Падежи — сложно, но можно. В русском языке много исключений и правила очень витиеватые. Однако где падежи, там и множественное или единственное число, там и род и пр.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории