Recursively insert element next to other element in array - php

I have a multidimensional array that may or may not contain the key name one or more times. What I'd like to do is for every instance of said element insert another element, next to that element, with the key key.
So, given this array:
[
[
'noname' => 'No name',
'label' => 'I have no name'
],
[
'name' => 'foo',
'label' => 'Foo',
'fields' => [
[
'name' => 'bar',
'label' => 'Bar'
]
]
],
[
'name' => 'baz',
'label' => 'Baz'
]
]
I'd like the following output:
[
[
'noname' => 'No name',
'label' => 'I have no name'
],
[
'name' => 'foo',
'key' => 'foo-key', # This is inserted by the function
'label' => 'Foo',
'fields' => [
[
'name' => 'bar',
'key' => 'bar-key', # This is inserted by the function
'label' => 'Bar'
]
]
],
[
'name' => 'baz',
'key' => 'baz-key', # This is inserted by the function
'label' => 'Baz'
]
]
I've looked into array_walk_recursive but can't get it to work. Do I need to write my own recursive function or is there something appropriate built in that I can use for this?

Your code would be something like this:
<?php
$array = [
[
'noname' => 'No name',
'label' => 'I have no name'
],
[
'name' => 'foo',
'label' => 'Foo',
'fields' => [
[
'name' => 'bar',
'label' => 'Bar'
]
]
],
[
'name' => 'baz',
'label' => 'Baz'
]
];
function fix_array($array){
foreach ($array as $key => $value){
if (is_array($value)){
$array[$key] = fix_array($value);
}
elseif ($key == 'name'){
$array['key'] = $value . '-key';
}
}
return $array;
}
$new_array = fix_array($array);
print_r($new_array);

Related

Remove a array from an multidimensional array using object value in laravel

i need to remove duplicate array from below array.
first and third arrays are same, consider only "id"
$data = [
[
'id' => 'test_fun%test',
'text' => 'test_fun',
'data-value' => 'test',
],
[
'id' => 'test_fun1%test',
'text' => 'test_fun1',
'data-value' => 'test',
],
[
'id' => 'test_fun%test',
'text' => 'test_fun',
'data-value' => 'test',
'selected' => true
]
];
i'm tried to below code.
-> array_unique($data);
-> array_map("unserialize", array_unique(array_map("serialize", $data)));
Expected Output
$data = [
[
'id' => 'test_fun1%test',
'text' => 'test_fun1',
'data-value' => 'test',
],
[
'id' => 'test_fun%test',
'text' => 'test_fun',
'data-value' => 'test',
'selected' => true
]
];
array_unique is not going to work since you have "selected" in the third array. I agree with the comments that this is quite unclear but to me it seems you're looking for a custom filtration rule, so a plain old foreach is the tool for the job.
<?php
$data = [
[
'id' => 'test_fun%test',
'text' => 'test_fun',
'data-value' => 'test',
],
[
'id' => 'test_fun1%test',
'text' => 'test_fun1',
'data-value' => 'test',
],
[
'id' => 'test_fun%test',
'text' => 'test_fun',
'data-value' => 'test',
'selected' => true
]
];
$filtered = [];
foreach ($data as $row) {
$id = $row['id'];
$selected = $row['selected'] ?? false;
if (isset($filtered[$id])) {
if (!$selected) {
continue;
}
unset($filtered[$id]);
}
$filtered[$id] = $row;
}
// optional use if you don't want ids for keys
$filtered = array_values($filtered);
print_r($filtered);

Removing part of an array from a multidimensional nested array

