Group array of rows and fill earliest group without exceeding capacity - php

I have a 2d array of "packs" with their own specified size in centimeters. I need to optimally distribute these packs into a new 3d array to represent pallets of packs which each have a maximum capacity of 265cm.
Current code which works with a 1d array of sizes:
$default_cc_height_fa = 265; // size of the pallet in cm
$sh_array = [50, 45, 30, 60, 70, 80]; // array of packs
// sort the array of packs in decreasing order of size
usort($sh_array, function($a, $b) {
return $b - $a;
});
// initialize the array of pallets
$mix_cc_array = [];
// iterate through the array of packs
foreach ($sh_array as $pack) {
// try to fit the pack into an existing pallet
$packed = false;
foreach ($mix_cc_array as &$pallet) {
if ($pack <= $default_cc_height_fa - array_sum($pallet)) {
$pallet[] = $pack;
$packed = true;
break;
}
}
// if the pack does not fit into any existing pallet, create a new one
if (!$packed) {
$mix_cc_array[] = [$pack];
}
}
print_r($mix_cc_array);
How is it possible to adjust the above code to accommodate a 2d array like this:
$sh_array = [
['id_product' => 13, 'size' => 50],
['id_product' => 13, 'size' => 45],
['id_product' => 13, 'size' => 30],
['id_product' => 13, 'size' => 60],
['id_product' => 13, 'size' => 70],
['id_product' => 13, 'size' => 80]
];
// array of array of packs
Of course, I need to manage the sizes, but I need entire rows of data (packs) to be pushed into the subarrays (pallets).

With minor modifications, we can loop through an array of packs that have both id and size attributes. To make the comparison between the size of the current pack and the maximum height constant, we can also add attributes to the pack to accumulate everything.
Finally, because I was interested in exiting both loops, I used continue 2 although you could keep your original $packed = false; approach as well.
<?php
const DEFAULT_CC_HEIGHT_FA = 265; // size of the pallet in cm
// define array of products to add
$sh_array = [["id_product" => 13, "size" => 50],
["id_product" => 13, "size" => 45],
["id_product" => 13, "size" => 30],
["id_product" => 13, "size" => 60],
["id_product" => 13, "size" => 70],
["id_product" => 13, "size" => 80]];
// sort the array of packs in decreasing order of size
usort($sh_array, function($a, $b) {
return $b["size"] - $a["size"];
});
// initialize the array of pallets
$mix_cc_array = [];
// iterate through the array of packs
foreach ($sh_array as $listRow) {
// try to fit the pack into an existing pallet
foreach ($mix_cc_array as &$pallet) {
$newHeight = $listRow["size"] + $pallet["totalHeight"];
if ($newHeight <= DEFAULT_CC_HEIGHT_FA) {
// add new pack to this pallet
array_push($pallet["pallets"],$listRow);
// update accumulators
$pallet["palletsStored"] += 1;
$pallet["totalHeight"] = $newHeight;
// continue the outer loop
continue 2;
}
}
// if the pack does not fit into any existing pallet, create a new one
$mix_cc_array[] = array("totalHeight"=> $listRow["size"],
"palletsStored" => 1, "pallets" => [$listRow]);
}
print_r($mix_cc_array);

The only difference between my earlier answer and this answer is how to sort and count the array data. The algorithm remains the same.
Code: (Demo)
$pallet_limit = 111;
$packs = [
['id_product' => 13, 'size' => 50],
['id_product' => 13, 'size' => 45],
['id_product' => 13, 'size' => 30],
['id_product' => 13, 'size' => 60],
['id_product' => 13, 'size' => 70],
['id_product' => 13, 'size' => 80]
];
usort($packs, fn($a, $b) => $b["size"] <=> $a["size"]);
$pallets = [];
foreach ($packs as $pack) {
foreach ($pallets as &$pallet) {
if ((array_sum(array_column($pallet, 'size')) + $pack["size"]) <= $pallet_limit) {
$pallet[] = $pack;
continue 2;
}
}
$pallets[] = [$pack];
}
var_export($pallets);
Sort the rows by size value in a descending direction.
Iterate your array of products.
Iterate a new array where pallets will be stored.
If a given pack "fits" in an encountered pallet, push it into that pallet; otherwise check the subsequent pallets; if no pallet is suitable, create a new pallet and push the pack into it.

