Code: Select all
<?php
/*
Class: CombinationsIterator
Provide an iterator interface for all combinations of values in a collection of iterators, ie:
iterator A: 1, 2, 3
iterator B: a, b, c
combinations returned: array(a, 1), array(a, 2), array(a, 3), array(b, 1), array(b, 2), etc..
*/
class CombinationsIterator
{
var $_iterators;
var $_num_iterators;
// public
var $is_valid = false; // initialise false to force a reset before first use
var $k = 0; // pseudo-key (simple counter)
/*
param (array) 0 indexed, sequential, numerical array of (Eclipse) iterators
*/
function CombinationsIterator(&$iterators)
{
$this->_iterators =& $iterators;
$this->_num_iterators = count($iterators);
}
/*
*/
function reset()
{
if($this->_num_iterators == 0)
{
$this->is_valid = false;
} else {
$this->is_valid = true;
$this->k = 0;
$this->_resetIterators();
}
}
/*
return (bool)
*/
function isValid()
{
return $this->is_valid;
}
/*
Advance the appropriate iterator cursor.
Set $is_valid = false after final combination.
*/
function next()
{
$this->k++;
for($j=$this->_num_iterators-1; $j>=0; $j--)
{
$this->_iterators[$j]->next();
if($j == 0 and !$this->_iterators[$j]->isValid()) // finished
{
$this->is_valid = false;
return;
}
if($this->_iterators[$j]->isValid())
{
return;
} else {
$this->_iterators[$j]->reset();
}
}
}
/*
return (array)
*/
function &getCurrent()
{
$row = array();
for($i=0; $i<$this->_num_iterators; $i++)
{
$row[] =& $this->_iterators[$i]->getCurrent();
}
return $row;
}
function getKey()
{
return $this->k;
}
//////////////////////////////////////////
// PRIVATE //
//////////////////////////////////////////
function _resetIterators()
{
foreach(array_keys($this->_iterators) as $key)
{
$this->_iterators[$key]->reset();
}
}
}Code: Select all
class TestOfCombinationsIterator extends UnitTestCase
{
function TestOfCombinationsIterator()
{
$this->UnitTestCase();
}
/*
Document behaviour with an empty array.
*/
function testEmptyArrayParameter()
{
$iterators = array();
$cit =& new CombinationsIterator($iterators);
$cit->reset();
$this->assertFalse($cit->isValid());
$this->assertIdentical($cit->getCurrent(), array());
$i = 0;
for($cit->reset(); $cit->isValid(); $cit->next())
{
$i++;
}
$this->assertIdentical($i, 0); // ie no output
}
/*
Iterators collections in $iterators param may have no members.
Check this doesn't break things.
*/
function testEmptyCollectionMixedWithValidCollections()
{
$a = array('a', 'b', 'c');
$b = array(1, 2, 3);
$c = array();
$iterators = array();
$iterators[] =& new EclipseArrayIterator($a);
$iterators[] =& new EclipseArrayIterator($b);
$iterators[] =& new EclipseArrayIterator($c);
$cit =& new CombinationsIterator($iterators);
$cit->reset();
$this->assertTrue($cit->isValid());
$i = 0;
for($cit->reset(); $cit->isValid(); $cit->next())
{
$i++;
}
$this->assertIdentical($i, 9);
}
/*
Document behaviour if all are empty.
*/
function testAllEmptyCollections()
{
$a = array();
$b = array();
$c = array();
$iterators = array();
$iterators[] =& new EclipseArrayIterator($a);
$iterators[] =& new EclipseArrayIterator($b);
$iterators[] =& new EclipseArrayIterator($c);
$cit =& new CombinationsIterator($iterators);
$cit->reset();
$this->assertTrue($cit->isValid());
$i = 0;
for($cit->reset(); $cit->isValid(); $cit->next())
{
$i++;
}
$this->assertIdentical($i, 1);
// for reference:
// "empty" collections always behave as if they have one value: null
// thus a single combination is obtained from n empty collections
// this is a numerical array with null values at each key (and n keys)
// if null values in combinations are going to upset things, either
// check iterator collections before passing in, or check
// output array for null values
$cit->reset();
$output = $cit->getCurrent();
$this->assertNull($output[0]);
$this->assertNull($output[1]);
$this->assertNull($output[2]);
}
function testWithSingleElementCollections()
{
$a = array(1);
$b = array(2);
$c = array(3);
$iterators = array();
$iterators[] =& new EclipseArrayIterator($a);
$iterators[] =& new EclipseArrayIterator($b);
$iterators[] =& new EclipseArrayIterator($c);
$cit =& new CombinationsIterator($iterators);
#!! are also testing element order here - not required
$cit->reset();
$output = $cit->getCurrent();
$this->assertEqual($output[0], 1);
$this->assertEqual($output[1], 2);
$this->assertEqual($output[2], 3);
// a single combination
$cit->next();
$this->assertFalse($cit->isValid());
}
function testReset()
{
$a = array(1, 2);
$b = array('a', 'b', 'c');
$iterators = array();
$iterators[] =& new EclipseArrayIterator($a);
$iterators[] =& new EclipseArrayIterator($b);
$cit =& new CombinationsIterator($iterators);
$i = 0;
for($cit->reset(); $cit->isValid(); $cit->next())
{
$i++;
}
$this->assertIdentical($i, 6);
$i = 0;
for($cit->reset(); $cit->isValid(); $cit->next())
{
$i++;
}
$this->assertIdentical($i, 6);
}
function testOutput()
{
$a = array(1, 2);
$b = array('a', 'b', 'c');
$iterators = array();
$iterators[] =& new EclipseArrayIterator($a);
$iterators[] =& new EclipseArrayIterator($b);
$cit =& new CombinationsIterator($iterators);
#!! are also testing element order here - not required
$cit->reset();
$output = $cit->getCurrent();
$this->assertEqual($output[0], 1);
$this->assertEqual($output[1], 'a');
$cit->next();
$output = $cit->getCurrent();
$this->assertEqual($output[0], 1);
$this->assertEqual($output[1], 'b');
$cit->next();
$output = $cit->getCurrent();
$this->assertEqual($output[0], 1);
$this->assertEqual($output[1], 'c');
$cit->next();
$output = $cit->getCurrent();
$this->assertEqual($output[0], 2);
$this->assertEqual($output[1], 'a');
$cit->next();
$output = $cit->getCurrent();
$this->assertEqual($output[0], 2);
$this->assertEqual($output[1], 'b');
$cit->next();
$output = $cit->getCurrent();
$this->assertEqual($output[0], 2);
$this->assertEqual($output[1], 'c');
}
}
?>