Finding factor for position - php

I know question title seems quite 'un-understandable', but I don't know how to write question title for this particular question.
Question:
I want to find factor for position.
Let me clear you with an example.
Value Factor
[Available] [Have to find out]
----------------------------------
1 10
3 10
9 10
10 10
11 10
25 10
50 10
75 10
99 10
100 100
101 100
105 100
111 100
127 100
389 100
692 100
905 100
999 100
1000 1000
1099 1000
1111 1000
4500 1000
6825 1000
7789 1000
9999 1000
10000 10000
10099 10000
51234 10000
98524 10000
99999 10000
100000 100000
and so on.
I hope you understand what I mean to get.

Assuming that the first three values should be 1 (as noted by Asaph), then you just need to use all that logarithm stuff you learned in school:
pow(10, floor(log10($n)))
So, how does this work? The base-10 logarithm of a number x is the y such that 10^y = x (where ^ stands for exponentiation). This gives us the following:
log( 1) 0
log( 10) 1
log(100) 2
...
So the log10 of a number between 1 and 10 will be between 0 and 1, the log10 of a number between 10 and 100 will be between 1 and 2, etc. The floor function will give you the integer part of the logarithm (we're only dealing with non-negative values here so there's no need to worry about which direction floor goes with negative values) so floor(log10()) will be 0 for for anything between 1 and 10, 1 for anything between 10 and 100, etc. Now we have how many factors of ten we need so a simple pow(10, ...) gives us the final result.
References:
log10
floor
pow

I'm still a little unsure of what you're asking, but it seems like you want to map values to other values... In php arrays can be indexed with anything (making them a map). If 999 always means a factor of 100 and 1099 always means a factor of 1000, you can set the value of array[999] to 100 and the value of array[1099] to 1000, etc.

Basically Factor is 10 to the power of number of digits in $value minus 1 (except for the single digit numbers):
if($value < 10) {
$value += 10;
}
$numOfDigits = count(str_split($value,1));
$factor = pow(10,$numDigits-1);

This function should work for you. It seems like the first 3 "factors" in your list don't fit the pattern. If, in your sample data set, those first 3 "factors" should really be 1 instead of 10, then you can safely remove the first 3 lines of the body of the function below.
function getFactor($num) {
if ($num < 10) { // If the first 3 "factors" listed
return 10; // in the question should be 1 instead of 10
} // then remove these 3 lines.
$factor = 1;
while($factor <= $num) {
$factor *= 10;
}
return $factor / 10;
}

Related

Find a number by given digit

