Highest multiplication with multiple and max number set - php

Not quite sure what to set this title as, or what to even search for. So I'll just ask the question and hope I don't get too many downvotes.
I'm trying to find the easiest way to find the highest possible number based on two fixed numbers.
For example:
The most I can multiply by is, say, 18 (first number). But not going over the resulted number, say 100 (second number).
2 x 18 = 36
5 x 18 = 90
But if the first number is a higher number, the second number would need to be less than 18, like so:
11 x 9 = 99
16 x 6 = 96
Here I would go with 11, because even though the second number is only 9, the outcome is the highest. The second number could be anything as long as it's 18 or lower. The first number can be anything, as long as the answer remains below 100. Get what I mean?
So my question is, how would write this in php without having to use switches, if/then statements, or a bunch of loops? Is there some math operator I don't know about that handles this sort of thing?
Thanks.
Edit:
The code that I use now is:
function doMath($cost, $max, $multiplier) {
do {
$temp = $cost * $multiplier;
if ($temp > $max) { --$multiplier; }
} while ($temp > $max);
return array($cost, $temp, $multiplier);
}
If we look at the 11 * 9 = 99 example,
$result = doMath(11, 100, 18);
Would return,
$cost = 11, $temp = 99, $multiplier = 9
Was hoping there was an easier way so that I wouldn't need to use a loop, being as how there are a lot of numbers I need to check.

If I understood you right, you are looking for the floor function, combining it with the min function.
Both a bigger number c and a smaller number a are part of the problem, and you want to find a number b in the range [0, m] such that a * b is maximal while staying smaller (strictly) than c.
In your example, 100/18 = 5.55555, so that means that 18*5 is smaller than 100, and 18*6 is bigger than 100.
Since floor gets you the integral part of a floating point number, $b = floor($c/$a) does what you want. When a divides c (that is, c/a is an integer already), you get a * b == c.
Now b may be outside of [0,m] so we want to take the smallest of b and m :
if b is bigger than m, we are limited by m,
and if m is bigger than b, we are limited by a * b <= c.
So in the end, your function should be :
function doMath($cost, $max, $multiplier)
{
$div = min($multiplier, floor($max/$cost));
return array($cost, $div * $cost, $div);
}

Related

PHP - Optimize finding closest point in an Array

I have created a script which gets a big array of points and then finds the closest point in 3D-space based on a limited array of chosen points. It works great. However, sometimes I get like over 2 Million points to compare to an array of 256 items so it is over 530 million calculations! Which takes a considerable amount of time and power (taking that it will be comparing stuff like that few times a min).
I have a limited group of 3D coordinates like this:
array (size=XXX)
0 => 10, 20, 30
1 => 200, 20, 13
2 => 36, 215, 150
3 => ...
4 => ...
... // this is limited to max 256 items
Then I have another very large group of, let's say, random 3D coordinates which can vary in size from 2,500 -> ~ 2,000,000+ items. Basically, what I need to do is to iterate through each of those points and find the closest point. To do that I use Euclidean distance:
sq((q1-p1)2+(q2-p2)2+(q3-p3)2)
This gives me the distance and I compare it to the current closest distance, if it is closer, replace the closest, else continue with next set.
I have been looking on how to change it so I don't have to do so many calculations. I have been looking at Voronoi Diagrams then maybe place the points in that diagram, then see which section it belongs to. However, I have no idea how I can implement such a thing in PHP.
Any idea how I can optimize it?
Just a quick shot from the hip ;-)
You should be able to gain a nice speed up if you dont compare each point to each other point. Many points can be skipped because they are already to far away if you just look at one of the x/y/z coordinates.
<?php
$coord = array(18,200,15);
$points = array(
array(10,20,30),
array(200,20,13),
array(36,215,150)
);
$closestPoint = $closestDistance= false;;
foreach($points as $point) {
list($x,$y,$z) = $point;
// Not compared yet, use first poit as closest
if($closestDistance === false) {
$closestPoint = $point;
$closestDistance = distance($x,$y,$z,$coord[0],$coord[1],$coord[2]);
continue;
}
// If distance in any direction (x/y/z) is bigger than closest distance so far: skip point
if(abs($coord[0] - $x) > $closestDistance) continue;
if(abs($coord[1] - $y) > $closestDistance) continue;
if(abs($coord[2] - $z) > $closestDistance) continue;
$newDistance = distance($x,$y,$z,$coord[0],$coord[1],$coord[2]);
if($newDistance < $closestDistance) {
$closestPoint = $point;
$closestDistance = distance($x,$y,$z,$coord[0],$coord[1],$coord[2]);
}
}
var_dump($closestPoint);
function distance($x1,$y1,$z1,$x2,$y2,$z2) {
return sqrt(pow($x1-$x2,2) + pow($y1 - $y2,2) + pow($z1 - $z2,2));
}
A working code example can be found at http://sandbox.onlinephpfunctions.com/code/8cfda8e7cb4d69bf66afa83b2c6168956e63b51e

