PHP array_reduce with initial parameter as an array - php

I have this initial array:
[
0 => ['id' => 5, 'value' => 50],
1 => ['id' => 6, 'value' => 60],
2 => ['id' => 7, 'value' => 70],
]
and want to convert it to:
[
5 => ['value' => 50],
6 => ['value' => 60],
7 => ['value' => 70],
]
At first, I tried to use map, but it can't modify the array keys, so I thought reduce would solve the problem because it reduces the array to a single value, in this case, an array. So I tried:
array_reduce(
$array,
function($carry, $item) {
return $carry[$item['id']] = $item['value'];
},
[]
);
But it returns this error Cannot use a scalar value as an array. What am I doing wrong? Does array_reduce cannot receive an array as an initial value?

Your array_reduce didn't work because You weren't returning the accumulator array (carry in Your case) from the callback function.
array_reduce(
$array,
function($carry, $item) {
$carry[$item['id']] = $item['value'];
return $carry; // this is the only line I added :)
},
[]
);
I came to this question while looking for a way to use array_reduce, so I felt I should write this comment. I hope this will help future readers. :)

As Mark Bakerdid it. I also did with foreach loop.
$arr = array(
array('id' => 5, 'value' => 50),
array('id' => 6, 'value' => 60),
array('id' => 7, 'value' => 70)
);
$result = array();
$result = array_column($arr, 'value', 'id');
array_walk($result, function(&$value) { $value = ['value' => $value]; });
//I did this using foreach loop, But the OP need it through array function.
//foreach($arr as $key => $value){
// $result[$value['id']] = array('value' => $value['value']);
//}
echo '<pre>';
print_r($result);
Result:
Array
(
[5] => Array
(
[value] => 50
)
[6] => Array
(
[value] => 60
)
[7] => Array
(
[value] => 70
)
)

Sometimes the best solutions are the simplest. Loop through your array and assign the id and value to a new array.
$new_array = array();
foreach ($array as $key => $arr) {
$new_array[$arr['id']] = array('value' => $arr['value']);
}

You can do it functionally. I suspect it's not actually more readable however.
array_combine(
array_column($a, 'id'),
array_map(function($v) { return ['value' => $v['value']]; }, $a)
);
Or even...
array_map(
function($v) { return ['value' => $v['value']]; },
array_column($a, null, 'id')
)

array_reduce($ar, function ($acc, $item) {
$acc[$item['id']] = [ 'value' => $item['value']];
return $acc;
}, [])

Related

Sum array values of a column within each column of an array with 3 levels

I'm trying to use array_sum() on columns within columns of a multidimensional array.
For eg: I have an array that looks like this:
$array = [
[['value' => 1100], ['value' => 1000], ['value' => 3000]],
[['value' => 1200], ['value' => 2000], ['value' => 2000]],
[['value' => 1300], ['value' => 4000], ['value' => 1000]],
];
I tried with:
$arr = [];
foreach($array as $point){
$arr[] = array_sum(array_column($array, $point[0]['value']));
}
print_r($arr);
but I'm expecting this output:
[['value' => 3600], ['value' => 7000], ['value' => 6000]]
Or more simply: [3600, 7000, 6000]
Tested here: https://onecompiler.com/php/3y3mxqky9
You can sum your columns doing like this:
foreach($array as $key => $point){
$arr[] = array_sum(array_column( array_column($array,$key),'value'));
}
print_r($arr);
Since you wish to have the sum vertically and not horizontally, the array_column style you used won't work. You can simply achieve this with 2 nested loops like below:
<?php
$arr = [];
foreach($array as $point){
foreach($point as $k => $v){
$arr[$k] = ($arr[$k] ?? 0) + $v['value'];
}
}
print_r($arr);
Online Demo
Transpose your input array, then isolate and sum the value column of data.
The variadic offering of $array to array_map() is the transposing part.
In other words, array_map(fn(...$col) => $col, ...$array) converts rows of data into columns of data.
Code: (Demo)
var_export(
array_map(
fn(...$col) => array_sum(array_column($col, 'value')),
...$array
)
);
Output:
array (
0 => 3600,
1 => 7000,
2 => 6000,
)

Reindex multidimensional array