Let’s say I have a series of numbers like:
12345678910111213141516... (until unlimited)
Then I would like to get a number from it by given digit. For example:
Digit 10th: 1
Digit 17th: 3
...
I have tried to make the algorithm to do it by using PHP but it always showed me an error due to the looping that I made was out of memory size if the given digit that I gave is more than 10.000.000. Allowed Memory Size of 134217728 Bytes Exhausted
How do I deal with this without having to modify memory_limit on php.ini file?
Here are what I have tried to figure the algorithm out: I benchmark the maximum of upper limit of the loop that my local machine could handle, and I found out it's 10.000.000, then I assumed I need to make a separate loop if the given digit/parameter is more than 10.000.000. But in the end I still got that error of out of memory size. Really grateful in advance.
<?php
/*
* benchmark result:
* max digit = 10.000.000
*/
$benchmarkedDigit = 10000000;
$digit = 1000000000000; // it could be dynamically assigned, i.e. a parameter. In this case will show an error since the given digit is 10 trillion
$s = '';
if ($digit > $benchmarkedDigit) {
$mod = fmod($digit, $benchmarkedDigit);
$div = $digit / $benchmarkedDigit;
for ($x = 1; $x <= $div; $x++) {
$upperLimit = ($x * $benchmarkedDigit);
for ($y = ($upperLimit - $benchmarkedDigit + 1); $y <= $upperLimit; $y++) {
$s .= $y;
}
// so it could be:
// 1 - 10.000.000
// 10.000.001 - 20.000.000
// 20.000.001 - 30.000.000
// ...
}
// loop for the rest of the fmod(), if its result is not 0
for ($i = ($upperLimit + 1); $i <= ($upperLimit + $mod); $i++) {
$s .= $i;
}
} else {
for ($x = 1; $x <= $digit; $x++) {
$s .= $x;
}
}
echo substr($s, ($digit - 1), 1);
You can use the fact that there's always 10^n - 10^(n-1) number of n-digit long numbers (even 1 digit, because I see 0 is not there).
With this knowledge, you can skip potentially huge number of numbers.
You start with n=1, and check if the number of n digit numbers is lower than the desired digit. If it is, then reduce the number of n digit numbers from the desired number, increase n by one and start again.
For example: you want to know the 512th digit in that number
Is the number of 1 digit numbers (10) lower than the desired digit (512)?
Yes, so the desired digit should be reduced by that many (512 - 9).
Is the number of 2 digit numbers (90) lower than the desired digit (503 now)?
Yes, so the desired digit should be reduced by that many (503 - 90).
Is the number of 3 digit numbers (900) lower than the desired digit(413 now)?
No, so the desired digit is one of the digits of a 3 digit number.
413 / 3 is 137 (rounded down), so it's one of the digits of the 137th 3 digit numbers (so 237).
413 % 3 (modulo) is 2, so it's the 2nd digit, so it's supposed to be 3.
There can be miscalculations in this, but the overall logic should not be far.
Edit: you could also use a generator, but this can increase the runtime for big numbers
function getNthDigit() {
for ($i = 0;; ++$i) { // Start with 0, which is the 0-th digit
foreach (str_split((string)$i) as $digit) {
yield $digit;
}
}
}
$desiredDigit = 512;
foreach (getNthDigit() as $number => $digit) {
if ($number == $desiredDigit) {
break;
}
}
// $digit should be the desired digit
<?php
function getDigit($Nth){
if($Nth < 10) return $Nth;
$no_of_digits = 1;
$current_contribution = 9;
$actual_length = 9;
$prev_length = 0;
$starting_number = 1;
$power_of_10 = 1;
while($actual_length < $Nth){
$no_of_digits++;
$current_contribution *= 10;
$prev_length = $actual_length;
$actual_length += ($current_contribution * $no_of_digits);
$power_of_10 *= 10;
$starting_number *= 10;
}
$Nth = $Nth - $prev_length;
$offset = $Nth % $no_of_digits === 0 ? intval($Nth / $no_of_digits) - 1 : intval($Nth / $no_of_digits);
$number = strval($starting_number + $offset);
for($i=1;$i<=$no_of_digits;++$i){
if(($Nth - $i) % $no_of_digits === 0){
return $number[$i-1];
}
}
}
// first 100 Digits
for($i=1;$i<=100;++$i){
echo getDigit($i),PHP_EOL;
}
Demo: https://3v4l.org/3l0I7
Algorithm:
To find the nth digit, we will first find the number and then which digit of that number to choose as an answer.
Find the number:
If we carefully observe, the series increases in a sequential manner, such as shown in the table.
Table:
| Digits| Total numbers(of current digit)| Total Digits | Total digits of whole string |
|-------|--------------------------------|--------------|-------------------------------|
| 1 | 9 | 9 | 9 |
| 2 | 90 | 180 | 189 |
| 3 | 900 | 2700 | 2889 |
| 4 | 9000 | 36000 | 38889 |
The above table shows us that if we want to find, let's say 500th digit, then it's some digit of 3 digit number. If we go for 17th digit, then it's some digit of a 2 digit number and so on.
Now, let's take 200th digit as an example. Since it's less than 2889 and greater than 189, it's from a 3 digit number.
What we would do is breakdown the 200 into a smaller number such as 200 - 189 = 11. This 11 means that it's 11th digit of some 3 digit number which started with initial 3 digit number of 100(the starting number for 3 digit).
Now, we do 11 / 3(where 3 is number of digits) and get the quotient as 3. This 3 means that it's 3 numbers past the starting number 100, which we can say as 100 + 3 = 103(since it's 100,101,102 and then the 4th one as 103).
Now, we came to know that the number is 103. All is left to find out is which digit from 103.
Note that sometimes we come across a corner case of even divisibility such as 12 / 3. In this case, we subtract 1 from the quotient since our series of 3 digits starts from 100 and not 101( and so on and so forth for other digits).
Find out the digit:
Now, we know that the number is 103 for a 200 th digit( a.k.a 11 as we calculated above). To find out which one, we write down numbers of 3 digits in sequence and closely observe them.
Sequence:
1 0 0 1 0 1 1 0 2 1 0 3 1 0 4 1 0 5 1 0 6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
If you observe, you can understand that the most MSB digit follows a sequence of 1,4,7,10,13 etc. Second most MSB follows a sequence of 2,5,8,11,14 etc and the last MSB(which is LSB) follows a sequence of 3,6,9,12,15 etc.
So, from th above sequence, it's pretty evident that 11(which we got after breaking down 200 initially) belongs to a sequence of the 2nd most MSB digit.
So, the final answer from 103 is 0 (the 2nd digit from left).
$num = '12345678910111213141516';
echo $num[16];
Result: 3

