Switch elements of 2d array - php

I have a problem: I can't make my script working with penalties, but I made it work with bonuses.
Start: two-dimensional array of users, each has id, name, start position and modificator (if modificator < 0 its bonus; if modificator > 0 its penalty).
How modificators work: for example, I have a queue of users Test1, Test2, Test3. If I give user Test3 bonus 1, he will switch places with user Test 2 and final queue will be Test1, Test3, Test2. If I give him bous 2, than he will be first at queue.
Penalties works the other way around. If I give user Test1 penalty 1, he will be second in a queue. If I give him penalty 3, he will be moved to the end of the queue.
This is my working code for bonuses:
<?
$new = array(); $orders = array();
$new[111]['position'] = 1; $new[111]['user'] = 'Test1'; $orders[1] = 111; $new[111]['shift'] = 0;
$new[222]['position'] = 2; $new[222]['user'] = 'Test2'; $orders[2] = 222; $new[222]['shift'] = 0;
$new[333]['position'] = 3; $new[333]['user'] = 'Test3'; $orders[3] = 333; $new[333]['shift'] = 0;
$new[444]['position'] = 4; $new[444]['user'] = 'Test4'; $orders[4] = 444; $new[444]['shift'] = 0;
$new[555]['position'] = 5; $new[555]['user'] = 'Test5'; $orders[5] = 555; $new[555]['shift'] = 0;
$new[666]['position'] = 6; $new[666]['user'] = 'Test6'; $orders[6] = 666; $new[666]['shift'] = 0;
$new[777]['position'] = 7; $new[777]['user'] = 'Test7'; $orders[7] = 777; $new[777]['shift'] = 0;
$new[888]['position'] = 8; $new[888]['user'] = 'Test8'; $orders[8] = 888; $new[888]['shift'] = 0;
//shift function
function bonus($start, $bonus)
{
global $new, $orders;
//for bonuses, shift to start, $bonus is negative number
if ($bonus < 0)
{
for ($i = $start; $i > ($start - abs($bonus)); $i--)
{
$order_id = intval($orders[$i]);
$prev = intval($orders[$i - 1]);
if ($prev != 0)
{
$temp = $new[$order_id]['position'];
$new[$order_id]['position'] = $new[$prev]['position'];
$new[$prev]['position'] = $temp;
$orders[$i] = $prev;
$orders[$i - 1] = $order_id;
}
}
}
//end for bonuses
}
// Now I set modificator (bonus negative, penalty positive)
$new[555]['shift'] = -2;
// sort by bonuses
$i = 1;
foreach($new as $value)
{
$order_id = $orders[$i];
bonus($new[$order_id]['position'], $new[$order_id]['shift']);
$i++;
}
// sort 2d array with field position
usort($new, function($a, $b){
return $a['position'] <=> $b['position'];
});
//DATA OUTPUT
$i = 1;
foreach($new as $value)
{
echo $i . '. ' . $value['user'] . ' ; shift: ' . $value['shift'];
echo '<br>';
$i++;
}
?>
At this code I set bonus with: $new[555]['shift'] = -2;
So, penalty will be like: $new[444]['shift'] = 3;
Code for penalties I think must be something like this:
//for penalty, shift to the end, $bonus positive number
if ($bonus > 0)
{
$total_new = count($new);
for ($i = $start; $i < ($start + $bonus); $i++)
{
$order_id = intval($orders[$i]);
$next = intval($orders[$i + 1]);
if ($next < $total_new)
{
$temp = $new[$order_id]['position'];
$new[$order_id]['position'] = $new[$next]['position'];
$new[$next]['position'] = $temp;
$orders[$i] = $next;
$orders[$i + 1] = $order_id;
}
}
}
//end code for penalty
Here is something wrong with $orders. I tried many different types of code all day long, including array enumeration in asc/desc order, but it didn't work correcty...
P.S. I know about array_multisort, but I dont need to sort, I need to switch elements with each other.
Again: bonus 2 means that user must switch places with previous number 2 times. Penalty 3 means that user must switch places with next user 3 times.

Related

PHP: Getting random combinations to specified input