I'd like to re-index a multidimensional array to be like this:
[
0 => ['id' => 1, 'children' => [['id' => 2], ['id' => 3]]],
4 => ['id' => 5, 'children' => [['id' => 6], ['id' => 7]]]
8 => ...
]
If I use array_walk_recursive I get:
[
0 => ['id' => 1, 'children' => [['id' => 2], ['id' => 3]]],
1 => ['id' => 4, 'children' => [['id' => 5], ['id' => 6]]]
2 => ...
]
This is almost there, but not quite...
array_walk_recursive($out, function(&$item, $key) {
if($key == 'id')
{
$item = $this->_i;
$this->_i++;
}
});
Managed to solve this, it not pretty (I later found that the problem was elsewhere and left the code in an proof of concept state). Posting this if anyone else need this kind of structure.
// restructure the array for reindexing
$out = [];
$k = 0;
foreach ($arr as $as)
{
$test = ['id' => $k, 'items' => $as];
array_push($out, $test);
$k++;
}
// reindex multidimensiona array
$i = 1;
array_walk_recursive($out, function(&$item, $key) {
global $i;
if($key == 'id')
{
$item = $i;
$i++;
}
});
// final array restructuring
$res = [];
foreach($out as $oo)
{
$firstKey = array_key_first($oo);
$res[$oo[$firstKey]] = $oo['items'];
}
var_dump( $res );

how to combine php array of object by unique id?

I have these 2 arrays.
First array is from user input $cart:
$cart = [
['id' => 3, 'weight' => 20, 'percentage' => 80],
['id' => 1, 'weight' => 50, 'percentage' => 80],
['id' => 2, 'weight' => 40, 'percentage' => 80],
];
and second array, I do a database SELECT id, stock WHERE id IN (3,1,2), resulting $db_item
$db_item = [
['id' => 1, 'stock' => 9539.00],
['id' => 2, 'stock' => 9468.00],
['id' => 3, 'stock' => 9295.00],
];
I want to add the stock attribute in second array to first array.
Expected output:
$cart = [
['id' => 3, 'weight' => 20, 'percentage' => 80, 'stock' => 9295.00],
['id' => 1, 'weight' => 50, 'percentage' => 80, 'stock' => 9539.00],
['id' => 2, 'weight' => 40, 'percentage' => 80, 'stock' => 9468.00],
];
This is what I tried, and it works, but I don't think it is necessary to have foreach, array_filter, and array_column:
foreach ($cart as $key => $cart_item) {
$item = array_filter($db_item, function($item) use ($cart_item) {
return $item['id'] === $cart_item['id'];
});
$cart[$key]['stock'] = array_column($item, 'stock')[0];
}
anyone has better idea how to optimize this?
EDIT: following Mohammad's answer, I can use more attribute in second array
$keys = [];
foreach ($arr2 as $item) {
$keys[$item['id']] = array(
'attr1' => $item['attr1'],
'attr2' => $item['attr2'],
// and so on
);
}
$newArr = array_map(function($item) use($keys){
$item['attr1'] = $keys[$item['id']]['attr1'];
$item['attr2'] = $keys[$item['id']]['attr2'];
// and so on
return $item;
}, $arr1);
EDIT2: found out that we can simplify the foreach loop with just a single line using array_column.
$keys = array_column($arr2, null, 'id');
$newArr = array_map(function($item) use($keys){
$item['attr1'] = $keys[$item['id']]['attr1'];
$item['attr2'] = $keys[$item['id']]['attr2'];
// and so on
return $item;
}, $arr1);
Use combination of array_flip() and array_column() to create array contain id and index of second array.
Then use array_map() to add new key stock to first array.
$keys = array_flip(array_column($arr2, 'id'));
$newArr = array_map(function($item) use($keys, $arr2){
$item['stock'] = $arr2[$keys[$item['id']]]['stock'];
return $item;
}, $arr1);
Check result in demo
Also you can use foreach instead of array_flip()
$keys = [];
foreach ($arr2 as $item)
$keys[$item['id']] = $item['stock'];
$newArr = array_map(function($item) use($keys){
$item['stock'] = $keys[$item['id']];
return $item;
}, $arr1);
Check result in demo
This can help too, one array_column + one array_map :
$arr2=array_column($arr2,'stock','id');
$arr1=array_map(function($val)use($arr2){$val['stock']=$arr2[$val['id']];return $val;},$arr1);