PHP auto convert price to 99 rather than 100

I have different prices:
100
550
799
1200
350
Wonder how i can automatically convert them to
99
549
799
1200
349
I'd just do -1, but some are already correct like 799, and i don't want it to be 798.
Cant think of way to do this conversion i am good with PHP but not great with math. I bet we need to find if it divides by 2 if yes then subtract 1 if not then do nothing.
if ($price % 2) {
$price = intval($price) - 1;
}
Is this the best way to do it?
You could do something like this :
if (($price % 50) == 0)
{
$price--;
}
Everytime the modulo of 50 will be equal to zero (this will be the case for any number that ends with "50" or "100"), it will remove one.
Change the second value (50 in this case) if you want to target more or less prices.
Something like this?
if(($price % 10) == 0) {
$price--;
}
As you don't want to have 798, your code does not work
But you can use this.
if ($price % 10) {
$price = intval($price) - 1;
}
799 % 2 = 1
800 % 2 = 0
798 % 2 = 0
799 % 10 = 0
800 % 10 = 1
798 % 10 = 0
or %50 if you only want to subtract 1 from numbers like 750 and 800

Rounding to nearest 5/10 in PHP

I have two inputs
min, max
In the case where min=32 and max=46 then I would like PHP to automatically the value to the nearest 5/10 i.e. in this case min=30 and max=50?
But in the instance of course if min=35 or 40 and max=40 or 45 there would be no need to round off.
How do achieve this in PHP?
Btw the system only deals with integer values and the above values are just examples. It needs to work for a range of numbers ranging from 0 to infinity. So 4 would round to 0, 6 to 10... etc...
function round_to_10($n) {
return round($n / 10) * 10;
}
php> echo round_to_10(32)
30
php> echo round_to_10(46)
50
If you multiply the number by two, you can round this to 10s and then divide it by 2 again.
function round_five($num) {
return round($num*2,-1)/2;
}
$nums = array(32,46,35,40);
foreach($nums as $num) {
printf("%s: %s\n",$num,roundFive($num));
}
The above will return
32: 30
46: 45
35: 35
40: 40

Using PHP, how can I make a counter that turns any Int into 1 to 9999?

