Greg-J Greg-J - 6 months ago 15
PHP Question

Looking for a more elegant solution to this loop

I tried asking this earlier, but I don't think I phrased the question correctly so I worked out something that got me the result I was after and now am hoping that it will help someone help me.

Problem: I have 10 items. If you buy 1, it's $10. I will sell you the second one for $9. I will sell you the third item for $8. I will keep taking off money until we get to $5/item because that is the lowest I will sell it for. So, if you buy all 10, it will cost you $65.

This is the pricing model I am trying to achieve, except at a much larger scale. Instead of a handful of items using dollars, I'm talking about up to millions and using fractions of pennies.

This is my current code:

<?php

function getCost($num_items)
{
$min_price = 0.002;
$max_price = 0.007;
$discount_range = 1000000;

$discount_per_additional_item = ($max_price - $min_price) / ($discount_range - 1);

$price_per_unit = MAX($min_price, ($max_price - ($num_items - 1) * $discount_per_additional_item) );

return $price_per_unit;
}

$array = [100, 1000, 10000, 100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, 1000000];

foreach ($array as $value)
{

$sum = 0;
for ($i = 0; $i < $value; ++$i)
$sum += getCost($i);

echo number_format($value) . ' | $' . number_format($sum) . "\n";

}


Which results in:

100 | $1
1,000 | $7
10,000 | $70
100,000 | $675
200,000 | $1,300
300,000 | $1,875
400,000 | $2,400
500,000 | $2,875
600,000 | $3,300
700,000 | $3,675
800,000 | $4,000
900,000 | $4,275
1,000,000 | $4,500


I'm using $array as a sanity check where in the real world, I would simply calculate for the actual number the customer is being charged for.

My question is: Is there a way to accomplish this without using a for loop? Something, perhaps, more elegant?

I made an example online: http://sandbox.onlinephpfunctions.com/code/47e270dbad8cbe16c9ea906ffd2dce098a52fbca

Answer

This code will have the same output, and does not have the inner loop:

$min_price            = 0.002;
$max_price            = 0.007;
$discount_range       = 1000000;
$discount_per_additional_item = ($max_price - $min_price)/($discount_range - 1);

$num_progressively_discounted_items = 
        ceil(($max_price - $min_price) / $discount_per_additional_item);
foreach ($array as $value) {
    $num_items_above_min = min($value, $num_progressively_discounted_items);
    $num_items_at_min = $value - $num_items_above_min; 
    $sum = $num_items_at_min * $min_price + 
           $num_items_above_min * $max_price - 
           $discount_per_additional_item 
               * $num_items_above_min * ($num_items_above_min - 1)/2;

    echo number_format($value) . '  |  $' . number_format($sum) . "\n";
}

This is what it does:

  • It first checks how many times the unit discount can be subtracted from the original price before hitting the minimum price. If more than the number of items you are buying, then this calculated figure is corrected to that number of items.
  • The remaining number of items (if any) are also taken note of: these will all have the minimum price.
  • The sum consists of two parts. The easy part is represented by the number of items that will go for the minimum price, and it is a simple multiplication.
  • The second part of the sum consists of an always decreasing term, or otherwise put: it is the maximum price for the number of items that don't go for the minimum price, minus the sum of 0+1+2+3+4+5...+n. For that the formula is known: n(n-1)/2.

Like I mentioned in comments, there is something strange in your code: for $i=0 the value returned by getCost($i) is higher than the max price, as the unit discount gets added to it. This can be corrected by starting your inner loop with $i=1. Anyway, this means there is a tiny difference in the result of my proposed code, as it does not have this peculiarity. But as the discount per unit is so tiny, you don't actually notice it in the printed output.

Comments