Loop through arrays with limitations - php

I have an array of transports:
$transports = [
0 => ['label' => 'Transport 1', 'maxPalettesToLoad' => 5],
1 => ['label' => 'Transport 2', 'maxPalettesToLoad' => 2]
];
and an array of palettes with certain products i want to "load" on the transport above
$palettes = [
0 => ['id_product' => 2, 'id_sale_order_item' => 1, 'amount' => 4],
1 => ['id_product' => 4, 'id_sale_order_item' => 4, 'amount' => 3]
];
What I'm struggling to accomplish is to load all the palettes onto transports where total number of loaded palettes is equal to maxPalettesToLoad parameter of each transport. I'm looking for a solution that will generate me values like:
$toret = [
'Transport 1' => [
0 => ['id_product' => 2, 'id_sale_order_item' => 1],
1 => ['id_product' => 2, 'id_sale_order_item' => 1],
2 => ['id_product' => 2, 'id_sale_order_item' => 1],
3 => ['id_product' => 2, 'id_sale_order_item' => 1],
4 => ['id_product' => 4, 'id_sale_order_item' => 4]
],
'Transport 2' => [
0 => ['id_product' => 4, 'id_sale_order_item' => 4],
1 => ['id_product' => 4, 'id_sale_order_item' => 4]
],
];
EDIT: Here is my attempt. It's kinda different in terms of data structure, because I simplified it to make it shorted
foreach ($transportsData as $transportData) {
$modelTransportOrder = new TransportOrder();
$modelTransportOrder->max_palettes_amount = $maxPalettesAmount;
$status &= $modelTransportOrder->save();
$amountAssigned = $transportData['palletes'];
if ($status) {
$j = 1;
foreach ($palettesData as $paletteData) {
if ($modelSaleOrderItem = SaleOrderItem::findOne([$paletteData['id_sale_order_item']])) {
for ($i = 0; $i < $paletteData['amount']; $i++) {
if ($j <= $amountAssigned) {
$modelTransportOrderItem = new TransportOrderItem();
$modelTransportOrderItem->id_transport_order = $modelTransportOrder->id;
$modelTransportOrderItem->id_product = $modelSaleOrderItem->id_product;
$modelTransportOrderItem->id_sale_order_item = $modelSaleOrderItem->id;
$modelTransportOrderItem->index = $j;
$status &= $modelTransportOrderItem->save();
// Index reset
if ($j == $amountAssigned) {
$j = 0;
break;
}
}
$j++;
}
} else {
$status = false;
}
}
} else {
// Zapis transportu nie powiódł się
}
}

I have managed to solve my problem using unset after each used palette:
foreach ($initedPalettes as $key => $palette) {
if ($i <= $amountAssigned) {
$modelsTransportOrderItem = self::createTransportOrderItem
(
$modelTransportOrder->id,
$palette['idProduct'],
$palette['idSaleOrderItem'],
$i
);
$status &= $modelsTransportOrderItem->save();
unset($initedPalettes[$key]);
$i++;
}
}

Related

reducing an array into another array