I have an internal counter that counts from 0-999999999999.
I would like this to display as a number between 0-9999, then rollover again.
This means:
0 displays as 1
1 displays as 2
9998 displays as 9999
...
9999 displays as 1
10000 displays as 2
...
19999 displays as 1
20000 displays as 2
edit:
1 + $number % 9999 was the answer (Thanks #Brad Christie). My table of expected results is wrong. (Thanks #Tevo D)
$x = 9280293;
$baseNineNineNineNine = $x % 9999;
Use MOD, it will give you the remainder past 9999 (e.g. Any number divided by 9999 can go in N times, with a remainder of Y (you'll end up with Y as a value)
For the numbers you're looking for, you may want to +1 any value you get after the MOD (%), or use 10000
See also the Modulus Operator
Take reminder with 10000, that guarantees the result to be in between 0 and 9999 and rolls over
$result = $int % 10000;
Your table of values doesn't match what you asking for. In the example you are using 9999 result values (1-9999) for 10000 input values. In the text you are saying 10000 output values (0-9999).
Here is what I think you are really asking for. This algorithm will output 1-9999 and then roll over to 1 again.
In other words, this solution will provide a four digit non-zero value:
$result = $int % 9999 + 1;
The output will NOT match your example, as your example has it rolling over every 10000 values, not 9999. Here is the output:
input output
0 1
1 2
.... ....
9997 9998
9998 9999
9999 1 <--- 9999 * 1
10000 2
.... ....
19996 9998
19997 9999
19998 1 <--- 9999 * 2
19999 2
.... ....
19995 9998
19996 9999
19997 1 <--- 9999 * 3
19998 2
.... ....

Generate random player strengths in a pyramid structure (PHP)

For an online game (MMORPG) I want to create characters (players) with random strength values. The stronger the characters are, the less should exist of this sort.
Example:
12,000 strength 1 players
10,500 strength 2 players
8,500 strength 3 players
6,000 strength 4 players
3,000 strength 5 players
Actually, I need floating, progressive strength values from 1.1 to 9.9 but for this example it was easier to explain it with integer strengths.
Do you have an idea how I could code this in PHP? Of course, I would need mt_rand() to generate random numbers. But how can I achieve this pyramid structure?
What function is it? Root function, exponential function, power function or logarithm function?
Thanks in advance!
It should look like this in a graph:
Pyramid graph http://img7.imageshack.us/img7/107/pyramidy.jpg
You can simulate a distribution such as the one you described using a logarithmic function. The following will return a random strength value between 1.1 and 9.9:
function getRandomStrength()
{
$rand = mt_rand() / mt_getrandmax();
return round(pow(M_E, ($rand - 1.033) / -0.45), 1);
}
Distribution over 1000 runs (where S is the strength value floored):
S | Count
--+------
1 - 290
2 - 174
3 - 141
4 - 101
5 - 84
6 - 67
7 - 55
8 - 50
9 - 38
Note:
This answer was updated to include a strength value of 1.1 (which wasn't included before because of the rounding) and to fix the name of the mt_getrandmax() function
The simplest way to do this would be to provide 'bands' for where a random number should go. In your example, you have 15 players so you could have:
rand < 1/15, highest strength
1/15 < rand < 3/15, second highest
3/15 < rand < 6/15, third highest
6/15 < rand < 10/15, fourth highest
10/15 < rand < 15/15, lowest strength
You could also parameterise such a function with a 'max' number of each band that you allow and when the band is filled, it is subsumed into the next lowest existing band (apart from the bottom band, which would be subsumed into the next highest) to ensure only a certain number of each with a random distribution.
Edit adding from my comments:
To get a floating range pyramid structure the best function would most likely be a logarithm. The formula:
11 - log10(rand)
would work (with log10 being a logarithm with base 10) as this would give ranges like:
1 < rand < 10 = 9 < strength < 10
10 < rand < 100 = 8 < strength < 9
100 < rand < 1000 = 7 < strength < 8
etc.
but rand would need to range from 1 to 10^10 which would require a lot of randomness (more than most random generators can manage). To get a random number in this sort of range you could multiply some together. 3 random numbers could manage it:
11 - log10(rand1 * rand2 * rand3)
with rand1 having range 1-10000 and rand2 and rand3 having range 1-1000. This would skew the distribution away from a proper pyramid slightly though (more likely to have numbers in the centre I believe) so it may not be suitable.
workmad3 has the start of it down, I think, but there's a catch - you need to track your bucket sizes and whether or not they're full. A random number generator won't guarantee that. You'll need to assign your bucket values (strenghs) and sizes (number of people), and let your random generator tell you which bucket to drop the player into - if that one is full, 'overflow' to the next lower.
As to assigning the bucket sizes for a given strength value, that's the tricky bit (and I think what you're really working at). The characteristics of your desired distribution are critical. If you want a linear drop (which the pyramid shape hints at), a line of the form
strength = max_strength - m(number_characters)
would work. Varying the value of m would change the speed at which the line drops off, and will basically limit your max number of total characters. If you're looking for a more sophisticated way for the strength values to drop off, you could use a parabolic or hyperbolic curve - these are a bit more complex, but give you very different characteristics.
something like this
<?php
$rand = rand(1,10);
switch ($rand) {
case 1:
echo "band 1";
break;
case 2:
case 3:
echo "band 2";
break;
case 4:
case 5:
case 6:
echo "band 3";
break;
default:
echo "band 4";
break;
}
?>
Band 1 being the strongest, band 4 being the weakest.
Ofcourse this is basic, you would want to refactor this to use loops instead of hardcoded switches, but you get the idea :)
It's probably easiest to use percentages in this case.
From your examples would approximately be (converted to an array for ease of use later):
$strength[1] = .3; // start with a key of 1
$strength[2] = .26;
$strength[3] = .21;
$strength[4] = .15;
$strength[5] = .08;
That way, you can generate a random number using mt_rand() and divide by the maximum possible value to get a number between 0 and 1:
$rand = mt_rand() / mt_getrandmax(); // rand is some random value between 0 and 1
Then you can use a foreach statement to isolate each case:
$comparisonPercentage = 1;
$selectedLevel = count($strength); // covers the case where mt_rand() returns 0
foreach($strength as $level => $currentPercentage)
{
$comparisonPercentage -= $currentPercentage;
if ($rand > $comparisonPercentage)
{
$selectedLevel = $level;
break;
}
}
// $selectedLevel contains the level you need...
If you do it this way, you only have to change the $strength array if you need to fiddle with the percentages.
generate a random number between 0 and 40000, if its between 0 and 12000, assign strength 1, between 12000 and 22500 assign 2 etc.
Edit: for progressive values between 0 and 10 use the square root of a random number between 0 and 100, then substract if from 10
rand -> strengh
0-1 -> 9.9 -> 9 (1%)
2-4 -> 9 -> 8 (2%)
...
81 - 100 -> 1 - 0 (19%)
For results between 1.1 and 9.9 the formula would be in pseudocode)
strength = 10 - sqrt(rand(1..79))

Categories