patternphpMinor
Better way to achieve looped output result
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:
I have wrote following loop
Is there any faster or better way to achieve above result?
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 4I 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:
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.
The code I used for the benchmark was:
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.):
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:
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.0016560554504395The 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, 8This 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.