Reindex multidimensional array - php

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 );

Related

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);

How to recursively filter an array if value exists on another array

I have two arrays.
The first one is the structure, an associative array that containts all elements inside categories. the name of the categories are keys inside this array. The categories can have different levels of depth:
check this example:
$structure = [
'fruits' => [
'sweet' => [
'red' => [
'watermelon' =>['id' => 1],
'cherry' =>['id' => 2],
'litchi' =>['id' => 3]
],
'white' => [
'coco' =>['id' => 13],
'lucuma' =>['id' => 14],
]
],
'sour' => [
'lemon' =>['id' => 4],
'orange' =>['id' => 5],
'tangerine' =>['id' => 6]
],
],
'colors' => [
'black' =>['id' => 7],
'green' =>['id' => 8],
'blue' => ['id' => 9]
],
'names' => [
'jack' =>['id' => 10],
'paul' =>['id' => 11],
'peter' =>['id' => 12]
]
];
I have a second array, with the elements that I want to search:
$results = ['coco', 'green','peter'];
I need to write a function that gives an output array with the elements and the tree of categories where they belong. Something like this:
$resultsInStructure = [
'fruits' => [
'sweet' => [
'white' => [
'coco' => ['id' => 13]
]
]
],
'colors' => [
'green' =>['id' => 8]
],
'names' => [
'peter' =>['id' => 12]
]
];
The function needs to be recursive, of course. Either walking the first of the second array, it's just that I can't manage to get it right... any help is appreciated !
try this
function walk($arr,$results)
{
if (is_array($arr))
{
$arrFound = [];
$arrTmpReturn = [];
foreach($arr AS $key => $val)
{
if (in_array($key, $results))
{
$arrFound = array_merge($arrFound,[$key => $val]);
}
$arrReturn = walk($val,$results);
if (!is_null($arrReturn))
{
$arrTmpReturn = array_merge($arrTmpReturn,[$key => $arrReturn]);
}
}
if (count($arrFound) > 0) return $arrFound;
if (count($arrTmpReturn) > 0) return $arrTmpReturn;
}
}
print_r(walk($structure,$results));
You can use RecursiveIteratorIterator to traverse the structure and references to put the value into the right place of the resulting array:
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($structure),
RecursiveIteratorIterator::SELF_FIRST
);
$filters = array_flip($results);
$keys = [];
$resultsInStructure = [];
foreach ($iterator as $key => $value) {
if ($key === 'id') {
continue;
}
// Put key to the stack, so we can get the path of results.
$keys[$iterator->getDepth()] = $key;
if (!isset($filters[$key])) {
continue;
}
// Put the result in the right place in the tree structure.
$tmp =& $resultsInStructure;
foreach (array_slice($keys, 0, $iterator->getDepth()) as $_key) {
if (!isset($tmp[$_key])) {
$tmp[$_key] = [];
}
$tmp =& $tmp[$_key];
}
$tmp[$key] = $value;
}
Here is working demo.

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)
)

PHP array_reduce with initial parameter as an array

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;
}, [])

array_multisort issue with multidimensonal array in PHP

I have the following array:
array(10) {
[0]=> array(109) {
["id"]=> string(4) "2632", ["_category_sortorder"] => 8, ["_sortorder" => 1]
},
[1]=> array(109) {
["id"]=> string(4) "2635", ["_category_sortorder"] => 5, ["_sortorder" => 2]
},
...
}
I want to sort it based on two criterias:
a) by _category_sortorder asc (with priority)
b) by _sortorder asc
I tried this:
foreach($resources as $k => $v) {
$sort[$k]['_category_sortorder'] = $resources[$k]['_category_sortorder'];
$sort[$k]['_sortorder'] = $resources[$k]['_sortorder'];
}
array_multisort($sort['_category_sortorder'], SORT_ASC, $sort['_sortorder'], SORT_ASC, $resources);
But it's not working as expected. Any suggestions?
Try like this,
$sort = array(
array("id"=>"263", "_category_sortorder"=> 8, "_sortorder" => 1),
array( "id"=> "145", "_category_sortorder" => 155, "_sortorder" => 2),
array( "id"=> "2145", "_category_sortorder" => 55, "_sortorder" => 12),
array( "id"=> "3145", "_category_sortorder" => 155, "_sortorder" => 10),
);
usort($sort, function(array $a, array $b) {
return $b['_category_sortorder'] - $a['_category_sortorder'];
});
echo '<pre>';
print_r($sort);
echo '</pre>';
I think there is a simple solution but this code block is doing is job:
You split the array in sub arrays including only elements with the same _category_sortorder value. Then you sort each sub array with usort. At the end you merge them together.
<?php
$array = [
['id' => 1, '_category_sortorder' => 2, '_sortorder' => 1],
['id' => 2, '_category_sortorder' => 2, '_sortorder' => 3],
['id' => 3, '_category_sortorder' => 3, '_sortorder' => 19],
['id' => 4, '_category_sortorder' => 1, '_sortorder' => 2],
['id' => 5, '_category_sortorder' => 1, '_sortorder' => 1],
];
foreach ($array as $value) {
if (!isset($newElements[$value['_category_sortorder']]))
$newElements[$value['_category_sortorder']] = [];
$newElements[$value['_category_sortorder']][] = $value;
}
$array = [];
foreach ($newElements as $key => $value) {
usort($value, function($a, $b) {
return strcmp($a["_sortorder"], $b["_sortorder"]);
});
$array[$key] = $value;
}
ksort($array);
$new = [];
foreach ($array as $value) {
$new = array_merge($new, $value);
}
echo "<pre>".print_r($new, true).'</pre>';

Categories