Controlling likelyhood of randomly generated numbers

If I wanted a random number between one and three I could do $n = mt_rand(1,3).
There is a 33% chance that $n = 1, a 33% chance it's 2, and a 33% chance that it's 3.
What if I want to make it more difficult to get a 3 than a 1?
Say I want a 50% chance that a 1 is drawn, a 30% chance that a 2 is drawn and a 20% chance that a 3 is drawn?
I need a scalable solution as the possible range will vary between 1-3 and 1-100, but in general I'd like the lower numbers to be drawn more often than the higher ones.
How can I accomplish this?
There is a simple explanation of how you can use standard uniform random variable to produce random variable with a distribution similar to the one you want:
https://math.stackexchange.com/a/241543
This is maths.
In your example the just chose a random number between 0 and 99.
Values returned between 0 to 49 - call it 1
Values returned between 50 - 69 - Call it 2
Values returned between 70 - 99 - Call it 3
Simple if statement will do this or populate an array for the distribution required
Assuming a 1 - 10 scale, you can use a simple if statement and have the numbers represent percentages. And just have each if statement set $n to a specific. Only downfall, it isn't universal.
$dummy = mt_rand(1,10);
// represents 50%
if ($dummy <= 5) {
$n = 1;
}
// represents 40%
if ($dummy >= 6 && $dummy <= 9) {
$n = 2;
} else {
// represents 10%
$n = 3;
}

Is there a clever way to do this with pure math

I've got this spot of code that seems it could be done cleaner with pure math (perhaps a logarigthms?). Can you help me out?
The code finds the first power of 2 greater than a given input. For example, if you give it 500, it returns 9, because 2^9 = 512 > 500. 2^8 = 256, would be too small because it's less than 500.
function getFactor($iMaxElementsPerDir)
{
$aFactors = range(128, 1);
foreach($aFactors as $i => $iFactor)
if($iMaxElementsPerDir > pow(2, $iFactor) - 1)
break;
if($i == 0)
return false;
return $aFactors[$i - 1];
}
The following holds true
getFactor(500) = 9
getFactor(1000) = 10
getFactor(2500) = 12
getFactor(5000) = 13
You can get the same effect by shifting the bits in the input to the right and checking against 0. Something like this.
i = 1
while((input >> i) != 0)
i++
return i
The same as jack but shorter. Log with base 2 is the reverse function of 2^x.
echo ceil(log(500, 2));
If you're looking for a "math only" solution (that is a single expression or formula), you can use log() and then take the ceiling value of its result:
$factors = ceil(log(500) / log(2)); // 9
$factors = ceil(log(5000) / log(2)); // 13
I seem to have not noticed that this function accepts a second argument (since PHP 4.3) with which you can specify the base; though internally the same operation is performed, it does indeed make the code shorter:
$factors = ceil(log(500, 2)); // 9
To factor in some inaccuracies, you may need some tweaking:
$factors = floor(log($nr - 1, 2)) + 1;
There are a few ways to do this.
Zero all but the most significant bit of the number, maybe like this:
while (x & x-1) x &= x-1;
and look the answer up in a table. Use a table of length 67 and mod your power of two by 67.
Binary search for the high bit.
If you're working with a floating-point number, inspect the exponent field. This field contains 1023 plus your answer, except in the case where the number is a perfect power of two. You can detect the perfect power case by checking whether the significand field is exactly zero.
If you aren't working with a floating-point number, convert it to floating-point and look at the exponent like in 3. Check for a power of two by testing (x & x-1) == 0 instead of looking at the significand; this is true exactly when x is a power of two.
Note that log(2^100) is the same double as log(nextafter(2^100, 1.0/0.0)), so any solution based on floating-point natural logarithms will fail.
Here's (nonconformant C++, not PHP) code for 4:
int ceillog2(unsigned long long x) {
if (x < 2) return x-1;
double d = x-1;
int ans = (long long &)d >> 52;
return ans - 1022;
}