Inside a laravel blade template, I am trying to reduce an array like this:
$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];
into something like this:
$reducedList = [['count' => 2, 'kg' => 2], ['count' => 1, 'kg' => 3]];
This is what I have so far:
#php
$variableWeights = isset($sale->variable_weight_json) ? collect(json_decode($sale->variable_weight_json, true)) : null;
$groups = array();
if (isset($variableWeights)) {
$groups = $variableWeights->reduce(function($carry, $item) {
$index = array_search($item['kg'], array_column($carry, 'weight'));
if (isset($index)) {
$existing = $carry[$index];
array_splice($carry, $index, 1, [
'count' => $existing['count'] + 1,
'weight' => $item['kg']
]);
} else {
array_push($carry, [
'count' => 1,
'weight' => $item['kg'],
]);
}
return $carry;
}, array());
}
#endphp
But it is giving me the error Undefined offset: 0
I am new to php. How should the code be corrected or is there a better approach to achieve the desired result?
Why dont you reduce it to something simpler like this
$reducedList = [2 => 2, 3 => 1]
where the weight is the index and the value is the count.
$reducedList = [];
foreach ($longList as $box) {
if (isset($reducedList[$box['kg']]) {
$reducedList[$box['kg']]++;
} else {
$reducedList[$box['kg']] = 1;
}
}
This way you avoid complexity but you still get the same amount of information.
I guess you can achieve it with code like:
$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];
$reducedList = array_values(array_reduce(
$longList,
function($carry, $item) {
if (isset($carry[$item['kg']])) {
++$carry[$item['kg']]['count'];
} else {
$carry[$item['kg']] = ['count' => 1, 'kg' => $item['kg']];
}
return $carry;
},
[]
));
print_r($reducedList);
Here is a working example.
don't use isset() function. it checks only variable existing. use empty() or other condition, it will check variable existing and value. try this.
#php
$variableWeights = isset($sale->variable_weight_json) ? collect(json_decode($sale->variable_weight_json, true)) : null;
$groups = array();
if ($variableWeights->isNotEmpty()) {
$groups = $variableWeights->reduce(function($carry, $item) {
$index = array_search($item['kg'], array_column($carry, 'weight'));
if ($index != false ) {
$existing = $carry[$index]?: false;
if ($existing) {
array_splice($carry, $index, 1, [
'count' => $existing['count'] + 1,
'weight' => $item['kg']
]);
}
} else {
array_push($carry, [
'count' => 1,
'weight' => $item['kg'],
]);
}
return $carry;
}, array());
}
#endphp
You can use the sexy countBy() method (combined with the famous map() one) of the Collection class.
Try this:
$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];
$shortList = collect($longList)
->countBy('kg')
->map(function ($count, $kg) {
return [
'kg' => $kg,
'count' => $count,
];
});
With that, you'll get this:
dd($shortList);
=> Illuminate\Support\Collection {#3380
all: [
2 => [
"kg" => 2,
"count" => 2,
],
3 => [
"kg" => 3,
"count" => 1,
],
],
}
Here you have a working demo.
isset checks if variable is set (i.e it exists and not NULL). As I (and core developers) can hardly imagine in which cases array_search can return NULL, there's a manual which says:
Returns the key for needle if it is found in the array, FALSE otherwise.
So, you need to check if $index is not false:
$index = array_search($item['kg'], array_column($carry, 'weight'));
// Note that I use `!==` because using `!=` will convert
// `0` index to false, which is not correct in your case
if (false !== $index) {

How do I move an array element with a known key weight of an array in PHP?

Having a brain freeze over a fairly trivial problem. If I start with an array like this:
$my_array = array(
1 => [
"id" => 1,
"weight" => 0
],
2 => [
"id" => 2,
"weight" => -1
],
3 => [
"id" => 3,
"weight" => 0
],
4 => [
"id" => 4,
"weight" => -1
],
);
and i will do a function that move the keys of the array to key + 'weight'. So the result gona be like this:
$my_array = array(
1 => [
"id" => 2,
"weight" => -1
],
2 => [
"id" => 1,
"weight" => 0
],
3 => [
"id" => 4,
"weight" => -1
],
4 => [
"id" => 3,
"weight" => 0
],
);
What is the most efficient way to do this?
Here is the function if it can help some people
function reorder_array(&$my_array) {
foreach ($my_array as $key => $object) {
$move = $object['weight'];
$arr = $my_array;
if ($move == 0 || !isset($arr[$key])) {
continue;
}
$i = 0;
foreach($arr as &$val){
$val = array('sort' => (++$i * 10), 'val' => $val);
}
$arr[$key]['sort'] = $arr[$key]['sort'] + ($move * 10 + ($key == $key ? ($move < 0 ? -5 : 5) : 0));
uasort($arr, function($a, $b) {
return $a['sort'] > $b['sort'];
});
foreach($arr as &$val) {
$val = $val['val'];
}
$my_array = $arr;
}
}
Source of my solution

need a cleaner way to break a tie in php

I have a poll which has 4 choices, sometimes people vote for the same things and choices end up in a tie. I'm looking for a way to break this tie.
The poll variables are always different the below is just an example of how they come. I'm currently doing this with messy if statements and randomizing results for each scenario or one === the other.
<?php
$choice1=4;
$choice2=4;
$choice3=2;
$choice4=4;
if ($choice1==$choice2) {
$a = ['$choice1','$choice2'];
$breaker = $a[mt_rand(0, count($a)-1)];
echo $breaker;
}elseif ($choice1==$choice3) {
$a = ['$choice1','$choice3'];
$breaker = $a[mt_rand(0, count($a)-1)];
echo $breaker;
}elseif ($choice1==$choice4) {
$a = ['$choice1','$choice4'];
$breaker = $a[mt_rand(0, count($a)-1)];
echo $breaker;
}elseif ($choice2==$choice1) {
$a = ['$choice2','$choice1'];
$breaker = $a[mt_rand(0, count($a)-1)];
echo $breaker;
//etc...
// This goes on and on also goes up to 3 way ties and 4 way ties.
This method seems extremely inelegant, also the number of votes is going to increase as more and more users register to vote so it is not scalable at this point any suggestions?
This approach uses an array with two set of sorts.
// As many choices as you would like
$choices = [
['id' => 1, 'name' => 'choice1', 'count' => 4],
['id' => 2, 'name' => 'choice2', 'count' => 4],
['id' => 3, 'name' => 'choice3', 'count' => 2],
['id' => 4, 'name' => 'choice4', 'count' => 4],
['id' => 5, 'name' => 'choice5', 'count' => 4],
['id' => 6, 'name' => 'choice6', 'count' => 4],
['id' => 7, 'name' => 'choice7', 'count' => 4],
['id' => 8, 'name' => 'choice8', 'count' => 6],
['id' => 9, 'name' => 'choice9', 'count' => 7],
['id' => 10, 'name' => 'choice10', 'count' => 4],
['id' => 11, 'name' => 'choice11', 'count' => 6],
['id' => 12, 'name' => 'choice12', 'count' => 6],
['id' => 13, 'name' => 'choice13', 'count' => 7],
['id' => 14, 'name' => 'choice14', 'count' => 3],
['id' => 15, 'name' => 'choice15', 'count' => 4],
];
// First, sort by count
usort($choices, function($a, $b) {
if ( $a['count'] < $b['count'] ) return -1; // a < b
elseif ( $a['count'] > $b['count'] ) return 1; // a > b
return 0;
});
// Now, all are sorted by count.
// Walk through and deal with items that have the same count.
$buf = []; // A temp buffer to keep all items with a certain count
$prevCcount = null;
$output = [];
foreach($choices as $choice) {
$count = $choice['count'];
echo sprintf('id %d count %d', $choice['id'], $choice['count']) . "\n";
if ( $prevCount != $count ) {
// Possible new group of items with the same count
// Does the buffer have more than 1. If so, randomize.
if ( count($buf) > 1 ) {
echo "Shuffling " . count($buf) . "items\n";
$shuffled = shuffle($buf);
if ( ! $shuffled ) {
throw new Exception('Failed to shuffle');
}
}
if ( count($buf) > 0 ) {
$output = array_merge($output, $buf);
$buf = [];
}
}
$prevCount = $count; // Keep track of count of previous item
$buf[] = $choice; // add current item to buffer
}
// Deal with the tail
// There will be 1 or more items still in the buffer that must be dealt with
echo "Final buf has " . count($buf) . " items\n";
if ( count($buf) > 1 ) {
echo "Shuffling " . count($buf) . " items\n";
$shuffled = shuffle($buf);
if ( ! $shuffled ) {
throw new Exception('Failed to shuffle');
}
}
$output = array_merge($output, $buf);
print_r($output);
echo "\n";
You could use array_keys and max to get back which of them has the max score then chose one in that array in your liking
$choice1=4;
$choice2=4;
$choice3=2;
$choice4=4;
$arr = ["choice1" => $choice1,"choice2" => $choice2,"choice3" => $choice3,"choice4" => $choice4];
$winners = array_keys($arr, max($arr));
var_dump($winners);
Output:
array(3) {
[0]=>
string(7) "choice1"
[1]=>
string(7) "choice2"
[2]=>
string(7) "choice4"
}
You can then use your existing method to choose one of the three (in this case).
$breaker = $winners[mt_rand(0, count($winners)-1)];
echo $breaker;

Random weighted distribution

For example I have an array like:
$items = [
'0' => [
'name' => 'item1',
'percent' => 10
],
'2' => [
'name' => 'item2',
'percent' => 20
],
'3' => [
'name' => 'item3',
'percent' => 30
],
'4' => [
'name' => 'item4',
'percent' => 40
],
];
And function:
function echoRandomItem(){
}
If I call this function it should return 'name' of the item depending on the 'percent' value. Basically if I call this function 100 times it should return item1 10 times, item2 20 times, item3 30 times, item4 40 times.
Here is a solution I've come up with thanks to Oliver's hint. $items is a given array. wrand() function is responsible for random weighted distribution calculation.
$items = [
'1' => [
'name' => 'item1',
'rand' => 10
],
'2' => [
'name' => 'item2',
'rand' => 20
],
'3' => [
'name' => 'item3',
'rand' => 30
],
'4' => [
'name' => 'item4',
'rand' => 40
],
];
function wrand($data) {
foreach ($data as $value) {
$itemsRand[] = $value ['rand'];
}
$totalw = array_sum($itemsRand);
$rand = rand(1, $totalw);
$curw = 0;
foreach ($data as $val) {
$curw += $val['rand'];
if ($curw >= $rand) return $val['name'];
}
return end($data);
}
Further code simply calls wrand() function 100 times and counts the results.
static $item1 = 0;
static $item2 = 0;
static $item3 = 0;
static $item4 = 0;
for ($i = 0; $i < 100; $i++){
$k = wrand($items);
if ($k == 'item1') {
$item1++;
} elseif ($k == 'item2'){
$item2++;
} elseif ($k == 'item3'){
$item3++;
} elseif ($k == 'item4'){
$item4++;
}
}
echo "item1 = $item1<br>";
echo "item2 = $item2<br>";
echo "item3 = $item3<br>";
echo "item4 = $item4";

Php rearrange array according to the key

I tried convert the array type from $data to $data2 but didn't have success.
In the $data array all values placed according to dates.
But in the $data2 values are placed according to the title.
I wonder if it's convertable array.
$data['Group1']['Date1']['Empty'] = 5;
$data['Group1']['Date1']['Reservated'] = 1;
$data['Group1']['Date1']['WillReturn'] = 3;
$data['Group1']['Date1']['Remains'] = 1;
$data['Group1']['Date2']['Empty'] = 2;
$data['Group1']['Date2']['Reservated'] = 2;
$data['Group1']['Date2']['WillReturn'] = 3;
$data['Group1']['Date2']['Remains'] = -3;
$data['Group2']['Date1']['Empty'] = 0;
$data['Group2']['Date1']['Reservated'] = 1;
$data['Group2']['Date1']['WillReturn'] = 3;
$data['Group2']['Date1']['Remains'] = -4;
$data['Group2']['Date2']['Empty'] = 10;
$data['Group2']['Date2']['Reservated'] = 1;
$data['Group2']['Date2']['WillReturn'] = 1;
$data['Group2']['Date2']['Remains'] = 8;
$data2 = [
'Group1' => [
[ 'Title' => 'Empty',
'Date1' => 5,
'Date2' => 2,
],
[ 'Title' => 'Reservated',
'Date1' => 1,
'Date2' => 2,
],
[ 'Title' => 'WillReturn',
'Date1' => 3,
'Date2' => 3,
],
[ 'Title' => 'Remains',
'Date1' => 1,
'Date2' => -3,
],
// etc...
],
'Group2' => [
[ 'Title' => 'Empty',
'Date1' => 0,
'Date2' => 10,
],
[ 'Title' => 'Reservated',
'Date1' => 1,
'Date2' => 1,
],
[ 'Title' => 'WillReturn',
'Date1' => 3,
'Date2' => 1,
],
[ 'Title' => 'Remains',
'Date1' => -4,
'Date2' => -8,
],
// etc...
],
// etc...
];
Some try here:
$new = [];
$grp = array_keys($datax);
$i =0;
foreach($datax as $key => $val)
{
$dates = array_keys($val);
foreach($val as $k=>$v)
{
$cases = array_keys($v);
$caseAndVals[$i]=$v;
$i++;
}
}
$z =0;
$y = 0;
foreach($grp as $g)
{
foreach($cases as $c)
{
$new[$g][$z]['Title']=$c;
foreach($dates as $d)
{
$new[$g][$z][$d] = 'values which correspond to the date and title';
}
$z++;
}
}
I converted array according to group and cases but i didn't have success to place values corresponded dates...
You're very close.
To get the values which correspond to the date and title, use $data[$g][$d][$c].
And you need to reset $z to 0 each time through the $grp loop, so you get correct indexes on each group array.
$new = array();
$grp = array_keys($data);
foreach($data as $key => $val)
{
$dates = array_keys($val);
foreach($val as $k=>$v)
{
$cases = array_keys($v);
$caseAndVals[]=$v;
}
}
foreach($grp as $g)
{
$z = 0;
foreach($cases as $c)
{
$new[$g][$z]['Title']=$c;
foreach($dates as $d)
{
$new[$g][$z][$d] = $data[$g][$d][$c];
}
$z++;
}
}

Categories