I am trying to display possibilities for additions of specific numbers but have not been getting the right results.
<?php
$num3 = 20;
$set = null;
$balance = $num3;
$dig = mt_rand(1,5);
for($i = $balance; $i > 0; $i -= $dig ){
echo $dig.'<br>';
if($i < 1){
$set .= $i;
$balance = 0;
}
else{
$set .= $dig.'+';
$dig = mt_rand(1,5);
}
}
echo $set.'='.$num3;
?>
Here are some of the outputs:
2+5+1+4+5+3+=20
1+4+3+5+3+=20
3+1+1+2+3+4+4+1+3+=20
Appreciate any pointers. Thank in advance...
Ok, even though the requirement isn't completely clear, here's an approach:
(edit: demonstrating prevention of endless loop)
$max_loops = 1000;
$balance = 20;
$found = [];
while($balance > 0 && $max_loops-- > 0) {
$r = mt_rand(1, 5);
if ($balance - $r >= 0) {
$found[] = $r;
$balance -= $r;
}
}
echo implode(' + ', $found) . ' = '.array_sum($found);
Note: This code has a small risk of getting caught in an endless loop... though it's doubtful that it'll ever happen :)
Edit: Now the loop contains a hard-limit of 1000 iterations, after which the loop will end for sure...
To provoke an endless loop, set $balance = 7 and modify mt_rand(4, 5).
You can use a recursive function for this:
function randomAddends($target, $maxAddend = 5, $sum = 0, $addends = [])
{
// Return the array of addends when the target is reached
if ($target <= $sum) {
return $addends;
}
// generate a new random addend and add it to the array
$randomAddend = mt_rand(1, min($maxAddend, $target - $sum));
$addends[] = $randomAddend;
// make the recursive call with the new sum
return randomAddends($target, $maxAddend, $sum + $randomAddend, $addends);
}

distribute a number without remainder

I want to distribute an item to specific pieces without cutting the excess piece,
example:
$persons = 7;
$rooms = 2;
for($i = 0; $i < $rooms; $i++)
{
//distribute the persons per room
}
//the endpoint should be
$the_room[0] = 4 //instead of 3.5
$the_room[1] = 3 //instead of 3.5
How about an approach that first equally distributes pieces, then randomises the remainder?
$persons = 7;
$rooms = 2;
$the_room = array();
for($i = 0; $i < $rooms; $i++)
{
$the_room[$i] = floor($persons / $rooms);
}
// Random Distribution
while( $persons - array_sum($the_room) > 0)
{
$the_room[array_rand($the_room)]++;
}
// Sequential Distribution
// $index = 0;
// while( $persons - array_sum($the_room) > 0)
// {
// $the_room[$index++]++;
// }
print_r($the_room);

What are better ways to insert element in sorted array in PHP

