Add `binarySearch()` and `binarySearchBy()`. Refactor code.

This commit is contained in:
CismonX 2018-09-12 13:42:13 +08:00
parent 7c9fda5c64
commit 1b2d443697
6 changed files with 195 additions and 25 deletions

View File

@ -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)

View File

@ -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(&params[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, &params[0]);
@ -939,6 +1053,7 @@ PHP_METHOD(Collection, chunked)
if (transform) {
ZVAL_LONG(&params[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, &params[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) {

View File

@ -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.

View File

@ -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;
}

View File

@ -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);

View File

@ -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--