Skip to main content
added more comments in reset()
Source Link
Sylwester
  • 529
  • 2
  • 7
<?php
class Gen
{
    private $mask;            // bitmask all combinations
    private $firstOffset;     // what first range starts from
    private $secondOffset;    // what second range starts from
    private $firstWidth;      // the width of first range
    private $secondWidth;     // the width of second range
    private $permutations;    // number of permutations
    private $used = 0;        // number of permutations used

    public function __construct(array $range1, array $range2)
    {
        if( ! count($range1) || ! count($range2) )
            throw new Exception("Both ranges must contain at least one element");
        
        // I support both array(1, 2, 3, 4) and array(1, 4) as a range
        $this->firstOffset = $range1[0];
        $this->secondOffset = $range2[0];
        $firstEnd = array_pop($range1);
        $secondEnd = array_pop($range2);

        if( $firstEnd < $this->firstOffset ||
            $secondEnd < $this->secondOffset )
            throw new Exception('End lower than start in at least one range.' . 
                        "Given [{$this->firstOffset}, $firstEnd] " . 
                        "and [{this->secondOffset}, $secondEnd]");

        $this->firstWidth = 1 + $firstEnd - $this->firstOffset;
        $this->secondWidth = 1 + $secondEnd - $this->secondOffset;
        $this->permutations = $this->firstWidth*$this->secondWidth; // with no overlap
        $this->reset();
    }

    public function next()
    {
        if( $this->used != $this->permutations ) {
            $random_permutation = rand(0,$this->permutations);
            if( ($found = gmp_scan0($this->mask, $random_permutation)) == $this->permutations )
                $found = gmp_scan0($this->mask, 0);
            $this->used++;
            gmp_setbit($this->mask, $found);
            $first = $found % $this->firstWidth + $this->firstOffset;
            $second = floor($found / $this->firstWidth) + $this->secondOffset;
            return array( $first, $second);
        }
        throw new Exception("No more pairs available");
    }
    
    public function reset()
    {
        $this->mask = gmp_init(0);
        $this->used = 0;
        
        // Make simple variables of
        // so that the original ranges
        // are [$s1, $e1] and [$s2, $e2]
        $s1 = $this->firstOffset;    
        $s2 = $this->secondOffset;
        $e1 = $s1 + $this->firstWidth - 1;
        $e2 = $s2 + $this->secondWidth - 1;
        
        if( $s1 <= $e2 && $s2 <= $e1 ) {        // if the rhe ranges overlap
            $is = max($s1, $s2);                // get the start of intersect
            $ie = min($e1, $e2);                // get the end of intersect
            $this->used +=    1 + $ie - $is;    // the count of emelemts in common
            $i1end = $ie - $s1 + 1;             // calculate the end element for first range
            for( $i1 = $is - $s1,               // initialize range1 by reducing by s1
                 $i2 = $is - $s2;               // initialize range2 by reducing by s2
                 $i1 < $i1end;                  // as long as $i1 is lower than the calculates stop
                 $i1++, $i2++) {                // increment both indexes by one for next iteration

                // body of for loop sets bitmask where both
                // would get the same value when offset is added
                gmp_setbit( $this->mask, $i1 + $i2 * $this->firstWidth ); 
            }
        }
    }
}


//$g = new Gen(range(1,6), range(1,10));
//$g = new Gen(array(3,4), array(5));
$g = new Gen(array(1,6), array(3291,83901)); 
try {
    while(1) {
        print implode(", ", $g->next()) . "\n";
    }
} catch ( Exception $e ) {
    print "Done!\n";
}
<?php
class Gen
{
    private $mask;            // bitmask all combinations
    private $firstOffset;     // what first range starts from
    private $secondOffset;    // what second range starts from
    private $firstWidth;      // the width of first range
    private $secondWidth;     // the width of second range
    private $permutations;    // number of permutations
    private $used = 0;        // number of permutations used

    public function __construct(array $range1, array $range2)
    {
        if( ! count($range1) || ! count($range2) )
            throw new Exception("Both ranges must contain at least one element");
        
        // I support both array(1, 2, 3, 4) and array(1, 4) as a range
        $this->firstOffset = $range1[0];
        $this->secondOffset = $range2[0];
        $firstEnd = array_pop($range1);
        $secondEnd = array_pop($range2);

        if( $firstEnd < $this->firstOffset ||
            $secondEnd < $this->secondOffset )
            throw new Exception('End lower than start in at least one range.' . 
                        "Given [{$this->firstOffset}, $firstEnd] " . 
                        "and [{this->secondOffset}, $secondEnd]");

        $this->firstWidth = 1 + $firstEnd - $this->firstOffset;
        $this->secondWidth = 1 + $secondEnd - $this->secondOffset;
        $this->permutations = $this->firstWidth*$this->secondWidth; // with no overlap
        $this->reset();
    }

