diff --git a/src/collections_me.c b/src/collections_me.c index 9bb2430..0e06003 100644 --- a/src/collections_me.c +++ b/src/collections_me.c @@ -136,6 +136,18 @@ ZEND_BEGIN_ARG_INFO(to_collection_arginfo, 0) ZEND_ARG_OBJ_INFO(0, destination, Collection, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO(windowed_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, step, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, partial_windows, _IS_BOOL, 0) + ZEND_ARG_CALLABLE_INFO(0, transform, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(zip_arginfo, 0) + ZEND_ARG_INFO(0, other) + ZEND_ARG_CALLABLE_INFO(0, transform, 0) +ZEND_END_ARG_INFO() + const zend_function_entry collection_methods[] = { PHP_ME(Collection, __construct, NULL, ZEND_ACC_PRIVATE | ZEND_ACC_CTOR) PHP_ME(Collection, addAll, elements_arginfo, ZEND_ACC_PUBLIC) @@ -240,6 +252,9 @@ const zend_function_entry collection_methods[] = { PHP_ME(Collection, toPairs, NULL, ZEND_ACC_PUBLIC) PHP_ME(Collection, union, other_arginfo, ZEND_ACC_PUBLIC) PHP_ME(Collection, values, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Collection, windowed, windowed_arginfo, ZEND_ACC_PUBLIC) + PHP_ME(Collection, zip, zip_arginfo, ZEND_ACC_PUBLIC) + PHP_ME(Collection, zipWithNext, transform_arginfo, ZEND_ACC_PUBLIC) PHP_FE_END }; diff --git a/src/collections_methods.c b/src/collections_methods.c index de9d8a5..8a16098 100644 --- a/src/collections_methods.c +++ b/src/collections_methods.c @@ -2594,7 +2594,7 @@ PHP_METHOD(Collection, shuffle) Z_TRY_ADDREF_P(val); zend_hash_next_index_insert(shuffled, val); ZEND_HASH_FOREACH_END(); - size_t offset = 0; + uint32_t offset = 0; Bucket* bucket = shuffled->arData; for (; offset < num_elements - 1; ++offset) { zend_long rand_idx = php_mt_rand_range(offset, num_elements - 1); @@ -2613,7 +2613,7 @@ PHP_METHOD(Collection, shuffled) Z_TRY_ADDREF_P(val); zend_hash_next_index_insert(shuffled, val); ZEND_HASH_FOREACH_END(); - size_t offset = 0; + uint32_t offset = 0; Bucket* bucket = shuffled->arData; for (; offset < num_elements - 1; ++offset) { zend_long rand_idx = php_mt_rand_range(offset, num_elements - 1); @@ -3263,6 +3263,113 @@ PHP_METHOD(Collection, values) RETVAL_NEW_COLLECTION(new_collection); } +PHP_METHOD(Collection, windowed) +{ + zend_long size; + zend_long step = 1; + zend_bool partial_windows = 0; + zend_fcall_info fci; + zend_fcall_info_cache fcc; + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_LONG(size) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(step) + Z_PARAM_BOOL(partial_windows) + Z_PARAM_FUNC(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + zend_array* current = THIS_COLLECTION; + if (size < 1 || step < 1) { + ERR_BAD_SIZE(); + RETURN_NULL(); + } + if (!HT_IS_PACKED(current)) { + ERR_NOT_PACKED(); + RETURN_NULL(); + } + zend_bool has_transform = EX_NUM_ARGS() == 4; + INIT_FCI(&fci, 2); + uint32_t num_elements = zend_hash_num_elements(current); + uint32_t num_ret; + if (partial_windows) { + num_ret = num_elements / step + (num_elements % step ? 1 : 0); + } else { + num_ret = num_elements >= size ? (num_elements - size) / step + 1 : 0; + } + ARRAY_NEW(windowed, num_ret); + Bucket* start = current->arData; + uint32_t idx; + uint32_t pos = 0; + for (idx = 0; idx < num_ret; ++idx, pos += step) { + ARRAY_NEW(snapshot, size); + uint32_t snapshot_idx; + for (snapshot_idx = 0; snapshot_idx < size; ++snapshot_idx) { + if (partial_windows && UNEXPECTED(pos + snapshot_idx >= num_elements)) { + break; + } + Bucket* bucket = start + pos + snapshot_idx; + Z_TRY_ADDREF(bucket->val); + zend_hash_next_index_insert(snapshot, &bucket->val); + } + if (has_transform) { + ZVAL_ARR(¶ms[0], snapshot); + ZVAL_LONG(¶ms[1], idx); + zend_call_function(&fci, &fcc); + array_release(snapshot); + } else { + ZVAL_ARR(&retval, snapshot); + } + zend_hash_next_index_insert(windowed, &retval); + } + RETVAL_NEW_COLLECTION(windowed); +} + +PHP_METHOD(Collection, zip) +{ + +} + +PHP_METHOD(Collection, zipWithNext) +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_FUNC(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + zend_array* current = THIS_COLLECTION; + if (!HT_IS_PACKED(current)) { + ERR_NOT_PACKED(); + RETURN_NULL(); + } + zend_bool has_transform = EX_NUM_ARGS() == 1; + INIT_FCI(&fci, 2); + uint32_t num_elements = zend_hash_num_elements(current); + ARRAY_NEW(zipped, num_elements); + if (num_elements > 1) { + Bucket* start = current->arData; + uint32_t idx; + for (idx = 0; idx < num_elements - 1; ++idx) { + Bucket* bucket = start + idx; + if (has_transform) { + ZVAL_COPY_VALUE(¶ms[0], &bucket->val); + ZVAL_COPY_VALUE(¶ms[1], &(bucket + 1)->val); + zend_call_function(&fci, &fcc); + } else { + zend_object* obj = create_pair_obj(); + Z_TRY_ADDREF(bucket->val); + Z_TRY_ADDREF((bucket + 1)->val); + pair_update_first(obj, &bucket->val); + pair_update_second(obj, &(bucket + 1)->val); + zval pair; + ZVAL_OBJ(&pair, obj); + ZVAL_COPY_VALUE(&retval, &pair); + } + zend_hash_next_index_insert(zipped, &retval); + } + } + RETVAL_NEW_COLLECTION(zipped); +} + PHP_METHOD(Pair, __construct) { zval* first; diff --git a/src/php_collections_me.h b/src/php_collections_me.h index c2344b5..1b32883 100644 --- a/src/php_collections_me.h +++ b/src/php_collections_me.h @@ -112,6 +112,9 @@ PHP_METHOD(Collection, toCollection); PHP_METHOD(Collection, toPairs); PHP_METHOD(Collection, union); PHP_METHOD(Collection, values); +PHP_METHOD(Collection, windowed); +PHP_METHOD(Collection, zip); +PHP_METHOD(Collection, zipWithNext); PHP_METHOD(Pair, __construct); diff --git a/stubs/Collection.php b/stubs/Collection.php index 0fd3af9..1aad14e 100644 --- a/stubs/Collection.php +++ b/stubs/Collection.php @@ -923,4 +923,48 @@ class Collection implements ArrayAccess, Countable * @return Collection */ function values() {} + + /** + * Returns a collection of snapshots of the window of the given size sliding along this + * collection with the given step, where each snapshot is an array. + * + * If provided, the transform function will be applied to each snapshot. + * + * The wrapped array must be packed. + * + * @param int $size + * @param int $step[optional] = 1 + * @param bool $partial_windows[optional] = false + * @param callable $transform[optional] ($value, $key) -> $new_value + * @return Collection + */ + function windowed($size, $step, $partial_windows, $transform) {} + + /** + * Returns a collection of pairs built from the elements of this array and other array + * with the same index. The returned collection has length of the shortest array. + * + * If the transform function is provided, return a collection of the return values of + * the transform function applied to each pair of elements. + * + * Both arrays must be packed. + * + * @param array|Collection $other + * @param callable $transform[optional] ($v1, $v2) -> $new_value + * @return Collection + */ + function zip($other, $transform) {} + + /** + * Returns a list of pairs of each two adjacent elements in this collection. + * + * If the transform function is provided, return a collection of the return values of + * the transform function applied to each pair of elements. + * + * The wrapped array must be packed. + * + * @param callable $transform[optional] ($v1, $v2) -> $new_value + * @return Collection + */ + function zipWithNext($transform) {} } diff --git a/tests/059-windowed.phpt b/tests/059-windowed.phpt new file mode 100644 index 0000000..717a0c9 --- /dev/null +++ b/tests/059-windowed.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test Collection::windowed(). +--FILE-- +windowed(3, 2, false)->toArray() != $array1) { + echo 'Collection::windowed() failed.', PHP_EOL; +} + +$array1 = [ + ['a', 'b', 'c', 'd', 4], + ['d', 'e', 'f', 'g', 4], + ['g', 'h', 2] +]; +$transform = function ($snapshot) { + $snapshot[] = count($snapshot); + return $snapshot; +}; +if ($collection->windowed(4, 3, true, $transform)->toArray() != $array1) { + echo 'Collection::windowed() failed.', PHP_EOL; +} +?> +--EXPECT-- diff --git a/tests/060-zip-with-next.phpt b/tests/060-zip-with-next.phpt new file mode 100644 index 0000000..665ca35 --- /dev/null +++ b/tests/060-zip-with-next.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test Collection::zipWithNext(). +--FILE-- +zipWithNext()->toArray() != []) { + echo 'Collection::zipWithNext() failed.', PHP_EOL; +} + +$array = ['a', 'b', 'c', 'd']; +$collection = Collection::init($array); +$array1 = [ + new Pair('a', 'b'), + new Pair('b', 'c'), + new Pair('c', 'd') +]; +if ($collection->zipWithNext()->toArray() != $array1) { + echo 'Collection::zipWithNext() failed.', PHP_EOL; +} + +$transform = function ($v1, $v2) { + return $v1.$v2; +}; +$array1 = ['ab', 'bc', 'cd']; +if ($collection->zipWithNext($transform)->toArray() != $array1) { + echo 'Collection::zipWithNext() failed.', PHP_EOL; +} +?> +--EXPECT--