|
46 | 46 | #include "php_string.h" |
47 | 47 | #include "php_rand.h" |
48 | 48 | #include "zend_smart_str.h" |
| 49 | +#include "zend_bitset.h" |
49 | 50 | #include "ext/spl/spl_array.h" |
50 | 51 |
|
51 | 52 | /* {{{ defines */ |
@@ -5033,56 +5034,79 @@ PHP_FUNCTION(array_rand) |
5033 | 5034 | { |
5034 | 5035 | zval *input; |
5035 | 5036 | zend_long randval, num_req = 1; |
5036 | | - int num_avail; |
5037 | 5037 | zend_string *string_key; |
5038 | 5038 | zend_ulong num_key; |
| 5039 | + int i; |
| 5040 | + int num_avail; |
| 5041 | + zend_bitset bitset; |
| 5042 | + int negative_bitset = 0; |
| 5043 | + uint32_t bitset_len; |
5039 | 5044 |
|
5040 | 5045 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|l", &input, &num_req) == FAILURE) { |
5041 | 5046 | return; |
5042 | 5047 | } |
5043 | 5048 |
|
5044 | 5049 | num_avail = zend_hash_num_elements(Z_ARRVAL_P(input)); |
5045 | 5050 |
|
5046 | | - if (ZEND_NUM_ARGS() > 1) { |
5047 | | - if (num_req <= 0 || num_req > num_avail) { |
5048 | | - php_error_docref(NULL, E_WARNING, "Second argument has to be between 1 and the number of elements in the array"); |
5049 | | - return; |
5050 | | - } |
| 5051 | + if (num_avail == 0) { |
| 5052 | + php_error_docref(NULL, E_WARNING, "Array is empty"); |
| 5053 | + return; |
5051 | 5054 | } |
5052 | 5055 |
|
5053 | | - /* Make the return value an array only if we need to pass back more than one result. */ |
5054 | | - if (num_req > 1) { |
5055 | | - array_init_size(return_value, (uint32_t)num_req); |
5056 | | - } |
| 5056 | + if (num_req == 1) { |
| 5057 | + randval = php_mt_rand_range(0, num_avail - 1); |
5057 | 5058 |
|
5058 | | - /* We can't use zend_hash_index_find() because the array may have string keys or gaps. */ |
5059 | | - ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(input), num_key, string_key) { |
5060 | | - if (!num_req) { |
5061 | | - break; |
5062 | | - } |
5063 | | - |
5064 | | - randval = php_rand(); |
5065 | | - |
5066 | | - if ((double) (randval / (PHP_RAND_MAX + 1.0)) < (double) num_req / (double) num_avail) { |
5067 | | - /* If we are returning a single result, just do it. */ |
5068 | | - if (Z_TYPE_P(return_value) != IS_ARRAY) { |
| 5059 | + ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(input), num_key, string_key) { |
| 5060 | + if (randval-- == 0) { |
5069 | 5061 | if (string_key) { |
5070 | 5062 | RETURN_STR_COPY(string_key); |
5071 | 5063 | } else { |
5072 | 5064 | RETURN_LONG(num_key); |
5073 | 5065 | } |
| 5066 | + } |
| 5067 | + } ZEND_HASH_FOREACH_END(); |
| 5068 | + } |
| 5069 | + |
| 5070 | + if (num_req <= 0 || num_req > num_avail) { |
| 5071 | + php_error_docref(NULL, E_WARNING, "Second argument has to be between 1 and the number of elements in the array"); |
| 5072 | + return; |
| 5073 | + } |
| 5074 | + |
| 5075 | + /* Make the return value an array only if we need to pass back more than one result. */ |
| 5076 | + array_init_size(return_value, (uint32_t)num_req); |
| 5077 | + if (num_req > (num_avail >> 1)) { |
| 5078 | + negative_bitset = 1; |
| 5079 | + num_req = num_avail - num_req; |
| 5080 | + } |
| 5081 | + |
| 5082 | + ALLOCA_FLAG(use_heap); |
| 5083 | + bitset_len = zend_bitset_len(num_avail); |
| 5084 | + bitset = ZEND_BITSET_ALLOCA(bitset_len, use_heap); |
| 5085 | + zend_bitset_clear(bitset, bitset_len); |
| 5086 | + |
| 5087 | + i = num_req; |
| 5088 | + while (i) { |
| 5089 | + randval = php_mt_rand_range(0, num_avail - 1); |
| 5090 | + if (!zend_bitset_in(bitset, randval)) { |
| 5091 | + zend_bitset_incl(bitset, randval); |
| 5092 | + i--; |
| 5093 | + } |
| 5094 | + } |
| 5095 | + |
| 5096 | + /* We can't use zend_hash_index_find() because the array may have string keys or gaps. */ |
| 5097 | + i = 0; |
| 5098 | + ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(input), num_key, string_key) { |
| 5099 | + if (zend_bitset_in(bitset, i) ^ negative_bitset) { |
| 5100 | + if (string_key) { |
| 5101 | + add_next_index_str(return_value, zend_string_copy(string_key)); |
5074 | 5102 | } else { |
5075 | | - /* Append the result to the return value. */ |
5076 | | - if (string_key) { |
5077 | | - add_next_index_str(return_value, zend_string_copy(string_key)); |
5078 | | - } else { |
5079 | | - add_next_index_long(return_value, num_key); |
5080 | | - } |
| 5103 | + add_next_index_long(return_value, num_key); |
5081 | 5104 | } |
5082 | | - num_req--; |
5083 | 5105 | } |
5084 | | - num_avail--; |
| 5106 | + i++; |
5085 | 5107 | } ZEND_HASH_FOREACH_END(); |
| 5108 | + |
| 5109 | + free_alloca(bitset, use_heap); |
5086 | 5110 | } |
5087 | 5111 | /* }}} */ |
5088 | 5112 |
|
|
0 commit comments