    public function next()
    {
        if( $this->used != $this->permutations ) {
            $random_permutation = rand(0,$this->permutations);
            if( ($found = gmp_scan0($this->mask, $random_permutation)) == $this->permutations )
                $found = gmp_scan0($this->mask, 0);
            $this->used++;
            gmp_setbit($this->mask, $found);
            $first = $found % $this->firstWidth + $this->firstOffset;
            $second = floor($found / $this->firstWidth) + $this->secondOffset;
            return array( $first, $second);
        }
        throw new Exception("No more pairs available");
    }
    
    public function reset()
    {
        $this->mask = gmp_init(0);
        $this->used = 0;
        $s1 = $this->firstOffset;
        $s2 = $this->secondOffset;
        $e1 = $s1 + $this->firstWidth - 1;
        $e2 = $s2 + $this->secondWidth - 1;
        if( $s1 <= $e2 && $s2 <= $e1 ) {
            $is = max($s1, $s2);
            $ie = min($e1, $e2);
            $this->used +=    1 + $ie - $is;
            $i1end = $ie - $s1 + 1;
            for( $i1 = $is - $s1,
                 $i2 = $is - $s2;
                 $i1 < $i1end;
                 $i1++, $i2++) {
                gmp_setbit( $this->mask, $i1 + $i2 * $this->firstWidth );
            }
        }
    }
}


//$g = new Gen(range(1,6), range(1,10));
//$g = new Gen(array(3,4), array(5));
$g = new Gen(array(1,6), array(3291,83901)); 
try {
    while(1) {
        print implode(", ", $g->next()) . "\n";
    }
} catch ( Exception $e ) {
    print "Done!\n";
}
<?php
class Gen
{
    private $mask;            // bitmask all combinations
    private $firstOffset;     // what first range starts from
    private $secondOffset;    // what second range starts from
    private $firstWidth;      // the width of first range
    private $secondWidth;     // the width of second range
    private $permutations;    // number of permutations
    private $used = 0;        // number of permutations used

    public function __construct(array $range1, array $range2)
    {
        if( ! count($range1) || ! count($range2) )
            throw new Exception("Both ranges must contain at least one element");
        
        // I support both array(1, 2, 3, 4) and array(1, 4) as a range
        $this->firstOffset = $range1[0];
        $this->secondOffset = $range2[0];
        $firstEnd = array_pop($range1);
        $secondEnd = array_pop($range2);

        if( $firstEnd < $this->firstOffset ||
            $secondEnd < $this->secondOffset )
            throw new Exception('End lower than start in at least one range.' . 
                        "Given [{$this->firstOffset}, $firstEnd] " . 
                        "and [{this->secondOffset}, $secondEnd]");

        $this->firstWidth = 1 + $firstEnd - $this->firstOffset;
        $this->secondWidth = 1 + $secondEnd - $this->secondOffset;
        $this->permutations = $this->firstWidth*$this->secondWidth; // with no overlap
        $this->reset();
    }

    public function next()
    {
        if( $this->used != $this->permutations ) {
            $random_permutation = rand(0,$this->permutations);
            if( ($found = gmp_scan0($this->mask, $random_permutation)) == $this->permutations )
                $found = gmp_scan0($this->mask, 0);
            $this->used++;
            gmp_setbit($this->mask, $found);
            $first = $found % $this->firstWidth + $this->firstOffset;
            $second = floor($found / $this->firstWidth) + $this->secondOffset;
            return array( $first, $second);
        }
        throw new Exception("No more pairs available");
    }
    