I've a four levels of nested array like this:
$array = [
[
'website' => [
'id' => 'one'
],
'children' => [
[
'website' => [
'id' => 'one.one'
],
'children' => [
[
'website' => [
'id' => 'one.one.one'
],
'children' => [
[
'website' => [
'id' => 'one.one.one.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.one.one.two'
],
'children' => []
]
]
],
[
'website' => [
'id' => 'one.one.two'
],
'children' => [
[
'website' => [
'id' => 'one.one.two.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.one.two.two'
],
'children' => []
]
]
]
]
],
[
'website' => [
'id' => 'one.two'
],
'children' => [
[
'website' => [
'id' => 'one.two.one'
],
'children' => [
[
'website' => [
'id' => 'one.two.one.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.two.one.two'
],
'children' => []
]
]
],
[
'website' => [
'id' => 'one.two.two'
],
'children' => [
[
'website' => [
'id' => 'one.two.two.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.two.two.two'
],
'children' => []
]
]
]
]
]
]
]
];
now, I'd like to remove some of the array element based on some rules. For simplicity, let's say, we'd like to remove array elements that has 'id' equals to one.one.two or one.two.one.
I was looking into some answers provided on stackoverflow and was trying to apply them to solve my issue, but didn't go much. The most chalanging thing is to unset part of an array where it's not the farthest point in the array.
I was trying this to note at which level I want to delete the arrays and then trying to unset them using those array index.
$pointer = [];
foreach($array as $key => $level1) {
$level1pointer = $key;
$pointer[] = markToDelete($level1, $level1pointer);
foreach($level1['children'] as $key => $level2) {
$level2pointer = $key;
$pointer[] = markToDelete($level2, $level1pointer, $level2pointer);
foreach($level2['children'] as $key => $level3) {
$level3pointer = $key;
$pointer[] = markToDelete($level3, $level1pointer, $level2pointer, $level3pointer);
foreach($level3['children'] as $key => $level4) {
$level4pointer = $key;
$pointer[] = markToDelete($level4, $level1pointer, $level2pointer, $level3pointer, $level4pointer);
}
}
}
}
function markToDelete($array, $level1 = null, $level2 = null, $level3 = null, $level4 = null) {
$exclusionList = [
'one.one.two',
'one.two.one'
];
if (!empty($array['website']) && in_array($array['website']['id'], $exclusionList)) {
print_r('marking for deletion: '. $array['website']['id'] . PHP_EOL);
return [
'id' => $array['website']['id'],
'level1' => $level1,
'level2' => $level2,
'level3' => $level3,
'level4' => $level4
];
}
return [];
}
I was also trying to use Iterator like this:
$it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array), \RecursiveIteratorIterator::LEAVES_ONLY);
$newArray = [];
foreach($it as $key => $value) {
$exclusionList = [
'one.one.two',
'one.two.one'
];
if(!in_array($value, $exclusionList)) {
print_r(sprintf('value: %s is ready to be deleted', $value).PHP_EOL);
$newArray[] = $value;
}
}
but I need a way to unset the array when looping through the iterator.
I'd like to get an output like this:
$array = [
[
'website' => [
'id' => 'one'
],
'children' => [
[
'website' => [
'id' => 'one.one'
],
'children' => [
[
'website' => [
'id' => 'one.one.one'
],
'children' => [
[
'website' => [
'id' => 'one.one.one.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.one.one.two'
],
'children' => []
]
]
],
]
],
[
'website' => [
'id' => 'one.two'
],
'children' => [
[
'website' => [
'id' => 'one.two.two'
],
'children' => [
[
'website' => [
'id' => 'one.two.two.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.two.two.two'
],
'children' => []
]
]
]
]
]
]
]
];
I'd really appreciate help on how to resolve this in more efficient way. Thanks.
Here's a recursive function that will do what you want. It traverses the array, looking for children whose website id is in the list of ids to be removed and unsetting them. Note that because of your additional top-level array, you need to iterate the function over those values.
function delete_entries(&$array, $ids_to_delete) {
foreach ($array['children'] as $index => &$child) {
if (in_array($child['website']['id'], $ids_to_delete)) {
unset($array['children'][$index]);
}
delete_entries($child, $ids_to_delete);
}
}
foreach ($array as &$arr) {
delete_entries($arr, array('one.one.two', 'one.two.one'));
}
var_export($array);
The output is as you desire but too long to reproduce here. See the demo on 3v4l.org
Update
The above code won't delete entries on the top level because the array structure is different from lower levels in the array. That can be dealt with in the outer foreach loop:
$excluded = array('two', 'one.one.two', 'one.two.one');
foreach ($array as $key => &$arr) {
if (in_array($arr['website']['id'], $excluded)) {
unset($array[$key]);
}
else {
delete_entries($arr, $excluded);
}
}
Updated demo

