Merging by array value (and doing calculations with the other keys) - php

I'm having a difficult time finding a viable solution for this problem:
This is the array I have, and I want to group this array by product_name and count the quantity. But array structure needs to remain the same.
Array(
[0] => Array
(
[product_quantity] => 1
[product_name] => Appel
)
[1] => Array
(
[product_quantity] => 1
[product_name] => D-Day
)
[2] => Array
(
[product_quantity] => 4
[product_name] => D-Day
)
[3] => Array
(
[product_quantity] => 2
[product_name] => D-Day
)
[4] => Array
(
[product_quantity] => 1
[product_name] => Emiel
)
[5] => Array
(
[product_quantity] => 9
[product_name] => Emiel
)
[6] => Array
(
[product_quantity] => 3
[product_name] => Estella
)
[7] => Array
(
[product_quantity] => 4
[product_name] => Feeke
)
[8] => Array
(
[product_quantity] => 1
[product_name] => Feeke
)
[9] => Array
(
[product_quantity] => 7
[product_name] => Reset
)
[10] => Array
(
[product_quantity] => 1
[product_name] => Reset
))
What I need as output:
Array(
[0] => Array
(
[product_quantity] => 1
[product_name] => Appel
)
[1] => Array
(
[product_quantity] => 7
[product_name] => D-Day
)
[2] => Array
(
[product_quantity] => 10
[product_name] => Emiel
)
[3] => Array
(
[product_quantity] => 3
[product_name] => Estella
)
[4] => Array
(
[product_quantity] => 5
[product_name] => Feeke
)
[5] => Array
(
[product_quantity] => 8
[product_name] => Reset
)
)
I would love to hear some advice, or solutions on how to tackle this!

The goal can be reached in many ways. If you aim for speed then use a foreach to iterate over the input array, incrementally computing and storing the values into a new array (the body of the callback function below).
If you aim to impress your workmates or a recruiter with your knowledge of PHP then use array_reduce():
$output = array_values( // We don't need the keys of the generated array
array_reduce( // Incrementally process $input using a callback
$input,
// $carry is the partial result
// $item is the currently processed item
function(array $carry, array $item) {
// Extract the product name into a local variable
// we'll use it several times below
$name = $item['product_name'];
// If it's a new product then initialize its entry in the output
if (! array_key_exists($name, $carry)) {
$carry[$name] = array(
'product_quantity' => 0,
'product_name' => $name,
);
}
// Update the quantity of this group
$carry[$name]['product_quantity'] += $item['product_quantity'];
// Return the partial result
return $carry;
},
// Start with an empty array
array()
)
);
Update
The OP asked in a comment how to reuse the callback function and make it customizable ("how do I use a parameter to make 'product_name' dynamicaly?").
You can put the value ('product_name') into a variable ($key) outside the function and use the use language construct to let the anonymous function inherit it.
$key = 'product_name'; // The values needs to be in a variable
$output = array_values(
array_reduce(
$input,
// The 'use' keyword lets the anonymous function use a copy of $key
function(array $carry, array $item) use ($key) {
$name = $item[$key];
// The rest of the callback's code comes here unchanged
// ...
// Return the partial result
return $carry;
},
array()
)
);
See Example #3 on the documentation of anonymous functions for more examples.
You can store the callback function into a variable. This doesn't improve anything but the readability of the code. However, keep reading, this is just an intermediate step, the real magic happens below.
$key = 'abc';
$f = function(array $carry, array $item) use ($key) {
$name = $item[$key];
// The rest of the callback's code comes here unchanged
// ...
// Return the partial result
return $carry;
};
$output = array_values(array_reduce($input, $f, array()));
To make it really reusable you need to have a way to generate callbacks for different values of $key. Let's write a simple function that creates parametrized callbacks:
// This function creates a new callback that uses the value of argument $key
function makeFn($key)
{
return function(array $carry, array $item) use ($key) {
$name = $item[$key];
// The rest of the callback's code comes here unchanged
// ...
// Return the partial result
return $carry;
};
}
Now, let's use the new function. It creates different callback functions that can be used to reduce the array by using various keys:
$output1 = array_values(array_reduce($input, makeFn('product_name'), array()));
$output2 = array_values(array_reduce($input, makeFn('product_code'), array()));

