|
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