Recursive function returns NULL but I can echo or var_dump() [duplicate]

This question already has answers here:
How to use return inside a recursive function in PHP
(4 answers)
Closed 9 months ago.
I have a nested menu (a sidebar) like this:
$menu = [
'dashboard' => [
'type' => 'item',
'name' => 'bacheca',
'url' => 'home.php',
],
'administration' => [
'type' => 'header',
'name' => 'amministrazione',
'items' => [
'configuration' => [
'type' => 'item',
'name' => 'configurazione',
'url' => 'configuration.php',
],
'admins' => [
'type' => 'item',
'name' => 'amministratori',
'url' => 'admins.php',
],
'activities' => [
'type' => 'item',
'name' => 'attività',
'url' => 'activities.php',
],
],
],
'intranet' => [
'type' => 'header',
'name' => 'intranet',
'items' => [
'chiefs' => [
'type' => 'item',
'name' => 'capiarea',
'url' => 'chiefs.php',
],
'agents' => [
'type' => 'item',
'name' => 'informatori',
'url' => 'agents.php',
],
'samples' => [
'type' => 'tree',
'name' => 'campioni',
'url' => '#',
'submenu' => array_merge(
[
'dummy' => [
'type' => 'item',
'name' => 'dummy',
'url' => 'dummy.php',
],
],
[
'temperature' => [
'type' => 'item',
'name' => 'temperature',
'url' => 'temperature.php',
],
]),
],
'files' => [
'type' => 'item',
'name' => 'riferimenti normativi',
'url' => 'files.php',
],
'documents' => [
'type' => 'item',
'name' => 'documenti',
'url' => 'documents.php',
],
],
],
];
With a recursive function I try to find the matching 'item' type in the menu and return it, for future elaboration (types 'header' and 'tree' are not targeted). Here is the recursive function I wrote:
function item($needle, $haystack, $result = []){
foreach ($haystack as $key => $value){
switch ($value['type']) {
case 'header':
if (isset($value['items'])){
item($needle, $value['items'], $result);
}
break;
case 'tree':
if (isset($value['submenu'])){
item($needle, $value['submenu'], $result);
}
break;
default:
if ($needle == $key){
$result['name'] = $value['name'];
$result['url'] = $value['url'];
return $result;
}
}
}
}
I don't know why, this function returns nothing: if I type var_dump(item('chiefs')); I expect it to return the item with chief key, but I get nothing
The problem lies in the return statement: in the if ($needle == $key){ .. } condition I can echo or var_dump() the matching array, but when I use return $result, it prints NULL
Here is a "live" script to play with: where am I wrong?
https://www.tehplayground.com/Ix0ODne19Acsjvyx
The problem is that
case 'header':
if (isset($value['items'])){
item($needle, $value['items'], $result);
}
break;
case 'tree':
if (isset($value['submenu'])){
item($needle, $value['submenu'], $result);
}
break;
If it goes into either of these and find what you are looking for, you don't return anything.
You need to save the return value of the recursion, if it's not null, then return it, otherwise, continue.
case 'header':
if (isset($value['items'])){
$v = item($needle, $value['items'], $result);
if ($v) return $v;
}
break;
case 'tree':
if (isset($value['submenu'])){
$v = item($needle, $value['submenu'], $result);
if ($v) return $v;
}
break;

denormalization(?) of an array of items