I've recently send my CV to one company that was hiring PHP developers. They send me back a task to solve, to mesure if I'm experienced enough.
The task goes like that:
You have an array with 10k unique elements, sorted descendant. Write function that generates this array and next write three different functions which inserts new element into array, in the way that after insert array still will be sorted descendant. Write some code to measure speed of those functions. You can't use PHP sorting functions.
So I've wrote function to generate array and four functions to insert new element to array.
/********** Generating array (because use of range() was to simple :)): *************/
function generateSortedArray($start = 300000, $elementsNum = 10000, $dev = 30){
$arr = array();
for($i = 1; $i <= $elementsNum; $i++){
$rand = mt_rand(1, $dev);
$start -= $rand;
$arr[] = $start;
}
return $arr;
}
/********************** Four insert functions: **************************/
// for loop, and array copying
function insert1(&$arr, $elem){
if(empty($arr)){
$arr[] = $elem;
return true;
}
$c = count($arr);
$lastIndex = $c - 1;
$tmp = array();
$inserted = false;
for($i = 0; $i < $c; $i++){
if(!$inserted && $arr[$i] <= $elem){
$tmp[] = $elem;
$inserted = true;
}
$tmp[] = $arr[$i];
if($lastIndex == $i && !$inserted) $tmp[] = $elem;
}
$arr = $tmp;
return true;
}
// new element inserted at the end of array
// and moved up until correct place
function insert2(&$arr, $elem){
$c = count($arr);
array_push($arr, $elem);
for($i = $c; $i > 0; $i--){
if($arr[$i - 1] >= $arr[$i]) break;
$tmp = $arr[$i - 1];
$arr[$i - 1] = $arr[$i];
$arr[$i] = $tmp;
}
return true;
}
// binary search for correct place + array_splice() to insert element
function insert3(&$arr, $elem){
$startIndex = 0;
$stopIndex = count($arr) - 1;
$middle = 0;
while($startIndex < $stopIndex){
$middle = ceil(($stopIndex + $startIndex) / 2);
if($elem > $arr[$middle]){
$stopIndex = $middle - 1;
}else if($elem <= $arr[$middle]){
$startIndex = $middle;
}
}
$offset = $elem >= $arr[$startIndex] ? $startIndex : $startIndex + 1;
array_splice($arr, $offset, 0, array($elem));
}
// for loop to find correct place + array_splice() to insert
function insert4(&$arr, $elem){
$c = count($arr);
$inserted = false;
for($i = 0; $i < $c; $i++){
if($elem >= $arr[$i]){
array_splice($arr, $i, 0, array($elem));
$inserted = true;
break;
}
}
if(!$inserted) $arr[] = $elem;
return true;
}
/*********************** Speed tests: *************************/
// check if array is sorted descending
function checkIfArrayCorrect($arr, $expectedCount = null){
$c = count($arr);
if(isset($expectedCount) && $c != $expectedCount) return false;
$correct = true;
for($i = 0; $i < $c - 1; $i++){
if(!isset($arr[$i + 1]) || $arr[$i] < $arr[$i + 1]){
$correct = false;
break;
}
}
return $correct;
}
// claculates microtimetime diff
function timeDiff($startTime){
$diff = microtime(true) - $startTime;
return $diff;
}
// prints formatted execution time info
function showTime($func, $time){
printf("Execution time of %s(): %01.7f s\n", $func, $time);
}
// generated elements num
$elementsNum = 10000;
// generate starting point
$start = 300000;
// generated elements random range 1 - $dev
$dev = 50;
echo "Generating array with descending order, $elementsNum elements, begining from $start\n";
$startTime = microtime(true);
$arr = generateSortedArray($start, $elementsNum, $dev);
showTime('generateSortedArray', timeDiff($startTime));
$step = 2;
echo "Generating second array using range range(), $elementsNum elements, begining from $start, step $step\n";
$startTime = microtime(true);
$arr2 = range($start, $start - $elementsNum * $step, $step);
showTime('range', timeDiff($startTime));
echo "Checking if array is correct\n";
$startTime = microtime(true);
$sorted = checkIfArrayCorrect($arr, $elementsNum);
showTime('checkIfArrayCorrect', timeDiff($startTime));
if(!$sorted) die("Array is not in descending order!\n");
echo "Array OK\n";
$toInsert = array();
// number of elements to insert from every range
$randElementNum = 20;
// some ranges of elements to insert near begining, middle and end of generated array
// start value => end value
$ranges = array(
300000 => 280000,
160000 => 140000,
30000 => 0,
);
foreach($ranges as $from => $to){
$values = array();
echo "Generating $randElementNum random elements from range [$from - $to] to insert\n";
while(count($values) < $randElementNum){
$values[mt_rand($from, $to)] = 1;
}
$toInsert = array_merge($toInsert, array_keys($values));
}
// some elements to insert on begining and end of array
array_push($toInsert, 310000);
array_push($toInsert, -1000);
echo "Generated elements: \n";
for($i = 0; $i < count($toInsert); $i++){
if($i > 0 && $i % 5 == 0) echo "\n";
printf("%8d, ", $toInsert[$i]);
if($i == count($toInsert) - 1) echo "\n";
}
// functions to test
$toTest = array('insert1' => null, 'insert2' => null, 'insert3' => null, 'insert4' => null);
foreach($toTest as $func => &$time){
echo "\n\n================== Testing speed of $func() ======================\n\n";
$tmpArr = $arr;
$startTime = microtime(true);
for($i = 0; $i < count($toInsert); $i++){
$func($tmpArr, $toInsert[$i]);
}
$time = timeDiff($startTime, 'checkIfArraySorted');
showTime($func, $time);
echo "Checking if after using $func() array is still correct: \n";
if(!checkIfArrayCorrect($tmpArr, count($arr) + count($toInsert))){
echo "Array INCORRECT!\n\n";
}else{
echo "Array OK!\n\n";
}
echo "Few elements from begining of array:\n";
print_r(array_slice($tmpArr, 0, 5));
echo "Few elements from end of array:\n";
print_r(array_slice($tmpArr, -5));
//echo "\n================== Finished testing $func() ======================\n\n";
}
echo "\n\n================== Functions time summary ======================\n\n";
print_r($toTest);
Results can be found here: http://ideone.com/1xQ3T
Unfortunately I was rated only 13 points out of 30 for this task (don't know how it was calculated or what exactly was taken in account). I can only assume that's because there are better ways to insert new element into sorted array in PHP. I'm searching this topic for some time now but couldn't find anything good. Maby you know of better approach or some articles about that topic?
Btw on my localhost (PHP 5.3.6-13ubuntu3.6 with Suhosin-Patch, AMD Athlon(tm) II X4 620) insert2() is fastest, but on ideone (PHP 5.2.11) insert3() is fastest.
Any ideas why? I suppose that array_splice() is tuned up somehow :).
//EDIT
Yesterday I thought about it again, and figured out the better way to do inserts. If you only need sorted structure and a way to iterate over it and your primary concern is the speed of insert operation, than the best choise would be using SplMaxHeap class. In SplMaxHeap class inserts are damn fast :) I've modified my script to show how fast inserts are. Code is here: http://ideone.com/vfX98 (ideone has php 5.2 so there won't be SplMaxHeap class)
On my localhost I get results like that:
================== Functions time summary ======================
insert1() => 0.5983521938
insert2() => 0.2605950832
insert3() => 0.3288729191
insert4() => 0.3288729191
SplMaxHeap::insert() => 0.0000801086
It may just be me, but maybe they were looking for readability and maintainability as well?
I mean, you're naming your variables $arr, and $c and $middle, without even bothering to place proper documentation.
Example:
/**
* generateSortedArray() Function to generate a descending sorted array
*
* #param int $start Beginning with this number
* #param int $elementsNum Number of elements in array
* #param int $dev Maximum difference between elements
* #return array Sorted descending array.
*/
function generateSortedArray($start = 300000, $elementsNum = 10000, $dev = 30) {
$arr = array(); #Variable definition
for ($i = 1; $i <= $elementsNum; $i++) {
$rand = mt_rand(1, $dev); #Generate a random number
$start -= $rand; #Substract from initial value
$arr[] = $start; #Push to array
}
return $arr;
}

