Looking for a more elegant solution to this loop - php

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

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.

You can do this a little bit more functional style:
function sumOfNaturalSeries($n)
{
return ((1 + $n) / 2) * $n;
}
$minPrice = 0.002;
$maxPrice = 0.007;
$discountRange = 1000000;
$discountStep = ($maxPrice - $minPrice) / $discountRange;
$getPrice = function ($numberOfItems) use (
$minPrice,
$maxPrice,
$discountRange,
$discountStep
) {
if ($numberOfItems <= $discountRange) {
return $maxPrice * $numberOfItems - sumOfNaturalSeries($numberOfItems - 1) * $discountStep;
}
$itemsAboveRange = $numberOfItems - $discountRange;
return $maxPrice * $discountRange - sumOfNaturalSeries($discountRange - 1) * $discountStep + $minPrice * $itemsAboveRange;
};
$array = [100, 1000, 10000, 100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, 1000000];
$sums = array_map($getPrice, $array);
var_dump($sums);
var_dump(array_map('number_format', $sums));
Here is demo.
Take a notice on computational error.

Related

How to determine every 100 item in a while loop php?

I'm currently learning PHP, and I'm struggling with this:
"For every 100 ordered products in a category, 2% will be deducted:"
This is my code:
$gesA = 309; // (The amount of product)
$gesN = 1011.08; // (The full price of product)
$i = 1;
while($i)
{
if($gesA % 100 == 0)
{
echo $gesN;
echo "<br>";
$gesN = $gesN / 0.2;
}
$i++;
$gesN++;
}
echo $gesN;
Yet, I can't figure it out. Could someone help me?
First you find how many times it is that there are 100 ordered products, which can be calculated by divide the number of products by 100.
$no = $getA / 100;
But that can get you a floating number so you remove the decimal part with floor()
$no = floor($getA / 100);
Then the percentage will be 2% times the integer number.
$deductPercentage = 2 * $no;
And the final product price will be the remaining of the deducted price
$deductedPrice = $gesN * $deductPercentage / 100;
$finalPrice = $gesN - $deductedPrice;

How to create tier price amount according to value