Related

Divide integer by qualifying rows and add to column values

I need the variable $tsp to be evenly added to the value in the "count" field, but only in those fields where the "count" is greater than 0 .
$tsp = 9;
$fin = [
"1701" => ["total_space" => 0, "count" => 0],
"1702" => ["total_space" => 0, "count" => 0],
"1703" => ["total_space" => 20, "count" => 20],
"1704" => ["total_space" => 28, "count" => 28]
];
that 's what the result should be
$tsp = 9;
$fin = [
"1701" => ["total_space" => 0, "count" => 0],
"1702" => ["total_space" => 0, "count" => 0],
"1703" => ["total_space" => 20, "count" => 25], // +5
"1704" => ["total_space" => 28, "count" => 32] // +4
];
I wrote a loop, but it increases both fields by 9,
for ($i = $tsp; $i > 0; $i--) {
foreach ($fin as $dt => $s) {
if ($s['count'] > 0) {
$fin[$dt]['count'] = $s['count'] + 1;
$tsp = $tsp - 1;
}
}
}
The issue with your attempted implementation is that you iterate over all entries in the $fin array and increment the counter for each $tsp. You only want to increment one of those, not all ...
This would be a possible solution:
<?php
$tsp=9;
$fin = [
"1701"=> ["total_space"=> 0, "count"=> 0],
"1702"=> ["total_space"=> 0, "count"=> 0],
"1703"=> ["total_space"=> 20, "count"=> 20],
"1704"=> ["total_space"=> 28, "count"=> 28]
];
while ($tsp > 0) {
array_walk($fin, function(&$s) use (&$tsp) {
if ($tsp > 0 && $s['count'] > 0) {
$s['count']++;
$tsp--;
}
});
}
print_r($fin);
The output obviously is:
Array
(
[1701] => Array
(
[total_space] => 0
[count] => 0
)
[1702] => Array
(
[total_space] => 0
[count] => 0
)
[1703] => Array
(
[total_space] => 20
[count] => 25
)
[1704] => Array
(
[total_space] => 28
[count] => 32
)
)
Rather than brute forcing the arithmetic with single increments and decrements in a loop which re-checks for 0 counts, use a technique with lower time complexity that doesn't ever revisit 0 count rows.
My snippet filters the array once, then only adds value to qualifying rows once.
Code: (Demo)
$qualifiers = array_filter($fin, fn($row) => $row['count']);
$divideBy = count($qualifiers);
foreach ($qualifiers as $id => $row) {
$tsp -= $toAdd = ceil($tsp / $divideBy);
--$divideBy;
$fin[$id]['count'] += $toAdd;
}
var_export($fin);

How can I merge and sum simple Laravel Collections with the same keys?

