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;
}
网友评论