Get result based on probability distribution

In a browser game we have items that occur based on their probabilities.
P(i1) = 0.8
P(i2) = 0.45
P(i3) = 0.33
P(i4) = 0.01
How do we implement a function in php that returns a random item based on its probability chance?
edit
The items have a property called rarity which varies from 1 to 100 and represents the probability to occcur. The item that occurs is chosen from a set of all items of a certain type. (e.x the given example above represents all artifacts tier 1)
I don't know if its the best solution but when I had to solve this a while back this is what I found:
Function taken from this blog post:
// Given an array of values, and weights for those values (any positive int)
// it will select a value randomly as often as the given weight allows.
// for example:
// values(A, B, C, D)
// weights(30, 50, 100, 25)
// Given these values C should come out twice as often as B, and 4 times as often as D.
function weighted_random($values, $weights){
$count = count($values);
$i = 0;
$n = 0;
$num = mt_rand(0, array_sum($weights));
while($i < $count){
$n += $weights[$i];
if($n >= $num){
break;
}
$i++;
}
return $values[$i];
}
Example call:
$values = array('A','B','C');
$weights = array(1,50,100);
$weighted_value = weighted_random($values, $weights);
It's somewhat unwieldy as obviously the values and weights need to be supplied separately but this could probably be refactored to suit your needs.
Tried to understand how Bulk's function works, and here is how I understand based on Benjamin Kloster answer:
https://softwareengineering.stackexchange.com/questions/150616/return-random-list-item-by-its-weight
Generate a random number n in the range of 0 to sum(weights), in this case $num so lets say from this: weights(30, 50, 100, 25).
Sum is 205.
Now $num has to be 0-30 to get A,
30-80 to get B
80-180 to get C
and 180-205 to get D
While loop finds in which interval the $num falls.

A Project Euler Puzzler (specifically in PHP)