I have three Collections with the same keys and different numeric values, and I'd like to sum them together.
e.g.
$broadInventory = {'horse': 300, 'cow': 400, 'entropy': 400};
$ericaInventory = {'horse': 10, 'cow': 20, 'entropy': 30};
$johnsonInventory = {'horse': 5, 'cow': 9, 'entropy': 3};
and I'm looking for this result:
{'horse': 315, 'cow': 429, 'entropy': 433};
I figured this would be easy, but it's turned out harder than I thought!
EDIT: Thanks to Abdulla, this is the answer I came up with:
private function sumCollections(Collection ...$collections) {
$merge = collect($collections);
$keys = $collections[0]->keys();
return $merge->pipe(
static function ($item) use ($keys) {
return collect([
$keys->mapWithKeys(fn($key) => [$key => $item->sum($key)])
]);
}
);
}
Bit tricky, but it works. By using pipe() with collection iteration.
# assume as given collection =
$broadInventory = collect(['horse' => 300, 'cow' => 400, 'entropy' => 400]);
$ericaInventory = collect(['horse' => 10, 'cow' => 20, 'entropy' => 30]);
$johnsonInventory = collect(['horse' => 5, 'cow' => 9, 'entropy' => 3]);
# Mergeing three collections into one
$merge = collect([$broadInventory, $ericaInventory, $johnsonInventory]);
# looping through the collection
$sum = $merge->pipe(static function ($item) {
return collect([
'horse' => $item->sum('horse'),
'cow' => $item->sum('cow'),
'entropy' => $item->sum('entropy'),
]);
});
print_r($sum);
First of all, that is wrong array/collection syntax you wrote up there.
convert all the collections to arrays using the toArray() function. Then use the array_merge_recursive() to merge the arrays so you get one single array with 3 keys. Then loop through the array and sum the values, appending each value to a the same array. Here is the explanation in code:
$broadInventory = collect(['horse' => 300, 'cow' => 400, 'entropy' => 400]);
$ericaInventory = collect(['horse' => 10, 'cow' => 20, 'entropy' => 30]);
$johnsonInventory = collect(['horse' => 5, 'cow' => 9, 'entropy' => 3]);
$broadInventory = $broadInventory->toArray();
$ericaInventory = $ericaInventory->toArray();
$johnsonInventory = $johnsonInventory->toArray();
$new_array = array_merge_recursive($broadInventory, $ericaInventory, $johnsonInventory);
foreach(array_keys($new_array) as $key) {
$new_array[$key] = array_sum($new_array[$key]);
}
$new_colection = collect($new_array);
dd($new_collection);
will give you:
^ Illuminate\Support\Collection {#1269 ▼
#items: array:3 [▼
"horse" => 315
"cow" => 429
"entropy" => 433
]
#escapeWhenCastingToString: false
}

Merge arrays on one column, sum other column values in each group [duplicate]

