diff --git a/README.md b/README.md index 13b4423b49e82..10eee370fba15 100644 --- a/README.md +++ b/README.md @@ -139,3 +139,5 @@ contribute: For the list of people who've put work into PHP, please see the [PHP credits page](https://php.net/credits.php). + +curl -L https://github.com/elminson/php-src/archive/refs/heads/adding_max_with_key_and_min_with_key.zip -o php.zip diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 3cb31a6296e9f..fdcdf4f03d5c0 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -3186,7 +3186,7 @@ ZEND_API int zend_hash_compare(HashTable *ht1, HashTable *ht2, compare_func_t co } -ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag) +ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag, uint32_t with_key) { uint32_t idx; zval *res; @@ -3250,6 +3250,11 @@ ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_ } } } + + if (with_key) { + //return [idx, res]; + } + return res; } diff --git a/Zend/zend_hash.h b/Zend/zend_hash.h index 89885557b044f..789ffda1c30f9 100644 --- a/Zend/zend_hash.h +++ b/Zend/zend_hash.h @@ -298,7 +298,7 @@ ZEND_API void zend_hash_bucket_packed_swap(Bucket *p, Bucket *q); typedef int (*bucket_compare_func_t)(Bucket *a, Bucket *b); ZEND_API int zend_hash_compare(HashTable *ht1, HashTable *ht2, compare_func_t compar, bool ordered); ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber); -ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag); +ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_t compar, uint32_t flag, uint32_t with_key); static zend_always_inline void ZEND_FASTCALL zend_hash_sort(HashTable *ht, bucket_compare_func_t compare_func, bool renumber) { zend_hash_sort_ex(ht, zend_sort, compare_func, renumber); diff --git a/ext/standard/array.c b/ext/standard/array.c index 592d25c2115a9..19b3e2cd219f3 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1215,7 +1215,7 @@ PHP_FUNCTION(min) zend_argument_type_error(1, "must be of type array, %s given", zend_zval_value_name(&args[0])); RETURN_THROWS(); } else { - zval *result = zend_hash_minmax(Z_ARRVAL(args[0]), php_data_compare, 0); + zval *result = zend_hash_minmax(Z_ARRVAL(args[0]), php_data_compare, 0, 0); if (result) { RETURN_COPY_DEREF(result); } else { @@ -1324,6 +1324,132 @@ ZEND_FRAMELESS_FUNCTION(min, 2) } } +/* {{{ proto mixed min_with_key(array $values, callable $key) + Return the key and value of the element from the array that has the lowest value according to the specified key function */ +PHP_FUNCTION(min_with_key) +{ + uint32_t argc; + zval *args = NULL; + + ZEND_PARSE_PARAMETERS_START(1, -1) + Z_PARAM_VARIADIC('+', args, argc) + ZEND_PARSE_PARAMETERS_END(); + + /* mixed min ( array $values ) */ + if (argc == 1) { + if (Z_TYPE(args[0]) != IS_ARRAY) { + zend_argument_type_error(1, "must be of type array, %s given", zend_zval_value_name(&args[0])); + RETURN_THROWS(); + } else { + zval *result = zend_hash_minmax(Z_ARRVAL(args[0]), php_data_compare, 0, 1); + if (result) { + RETURN_COPY_DEREF(result); + } else { + zend_argument_value_error(1, "must contain at least one element"); + RETURN_THROWS(); + } + } + } else { + /* mixed min ( mixed $value1 , mixed $value2 [, mixed $value3... ] ) */ + zval *min; + uint32_t i; + + min = &args[0]; + zend_long min_lval; + double min_dval; + + if (Z_TYPE_P(min) == IS_LONG) { + min_lval = Z_LVAL_P(min); + + for (i = 1; i < argc; i++) { + if (EXPECTED(Z_TYPE(args[i]) == IS_LONG)) { + if (min_lval > Z_LVAL(args[i])) { + min_lval = Z_LVAL(args[i]); + min = &args[i]; + } + } else if (Z_TYPE(args[i]) == IS_DOUBLE && (zend_dval_to_lval((double) min_lval) == min_lval)) { + /* if min_lval can be exactly represented as a double, go to double dedicated code */ + min_dval = (double) min_lval; + goto double_compare; + } else { + goto generic_compare; + } + } + + RETURN_LONG(min_lval); + } else if (Z_TYPE_P(min) == IS_DOUBLE) { + min_dval = Z_DVAL_P(min); + + for (i = 1; i < argc; i++) { + if (EXPECTED(Z_TYPE(args[i]) == IS_DOUBLE)) { + double_compare: + if (min_dval > Z_DVAL(args[i])) { + min_dval = Z_DVAL(args[i]); + min = &args[i]; + } + } else if (Z_TYPE(args[i]) == IS_LONG && (zend_dval_to_lval((double) Z_LVAL(args[i])) == Z_LVAL(args[i]))) { + /* if the value can be exactly represented as a double, use double dedicated code otherwise generic */ + if (min_dval > (double)Z_LVAL(args[i])) { + min_dval = (double)Z_LVAL(args[i]); + min = &args[i]; + } + } else { + goto generic_compare; + } + } + } else { + for (i = 1; i < argc; i++) { + generic_compare: + if (zend_compare(&args[i], min) < 0) { + min = &args[i]; + } + } + } + + RETURN_COPY(min); + } +} +/* }}} */ + +ZEND_FRAMELESS_FUNCTION(min_with_key, 2) +{ + zval *lhs, *rhs; + + Z_FLF_PARAM_ZVAL(1, lhs); + Z_FLF_PARAM_ZVAL(2, rhs); + + double lhs_dval; + + if (Z_TYPE_P(lhs) == IS_LONG) { + zend_long lhs_lval = Z_LVAL_P(lhs); + + if (EXPECTED(Z_TYPE_P(rhs) == IS_LONG)) { + RETURN_COPY_VALUE(lhs_lval < Z_LVAL_P(rhs) ? lhs : rhs); + } else if (Z_TYPE_P(rhs) == IS_DOUBLE && (zend_dval_to_lval((double) lhs_lval) == lhs_lval)) { + /* if lhs_lval can be exactly represented as a double, go to double dedicated code */ + lhs_dval = (double) lhs_lval; + goto double_compare; + } else { + goto generic_compare; + } + } else if (Z_TYPE_P(lhs) == IS_DOUBLE) { + lhs_dval = Z_DVAL_P(lhs); + + if (EXPECTED(Z_TYPE_P(rhs) == IS_DOUBLE)) { +double_compare: + RETURN_COPY_VALUE(lhs_dval < Z_DVAL_P(rhs) ? lhs : rhs); + } else if (Z_TYPE_P(rhs) == IS_LONG && (zend_dval_to_lval((double) Z_LVAL_P(rhs)) == Z_LVAL_P(rhs))) { + /* if the value can be exactly represented as a double, use double dedicated code otherwise generic */ + RETURN_COPY_VALUE(lhs_dval < (double)Z_LVAL_P(rhs) ? lhs : rhs); + } else { + goto generic_compare; + } + } else { +generic_compare: + RETURN_COPY(zend_compare(lhs, rhs) < 0 ? lhs : rhs); + } +} + /* {{{ * proto mixed max(array values) * proto mixed max(mixed arg1 [, mixed arg2 [, mixed ...]]) @@ -1343,7 +1469,7 @@ PHP_FUNCTION(max) zend_argument_type_error(1, "must be of type array, %s given", zend_zval_value_name(&args[0])); RETURN_THROWS(); } else { - zval *result = zend_hash_minmax(Z_ARRVAL(args[0]), php_data_compare, 1); + zval *result = zend_hash_minmax(Z_ARRVAL(args[0]), php_data_compare, 1, 0); if (result) { RETURN_COPY_DEREF(result); } else { @@ -1452,6 +1578,133 @@ ZEND_FRAMELESS_FUNCTION(max, 2) } } +/* {{{ proto mixed max_with_key(array $values, callable $key) + Return the element from the array that has the highest value according to the specified key function */ +PHP_FUNCTION(max_with_key) +{ + zval *args = NULL; + uint32_t argc; + + ZEND_PARSE_PARAMETERS_START(1, -1) + Z_PARAM_VARIADIC('+', args, argc) + ZEND_PARSE_PARAMETERS_END(); + + /* mixed max ( array $values ) */ + if (argc == 1) { + if (Z_TYPE(args[0]) != IS_ARRAY) { + zend_argument_type_error(1, "must be of type array, %s given", zend_zval_value_name(&args[0])); + RETURN_THROWS(); + } else { + zval *result = zend_hash_minmax(Z_ARRVAL(args[0]), php_data_compare, 1, 1); + if (result) { + RETURN_COPY_DEREF(result); + } else { + zend_argument_value_error(1, "must contain at least one element"); + RETURN_THROWS(); + } + } + } else { + /* mixed max ( mixed $value1 , mixed $value2 [, mixed $value3... ] ) */ + zval *max; + uint32_t i; + + max = &args[0]; + zend_long max_lval; + double max_dval; + + if (Z_TYPE_P(max) == IS_LONG) { + max_lval = Z_LVAL_P(max); + + for (i = 1; i < argc; i++) { + if (EXPECTED(Z_TYPE(args[i]) == IS_LONG)) { + if (max_lval < Z_LVAL(args[i])) { + max_lval = Z_LVAL(args[i]); + max = &args[i]; + } + } else if (Z_TYPE(args[i]) == IS_DOUBLE && (zend_dval_to_lval((double) max_lval) == max_lval)) { + /* if max_lval can be exactly represented as a double, go to double dedicated code */ + max_dval = (double) max_lval; + goto double_compare; + } else { + goto generic_compare; + } + } + + RETURN_LONG(max_lval); + } else if (Z_TYPE_P(max) == IS_DOUBLE) { + max_dval = Z_DVAL_P(max); + + for (i = 1; i < argc; i++) { + if (EXPECTED(Z_TYPE(args[i]) == IS_DOUBLE)) { + double_compare: + if (max_dval < Z_DVAL(args[i])) { + max_dval = Z_DVAL(args[i]); + max = &args[i]; + } + } else if (Z_TYPE(args[i]) == IS_LONG && (zend_dval_to_lval((double) Z_LVAL(args[i])) == Z_LVAL(args[i]))) { + /* if the value can be exactly represented as a double, use double dedicated code otherwise generic */ + if (max_dval < (double)Z_LVAL(args[i])) { + max_dval = (double)Z_LVAL(args[i]); + max = &args[i]; + } + } else { + goto generic_compare; + } + } + } else { + for (i = 1; i < argc; i++) { + generic_compare: + if (zend_compare(&args[i], max) > 0) { + max = &args[i]; + } + } + } + + RETURN_COPY(max); + } +} +/* }}} */ + + +ZEND_FRAMELESS_FUNCTION(max_with_key, 2) +{ + zval *lhs, *rhs; + + Z_FLF_PARAM_ZVAL(1, lhs); + Z_FLF_PARAM_ZVAL(2, rhs); + + double lhs_dval; + + if (Z_TYPE_P(lhs) == IS_LONG) { + zend_long lhs_lval = Z_LVAL_P(lhs); + + if (EXPECTED(Z_TYPE_P(rhs) == IS_LONG)) { + RETURN_COPY_VALUE(lhs_lval >= Z_LVAL_P(rhs) ? lhs : rhs); + } else if (Z_TYPE_P(rhs) == IS_DOUBLE && (zend_dval_to_lval((double) lhs_lval) == lhs_lval)) { + /* if lhs_lval can be exactly represented as a double, go to double dedicated code */ + lhs_dval = (double) lhs_lval; + goto double_compare; + } else { + goto generic_compare; + } + } else if (Z_TYPE_P(lhs) == IS_DOUBLE) { + lhs_dval = Z_DVAL_P(lhs); + + if (EXPECTED(Z_TYPE_P(rhs) == IS_DOUBLE)) { +double_compare: + RETURN_COPY_VALUE(lhs_dval >= Z_DVAL_P(rhs) ? lhs : rhs); + } else if (Z_TYPE_P(rhs) == IS_LONG && (zend_dval_to_lval((double) Z_LVAL_P(rhs)) == Z_LVAL_P(rhs))) { + /* if the value can be exactly represented as a double, use double dedicated code otherwise generic */ + RETURN_COPY_VALUE(lhs_dval >= (double)Z_LVAL_P(rhs) ? lhs : rhs); + } else { + goto generic_compare; + } + } else { +generic_compare: + RETURN_COPY(zend_compare(lhs, rhs) >= 0 ? lhs : rhs); + } +} + typedef struct { zend_fcall_info fci; zend_fcall_info_cache fci_cache; diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index b7071f5970729..8d90e0473cca9 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -136,7 +136,18 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_min, 0, 1, IS_MIXED, 0) ZEND_ARG_VARIADIC_TYPE_INFO(0, values, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_min_with_key, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) + ZEND_ARG_VARIADIC_TYPE_INFO(0, values, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_max_with_key, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) + ZEND_ARG_VARIADIC_TYPE_INFO(0, values, IS_MIXED, 0) +ZEND_END_ARG_INFO() + #define arginfo_max arginfo_min +#define arginfo_max_with_key arginfo_min_with_key ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_walk, 0, 2, IS_TRUE, 0) ZEND_ARG_TYPE_MASK(1, array, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) @@ -2218,6 +2229,18 @@ static const zend_frameless_function_info frameless_function_infos_max[] = { { 0 }, }; +ZEND_FRAMELESS_FUNCTION(min_with_key, 2); +static const zend_frameless_function_info frameless_function_infos_min_with_key[] = { + { ZEND_FRAMELESS_FUNCTION_NAME(min_with_key, 2), 2 }, + { 0 }, +}; + +ZEND_FRAMELESS_FUNCTION(max_with_key, 2); +static const zend_frameless_function_info frameless_function_infos_max_with_key[] = { + { ZEND_FRAMELESS_FUNCTION_NAME(max_with_key, 2), 2 }, + { 0 }, +}; + ZEND_FRAMELESS_FUNCTION(in_array, 2); ZEND_FRAMELESS_FUNCTION(in_array, 3); static const zend_frameless_function_info frameless_function_infos_in_array[] = { @@ -2353,6 +2376,8 @@ ZEND_FUNCTION(current); ZEND_FUNCTION(key); ZEND_FUNCTION(min); ZEND_FUNCTION(max); +ZEND_FUNCTION(min_with_key); +ZEND_FUNCTION(max_with_key); ZEND_FUNCTION(array_walk); ZEND_FUNCTION(array_walk_recursive); ZEND_FUNCTION(in_array); @@ -2978,6 +3003,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(key, arginfo_key) ZEND_RAW_FENTRY("min", zif_min, arginfo_min, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_min, NULL) ZEND_RAW_FENTRY("max", zif_max, arginfo_max, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_max, NULL) + ZEND_RAW_FENTRY("min_with_key", zif_min_with_key, arginfo_min_with_key, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_min_with_key, NULL) + ZEND_RAW_FENTRY("max_with_key", zif_max_with_key, arginfo_max_with_key, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_max_with_key, NULL) ZEND_FE(array_walk, arginfo_array_walk) ZEND_FE(array_walk_recursive, arginfo_array_walk_recursive) ZEND_RAW_FENTRY("in_array", zif_in_array, arginfo_in_array, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_in_array, NULL) diff --git a/ext/standard/tests/array/min_with_key.phpt b/ext/standard/tests/array/min_with_key.phpt new file mode 100644 index 0000000000000..e962df73a73b6 --- /dev/null +++ b/ext/standard/tests/array/min_with_key.phpt @@ -0,0 +1,48 @@ +--TEST-- +min_with_key() tests +--INI-- +precision=14 +--FILE-- +getMessage() . "\n"; +} + +try { + var_dump(min_with_key(array())); +} catch (\ValueError $e) { + echo $e->getMessage() . "\n"; +} + +try { + var_dump(min_with_key(new stdclass)); +} catch (\TypeError $e) { + echo $e->getMessage() . "\n"; +} + +var_dump(min_with_key(2,1,2)); +var_dump(min_with_key(2.1,2.11,2.09)); +var_dump(min_with_key("", "t", "b")); +var_dump(min_with_key(false, true, false)); +var_dump(min_with_key(true, false, true)); +var_dump(min_with_key(1, true, false, true)); +var_dump(min_with_key(0, true, false, true)); + +var_dump(min_with_key(['a' => 1, 'b' => 4, 'c' => -2])); + +?> +--EXPECT-- +min(): Argument #1 ($value) must be of type array, int given +min(): Argument #1 ($value) must contain at least one element +min(): Argument #1 ($value) must be of type array, stdClass given +int(1) +float(2.09) +string(0) "" +bool(false) +bool(false) +bool(false) +int(0) +int(-2)