Simplest way to assign numeric ranges to values in php? - php

I'm trying to create a more simple version of if/else php statements because there are very many of them, so I want to make the code shorter and simpler.
I thought since each outputted value will equal a number going up from 0, then maybe I can do an array, but I'm also open to other solutions.
In human language, I want the value to be one of these numbers between 0 and 4 if it is within different parts of a range of numbers:
// 4 = 10,000+
// 3 = 1000-9,999
// 2 = 500-999
// 1 = 100-499
// 0 = <100
e.g. if $aa is 547, then $zz should equal the numeric value of 2, because it falls within 500-1000.
Now using if/else it's possible, and that works, but I want to make it shorter. Here it is in if/else statements:
if ( $aa >= 10000 ) {
$zz = 4;
} else if ( $aa >= 1000 && $aa < 10000 ) {
$zz = 3;
} else if ( $aa >= 500 && $aa < 1000 ) {
$zz = 2;
} else if ( $aa >= 100 && $aa < 500 ) {
$zz = 1;
} else {
$zz = 0;
}
Now I tried making an array but having trouble figuring out how to do this. Here's what I started with:
$b4 = $aa >= 10000;
$b3 = $aa >= 1000 && $aa < 10000;
$b2 = $aa >= 500 && $aa < 1000;
$b1 = $aa >= 100 && $aa < 500;
$b0 = $aa < 100;
$b_val = array( $b0, $b1, $b2, $b3, $b4 );
This is terribly wrong I know. It's not going to work like that, but maybe there's a way to make it work.
I thought of using switch, but it seems switch isn't designed for this, even though it can be done, and it isn't any shorter. I thought of using ternary, but that doesn't seem shorter either, unless you know how.
How to get a short, crisp, clean code to shorten the if/else statement on ranges of numbers?

You can use a nested ternary working smallest to largest
$zz = $aa < 100 ? 0 : ($aa < 500 ? 1 : ($aa < 1000 ? 2 : ($aa < 10000 ? 3 : 4)));
Demo ~ https://3v4l.org/MnqQv
I'd argue it's not as readable and therefore not as good as a plain old if..elseif..else or a function that returns at the appropriate logic branch.
function getRangeIndex($aa) {
if ($aa < 100) return 0;
if ($aa < 500) return 1;
if ($aa < 1000) return 2;
if ($aa < 10000) return 3;
return 4;
}

Related

Two Sum problem but target is within a range