Recursively find combinations in PHP

I have the following structure:
A 3
B 2
C 2
D 1
E 0
While letters represent the actual value of the element, numbers represent the level of the element. I want to be able to output the following:
A B D E
A C D E
the code that I have right now is not doing the job properly and I need to find a recursive way to address the problem. Any help would be really appreciated.
<?php
// An array that holds the values
$values = array();
$values[] = "A";
$values[] = "B";
$values[] = "C";
$values[] = "D";
$values[] = "E";
// An array that holds the levels
$levels = array();
$levels[] = 3;
$levels[] = 2;
$levels[] = 2;
$levels[] = 1;
$levels[] = 0;
// We are asuming that 3 is the heighest level
$startingLevel = 3;
// this array will holds all combinations. each group is seperated by a |
$results = array();
for($i = 0; $i < count($values); $i++)
{
$thisValue = $values[$i];
$thisLevel = $levels[$i];
if($thisLevel == $startingLevel)
{
$results[] = $thisValue;
$j = 0;
$k = $i;
$limit = $thisLevel;
while($j < $thisLevel)
{
if($levels[$k] < $limit)
{
$results[] = $values[$k];
$limit = $levels[$k];
$j++;
}
$k++;
}
// separating groups by |
$results[] = "|";
}
}
// Show results
print_r($results);
?>
Most likely what you want is to generate all combinations with O((n^2-n)/2) and then compare it with the 2nd array and also what you want is to look at my example in Javascript. Array Waypoints hold your first Array. Array wayStr holds your solution. Then you need only to iterate through the solution and compare it with your 2nd array.
function getWayStr(curr) {
var nextAbove = -1;
for (var i = curr + 1; i < waypoints.length; ++i) {
if (nextAbove == -1) {
nextAbove = i;
} else {
wayStr.push(waypoints[i]);
wayStr.push(waypoints[curr]);
}
}
if (nextAbove != -1) {
wayStr.push(waypoints[nextAbove]);
getWayStr(nextAbove);
wayStr.push(waypoints[curr]);
}
}
Separate the different levels and use permutation:
lexicographic ordering