Reformat array without temp variable

I have the next array
[
['id' => 30, 'count' => 3],
['id' => 45, 'count' => 7]
]
I need it to be
[
30 => ['count' => 3],
45 => ['count' => 7]
]
What I did
$formatted = [];
foreach ($services as $service) {
$formatted[$service['id']] = [
'count' => $service['count']
];
}
What I'd like is a more elegant solution without the temporary $formatted variable. Thanks!
Update. Thanks a lot #rtrigoso !
With the laravel collection, my code looks next
$services->reduce(function ($carry, $item) {
$carry[$item['id']] = ['count' => $item['count']];
return $carry;
});
You can do this in one line with array_column:
$array = array_column($array, null, 'id');
The one difference between your desired output is that this will still contain the id key in the second level of the array, like so:
[
30 => ['id' => 30, 'count' => 3],
45 => ['id' => 45, 'count' => 7],
]
but that hopefully shouldn't cause any problems. If you do need to remove it, you can do it with something like:
$array = array_map(function ($e) {
unset($e['id']);
return $e;
}, $array);
This approach is probably best if your rows could potentially have a lot more keys in them in future, i.e. it's quicker to list the keys to remove rather than the ones to keep. If not, and you'll only have a count, then to be honest your original example is probably the best you'll get.
You can use array_reduce
$x_arr = array(
array('id' => 30, 'count' => 3),
array('id' => 45, 'count' => 7),
);
$y_arr = array_reduce($x_arr, function ($result, $item) {
$result[$item['id']] = array('count' => $item['count']);
return $result;
}, array());
print_r($y_arr);
It will give you your desired result:
Array
(
[30] => Array
(
[count] => 3
)
[45] => Array
(
[count] => 7
)
)

Is there an array function in PHP to do this instead of a foreach loop?

In PHP, is there an array function do the same thing as the code below? I'm wondering if it's possible with something like array_map or array_reduce in such a way that the $items array does not need to be declared beforehand.
$data = [
['order_product_id' => 123, 'quantity' => 1],
['order_product_id' => 456, 'quantity' => 2],
['order_product_id' => 567, 'quantity' => 3],
];
$items = [];
foreach ($data as $item) {
$items[$item->order_product_id] = ['quantity' => $item->quantity];
}
print_r($items);
/*
Array
(
[123] => ['quantity' => 1]
[456] => ['quantity' => 2]
[789] => ['quantity' => 3]
)
*/
You can use array_map to do that:
<?php
$data = [
['order_product_id' => 123, 'quantity' => 1],
['order_product_id' => 456, 'quantity' => 2],
['order_product_id' => 567, 'quantity' => 3],
];
$items = [];
array_map(function ($item) use (&$items) {
$items[$item['order_product_id']] = ['quantity' => $item['quantity']];
}, $data);
You can use two array_map calls to do this too:
$keys = array_map(function ($item) {
return $item['order_product_id'];
}, $data);
$values = array_map(function ($item) {
return ['quantity' => $item['quantity']];
}, $data);
$items = array_combine($keys, $values);
Or if you don't want to declare any temporary variables:
$items = array_combine(
array_map(function ($item) {
return $item['order_product_id'];
}, $data),
array_map(function ($item) {
return ['quantity' => $item['quantity']];
}, $data)
);
None of them are as efficient as foreach.
Array_reduce will do what you want as it hides the declaration of the output array in the 'initial value' of the 'carry' or 'accumulator' parameter. Also, rather than 'reduce' it actually adds entries to the output.
imo, It is more useful to think of the carry parameter to the callback as an accumulator. In this case, the 'accumulator' is an array, that we are adding entries to.
imo, It isn't as easy to understand as the original version. And is no faster.
$data = [
(object) ['order_product_id' => 123, 'quantity' => 1],
(object) ['order_product_id' => 456, 'quantity' => 2],
(object) ['order_product_id' => 567, 'quantity' => 3],
];
$items = array_reduce($data,
function ($out, $item) {
$out[$item->order_product_id] = ['quantity' => $item->quantity];
return $out;
},
array());
print_r($items);
Output:
Array(
[123] => Array([quantity] => 1)
[456] => Array([quantity] => 2)
[567] => Array([quantity] => 3)
)

Categories