diff --git a/src/collections_methods.c b/src/collections_methods.c index a4b1099..21dc535 100644 --- a/src/collections_methods.c +++ b/src/collections_methods.c @@ -97,8 +97,7 @@ #define ARRAY_NEW_EX(name, other) \ ARRAY_NEW(name, zend_hash_num_elements(other)) #define ARRAY_CLONE(dest, src) \ - ARRAY_NEW_EX(dest, src); \ - zend_hash_copy(dest, src, NULL) + zend_array* (dest) = zend_array_dup(src); #define RETVAL_NEW_COLLECTION(collection) \ do \ @@ -242,6 +241,15 @@ static int bucket_reverse_compare_regular(const void* op1, const void* op2) return ZEND_NORMALIZE_BOOL(Z_LVAL(result)); } +static int bucket_compare_by(const void* op1, const void* op2) +{ + Bucket* b1 = (Bucket*)op1; + Bucket* b2 = (Bucket*)op2; + zval* ref = COLLECTIONS_G(ref); + compare_func_t cmp = COLLECTIONS_G(cmp); + return cmp(&ref[b1->h], &ref[b2->h]); +} + static int bucket_compare_userland(const void* op1, const void* op2) { Bucket* b1 = (Bucket*)op1; @@ -295,6 +303,55 @@ static zend_always_inline compare_func_t compare_func_init( return reverse ? bucket_reverse_compare_regular : bucket_compare_regular; } +static zend_always_inline void zend_hash_sort_by(zend_array* ht) +{ + uint32_t i; + if (HT_IS_WITHOUT_HOLES(ht)) + { + i = ht->nNumUsed; + } + else + { + uint32_t j; + for (j = 0, i = 0; j < ht->nNumUsed; j++) + { + Bucket *p = ht->arData + j; + if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) + { + continue; + } + if (i != j) + { + ht->arData[i] = *p; + } + i++; + } + } + uint32_t num_elements = zend_hash_num_elements(ht); + if (num_elements > 1) + { + zend_sort(ht->arData, i, sizeof(Bucket), bucket_compare_by, + (swap_func_t)zend_hash_bucket_packed_swap); + ht->nNumUsed = i; + } + HashPosition idx = 0; + ZEND_HASH_FOREACH_BUCKET(ht, Bucket* bucket) + bucket->h = idx++; + ZEND_HASH_FOREACH_END(); + if (!HT_IS_PACKED(ht)) + { + void *new_data, *old_data = HT_GET_DATA_ADDR(ht); + Bucket *old_buckets = ht->arData; + new_data = pemalloc(HT_SIZE_EX(ht->nTableSize, HT_MIN_MASK), (ht->u.flags & HASH_FLAG_PERSISTENT)); + ht->u.flags |= HASH_FLAG_PACKED | HASH_FLAG_STATIC_KEYS; + ht->nTableMask = HT_MIN_MASK; + HT_SET_DATA_ADDR(ht, new_data); + memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); + pefree(old_data, ht->u.flags & HASH_FLAG_PERSISTENT & HASH_FLAG_PERSISTENT); + HT_HASH_RESET_PACKED(ht); + } +} + int count_collection(zval* obj, zend_long* count) { zend_array* current = COLLECTION_FETCH(obj); @@ -797,7 +854,7 @@ PHP_METHOD(Collection, drop) continue; } --n; - Z_TRY_ADDREF(bucket->val); + zval_ptr_dtor(&bucket->val); zend_hash_del_bucket(new_collection, bucket); } RETVAL_NEW_COLLECTION(new_collection); @@ -825,7 +882,7 @@ PHP_METHOD(Collection, dropLast) continue; } --n; - Z_TRY_ADDREF(bucket->val); + zval_ptr_dtor(&bucket->val); zend_hash_del_bucket(new_collection, bucket); } RETVAL_NEW_COLLECTION(new_collection); @@ -1587,7 +1644,7 @@ PHP_METHOD(Collection, maxWith) FCI_G = &fci; FCC_G = &fcc; zend_array* current = COLLECTION_FETCH_CURRENT(); - zend_array* max_with = zend_array_dup(current); + ARRAY_CLONE(max_with, current); ZEND_HASH_FOREACH_BUCKET(max_with, Bucket* bucket) NEW_PAIR_OBJ(obj); bucket_to_pair(obj, bucket); @@ -1671,7 +1728,7 @@ PHP_METHOD(Collection, minWith) FCI_G = &fci; FCC_G = &fcc; zend_array* current = COLLECTION_FETCH_CURRENT(); - zend_array* min_with = zend_array_dup(current); + ARRAY_CLONE(min_with, current); ZEND_HASH_FOREACH_BUCKET(min_with, Bucket* bucket) NEW_PAIR_OBJ(obj); bucket_to_pair(obj, bucket); @@ -2230,12 +2287,84 @@ PHP_METHOD(Collection, sort) PHP_METHOD(Collection, sortBy) { - + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zend_long flags = 0; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(flags) + ZEND_PARSE_PARAMETERS_END(); + zend_array* current = COLLECTION_FETCH_CURRENT(); + SEPARATE_CURRENT_COLLECTION(current); + uint32_t num_elements = zend_hash_num_elements(current); + zval* sort_by = (zval*)malloc(num_elements * sizeof(zval)); + INIT_FCI(&fci, 2); + compare_func_t cmp = NULL; + HashPosition idx = 0; + ZEND_HASH_FOREACH_BUCKET(current, Bucket* bucket) + CALLBACK_KEYVAL_INVOKE(params, bucket); + if (UNEXPECTED(cmp == NULL)) + { + cmp = compare_func_init(&retval, 0, flags); + } + if (bucket->key) + { + zend_string_release(bucket->key); + bucket->key = NULL; + } + bucket->h = idx; + ZVAL_COPY_VALUE(&sort_by[idx++], &retval); + ZEND_HASH_FOREACH_END(); + COLLECTIONS_G(ref) = sort_by; + COLLECTIONS_G(cmp) = cmp; + zend_hash_sort_by(current); + for (idx = 0; idx < num_elements; ++idx) + { + zval_ptr_dtor(&sort_by[idx]); + } + free(sort_by); } PHP_METHOD(Collection, sortByDescending) { - + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zend_long flags = 0; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(flags) + ZEND_PARSE_PARAMETERS_END(); + zend_array* current = COLLECTION_FETCH_CURRENT(); + SEPARATE_CURRENT_COLLECTION(current); + uint32_t num_elements = zend_hash_num_elements(current); + zval* sort_by = (zval*)malloc(num_elements * sizeof(zval)); + INIT_FCI(&fci, 2); + compare_func_t cmp = NULL; + HashPosition idx = 0; + ZEND_HASH_FOREACH_BUCKET(current, Bucket* bucket) + CALLBACK_KEYVAL_INVOKE(params, bucket); + if (UNEXPECTED(cmp == NULL)) + { + cmp = compare_func_init(&retval, 1, flags); + } + if (bucket->key) + { + zend_string_release(bucket->key); + bucket->key = NULL; + } + bucket->h = idx; + ZVAL_COPY_VALUE(&sort_by[idx++], &retval); + ZEND_HASH_FOREACH_END(); + COLLECTIONS_G(ref) = sort_by; + COLLECTIONS_G(cmp) = cmp; + zend_hash_sort_by(current); + for (idx = 0; idx < num_elements; ++idx) + { + zval_ptr_dtor(&sort_by[idx]); + } + free(sort_by); } PHP_METHOD(Collection, sortDescending) @@ -2265,7 +2394,7 @@ PHP_METHOD(Collection, sortWith) FCI_G = &fci; FCC_G = &fcc; zend_array* current = COLLECTION_FETCH_CURRENT(); - zend_array* sorted_with = zend_array_dup(current); + ARRAY_CLONE(sorted_with, current); ZEND_HASH_FOREACH_BUCKET(sorted_with, Bucket* bucket) NEW_PAIR_OBJ(obj); bucket_to_pair(obj, bucket); @@ -2308,12 +2437,86 @@ PHP_METHOD(Collection, sorted) PHP_METHOD(Collection, sortedBy) { - + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zend_long flags = 0; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(flags) + ZEND_PARSE_PARAMETERS_END(); + zend_array* current = COLLECTION_FETCH_CURRENT(); + ARRAY_CLONE(sorted, current); + uint32_t num_elements = zend_hash_num_elements(current); + zval* sort_by = (zval*)malloc(num_elements * sizeof(zval)); + INIT_FCI(&fci, 2); + compare_func_t cmp = NULL; + HashPosition idx = 0; + ZEND_HASH_FOREACH_BUCKET(sorted, Bucket* bucket) + CALLBACK_KEYVAL_INVOKE(params, bucket); + if (UNEXPECTED(cmp == NULL)) + { + cmp = compare_func_init(&retval, 0, flags); + } + if (bucket->key) + { + zend_string_release(bucket->key); + bucket->key = NULL; + } + bucket->h = idx; + ZVAL_COPY_VALUE(&sort_by[idx++], &retval); + ZEND_HASH_FOREACH_END(); + COLLECTIONS_G(ref) = sort_by; + COLLECTIONS_G(cmp) = cmp; + zend_hash_sort_by(sorted); + for (idx = 0; idx < num_elements; ++idx) + { + zval_ptr_dtor(&sort_by[idx]); + } + free(sort_by); + RETVAL_NEW_COLLECTION(sorted); } PHP_METHOD(Collection, sortedByDescending) { - + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zend_long flags = 0; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(flags) + ZEND_PARSE_PARAMETERS_END(); + zend_array* current = COLLECTION_FETCH_CURRENT(); + ARRAY_CLONE(sorted, current); + uint32_t num_elements = zend_hash_num_elements(current); + zval* sort_by = (zval*)malloc(num_elements * sizeof(zval)); + INIT_FCI(&fci, 2); + compare_func_t cmp = NULL; + HashPosition idx = 0; + ZEND_HASH_FOREACH_BUCKET(sorted, Bucket* bucket) + CALLBACK_KEYVAL_INVOKE(params, bucket); + if (UNEXPECTED(cmp == NULL)) + { + cmp = compare_func_init(&retval, 1, flags); + } + if (bucket->key) + { + zend_string_release(bucket->key); + bucket->key = NULL; + } + bucket->h = idx; + ZVAL_COPY_VALUE(&sort_by[idx++], &retval); + ZEND_HASH_FOREACH_END(); + COLLECTIONS_G(ref) = sort_by; + COLLECTIONS_G(cmp) = cmp; + zend_hash_sort_by(sorted); + for (idx = 0; idx < num_elements; ++idx) + { + zval_ptr_dtor(&sort_by[idx]); + } + free(sort_by); + RETVAL_NEW_COLLECTION(sorted); } PHP_METHOD(Collection, sortedDescending) @@ -2344,7 +2547,7 @@ PHP_METHOD(Collection, sortedWith) FCI_G = &fci; FCC_G = &fcc; zend_array* current = COLLECTION_FETCH_CURRENT(); - zend_array* sorted_with = zend_array_dup(current); + ARRAY_CLONE(sorted_with, current); ZEND_HASH_FOREACH_BUCKET(sorted_with, Bucket* bucket) NEW_PAIR_OBJ(obj); bucket_to_pair(obj, bucket); diff --git a/src/php_collections.h b/src/php_collections.h index f2ed7ea..10fdea1 100644 --- a/src/php_collections.h +++ b/src/php_collections.h @@ -35,6 +35,8 @@ extern zend_module_entry collections_module_entry; ZEND_BEGIN_MODULE_GLOBALS(collections) zend_fcall_info* fci; zend_fcall_info_cache* fcc; + zval* ref; + compare_func_t cmp; ZEND_END_MODULE_GLOBALS(collections) ZEND_EXTERN_MODULE_GLOBALS(collections) diff --git a/tests/042-shuffle-shuffled.phpt b/tests/042-shuffle-shuffled.phpt index d7fd0d1..43b8a2f 100644 --- a/tests/042-shuffle-shuffled.phpt +++ b/tests/042-shuffle-shuffled.phpt @@ -18,15 +18,15 @@ if (array_sum($array) != array_sum($collection->toArray())) echo 'Collection::shuffle() failed.', PHP_EOL; for ($i = 0; ; ++$i) { - $reversed = Collection::init($array)->shuffled(); - if ($reversed->toArray() != array_values($array)) + $shuffled = Collection::init($array)->shuffled(); + if ($shuffled->toArray() != array_values($array)) break; if ($i > 10) { echo 'Collection::shuffled() failed.', PHP_EOL; exit; } } -if (array_sum($array) != array_sum($reversed->toArray())) +if (array_sum($array) != array_sum($shuffled->toArray())) echo 'Collection::shuffled() failed.', PHP_EOL; ?> --EXPECT-- diff --git a/tests/045-sort-descending.phpt b/tests/045-sort-sorted.phpt similarity index 51% rename from tests/045-sort-descending.phpt rename to tests/045-sort-sorted.phpt index 48b25b8..0b446ef 100644 --- a/tests/045-sort-descending.phpt +++ b/tests/045-sort-sorted.phpt @@ -1,5 +1,5 @@ --TEST-- -Test Collection::sort() and Collection::sortDescending(). +Test Collection::sort(), Collection::sortDescending(), Collection::sorted(), Collection::sortedDescending(). --FILE-- toArray()) echo 'Collection::sortDescending() failed.', PHP_EOL; + +$collection = Collection::init($array); +$array1 = $array; +sort($array1, SORT_NUMERIC); +if ($array1 != $collection->sorted()->toArray()) + echo 'Collection::sorted() failed.', PHP_EOL; + +$collection = Collection::init($array); +$array1 = $array; +rsort($array1, SORT_NUMERIC); +if ($array1 != $collection->sortedDescending()->toArray()) + echo 'Collection::sortedDescending() failed.', PHP_EOL; ?> --EXPECT-- diff --git a/tests/046-sort-by-sorted-by.phpt b/tests/046-sort-by-sorted-by.phpt new file mode 100644 index 0000000..467cad6 --- /dev/null +++ b/tests/046-sort-by-sorted-by.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test Collection::sortBy(), Collection::sortByDescending(), Collection::sortedBy(), Collection::sortedByDescending(). +--FILE-- +sortBy($sort_by); +$array1 = $array; +usort($array1, $sort_with); +if ($array1 != $collection->toArray()) + echo 'Collection::sortBy() failed.', PHP_EOL; + +$collection = Collection::init($array); +$collection->sortByDescending($sort_by); +$array1 = $array; +usort($array1, $sort_with_descending); +if ($array1 != $collection->toArray()) + echo 'Collection::sortByDescending() failed.', PHP_EOL; + +$collection = Collection::init($array); +$array1 = $array; +usort($array1, $sort_with); +if ($array1 != $collection->sortedBy($sort_by)->toArray()) + echo 'Collection::sortedBy() failed.', PHP_EOL; + +$collection = Collection::init($array); +$array1 = $array; +usort($array1, $sort_with_descending); +if ($array1 != $collection->sortedByDescending($sort_by)->toArray()) + echo 'Collection::sortedByDescending() failed.', PHP_EOL; +?> +--EXPECT-- diff --git a/tests/046-sorted-descending.phpt b/tests/046-sorted-descending.phpt deleted file mode 100644 index 5af7376..0000000 --- a/tests/046-sorted-descending.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -Test Collection::sorted() and Collection::sortedDescending(). ---FILE-- -sorted()->toArray()) - echo 'Collection::sort() failed.', PHP_EOL; - -$collection = Collection::init($array); -$array1 = $array; -rsort($array1, SORT_NUMERIC); -if ($array1 != $collection->sortedDescending()->toArray()) - echo 'Collection::sortDescending() failed.', PHP_EOL; -?> ---EXPECT--