Faster way to check if integer is in any of ranges? - php

I wonder if there is another way to shorten this ? Here is the example im looking for to do same thing just shorter.
if($c <= 100){
echo 'A';
}elseif($c <= 200 && $c > 100){
echo 'B';
}elseif($c <= 300 && $c > 200){
echo 'C';
}elseif($c <= 400 && $c > 300){
echo 'D';
}elseif($c <= 500 && $c > 400){
echo 'E';
}elseif($c <= 600 && $c > 500){
echo 'F';
}elseif($c <= 700 && $c > 600){
echo 'Z';
}elseif($c <= 800 && $c > 700){
echo 'H';
}elseif($c <= 900 && $c > 800){
echo 'I';
}elseif($c < 1000 && $c > 900){
echo 'K';
}elseif($c <= 1100 && $c > 1000){
echo 'L';
}elseif($c <= 1200 && $c > 1100){
echo 'M';
}elseif($c < 1300 && $c > 1200){
echo 'N';
}elseif($c <= 1400 && $c > 1300){
echo 'O';
}elseif($c <= 1500 && $c > 1400){
echo 'P';
}elseif($c <= 1600 && $c > 1500){
echo 'Q';
}elseif($c <= 1700 && $c > 1600){
echo 'R';
}elseif($c <= 1800 && $c > 1700){
echo 'S';
}elseif($c <= 1900 && $c > 1800){
echo 'T';
}elseif($c <= 2000 && $c > 1900){
echo 'V';
}elseif($c <= 2100 && $c > 2000){
echo 'X';
}else{
echo 'AA';
}

faster and shorter - no*, but you can make it more flexible and elegant
function find_range($n, $ranges) {
foreach($ranges as $key => $range)
if($n >= $range[0] && $n <= $range[1])
return $key;
return false;
}
$ages = array(
'baby' => array(0, 1),
'child' => array(2, 13),
'teen' => array(14, 19),
'adult' => array(20, 59),
'senior' => array(60, 100)
);
var_dump(find_range(20, $ages));
(* assuming ranges are arbitrary. If we know more about ranges, for example, that they are sorted, or always intersect, or follow some formula, we can find a better algorithm).

You could write an auxiliary function that could take an array full of anonymous functions, I guess. It would work well if your code to execute was nice and simple. Otherwise, if you're on PHP5.3 you could use proper lambdas to do more complex things. That would make it neater.
function integer_in_ranges($integer, $array) {
foreach ($array as $range => $function) {
if ($integer < $range) {
$results[] = $function();
}
}
return $results;
}
integer_in_ranges($c, array(
101 => create_function('', "execute_function(); return TRUE;"),
202 => create_function('', "execute_other_function(); return TRUE;"),
// ...
));

Not builtin, but because your plan is not that complex
switch ((int) (($c - 1) / 100)) {
case 0: /* action here */ break;
case 1: /* action here */ break;
case 2: /* action here */ break;
case 3: /* action here */ break;
default: /* action here */ break;
}
Another solution which works with arbitrary limits. I assume, that there is an upper limit
$c; // value to test
$ranges = array(101,201,199); // and so on
$index = 0;
while ($ranges[$index] >= $c) { $index++; }
// Do something useful with "$index"

Is this really your code? You always want to return AA if $c is a multiple of 100?
The fastest way to get the same result with the least amount of code would be....
$res=array('A','B','C',....);
if (($c % 100) && ($c>0) && ($c<2100)) {
echo $res[(integer)($c/100)];
} else {
echo 'AA';
}
But I would use a solution like that from sterofrog.

