Add `binarySearch()` and `binarySearchBy()`. Refactor code.
This commit is contained in:
parent
7c9fda5c64
commit
1b2d443697
|
@ -97,21 +97,21 @@ ZEND_BEGIN_ARG_INFO(copy_of_arginfo, 0)
|
|||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO(copy_of_range_arginfo, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, from_idx, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, num_elements, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, from_index, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, to_index, IS_LONG, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO(fill_arginfo, 0)
|
||||
ZEND_ARG_INFO(0, element)
|
||||
ZEND_ARG_TYPE_INFO(0, from_index, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, num_elements, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, to_index, IS_LONG, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO(binary_search_arginfo, 0)
|
||||
ZEND_ARG_INFO(0, element)
|
||||
ZEND_ARG_TYPE_INFO(0, flags, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, from_index, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, num_elements, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, to_index, IS_LONG, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO(binary_search_by_arginfo, 0)
|
||||
|
@ -119,7 +119,7 @@ ZEND_BEGIN_ARG_INFO(binary_search_by_arginfo, 0)
|
|||
ZEND_ARG_CALLABLE_INFO(0, selector, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, flags, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, from_index, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, num_elements, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, to_index, IS_LONG, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO(chuncked_arginfo, 0)
|
||||
|
|
|
@ -75,6 +75,8 @@
|
|||
PHP_COLLECTIONS_ERROR(E_WARNING, "Elements should be int or double")
|
||||
#define ERR_BAD_GROUP() \
|
||||
PHP_COLLECTIONS_ERROR(E_WARNING, "Group value must be array")
|
||||
#define ERR_NOT_PACKED() \
|
||||
PHP_COLLECTIONS_ERROR(E_WARNING, "The array should be packed")
|
||||
#define ERR_SILENCED()
|
||||
|
||||
#define ELEMENTS_VALIDATE(elements, err, err_then) \
|
||||
|
@ -147,6 +149,15 @@ static zend_always_inline zend_object* create_pair_obj()
|
|||
return create_object(collections_pair_ce, &std_object_handlers);
|
||||
}
|
||||
|
||||
static zend_always_inline void array_release(zend_array* ht)
|
||||
{
|
||||
if (GC_REFCOUNT(ht) > 1) {
|
||||
GC_DELREF(ht);
|
||||
} else {
|
||||
zend_array_destroy(ht);
|
||||
}
|
||||
}
|
||||
|
||||
static zend_always_inline zend_array* array_group_fetch(zend_array* ht, zval* key)
|
||||
{
|
||||
zend_array* group = NULL;
|
||||
|
@ -351,6 +362,25 @@ static zend_always_inline compare_func_t compare_func_init(
|
|||
return reverse ? bucket_reverse_compare_regular : bucket_compare_regular;
|
||||
}
|
||||
|
||||
static zend_always_inline zend_long binary_search(Bucket* ref, zval* val,
|
||||
zend_long from_idx, zend_long to_idx, compare_func_t cmp)
|
||||
{
|
||||
zend_long low = from_idx;
|
||||
zend_long high = to_idx - 1;
|
||||
while (low <= high) {
|
||||
zend_long mid = (low + high) >> 1;
|
||||
int result = cmp(&ref[mid].val, val);
|
||||
if (result == 1) {
|
||||
high = mid - 1;
|
||||
} else if (result == -1) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
return -(low + 1);
|
||||
}
|
||||
|
||||
static zend_always_inline void array_sort_by(zend_array* ht)
|
||||
{
|
||||
uint32_t i;
|
||||
|
@ -879,12 +909,95 @@ PHP_METHOD(Collection, average)
|
|||
|
||||
PHP_METHOD(Collection, binarySearch)
|
||||
{
|
||||
|
||||
zval* element;
|
||||
zend_long flags = 0;
|
||||
zend_long from_idx = 0;
|
||||
zend_array* current = THIS_COLLECTION;
|
||||
uint32_t num_elements = zend_hash_num_elements(current);
|
||||
zend_long to_idx = num_elements;
|
||||
ZEND_PARSE_PARAMETERS_START(1, 4)
|
||||
Z_PARAM_ZVAL(element)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_LONG(flags)
|
||||
Z_PARAM_LONG(from_idx)
|
||||
Z_PARAM_LONG(to_idx)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
if (UNEXPECTED(!HT_IS_PACKED(current))) {
|
||||
ERR_NOT_PACKED();
|
||||
RETURN_NULL();
|
||||
}
|
||||
if (UNEXPECTED(from_idx < 0)) {
|
||||
ERR_BAD_INDEX();
|
||||
RETURN_NULL();
|
||||
}
|
||||
if (to_idx > num_elements) {
|
||||
to_idx = num_elements;
|
||||
}
|
||||
if (UNEXPECTED(to_idx < from_idx)) {
|
||||
ERR_BAD_SIZE();
|
||||
RETURN_NULL();
|
||||
}
|
||||
compare_func_t cmp;
|
||||
ZEND_HASH_FOREACH_VAL(current, zval* val)
|
||||
cmp = compare_func_init(val, 0, flags);
|
||||
break;
|
||||
ZEND_HASH_FOREACH_END();
|
||||
RETVAL_LONG(binary_search(current->arData, element, from_idx, to_idx, cmp));
|
||||
}
|
||||
|
||||
PHP_METHOD(Collection, binarySearchBy)
|
||||
{
|
||||
|
||||
zval* element;
|
||||
zend_fcall_info fci;
|
||||
zend_fcall_info_cache fcc;
|
||||
zend_long flags = 0;
|
||||
zend_long from_idx = 0;
|
||||
zend_array* current = THIS_COLLECTION;
|
||||
uint32_t num_elements = zend_hash_num_elements(current);
|
||||
zend_long to_idx = num_elements;
|
||||
ZEND_PARSE_PARAMETERS_START(2, 5)
|
||||
Z_PARAM_ZVAL(element)
|
||||
Z_PARAM_FUNC(fci, fcc)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_LONG(flags)
|
||||
Z_PARAM_LONG(from_idx)
|
||||
Z_PARAM_LONG(to_idx)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
if (UNEXPECTED(!HT_IS_PACKED(current))) {
|
||||
ERR_NOT_PACKED();
|
||||
RETURN_NULL();
|
||||
}
|
||||
if (UNEXPECTED(from_idx < 0)) {
|
||||
ERR_BAD_INDEX();
|
||||
RETURN_NULL();
|
||||
}
|
||||
if (to_idx > num_elements) {
|
||||
to_idx = num_elements;
|
||||
}
|
||||
if (UNEXPECTED(to_idx < from_idx)) {
|
||||
ERR_BAD_SIZE();
|
||||
RETURN_NULL();
|
||||
}
|
||||
INIT_FCI(&fci, 2);
|
||||
uint32_t range = to_idx - from_idx;
|
||||
Bucket* ref = (Bucket*)malloc(range * sizeof(Bucket));
|
||||
compare_func_t cmp = NULL;
|
||||
uint32_t idx = 0;
|
||||
Bucket* bucket = current->arData + from_idx;
|
||||
Bucket* end = current->arData + to_idx;
|
||||
for (; bucket < end; ++bucket) {
|
||||
CALLBACK_KEYVAL_INVOKE(params, bucket);
|
||||
memcpy(&ref[idx++].val, &bucket->val, sizeof(zval));
|
||||
if (UNEXPECTED(cmp == NULL)) {
|
||||
cmp = compare_func_init(&retval, 0, flags);
|
||||
}
|
||||
}
|
||||
zend_long result = binary_search(ref, element, 0, range, cmp);
|
||||
RETVAL_LONG(result < 0 ? result - from_idx : result + from_idx);
|
||||
for (idx = 0; idx < range; ++idx) {
|
||||
zval_ptr_dtor(&ref[idx].val);
|
||||
}
|
||||
free(ref);
|
||||
}
|
||||
|
||||
PHP_METHOD(Collection, chunked)
|
||||
|
@ -928,6 +1041,7 @@ PHP_METHOD(Collection, chunked)
|
|||
if (transform) {
|
||||
ZVAL_LONG(¶ms[1], num_chunks++);
|
||||
zend_call_function(&fci, &fcc);
|
||||
array_release(chunk);
|
||||
zend_hash_next_index_insert(chunked, &retval);
|
||||
} else {
|
||||
zend_hash_next_index_insert(chunked, ¶ms[0]);
|
||||
|
@ -939,6 +1053,7 @@ PHP_METHOD(Collection, chunked)
|
|||
if (transform) {
|
||||
ZVAL_LONG(¶ms[1], num_chunks++);
|
||||
zend_call_function(&fci, &fcc);
|
||||
array_release(chunk);
|
||||
zend_hash_next_index_insert(chunked, &retval);
|
||||
} else {
|
||||
zend_hash_next_index_insert(chunked, ¶ms[0]);
|
||||
|
@ -1115,20 +1230,21 @@ PHP_METHOD(Collection, copyOf)
|
|||
|
||||
PHP_METHOD(Collection, copyOfRange)
|
||||
{
|
||||
zend_long from_idx, num_elements;
|
||||
zend_long from_idx, to_idx;
|
||||
ZEND_PARSE_PARAMETERS_START(2, 2)
|
||||
Z_PARAM_LONG(from_idx)
|
||||
Z_PARAM_LONG(num_elements)
|
||||
Z_PARAM_LONG(to_idx)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
if (from_idx < 0) {
|
||||
if (UNEXPECTED(from_idx < 0)) {
|
||||
ERR_BAD_INDEX();
|
||||
RETURN_NULL();
|
||||
}
|
||||
if (num_elements < 0) {
|
||||
if (UNEXPECTED(to_idx < from_idx)) {
|
||||
ERR_BAD_SIZE();
|
||||
RETURN_NULL();
|
||||
}
|
||||
zend_array* current = THIS_COLLECTION;
|
||||
uint32_t num_elements = to_idx - from_idx;
|
||||
ARRAY_NEW(new_collection, num_elements);
|
||||
zend_bool packed = HT_IS_PACKED(current);
|
||||
Bucket* bucket = current->arData;
|
||||
|
@ -1316,14 +1432,23 @@ PHP_METHOD(Collection, fill)
|
|||
zval* element;
|
||||
zend_long from_idx = 0;
|
||||
zend_array* current = THIS_COLLECTION;
|
||||
SEPARATE_CURRENT_COLLECTION(current);
|
||||
zend_long num_elements = zend_hash_num_elements(current - from_idx);
|
||||
zend_long to_idx = zend_hash_num_elements(current);
|
||||
ZEND_PARSE_PARAMETERS_START(1, 3)
|
||||
Z_PARAM_ZVAL(element)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_LONG(from_idx)
|
||||
Z_PARAM_LONG(num_elements)
|
||||
Z_PARAM_LONG(to_idx)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
if (UNEXPECTED(from_idx < 0)) {
|
||||
ERR_BAD_INDEX();
|
||||
RETURN_NULL();
|
||||
}
|
||||
if (UNEXPECTED(to_idx < from_idx)) {
|
||||
ERR_BAD_SIZE();
|
||||
RETURN_NULL();
|
||||
}
|
||||
SEPARATE_CURRENT_COLLECTION(current);
|
||||
uint32_t num_elements = to_idx - from_idx;
|
||||
Bucket* bucket = current->arData + from_idx;
|
||||
Bucket* end = bucket + current->nNumUsed;
|
||||
for (; num_elements > 0 && bucket < end; ++bucket, --num_elements) {
|
||||
|
|
|
@ -102,10 +102,10 @@ class Collection implements ArrayAccess, Countable
|
|||
* @param mixed $element
|
||||
* @param int $flags[optional]
|
||||
* @param int $from_index[optional]
|
||||
* @param int $num_elements[optional]
|
||||
* @param int $to_index[optional]
|
||||
* @return int|null
|
||||
*/
|
||||
function binarySearch($element, $flags, $from_index, $num_elements) {}
|
||||
function binarySearch($element, $flags, $from_index, $to_index) {}
|
||||
|
||||
/**
|
||||
* Searches the array or the range of the array for the provided element using the
|
||||
|
@ -119,10 +119,25 @@ class Collection implements ArrayAccess, Countable
|
|||
* @param callable $selector ($value, $key) -> $new_value
|
||||
* @param int $flags[optional]
|
||||
* @param int $from_index[optional]
|
||||
* @param int $num_elements[optional]
|
||||
* @param int $to_index[optional]
|
||||
* @return int|null
|
||||
*/
|
||||
function binarySearchBy($element, $selector, $flags, $from_index, $num_elements) {}
|
||||
function binarySearchBy($element, $selector, $flags, $from_index, $to_index) {}
|
||||
|
||||
/**
|
||||
* Searches the array or the range of the array for the provided element using the
|
||||
* binary search algorithm.
|
||||
*
|
||||
* The array is expected to be packed and sorted into ascending order according to
|
||||
* the specified comparator, otherwise the result is undefined.
|
||||
*
|
||||
* @param mixed $element
|
||||
* @param callable $comparator (Pair($key, $value), Pair($key, $value)) -> int
|
||||
* @param int $from_index[optional]
|
||||
* @param int $to_index[optional]
|
||||
* @return int|null
|
||||
*/
|
||||
function binarySearchWith($element, $comparator, $from_index, $to_index) {}
|
||||
|
||||
/**
|
||||
* Splits this collection into a collection of arrays each not exceeding the given size.
|
||||
|
@ -187,10 +202,10 @@ class Collection implements ArrayAccess, Countable
|
|||
* Returns new collection which is a copy of range of original collection.
|
||||
*
|
||||
* @param int $from_index
|
||||
* @param int $num_elements
|
||||
* @param int $to_index
|
||||
* @return Collection
|
||||
*/
|
||||
function copyOfRange($from_index, $num_elements) {}
|
||||
function copyOfRange($from_index, $to_index) {}
|
||||
|
||||
/**
|
||||
* Returns the number of elements in this collection.
|
||||
|
@ -260,10 +275,10 @@ class Collection implements ArrayAccess, Countable
|
|||
*
|
||||
* @param mixed $element
|
||||
* @param int $from_index[optional]
|
||||
* @param int $num_elements[optional]
|
||||
* @param int $to_index[optional]
|
||||
* @return void
|
||||
*/
|
||||
function fill($element, $from_index, $num_elements) {}
|
||||
function fill($element, $from_index, $to_index) {}
|
||||
|
||||
/**
|
||||
* Returns a collection containing only elements matching the given predicate.
|
||||
|
|
|
@ -7,8 +7,8 @@ $array = [3, 7, 6, 9, 2];
|
|||
// An associative array, however, Collection::copyOfRange() still works,
|
||||
// and string keys will be preserved.
|
||||
$array1 = ['a' => 'b', 'c', 'd' => 'e'];
|
||||
$array2 = Collection::init($array)->copyOfRange(2, 4)->toArray();
|
||||
$array3 = Collection::init($array1)->copyOfRange(1, 2)->toArray();
|
||||
$array2 = Collection::init($array)->copyOfRange(2, 6)->toArray();
|
||||
$array3 = Collection::init($array1)->copyOfRange(1, 3)->toArray();
|
||||
if ($array2 != array_slice($array, 2, 4) || $array3 != array_slice($array1, 1, 2)) {
|
||||
echo 'Collection::copyOfRange() failed.', PHP_EOL;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ Test Collection::fill().
|
|||
<?php
|
||||
$array = ['foo', 'bar' => 'baz', 1, 2, 3];
|
||||
$collection = Collection::init($array);
|
||||
$collection->fill('t', 1, 3);
|
||||
$collection->fill('t', 1, 4);
|
||||
$array1 = ['foo', 'bar' => 't', 't', 't', 3];
|
||||
$collection1 = Collection::init($array);
|
||||
$collection1->fill(0);
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
--TEST--
|
||||
Test Collection::binarySearch().
|
||||
--FILE--
|
||||
<?php
|
||||
$array = [];
|
||||
$size = random_int(20, 30);
|
||||
for ($i = 0; $i < 50; ++$i) {
|
||||
$array[] = random_int(100, 200);
|
||||
}
|
||||
$array = array_unique($array);
|
||||
sort($array);
|
||||
$idx = random_int(0, count($array) - 1);
|
||||
$which = $array[$idx];
|
||||
$from = random_int(0, $idx);
|
||||
$to = random_int($idx, count($array));
|
||||
$collection = Collection::init($array);
|
||||
if ($collection->binarySearch($which, 0, $from, $to) != $idx) {
|
||||
echo 'Collection::binarySearch() failed.', PHP_EOL;
|
||||
}
|
||||
$array = array_map(function ($value) {
|
||||
return [$value];
|
||||
}, $array);
|
||||
$selector = function ($value) {
|
||||
return $value[0];
|
||||
};
|
||||
if ($collection->binarySearchBy($which, $selector, 0, $from, $to) != $idx) {
|
||||
echo 'Collection::binarySearchBy() failed.', PHP_EOL;
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
Reference in New Issue