This question already has answers here:
Group 2d array data by one column and sum other columns in each group (separately)
(4 answers)
Closed last month.
I have 2 arrays:
$array1 = [
['amount' => 21600.00, 'rows' => 2, 'student_id' => 1],
];
$array2 = [
['amount' => 541990.00, 'rows' => 512, 'student_id' => 1],
['amount' => 347480.00, 'rows' => 281, 'student_id' => 2],
['amount' => 507400.00, 'rows' => 214, 'student_id' => 3],
];
I want to merge both arrays based on the same student_id value. When a student_id is found in both arrays, I want to sum the two amount values and the two rows values.
Expected result:
array (
0 =>
array (
'amount' => 563590.0,
'rows' => 514,
'student_id' => 1,
),
1 =>
array (
'amount' => 347480.0,
'rows' => 281,
'student_id' => 2,
),
2 =>
array (
'amount' => 507400.0,
'rows' => 214,
'student_id' => 3,
),
)
I have tried using array_merge() and array_merge_recursive() but they didn't give me the expected result.
Here is simple code to solve your problem,
count($arr) > count($arr0) ? ($greater = $arr AND $smaller = $arr0) : ($smaller = $arr AND $greater = $arr0);
foreach ($greater as $key => &$value) {
foreach ($smaller as $key1 => &$value1) {
if($value['student_id'] == $value1['student_id']){
$value['amount'] +=$value1['amount'];
$value['rows'] +=$value1['rows'];
}
}
}
Check output here
The comment about combining the queries is probably the better approach.
SELECT student_id, sum(amount) as amount, sum(rows) as rows
from students
group by student_id;
However, if you can't do it via a query, PHP doesn't natively add array values together (it doesn't make sense)
Here's what you'd have to do
function arrayMergeMatch(&$a, &$b)
{
foreach ($a as $key => $value) {
if (array_key_exists($key, $b)) {
// there's a matching index in both a & b
$a[$key] = [
$a[$key]['amount'] += $b[$key]['amount'],
$a[$key]['rows'] += $b[$key]['rows'],
$a[$key]['student_id'],
];
}
}
}
$firstArray = $secondArray = [];
$firstArray[0] = [
'amount' => 21600.00,
'rows' => 2,
'student_id' => 1
];
$secondArray[0] = [
'amount' => 541990.00,
'rows' => 512,
'student_id' => 1,
];
$secondArray[1] = [
'amount' => 347480.00,
'rows' => 281,
'student_id' => 2,
];
$secondArray[2] = [
'amount' => 507400.00,
'rows' => 214,
'student_id' => 3,
];
$firstArrayLength = count($x);
$secondArrayLength = count($y);
$combinedArray = null;
if ($firstArrayLength > $secondArrayLength) {
// loop through x checking to see if there's a matching y
arrayMergeMatch($firstArray, $secondArray);
$combinedArray = $firstArray;
}
else {
// y is longer than or equal to x, so loop through y, looking for matching
// index in x
arrayMergeMatch($secondArray, $firstArray);
$combinedArray = $secondArray;
}
print_r($combinedArray);
Nested loops are not necessary. There will be several different styles, but the crux of the task is to create a result array that temporarily serves as a lookup array while you iterate the incoming data.
To do this, assign temporary keys using student_id values. When a student_id is encountered after it is found in the result array, just add the values to the stored related values in the group. When finished iterating, re-index the result with array_values().
Functional style: (Demo1) (Demo2 - likely slower)
var_export(
array_values(
array_reduce(
$array2,
function($result, $row) {
if (!isset($result[$row['student_id']])) {
$result[$row['student_id']] = $row;
} else {
$result[$row['student_id']]['amount'] += $row['amount'];
$result[$row['student_id']]['rows'] += $row['rows'];
}
return $result;
},
array_column($array1, null, 'student_id')
)
)
);
Language construct loop: (Demo1) (Demo2 - likely slower)
$result = array_column($array1, null, 'student_id');
foreach ($array2 as $row) {
if (!isset($result[$row['student_id']])) {
$result[$row['student_id']] = $row;
} else {
$result[$row['student_id']]['amount'] += $row['amount'];
$result[$row['student_id']]['rows'] += $row['rows'];
}
}
var_export(array_values($result));
All of the above snippets should be used instead of the other posted answers because they will never iterate more than m + n. Here's a critique of the other answers:
Rahul's answer is using a nested loop -- this means the number of iterations will be m*n (number of rows in array1 multiplied by number of rows in array2) -- this is very indirect/inefficient. Even if a break was used, it still wouldn't be as direct/efficient as my snippets.
Kearney's answer is completely ignoring the requirement to sort on student_id values (even after the $x and $y typos are fixed). This php-based answer is simply incorrect. Proof. Less importantly, $b does not need to be made modifiable by reference.

Sort and shuffle multidimensional array

I am working on gaming project, which needs sort and shuffle multidimensional array. First i need to sort based on bid. If multiple bids are same i need to sort based on priority. If bid & priority both are same means I need to shuffle those elements. For example we have 3 array elements in bid=0.4 & priority=4. Id's are 102,103 & 104. These array element position should be shuffled.
array(
array('id' => 101, 'bid' => 0.5, 'priority' => 5),
array('id' => 102, 'bid' => 0.4, 'priority' => 4),
array('id' => 103, 'bid' => 0.4, 'priority' => 4),
array('id' => 104, 'bid' => 0.4, 'priority' => 4),
array('id' => 105, 'bid' => 0.3, 'priority' => 5),
array('id' => 106, 'bid' => 0.3, 'priority' => 5),
array('id' => 107, 'bid' => 0.2, 'priority' => 5),
array('id' => 108, 'bid' => 0.7, 'priority' => 5),
array('id' => 108, 'bid' => 0.1, 'priority' => 4)
);
You should take a look at array_multisort to do that. There are examples on that page on how to sort a multi-dimensional array
<?php
// Obtain a list of columns
foreach ($data as $key => $row) {
$bid[$key] = $row['bid'];
$prio[$key] = $row['priority'];
}
array_multisort($bid, SORT_ASC, $prio, SORT_ASC, $data);
?>
To shuffle I would add an extra column to the multidimensional array called "rand" and then fill it with a random number before you use multisort. Then you can add a 3th sort order on that column for shuffling.
Based on idea #Hugo's answer about using random weight:
$array = array(
array('id' => 101, 'bid' => 0.5, 'priority' => 5),
array('id' => 102, 'bid' => 0.4, 'priority' => 4),
array('id' => 103, 'bid' => 0.4, 'priority' => 4),
array('id' => 104, 'bid' => 0.4, 'priority' => 4),
array('id' => 105, 'bid' => 0.3, 'priority' => 5),
array('id' => 106, 'bid' => 0.3, 'priority' => 5),
array('id' => 107, 'bid' => 0.2, 'priority' => 5),
array('id' => 108, 'bid' => 0.7, 'priority' => 5),
array('id' => 108, 'bid' => 0.1, 'priority' => 4)
);
function cmp(&$a, &$b) { # notice the use of & in function signature
if ($a['bid'] - $b['bid']) {
return $a['bid'] - $b['bid'] > 0 ? 1 : -1; # bid is different, sort using bid
} else if ($a['priority'] - $b['priority']) {
return $a['priority'] - $b['priority'] > 0 ? 1 : -1; # priority is different, sort using priority
} else {
if (isset($a['rw']) == false) {
$a['rw'] = rand(1, 100); # assign random tie breaker
}
if (isset($b['rw']) == false) {
$b['rw'] = rand(1, 100); # assign random tie breaker
}
if ($a['rw'] - $b['rw']) {
return $a['rw'] - $b['rw'] > 0 ? 1 : -1; # sort using random weight
} else {
return 0;
}
}
}
usort($array, 'cmp');
var_dump($array);
Output
array(9) {
[0]=>array(3) {["id"]=>int(108) ["bid"]=>float(0.1) ["priority"]=>int(4)}
[1]=>array(3) {["id"]=>int(107) ["bid"]=>float(0.2) ["priority"]=>int(5)}
[2]=>array(4) {["id"]=>int(106) ["bid"]=>float(0.3) ["priority"]=>int(5) ["rw"]=>int(70)}
[3]=>array(4) {["id"]=>int(105) ["bid"]=>float(0.3) ["priority"]=>int(5) ["rw"]=>int(73)}
[4]=>array(4) {["id"]=>int(103) ["bid"]=>float(0.4) ["priority"]=>int(4) ["rw"]=>int(29)}
[5]=>array(4) {["id"]=>int(104) ["bid"]=>float(0.4) ["priority"]=>int(4) ["rw"]=>int(67)}
[6]=>array(4) {["id"]=>int(102) ["bid"]=>float(0.4) ["priority"]=>int(4) ["rw"]=>int(80)}
[7]=>array(3) {["id"]=>int(101) ["bid"]=>float(0.5) ["priority"]=>int(5)}
[8]=>array(3) {["id"]=>int(108) ["bid"]=>float(0.7) ["priority"]=>int(5)}
}
Usort would be ideal for this situation http://www.php.net/manual/en/function.usort.php
You define the function that does the comparisons
<?php
function cmp($a, $b)
{
if ($a['bid'] == $b['bid']) {
if ($a['priority'] == $b['priority']) return 0;
return ($a['priority'] < $b['priority']) ? -1 : 1;
}
return ($a['bid'] < $b['bid']) ? -1 : 1;
}
usort($data, "cmp");
?>
Also based on #Hugos answer, adding shuffle using PHP built in shuffle() and range() functions:
<?php
$data=array(
array('id'=>101,'bid'=>0.5,'priority'=>5),
array('id'=>102,'bid'=>0.4,'priority'=>4),
array('id'=>103,'bid'=>0.4,'priority'=>4),
array('id'=>104,'bid'=>0.4,'priority'=>4),
array('id'=>105,'bid'=>0.3,'priority'=>5),
array('id'=>106,'bid'=>0.3,'priority'=>5),
array('id'=>107,'bid'=>0.2,'priority'=>5),
array('id'=>108,'bid'=>0.7,'priority'=>5),
array('id'=>108,'bid'=>0.1,'priority'=>4)
);
$rand=range(0,count($data)-1);
shuffle($rand);
foreach ($data as $key => $row) {
$bid[$key] = $row['bid'];
$prio[$key] = $row['priority'];
}
array_multisort($bid, SORT_ASC, $prio, SORT_ASC, $rand, SORT_ASC, $data);
I first tried simply shuffling the array before sorting, but for some unknown reason, sorting seems to sort on id as well. Probably some effect of algorithm used.
You can use usort
usort($array,function ($a, $b) {
if ($a['id'] == $b['id']) {
if ($a['priority'] == $b['priority'] && $a['bid'] == $b['bid']) {
$pos = array(-1,0,1);
return $pos[mt_rand(0, 2)]; // shurffle
}
$a = $a['priority'];
$b = $b['priority'];
return ($a == $b) ? 0 : (($a > $b) ? - 1 : 1); // sorty by priority
}
// First i need to sort based on bid
$a = $a['id'];
$b = $b['id'];
return ($a == $b) ? 0 : (($a < $b) ? - 1 : 1); //sort by bid id
});
<?php
foreach ($data as $key => $row) {
//just create a random field
$data[$key]['randSort']= rand(1,999999);
$sameRand[$key] = $data[$key]['randSort'];
$bid[$key] = $row['bid'];
$prio[$key] = $row['priority'];
}
array_multisort($bid, SORT_ASC, $prio, SORT_ASC, $sameRand, SORT_ASC, $data);
?>
put elements to be shuffled into an array and remove them from the initial array until the initial array is empty.
call shuffle on the created array
put the shuffled array into a new array with the results.

Most efficient way to mathematically add two multidimensional arrays?

Consider two simple arrays:
<?php
$array1 = array(
'blue' => 5,
'green' => array(
'square' => 10,
'sphere' => 0.5,
'triangle' => 3
),
'red' => array(
'circle' => 1000,
),
'black' => 4,
);
$array2 = array(
'blue' => 1,
'green' => array(
'square' => 11,
'circle' => 5,
),
'purple' => 10,
'yellow' => array(
'triangle' => 4
),
'black' => array(
'circle' => 6,
),
);
I need to mathmatically add together in a recursive way, each value from each $array1 and $array2.
Preserve keys
Where a key does not exist in $array1 but does exist in $array2, the final array would simply contain the value of $array2 (and the other way around as well)
Where they exist in both, the numeric values would be added +
Non-numeric values wouldn't be touched
If a value on $array1 points to another sub-array, and in $array2 it points to a value, the end value would result in that key containing a subarray that contains the values from $array1 plus a new key/value using the parent name and it's value (see black in the example)
Should be able to work at virtually unlimited nesting
To clarify, e.g. if we said
<?php
$final = array_merge_special($array1, $array2);
// We would end up with, if you var_export()'d final, something like:
// (Note: Hope I didn't make mistakes in this or it will be confusing,
// so expect mild human error)
$final = array(
'blue' => 6, // 5+1
'green' => array(
'square' => 21, // (10+11)
'sphere' => 0.5, // only in $array1
'triangle' => 3 // only in $array1
'circle' => 5, // only in $array2
),
'purple' => 10, // only in $array2
'yellow' => array( // only in $array2
'triangle' => 4
),
'red' => array( // only in $array1
'circle' => 1000,
),
'black' => array(
'circle' => 6, // untouched, while $black is present in both, the $array1 value does not have a 'circle' key, and is actually only a key/value (see below)
'black' => 4, // the key/value from $array1 that was not a subarray, even though it was a subarray in $array2
),
);
This seems outragously daunting to me. I know I could loop over one array and get easily recursively add the values, and I have this working (somewhat), but it's when I get into special rules (such as ones for black) that I can't even imagine how broken code would look. There has to be a way to do this would looping over each array individually and unset()'ing values to merge?
You would use array_walk_recursive (see: See php Manual here) and possibly array_merge_recursive. I'd have to think it through further to get the full picture.
OK, decided that this wouldn't work! Array_walk_recursive doesn't pass keys that hold arrays to the function. This problem kept flowing aroung in my brain, so I just had to write a function to do it! Here it is:
function dosum($arin) {
$arout = array();
foreach ($arin as $key1 => $item1) {
$total = 0;
if(is_array($item1)) {
foreach($item1 as $key2 => $item2) {
if(is_numeric($key2))
$total += $item2;
else
if(is_array($item2))
$arout[$key1] = dosum(array($key2 => $item2));
else
$arout[$key1][$key2] =$item2;
}
if($total)
if(isset($arout[$key1]))
$arout[$key1][$key1] = $total;
else
$arout[$key1] = $total;
}
else
$arout[$key1] = $item1;
}
return $arout;
}
For the 2 arrays given, you would use it like this:
print_r(dosum(array_merge_recursive($array1, $array2)));

Categories