HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpMinor

Better way to achieve looped output result

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
resultachievewayoutputbetterlooped

Problem

I have 4 variables - say A, B, C and D

Each variable can have 4 values - say 1, 2, 3 and 4

I need to generate all possible combinations of values

Example of Expected Output:

A B C D
---------
 1 1 1 1
 1 1 1 2
 1 1 1 3
 1 1 1 4
 1 1 2 1
 1 1 2 2
 1 1 2 3
 .
 .
 .
 4 4 4 1
 4 4 4 2
 4 4 4 3
 4 4 4 4


I have wrote following loop

for ($A = 1 ; $A < 5 ; $A++){
 for ($B = 1 ; $B < 5 ; $B++){
  for ($C = 1 ; $C < 5 ; $C++){
   for ($D = 1 ; $D < 5 ; $D++){
    echo $A.$B.$C.$D;
   }
  }
 }
}


Is there any faster or better way to achieve above result?

Solution

Review

Your answer is perfectly fine for this usage, I would keep that. If you were that deep in loops generally it would be a sign that something was wrong, but for this task it is perfectly okay.

I would recommend using upper case only for constants.

Over Optimisation

This is actually a lot simpler than the other answers are making it out to be. The best answer in terms of performance (which is a silly thing to aim for) that I can think of is:

$end = pow(4, 4);

for($i = 0; $i < $end; ++$i)
{
    echo 1111 + base_convert($i, 10, 4), PHP_EOL;
}


Addition is quite a cheap operation for a processor to perform. Comparing the benchmarks from Elias Van Ootegem's answer shows that the above code is significantly quicker. This is due to the loop running only on the numbers that it needs to and not having to check a string. It also does not introduce any pre-computed magic numbers.

OP Time taken:    0.00325608253479
Elias Time taken: 0.0021388530731201
Pauls Time taken: 0.0016560554504395


The code I used for the benchmark was:

// OP version
$start = microtime(true);
for ($a=1;$a<5;++$a)
    for ($b=1;$b<5;++$b)
        for ($c=1;$c<5;++$c)
            for ($d=1;$d<5;++$d)
                echo $a, $b, $c, $d, PHP_EOL;
                //comma's are more efficient then dots here
echo 'OP Time taken: ', microtime(true) - $start, PHP_EOL;

// Elias version
$start = microtime(true);
for($i=156;$i<625;++$i)
{
    $str = base_convert($i, 10, 5);
    if (strstr($str, '0') === false)
        echo $str, PHP_EOL;
}
echo 'Elias Time taken: ', microtime(true) - $start, PHP_EOL;

// Paul Version
$start = microtime(true);
$end = base_convert(3333, 4, 10) + 1;

for($i = 0; $i < $end; ++$i)
{
    echo 1111 + base_convert($i, 10, 4), PHP_EOL;
}
echo 'Pauls Time taken: ', microtime(true) - $start, PHP_EOL;


Overkill

If you do want to generalise the output you could create a function such as the one below (This is complete overkill, but demonstrates that the solution does not come from magic numbers.):

/**
 * Count in a base that has been offset from zero.
 *
 * @param int $digits The number of digits to use.
 * @param int $offset The offset from zero that the base uses.
 * @param int $base   The base to count in.
 */
function countInAnOffsetBase($digits, $offset, $base)
{
    $end = pow($base, $digits);
    $offset = str_repeat($offset, $digits);

    for ($i = 0; $i < $end; ++$i)
    {
        echo $offset + base_convert($i, 10, $base), PHP_EOL;
    }
}

countInAnOffsetBase(4, 1, 4);
// Or try 3, 4, 5 to count from 444 to 888 using the digits 4, 5, 6, 7, 8


This is almost as quick as the version I proposed at the top of this post.

Note

The function and method above assume that the number will not roll over the base (for base 10, the offset + base must be less than 10). An even more general function would specify the output base:

/**
 * Count in a base that has been offset from zero.
 *
 * @param int $digits The number of digits to use.
 * @param int $offset The offset from zero that the base uses.
 * @param int $base   The base to count in.
 * @param int $base   The base to output the count in.
 */
function countInAnOffsetBase($digits, $offset, $base, $outputBase)
{
    if ($offset + $base > $outputBase)
    {
        throw new OverflowException(
            'offset and base cannot be greater than outputBase otherwise ' .
            'the value will overflow.');
    }

    $end = pow($base, $digits);
    $offset = intval(str_repeat($offset, $digits), $outputBase);

    for ($i = 0; $i < $end; ++$i)
    {
        echo base_convert(
            $offset + intval(base_convert($i, 10, $base), $outputBase),
            10,
            $outputBase),
            PHP_EOL;
    }
}

Code Snippets

$end = pow(4, 4);

for($i = 0; $i < $end; ++$i)
{
    echo 1111 + base_convert($i, 10, 4), PHP_EOL;
}
OP Time taken:    0.00325608253479
Elias Time taken: 0.0021388530731201
Pauls Time taken: 0.0016560554504395
// OP version
$start = microtime(true);
for ($a=1;$a<5;++$a)
    for ($b=1;$b<5;++$b)
        for ($c=1;$c<5;++$c)
            for ($d=1;$d<5;++$d)
                echo $a, $b, $c, $d, PHP_EOL;
                //comma's are more efficient then dots here
echo 'OP Time taken: ', microtime(true) - $start, PHP_EOL;

// Elias version
$start = microtime(true);
for($i=156;$i<625;++$i)
{
    $str = base_convert($i, 10, 5);
    if (strstr($str, '0') === false)
        echo $str, PHP_EOL;
}
echo 'Elias Time taken: ', microtime(true) - $start, PHP_EOL;

// Paul Version
$start = microtime(true);
$end = base_convert(3333, 4, 10) + 1;

for($i = 0; $i < $end; ++$i)
{
    echo 1111 + base_convert($i, 10, 4), PHP_EOL;
}
echo 'Pauls Time taken: ', microtime(true) - $start, PHP_EOL;
/**
 * Count in a base that has been offset from zero.
 *
 * @param int $digits The number of digits to use.
 * @param int $offset The offset from zero that the base uses.
 * @param int $base   The base to count in.
 */
function countInAnOffsetBase($digits, $offset, $base)
{
    $end = pow($base, $digits);
    $offset = str_repeat($offset, $digits);

    for ($i = 0; $i < $end; ++$i)
    {
        echo $offset + base_convert($i, 10, $base), PHP_EOL;
    }
}

countInAnOffsetBase(4, 1, 4);
// Or try 3, 4, 5 to count from 444 to 888 using the digits 4, 5, 6, 7, 8
/**
 * Count in a base that has been offset from zero.
 *
 * @param int $digits The number of digits to use.
 * @param int $offset The offset from zero that the base uses.
 * @param int $base   The base to count in.
 * @param int $base   The base to output the count in.
 */
function countInAnOffsetBase($digits, $offset, $base, $outputBase)
{
    if ($offset + $base > $outputBase)
    {
        throw new OverflowException(
            'offset and base cannot be greater than outputBase otherwise ' .
            'the value will overflow.');
    }

    $end = pow($base, $digits);
    $offset = intval(str_repeat($offset, $digits), $outputBase);

    for ($i = 0; $i < $end; ++$i)
    {
        echo base_convert(
            $offset + intval(base_convert($i, 10, $base), $outputBase),
            10,
            $outputBase),
            PHP_EOL;
    }
}

Context

StackExchange Code Review Q#49522, answer score: 4

Revisions (0)

No revisions yet.