Distributing x amount of gold coins to n number of thieves - php

Suppose if there are 10 thieves and 100 gold coins to be distributed and the distribution pattern goes like this:
Thief 1 gets 1 coin.
Thief 2 gets 2 coins.
Thief 3 gets 3 coins.... and so on upto 10.
When all the thief have received the coins then the sum of total coins will be (1+2+3+... = 55 coins). Now the coins left are 45. Now, how can I start redistribution from thief one but with the last incremented value instead of starting from 1 coin again? Like 1st thief in 2nd round should get 11 coins instead of 1 and this should continue until all the coins are distributed and coin left is 0. If the last thief has to get 7 coins but the coin available is 3 then he should get 3 coins and the distribution should be over.
I tried this...
$thieves = 10;
$goldCoins = 100;
$thiefArr = range(1, $thieves);
$assgnCoins = 0;
foreach($thiefArr as $key => $value){
for($i=1; $i<=$goldCoins; $i++){
$assgnCoins = $value; // This assigns first round of coins but how to redistribute it again I have no idea.
}
echo "Thief ".$value." will have ".$assgnCoins." gold coins. <br><br>";
}

I'm not really sure if I understood your problem correctly but here's my take, hope this helps:
<?php
$numt = 10; //Total number of thieves
$totalCoins = 100; //Total coins to redistribute
$data = []; //Data array (will contains the index, which is the "thief" number, and the value of a specific index is the total amount of coins for that specific thief
//Loop init: $coins is the amount of coins given to a thief in a specific distribution round
//Loop condition: We loop until there are still coins to redistribute
//Loop statement: We add 1 coin to each redistribution round
// (I start from 0 and use $coins + 1 because I do modulus on $numt ($coins % $numt) which will give me a number from 0 to $numt - 1 (Hint/fact: array indexes start from 0 and not 1)
for ($coins = 0; $totalCoins > 0; $coins++)
{
//This will always give me a number between 0 and $numt - 1 (so that we know which thief's turn is)
//(If this is not clear, you can print out $coins and $thiefIndex then you'll see/understand why I do this)
$thiefIndex = ($coins % $numt);
//Because we did not initialize $data values, we check if this specific thief's coins amount has been initialized
if (!isset($data[$thiefIndex]))
{
$data[$thiefIndex] = 0; //Every thief starts with 0 coins
}
//Because we started from 0, we need to add 1 (this is the amount of coins that this thief ($thiefIndex) will get in this redistribution round
$coinsToGive = ($coins + 1);
//If there's not enough coins left, we just give the total coins remaining
if ($totalCoins < $coinsToGive)
{
$coinsToGive = $totalCoins;
}
//Here we sum up all coins that a thief receives in a specific redistribution round
$data[$thiefIndex] += $coinsToGive;
//We need to subtract the given coins to the total conins that we redistribute (this makes the loop break when it reaches 0 (see the loop condition part)
$totalCoins -= $coinsToGive;
}
//Use data/print in your case
foreach ($data as $idx => $tot)
{
echo "Thief ".($idx + 1)." will have ".$tot." gold coins. <br />";
}
EDIT: I think you edited the question while I was writing my answer. Anyways, if you want to see how much coins every thief is getting on every redistribution round, you can add an echo in the first loop (at the end).
If you're new to programming, you may not know what += or -= means:
$foo += $bar;
Is the same as:
$foo = $foo + $bar;
This can be applied to every arithmetical operator like +, -, *, / (and even other signs like &= and |=, etc... but those will be bit-wise operations)
Information about operators can be found online on the PHP website.

<?php
$thieves = range(1, 10);
$availableCoins = 100;
$assignCoins = 0;
while($availableCoins > 0) {
foreach($thieves as $thief) {
// increase the coins to assign
$assignCoins++;
// check if assign coins are greater than the availableCoins
if ($assignCoins > $availableCoins) {
$assignCoins = $availableCoins;
}
echo "Thief ".$thief." will have ".$assignCoins." gold coins. <br><br>";
// substract assignedCoins from availableCoins
$availableCoins -= $assignCoins;
// break the loop if no coins left
if ($availableCoins <= 0) {
break;
}
}
}

