PHP-Relooping array associative backwards - php
I have a created a grid like this (some gaps has an obstacle, but it is not important for the question):
private function newGrid()
{
$grid = array();
for ($i = 0; $i < 10; $i++) {
for ($j = 0 ; $j < 10; $j++) {
$grid[$i][$j] = ['obstacle' => rand(0,1)];
}
}
return $grid;
}
So, the initial location is an array:
$loc = array('x' => 1, 'y' => 3);
For example, if i'm moving to x direction, I want wrapping from one edge of the grid to another(like an sphere). (Forward and backward)
When i'm going forward I'm using the modulus like this:
$loc['x'] = ($loc['x'] + 1) % 10 ;
But if I want to do the same, but going backwards, which is the better way to do that? when x gets to 0, go to position x = 9
Any suggestion?
For going backwards you can do:
$loc['x'] = ($loc['x'] + 9) % 10 ;
If you have dynamic "direction" variable which can take values 1 (forward) and -1 (backwards), then:
$loc['x'] = ($loc['x'] + 10 + $direction) % 10 ;
Maybe this would be a practical function:
function move($loc, $deltaX, $deltaY) {
$loc['x'] = ($loc['x'] + 10 + $deltaX) % 10;
$loc['y'] = ($loc['y'] + 10 + $deltaY) % 10;
return $loc;
}
Related
Expanding Round-robin tournament 1v1 to a 1v1v1v1
I am trying to expand and improve the round-robin algorithm from 1v1 group to a 1v1v1v1 group(something like free for all). I've made the function itself to do the schedule, but when I tried to expand it, some teams repetead. For example, I have 16 teams and I want to have 5 rounds, team 1 appears 7 times in 5 rounds and team2 appears 3 times in 5 rounds. I need them to appear 5 times at most.I really can't understand how I can do it. Any advice is welcomed and links. function make_schedule(array $teams, int $rounds = null, bool $shuffle = true, int $seed = null): array { $teamCount = count($teams); if($teamCount < 4) { return []; } //Account for odd number of teams by adding a bye if($teamCount % 2 === 1) { array_push($teams, null); $teamCount += 1; } if($shuffle) { //Seed shuffle with random_int for better randomness if seed is null srand($seed ?? random_int(PHP_INT_MIN, PHP_INT_MAX)); shuffle($teams); } elseif(!is_null($seed)) { //Generate friendly notice that seed is set but shuffle is set to false trigger_error('Seed parameter has no effect when shuffle parameter is set to false'); } $quadTeamCount = $teamCount / 4; if($rounds === null) { $rounds = $teamCount - 1; } $schedule = []; for($round = 1; $round <= $rounds; $round += 1) { $matchupPrev = null; foreach($teams as $key => $team) { if($key >= $quadTeamCount ) { break; } $keyCount = $key + $quadTeamCount; $keyCount2 = $key + $quadTeamCount + 1; $keyCount3 = $key + $quadTeamCount + 2; $team1 = $team; $team2 = $teams[$keyCount]; $team3 = $teams[$keyCount2]; $team4 = $teams[$keyCount3]; //echo "<pre>Round #{$round}: {$team1} - {$team2} - {$team3} - {$team4} == KeyCount: {$keyCount} == KeyCount2: {$keyCount2} == KeyCount3: {$keyCount3}</pre>"; //Home-away swapping $matchup = $round % 2 === 0 ? [$team1, $team2, $team3, $team4 ] : [$team2, $team1, $team4, $team3]; $schedule[$round][] = $matchup ; } rotate($teams); } return $schedule; } Rotate function: function rotate(array &$items) { $itemCount = count($items); if($itemCount < 3) { return; } $lastIndex = $itemCount - 1; /** * Though not technically part of the round-robin algorithm, odd-even * factor differentiation included to have intuitive behavior for arrays * with an odd number of elements */ $factor = (int) ($itemCount % 2 === 0 ? $itemCount / 2 : ($itemCount / 2) + 1); $topRightIndex = $factor - 1; $topRightItem = $items[$topRightIndex]; $bottomLeftIndex = $factor; $bottomLeftItem = $items[$bottomLeftIndex]; for($i = $topRightIndex; $i > 0; $i -= 1) { $items[$i] = $items[$i - 1]; } for($i = $bottomLeftIndex; $i < $lastIndex; $i += 1) { $items[$i] = $items[$i + 1]; } $items[1] = $bottomLeftItem; $items[$lastIndex] = $topRightItem; } For example: If I set rounds to 5, every team play 5 matches. Array example Screenshot Dealing with the 5th round: Well, after I thought for a bit, maybe there isn't a way for them to play without repeatence, but if it is lowered to minumum, like every team should play 5 times only - this means once a round. That's what I meant. And what I meant under 'they repeat' is that there are like: 16 teams, 5 rounds and some teams are going like 7 times for all these rounds and other teams are going 3 times for these 5 rounds. I want to avoid this and to make every team play 5 rounds at most.
Your foreach() with the selection of the other 3 teams is wrong. One of them have to make steps with a multiple of 4. If you don't, you will select the teams at the beginning more than one and don't select the teams at the end of the array at all. This will result in wrong team matchups like this (teams are letters here): abcd bcde cdef defg And then your break; hits. Instead it should look something like this: for ($i=0; $i<4; $i++) { $matchup = array(); for ($j=0; $j<4; $j++) { $matchup[] = $teams[4*$i+$j]; } $schedule[$round][] = $matchup ; } This way you get the following pairing (again, using letters as teams): abcd efgh ijkl mnop This algorithm will split the team list in four groups: abcd|efgh|ijkl|mnop Keep in mind that depending on the shuffling of the $teams array for the next round you might get the same opponent twice. adei|klnf|gjmc|pobh Here the teams ad, kl and op will face again.
Find the highest product in 4 directions in a matrix
I got this challenge to find the highest product of 4 consecutive numbers on a 20x20 matrix of integers. The numbers are read line by line from a file separated by a space. The products can be in horizontal, vertical and diagonal in both directions My "solution" gives the wrong answer. EDIT: I've updated the code to work without file input and added sample data; also fixed one of my mistakes that were pointed out in the comments $data = [ [89,32,92,64,81,2,20,33,44,1,70,75,39,62,76,35,16,77,22,27], [53,11,6,95,41,51,31,59,8,23,19,13,61,91,48,69,84,52,66,24], [93,72,85,97,21,79,56,5,45,3,65,30,83,87,43,7,34,0,4,14], [29,17,49,9,82,90,55,67,15,63,54,94,12,28,96,37,58,98,86,78], [74,40,50,60,26,99,80,18,10,46,36,68,25,57,47,71,42,73,88,38], [50,22,6,26,18,53,52,5,46,2,89,77,83,48,4,58,45,28,84,81], [49,82,31,14,69,17,91,54,34,40,0,33,30,95,60,44,29,24,85,16], [27,11,76,39,15,86,92,74,99,59,94,12,55,57,38,96,47,32,78,75], [51,20,87,42,62,41,7,35,23,21,71,25,67,97,80,90,88,64,13,70], [19,9,56,43,68,93,65,98,36,3,61,63,10,72,8,73,1,66,79,37], [22,58,52,12,3,41,28,72,42,74,76,64,59,35,85,78,14,27,53,88], [46,80,5,96,7,68,61,69,67,34,36,40,82,26,75,50,29,91,10,2], [30,39,19,48,33,93,1,45,66,98,0,23,62,25,51,71,56,77,24,21], [79,87,94,60,8,32,13,65,4,92,73,9,31,37,17,84,15,90,86,20], [95,6,81,70,47,16,44,83,49,43,55,54,18,63,38,11,97,89,99,57], [95,78,64,58,7,17,53,28,74,86,6,12,54,85,21,94,16,69,25,68], [13,20,41,97,1,2,80,30,0,84,67,45,93,96,82,92,62,33,18,44], [60,77,31,70,76,36,59,38,15,3,91,46,65,73,49,11,8,35,5,52], [61,66,79,40,26,72,89,71,75,99,22,9,43,32,14,81,98,88,87,83], [10,4,23,19,56,57,51,47,50,27,90,63,42,29,24,55,48,37,39,34] ]; $matrix = []; //maximums in possible directions $maxes = [0, 0, 0, 0]; //while ($line = trim(fgets(STDIN))) { while ($line = current($data)) { //the horizontal maxes can be calculated while loading //$array = explode(" ", $line); $array = $line; $hMax = array_product(array_slice($array, 0, 4)); for ($i = 1; $i < (count($array)-4); $i++) { $max = array_product(array_slice($array, $i, 4)); if($max > $hMax) { $hMax = $max; } } if ( $hMax > $maxes[0] ) { $maxes[0] = $hMax; } $matrix[] = $array; next($data); } // the last 3 rows can be skipped for($i = 0; $i < (count($matrix)-4); $i++) { for ($j = 0; $j < (count($matrix[$i])-1); $j++) { $vMax = 1; // vertical $dlMax = 1; // diagonal left $drMax = 1; // diagonal rigth for ($k = 0; $k < 5; $k++) { $vMax *= $matrix[$i + $k][$j]; if ( $j < (count($matrix[$i]) - 4) ) { $drMax *= $matrix[$i + $k][$j + $k]; } if ( $j > 3 ) { $dlMax *= $matrix[$i + $k][$j - $k]; } } if ( $maxes[1] < $vMax ) $maxes[1] = $vMax; // the index used to be 1 - my first mistake if ( $maxes[2] < $dlMax ) $maxes[2] = $dlMax; // the index used to be 1 - my first mistake if ( $maxes[3] < $drMax ) $maxes[3] = $drMax; // the index used to be 1 - my first mistake } } sort($maxes); echo end($maxes).PHP_EOL; Where did my approach go wrong, and how can it be sped up? Are there any math tricks that can be applied here (besides checking for zeros)? EDIT: the solution that the code gives for the current data is 4912231320 is it correct?
I've found 2 major errors, and now the result is a plausible 67352832 I'm considering it solved for that reason, but if anyone comes up with some math trick that simplifies or makes it faster I'll give up the accepted answer. The first mistake was for ($k = 0; $k < 5; $k++) { It should've been for ($k = 0; $k < 4; $k++) { since we are only counting 4 numbers at once, thats why the result was so large compared to 10^8 The second was if ( $j > 3 ) { which should've been if ( $j > 2 ) { which will now include one more diagonal possibility
We can consider the four directions a bottom- or right-most cell can be the last of in a sequence. If m[i][j][k][d] is the highest total for a sequence of length k coming from direction d, then: m[i][j][1][d] = data[i][j] for all d m[i][j][k]['E'] = data[i][j] * m[i][j - 1][k - 1]['E'] m[i][j][k]['NE'] = data[i][j] * m[i - 1][j - 1][k - 1]['NE'] m[i][j][k]['N'] = data[i][j] * m[i - 1][j][k - 1]['N'] m[i][j][k]['NW'] = data[i][j] * m[i - 1][j + 1][k - 1]['NW'] If we traverse north to south, east to west, the needed cells should have already been calculated, and, clearly, we're looking for max(m[i][j][4][d]) for all i, j, d
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.
what does a comma do in the first parameter of a for loop?
What does it mean when there is a comma in the first parameter of a for loop? For example: for ($j=0, $n2=sizeof($quotes[$i]['methods']); $j<$n2; $j++) { // }
A comma in the first section of the loop just separates variable declarations. Essentially it is just declaring two variables $j=0 and $n2=sizeof($quotes[$i]['methods']), but in the loop constructor instead of before the loop constructor.
PHP inherited C++-like syntax. It is common for C++-like languages to have scope visibility for code blocks or control structures, like: #include <iostream> using namespace std; int main() { int a = 0; // main scope; int b = 5; // main scope; if(a != b){ int c = a + b; // if scope; // a, b, c visible. } // a and b visible, but c - not visible. for(int i = 10; i < 20; i++){ // for-loop scope: // i, a and b visible cout << "i: " << i << endl; } // a and b visible, but i - not visible. return 0; } PHP has no such feature, but inherited syntax rules (and most of C++ code conventions). <?php header('Content-Type: text/plain'); for($i = 0, $j = 10; $i < 10; $i++, $j += 2){ echo "{$i} + {$j} = ", $i + $j, PHP_EOL; } ?> is equival to <?php header('Content-Type: text/plain'); $j = 10; for($i = 0; $i < 10; $i++){ echo "{$i} + {$j} = ", $i + $j, PHP_EOL; $j += 2; } ?> Variables $i and $j will be accessible everywhere after for-loop, but declaring them in for-loop header section might be done for obviosity (some people say, that those variables are definately used in this cycle). Also it is shorter. NOTE: You may use , for every header section of for-loop too. UPDv1: for-loop declaration: for(initialization_section; condition_section; post_execution_section){ // actions } Each of sections might contain expression, but can not contain other control structures. 1) initialization_section: Should contain expressions to execute before loop starts. If there is more than one separate expression, they should be separated by comma ,. Executes before any of for-loop iterations. for($i = 0, $j = 1; $i < 10; $i++){ echo "{$i} + {$j} = ", $i + $j, PHP_EOL; } Result: 0 + 1 = 1 1 + 1 = 2 2 + 1 = 3 3 + 1 = 4 4 + 1 = 5 5 + 1 = 6 6 + 1 = 7 7 + 1 = 8 8 + 1 = 9 9 + 1 = 10 2) condition_section: Should contain expressions to check if loop continues or stops. If there is more than one separate action, they should be separated by comma ,. Executes before every for-loop iteration. for($i = 1, $j = 0; $i++, $j < 10; $j++){ echo "{$i} + {$j} = ", $i + $j, PHP_EOL; } Result: 2 + 0 = 2 3 + 1 = 4 4 + 2 = 6 5 + 3 = 8 6 + 4 = 10 7 + 5 = 12 8 + 6 = 14 9 + 7 = 16 10 + 8 = 18 11 + 9 = 20 Comma in this section of for-loop causes ignore of all previous expressions of this section, except the last one (as #icktoofay mentioned). for($i = 0, $j = 1; $i < 1, $j < 5; $i++, $j++){ echo "{$i} + {$j} = ", $i + $j, PHP_EOL; } Result: 0 + 1 = 1 1 + 2 = 3 2 + 3 = 5 3 + 4 = 7 Regardless of $i < 1 is false for the second iteration. It might be used as default pre-execution statement. 3) post_execution_section: Should contain expressions to execute after loop body actions are performed. If there is more than one separate action, they should be separated by comma ,. Executes after every for-loop iteration. for($i = 0; $i < 10; $i++, $i *= 2){ echo $i, PHP_EOL; } Result: 0 2 6 Also, each of for-loop sections might be empty (do nothing) with preserve of semicolon ; separators: for(;;){ // infinite loop }
how to find the sum of all the multiples of 3 or 5 below 1000 in php, issue?
i have an small issue with the way this problem is resolved. some would say: println((0 /: ((0 until 1000).filter(x => x % 3 == 0 || x % 5 == 0))) (_+_)) is the solution witch adds to 233168 my way was to do: $maxnumber = 1000; for ($i = 3; $i < $maxnumber; $i += 3) { $t += $i; echo $i.','; } echo '<br>'; for ($j = 5; $j < $maxnumber; $j += 5) { $d += $j; echo $j.','; } echo '<br>'; echo $t; echo '<br>'; echo $d; echo '<br>'; echo $t+$d; this will give me : 3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54,57,60,63,66,69,72,75,78,81,84,87,90,93,96,99,102,105,108,111,114,117,120,123,126,129,132,135,138,141,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186,189,192,195,198,201,204,207,210,213,216,219,222,225,228,231,234,237,240,243,246,249,252,255,258,261,264,267,270,273,276,279,282,285,288,291,294,297,300,303,306,309,312,315,318,321,324,327,330,333,336,339,342,345,348,351,354,357,360,363,366,369,372,375,378,381,384,387,390,393,396,399,402,405,408,411,414,417,420,423,426,429,432,435,438,441,444,447,450,453,456,459,462,465,468,471,474,477,480,483,486,489,492,495,498,501,504,507,510,513,516,519,522,525,528,531,534,537,540,543,546,549,552,555,558,561,564,567,570,573,576,579,582,585,588,591,594,597,600,603,606,609,612,615,618,621,624,627,630,633,636,639,642,645,648,651,654,657,660,663,666,669,672,675,678,681,684,687,690,693,696,699,702,705,708,711,714,717,720,723,726,729,732,735,738,741,744,747,750,753,756,759,762,765,768,771,774,777,780,783,786,789,792,795,798,801,804,807,810,813,816,819,822,825,828,831,834,837,840,843,846,849,852,855,858,861,864,867,870,873,876,879,882,885,888,891,894,897,900,903,906,909,912,915,918,921,924,927,930,933,936,939,942,945,948,951,954,957,960,963,966,969,972,975,978,981,984,987,990,993,996,999 5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100,105,110,115,120,125,130,135,140,145,150,155,160,165,170,175,180,185,190,195,200,205,210,215,220,225,230,235,240,245,250,255,260,265,270,275,280,285,290,295,300,305,310,315,320,325,330,335,340,345,350,355,360,365,370,375,380,385,390,395,400,405,410,415,420,425,430,435,440,445,450,455,460,465,470,475,480,485,490,495,500,505,510,515,520,525,530,535,540,545,550,555,560,565,570,575,580,585,590,595,600,605,610,615,620,625,630,635,640,645,650,655,660,665,670,675,680,685,690,695,700,705,710,715,720,725,730,735,740,745,750,755,760,765,770,775,780,785,790,795,800,805,810,815,820,825,830,835,840,845,850,855,860,865,870,875,880,885,890,895,900,905,910,915,920,925,930,935,940,945,950,955,960,965,970,975,980,985,990,995 $t - 166833 $d - 99500 and total: 266333 why am i wrong?
Some numbers are multiples of both 3 and 5. (Your algorithm adds these numbers to the total twice.)
Because 6 * 5 == 30 and 10 * 3 == 30, you're adding the some numbers up twice. $sum = 0; $i = 0; foreach(range(0, 999) as $i) { if($i % 3 == 0 || $i % 5 == 0) $sum += $i; }
Because you double-count numbers that are multiple of both 3 and 5, i.e. multiples of 15. You can account for this naively by subtracting all multiples of 15. for ($j = 15; $j < $maxnumber; $j += 15) { $e += $j; echo $j.','; } $total = $total - $d;
In your case, if it is 15, you will add the number twice. Try this: $t = 0; $d = 0; for ($i = 0; $i <= $maxnumber; $i++){ if ($i % 3 == 0) $t+= $i; else if ($i % 5 == 0) $d += $i; } echo $t.'<br>'.$d;
I think that in your code, if a number is a multiple of 3 and 5, it is added twice. Take 15 for example. It's in your list of multiples of 3 and in the list of multiples of 5. Is this the behaviour you want?
One of the best approach to this solution (to achieve optimum time complexity), run an Arithmetic Progression series and find the number of terms in all series by using AP formula: T=a+(n-1)d, then find sum by : S=n/2[2*a+(n-1)d] where : a=first term ,n=no. of term , d=common deference, T=nth term The code solution below has been implemented to suit the question above - so the values 3 and 5 are hard-coded. However, the function can modified such that values are passed in as variable parameters. function solution($number){ $val1 = 3; $val2 = 5; $common_term = $val1 * $val2; $sum_of_terms1 = calculateSumofMulitples($val1,$number); $sum_of_terms2 = calculateSumofMulitples($val2,$number); $sum_of_cterms = calculateSumofMulitples($common_term,$number); $final_result = $sum_of_terms1 + $sum_of_terms2 - $sum_of_cterms; return $final_result; } function calculateSumofMulitples($val, $number) { //first, we begin by running an aithmetic prograssion for $val up to $number say N to get the no of terms [using T=a +(n-1)d] $no_of_terms = (int) ($number / $val); if($number % $val == 0) $no_of_terms = (int) ( ($number-1)/$val ); //since we are computing for a no of terms below N and not up to/exactly/up to N. if N divides val with no remainder, use no of terms = (N-1)/val //second, we compute the sum of terms [using Sn = n/2[2a + (n-1)d] $sum_of_terms = ($no_of_terms * 0.5) * ( (2*$val) + ($no_of_terms - 1) * $val ); // sum of multiples return $sum_of_terms; }
You can run a single loop checking whether the number is multiple of 3 OR 5: for ($i = 0; $i < $maxnumber; $i++) { if($i%3 || $i%5){ $t += $i; echo $i.',';} }
I think the original code is not including numbers which are multiples of both 3 and 5 in the total: if the test for multiple of 3 matches, it takes that and goes on. If you total the multiples of 15 up to 1000, you get 33165, which is exactly the difference between your total, 266333, and the original total, 233168.
Here's my solution to the question: <?php $sum = 0; $arr = []; for($i = 1; $i < 1000; $i++){ if((int)$i % 3 === 0 || (int)$i % 5 === 0) { $sum += $i; array_push($arr,$i); } } echo $sum; echo '<br>'; print_r($arr);//Displays the values meeting the criteria as an array of values