There is another recent Project Euler question but I think this is a bit more specific (I'm only really interested in PHP based solutions) so I'm asking anyway.
Question #5 tasks you with: "What is the smallest number that is evenly divisible by all of the numbers from 1 to 20?"
Now, I have solved it twice. Once very inefficiently and once much more efficiently but I am still far away from an especially sophisticated answer (and I am not especially solid in math hence my brute force solution). I can see a couple of areas where I could improve this but I am wondering if any of you could demonstrate a more efficient solution to this problem.
*spoiler: Here is my less than optimal (7 seconds to run) but still tolerable solution (not sure what to do about the double $... just pretend you only see 1...
function euler5(){
$x = 20;
for ($y = 1; $y < 20; $y++) {
if (!($x%$y)) {
} else {
$x+=20;
$y = 1;
}
}echo $x;
};
Collect prime factors for all numbers between 1 and 20. Counting the maximal exponents of each prime factor, we have 16 = 2**4, 9 = 3**2, as well as 5, 7, 11, 13, 17, 19 (each appearing only once). Multiply the lot, and you have your answer.
in php it will look like this:
<?php
function gcd($a,$b) {
while($a>0 && $b>0) {
if($a>$b) $a=$a-$b; else $b=$b-$a;
}
if($a==0) return $b;
return $a;
}
function euler5($i=20) {
$euler=$x=1;
while($x++<$i) {
$euler*=$x/gcd($euler,$x);
}
return $euler;
}
?>
Its at least twice as fast than what you posted.
Chris Jester-Young is right.
In general if you wanted the smallest number that is evenly divisible by all of the numbers from 1 to N, you would want to find all the prime numbers from 2 to N, and for each one, find the greatest number of times it divides any number in the range. This can be calculated by finding the greatest power of the prime that's not greater than N.
In the case of 20, as Chris pointed out, 2^4 is the greatest power of 2 not greater than 20, and 3^2 is the greatest power of 3 not greater than 20, and for all other primes, only the first power is not greater than 20.
You can remove some numbers that are divided with, for example 1 is unnecessary, all natural numbers are divisible by 1.you don’t need 2 either, and therefore, all numbers are divisible by multiples of 2 (4, 8, 16, etc) are divisible by 2, also. So the relevant numbers will be 11, 12, 13, 14, 15, 16, 17, 18, and 19.
So:
<?
function eulerPuzzle()
{
$integers = array( 11,12,13,14,15,16,17,18,19 );
for ($n = 20; 1; $n += 20 ) {
foreach ($integers as $int) {
if ( $n % $int ) {
break;
}
if ( $int == 19 ) {
die ("Result:" . $n);
}
}
}
}
eulerPuzzle();
?>
<?php
$i=20;
while ($i+=20) {
for ($j=19;$j!==10;--$j){
if ($i%$j) continue 2;
}
die ("result: $i\n");
}
Is the fastest and shortest php solution so far. About 1.4x faster than Czimi's on my comp. But check out the python solution, thats a nice algo.
Some people really over-think this...
In Ruby:
puts 5*7*9*11*13*16*17*19
#People doing simple math; I'm not sure if that is the goal of the exercise. You are to learn new languages and new ways to perform stuff. Just doing it by a calculator isn't the right way going about things.
And I know this is a post in an old old thread but it still comes up in google results :)
Doing it in code (PHP that is) I found this to be the fastest solution:
function eulerPuzzle() {
$integers = array (11, 12, 13, 14, 15, 16, 17, 18, 19 );
for($n = 2520; 1; $n += 2520) {
foreach ( $integers as $int ) {
if ($n % $int) {
break;
}
if ($int == 19) {
die ( "Result:" . $n );
}
}
}
}
eulerPuzzle ();
Yes, it's a modified piece from CMS. The main reason it is faster is because when you read the question, they already state that the lowest possible number for the first 10 integers is 2520. therefor, you can just increment by 2520 instead of 20. resulting in 126 times less loops
I know you said PHP, but here's my rough draft in Python.
#!/usr/bin/env python
from operator import mul
def factor(n):
factors = {}
i = 2
while i < n and n != 1:
while n % i == 0:
try:
factors[i] += 1
except KeyError:
factors[i] = 1
n = n / i
i += 1
if n != 1:
factors[n] = 1
return factors
base = {}
for i in range(2, 2000):
for f, n in factor(i).items():
try:
base[f] = max(base[f], n)
except KeyError:
base[f] = n
print reduce(mul, [f**n for f, n in base.items()], 1)
It's not as elegant as I could have made it, but it calculates the least common multiple of the numbers from 2 to 2000 in .15s. If your iterative solution could process a billion candidates per second, it would take 10^849 years to finish.
In other words, don't bother optimizing the wrong algorithm.

Categories