На практике, при программировании на PHP, довольно часто приходиться использовать функции min() и max() для поиска минимального или максимального значения элемента в массиве. Довольно редко, исходя из своей практики, среди аргументов функций. Перед тем как начать исследовать различия, думаю, что лучше для наглядности привести код вызова функций с различным набором аргументов.

php > echo min(1,2,3,4,5,6,7,-1);
-1

php > echo min([1,5,3]);
1



Если мы попытаемся смешивать аргументы (массив с числами), то в этом случае функция пытается преобразовать массив в строку.

php > echo min([-1,2],[1,5,3]);
PHP Notice:  Array to string conversion in php shell code on line 1
PHP Stack trace:
PHP   1. {main}() php shell code:0

Notice: Array to string conversion in php shell code on line 1

Call Stack:
  659.0962     352688   1. {main}() php shell code:0

Array


Для случая, если передаются строки, то происходит поиск минимального или максимального символа согласно следованию порядка в алфавите.

php > echo min("1","2");
1
php > echo min("a","b");
a
php > echo min("a","b", "c");
a
php > echo max("a","b", "c");
c
php > echo max("ab","ba", "cd");
cd


В общем, все понятно и ясно, все тоже самое, что и в документации. Работая с этими функциями как бы создается впечатление, что это совершенно разные функции, но на самом деле в исходниках PHP это одна и та же функция, в которой передается дополнительный флаг, подсказывающий, что в данный момент вернуть — максимальное или минимальное значение.

Приведу для наглядности исходники кода, удалив из тела функций различного рода проверки.

Файл: ext/standard/array.c


Функция поиска минимального значения.

PHP_FUNCTION(min)
{
        int argc;
        zval *args = NULL;

        if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
                return;
        }

       //….
       
     if ((result = zend_hash_minmax(Z_ARRVAL(args[0]), php_array_data_compare, 0)) != NULL) {
       //….
     }
     //….
}



Функция поиска максимального значения.

PHP_FUNCTION(min)
{
        int argc;
        zval *args = NULL;

        if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
                return;
        }

       //….
       
     if ((result = zend_hash_minmax(Z_ARRVAL(args[0]), php_array_data_compare, 1)) != NULL) {
       //….
     }
     //….
}


То есть, если присмотреться, то отличается всего лишь последний аргумент в функции zend_hash_minmax():
1 — возвращает максимальное значение, 0 — возвращает минимальное значение.

Перейдем в тело функции zend_hash_minmax(), также удалив код который в данный момент не требует внимания.

Файл: Zend/zend_hash.c


ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag)
{
  //….
        for (; idx < ht->nNumUsed; idx++) {
                p = ht->arData + idx;
                if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) continue;

                if (flag) {
                        if (compar(res, p) < 0) { /* max */
                                res = p;
                        }
                } else {
                        if (compar(res, p) > 0) { /* min */
                                res = p;
                        }
                }
        }
        return &res->val;
}


Таким образом, разные названия функции используют одну и туже функцию (своего рода скрытый полиморфизм). Я поначалу думал, что наверняка эти функции используют разные алгоритмы поиска минимального или максимального значения в массиве. На самом деле, как мы видим, используется обычный последовательный переход. Если использовать ранее мной описанный макрос цикла, то получаем подобный код.

PHP_FUNCTION(ms_maximal)
{
   int argc = ZEND_NUM_ARGS();
   double maximal = ZEND_LONG_MIN, cur_value = 0;
   zval *array,
        *value;

   if (zend_parse_parameters(argc, "a", &array) == FAILURE) {
        RETURN_DOUBLE(maximal);
   }

   ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), value) {
        cur_value = zval_get_double (value);
        if(cur_value > maximal) {
           maximal = cur_value;
        }
   } ZEND_HASH_FOREACH_END();

   RETURN_DOUBLE(maximal);
}


Возможно, функция zend_hash_minmax() довольно долго отрабатывает при передаче “гигантского” списка элементов в массиве. Интересно, в этом случае наверное, было бы оптимальней предварительно отсортировать массив наиболее подходящим алгоритмом сортировки (в зависимости от объема элементов), а затем брать крайние значения.
Поделиться с друзьями
-->

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


  1. napa3um
    21.06.2016 14:08

    Это же шутка, иронизирование над «хабром нетортом»?