I've encountered a problem similar to the old two sum problem but instead of solving for a value, it needs to be within a range, and I'm not sure how to efficiently approach this. Here is a simplified version of my problem:
Given an array of integers in order of preference, find the first two integers whose sum lies between the range of X and Y s.t. X <= sum <= Y (where X < Y and are known, i.e. arbitrarily X=20 and Y=40).
I've done a brute force approach using a for loop, but I'm unsure if this is the most performant solution. I've considered using a hash table, but I don't know how to apply it.
note: by order of preference I mean, the return the first two integers that fulfill this criteria
You could use the binary search method of resolving the 2 sum problem, and tweak your binary search function to search within a range. Something like this:
$arr = [1,2,4,6,8,14,15,17];
print_r(first_sum_in_range($arr, 25, 40));
function first_sum_in_range($array, $min, $max){
foreach ($array as $k=>$a) {
$b = binary_search_range($array, $a, $min, $max);
if ($b !== false) {
return [$a,$b];
}
}
}
function binary_search_range($array, $a, $min, $max) {
$top = sizeof($array) -1;
$bot = 0;
while($top >= $bot)
{
$p = floor(($top + $bot) / 2);
if ($a+$array[$p] < $min) $bot = $p + 1;
elseif ($a+$array[$p] > $max) $top = $p - 1;
else return $array[$p];
}
return false;
}
OUTPUT:
Array
(
[0] => 8
[1] => 17
)
Add each element to a tree map with key as element and value as list of indices where that element occurs.
While adding element to tree map check if there is a submap whose keys range from X - current_element to Y - current_element both inclusive. If you have a submap your answer is [curr_element, A[first_index_of_submap's value_of_first_key] ]
Maybe this is the brute force method you've already tried, but I think this is the simplest way.
Starting with a subset of the first two elements, iterate subsets of increasing size comparing the sum of the value of each element in the subset and the value of the last element. When you find a sum within the range, you're done.
This will find the first pair of numbers within the range based on the definition of "first" being "the pair with the lowest maximum index".
function findFirstSumInRange(int $min, int $max, array $values = []): array
{
for ($b = 1, $n = count($values); $b < $n; $b++) {
for ($a = 0; $a < $b; $a++) {
if ($min <= ($sum = $values[$a] + $values[$b]) && $sum <= $max) {
return [$values[$a], $values[$b]];
// or return [$a => $values[$a], $b => $values[$b]]; if you need the keys as well
}
}
}
return [];
}
You can make it faster by skipping any values that are already greater than the upper limit of the range.
function findFirstSumInRangeB(int $min, int $max, array $values = []): array
{
for ($b = 1, $n = count($values); $b < $n; $b++) {
if ($values[$b] < $max) { // else these sums will all be > the range because one addend is
for ($a = 0; $a < $b; $a++) {
if ($values[$a] < $max && $min <= ($sum = $values[$a] + $values[$b]) && $sum <= $max) {
return [$a => $values[$a], $b => $values[$b]];
}
}
}
}
return [];
}
Regarding "the most performant solution", I'd prefer to go for simplicity rather than optimizing for performance unless the performance is causing problems. Just my opinion.

List of numbers until n divisible by A or B but not divisible by C

I have three integers: A, B, C
I want to print all integers from 1 to range which are divisible by A or B but not by C.
My code
for($n=0; $n < $range; $n++){
if(($n < $a && $n < $b) || ($n % $c == 0)){
return [];
}
if(($n % $a == 0 || $n % $b == 0) && ($n % $c > 0)){
$outputArr[] = $n;
}
}
Is there any more efficient way to do this?
You can speed this up but it is more complicated, especially if you must print these out in order. Here is a method that doesn't print them out in order.
First write a greatest common divisor (gcd) function in PHP, and then write a least common multiple (lcm) function that uses the gcd function. Compute m = lcm(a, b). Iterate over multiples of a and print them out if they are not divisible by c. Next, iterate over multiples of b and print them out if they are not divisible by m or c.
Other optimizations along these lines are possible. For example, you can precompute the multiples of a or b that are not multiples of m and store them in an array. This works if m is not too large, division is more expensive than array access in PHP, and range is significantly larger than m.
PHP version 7 or higher is so fast when only integer operations are used that micro-optimizations are no longer needed.
$res = [];
$a = 9;
$b = 13;
$c = 26;
$range = 10000;
for($n=$a; $n <= $range; $n += $a){
if($n%$c != 0) $res[] = $n;
}
for($n=$b; $n <= $range; $n += $b){
if($n%$c != 0) $res[] = $n;
}
$res = array_unique($res);
sort($res);
This example takes about 1 millisecond to calculate the 1411 values on my 8-year-old system. This time for the presentation of the result is several times greater.
I would use range() and array_filter().
$range = 20;
$A = 2;
$B = 3;
$C = 9;
$nums = array_filter(range(1, $range), function ($x) use ($A, $B, $C) {
return (!($x % $A) || !($x % $B)) && $x % $C;
});
var_dump($nums);
Here is a more optimized solution, that also works efficient when a and b are large. You can simply run through the multiples of a and b:
for($na=$a, $nb=$b; $na <= $range || $nb <= $range; ){
if ($na <= $nb) {
if ($na % $c != 0)
$outputArr[] = $na;
if ($na == $nb)
$nb += $b;
$na += $a;
} else {
if ($nb % $c != 0)
$outputArr[] = $nb;
$nb += $b;
}
}
Each output number is only generated once, and already in the desired order.
If you are afraid the modulo test is slow, you could also have a next multiple of c running along, but that looks like too much overhead.

combinations without duplicates using php

I need all possible combinations in math sense (without duplicates) where n=30 and k=18
function subcombi($arr, $arr_size, $count)
{
$combi_arr = array();
if ($count > 1) {
for ($i = $count - 1; $i < $arr_size; $i=$i+1) {
$highest_index_elem_arr = array($i => $arr[$i]);
foreach (subcombi($arr, $i, $count - 1) as $subcombi_arr)
{
$combi_arr[] = $subcombi_arr + $highest_index_elem_arr;
}
}
} else {
for ($i = $count - 1; $i < $arr_size; $i=$i+1) {
$combi_arr[] = array($i => $arr[$i]);
}
}
return $combi_arr;
}
function combinations($arr, $count)
{
if ( !(0 <= $count && $count <= count($arr))) {
return false;
}
return $count ? subcombi($arr, count($arr), $count) : array();
}
$numeri="01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30";
$numeri_ar=explode(".",$numeri);
$numeri_ar=array_unique($numeri_ar);
for ($combx = 2; $combx < 19; $combx++)
{
$combi_arr = combinations($numeri_ar, $combx);
}
print_r($combi_arr);
It works but it terminates with an out of memory error, of course, number of combinations is too large.
Now I do not need exactly all the combinations. I need only a few of them.
I'll explain.
I need this work for a statistical study over Italian lotto.
I have the lotto archive in this format saved in $archivio array
...
35.88.86.03.54
70.72.45.18.09
55.49.35.30.43
15.52.49.41.72
74.26.54.77.90
33.14.56.42.11
08.79.41.01.52
82.33.32.83.43
...
A full archive is available here
https://pastebin.com/tut6kFXf
newer extractions are on top.
I tried (unsuccessfully) to modify the function to do this
for each 18 numbers combination found by the function combinations, the function should check if there are min. 3 numbers in one of the first 30 rows of $archivio. If "yes", the combination must not be saved in combination array, this combination has no statistical value for my need. If "no", the combination must be saved in combination array, this combination has great statistical value for my need.
In this way the total combinations will be no more than some hundred or thousand and I will avoid the out of memory and I'll have what I need.
The script time will be surely long but there should not be out of memory using the way above.
Anyone is able to help me in this ?
Thank you

How can I keep a number in specific range?

I have a function like this:
<?php
function keepInRange($n){
$min = 5;
$max = 15;
if ( $n < $min ) {
$res = $min;
} elseif ( $n > $max ) {
$res = $max;
} else {
$res = $n;
}
return $res;
}
It always returns a number between $min and $max. It works as well, but doesn't seem clean an professional to me. I think it can be better (without those conditions). Any idea how can I make it shorter and cleaner?
If you are trying to make it shorter (and probably cleaner) and also removing those if statements, you can use max() and min() functions:
function keepInRange($n){
$min = 5;
$max = 15;
return max(min($max, $n), $min);
}
Also as #admcfajn mentioned, you can pass $min and $max as arguments to make the function more flexible:
function keepInRange($n, $min = 5, $max = 15){
return max(min($max, $n), $min);
}
Definitely its not the best way, You can use Rand() to generate numbers between a particular range.
Try this
print rand(10, 30) . "";​
It'll generate and print a random number between 10 and 30 (10 and 30 are included).
I think it can be better (without those conditions). Any idea how can I make it shorter and cleaner?
Well if you want it without explicit conditions, then you could simply use min() and max():
function keepInRange2($n) {
$min = 5;
$max = 15;
return max($min, min($max, $n));
}
The order of the functions and parameters might seem odd at first glance - but since you want 15 to be the maximum value, we need to get the minimum of 15 and whatever the value is first - and then vice versa for the minimum 5.
This can be achieved cleaner using ternary operator.
function keepInRange($n){
$min = 5;
$max = 15;
return ( $n < $min ) ? $min : (( $n > $max ) ? $max : $n);
}

How to generate random numbers to produce a non-standard distributionin PHP

I've searched through a number of similar questions, but unfortunately I haven't been able to find an answer to this problem. I hope someone can point me in the right direction.
I need to come up with a PHP function which will produce a random number within a set range and mean. The range, in my case, will always be 1 to 100. The mean could be anything within the range.
For example...
r = f(x)
where...
r = the resulting random number
x = the mean
...running this function in a loop should produce random values where the average of the resulting values should be very close to x. (The more times we loop the closer we get to x)
Running the function in a loop, assuming x = 10, should produce a curve similar to this:
+
+ +
+ +
+ +
+ +
Where the curve starts at 1, peeks at 10, and ends at 100.
Unfortunately, I'm not well versed in statistics. Perhaps someone can help me word this problem correctly to find a solution?
interesting question. I'll sum it up:
We need a funcion f(x)
f returns an integer
if we run f a million times the average of the integer is x(or very close at least)
I am sure there are several approaches, but this uses the binomial distribution: http://en.wikipedia.org/wiki/Binomial_distribution
Here is the code:
function f($x){
$min = 0;
$max = 100;
$curve = 1.1;
$mean = $x;
$precision = 5; //higher is more precise but slower
$dist = array();
$lastval = $precision;
$belowsize = $mean-$min;
$abovesize = $max-$mean;
$belowfactor = pow(pow($curve,50),1/$belowsize);
$left = 0;
for($i = $min; $i< $mean; $i++){
$dist[$i] = round($lastval*$belowfactor);
$lastval = $lastval*$belowfactor;
$left += $dist[$i];
}
$dist[$mean] = round($lastval*$belowfactor);
$abovefactor = pow($left,1/$abovesize);
for($i = $mean+1; $i <= $max; $i++){
$dist[$i] = round($left-$left/$abovefactor);
$left = $left/$abovefactor;
}
$map = array();
foreach ($dist as $int => $quantity) {
for ($x = 0; $x < $quantity; $x++) {
$map[] = $int;
}
}
shuffle($map);
return current($map);
}
You can test it out like this(worked for me):
$results = array();
for($i = 0;$i<100;$i++){
$results[] = f(20);
}
$average = array_sum($results) / count($results);
echo $average;
It gives a distribution curve that looks like this:
I'm not sure if I got what you mean, even if I didn't this is still a pretty neat snippet:
<?php
function array_avg($array) { // Returns the average (mean) of the numbers in an array
return array_sum($array)/count($array);
}
function randomFromMean($x, $min = 1, $max = 100, $leniency = 3) {
/*
$x The number that you want to get close to
$min The minimum number in the range
$max Self-explanatory
$leniency How far off of $x can the result be
*/
$res = [mt_rand($min,$max)];
while (true) {
$res_avg = array_avg($res);
if ($res_avg >= ($x - $leniency) && $res_avg <= ($x + $leniency)) {
return $res;
break;
}
else if ($res_avg > $x && $res_avg < $max) {
array_push($res,mt_rand($min, $x));
}
else if ($res_avg > $min && $res_avg < $x) {
array_push($res, mt_rand($x,$max));
}
}
}
$res = randomFromMean(22); // This function returns an array of random numbers that have a mean close to the first param.
?>
If you then var_dump($res), You get something like this:
array (size=4)
0 => int 18
1 => int 54
2 => int 22
3 => int 4
EDIT: Using a low value for $leniency (like 1 or 2) will result in huge arrays, since testing, I recommend a leniency of around 3.

Categories