If the number of branches is large, you might want to look at a binary search tree
This will reduce the maximum number of comparisons that any specific branch will need to be tested against (although the minimum number of tests will also increase).
e.g. The below case there is a minimum of 2 tests and a maximum of 3 tests. In your example above, there is a maximum of 4 tests.
if ($c< 202)
{
if($c < 101){
// action 101 here
}
else {
// action 202 here
}
}
else {
if($c < 303){
// action here
}
elseif($c < 404){
// action here
}
Edit : Depends on what you mean by 'shorten'. As per your title 'faster' am assuming performance.
Edit (you've updated the List) : Case in Point 'A' needs only one comparison test, whereas the 'AA' fall through will have failed ~25 tests. Assuming an even distribution of data across all bins, the average number of tests would be ~12 ('L') whereas in a binary tree formation, the most checks would be Log2(N) + 1 - i.e. 6 tests would cover 32 branches. Unfortunately in your case, there are gaps in the data on the even '100's. If you exclude these (i.e. if value MOD 100 == 0 then fall through to 'AA') then you can do just a one sided test for the rest of the permutations.

Related

Improving summing algorithm for 2048 game developed with PHP

I challenged myself to improve my skills by doing some individual projects and for now I am stuck with the 2048.
I have the text file which stores the present values of the game.
128 64 32 16 32 8 8 8 16 4 4 4 8 2 2 2
<?php
$logs = file("logs-2048.txt");
if ($_SERVER["REQUEST_METHOD"] == "POST") {
file_put_contents('logs-2048.txt', '');
$tableValues = [];
$arrayLine = explode(" ", $logs[0]);
for($g=0; $g<4; $g++) {
$newLine = [];
for($h=0; $h<4; $h++) {
array_push($newLine, htmlspecialchars($arrayLine[$g*4+$h]));
}
array_push($tableValues, $newLine);
}
$direction = $_POST["action-joueur"];
$string = "";
if($direction == "^") {
for($g = 0; $g < 3; $g++) {
for ($h = 0; $h < 4; $h++) {
for ($i = 0; $i < 3; $i++) {
if ($tableValues[$i][$h] == $tableValues[$i+1][$h] || $tableValues[$i][$h] == 0) {
$tableValues[$i][$h] += $tableValues[$i+1][$h];
$tableValues[$i+1][$h] = 0;
}
}
}
}
} else if ($direction == "v") {
//Almost simillar stuff as for ^
} else if ($direction == "<") {
//Almost similar stuff as for ^
} else if ($direction == ">") {
//Almost similar stuff as for ^
}
$arrayEmpty = [];
for($g = 0; $g < 4; $g++) {
for($h = 0; $h < 4; $h++) {
if($tableValues[$g][$h] == 0) {
array_push($arrayEmpty, [$g, $h]);
}
}
}
$findRand = mt_rand(0, count($arrayEmpty)-1);
$tableValues[$arrayEmpty[$findRand][0]][$arrayEmpty[$findRand][1]] = 2;
for($g = 0; $g < 4; $g++) {
for ($h = 0; $h < 4; $h++) {
$string = $string.$tableValues[$g][$h]." ";
}
}
file_put_contents("logs-2048.txt", $string);
}
?>
The first loop is to parse the array that I take from the file, and convert it to a 2 dimensional array(table).
The problem is in the next 4 nested loops which does the summing, I can't figure how can I do other way then just use another loop(first one for each nested loop) which goes 3 times back if some numbers were stuck somewhere adding themselves (EX. 2 2 2 2 in first column => 4 2 2 0 => 4 4 0 0 => 8 0 0 0).
I am 100% sure that the algorithms for summing is terribly inefficient, but I can't figure out what solution I can provide to make it faster and more intuitive, then have 4 for's... I tried also to look at different resources on internet but it's pretty messed up for me because I am starting with php.
Yes, I know that php isn't the best way to do, I'll do myself with
Javascript, but I need it for learning, any ideas or tips to improve the algorithm will be greatly appreciated.

PHP - Number of units between two numbers

I'm trying to find the number of units between 2 numbers that are under zero between 0 and a limit and over that limit. Here is my function. It works fine until I have to work with some huge numbers which takes a lot of time to process. I am trying to find a way to execute this code without using a loop.
public function getBetween($num1, $num2) {
$limit = 500000;
$array = array(0,0,0);
if ($num1 >= $num2) {
$low = $num2;
$high = $num1;
} else {
$low = $num1;
$high = $num2;
}
for($i=$low; $i < $high; $i++) {
if ($i < 0) {
$array[0]++;
} elseif ($i >= 0 && $i < $limit) {
$array[1]++;
} else {
$array[2]++;
}
}
return $array;
}
I have started to split my loop into elseif statements but this is getting messy really quick and I will also have to eventually be able to set more than one limit which will become impossible to use.
if ($low < 0 && $high < 0) {
} elseif ($low < 0 && $high >= 0 && $high < $limit) {
} elseif ($low < 0 && $high >= $limit) {
} elseif ($low >= 0 && $low < $limit && $high < 0) {
} elseif ($low >= 0 && $low < $limit && $high >= 0 && $high < $limit) {
} elseif ($low >= 0 && $low < $limit && $high >= $limit) {
} elseif ($low >= $limit && $high < 0) {
} elseif ($low >= $limit && $high >= 0 && $high < $limit) {
} elseif ($low >= $limit && $high >= $limit) {
}
I am trying to find a clean way to do it. Any ideas?
EDIT
Here is an example of the array I'm trying to get.
If my limit was 500, $num1 = -100 and $num2 = 700 i would get the array
$array[0] = 100
$array[1] = 500
$array[2] = 200
I didn't test it (didn't run a PHP script but I tried it "manually" with a few examples).
You still have loops, but only one iteration per limit (instead of one per unit).
// Example datas
$limits = array(0, 500, 800);
$low = -100;
$high = 1000;
$splittedResults = array();
// Get total of units
$totalUnits = abs($high - $low);
$totalCounted = 0;
foreach($limits as $limit) {
if ($low > $limit) {
// Nothing under the limit
$nbUnderLimit = 0;
} elseif($high < $limit) {
// Both values under the limit
$nbUnderLimit = $totalUnits;
} else {
// $low under the limit and $high over it
$nbUnderLimit = abs($limit - $low);
}
// Here we know how much units are under current limit in total.
// We want to know how much are between previous limit and current limit.
// Assuming that limits are sorted ascending, we have to remove already counted units.
$nbBetweenLimits = $nbUnderLimit - $totalCounted;
$splittedResults[] = $nbBetweenLimits;
$totalCounted += $nbBetweenLimits;
}
// Finally, number of units that are over the last limit (the rest)
$splittedResults[] = $totalUnits - $totalCounted;
You could create an array of the numbers with range() and use array_filter
$count = sizeof(array_filter (range(0,800), function($value){ return ($value > 500); }));
And one for < as well etc.
You only need to define range array once, separately.

Fix for convert fraction short PHP snippet, issue with calculation converting a fraction to more readable format

Here is the problem, when it encounters fractions like: 300/10 instead of giving a result of "30"
the following code gives me: 1/0
$tokens = explode('/', $value);
while ($tokens[0] % 10 == 0) {
$tokens[0] = $tokens[0] / 10;
$tokens[1] = $tokens[1] / 10;
}
if ($tokens[1] == 1) {
return $tokens[0].' s';
} else {
return '1/'.floor(1/($tokens[0]/$tokens[1])).' s';
// return $tokens[0].'/'.$tokens[1].' s';
}
thanks
You should change the line while($tokens[0] % 10 === 0 && $tokens[1] % 10 === 0) { to while($tokens[0] % 10 === 0 && $tokens[1] % 10 === 0) {.
And the line return '1/'.floor(1/($tokens[0]/$tokens[1])).' s'; is not reliable.
If you want to reduce fractions, try this function:
function reduceFraction($fraction) {
sscanf($fraction, '%d/%d %s', $numerator, $denominator, $junk);
// TODO: validation
if( $denominator === null ) {
return (string)$numerator;
}
if( $numerator === $denominator ) {
return 1;
}
$max = max(array($numerator, $denominator));
for($i = 1; $i < $max; ++$i) {
if( $denominator % $i === 0 && $numerator % $i === 0) {
$common = $i;
}
}
if( $denominator === $common ) {
return (string)($numerator / $common);
}
return ($numerator / $common) . '/' . ($denominator / $common);
}
You could use it like this:
reduceFraction('300/10') . ' s';
It's also possible to generalize more the function for chained fractions (eg: '300/100/10'). I can send an implementation of it if you wish.
tell me why the "while ($tokens[0] % 10 == 0 && $tokens[1] % 10 ==0)"
would be better to use than just "while ($tokens[0] % 100 == 0)" since
both methods seem to work ok
If you try to use the string "3000/10" as an argument for each implementation, the one with while ($tokens[0] % 10 == 0 && $tokens[1] % 10 ==0) will return 300 s, and the other with while ($tokens[0] % 100 == 0) will return 1/0 s.
If you use the while ($tokens[0] % 100 == 0) method, the loop iterations are:
$tokens[0] = 3000 / 10 = 300;
$tokens[1] = 10 / 10 = 10;
$tokens[0] = 30 / 10 = 30;
$tokens[1] = 10 / 1 = .1;
Stopped because 30 % 100 != 0.
Since the $token[1] is not 1, it does not return "30 s".
1/30 is less than zero (0.0333...), thus floor(1/30) = 0. That's why it returns "1/0 s".
If you use the while ($tokens[0] % 10 == 0 && $tokens[1] % 10 == 0) method, the loop iterations are:
$tokens[0] = 3000 / 10 = 300;
$tokens[1] = 10 / 10 = 1;
Stopped because 1 % 10 != 0.
Since the $token[1] is not 1, it returns "30 s".
It is better because it will work with more inputs.
But I recommend you to use the "reduceFraction" function that I implemented.
It uses the maximum common denominator technique to reduce functions.
echo reduceFraction('3000/10'); outputs "300".
echo reduceFraction('300/10'); outputs "30".
echo reduceFraction('30/10'); outputs "3".
echo reduceFraction('3/10'); outputs "3/10".
echo reduceFraction('3/3'); outputs "1".
echo reduceFraction('222/444'); outputs "1/2".
echo reduceFraction('444/222'); outputs "2".

Why does this return 1 number and then stops the loop? PHP

I'm a beginner at PHP, so my code might not be efficient or good.
Why does this code return 1 number and then stops the loop? It's supposed to stop the loop when "the dice" rolled two of every number (1,2,3,4,5,6). But now it stops after randomly generating 1 number..
<?php
$sixCount = 0;
$fiveCount = 0;
$fourCount = 0;
$threeCount = 0;
$twoCount = 0;
$oneCount = 0;
$rollCount = 0;
do{
$roll = rand(1,6);
$rollCount++;
if($roll == 6){
$sixCount++;
echo "6";
} else if($roll == 5){
$fiveCount++;
echo "5";
} else if($roll == 4){
$fourCount++;
echo "4";
} else if($roll == 3){
$threeCount++;
echo "3";
} else if($roll == 2){
$twoCount++;
echo "2";
} else {
$oneCount++;
echo "1";
}
} while($sixCount < 3 && $sixCount > 1 && $fiveCount < 3 && $fiveCount > 1 && $fourCount < 3 && $fourCount > 1 && $threeCount < 3 && $threeCount > 1 && $twoCount < 3 && $twoCount > 1 && $oneCount < 3 && $oneCount > 1);
echo "<br />It took {$rollCount} rolls!";
?>
This is an exercise from Codecademy.com!
Thanks,
Jesper (New at Stackoverflow!)
After first execution of loop, you cannot have $sixCount > 1 && $fiveCount > 1, among other conditions.
After first roll, suppose it's 3, your variables are:
$sixCount = 0;
$fiveCount = 0;
$fourCount = 0;
$threeCount = 1;
$twoCount = 0;
$oneCount = 0;
It doesn't suit while conditions, cuz, for example, $sixCount > 1 is false and other vars too.
The while expression says:
while ($sixCount < 3 && $sixCount > 1 && $fiveCount < 3 && $fiveCount > 1 ...
If $sixCount is less than 3 and more than 1 that implies $sixCount equals 2. Ditto for the others. So it means "keep looping while $sixCount equals 2 and $fiveCount equals 2 and [all the others equal 2]".
You start with those variables at 0:
$sixCount = 0;
$fiveCount = 0;
...
So the loop condition is not initially met. The loop allows at most one of them to be incremented at most once:
$roll = rand(1, 6);
if ($roll == 6) {
$sixCount++;
echo "6";
} else if ($roll == 5) {
$fiveCount++;
echo "5";
} ...
No matter what number is rolled it is impossible to get any of the counts to 2 by the end of a single roll, and certainly not all of them, so the loop condition will not be met, and the loop will inevitably stop.
It's supposed to stop the loop when "the dice" rolled two of every number (1,2,3,4,5,6)
In that case, the correct condition would be:
while ($sixCount < 2 && $fiveCount < 2 && ...
As others have said, your conditional for the while loop will never be true. Instead, you want to make sure the variables aren't all at 2. Try this instead:
while ($sixCount < 2 || $fiveCount < 2 || $fourCount < 2 || $threeCount < 2 || $twoCount < 2 || $oneCount < 2)
I just adapted your script. It can be a funny game.
It rolls 6 dice times 2 (6 x 2) and then if requirements are not met, it rolls the dice again :
$rollCount = 0;
do{
$sixCount = 0;
$fiveCount = 0;
$fourCount = 0;
$threeCount = 0;
$twoCount = 0;
$oneCount = 0;
$rollCount++;
for ($i= 0; $i< 2 * 6; $i++) {
$roll = rand(1,6);
if($roll == 6){
$sixCount++;
echo "6";
} else if($roll == 5){
$fiveCount++;
echo "5";
} else if($roll == 4){
$fourCount++;
echo "4";
} else if($roll == 3){
$threeCount++;
echo "3";
} else if($roll == 2){
$twoCount++;
echo "2";
} else {
$oneCount++;
echo "1";
}
}
echo "_";
} while(!($sixCount < 3 && $sixCount > 1 && $fiveCount < 3 && $fiveCount > 1 && $fourCount < 3 && $fourCount > 1 && $threeCount < 3 && $threeCount > 1 && $twoCount < 3 && $twoCount > 1 && $oneCount < 3 && $oneCount > 1));
echo "<br />It took {$rollCount} rolls!";
$a = array(
$sixCount,
$fiveCount,
$fourCount,
$threeCount,
$twoCount,
$oneCount);
echo '<pre>';
print_r($a);
echo '</pre>';
Giving this king of output :
262225451666_535451252543_666153663214_652652635413_522615315213_412123422526_113553235335_255616351453_124215216465_112544353243_161145351612_522462262355_114331531645_563664155335_455623424146_233336226515_213136514365_646344361534_445325236533_423153546564_324466143565_422464136444_631511342612_516266141216_613556242333_351541131651_554665566244_261433652145_
It took 28 rolls!
Array
(
[0] => 2
[1] => 2
[2] => 2
[3] => 2
[4] => 2
[5] => 2
)

Make an if statement shorter

Hay all, I have an if statement. How could I make this as short as possible?
if ( $a < 0 ){
if( $a > -2 ){
echo "standard";
}elseif( $a <= -2 && $a > -4 ){
echo "thin";
}elseif( $a <= -4 ){
echo "super thin";
}
}else{
if( $a < 2 ){
echo "standard";
}
if( $a > 2.01 && $a <= 4){
echo "thin";
}
if( $a > 4.01 && $a <= 8.00){
echo "super thin";
}
}
EDIT: basically $a will be any number (positive or negative) and we match it like this.
If the value is
+0.00 to +/- 2.00 – Standard
+/- 2.25 to +/- 4.00 – Thin
+/- 4.25 to +/- 8.00 – Super Thin
Extra marks for anyone who knows what this might be used for :)
You can shorten it by using the absolute value:
$b = abs($a);
if ($b <= 2) {
echo "standard";
} else if ($b <= 4) {
echo "thin";
} else if ($b <= 8) {
echo "super thin";
}
What about this:
$a = abs($a);
if($a<=2) echo "standard";
elseif($a<=4) echo "thin";
elseif($a<=8) echo "super thin";
This is about as simple as it gets:
<?php
$b = abs($a);
$ranges = array(
'standard' => 2,
'thin' => 4,
'super thin' => 8
);
foreach($ranges as $range=>$max) {
$size = $range;
if($b > $max) continue;
break;
}
?>
Or you can keep the actual ranges, allow the range ends to be different (rather than just the absolute values), and allow easy inclusion of additional ranges:
$ranges = array(
'standard' => array(-2, 2),
'thin' => array(-4, 4),
'super thin' => array(null,8),
);
foreach($ranges as $key => $range) {
if((is_null($range[0]) || $a => $range[0]) && (is_null($range[1]) || $a <= $range[1])) {
echo $key;
break;
}
}
The first matching range in the list is the one that qualifies.
if ( $a < 0 )
{
if( $a > -2 ) echo "standard";
elseif($a > -4) echo "thin";
else echo "super thin";
}
else
{
if( $a < 2 ) echo "standard";
elseif( $a > 2.01 && $a <= 4) echo "thin";
elseif( $a > 4.01 && $a <= 8.00) echo "super thin";
}
Its always better to use switch rather than else if! It makes the code cleaner and extensible.
switch($a) {
case (-2 < $a):
case ($a < 2):
$r = 'Standard';
break;
case ( 2.25 > $a):
case (-2.25 < $a):
case ($a < 4):
case ($a > -4):
$r = 'Thin';
break;
case ( 4.25 > $a):
case (-4.25 < $a):
case ($a < 8):
case ($a > -8):
$r = 'Super Thin';
break;
default:
$r = 'Out of range';
}
echo $r;
The case expression may be any
expression that evaluates to a simple
type, that is, integer or
floating-point numbers and strings.
Arrays or objects cannot be used here
unless they are dereferenced to a
simple type.
Taken from the documentation at http://us3.php.net/manual/en/control-structures.switch.php

Categories