I have now edited the answer of #julian S to give the result that the homework should give:
I also have added some more HTMl input to demonstrate the structure of the array the running operations and the resulting array in readable form.
In short:
The range was wrong here.
Because it will fill an array with 1,2,3,4,5,6,7,8,9, that already is a numerical array so member 1 has a value 1 ?? Useless. NO!
So we crate an array of the 10thives weach with teh initial value = - because teh thieves have = money initially.
We print this array and add pre so that it looks a little better :-)
Then we have two loops - the outer while loop that has the function to look if there is still money left. if there is still moey left then make another round.
That was the logic missing to the original poster as he did understand that he needed a loop for the 10 thieves.
Then there is the INNER LOOP
just count from 1 to the number of thieves. So each thief will get its share.
$a += $b is the short form of $a=$a+$b
Add the currentcoins to the current arrray member
Add a break to leave the for loop if the monex is gone before you are at the end of the 10 thieves.
At the end just print the array with the wealth of the thieves.
0-9 as arrays start with 0.
<?php
$thieves = 10;
$availableCoins = 100;
$assignCoins = 0;
$array_thieves= array_fill(0,10,0);
echo "<h2>Initial array with wealth of each thief</h2> ";
echo "<pre>";
print_r($array_thieves)."<br>";
echo"</pre>";
while($availableCoins > 0) { //inerate as long as there are coins - outer loop
for($i=0; $i<$thieves; $i++) { //execute loop once per thieve so 10 x per round -inner loop
// increase the coins to assign
$assignCoins++; //we have started with 0 and each time we add 1
// check if assign coins are greater than the availableCoins
//if the number of available coins is smaller then just assign all available coins
if ($assignCoins > $availableCoins) {
$assignCoins = $availableCoins;
}
$array_thieves[$i] += $assignCoins; //add the coins to the thied (= array member) with the current number $i [0-9]
echo "Thief ". $i ." gets now additional ".$assignCoins." gold coins. He has now a total of: ". $array_thieves[$i] ." <br>";
// substract assignedCoins from availableCoins
$availableCoins -= $assignCoins;
// break the FOR loop if no coins left - otherwise the inner loop will continue
if ($availableCoins <= 0) {
break;
}
}
}
echo"<h2>Resulting array with all thieves and their wealth</h2>";
echo"<pre>";
print_r($array_thieves);
echo"</pre>";

Related

Less than operator not working properly in a PHP while loop

