Skip to content

Instantly share code, notes, and snippets.

@hakre
Created January 7, 2013 16:44
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hakre/4476432 to your computer and use it in GitHub Desktop.
Save hakre/4476432 to your computer and use it in GitHub Desktop.
Random Iterator - Picking n Iterations random out of x Iterations (Reservoir sampling)
<?php
/**
* Random Iterator - Picking n Iterations random out of x Iterations (Reservoir sampling)
*
* TODO Preserve Iteration Keys
*
* @author hakre
*/
class RandomIterator implements IteratorAggregate
{
private $innerIterator;
private $randomIterations;
private $count;
public function __construct(Traversable $iterator, $randomIterations = 1) {
$this->setRandomIterations($randomIterations);
$this->innerIterator = $iterator;
}
private function setRandomIterations($randomIterations) {
$number = (int)$randomIterations;
if ($number < 1) {
throw new InvalidArgumentException(
sprintf('Number of iterations must be larger than 0, %d (%s) given.', $number, $randomIterations)
);
}
$this->randomIterations = $number;
}
public function getIterator() {
return $this->randomIterations === 1
? $this->getRandomSingle()
: $this->getRandomMultiple();
}
public function getCount() {
return $this->count;
}
/**
* SOLID This belongs into it's own type
*
* @return ArrayIterator
*/
private function getRandomSingle() {
$result = null;
$count = 0;
foreach ($this->innerIterator as $current) {
mt_rand(0, $count++) || $result = $current;
}
$this->count = $count;
return new ArrayIterator(array($result));
}
/**
* SOLID This belongs into it's own type
*
* @return ArrayIterator
*/
private function getRandomMultiple() {
$iterator = new IteratorIterator($this->innerIterator);
$iterator->rewind();
$it = new NoRewindIterator($iterator);
$result = array();
$pickCount = $this->randomIterations;
$count = $pickCount;
while ($count-- && $it->valid()) {
$result[$count] = $it->current();
$it->next();
}
shuffle($result);
$count = $pickCount;
foreach ($it as $current) {
$random = mt_rand(0, $count++);
($random < $pickCount) && $result[$random] = $current;
}
$this->count = $count;
return new ArrayIterator($result);
}
}
@olekukonko
Copy link

Instead of getCount why not implement countable ??

@hakre
Copy link
Author

hakre commented Jan 7, 2013

@olekukonko I also thought about it, but didn't do it because the count is only available after aggregation of the iterator, not before. So it would not always be available, count can not return NULL officially, so would require error / exception handling and I just needed it for some functional tests I did run (e.g. if the number of iteration counts matches).

@d3y4n
Copy link

d3y4n commented Jul 11, 2013

<3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment