PHP / Math - Getting grid values - php

So this might be quite bizarre, but... Imagine a grid of 10x10, each numbered left to right, top to down, starting at 1 and ending in 100.
I want to input a grid number ($plot) and get each grid number that surrounds it.
I started by making an array:
$plot = 45;
$arr = array(
$plot-11, $plot-10, $plot-9,
$plot-1, $plot, $plot+1,
$plot+9, $plot+10, $plot+11
);
This works fine.
Except if I add a plot near the edge of the grid (like $plot = 50) it would give me results at the start of the next row. Eg:
Any clever ways to solve this?

You can filter the array and only keep cells which have a distance of 1 or 0:
$plot = 50;
$arr = array(
$plot-11, $plot-10, $plot-9,
$plot-1, $plot, $plot+1,
$plot+9, $plot+10, $plot+11
);
$plotX = ($plot - 1) % 10 + 1;
$plotY = ceil($plot / 10);
$arr = array_filter($arr, function($item) use($plotX, $plotY){
return 1 >= abs($plotX - (($item - 1) % 10 + 1))
&& 1 >= abs($plotY - ceil($item / 10));
});
var_export($arr);
Result:
array (
0 => 39,
1 => 40,
3 => 49,
4 => 50,
6 => 59,
7 => 60,
)

Related

PHP: How to select maximum probability value randomly?

I have below array & code
$a = [
149 => 55,
130 => 10,
131 => 5,
132 => 5,
133 => 10,
134 => 10,
135 => 5
];
$rand = rand (0,(count($a)-1));
echo array_values($a)[$rand];
This will give majorly result as 5,10 instead of 55.
Total of value is 100% probability. Values can be in decimal as well like 55.55, 10.10, etc. but overall going to be 100%
I already followed https://www.geeksforgeeks.org/how-to-get-random-value-out-of-an-array-in-php/
But this is not giving perfect result as expected.
So which has highest probability should be selected majorly and randomly.
So result can be like this : 55, 55, 10, 10, 10, 55, 5, etc..
I found some useful link Generating random results by weight in PHP? where Probability = Weight
Right now, your array is this: -
55, 10, 5, 5, 10, 10, 5
Now, you should generate a random number between [0, 100), let's call it r.
Now, if r lies between [0, 55), select the value 55.
else if r lies between [55, 55 + 10 = 65), select the value 10.
else if r lies between [65, 65 + 5 = 70), select the value 5.
else if r lies between [70, 70 + 5 = 75), select the value 5.
else if r lies between [75, 75 + 10 = 85), select the value 10.
else if r lies between [85, 85 + 10 = 95), select the value 10.
else if r lies between [95, 95 + 5 = 100), select the value 5.
I am sure you would have got the idea...
So, for the general case, if you have an array named 'arr', this is the pseudocode: -
function SELECTPROB()
{
$r = generateRandomNumber(0, 100); //function to generate random number between 0 and 100, (100 exclusive)
$sum = 0;
foreach($arr as $i)
{
if($r >= $sum && $r < $sum + $i)
{
return $i
}
$sum = $sum + $i
}
return -1 //Should technically never reach upto this, but it can if your probability's sum is not 100
}
Here is an implementation similar to roulette wheel selection in GA.
a version of the answer by EReload but bounded to the sum rather than a 100.
$a = [
149 => 55,
130 => 10,
131 => 5,
132 => 5,
133 => 10,
134 => 10,
135 => 5
];
echo randSelect($a);
function randSelect($a) {
$values = array_values($a);
$sum = array_sum($values);
$rand = (rand(0,1000)/1000) * $sum;
$partialSum = 0;
for ($i=0; $i < count($values); $i++) {
$partialSum += $values[$i];
if($partialSum >= $rand){
return $values[$i];
// incase you are using something like array_count_values and are actually looking for the keys
// return array_keys($a)[$i];
}
}
}
As i understand, you want higher number appear more frequently in rand method no matter how many times smaller number appear in your array. You need unique your array first.
Random by sum weight is simple method for random, but you can control weight more freely by sum power instead of itself.
$a = [
149 => 55,
130 => 10,
131 => 5,
132 => 5,
133 => 10,
134 => 10,
135 => 5
];
$val_arr = array_unique(array_values($a));
function rand_by_sum($arr, $power=1){
$sum = 0;
$f_val = function($f)use($power){
return pow($f, $power);
};
foreach($arr as $f){
$sum += $f_val($f);
}
$rand = mt_rand(0, $sum);
$tmp_sum = 0;
foreach($arr as $f){
$tmp_sum += $f_val($f);
if($tmp_sum >= $rand) return $f;
}
}
for($i=0; $i< 10; $i++){
echo rand_by_sum($val_arr, $argv[1]) . " ";
}
echo "\n";
And here some test result with different pow
php test.php 0.5
55 5 10 55 5 55 55 5 55 55
php test.php 2
55 55 10 55 55 55 55 55 55 55
php test.php 1
55 10 55 55 55 55 55 55 55 10
To get value, you revert array as 55 => [149] then get result from random, and random again in values of reverted array
Looking for answer which works in all scenarios or for any number.
and
Values can be in decimal as well like 55.55, 10.10, etc. but overall going to be 100%
Although you are limiting the total weight to 100, the fact that you want to accommodate decimal values in that range means that you cannot assume a maximum of 100 units to pick from. If you have a granularity of tenths, then each unit to potentially pick from will be .1 . If specifying down to hundredths (like 55.55) then you will need a relative base unit of .01 at a time.
Because I'd prefer not to iterate by float values, I recommend that you scale up all of your values by a factor that eliminates all floats in the weight and the random number generator -- simply multiply by 10/100/1000 whatever you need to convert all of the weights into integers.
Now to make the shortest work of the iteration process:
Loop through your input array once to establish the longest decimal precision.
Pick a random integer between 0 and ((the sum of all weights minus 1) multiplied by 10 to the power of "the longest representing decimal length").
Loop through your input array again and simply check if the random integer is less than the current weight plus any previous weight(s); if not if so, break the loop and because the selected weighted, random number has been located.
Code: (Demo) -- the demo makes 10 iterations to aid in revealing the weighted effect
$valueWeights = [
149 => 55.555,
130 => 10.0050,
131 => 5,
132 => 5.2,
133 => 10,
134 => 10.24,
135 => 5
];
$mostDecimals = 0;
// not bothering to validate against infinite and extremely fringe case floats
foreach ($valueWeights as $value => $weight) {
$tempDecimals = 0;
while ((string)$weight !== (string)floor($weight)) {
$weight *= 10; // this is not permanently mutating the weight
++$tempDecimals;
}
$mostDecimals = max($mostDecimals, $tempDecimals);
}
echo "Most Decimals: {$mostDecimals}\n";
$factor = pow(10, $mostDecimals);
echo "Factor: " , $factor , "\n";
$totalWeight = (array_sum($valueWeights) - 1) * $factor;
for ($i = 0; $i < 10; ++$i) {
$rand = mt_rand(0, $totalWeight);
echo "\nRand: " , $rand , "\n";
$cumulativeScaledWeight = 0;
foreach ($valueWeights as $value => $weight) {
$cumulativeScaledWeight += $weight * $factor;
if ($rand < $cumulativeScaledWeight) {
echo "Value: {$value}\n";
break;
}
}
}
Output:
Most Decimals: 3
Factor: 1000
Rand: 52197
Value: 149
Rand: 33785
Value: 149
Rand: 4783
Value: 149
Rand: 24994
Value: 149
Rand: 76588
Value: 133
Rand: 77417
Value: 133
Rand: 40541
Value: 149
Rand: 80009
Value: 133
Rand: 14826
Value: 149
Rand: 52691
Value: 149
I think you could actually shuffle the array and pop an element, shuffle again and pop the element, that would be randomly and those numbers with greater probability would be first.
What you can do is to create another array with 100 numbers, representing the total probability and inserting in it the amount of numbers equal to its value, finally you shuffle it to pick an index random later. Then you will get an array of 100 numbers where there most repeated number is the most probable. Lastly you just have to pick a random index and create your array.
can you tell me if you are looking for something like this or if I am misunderstanding the problem
function getProb($array, $elements)
{
$myNewArray = [];
$myProbabilisticArray = $this->getProbabilisticArray($array);
for ($i=0; $i < $elements; $i++) {
$myNewArray[] = $myProbabilisticArray[array_rand($myProbabilisticArray)];
}
return $myNewArray;
}
function getProbabilisticArray($array) {
$myNewArray = [];
rsort($array);
$currentProbability = 0;
$accumulatedProbability = $array[0];
$currentPosition = 0;
while ($currentProbability < 100) {
if ($currentProbability > $accumulatedProbability) {
$currentPosition++;
$accumulatedProbability += $array[$currentPosition];
}
array_push($myNewArray, $array[$currentPosition]);
$currentProbability++;
}
shuffle($myNewArray);
return $myNewArray;
}

Calculate average percentage difference in a php array

Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 3
[6] => 1
)
I was wondering how one would go about working out the average percentage difference between the current value in an array and the next value. If the next value were a larger one, it would perform like so. (ie keys [0]-[1] 1/2 * 100 = 50). If it were a smaller value it would perform like so. (ie keys [4]-[5] = 3/5 * 100 = -60).
The following will represent what I am aiming to do with these percentage calculations.
1/2 * 100
2/3 * 100
3/4 * 100
4/5 * 100
3/5 * 100 (negative)
1/3 * 100 (negative)
Total : total/count
This will iterate through the list and then work out the average from the count. I have looked into splitting arrays but don't see how else I could do this.
$count = count($num);
foreach ($num as $value) {
array_chunk($num, 1);
if($value<$value){
$total1 = $total1 + ($value/$value)*100;
}
if($value>$value){
$total2 = $total2 + ($value/$value)*100;
}
}
$average = (($total1-$total2)/$count);
print($average);
I understand the above code is incorrect, but I hope it reveals where I am getting at with this.
Any help would be greatly appreciated.
You don't want to use foreach as you'll always be needing two array elements. Note that this snippet does not protect you from 0 values. These will make your script fail.
$num = array(1, 2, 3, 4, 5, 3, 1);
$total = 0;
// The number of percent changes is one less than
// the size of your array.
$count = count($num) - 1;
// Array indexes start at 0
for ($i = 0; $i < $count; $i++) {
// The current number is $num[$i], and the
// next is $num[$i + 1]; knowing that it's
// pretty easy to compare them.
if ($num[$i] < $num[$i + 1]) {
$total += (100 * $num[$i] / $num[$i + 1]);
}
else {
$total += (-100 * $num[$i + 1] / $num[$i]);
};
};
echo ($total / $count);

More efficient than an if statement?