I'm trying to do some calculations on getting the total points used in a requested amount of money based on the amount per points.
Kindly refer to below code:
<?php
$amount_perpoints = bcdiv(622.9106666666667,1,2); // AMOUNT OF MONEY PER POINTS
$request_amount = 3114.55; //REQUESTED AMOUNT OF POINTS
$points = 0; // THIS WILL CONTAIN THE TOTAL POINTS
$total_amount = 0; // THIS WILL INCREMENT ACCORDING TO THE PRODUCT OF THE CURRENT POINT AND AMOUNT PER POINTS
while($total_amount < $request_amount){
$points = $points+0.50; //POINTS INCREMENTING BY 0.5
$total_amount = $points * $amount_perpoints;
}
echo $points;
?>
Output: 5.50
Above scenario outputs 5.50 but I believe it should only be 5.00. At 5.00 points of the iteration, $total_amount and $request_amount has already same value of 3114.55. Why does the while loop still satisfies even the two values of the comparing variables are not less than BUT EQUAL?
Your are comparing float numbers. Try to use
while (bccomp($total_amount, $request_amount) === -1) {
I think #jeff is right:
$amount_perpoints = bcdiv(622.9106666666667,1,2); // AMOUNT OF MONEY PER POINTS
$request_amount = 3114.55; //REQUESTED AMOUNT OF POINTS
$points = 0; // THIS WILL CONTAIN THE TOTAL POINTS
$total_amount = 0; // THIS WILL INCREMENT ACCORDING TO THE PRODUCT OF THE CURRENT POINT AND AMOUNT PER POINTS
while($total_amount <= $request_amount){
$points = $points+0.50; //POINTS INCREMENTING BY 0.5
$total_amount = $points * $amount_perpoints;
}
echo $points;
Since you count untill it reach the desired point it sould be less OR equal. It give the output of : 5.5
<?php
$amount_perpoints = bcdiv(622.9106666666667,1,2); // AMOUNT OF MONEY PER POINTS
$request_amount = 3114.55; //REQUESTED AMOUNT OF POINTS
$points = 0; // THIS WILL CONTAIN THE TOTAL POINTS
$new_points=0;
$total_amount = 0; // THIS WILL INCREMENT ACCORDING TO THE PRODUCT OF THE CURRENT POINT AND AMOUNT PER POINTS
while($total_amount < $request_amount){
$points=$new_points;
$new_points = $new_points+0.50; //POINTS INCREMENTING BY 0.5
$total_amount = $new_points * $amount_perpoints;
}
echo $points;
?>
Errors occur when calculating with fractional fractions as a float. Float cannot represent certain decimal numbers exactly. Use only the BC Math functions for such calculations. You don't need a loop either.
$amount_perpoints = "622.91"; // AMOUNT OF MONEY PER POINTS
$request_amount = "3114.55"; //REQUESTED AMOUNT OF POINTS for result 5.0
//$request_amount = "3426.005"; //REQUESTED AMOUNT OF POINTS for result 5.5
$point_step = "0.50";
//calculation
$points = bcdiv($request_amount,$point_step,2);
$points = bcdiv($points,$amount_perpoints,0);
$points = bcmul($points,$point_step,1);
//output
var_dump($points); //string(3) "5.0"

PHP: Optimizing array iteration

i am working on an algorithm for sorting teams based on highest number of score. Teams are to be generated from a list of players. The conditions for creating a team is
It should have 6 players.
The collective salary for 6 players must be less than or equal to 50K.
Teams are to be generated based on highest collective projection.
What i did to get this result is generate all possibilities of team then run checks on them to exclude those teams that have more than 50K salary and then sort the remainder based on projection. But generating all the possibilities takes a lot of time and sometimes it consume all the memory. For a list of 160 players it takes around 90 seconds. Here is the code
$base_array = array();
$query1 = mysqli_query($conn, "SELECT * FROM temp_players ORDER BY projection DESC");
while($row1 = mysqli_fetch_array($query1))
{
$player = array();
$mma_id = $row1['mma_player_id'];
$salary = $row1['salary'];
$projection = $row1['projection'];
$wclass = $row1['wclass'];
array_push($player, $mma_id);
array_push($player, $salary);
array_push($player, $projection);
array_push($player, $wclass);
array_push($base_array, $player);
}
$result_base_array = array();
$totalsalary = 0;
for($i=0; $i<count($base_array)-5; $i++)
{
for($j=$i+1; $j<count($base_array)-4; $j++)
{
for($k=$j+1; $k<count($base_array)-3; $k++)
{
for($l=$k+1; $l<count($base_array)-2; $l++)
{
for($m=$l+1; $m<count($base_array)-1; $m++)
{
for($n=$m+1; $n<count($base_array)-0; $n++)
{
$totalsalary = $base_array[$i][1]+$base_array[$j][1]+$base_array[$k][1]+$base_array[$l][1]+$base_array[$m][1]+$base_array[$n][1];
$totalprojection = $base_array[$i][2]+$base_array[$j][2]+$base_array[$k][2]+$base_array[$l][2]+$base_array[$m][2]+$base_array[$n][2];
if($totalsalary <= 50000)
{
array_push($result_base_array,
array($base_array[$i], $base_array[$j], $base_array[$k], $base_array[$l], $base_array[$m], $base_array[$n],
$totalprojection, $totalsalary)
);
}
}
}
}
}
}
}
usort($result_base_array, "cmp");
And the cmp function
function cmp($a, $b) {
if ($a[6] == $b[6]) {
return 0;
}
return ($a[6] < $b[6]) ? 1 : -1;
}
Is there anyway to reduce the time it takes to do this task, or any other workaround for getting the desired number of teams
Regards
Because number of elements in array can be very big (for example 100 players can generate 1.2*10^9 teams), you can't hold it in memory. Try to save resulting array to file by parts (truncate array after each save). Then use external file sorting.
It will be slow, but at least it will not fall because of memory.
If you need top n teams (like 10 teams with highest projection) then you should convert code that generates result_base_array to Generator, so it will yield next team instead of pushing it into array. Then iterate over this generator. On each iteration add new item to sorted resulted array and cut redundant elements.
Depending on whether the salaries are often the cause of exclusion, you could perform tests on this in the other loops as well. If after 4 player selections their summed salaries are already above 50K, there is no use to select the remaining 2 players. This could save you some iterations.
This can be further improved by remembering the lowest 6 salaries in the pack, and then check if after selecting 4 members you would still stay under 50K if you would add the 2 lowest existing salaries. If this is not possible, then again it is of no use to try to add the two remaining players. Of course, this can be done at each stage of the selection (after selecting 1 player, 2 players, ...)
Another related improvement comes into play when you sort your data by ascending salary. If after selecting the 4th player, the above logic brings you to conclude you cannot stay under 50K by adding 2 more players, then there is no use to replace the 4th player with the next one in the data series either: that player would have a greater salary, so it would also yield to a total above 50K. So that means you can backtrack immediately and work on the 3rd player selection.
As others pointed out, the number of potential solutions is enormous. For 160 teams and a team size of 6 members, the number of combinations is:
160 . 159 . 158 . 157 . 156 . 155
--------------------------------- = 21 193 254 160
6 . 5 . 4 . 3 . 2
21 billion entries is a stretch for memory, and probably not useful to you either: will you really be interested in the team at the 4 432 456 911th place?
You'll probably be interested in something like the top-10 of those teams (in terms of projection). This you can achieve by keeping a list of 10 best teams, and then, when you get a new team with an acceptable salary, you add it to that list, keeping it sorted (via a binary search), and ejecting the entry with the lowest projection from that top-10.
Here is the code you could use:
$base_array = array();
// Order by salary, ascending, and only select what you need
$query1 = mysqli_query($conn, "
SELECT mma_player_id, salary, projection, wclass
FROM temp_players
ORDER BY salary ASC");
// Specify with option argument that you only need the associative keys:
while($row1 = mysqli_fetch_array($query1, MYSQLI_ASSOC)) {
// Keep the named keys, it makes interpreting the data easier:
$base_array[] = $row1;
}
function combinations($base_array, $salary_limit, $team_size) {
// Get lowest salaries, so we know the least value that still needs to
// be added when composing a team. This will allow an early exit when
// the cumulative salary is already too great to stay under the limit.
$remaining_salary = [];
foreach ($base_array as $i => $row) {
if ($i == $team_size) break;
array_unshift($remaining_salary, $salary_limit);
$salary_limit -= $row['salary'];
}
$result = [];
$stack = [0];
$sum_salary = [0];
$sum_projection = [0];
$index = 0;
while (true) {
$player = $base_array[$stack[$index]];
if ($sum_salary[$index] + $player['salary'] <= $remaining_salary[$index]) {
$result[$index] = $player;
if ($index == $team_size - 1) {
// Use yield so we don't need to build an enormous result array:
yield [
"total_salary" => $sum_salary[$index] + $player['salary'],
"total_projection" => $sum_projection[$index] + $player['projection'],
"members" => $result
];
} else {
$index++;
$sum_salary[$index] = $sum_salary[$index-1] + $player['salary'];
$sum_projection[$index] = $sum_projection[$index-1] + $player['projection'];
$stack[$index] = $stack[$index-1];
}
} else {
$index--;
}
while (true) {
if ($index < 0) {
return; // all done
}
$stack[$index]++;
if ($stack[$index] <= count($base_array) - $team_size + $index) break;
$index--;
}
}
}
// Helper function to quickly find where to insert a value in an ordered list
function binary_search($needle, $haystack) {
$high = count($haystack)-1;
$low = 0;
while ($high >= $low) {
$mid = (int)floor(($high + $low) / 2);
$val = $haystack[$mid];
if ($needle < $val) {
$high = $mid - 1;
} elseif ($needle > $val) {
$low = $mid + 1;
} else {
return $mid;
}
}
return $low;
}
$top_team_count = 10; // set this to the desired size of the output
$top_teams = []; // this will be the output
$top_projections = [];
foreach(combinations($base_array, 50000, 6) as $team) {
$j = binary_search($team['total_projection'], $top_projections);
array_splice($top_teams, $j, 0, [$team]);
array_splice($top_projections, $j, 0, [$team['total_projection']]);
if (count($top_teams) > $top_team_count) {
// forget about lowest projection, to keep memory usage low
array_shift($top_teams);
array_shift($top_projections);
}
}
$top_teams = array_reverse($top_teams); // Put highest projection first
print_r($top_teams);
Have a look at the demo on eval.in, which just generates 12 players with random salary and projection data.
Final remarks
Even with the above mentioned optimisations, doing this for 160 teams might still require a lot of iterations. The more often the salaries amount to more than 50K, the better the performance will be. If this never happens, the algorithm cannot escape from having to look at each of the 21 billion combinations. If you would know beforehand that the 50K limit would not play any role, you would of course order the data by descending projection, like you originally did.
Another optimisation could be if you would feed back into the combination function the 10th highest team projection you have so far. The function could then eliminate combinations that would lead to a lower total projection. You could first take the 6 highest player projection values and use this to determine how high a partial team projection can still grow by adding the missing players. It might turn out that this becomes impossible after having selected a few players, and then you can skip some iterations, much like done on the basis of salaries.

Generating random variable from array based on weight percentage

I have the following code:
<?php
$gender = array(
'Male'=>30,
'Female'=>50,
'U' =>20);
$total = array_sum(array_values($gender));
$current = 0;
$rand = rand(1,$total);
foreach ($gender as $key=>$value)
{
$current += $value;
if ($current > $rand)
{
echo $key;
}
}
?>
However, when I run it, I sometimes get:
MaleFemaleU
Or:
FemaleU
I set the values for the $gender arrays, and would like to generate the gender based on the percentage given, i.e. in this case: male 30, female 50, and unknown 20.
You need to stop looping when you display the variable.
foreach ($gender as $key=>$value)
{
$current += $value;
if ($current > $rand)
{
echo $key;
break; // Terminate the loop
}
}
That means that all those items are reaching the value needed to be echo'd but you aren't putting a line between then.
if($current > $rand) {
echo $key . "<br>";
}
Try that so they will display on a new line. From there we can work on the math.
rand will be a value within the range 1..100 since you're summing the values in the array. So then in the foreach loop you will print out all the values whenever `rand generates a value small enough to print out all the values.
For example, lets say $rand = 1, then $current will always be greater than $rand, since it can only as a lowest value have 20.
So, yeah, you most probably wan't to throw in a little break there.
Random thoughts
I'm a bit suspicious about the logic in this code however, will this give you a correct percentage? That is that 20% of the time when you run this the gender will be U, etc. ? Something looks fishy to me.
A better way of checking percentages (experimental) *
With the current solution I don't think you'll get the distribution of 20% U, 30% Male and 50% Female.
Since U has a lower percentage than Male and Female in your algorithm are greater than 20 the condition that would render U will always generate true for Male and Female also. Thus, with this algorithm you will never get the value U.
I suggest an easier approach by using ranges, that is for example 1-20 = U, 21-50 = Male and 51 - 100 = Female, the order is not important, just that the range equals the precentage. Then it's just a trivial matter of checking which range the rand is in.

php calculation solution for pagination

I have a variable $total which is the total number of results and $page which is the page number. The result is limited to 12 per page.
Suppose if $total is 24, the script may return 1 and 2 for $page=1 and $page=2 respectively. It should also return 1 if the input number is less than 1 (negative or zero) or if the number is greater than 2
Again, suppose if $total is 25, the script may return 1, 2 and 3 for $page=1, $page=2 and $page=3 respectively. It should also return 1 if the input number is less than 1 (negative or zero) or if the number is greater than 1
Here's one way to calculate it:
// Assuming you have the $total variable which contains the total
// number of records
$recordsPerPage = 12;
// Declare a variable which will hold the number of pages required to
// display all the records, when displaying #recordsPerPage records on each page
$maxPages = 1;
if($total > 0)
$maxPages = (($total - 1) / $recordsPerPage) + 1;
// $maxPages now contains the number of pages required. you can do whatever
// it is you need to do with it. It wasn't clear from the question..
return $maxPages;
Further, if you wanted to generate an array containing the indexes of each available page you could just do this:
$pages = array();
for($i = 1; $i <= $maxPages; i++)
{
array_push($pages, $i);
}
print_r($pages);

Create numbers within an array that add up to a set amount

I'm fairly new to PHP - programming in general. So basically what I need to accomplish is, create an array of x amount of numbers (created randomly) whose value add up to n:
Let's say, I have to create 4 numbers that add up to 30. I just need the first random dataset. The 4 and 30 here are variables which will be set by the user.
Essentially something like
x = amount of numbers;
n = sum of all x's combined;
// create x random numbers which all add up to n;
$row = array(5, 7, 10, 8) // these add up to 30
Also, no duplicates are allowed and all numbers have to be positive integers.
I need the values within an array. I have been messing around with it sometime, however, my knowledge is fairly limited. Any help will be greatly appreciated.
First off, this is a really cool problem. I'm almost sure that my approach doesn't even distribute the numbers perfectly, but it should be better than some of the other approaches here.
I decided to build the array from the lowest number up (and shuffle them at the end). This allows me to always choose a random range that will allows yield valid results. Since the numbers must always be increasing, I solved for the highest possible number that ensures that a valid solution still exists (ie, if n=4 and max=31, if the first number was picked to be 7, then it wouldn't be possible to pick numbers greater than 7 such that the sum of 4 numbers would be equal to 31).
$n = 4;
$max = 31;
$array = array();
$current_min = 1;
while( $n > 1 ) {
//solve for the highest possible number that would allow for $n many random numbers
$current_max = floor( ($max/$n) - (($n-1)/2) );
if( $current_max < $current_min ) throw new Exception( "Can't use combination" );
$new_rand = rand( $current_min, $current_max ); //get a new rand
$max -= $new_rand; //drop the max
$current_min = $new_rand + 1; //bump up the new min
$n--; //drop the n
$array[] = $new_rand; //add rand to array
}
$array[] = $max; //we know what the last element must be
shuffle( $array );
EDIT: For large values of $n you'll end up with a lot of grouped values towards the end of the array, since there is a good chance you will get a random value near the max value forcing the rest to be very close together. A possible fix is to have a weighted rand, but that's beyond me.
I'm not sure whether I understood you correctly, but try this:
$n = 4;
$max = 30;
$array = array();
do {
$random = mt_rand(0, $max);
if (!in_array($random, $array)) {
$array[] = $random;
$n--;
}
} while (n > 0);
sorry i missed 'no duplicates' too
-so need to tack on a 'deduplicator' ...i put it in the other question
To generate a series of random numbers with a fixed sum:
make a series of random numbers (of largest practical magnitude to hide granularity...)
calculate their sum
multiply each in series by desiredsum/sum
(basicaly to scale a random series to its new size)
Then there is rounding error to adjust for:
recalculate sum and its difference
from desired sum
add the sumdiff to a random element
in series if it doesnt result in a
negative, if it does loop to another
random element until fine.
to be ultratight instead add or
subtract 1 bit to random elements
until sumdiff=0
Some non-randomness resulting from doing it like this is if the magnitude of the source randoms is too small causing granularity in the result.
I dont have php, but here's a shot -
$n = ; //size of array
$targsum = ; //target sum
$ceiling = 0x3fff; //biggish number for rands
$sizedrands = array();
$firstsum=0;
$finsum=0;
//make rands, sum size
for( $count=$n; $count>0; $count--)
{ $arand=rand( 0, $ceiling );
$sizedrands($count)=$arand;
$firstsum+=$arand; }
//resize, sum resize
for( $count=$n; $count>0; $count--)
{ $sizedrands($count)=($sizedrands($count)*$targsum)/$firstsum;
$finsum+=$sizedrands($count);
}
//redistribute parts of rounding error randomly until done
$roundup=$targsum-$finsum;
$rounder=1; if($roundup<0){ $rounder=-1; }
while( $roundup!=0 )
{ $arand=rand( 0, $n );
if( ($rounder+$sizedrands($arand) ) > 0 )
{ $sizedrands($arand)+=$rounder;
$roundup-=$rounder; }
}
Hope this will help you more....
Approch-1
$aRandomarray = array();
for($i=0;$i<100;$i++)
{
$iRandomValue = mt_rand(1000, 999);
if (!in_array($iRandomValue , $aRandomarray)) {
$aRandomarray[$i] = $iRandomValue;
}
}
Approch-2
$aRandomarray = array();
for($i=0;$i<100;$i++)
{
$iRandomValue = mt_rand(100, 999);
$sRandom .= $iRandomValue;
}
array_push($aRandomarray, $sRandom);

Categories