I do not test it, but I think that it can solve your problem.
$stocks = [];
foreach ($products as $product) {
if (isset($stocks[$product['product_name']])) {
$stocks[$product['product_name']] += $product['product_quantity'];
} else {
$stocks[$product['product_name']] = $product['product_quantity'];
}
}
$finalList = [];
foreach ($stocks as $key => $stock) {
$finalList[] = [
'product_quantity' => $stock,
'product_name' => $key
];
}
print_r($finalList);

Related

get specific array in a multi-dimentional array on php

I have the following structure of an array which is decoded from a JSON object. From this array result I need to fork widgets for processing. It is an unstable JSON array so the parent keys may differs in future but the widgets will not.
(
[2] => Array
(
[MA] => Array
(
[0] => Array
(
[activities] => Array
(
[0] => Array
(
[activity_id] => 3
[activity_name] => Excavation
[activity_unique_id] => EXCAV-d435
[created_date] => 2021-02-08 21:42:53
[end_date] => 2021-02-08 21:42:08
[fk_client_id] => 1
[fk_main_activity] => 3
[fk_project_id] => 2
[relation_type] => MA-A-SA-W
[start_date] => 2021-02-08 21:42:08
[widgets] => Array
(
[0] => Array
(
[activity_mapping] => STRUC-be4c
[check_box_val] => 0
[checkbox_unique_id] => EXCAV-dceae
[checkbox_widget_id] => 5
[created_date] => 2021-02-08 21:43:12
)
)
)
)
[created_date] => 2021-02-08 21:42:44
[end_date] => 2021-02-08 21:42:08
[fk_client_id] => 1
[fk_floor_ids] => 7
[fk_project_id] => 2
[main_activity_id] => 3
[main_activity_name] => Structure MA1
[main_activity_unique_id] => STRUC-be4c
[relation_type] => MA-A-SA-W
[sequence_no] => 1
[start_date] => 2021-02-08 21:42:08
[tower_ids] => 4
[widgets_ids] =>
)
...
it is a big source So I cut shorted it. From the above result set I only need to fork the following widgets object.
[widgets] => Array
(
[0] => Array
(
[activity_mapping] => STRUC-be4c
[check_box_val] => 0
[checkbox_unique_id] => EXCAV-dceae
[checkbox_widget_id] => 5
[created_date] => 2021-02-08 21:43:12
)
)
)
Thanks in advance.
This recursive function searches for the first occurrence of a key in a multidimensional array and returns its value.
function array_find_key(array $array, $key) {
if (array_key_exists($key, $array)) {
return [$key => $array[$key]];
} else {
foreach($array as $item) {
if (is_array($item) && $result = array_find_key($item, $key)) {
return $result;
}
}
}
}
$result = array_find_key($array, 'widgets');
fiddle
The answer from #id'7238 is designed to search for a qualifying value in the current level before attempting to traverse a deeper level. The nuance in my answer (which does not call array_key_exists()) is that it checks keys as it traverses a level and will go down a level as soon as a non-qualifying subarray is encountered.
While looping, if the key is found, the payload is returned (and potentially passed up the recursive stack). If a value is array type data, then the next level down is processed -- if that level contains a qualifying key, it is passed up the stack.
If there are no qualifying keys in the input array, then null will be returned.
Code: (Demo)
function searchByKey(array $array, $find) {
foreach($array as $k => $v) {
if ($k === $find) {
return [$k => $v];
}
if (is_array($v)) {
$result = searchByKey($v, $find);
if ($result) {
return $result;
}
}
}
}
var_export(searchByKey($a, 'foo'));
For fun, if the nesting level will be the same just with the keys being different, this will give you the widgets array:
$result = current(current(current(current(current($array)))))['widgets'];
This will give you the array under the widgets array:
$result = current(current(current(current(current(current($array)))))['widgets']);
The first example will only work if the widgets array is 5 levels deep in the array and for the second if there is only 1 array under widgets (if there are more you will get the first).

Multivalue search to find index number in multidimensional array

I have this array stored in $data variable:
Array
(
[0] => Array
(
[delivery_plan] => 2021/07
[code] => CR020
[amount] => 250
)
[1] => Array
(
[delivery_plan] => 2021/08
[code] => CR020
[amount] => 350
)
[2] => Array
(
[delivery_plan] => 2021/09
[code] => CR020
[amount] => 100
)
I'd like to find the numeric index by using the values of "delivery_plan" and "code".
For example the delivery plan = 2021/09 AND code = CR020 then the expected result is 2.
The code i've been using so far :
function searchForId($delivery_plan, $code, $array) {
foreach ($array as $key => $arr) {
if ($arr['delivery_plan'] === $delivery_plan && $arr['code'] === $code) {
return $key;
}
}
return null;
}
$id = searchForId('2021/06','CR020', $data);
print_r($id);
I've tried using loops and the result was fine (2) , however i don't want to using any loops for this case
What is the faster/better solution other than using the loops ?

functions to get/set values in multidimensional arrays dynamically

I am trying to write a shoping cart in php and I have a problem with get/set values in multidimentional arrays.
I keep the current order in $_SESSION['basket']. It looks like that:
[basket] => Array
(
[0] => Array
(
[pid] => 3
[name] => Camera
[price] => 200.99
[quantity] => 1
)
[1] => Array
(
[pid] => 5
[name] => Computer
[price] => 320.99
[quantity] => 1
[extras] => Array
(
[0] => Array
(
[pid] => 86
[name] => RAM
[price] => 99
[qty] => 1
)
[1] => Array
(
[pid] => 98
[name] => CD-ROM
[price] => 19.99
[qty] => 1
)
)
)
)
Every item is stored as a subarray. I have a function, which checks if a given item exists in the basket array and returns the path to it. For example, if I want to check for a product with id 98 (CD-Rom), the function returns the following path: 1:extras:1.
I cant figure out how to use the path if I want to get or a set a value in the array. Is it possible to construct the path to an array key, without the use of eval()? I have these functions:
function get_val($array, $path, $key) {
//some code
return eval('return '.$array.$path.$key.';');
}
So, $price = get_val($_SESSION['basket'], $path, 'price'); should return the price for CD-ROM (19.99)
function set_val($array, $path, $key, $value) {
//some code
$str = eval(''.$array.$path.$key.';');
$str = $value;
}
set_val($_SESSION['basket'], $path, 'price', '30'); will set the price for CD-ROM to 30.
Is there a better way for doing this?
Thank you.
Here you go a code I have finetuned some time ago:
function get_val($array,$path) {
for($i=$array; $key=array_shift($path); $i=$i[$key]) {
if(!isset($i[$key])) return null;
}
return $i;
}
function set_val(&$array,$path,$val) {
for($i=&$array; $key=array_shift($path); $i=&$i[$key]) {
if(!isset($i[$key])) $i[$key] = array();
}
$i = $val;
}
See this test example, I believe it is what you are looking for:
$data = array("x"=>array("y"=>array("z"=>"foo")));
echo get_val($data,array("x","y","z")); // foo
set_val($data,array("x","y","u"),"bar"); // $data["x"]["y"]["u"] = "bar";
Yesterday people down voted me because I got this function. And today I hope someone can use it.
Getting values
Below function will return the value of the path you define.
function getPath($path, $array)
{
$path = split(":", $path);
$active = $array;
foreach($path as $key => $part)
{
$active = $active[$part];
}
return $active;
}
$array = array(array(array(array("product" => array( "id" => 12 )))));
// Give the path to the data you want, by keys
echo getPath("0:0:0:product:id", $array);
Which echo's
12
And setting values
function setPath($path, &$array, $mykey, $value)
{
$path = split(":", $path);
$active =& $array;
foreach($path as $key => $part)
{
$active =& $active[$part];
}
$active[$mykey] = $value;
return $active;
}
$array = array(array(array(array("product" => array( "id" => 12 )))));
// Give the path to the data you want, by keys
setPath("0:0:0:product", $array, "price", 100);
print_r($array);
Results:
Array ( [0] => Array ( [0] => Array ( [0] => Array ( [product] => Array ( [id] => 12 [price] => 100 ) ) ) ) )

How can I create multidimensional arrays from a string in PHP?

So My problem is:
I want to create nested array from string as reference.
My String is "res[0]['links'][0]"
So I want to create array $res['0']['links']['0']
I tried:
$result = "res[0]['links'][0]";
$$result = array("id"=>'1',"class"=>'3');
$result = "res[0]['links'][1]";
$$result = array("id"=>'3',"class"=>'9');
when print_r($res)
I see:
<b>Notice</b>: Undefined variable: res in <b>/home/fanbase/domains/fanbase.sportbase.pl/public_html/index.php</b> on line <b>45</b>
I need to see:
Array
(
[0] => Array
(
[links] => Array
(
[0] => Array
(
[id] => 1
[class] => 3
)
)
)
[1] => Array
(
[links] => Array
(
[0] => Array
(
[id] => 3
[class] => 9
)
)
)
)
Thanks for any help.
So you have a description of an array structure, and something to fill it with. That's doable with something like:
function array_create(&$target, $desc, $fill) {
preg_match_all("/[^\[\]']+/", $desc, $uu);
// unoptimized, always uses strings
foreach ($uu[0] as $sub) {
if (! isset($target[$sub])) {
$target[$sub] = array();
}
$target = & $target[$sub];
}
$target = $fill;
}
array_create( $res, "[0]['links'][0]", array("id"=>'1',"class"=>'3') );
array_create( $res, "[0]['links'][1]", array("id"=>'3',"class"=>'9') );
Note how the array name itself is not part of the structure descriptor. But you could theoretically keep it. Instead call the array_create() function with a $tmp variable, and afterwards extract() it to achieve the desired effect:
array_create($tmp, "res[0][links][0]", array(1,2,3,4,5));
extract($tmp);
Another lazy solution would be to use str_parse after a loop combining the array description with the data array as URL-encoded string.
I have a very stupid way for this, you can try this :-)
Suppose your string is "res[0]['links'][0]" first append $ in this and then put in eval command and it will really rock you. Follow the following example
$tmp = '$'.'res[0]['links'][0]'.'= array()';
eval($tmp);
Now you can use your array $res
100% work around and :-)
`
$res = array();
$res[0]['links'][0] = array("id"=>'1',"class"=>'3');
$res[0]['links'][0] = array("id"=>'3',"class"=>'9');
print_r($res);
but read the comments first and learn about arrays first.
In addition to mario's answer, I used another function from php.net comments, together, to make input array (output from jquery form serializeArray) like this:
[2] => Array
(
[name] => apple[color]
[value] => red
)
[3] => Array
(
[name] => appleSeeds[27][genome]
[value] => 201
)
[4] => Array
(
[name] => appleSeeds[27][age]
[value] => 2 weeks
)
[5] => Array
(
[name] => apple[age]
[value] => 3 weeks
)
[6] => Array
(
[name] => appleSeeds[29][genome]
[value] => 103
)
[7] => Array
(
[name] => appleSeeds[29][age]
[value] => 2.2 weeks
)
into
Array
(
[apple] => Array
(
[color] => red
[age] => 3 weeks
)
[appleSeeds] => Array
(
[27] => Array
(
[genome] => 201
[age] => 2 weeks
)
[29] => Array
(
[genome] => 103
[age] => 2.2 weeks
)
)
)
This allowed to maintain numeric keys, without incremental appending of array_merge. So, I used sequence like this:
function MergeArrays($Arr1, $Arr2) {
foreach($Arr2 as $key => $Value) {
if(array_key_exists($key, $Arr1) && is_array($Value)) {
$Arr1[$key] = MergeArrays($Arr1[$key], $Arr2[$key]);
}
else { $Arr1[$key] = $Value; }
}
return $Arr1;
}
function array_create(&$target, $desc, $fill) {
preg_match_all("/[^\[\]']+/", $desc, $uu);
foreach ($uu[0] as $sub) {
if (! isset($target[$sub])) {
$target[$sub] = array();
}
$target = & $target[$sub];
}
$target = $fill;
}
$input = $_POST['formData'];
$result = array();
foreach ($input as $k => $v) {
$sub = array();
array_create($sub, $v['name'], $v['value']);
$result = MergeArrays($result, $sub);
}

php - recreate array?

I've "inherited" some data, which I'm trying to clean up. The array is from a database which, apparently, had no keys.
The array itself, is pretty long, so I'm simplifying things for this post...
[0] => Array
(
[id] => 2
[uid] => 130
[eid] => 8
[ename] => Standard
[eaction] => Check
)
[1] => Array
(
[id] => 2
[uid] => 110
[eid] => 8
[ename] => Standard
[eaction] => Check
)
[2] => Array
(
[id] => 2
[uid] => 200
[eid] => 8
[ename] => Standard
[eaction] => Check
)
I'm trying to shift things around so the array is multidimensional and is grouped by ename:
[0] => Array
(
[Standard] => Array
(
[id] => 2
[uid] => 130
[eid] => 8
[eaction] => Check
)
)
[0] => Array
(
[Standard] => Array
(
[id] => 2
[uid] => 130
[eid] => 8
[eaction] => Check
)
)
[0] => Array
(
[Standard] => Array
(
[id] => 2
[uid] => 130
[eid] => 8
[eaction] => Check
)
)
Anyone know how to do something like this?
You can use usort() to sort an array by a user-defined function. That function could compare the ename fields. Then it's just a simple transformation. Like:
usort($array, 'cmp_ename');
function cmp_ename($a, $b) {
return strcmp($a['ename'], $b['ename']);
}
and then:
$output = array();
foreach ($array as $v) {
$ename = $v['ename'];
unset($v['ename']);
$output[] = array($ename => $v);
}
$outputarray = array();
foreach($inputarray as $value) {
$outputarray[] = array($value['ename'] => $value);
}
would accomplish what your examples seem to indicate (aside from the fact that your 'result' example has multiple things all with key 0... which isn't valid. I'm assuming you meant to number them 0,1,2 et cetera). However, I have to wonder what benefit you're getting from this, since all it appears to be doing is adding another dimension that serves no purpose. Perhaps you could clarify your example if there are other things to take into account?
$outputarray = array();
foreach($inputarray as &$value) {
$outputarray[][$value['ename']] = $value;
unset($value['ename']);
} unset($value);
I'm guessing that this is what you're asking for:
function array_group_by($input, $field) {
$out = array();
foreach ($input as $row) {
if (!isset($out[$row[$field]])) {
$out[$row[$field]] = array();
}
$out[$row[$field]][] = $row;
}
return $out;
}
And usage:
var_dump(array_group_by($input, 'ename'));
philfreo was right but he was also off a little. with his code every time you encounter an array element with an ['ename'] the same as one you've already gone through it will overwrite the data from the previous element with the same ['ename']
you might want to do something like this:
$output = array();
foreach ($YOURARRAY as $value) {
$output[$value['ename']][] = $value;
}
var_dump($output); // to check out what you get

Categories