I'm setting up a webshop to use in a printshop but I'm stuck on how to automatically provide prices for different amounts.
In example:
The client wants to see the price for 100 sheets and immediately it should see the price for 150, 200, 250, ... (it's cheaper per piece every time you the amount is larger).
This is easy of course; get $amount and add +50 every time. But when the client asks 1000 it should not be +50 but maybe +100.
Is there a slick and easy way to do this or is it going to be a lot of
if($amount < 100){ add +50 }elseif($amount > 100 && < 500){ add + 75 }....?
Ok, how about this:
$amount = 100; // client's amount
$i = 1; // iterator
$suggested = array(); // array for suggested amounts
$increment = ($amount / 2); // half of client's suggested amount
# while loop
while ($i <= 3) {
$amount = $amount + $increment; // formula
array_push($suggested, $amount); // add to array
$i++;
}
# will output 150, 200, 250
foreach ($suggested as $s) {
echo $s . '<br />';
}
Will that get you started in the right direction?

Regular savings account interest calculation php

OK, I've had the same problem for a few weeks now and cant perfect it.
Aim
To build a regular deposit savings account system where it prints out the total balance at the current time.
Problem
The current equation I have:
If the interest is 6% with the user paying in 200 a month with compound being each month the balance would be after 6 months 1,220.61
I am getting 1217.13
I have tested different lengths of time and many different online calculators, my calculation is always less.
My code
<h2>Total Balance To Date</h2>
<?php
$p = 0; // Starting amount
$i = 0.06; // Interest rate
$c = 12; // compound frequency set to monthly
$n = 6/12; // Current time invested set to 6 months
$r = 200; // Monthly investment is 200
$x = $i / $c;
$y = pow((1 + $x), ($n * $c));
if($p!=0)
{
$vf = $p * $y + ($r * ($y - 1) / $x);
}
else
{
$vf = 1 + $y + ($r * ($y - 1) / $x);
}
?>
<p>£<?php echo round($vf, 2, PHP_ROUND_HALF_UP); ?></p> // Comes out at 1217.13
LINK to sandbox https://3v4l.org/9X7OH
Setting
q = pow(1.06 , 1.0/12) = 1.0048675505653430
and computing
200*(q+q^2+q^3+q^4+q^5+q^6) = 200*q*(q^6-1)/(q-1)
gives the result
1220.61037336530790
which is obviously what the online calculators worked with. It is slightly wrong, as for the nominal interest rate, the monthly compound factor should be
q = 1 + 0.06/12 = 1.005
resulting in a balance after 6 months of
1221.1758776293781
As you see, you got the formula almost right, it should be
$vf = $p * $y + ($r * (1 + $x) * ($y - 1) / $x);
since the rate is deposited at the start of the month, so the first rate gets compounded as r*(1+x)^6 and the last rate as r*(1+x). However, the second formula in the else branch does not make sense at all.

Increment Price by Hundred, Thousand, Ten-Thousand, etc

I'm trying to make a select list of prices in my system. The prices are stored as integers. I'm able to get the lowest price and highest price but I want to display them in a select list. I don't want the select list to increment slowly but by 100 or 10,000 or, 100,000 depending on what my starting number is what where i'm at in my incrementation.
For example, say I have these 2 prices:
500000
12345689
I'm trying to increment them by 100,000 Then when I get to 1,000,000 I want to increment by that. It will look something like this:
500000
600000
700000
800000
900000
1000000
2000000
I'm using a custom function and a bit of formatting to get all my prices and get my start price and end price:
$prices = my_custom_function(); // Pulls All Prices in a random order
if(!empty($prices)){
sort($prices); // Sort Prices Lowest to Highest
$price_low = $prices[0];
$price_high = $prices[count($prices)-1];
$price_start = intval( $price_low[0].str_repeat( '0', strlen( $price_low ) - 1 ) );
$price_end = intval( ( $price_high[0] + 1 ).str_repeat( '0', strlen( $price_high ) -1 ) );
}
Using the same example above, my start price and end price will be:
$price_start = 500000
$price_end = 20000000
Now it's at the loop where I run into trouble incrementing it by the values I want. I'm trying to use a while loop and determine where I am in my incrementer:
<?php $i = $price_start; $x = 0; while($x < 10) : ?>
<option value="<?php echo $i; ?>"><?php echo format_price($i); ?></option>
<?php
if(1000 % $i == 0)
$i+=1000;
else if(10000 % $i == 0)
$i+=10000;
else if(100000 % $i == 0)
$i+=100000;
else if(1000000 % $i == 0)
$i+=1000000;
else
$i+=10000000;
$x++;
endwhile;
?>
I ended up adding in the x variable because I kept running into infinite loop problems but theoretically it should be while($i <= $price_end). Can somebody point me in the right direction on how to get the expected output please? I feel like I'm close but not quite there yet and there's probably a better / faster way to go about it. Any help would be great.
I guess a simplified way of looking at it is:
1 -> +1
10 -> +10
100 -> +100
1000 -> +1000
10000 -> +10000
and so forth.
Get power of 10: log10(1234); // 3.09131
Round down: floor(log10(1234)); // 3
Re-raise as power of 10: pow(10,floor(log10(1234))); // 1000
???
Profit.
If someone needs the full solution here it is:
$price = 100; // Starting Price
$priceEnd = 10000; // Ending Price
while($price <= $priceEnd) {
echo $price . "<br/>";
$increase = pow(10,floor(log10($price)));
$price = $price + $increase;
}

Calculate which products together would deliver the requested power

Let's say I've got three products:
Product A
Will deliver 5 power. Costs 50.
Product B Will deliver 9 power. Costs 80.
Product C Will deliver 15 power. Costs 140.
I want to know what combination of products I could buy when I need 7 power. I could buy two of A but one of B is cheaper.
When I'd need 65 power. I would need 4 times C and 1 time A (costs 680). But I could also go for seven B products and one A (costs 610).
I am looking for a way to calculate the possible combinations of products for the given amount of power I need.
The way I tried doing this doesn't give me what I want:
// $products are sorted DESC on their $power
$power = 65
while( $power > 0 ) {
foreach( $products as $productPower ) {
if( ( $productPower > $power && $power - $productPower > 0 ) || $productPower == end( $products ) ) {
// Add product to list
$power -= $productPower;
break;
}
}
}
This sample code will only give me 4 times C and one time A. How should I go about it?
EDIT The number of products is variable. Also, the specific cost and power is variable. So there may be 10 products with cheeper and more expensive price tags.
EDIT 2 As I said above, I want to calculate the possible combinations (plural). Some people seem to have missed that in my description.
Introduction
This would have been a Knapsack problem but because you are not not just looking for the optimal solution you also want to find all possible combination
Then you can solve this Subset sum problem + Coin Change to get :
List all possible Combination and not just total combination
Get Best Combination
For example, for N = 4,S = {1,2,3}, there are four solutions: {1,1,1,1},{1,1,2},{2,2},{1,3}.
Example 1
echo "<pre>";
$start = microtime(true);
// Start Finder
$finder = new CombinationFinder(65);
// Add Produts
$finder->addProduct(new Product("A", 5, 50));
$finder->addProduct(new Product("B", 9, 80));
$finder->addProduct(new Product("C", 15, 140));
// Output All Found Combinations
foreach ( $finder as $key => $sales ) {
echo $sales->getName(), "\t\t\t", $sales->getCombinationCost(), PHP_EOL;
}
// Get Best Combination
echo "Combination: ", $finder->getBestCombination()->getName(), PHP_EOL;
echo "Cost: ", number_format($finder->getBestCombination()->getCombinationCost(), 2), PHP_EOL;
// Total Time
echo PHP_EOL, microtime(true) - $start;
Output
Top Combinations
["A",1],["C",4] 610
["A",1],["B",5],["C",1] 590
["A",4],["C",3] 620
["A",4],["B",5] 600
["A",7],["C",2] 630
["A",10],["C",1] 640
["A",13] 650
Best Combination
Combination: ["A",1],["B",5],["C",1]
Cost: 590.00
Total Time
0.2533269405365
Best Combination
You can see the best Combination is A*1 ,B*5 ,C*1 .. Break down
A B C
Power : 5 * 1 + 9 * 5 + 15 * 1 = 65
Cost : 50 * 1 + 80 * 5 + 140 * 1 = 590 <---- Better than 610.00
Example 2
The class can be use for 2, 3 , 4 or more product combination and yet sill very fast
echo "<pre>";
$start = microtime(true);
// Start Finder
$finder = new CombinationFinder(65);
// Add Produts
$finder->addProduct(new Product("A", 5, 50));
$finder->addProduct(new Product("B", 9, 80));
$finder->addProduct(new Product("C", 15, 140));
$finder->addProduct(new Product("D", 20, 120)); // more product class
$finder->run(); // just run
// Get Best Combination
echo "Combination: ", $finder->getBestCombination()->getName(), PHP_EOL;
echo "Cost: ", number_format($finder->getBestCombination()->getCombinationCost(), 2), PHP_EOL;
// Total Time
echo PHP_EOL, microtime(true) - $start;
Output
Combination: ["A",1],["D",3] //<---------------------- Best Combination
Cost: 410.00
Time Taken
1.1627659797668 // less than 2 sec
Class Used
class Product {
public $name;
public $power;
public $cost;
public $unit;
function __construct($name, $power, $cost) {
$this->name = $name;
$this->power = $power;
$this->cost = $cost;
$this->unit = floor($cost / $power);
}
}
class Sales {
/**
*
* #var Product
*/
public $product;
public $count;
public $salePower;
public $saleCost;
function __construct(Product $product, $count) {
$this->product = $product;
$this->count = $count;
$this->salePower = $product->power * $count;
$this->saleCost = $product->cost * $count;
}
}
class SalesCombination {
private $combinationPower;
private $combinationCost;
private $combinationName;
private $combinationItems;
private $args;
function __construct(array $args) {
list($this->combinationPower, $this->combinationCost, $this->combinationItems) = array_reduce($args, function ($a, $b) {
$a[0] += $b->salePower;
$a[1] += $b->saleCost;
$a[2] = array_merge($a[2], array_fill(0, $b->count, $b->product->name));
return $a;
}, array(0,0,array()));
$this->args = $args;
}
function getName() {
$values = array_count_values($this->combinationItems);
$final = array();
foreach ( $values as $name => $amount ) {
$final[] = array($name,$amount);
}
return substr(json_encode($final), 1, -1);
}
function getCombinationPower() {
return $this->combinationPower;
}
function getCombinationCost() {
return $this->combinationCost;
}
}
class CombinationFinder implements IteratorAggregate, Countable {
private $sales;
private $products = array();
private $power;
private $found = array();
private $bestCombination = null;
private $run = false;
function __construct($power) {
$this->power = $power;
}
function addProduct(Product $product) {
$this->products[] = $product;
}
function getBestCombination() {
return $this->bestCombination;
}
function getFound() {
return $this->found ? : array();
}
public function getIterator() {
if ($this->run === false) {
$this->run();
}
return new ArrayIterator($this->found);
}
public function count() {
return count($this->found);
}
function run() {
$this->run = true;
$this->buildSales();
$u = new UniqueCombination($this->sales);
$u->setCallback(array($this,"find"));
$u->expand();
}
function find() {
$salesCombination = new SalesCombination(func_get_args());
if ($salesCombination->getCombinationPower() == $this->power) {
isset($this->bestCombination) or $this->bestCombination = $salesCombination;
$salesCombination->getCombinationCost() < $this->bestCombination->getCombinationCost() and $this->bestCombination = $salesCombination;
$this->found[sha1($salesCombination->getName())] = $salesCombination;
}
}
function buildSales() {
$total = count($this->products);
foreach ( $this->products as $product ) {
$max = floor($this->power / $product->power);
for($i = 1; $i <= $max; $i ++) {
$this->sales[$product->name][] = new Sales($product, $i);
}
}
}
}
class UniqueCombination {
private $items;
private $result = array();
private $callback = null;
function __construct($items) {
$this->items = array_values($items);
}
function getResult() {
return $this->result;
}
function setCallback($callback) {
$this->callback = $callback;
}
function expand($set = array(), $index = 0) {
if ($index == count($this->items)) {
if (! empty($set)) {
$this->result[] = $set;
if (is_callable($this->callback)) {
call_user_func_array($this->callback, $set);
}
}
return;
}
$this->expand($set, $index + 1);
foreach ( $this->items[$index] as $item ) {
$this->expand(array_merge($set, array($item)), $index + 1);
}
}
}
Updated answer
I stand with my original answer, but have since derived an explicit solution. Unfortunately, I am not versed in PHP, so the implementation I'll present is in (poorly written) F#.
The point which makes your question interesting is that you are not looking for THE best solution, but for all feasible solutions. As I pointed out in my original answer, this is tricky, because the set of feasible solutions is infinite. As an illustration, if you want to produce 65 units, you can use 13xA, which yields a power of 5x13 = 65. But then, obviously, any solution which contains more than 13 units of A will also be a solution.
You can't return an infinite set from a function. What you need here is the set of all "boundary" cases:
if a solution contains as many units as a boundary case for all products, it is valid
if a unit can be removed from a boundary case and it is still feasible, it is not feasible anymore.
For instance, the solution S = { A = 13; B = 0; C = 0 } is a boundary case. Remove one unit from any product, and it is not feasible - and if a combination is such that for every product, it contains more units than S, it is a valid solution, but "dominated" by S.
In other words, we cannot return all possible solutions, but we can return the "limit" that separates feasible and unfeasible solutions.
Note also that the cost of the Products is irrelevant here - once you have the set of boundary cases, computing the cost of a solution is trivial.
Given that you specify that the number of products could be arbitrary, this sounds like a clear case for recursion.
If you have no product, the solution is trivially empty - there is no solution.
If you have 1 product, the solution is ceiling (target / product.Power)
If you have 2 products, say A:5 and B:2, with a target of 10, you could
use 0 of A -> remaining target is 10, use 5 B (or more)
use 1 of A -> remaining target is 10 - 5, use 3 B (or more)
use 2 of A -> remaining target is 10 - 10, use 0 B (or more)
A is maxed out, so we are done.
Note that I sorted A and B by decreasing Power. An unsorted list would work, too, but you would produced "useless" boundary points. For instance, we would get [1 B; 2 A], and [2 B; 2 A].
The idea can be extended to a full recursion, along the lines of
Given a list of Products and a remaining Target power to achieve,
If the Product is the last one in the list, use ceiling of Target/product Power,
Else take every possible combination of the head product from 0 to max, and
Search deeper, decreasing Target Power by the units supplied by the Product selected.
Below is a simple F# implementation, which could easily be improved upon, and will hopefully convey the idea. The units function returns the minimum number of units of a product with Power value required to supply target Power, and the recursive function solve builds up the combinations into a List of solutions, tuples with a Product Id and the number of units to use:
type Product = { Id: string; Power: int }
let A = { Id = "A"; Power = 5 }
let B = { Id = "B"; Power = 9 }
let C = { Id = "C"; Power = 15 }
let products = [ A; B; C ] |> List.sortBy(fun e -> - e.Power)
let units (target: int) (value: int) =
if target < 0
then 0
else
(float)target / (float)value |> ceil |> (int)
let rec solve (products: Product list)
(current: (string * int) list)
(solutions: (string * int) list list)
(target: int) =
match products with
| [ ] -> [ ]
| [ one ] -> ((one.Id, (units target one.Power)) :: current) :: solutions
| hd :: tl ->
let max = units target hd.Power
[ 0 .. max ]
|> List.fold (fun s u ->
solve tl ((hd.Id, u) :: current) s (target - u * hd.Power)) solutions
I would run it this way:
> solve [B;A] [] [] 65;;
Real: 00:00:00.001, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : (string * int) list list =
[[("A", 0); ("B", 8)]; [("A", 1); ("B", 7)]; [("A", 3); ("B", 6)];
[("A", 4); ("B", 5)]; [("A", 6); ("B", 4)]; [("A", 8); ("B", 3)];
[("A", 10); ("B", 2)]; [("A", 12); ("B", 1)]; [("A", 13); ("B", 0)]]
Note that the number of solutions will increase pretty fast. I ran your example, which yielded 28 solutions. As the number of products and the target power increases, the number of boundary solutions will expand quite a bit.
I can't code in PHP at all, but I assume it supports recursion - maybe someone will show a recursive solution in PHP? In any case, I hope this helps.
An interesting side question would be how different the problem would be, if the products could be purchased in non-integer quantities. In that case, the boundary would really be a surface (a polyhedron I believe); how to describe it adequately would be an interesting problem!
Original answer
Unless I am misunderstanding your question, what you describe is what is known in optimization as an Integer Linear Programming problem, with well established algorithms to resolve them. Your problem sounds like a variation of the Diet problem (given ingredients, find the cheapest way to get enough calories to survive), one of the archetypes of Linear Programming, with integer variable constraints.
First, the solution to your problem as stated has an infinite numbers of solutions; suppose that 5 x A is a solution to your problem, then any combination with more than 5 units of A will also satisfy your requirements.
Edit: I realize I might have misunderstood your problem - I assumed you could buy any quantity of each product. If you can buy only 1 unit of each, this is an easier problem: it is still an integer programming problem, but a simpler one, the Knapsack problem.
Note also that if you can by non-integer quantities of the products (doesn't seem to be the case for you), your problem is significantly easier to solve.
The most obvious way to restate your problem, which make it a standard optimization problem that can be solved fairly easily:
Find the combination of n Products which has the minimum total cost, subject to constraint that the total energy delivered is above desired threshold. (I assume that both the total cost and total energy delivered are linear functions of the quantity of A, B, C... purchased).
I assume this is actually what you really want - the best possible solution to your problem. If you are really interested in enumerating all the solution, one way to go about it is to identify the boundaries which define the feasible set (i.e. the geometric boundary such that if you are on one side you know it's not a solution, otherwise it is). This is much easier if you are working with numbers that don't have to be integers.
Hope this helps!
A simple observation on this specific problem may help people in solving this question. The way the power and costs are distributed here. You get the most value for your money with Product B. In fact, the only time you would ever use Product C, is when you need exactly 15 power, or 28-30 power.
So for any power needed above 30, just use integer division to get the # of Product B's you need by:
int num_productB = power_needed/9;
Then find out how much more power you need by:
int leftover = power_needed % 9;
If the leftover is greater than 5, just add one more Product B, Else use 1 Product A:
if(leftover > 5)
num_productB++;
else
productA = 1;
The full function would look something like this:
function computeBestCombination($power_needed){
$power_results = array();
//index 0 = Product A
//index 1 = Product B
//index 2 = Product C
if($power_needed == 15){
$power_results[0] = 0;
$power_results[1] = 0;
$power_results[2] = 1;
}
else if($power_needed >= 28 && $power_needed <= 30)
$power_results[0] = 0;
$power_results[1] = 0;
$power_results[2] = 2;
else{
$power_results[1] = $power_needed / 9;
$left_over = $power_needed % 9;
if($left_over > 5){
$power_results[1]++;
}
else{
$power_results[0] = 1;
}
$power_results[2] = 0;
}
return $power_results;
}
Check this code:
<?php
$products = array(5 => 50, 9 => 80, 15 => 140);
$power = 65;
$output = array();
function calculate_best_relation($products, $power, &$output) {
$aux = array_keys($products);
sort($aux);
$min = $aux[0];
if ($power <= $min) {
$output[] = $min;
return $output;
}
else {
//Calculate best relation
$relations = array();
foreach ($products as $p => $c) {
$relations[$p] = $c / $p;
}
asort($relations);
foreach($relations as $p => $c) {
if ($power > $c) {
$output[] = $p;
$power -= $c;
calculate_best_relation($products, $power, $output);
break;
}
}
}
}
calculate_best_relation($products, $power, $output);
print_r($output);
?>
This will print:
Array
(
[0] => 9
[1] => 9
[2] => 9
[3] => 9
[4] => 9
[5] => 9
[6] => 9
[7] => 5
)
Which is the correct solution.
P.D: Surely you can optimize the function.
An integer programming package such as pulp will make this easy as pie.
Here is a beautiful example that will guide you through the process.
Install python and then easy_install pulp and this will work.
The code should be easy to read and follow too.
__author__ = 'Robert'
import pulp
def get_lp_problem(products, required_power):
prob = pulp.LpProblem("MyProblem", pulp.LpMinimize)
total_cost = []
total_power = []
for product in products:
var = pulp.LpVariable(product.name,
lowBound=0,
upBound=None,
cat=pulp.LpInteger)
total_cost.append(var * product.cost)
total_power.append(var * product.power)
prob += sum(total_power) >= required_power #ensure we have required power
prob += sum(total_cost) #minimize total cost!
return prob
def solve(products, required_power):
lp_prob = get_lp_problem(products, required_power)
lp_prob.solve()
print lp_prob.solutionTime #0.01 seconds
for var in lp_prob.variables():
print var.name, var.varValue
from collections import namedtuple
Product = namedtuple("Product", "name, power, cost")
products = [
Product('A', 5, 50),
Product('B', 9, 80),
Product('C', 15, 140)
]
solve(products, 7)
"""
A 0.0
B 1.0
C 0.0
cost = 0*50 + 1*80 + 0*140 = 80
power = 0*5 + 1*9 + 0*15 = 9
"""
solve(products, 65)
"""
A 1.0
B 5.0
C 1.0
cost = 1*50 + 5*80 + 1*140 = 590
power = 1*5 + 5*9 + 1*15 = 65
"""
more products:
products = [Product(i, i, i-i/100) for i in range(1000)]
solve(products, 12345)
"""
solution time: 0.0922736688601
1 45.0
100 123.0
power = 123*100 + 45*1 =12345
"""
This is pretty nicely solved using dynamic programming. The trick is in finding the mathematical relationship between increasingly large values and previous, smaller values.
So let C(p) be the cost for p power. Then we know the following from your base cases:
Let's say I've got three products:
Product A Will deliver 5 power. Costs 50.
Product B Will deliver 9 power. Costs 80.
Product C Will deliver 15 power. Costs 140.
C(5) = 50
C(9) = 80
C(15) = 140
You can define the base cases however you want. Presumably C(0) = 0, but that is not given.
Then the trick is to find the recursion to solve this. Using the given values, we get
C(p) = Min(C(p-5) + 50, C(p-9) + 80, C(p-15) + 140)
More generally, you have to iterate over each of the base cases and see which way is cheaper.
So now you're left with two ways to build your solution: recursively or using dynamic programming. The former is easier given the recursive function, but is obviously quite inefficient. The other way to do this, then, is to start at the bottom and build your solution iteratively.
Lets say you want to find the cost for p power. Then the following pseudocode will work:
// Create an array big enough to hold elements 0 through p inclusive.
var solution = new Array(p+1);
// Initialize the array with the base cases.
for each base case b:
solution[power(b)] = cost(b);
// Now we build the array moving forward
for i from 0 to p:
// Start with a really big number
solution[i] = +Infinity;
// Iterate over base case to see what the cheapest way to get i power is.
for each base case b:
solution[i] = min(solution[i], solution[i - power(b)] + cost(b);
// The final answer is the last element in the array, but you get everything
// else for free. You can even work backwards and figure out the cheapest
// combination!
return solution[p]
Analysis left as an exercise to the reader :-)
You want to optimize the following function
$cost = $amountOfProductA * $costOfProductA + $amountOfProductB * $costOfProductB + $amountOfProductC * $costOfProductC
With the following restriction
$powerDeliveredByA * $amountOfProductA + $powerDeliveredByB * $amountOfProductB + $powerDeliveredByC * $amountOfProductC = 65
So these lines find solutions that yield 65 (or close to 65, using an acceptable threshold you'd have to set), then sort the solutions array by the cost, and get the first element of the solutions array:
$requiredPower = 65;
$productA = array('amount' => 0, 'cost' => 50, 'powerDelivered' => 5);
$productB = array('amount' => 0, 'cost' => 80, 'powerDelivered' => 9);
$productC = array('amount' => 0, 'cost' => 140, 'powerDelivered' => 15);
$increment = 0.01;
$threshold = 0.01;
$solutions = array();
while($productA['amount'] * $productA['powerDelivered'] < $requiredPower)
{
$productC['amount'] = 0;
while($productB['amount'] * $productB['powerDelivered'] < $requiredPower)
{
$productC['amount'] = 0;
while($productC['amount'] * $productC['powerDelivered'] < $requiredPower)
{
if($productA['amount'] * $productA['powerDelivered'] + $productB['amount'] * $productB['powerDelivered'] + $productC['amount'] * $productC['powerDelivered'] > $requiredPower + $threshold)
{
break;
}
if(isWithinThreshold($productA['powerDelivered'] * $productA['amount'] + $productB['powerDelivered'] * $productB['amount'] + $productC['powerDelivered'] * $productC['amount'], $requiredPower, $threshold))
{
//var_dump($productA['powerDelivered'] * $productA['amount'] + $productB['powerDelivered'] * $productB['amount'] + $productC['powerDelivered'] * $productC['amount']);
$cost = $productA['amount'] * $productA['cost'] + $productB['amount'] * $productB['cost'] + $productC['amount'] * $productC['cost'];
$solutions[number_format($cost,10,'.','')] = array('cost' => $cost, 'qA' => $productA['amount'], 'qB' => $productB['amount'], 'qC' => $productC['amount']);
}
$productC['amount'] = $productC['amount'] + $increment;
}
$productB['amount'] = $productB['amount'] + $increment;
}
$productA['amount'] = $productA['amount'] + $increment;
}
ksort($solutions, SORT_NUMERIC);
$minimumCost = array_shift($solutions);
var_dump($minimumCost);
//checks if $value1 is within $value2 +- $threshold
function isWithinThreshold($value1, $value2, $threshold)
{
if($value1 >= $value2 - $threshold && $value1 <= $value2 + $threshold)
{
return true;
}
}
The way to optimize a function is described here: Function Optimization

Categories