I'm making a game in PHP (don't ask lol), and the player has a location which is an integer. There's a travel page and this basically shows a 5x5 tiled map. Each tile is a different part of the player's universe. By clicking on it he can travel there.
Just to give you an idea of the integers behind the map:
11, 12, 13, 14, 15
21, 22, 23, 24, 25
31, 32, 33, 34, 35
41, 42, 43, 44, 45
51, 52, 53, 54, 55
Let's say the player starts at 33(the middle) and I wanted to charge him different rates depending on how far he traveled. So, for example, 1 tile in any direction is a 100 credits, 2 tiles is 200 and so on.
So what I came up with is this. $ol represents the player's current location and $nl is where they are travelling to...
if($ol-11==$nl || $ol-10==$nl || $ol-9==$nl || $ol+1==$nl || $ol+11==$nl || $ol+10==$nl || $ol+9==$nl || $ol-1==$nl || $ol-11==$nl ){
echo "cost 100 credits!";
}
else if($ol-22==$nl || $ol-21==$nl || $ol-20==$nl || $ol-19==$nl || $ol-18==$nl || $ol-8==$nl || $ol+2==$nl || $ol+12==$nl || $ol+22==$nl
|| $ol+21==$nl || $ol+20==$nl || $ol+19==$nl || $ol+18==$nl || $ol+8==$nl || $ol-2==$nl || $ol-12==$nl ){
echo "cost 200 credits!";
}
That's the code for 1 and 2 tile travel. As you can see it's a lengthy statement.
I basically worked out a pattern for the grid I'd set up. For example, travelling up 1 tile would always be -10 of the current tile.
Before I type out any more ridiculously long if statements, is there a neater or more efficient way to do this?
I would use a different method: As the first digit defines the row and the second digit the column, I would split the number in these two digits and use these numbers to determine how many rows and how many columns are being travelled.
So for any position:
$row = floor($tile_value / 10);
$column = $tile_value % 10;
With this it is easy to calculate distances.
Edit: A small example to measure absolute distances:
$row_org = floor($tile_org_value / 10);
$column_org = $tile_org_value % 10;
$row_new = floor($tile_new_value / 10);
$column_new = $tile_new_value % 10;
$row_diff = $row_new - $row_org;
$col_diff = $col_new - $col_org;
$distance = sqrt(pow($row_diff, 2) + pow($col_diff, 2));
As in my comment above, you cannot measure distance in units, since not all points can be reached in a straight line through points.
You need to consider these points to be points (x, y coordinates) on a graph. Then you can get the distance between any 2 points using Pythagoras.
For example, if we consider your top row as being the coordinates (1,1) (1,2) and so on, if the person travels from (1,1) to (4,3), the distance travelled is the square root of 3 (4-1) squared plus 2 (3-1) squared, i.e. sqrt(9+4) = sqrt(13)
I would probably try an array for coordinates. This will allow you to set the initial coordinates. You can then pass new coordinates to the function which will move the position and calculate the cost.
<?php
$array = array( );
//populate the array with 0's
for( $i = 1; $i <= 5; $i++ ) {
for( $j = 1; $j <= 5; $j++ ) {
$array[$i][$j] = 0;
}
}
//set beginning position
$array[3][3] = 1;
function newPosition( $array, $newX, $newY ) {
$oldX = 0;
$oldY = 0;
//locate current position
foreach($array as $key=>$subArray) {
foreach($subArray as $subKey=>$val) {
if($val === 1) {
$oldX = $key;
$oldY = $subKey;
}
}
}
//delete old position
$array[$oldX][$oldY] = 0;
//set new position
$array[$newX][$newY] = 1;
//Calculate x and y difference
$xTravel = abs($oldX - $newX);
$yTravel = abs($oldY - $newY);
//Add x and y difference
$totalTravel = $xTravel + $yTravel;
//Calculate the cost
$totalCost = $totalTravel * 100;
echo "cost $totalCost credits!\n";
return $array;
}
$array = newPosition( $array, 5, 2 );
$array = newPosition( $array, 1, 5 );
$array = newPosition( $array, 1, 5 );
$array = newPosition( $array, 3, 3 );
Output
cost 300 credits!
cost 700 credits!
cost 0 credits!
cost 400 credits!
See the demo
Your code seems legit. You could order the conditions so that the most used ones are first.

php modulo and print_r of the result?

