Say I have the following measures:
80
180
200
240
410
50
110
I can store each combination of numbers to a maximum of 480 per unit. How can I calculate the least number units required so all measures are spread in the most efficient way?
I've tagged PHP but it can be in JS too, or even pseudo language.
I know I'm supposed to tell what I did already but I'm quite stuck on how to approach this. The first thing that comes to mind is recursion but I'm no math expert to see how this can be done efficient...
Any help is greatly appreciated.
To further elaborate: I'm trying to calculate the number of skirtings I have to order, based on the different lengths I need for the walls. Each skirting has a length of 480cm and I want to know the best way to spread them so I have to buy the least number of skirtings. It's not so much about ordering a skirting extra, but the puzzle to figure it out is an interesting one (at least to me)
Update with solution
Despite people trying to close the question I've started fiddling with the Bin Packing Problem description and following the idea of sorting all items from largest to smallest and then fit them in the best possible way I created this small class that might help others in the future:
<?php
class BinPacker {
private $binSize;
public function __construct($binSize) {
$this->binSize = $binSize;
}
public function pack($elements) {
arsort($elements);
$bins = [];
$handled = [];
while(count($handled) < count($elements)) {
$bin = [];
foreach($elements as $label => $size) {
if(!in_array($label, $handled)) {
if(array_sum($bin) + $size < $this->binSize) {
$bin[$label] = $size;
$handled[] = $label;
}
}
}
$bins[] = $bin;
}
return $bins;
}
public function getMeta($bins) {
$meta = [
'totalValue' => 0,
'totalWaste' => 0,
'totalBins' => count($bins),
'efficiency' => 0,
'valuePerBin' => [],
'wastePerBin' => []
];
foreach($bins as $bin) {
$value = array_sum($bin);
$binWaste = $this->binSize - $value;
$meta['totalValue'] += $value;
$meta['totalWaste'] += $binWaste;
$meta['wastePerBin'][] = $binWaste;
$meta['valuePerBin'][] = $value;
}
$meta['efficiency'] = round((1 - $meta['totalWaste'] / $meta['totalValue']) * 100, 3);
return $meta;
}
}
$test = [
'Wall A' => 420,
'Wall B' => 120,
'Wall C' => 80,
'Wall D' => 114,
'Wall E' => 375,
'Wall F' => 90
];
$binPacker = new BinPacker(488);
$bins = $binPacker->pack($test);
echo '<h2>Meta:</h2>';
var_dump($binPacker->getMeta($bins));
echo '<h2>Bin Configuration</h2>';
var_dump($bins);
Which gives an output:
Meta:
array (size=6)
'totalValue' => int 1199
'totalWaste' => int 265
'totalBins' => int 3
'efficiency' => float 77.898
'valuePerBin' =>
array (size=3)
0 => int 420
1 => int 465
2 => int 314
'wastePerBin' =>
array (size=3)
0 => int 68
1 => int 23
2 => int 174
Bin Configuration
array (size=3)
0 =>
array (size=1)
'Wall A' => int 420
1 =>
array (size=2)
'Wall E' => int 375
'Wall F' => int 90
2 =>
array (size=3)
'Wall B' => int 120
'Wall D' => int 114
'Wall C' => int 80
While the data set is relatively small a rather high inefficiency rate is met. But in my own configuration where I entered all wall and ceiling measures I've reached an efficiency of 94.212% (n=129 measures).
(Note: the class does not check for ambigious labels, so if for example you define Wall A twice the result will be incorrect.)
Conclusion: for both the ceiling and the wall skirtings I can order one less skirting than my manual attempt to spread them efficiently.
Looks to me like a variation on the Bin Packing Problem where you're trying to pick the combination of elements that make up 480 (or just under). This is a fairly computationally hard problem and depending on how efficient/accurate it needs to be, might be overkill trying to get it exact.
A rough heuristic could be just to sort the measures, keep adding the smallest ones into a unit until the next one makes you go over, then add to a new unit and repeat.
Related
I'm a complete beginner at PHP and I'd like to create a simple 3 for 2 campaign with the help of built in functions (such as i.e array_pop(), array_sum() or something else) and I've unfortunately hit a wall.
I've created an array with completely made up pizzas:
$pizzaArray = [
"Hackaton" => [
"price" => 135,
"ingredients" => "tomato sauce and magic",
"randomComment" => "tastes like unicorns"
],
"Plug-in" => [
"price" => 145,
"ingredients" => "sardeler och oliver",
"randomComment" => "Ripasso"
],
"Lulz" => [
"price" => 150,
"ingredients" => "tomato sauce and old socks",
"randomComment" => "tastes like old socks"
]
];
Please don't mind the weird values. The only thing that is "important" are the price arrays.
What I basically want to achieve is that if a customer were to purchase 3 pizzas, then the $totalSum will equal the total amount minus the cheapest pizza.
I'm trying to think that it'd be achieveable if I were to be able to code this:
$numberOfPizzas = count($pizzaarray);
$totalSum = 0;
if ($numberOfPizzas == 3) {
//array_pop() the cheapest pizza
// $totalSum =array_sum() price of remaining pizza
} else {
//$totalSum = array_sum() prices of all pizzas
But since I can't really think like a programmer yet my head simply won't output something that is logical. Could this be achieved in a simple scenario?
You may use uasort to sort the pizzas by price, then array_slice to take all but the cheapest, then array_reduce to compute the final price:
$pizzas = [
'Hackaton' => ['price' => 135, 'ingredients' => 'tomato sauce and magic', 'randomComment' => 'tastes like unicorns'],
'Plug-in' => ['price' => 145, 'ingredients' => 'sardeler och oliver', 'randomComment' => 'Ripasso'],
'Lulz' => ['price' => 150, 'ingredients' => 'tomato sauce and old socks', 'randomComment' => 'tastes like old socks']
];
uasort($pizzas, static function (array $pizza1, array $pizza2): int {
return $pizza1['price'] <=> $pizza2['price'];
});
$pizzasMinusCheapest = array_slice($pizzas, 1);
// or, if you want the discount to apply for every 3 pizzas,
// $pizzasMinusCheapest = array_slice($pizzas, (int)(count($pizzas) / 3))
$finalPrice = array_reduce($pizzasMinusCheapest, static function (int $total, array $pizza): int {
return $total + $pizza['price'];
}, 0);
echo $finalPrice;
Demo: https://3v4l.org/gYKdY
Bonus - using PHP 7.4's short closures:
uasort($pizzas, fn(array $pizza1, array $pizza2): int => $pizza1['price'] <=> $pizza2['price']);
$pizzasMinusCheapest = array_slice($pizzas, 1);
$finalPrice = array_reduce($pizzasMinusCheapest, fn(int $total, array $pizza): int => $total + $pizza['price'], 0);
I have an multidimensional array from which i need to get some data, make some simple math and display them on a js graphic chart.
My only problem is that i can't get the data from the arrays.
This is how to array looks:
object(stdClass) [211]
public 'countries' =>
array (size=30)
0 =>
array (size=1)
0 =>
object(stdClass)[215]
public 'country' => string 'RO' (lenght=2)
public 'clicks' => int 4
1 =>
object(stdClass)[215]
public 'country' => string 'RU' (lenght=2)
public 'clicks' => int 1
1 =>
array (size=1)
0 =>
object(stdClass)[215]
public 'country' => string 'RU' (lenght=2)
public 'clicks' => int 4
1 =>
object(stdClass)[215]
public 'country' => string 'RO' (lenght=2)
public 'clicks' => int 2
...and so on till 29 (there are 30 days) so each day has clicks from different countries.
What i need to do is to extract the data from the array in this format:
name: ['RO', 'RU']
data: [6, 5]
I hope you understand what i am trying to do.
I am no expert, just a beginner and i can't work this out.
EDIT: in the end, i need to say that x country has x clicks on the last 30 days.
Be aware that i don't know witch countries are in the array because this is from an API that provides the top 10 countries by clicks.
So, in each day could be a country that wasn't in the day before and so on.
EDIT 2: this is what i have tried based on my experience:
function getCountriesByClicks($data)
{
$jsData = [];
$clicks = [];
$country = [];
for ($x = 0; $x < sizeof($data->data[0]->data->countries); $x++) {
foreach ($data->data[0]->data->countries[$x] as $value) {
array_push($country, $value->country);
array_push($clicks, $value->clicks);
}
$jsData[] = ['name' => $country, 'data' => $clicks];
}
return json_encode($jsData);
}
Ignore the json return for it's just to use in my javascript graphic chart.
Thank you!
try the following:
$name = array();
$data = array();
array_map(function($array){
array_map(function($obj){
$name[] = $obj->country;
$data[] = $obj->clicks;
},$array);
},$yourObject->countries);
Having this array :
array (size=1)
24 =>
array (size=7)
'user_id' => int 24
'date_clicked' =>
array (size=3)
0 => int 1382867319
1 => int 1382867419
2 => int 1382940698
'ip' => string '127.0.0.1' (length=9)
'email' => string 'test' (length=8)
'name' => string 'test' (length=7)
'request' => string 'test content' (length=12)
'faked_clicks' =>
array (size=3)
0 => int 1382867319
1 => int 1382867419
2 => int 1382940698
Here is my implementation that adds the faked_clicks array based on the date clicked array :
foreach($parsedUserClicks as $k => $v) {
foreach($v['date_clicked'] as $kk => $vv) {
$rangeHigh = range($vv, $vv+(60*60*24));
$checkHigh = array_intersect($v['date_clicked'], $rangeHigh );
if(count($checkHigh) >= 3) {
$parsedUserClicks[$k]['faked_clicks'] = $checkHigh;
}
}
}
The thing is that , by using array_intersect , it's taking quite a long time make a search only for 3 timestamps .
What I want to achieve is to get all 3 dates that are in an interval of 1 day . But my search is too slow (5 seconds for this simple search) . Any algorith i could use for this type of search ?
P.S. : I know i should not use such a big range to intersect arrays (60*60*24) . But i can't seem to find another solution . Also the range might get bigger so this method eventually will drop .
how about simply checking the values?
$dc_copy = $v['date_clicked'];
foreach($parsedUserClicks as $k => $v) {
$checkHigh = array();
foreach($v['date_clicked'] as $kk => $vv) {
$rangeHigh = $vv+(60*60*24);
foreach($dc_copy as $v2){
if($v2 >= $vv && $v2 <= $rangeHigh){
$checkHigh[] = $v2;
}
}
if(count($checkHigh) >= 3) {
$parsedUserClicks[$k]['faked_clicks'] = $checkHigh;
}
}
}
The only solution i could think of right now was to minimize the search to be made on days not on seconds . This is not a final answere , maybe someone else can give a proper search algorith for this type of search .
Is there any way to optimize this piece of code to work faster? I'd appreciate any suggestions!
This piece of code processes the transferring of edges during graph creation.
foreach($times_arrival as $city_id => $time_points) {
// if city is not prohibited for transfers and there is and exists any departure times for this city
if (isset($times_departure[$city_id]) && isset($cities[$city_id]))
{
foreach($times_arrival[$city_id] as $t1_info)
{
foreach($times_departure[$city_id] as $t2_info)
{
if ($t1_info[0] != $t2_info[0]) //transfers are allowed only for different passages
{
$t1 = $t1_info[1];
$t2 = $t2_info[1];
$vertex_key = new Vertex($city_id, $t1, 1);
$vertex_key = $vertex_key->toString();
//minimum transfer time is 10 min.
if (date('H:i', strtotime($t2)) > date('H:i', strtotime('+ 10 minutes', strtotime($t1))))
{
$this->graph[$vertex_key][] = new Edge(
NULL,
$vertex_key,
new Vertex($city_id, $t2, 0),
(float) 0,
$f((strtotime($t2) - strtotime($t1)) / 60, 0, 1) //edge weight
);
}
//if transfer is on the bound of the twenty-four hours
else if (date('H:i', strtotime('+ 24 hours', strtotime($t2))) > date('H:i', strtotime('+ 10 minutes', strtotime($t1))))
{
$this->graph[$vertex_key][] = new Edge(
NULL,
$vertex_key,
new Vertex($city_id, $t2, 0),
(float) 0,
$f(strtotime('+ 24 hours', strtotime($t2)) - strtotime($t1) / 60, 0, 1) //edge weight
);
}
}
}
}
}
}
example of variables:
var_dump($times_arrival); //$times_departure have the same structure
array
3 =>
array
0 =>
array
0 => string '1' (length=1)
1 => string '08:12' (length=5)
1 =>
array
0 => string '2' (length=1)
1 => string '08:40' (length=5)
41 =>
array
0 =>
array
0 => string '21' (length=2)
1 => string '12:40' (length=5)
Thank you all!
The reason of slow speed was coz of using functions strtotime() and date().
In that case only you can say whether you chose a good or bad algorithm. In my point of view your code not has no extra computations.
Only one recommendation - use Xdebug to profile your code and find out where the bottleneck is, if possible.
I have the following values from a database call that I want to apply some logic to. I thought I could originally use PHP's max however this doesn't appear to be the case.
I have three suppliers of a product. They might not all stock the item I am displaying, and they all offer a different margin, on a product by product basis though, so that is why I can't just say generally supplier 1 is better than supplier 2 etc.
$supplier1Live = 1
$supplier2Live = 1
$supplier3Live = 0
$marginSupplier1 = 20
$marginSupplier2 = 40
$martinSupplier3 = 50
In this example I would want to use Supplier 2 as they stock the product supplier2Live = 1 and also have the better margin than the other supplier who stocks the product (supplier1)
My mind however is drawing a complete blank in how to code this?
I thought I could add it to an array giving:
$array = array(
"supplier1" => array(
"live" => 1,
"margin" => 20
),
"supplier2" => array(
"live" => 1,
"margin" => 40
),
"supplier3" => array(
"live" => 0,
"margin" => 50
)
);
And run something on that, but not sure what to.
Filter the array using array_filter (filter by live==1), and then find the maximum out of the resultant array (maximum on the "margin" value)
Like this, if I understand correctly
$array = array(
"supplier1" => array(
"live" => 1,
"margin" => 20
),
"supplier2" => array(
"live" => 1,
"margin" => 40
),
"supplier3" => array(
"live" => 0,
"margin" => 50
)
);
$res = array_filter($array,function($v){return $v["live"];});
$supplier = array_reduce($res, function($a, $b){
return $a["margin"]>$b["margin"]?$a:$b;
});
print_r($supplier);
Try something like this:
$best_supplier = null;
$best_supplier_margin = null;
foreach($array as $name => $supplier) {
if($supplier['live']) {
if($supplier['margin'] > $best_supplier_margin || is_null($best_supplier_margin)) {
$best_supplier = $name;
$best_supplier_margin = $supplier['margin'];
}
}
}
if(is_null($best_supplier)) throw new Exception('No suppliers are live!');
echo $best_supplier;
So you basically want to find the max of supplierXLive * marginSupplierX?
You can also implement a custom compare function and provide it to PHPs usort() function