PHP 内核源码 Array 初入二: 强大的 array_mu

2019-01-12  本文已影响0人  过往云技

array_multisort 多维数组排序实现

/* {{{ proto bool array_multisort(array &$array1 [, mixed $array1_sort_order [, mixed $array1_sort_flags [, mixed ... ]]]
   Sort multiple arrays at once similar to how ORDER BY clause works in SQL */
PHP_FUNCTION(array_multisort)
{
    zval*           args;
    zval**          arrays;
    Bucket**        indirect;
    uint32_t            idx;
    Bucket*         p;
    HashTable*      hash;
    int             argc;
    int             array_size;
    int             num_arrays = 0;
    int             parse_state[MULTISORT_LAST];   /* 0 - flag not allowed 1 - flag allowed */
    int             sort_order = PHP_SORT_ASC;
    int             sort_type  = PHP_SORT_REGULAR;
    int             i, k, n;
    compare_func_t  *func;

    ZEND_PARSE_PARAMETERS_START(1, -1)
        Z_PARAM_VARIADIC('+', args, argc)
    ZEND_PARSE_PARAMETERS_END();

    /* Allocate space for storing pointers to input arrays and sort flags. */
    arrays = (zval **)ecalloc(argc, sizeof(zval *));
    for (i = 0; i < MULTISORT_LAST; i++) {
        parse_state[i] = 0;
    }
    func = ARRAYG(multisort_func) = (compare_func_t*)ecalloc(argc, sizeof(compare_func_t));

    /* Here we go through the input arguments and parse them. Each one can
     * be either an array or a sort flag which follows an array. If not
     * specified, the sort flags defaults to PHP_SORT_ASC and PHP_SORT_REGULAR
     * accordingly. There can't be two sort flags of the same type after an
     * array, and the very first argument has to be an array. */
    for (i = 0; i < argc; i++) {
        zval *arg = &args[i];

        ZVAL_DEREF(arg);
        if (Z_TYPE_P(arg) == IS_ARRAY) {
            SEPARATE_ARRAY(arg);
            /* We see the next array, so we update the sort flags of
             * the previous array and reset the sort flags. */
            if (i > 0) {
                ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func(sort_type, sort_order != PHP_SORT_ASC);
                sort_order = PHP_SORT_ASC;
                sort_type = PHP_SORT_REGULAR;
            }
            arrays[num_arrays++] = arg;

            /* Next one may be an array or a list of sort flags. */
            for (k = 0; k < MULTISORT_LAST; k++) {
                parse_state[k] = 1;
            }
        } else if (Z_TYPE_P(arg) == IS_LONG) {
            switch (Z_LVAL_P(arg) & ~PHP_SORT_FLAG_CASE) {
                case PHP_SORT_ASC:
                case PHP_SORT_DESC:
                    /* flag allowed here */
                    if (parse_state[MULTISORT_ORDER] == 1) {
                        /* Save the flag and make sure then next arg is not the current flag. */
                        sort_order = Z_LVAL_P(arg) == PHP_SORT_DESC ? PHP_SORT_DESC : PHP_SORT_ASC;
                        parse_state[MULTISORT_ORDER] = 0;
                    } else {
                        php_error_docref(NULL, E_WARNING, "Argument #%d is expected to be an array or sorting flag that has not already been specified", i + 1);
                        MULTISORT_ABORT;
                    }
                    break;

                case PHP_SORT_REGULAR:
                case PHP_SORT_NUMERIC:
                case PHP_SORT_STRING:
                case PHP_SORT_NATURAL:
#if HAVE_STRCOLL
                case PHP_SORT_LOCALE_STRING:
#endif
                    /* flag allowed here */
                    if (parse_state[MULTISORT_TYPE] == 1) {
                        /* Save the flag and make sure then next arg is not the current flag. */
                        sort_type = (int)Z_LVAL_P(arg);
                        parse_state[MULTISORT_TYPE] = 0;
                    } else {
                        php_error_docref(NULL, E_WARNING, "Argument #%d is expected to be an array or sorting flag that has not already been specified", i + 1);
                        MULTISORT_ABORT;
                    }
                    break;

                default:
                    php_error_docref(NULL, E_WARNING, "Argument #%d is an unknown sort flag", i + 1);
                    MULTISORT_ABORT;
                    break;

            }
        } else {
            php_error_docref(NULL, E_WARNING, "Argument #%d is expected to be an array or a sort flag", i + 1);
            MULTISORT_ABORT;
        }
    }
    /* Take care of the last array sort flags. */
    ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func(sort_type, sort_order != PHP_SORT_ASC);

    /* Make sure the arrays are of the same size. */
    array_size = zend_hash_num_elements(Z_ARRVAL_P(arrays[0]));
    for (i = 0; i < num_arrays; i++) {
        if (zend_hash_num_elements(Z_ARRVAL_P(arrays[i])) != (uint32_t)array_size) {
            php_error_docref(NULL, E_WARNING, "Array sizes are inconsistent");
            MULTISORT_ABORT;
        }
    }

    /* If all arrays are empty we don't need to do anything. */
    if (array_size < 1) {
        efree(func);
        efree(arrays);
        RETURN_TRUE;
    }

    /* Create the indirection array. This array is of size MxN, where
     * M is the number of entries in each input array and N is the number
     * of the input arrays + 1. The last column is NULL to indicate the end
     * of the row. */
    indirect = (Bucket **)safe_emalloc(array_size, sizeof(Bucket *), 0);
    for (i = 0; i < array_size; i++) {
        indirect[i] = (Bucket *)safe_emalloc((num_arrays + 1), sizeof(Bucket), 0);
    }
    for (i = 0; i < num_arrays; i++) {
        k = 0;
        for (idx = 0; idx < Z_ARRVAL_P(arrays[i])->nNumUsed; idx++) {
            p = Z_ARRVAL_P(arrays[i])->arData + idx;
            if (Z_TYPE(p->val) == IS_UNDEF) continue;
            indirect[k][i] = *p;
            k++;
        }
    }
    for (k = 0; k < array_size; k++) {
        ZVAL_UNDEF(&indirect[k][num_arrays].val);
    }

    /* Do the actual sort magic - bada-bim, bada-boom. */
    zend_sort(indirect, array_size, sizeof(Bucket *), php_multisort_compare, (swap_func_t)array_bucket_p_sawp);

    /* Restructure the arrays based on sorted indirect - this is mostly taken from zend_hash_sort() function. */
    for (i = 0; i < num_arrays; i++) {
        int repack;

        hash = Z_ARRVAL_P(arrays[i]);
        hash->nNumUsed = array_size;
        hash->nInternalPointer = 0;
        repack = !(HT_FLAGS(hash) & HASH_FLAG_PACKED);

        for (n = 0, k = 0; k < array_size; k++) {
            hash->arData[k] = indirect[k][i];
            if (hash->arData[k].key == NULL) {
                hash->arData[k].h = n++;
            } else {
                repack = 0;
            }
        }
        hash->nNextFreeElement = array_size;
        if (repack) {
            zend_hash_to_packed(hash);
        } else if (!(HT_FLAGS(hash) & HASH_FLAG_PACKED)) {
            zend_hash_rehash(hash);
        }
    }

    /* Clean up. */
    for (i = 0; i < array_size; i++) {
        efree(indirect[i]);
    }
    efree(indirect);
    efree(func);
    efree(arrays);
    RETURN_TRUE;
}
/* }}} */

PHP 利用 array_multisort

    /**
     * $array = [['test' => 'xxx'], ['test' => 'bbb']]
     * multisort($array, 'test')
     * @param array $array
     * @param $sortKey
     * @param int $sort
     * @return array|bool
     */
    function multisort(array $array, $sortKey,int $sort=SORT_ASC):?array
    {
        $tempArray = [];
        foreach($array as $value){
            if(is_array($value)){
                $tempArray[] = $value[$sortKey];
            }
        }
        if(empty($tempArray)){
            return null;
        }

        array_multisort($tempArray, $sort, $array);
        return $array;
    }
上一篇下一篇

猜你喜欢

热点阅读