i wanted to make my own quarter-final draw for the champion's league (tomorrow, friday 16 of march) : i've got 2 questions : first the modulo does not work : it shows "another match" after every entry in the array, whereas i wanted it to be written every two matches (every 2 entries)...
Second question : is there a better way to "print" the result? like a print_r without the index and where i could say "add \n after each entry" ?
<body>
<?php
$array = array("real", "barça", "bayern", "apoel", "chelsea", "milan", "benfica", "marseille" );
$new = array();
$incr = count($array);
while($incr>0){
$random = rand(0, count($array));
if (!in_array($array[$random], $new)){
$new[] = $array[$random];
if ( (count($new) % 2) ){
$new[] = " -- another match : ";
}
$incr--;
}
}
print_r($new);
?>
<p>results</p>
</body>
Thanks for your help
Another option would be to shuffle the array then just pop off each of the elements
$array = array("real", "barça", "bayern", "apoel", "chelsea", "milan", "benfica", "marseille" );
shuffle($array);
while($a = array_pop($array)) {
echo $a." vs. ".array_pop($array)." <br />";
}
Sample output:
apoel vs. real
barça vs. milan
marseille vs. bayern
chelsea vs. benfica
The modulo is working perfectly:
The array starts empty.
You add an element to it.
The length is 1, so 1 % 2, so 1, so truthy, so you add -- another match to the array
So the length is now 2
Next iteration of the loop, you add another element to the array.
The length is now 3, so 3 % 2, so 1, so truthy, so you add -- another match
And so on. Whatever it is you're trying to do, it's not what you told the server to do.
What you should probably do is something like this:
$array = Array(........);
while($a = array_shift($array)) {
$random = rand(0,count($array)-1); // -1 is important!
echo $a." vs. ".$array[$random]."<br />";
unset($array[$random)];
// no need to realign keys since array_shift already does that
}
The modulus is working exactly as you're telling it to.
(count($new) % 2) ){
when count($new) = 1, 1 % 2 = 1, = true
when count($new) = 2, 2 % 2 = 0, = false
when count($new) = 3, 3 % 2 = 1, = true
when count($new) = 4, 4 % 2 = 0, = false
when count($new) = 5, 5 % 2 = 1, = true
when count($new) = 6, 6 % 2 = 0, = false

How to find the changes between two lists, with immovable items (PHP)

I'm trying to get the changes between two lists of articles in order to animate the transition between the two lists. Articles can be added, removed or moved (not swapped).
However some articles can't be moved, and in each transition all other articles should be moved below those articles.
For example, if each number represents an article id, and bold represents immovable articles, then:
[1, 2, 3, 4, 5, 6] might become: [2, 4, 1, 6, 7]
I need to work out the changes required, for example in this case:
Move 1 after 4
Remove 5
Add 7 after 6
I have been using a diff algorithm, however it doesn't understand immovable items, so it might suggest:
Move 2 to the beginning
Move 4 after 2
Remove 5
Add 7 after 6
I've tried various things, but can't get it to work reliably.
Moving an immovable item 3 places left is the same thing as moving 3 movable items next to it 1 place right. Use your current diff algorithm, but when it wants to move an immovable item switch to those next to it instead.
UPD. Without multiple moves per article.
Transformations:
1. Remove numbers that are not in the second list (for each item n if not in_list(final_list) then say(n removed)).
[1 2* 4* 5 6] // say("remove 3")
[1 2* 4* 6] // say("remove 5")
2. Make an empty list size of the final list.
[_ _ _ _ _]
3. Prefill it with immovable numbers in their final positions.
[2* 4* _ _ _]
4. Loop through movable items from the first list moving them to their final positions
[2* 4* 1 _ _] // say("move 1 after 4")
[2* 4* 1 6 _] // say("move 6 after 1")
5. Add new items
[2* 4* 1 6 7] // say("add 7 after 6")
It was a fun problem to solve!
Here's the final code, thanks to Alexey:
$immovable = array(2);
$current = array(1,2,8,3,4,5,6);
$new = array(2,7,6,5,4,3,1);
$additions = $additionsCopy = array_diff($new, $current);
foreach($additions as $key => $addition) {
$after = array_key($new, $addition)-1;
$additions[$key] = array(
'value' => $addition,
'after' => ($after < 0 ? 0 : $new[$after])
);
}
for($key = 0; $key < count($new); $key++) {
if(in_array($new[$key], $additionsCopy))
$new = array_remove($new, $key--);
}
$removals = array_diff($current, $new);
for($key = 0; $key < count($current); $key++) {
if(in_array($current[$key], $removals))
$current = array_remove($current, $key--);
}
$lastImmovable = -1;
foreach($new as $key => $item) if(in_array($item, $immovable))
$lastImmovable = $key;
$prev = $lastImmovable < 0 ? 0 : $new[$lastImmovable];
foreach($new as $key => $item) if (!in_array($item, $immovable)) {
$moves[] = array('value' => $item, 'after' =>$prev);
$prev = $item;
}
At this point we can perform $removals, then $moves then $additions

Categories