SOLVED
Im trying to denormalization (if this is the right word) an array of items.
Each item has a qty. And if an item has f.e. qty of 2 then i want to add the same item (a copy) qty-times (minus one because 1st is already there).
Example:
$items = [
[
'name' => 'Foo',
'qty' => 2,
'items' => [
[
'name' => 'Bar',
'qty' => 2,
'items' => [
[
'name' => 'Baz',
'qty' => 2,
'items' => [],
],
],
],
],
],
];
$expected = [
[
'name' => 'Foo',
'qty' => 1,
'items' => [
[
'name' => 'Bar',
'qty' => 1,
'items' => [
[
'name' => 'Baz',
'qty' => 1,
'items' => [],
],
[
'name' => 'Baz',
'qty' => 1,
'items' => [],
],
],
],
[
'name' => 'Bar',
'qty' => 1,
'items' => [
[
'name' => 'Baz',
'qty' => 1,
'items' => [],
],
[
'name' => 'Baz',
'qty' => 1,
'items' => [],
],
],
],
],
],
[
'name' => 'Foo',
'qty' => 1,
'items' => [
[
'name' => 'Bar',
'qty' => 1,
'items' => [
[
'name' => 'Baz',
'qty' => 1,
'items' => [],
],
[
'name' => 'Baz',
'qty' => 1,
'items' => [],
],
],
],
[
'name' => 'Bar',
'qty' => 1,
'items' => [
[
'name' => 'Baz',
'qty' => 1,
'items' => [],
],
[
'name' => 'Baz',
'qty' => 1,
'items' => [],
],
],
],
],
],
];
Script (recursive test):
function func(array $items, array &$r)
{
foreach ($items as $item) {
for ($i = 1; $i <= $item['qty']; $i++) {
echo "{$item['name']}\r\n";
func($item['items'], $r);
}
}
}
$r = [];
func($items, $r);
file_put_contents('tmp/_test.php', "<?php return\r\n" . var_export($r, true) . ";");
Out:
Foo
Bar
Baz
Baz
Bar
Baz
Baz
Foo
Bar
Baz
Baz
Bar
Baz
Baz
So far so good.
But i cant get the items correctly sorted into $r.
Thanks for any help.
Sorry. Solution came to my mind as i re.read my own post.
I was stuck in references. But i just should have returned the items array.
Working script:
function func(array $items)
{
$_items = [];
$n = 0;
foreach ($items as $item) {
for ($i = 1; $i <= $item['qty']; $i++) {
echo "{$item['name']}\r\n";
$_items[$n] = $item;
$_items[$n]['qty'] = 1;
$_items[$n]['items'] = func($item['items']);
$n++;
}
}
return $_items;
}
$r = func($items);
file_put_contents('tmp/_test.php', "<?php return\r\n" . var_export($r, true) . ";");
You can compact your solution and eliminate the $n counter.
I am starting $i from 0 so that only < is needed to maintain the logic.
I have used ++$i instead of $i++ as a micro-optimization.
I have renamed the function and variables because I found it a little confusing with the variable names being so similar and the function not being descriptive.
I tested this with 2,2,2, 2,0,2,, 1,2,3, and 3,2,1 and my changes have not damaged the result. I think this is as tight as I can make it.
Code: (Demo)
function expand(array $array){
$expanded=[];
foreach($array as $sub){
for($i=0; $i<$sub['qty']; ++$i){
$expanded[]=['name'=>$sub['name'],'qty'=>1,'items'=>expand($sub['items'])];
}
}
return $expanded;
}
$items = [
[
'name' => 'Foo',
'qty' => 2,
'items' => [
[
'name' => 'Bar',
'qty' => 2,
'items' => [
[
'name' => 'Baz',
'qty' => 2,
'items' => [],
],
],
],
],
],
];
var_export(expand($items));

Invalid argument supplied for foreach() when i try to print value

This is my array
[
'field_test1' => [
'field_test2' => [ 'value' => 'Yes' ,'action' => 'visible']
],
'field_test3' => [
'field_test4' => [ 'value' => '2' ,'action' => 'visible']
]
'body' => [
'field_test2' => [ 'value' => 'No', 'action' => 'visible']
'field_test4' => [ 'value' => '1', 'action' => 'visible']
]
]
When i try to loop through each element i am getting error like invalid argument passed for foreach;
My code is
foreach ($myArray as $key => $value) {
echo $key;
}
what should i do??
You forgot the comma after your second array within your array. Before the one with the 'body' key. Try using a decent IDE like PhpStorm, it will highlight the errors in your syntax for you, making the search for common errors easy.
$myArray =
[
'field_test1' => [
'field_test2' => [ 'value' => 'Yes' ,'action' => 'visible']
],
'field_test3' => [
'field_test4' => [ 'value' => '2' ,'action' => 'visible']
],
'body' => [
'field_test2' => [ 'value' => 'No', 'action' => 'visible']
'field_test4' => [ 'value' => '1', 'action' => 'visible']
],
];

Categories