The user requests a Product Category and says what quantity they want of it, ie Sugar 7 lbs.
In the search results, from the database, I have the following items (which are each different products):
Sugar, 8 oz .75
Sugar, 1 lb 1.50
Sugar, 2 lb 4.00
Sugar, 5 lb 7.00
Sugar, 10 lb 11.00
Current process:
Get the search results
Check for the cheapest option for any one of the results (only 10 lbs would meet it) or multiples of one of the results (however many of 1 needed to match the criteria, so 7 X 1 lb)
Put the individual product ids into an array
Get the 1:1 permutations, but within that, use some code to add in up to 3 repeats (because 5 lbs + 2 X 1 lbs is the cheapest option, not 5 lbs + 2 lbs)
In this, check for different quantity unit (oz vs lb) and be able to convert and compare
Compare for cheapest and return cheapest
I'm even disregarding where there are more than 6 elements in the permutation to weed out unlikely options and reduce overhead
This works FINE unless there are > 9 product ids (for some I have > 25), then I get this error in the logs (and a completely unrelated error in the browser): Premature end of script headers
This is a lot of code. I'm sorry, just want to be thorough! Is there a more efficient/effective way?
function processCombos($arr, $qty_search, $qty_unit_search){ //$arr is the dataset, $qty_search is 7, $qty_unit_search is 1 for lbs
$combo=array();
$pid_arr = arrayifyProductIDs($arr);
$count = count($pid_arr);
$members = pow(2,$count);
for ($i = 0; $i < $members; $i++) {
$b = sprintf("%0".$count."b",$i);
$out = array();
for ($j = 0; $j < $count; $j++) {
if ($b{$j} == '1'){
$out[] = $pid_arr[$j];
}
}
$minLength=2;
$out_max = count($out);
if ($out_max >= $minLength) {
// now add in different repeats of each of them
$repeat_max = 3;
$indiv = array();
for($k=0;$k<$out_max;$k++){
$tmp = array();
for ($r = 0; $r < $repeat_max; $r++) $tmp[$r] = array_fill(0, $r + 1, $out[$k]);
$indiv[] = $tmp;
}
$x_ct = count($indiv[0]);
$y_ct = count($indiv[1]);
$z_ct = count($indiv[2]) > 0 ? count($indiv[2]): 0;
$perm = array();
for($x=0;$x<$x_ct;$x++){
for($y=0;$y<$y_ct;$y++){
if($z_ct > 0){
for($z=0;$z<$z_ct;$z++){
$perm = array_merge($indiv[0][$x],$indiv[1][$y],$indiv[2][$z]);
}
}else{
$perm = array_merge($indiv[0][$x],$indiv[1][$y]);
}
$p=0;
$max_p=count($perm);
if($max_p >=7){
}else{
$product_ids = array();
$qty = 0;
$price = 0;
while($p < $max_p){
$product_id = $perm[$p];
$data = $arr[$product_id];
if(!$data['qty_unit_id'] OR !$data['qty']){continue;} // go to the next one if it doens't have qty or qty_unit
if($data['qty_unit_id'] == $qty_unit_search){
$product_ids[] = $product_id;
$qty += $data['qty'];
$price += $data['price'];
}else{
$unit_to_convert_data = getQtyUnitName($qty_unit_search);
$unit_to_convert = $unit_to_convert_data['abbr'];
$unit_to_convert_type = $unit_to_convert_data['conv_file'];
if($unit_to_convert_type == $data['conv_file']){
if($data['conv_file'] == "Mass"){
$product_conv = new PhpUnitsOfMeasure\PhysicalQuantity\Mass($data['qty'], $data['qty_unit']);
}else{
$product_conv = new PhpUnitsOfMeasure\PhysicalQuantity\Volume($data['qty'], $data['qty_unit']);
}
$data['qty_CONV'] = number_format($product_conv->toUnit($unit_to_convert),3,".",",");
$product_ids[] = $product_id;
$qty += $data['qty_CONV'];
$price += $data['price'];
}
}
$p++;
}
if(count($combo)==0 AND $qty >= $qty_search){
$combo = array('product_ids' => $product_ids, 'qty' => $qty, 'price' => $price);
}elseif(($qty >= $qty_search AND $price < $combo['price']) OR
($qty >= $qty_search AND $price == $combo['price'] AND $qty > $combo['qty'])){
$combo = array('product_ids' => $product_ids, 'qty' => $qty, 'price' => $price);
}else{
}
}/// i think it should go here
}
}
}
}
return $combo;
}
Premature end of script headers usually means your script is killed before sending headers to the web server due to a resource limit in RLimitCPU and RLimitMEM directives in the httpd.conf. Either change these directives to allow more CPU and memory to your applications or write more efficient code (3 for loops means processing record^3 times the block inside it. consider rewriting it.)
Related
I am in a situation where I need to achieve divide 921/39 and by giving whole numbers (39 times for loop), achieve 921 again.
$packagesCount = count($packages); // = 39
$averageWeight = 921/$packagesCount; // = 23.6153846154
foreach ($packages as $package) {
$package['Weight'] = "<whole number>";
}
The reason is, I need to give the api whole numbers but the total should be 921. Thus, I can't give round numbers.
One way I thought of is:
$packagesCount = count($packages); // = 39
$averageWeight = 921/$packagesCount; // = 23.6153846154
$remainder = ceil($averageWeight); // = 24
foreach ($packages as $package) {
$package['Weight'] = floor($averageWeight);
if ($remainder > 0) {
$package['Weight'] += 1;
$remainder -= 1;
}
}
But trying it with 999 total weight doesn't work with this approach; instead of 999 in the end, it gives 39 * 25 + 26 = 1001.
For 999, I should use 39 * 25 + 24 = 999 but how?
I think you need intdiv and modulo %:
$packagesCount = count($packages);
$averageWeight = $totalWeight/$packagesCount;
$wholeWeight = intdiv($totalWeight,$packagesCount);
$weightRest = $totalWeight % $packagesCount;
// totalWeight = wholeWeight * packagesCount + weightRest
$i = 0;
foreach ($packages as $key => $package) {
$w = $wholeWeight;
if ($i < $weightRest) {
$w += 1;
}
$i+= 1;
$packages[$key]['Weight'] = $w;
}
The idea is that each package while have at least intdiv weight and first weightRest packages will have +1 to their weight. In such a way you will exactly match your totalWeight.
See also an online demo
P.S. PHP is not my language so the code might be very non-idiomatic. Still I hope it conveys the idea.
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.
I have managed to create an algorithm to check the rank of a poker hand. It works 100% correctly, but it's very slow. I've been analysing the code, and the check straight function is one of the slowest parts of it.
So my question is, is there a better way of calculating whether a hand make a straight?
Here is some details:
7 cards, 2 from holder, 5 from board. A can be high or low.
Each card is assigned a value:
2 = 2
3 = 3
..
9 = 9
T = 10
J = 11
Q = 12
K = 13
A = 14
The script has an array of all 7 cards:
$cards = array(12,5,6,7,4,11,3);
So now I need to be able to sort this into an array where it:
discards duplicates
orders the card from lowest to highest
only returns 5 consecutive cards I.e. (3,4,5,6,7)
It needs to be fast; loops and iterations are very costly. This is what I currently use and when it tries to analyse say 15000 hands, it takes its toll on the script.
For the above, I used:
discard duplicates (use array_unique)
order cards from lowest to highest (use sort())
only return 5 consecutive cards (use a for loop to check the values of cards)
Does anyone have any examples of how I could improve on this? Maybe even in another language that I could perhaps look at and see how it's done?
Instead of working with array deduping and sorting, consider using a bitmask instead, and setting bits to 1 where the card value is set. A bitmask works like a Set datastructure and comes with additional advantages when it comes to detecting contiguous elements.
for ($i = 0; $i < count($cards); $i++) {
$card = $cards[$i];
// For each card value, set the bit
if ($card == 14) {
// If card is an ace, also set bit 1 for wheel
$cardBitmask |= 0x2;
}
$cardBitmask |= (1 << $card);
}
// To compare, you simply write a for loop checking for 5 consecutive bits
for($i = 10; $i > 0; $i--)
{
if ($cardBitmask & (0x1F << $i) == (0x1F << $i)) {
// Straight $i high was found!
}
}
Consider the Java implementation at this link. I've included it here:
public static boolean isStraight( Card[] h )
{
int i, testRank;
if ( h.length != 5 )
return(false);
sortByRank(h); // Sort the poker hand by the rank of each card
/* ===========================
Check if hand has an Ace
=========================== */
if ( h[4].rank() == 14 )
{
/* =================================
Check straight using an Ace
================================= */
boolean a = h[0].rank() == 2 && h[1].rank() == 3 &&
h[2].rank() == 4 && h[3].rank() == 5 ;
boolean b = h[0].rank() == 10 && h[1].rank() == 11 &&
h[2].rank() == 12 && h[3].rank() == 13 ;
return ( a || b );
}
else
{
/* ===========================================
General case: check for increasing values
=========================================== */
testRank = h[0].rank() + 1;
for ( i = 1; i < 5; i++ )
{
if ( h[i].rank() != testRank )
return(false); // Straight failed...
testRank++; // Next card in hand
}
return(true); // Straight found !
}
}
A quick Google search for "check for poker straight (desired_lang)" will give you other implementations.
You could just sort the cards and loop over them in an array - saving always the last card and compare them with the current one.
$cards = array(12,5,6,7,4,11,3);
sort($cards);
$last = 0;
$count = 0;
$wheel = false;
foreach ($cards as $card) {
if ($card == $last) {
continue;
} else if ($card == ++$last) {
$count++;
} else {
if ($last == 6) $wheel = true;
$count = 1;
$last = $card;
}
if ($count == 5 || ($card == 14 && $wheel)) {
echo "straight $last";
$straight = range($last - 4, $last);
break;
}
}
You may go like this, you don't need to sort or anything (assuming that 2 is 2 and 14 is ace):
$cards = [12,5,6,7,4,11,3];
function _inc(&$i) {
if ($i == 14)
$i = 2;
else
$i++;
return $i;
}
$straight = false;
for($i = 2; $i <= 14; $i++) {
$ind = $i;
if (!in_array($ind, $cards)) continue;
$s = [$ind, _inc($ind), _inc($ind), _inc($ind), _inc($ind)];
$straight = count(array_intersect($s, $cards)) == count($s);
if ($straight) break;
}
print $straight;
I have the following array with undefined number of elements
$marks=array('2','4','9','3');
target=50;
I want to randomly loop through the array, add up the values I fetch until the total is my target.
$total=0; /////initialize total
for($i=0;$i<=sizeof($marks);++$i)
{
/////////Pick up random values add them up until $total==$target
/////////return the new array with selected elements that sums up to
/////////target
}
I hope my question is clear, also note that the loop should not iterate too many times since the elements might never add up to the total. I have tried adding the items in line but to no avail. Thanks in advance
I think this'll work for you and always return you value of count to be 50 only
$marks = array(6,7,9,6,7,9,3,4,12,23,4,6,4,5,7,8,4);
$target = 50;
function sum($marks, $target) {
$count = 0;
$result = [];
for ($i = 0; $i <= $target; $i++) {
if ($count < $target) {
$add = $marks[array_rand($marks)];
$count = $count + $add;
$result['add'][] = $add;
} elseif ($count == $target) {
break;
} elseif ($count >= $target) {
$extra = $count - $target;
$count = $count-$extra;
$result['extra'] = $extra;
}
}
return $result;
}
print_r(sum($marks, $target));
The way you describe your logic, a while loop might make more sense:
<?php
$marks = array(2, 4, 9, 3);
$target = 50;
$sum = 0;
$i = 0; // to keep track of which iteration we're on
// PHP can natively randomize an array:
shuffle($marks);
while ($sum < $target && $i < count($marks)) {
$sum += $marks[$i];
$i++; // keep track of which iteration we're on
}
// after the loop, we've either added every number in $marks,
// or $sum >= $target
Don't forget that it might exceed $target without ever being equal to it, as Dagon pointed out in a comment.
Look into PHP's native array shuffle: https://secure.php.net/manual/en/function.shuffle.php
This may be a good alternative for the above answer.
Why I say so is that I have set it in such a way that it doesn't let the total go over the target, and when there is such a situation, the current number in the array is decremented by one and added as a new element so that if there is no possible number in the stack, there will be one eventually making this loop not go on infinitely. :)
<?php
$marks = ['2', '4', '9', '3'];
$target = 50;
$total = 0;
$numbersUsed = [];
while($total != $target) {
$index = rand(0, count($marks) - 1);
$number = $marks[$index];
if($number + $total > $target) {
$number = 0;
$marks[] = $marks[$index] - 1;
} else {
$numbersUsed[] = $number;
}
$total += $number;
echo $total . "\n";
}
// To see which numbers were used:
print_r($numbersUsed);
?>
Testing:
Starting with the array ['2', '4', '9', '3'],
We loop and get the result:
4 13 17 20 22 31 35 44 46 48 48 48 48 50
And we get this array which includes the numbers used to get the final result:
Array
(
[0] => 4
[1] => 9
[2] => 4
[3] => 3
[4] => 2
[5] => 9
[6] => 4
[7] => 9
[8] => 2
[9] => 2
[10] => 2
)
I am trying to resolve project euler problem no 12 with PHP but it is taking too much time to process. I came across with similar processing problems of PHP while solving previous problems and I had to solve them in C++ just to test whether my approach is correct or not.
I want to know whether there is something wrong with my approach or somehow I can do something to make processing fast. Here is the code of my solution which works well for the triangle having 360 divisors. The link of problem is http://projecteuler.net/problem=12 and here is my code
<?php
set_time_limit(0);
ini_set('memory_limit', '1G');
$triangles = array(0);
$count = 1;
$numOfDivisiors = 0;
$lastTriangle = 0;
while($numOfDivisiors < 500){
$triangle = (int) $lastTriangle + (int) $count;
$factors = getFactors($triangle);
//$triangles[] = array('triangle' => $triangle, 'factors' => $factors, 'factorsCount' => count($factors));
$triangles[] = array('triangle' => $triangle, 'factorsCount' => count($factors));
$lastTriangle = $triangle;
$numOfDivisiors = count($factors);
$count++;
//echo '<pre>'; print_r(array('triangle' => $triangle, 'factorsCount' => count($factors), 'count' => $count)); echo '</pre>';
}
echo $numOfDivisiors; exit;
/**
for($i = 0 ; $i < 10 ; $i++){
}
**/
//echo '<pre>'; print_r($triangles); exit;
function getFactors($number){
$factors = array();
$break = false;
$count = 1;
while($break != true){
$remainder = $number % $count;
if($remainder == 0){
$factors[] = $count;
}
//echo $count." ".$number; exit;
if($count == $number){
$break = true;
}
$count++;
}
return $factors;
}
?>
use some maths.
triangle numbers can be generated by
n(n+1) /2
and that if you can find the prime factors, Adding 1 to their powers and multiplying together gives the number of divisors.
My PHP solution takes around 4 seconds and i think i can speed that up also
There are several ways to speed up your solution. The first one I'd point you at is the following:
if a * b = c then both a and b are factors of c
One of a and b will be <= to the square root of c
this should help speed up your solution