    public function reset()
    {
        $this->mask = gmp_init(0);
        $this->used = 0;
        
        // Make simple variables of
        // so that the original ranges
        // are [$s1, $e1] and [$s2, $e2]
        $s1 = $this->firstOffset;    
        $s2 = $this->secondOffset;
        $e1 = $s1 + $this->firstWidth - 1;
        $e2 = $s2 + $this->secondWidth - 1;
        
        if( $s1 <= $e2 && $s2 <= $e1 ) {        // if the rhe ranges overlap
            $is = max($s1, $s2);                // get the start of intersect
            $ie = min($e1, $e2);                // get the end of intersect
            $this->used +=    1 + $ie - $is;    // the count of emelemts in common
            $i1end = $ie - $s1 + 1;             // calculate the end element for first range
            for( $i1 = $is - $s1,               // initialize range1 by reducing by s1
                 $i2 = $is - $s2;               // initialize range2 by reducing by s2
                 $i1 < $i1end;                  // as long as $i1 is lower than the calculates stop
                 $i1++, $i2++) {                // increment both indexes by one for next iteration

                // body of for loop sets bitmask where both
                // would get the same value when offset is added
                gmp_setbit( $this->mask, $i1 + $i2 * $this->firstWidth ); 
            }
        }
    }
}


//$g = new Gen(range(1,6), range(1,10));
//$g = new Gen(array(3,4), array(5));
$g = new Gen(array(1,6), array(3291,83901)); 
try {
    while(1) {
        print implode(", ", $g->next()) . "\n";
    }
} catch ( Exception $e ) {
    print "Done!\n";
}
Source Link
Sylwester
  • 529
  • 2
  • 7

This is based on @MichaelT comments and code. I started with this before he posted it but i have been peeking after he posted his code so it's not entirely original work.

I reserve where the numbers in both ranges are the same and i add the offset so that it's more aligned with the original spec.

<?php
class Gen
{
    private $mask;            // bitmask all combinations
    private $firstOffset;     // what first range starts from
    private $secondOffset;    // what second range starts from
    private $firstWidth;      // the width of first range
    private $secondWidth;     // the width of second range
    private $permutations;    // number of permutations
    private $used = 0;        // number of permutations used

    public function __construct(array $range1, array $range2)
    {
        if( ! count($range1) || ! count($range2) )
            throw new Exception("Both ranges must contain at least one element");
        
        // I support both array(1, 2, 3, 4) and array(1, 4) as a range
        $this->firstOffset = $range1[0];
        $this->secondOffset = $range2[0];
        $firstEnd = array_pop($range1);
        $secondEnd = array_pop($range2);

        if( $firstEnd < $this->firstOffset ||
            $secondEnd < $this->secondOffset )
            throw new Exception('End lower than start in at least one range.' . 
                        "Given [{$this->firstOffset}, $firstEnd] " . 
                        "and [{this->secondOffset}, $secondEnd]");

        $this->firstWidth = 1 + $firstEnd - $this->firstOffset;
        $this->secondWidth = 1 + $secondEnd - $this->secondOffset;
        $this->permutations = $this->firstWidth*$this->secondWidth; // with no overlap
        $this->reset();
    }

    public function next()
    {
        if( $this->used != $this->permutations ) {
            $random_permutation = rand(0,$this->permutations);
            if( ($found = gmp_scan0($this->mask, $random_permutation)) == $this->permutations )
                $found = gmp_scan0($this->mask, 0);
            $this->used++;
            gmp_setbit($this->mask, $found);
            $first = $found % $this->firstWidth + $this->firstOffset;
            $second = floor($found / $this->firstWidth) + $this->secondOffset;
            return array( $first, $second);
        }
        throw new Exception("No more pairs available");
    }
    
    public function reset()
    {
        $this->mask = gmp_init(0);
        $this->used = 0;
        $s1 = $this->firstOffset;
        $s2 = $this->secondOffset;
        $e1 = $s1 + $this->firstWidth - 1;
        $e2 = $s2 + $this->secondWidth - 1;
        if( $s1 <= $e2 && $s2 <= $e1 ) {
            $is = max($s1, $s2);
            $ie = min($e1, $e2);
            $this->used +=    1 + $ie - $is;
            $i1end = $ie - $s1 + 1;
            for( $i1 = $is - $s1,
                 $i2 = $is - $s2;
                 $i1 < $i1end;
                 $i1++, $i2++) {
                gmp_setbit( $this->mask, $i1 + $i2 * $this->firstWidth );
            }
        }
    }
}


//$g = new Gen(range(1,6), range(1,10));
//$g = new Gen(array(3,4), array(5));
$g = new Gen(array(1,6), array(3291,83901)); 
try {
    while(1) {
        print implode(", ", $g->next()) . "\n";
    }
} catch ( Exception $e ) {
    print "Done!\n";
}