PHP - Patterns within Arrays

I am trying to create a function which maps a recurring pattern of integers using an array.
As an example if I have a starting array of (0,1,3) and I know that I want to stop the pattern when I hit 15.
The pattern gets incremented by a fixed integer each time (lets say 4) so my final pattern should be..
0
1
3
4 (0 + 4)
5 (1 + 4)
7 (2 + 4)
8 (4 + 4)
9 (5 + 4)
11(7 + 4)
12(8 + 4)
13(9 + 4)
15(11+ 4)
Does anyone have any pointers on how this can be achieved?
My current implementation works but is stupidly inefficient which something like this...
$array = array(0,1,3);
$inc = 4;
$end = end($array);
$final = 15;
while($end < $final)
{
$tmp = array();
foreach($array AS $row)
{
$tmp = $row + $inc;
}
$array = merge($tmp, $array);
$end = end($array);
}
$array = array(0,1,3);
$inc = 4;
$final = 15;
$end = end($array);
while($end < $final)
{
$end += $inc;
$array[] = $end;
}
Or with a for loop:
$array = array(0,1,3);
$inc = 4;
$final = 15;
for($i = end($array) + $inc; $i <= $final; $i += $inc)
{
$array[] = $i;
}
Y'all are missing the fact that 4 is being added to the value in the array 2 keys back, not the last value.
This is the code you need (tested, and working)
$array = array(0,1,3);
$inc = 4;
$end = end($array);
$key = key($array);
$final = 15;
while ($end < $final) {
if ($array[$key-2] >= 0) {
$end = $array[$key-2] + $inc;
$array[] = $end;
$key++;
}
}
I also included in there a check to make sure the key being added to actually exists, though that may not be needed.
I assume that you want to have all the new values in the same array.
So:
//original array
$values = array(0, 1, 3);
//incremental value
$inc = 4;
//stop value
$stop = 15;
//set the index counter to the origin
$curr_index = 0;
//while the last value of the array is lower than the stop value
while($values[end($values)] < $stop)
{
//calculate the new value
$new_value = $values[$curr_index] + $inc;
//add the new value to the array
array_push($values, $new_value);
//update the index counter
$curr_index ++;
}
this code should work for any initial value in the array, any incremental value and any stop value.
<?php
function myArrayFunction(array $array, $inc = 4, $final = 15, $end = null)
{
if(!$end)
{
$end = end($array);
}
while($end < $final)
{
$end += $inc;
$array[] = $end;
}
return $array; //assume you're wanting $array back
}
This is minus any sort of testing or checking of injected values but you get the idea.
It would be better to know what you are trying to achieve here as the whole thing looks horribly overcomplicated, but...
$array = array(0,1,3);
$pattern = array();
$inc = 4;
$final = 15;
for ($base = 0; ; $base += $inc) {
foreach($array as $rem) {
if ($base + $rem > $final) break 2;
$pattern []= $base + $rem;
}
}
Alternatively,
$i = $v = 0;
while ($v < $final) {
$v = $pattern []= $pattern[$i++] + $inc;
}
(This assumes $final will be part of the pattern.)
If you can figure out how to calculate the number of elements will be in the array beforehand and assign that to $tum this should work.
<?php
$arr = array(0, 1, 3);
$inc = 4; // 6
$fin = 15; // 55
$num = count($arr);
$lum = 0;
$tum = 12; // 29
do
{
for($i = $lum; $i < $num; $i++)
{
$tar = $arr[$i] + $inc;
$arr[$tar] = $tar;
}
$lum = $num;
$num *= 2;
} while(end($arr) < $fin);
$arr = array_slice($arr, 0, $tum);
print_r($arr);
echo "\n";
?>

Categories