Skip to content

Instantly share code, notes, and snippets.

@hakre
Created January 7, 2013 16:44
Show Gist options
  • Select an option

  • Save hakre/4476432 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

Instead of getCount why not implement countable ??

@hakre

hakre commented Jan 7, 2013

Copy link
Copy Markdown
Author

@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

d3y4n commented Jul 11, 2013

Copy link
Copy Markdown

<3

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