Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ list the function signatures as an overview:
Iterator take(int $num, iterable $iterable)
Iterator drop(int $num, iterable $iterable)
Iterator repeat(mixed $value, int $num = INF)
Iterator cycle(iterable $iterable, int $num = INF)
Iterator keys(iterable $iterable)
Iterator values(iterable $iterable)
bool any(callable $predicate, iterable $iterable)
Expand Down
27 changes: 27 additions & 0 deletions src/iter.php
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,33 @@ function repeat($value, $num = INF) {
}
}

/**
* Takes an iterator and cycles through its elements a given number of times
*
* If working with a generator it is best to make it rewindable if the expected
* number of elements is large
*
* Examples:
*
* iter\cycle([1, 2, 3], 3)
* => iter(1, 2, 3, 1, 2, 3, 1, 2, 3)
*
* @param mixed $iterable The iterator to cycle
* @param $num The number of items to cycle
*
* @return \Iterator
*/
function cycle($iterable, $num = INF) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't this run endless in case $num is not provided?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it will :) it is designed as repeat is and how the python cycle function is designed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@staabm It is useful because if you create conditions that stop iterating the generator then it wont run forever. In other words you get a chance to break the iteration after each yield.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But shouldn't there be a break or something like that after all values have been yielded?ä
How would the caller of this function otherwise break the endless loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It cycles through the values of the iterator yielding each value until $num cycles are complete. If $num is INF then the generator will continue yielding values from the iterator until iteration of the generator stops (which may be never).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a non-useful but informative example:

// this will run forever
foreach (cycle(['A', 'B']) as $value) {

}
// this will run until the condition is satisfied
foreach (cycle(['A', 'B']) as $value) {
    if (mt_rand(1, 100) === 50) {
       break;
   }
}

if ($iterable instanceof \Generator) {
$iterable = toArray($iterable);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main thing I'm unsure about here is this automatic toArray conversion. The iterator below preserves keys, so this should probably be toArrayWithKeys -- however, converting generators with keys will very often not produce the desired result due to overwritten keys. One could store keys and values in separate arrays to avoid that, though that'll make the implementation quite ugly.

for ($i = 0; $i < $num; ++$i) {
foreach ($iterable as $key => $value) {
yield $key => $value;
}
}
}

/**
* Returns the keys of an iterable.
*
Expand Down
1 change: 1 addition & 0 deletions src/iter.rewindable.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function slice() { return new _RewindableGenerator('iter\slice', fun
function take() { return new _RewindableGenerator('iter\take', func_get_args()); }
function drop() { return new _RewindableGenerator('iter\drop', func_get_args()); }
function repeat() { return new _RewindableGenerator('iter\repeat', func_get_args()); }
function cycle() { return new _RewindableGenerator('iter\cycle', func_get_args()); }
function takeWhile() { return new _RewindableGenerator('iter\takeWhile', func_get_args()); }
function dropWhile() { return new _RewindableGenerator('iter\dropWhile', func_get_args()); }
function keys() { return new _RewindableGenerator('iter\keys', func_get_args()); }
Expand Down
9 changes: 8 additions & 1 deletion test/iterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,17 @@ public function testFlip() {
toArrayWithKeys(flip(['a' => 1, 'b' => 2, 'c' => 3]))
);
}

public function testCycle() {
$this->assertSame(
[1, 2, 1, 2, 1, 2, 1, 2],
toArray(cycle(rewindable\range(1, 2), 4))
);
}
}

class _CountableTestDummy implements \Countable {
public function count() {
return 42;
}
}
}