I have an array which looks something like this:
$a = [
1 => [
'title' => 'test',
'items' => [
5 => [
'title' => 'hello',
'items' => []
]
]
],
2 => [
'title' => 'second',
'items' => [
7 => [
'title' => 'hello in second',
'items' => []
]
]
],
3 => [
'title' => 'third',
'items' => [
10 => [
'title' => 'hello in third',
'items' => []
]
]
],
];
I need a way to extract parts of it by key no matter where they are in the tree. I've tried several ways but I'm not sure how efficient they are. If it helps I need to extract only those parts which have a numeric key. Any help will be much appreciated.
Try using the SPL iterators:
class KeyFinderFilterIterator extends FilterIterator {
private $search = null;
public function __construct($iterator, $search) {
$this->search = $search;
parent::__construct($iterator);
}
public function accept(){
return $this->key() == $this->search;
}
}
$it = new KeyFinderFilterIterator(
new RecursiveIteratorIterator(
new RecursiveArrayIterator($a),
RecursiveIteratorIterator::SELF_FIRST
),
10
);
foreach ($it as $key => $value) {
var_dump($value);
}
Related
I have an array
[
[
'title' => 'title0',
'data' => 'data0'
],
[
'title' => 'title1',
'data' => 'data1'
]
]
I need to get the output
[
'title' => ['title0','title1'],
'data' => ['data0', 'data1']
]
Please tell me how can I do this?
You didn't show any attempt, but I'm bored. Just loop the keys from the first sub-array and extract that column. No need to know what the keys are:
foreach(array_keys(reset($array)) as $key) {
$result[$key] = array_column($array, $key);
}
You could also do it this way:
foreach(reset($array) as $key => $val) {
$result[$key] = array_column($array, $key);
}
Or if it's as simple as those two known keys:
$result = ['title' => array_column($array, 'title'),
'data' => array_column($array, 'data')
];
You can do it like this
<?php
$shortedArray = [
'title' => [],
'data' => []
];
$mainArray = [
[
'title' => 'test0',
'data' => 'data0'
],
[
'title' => 'test1',
'data' => 'data1'
]
];
// Loop thru it
foreach($mainArray as $row){
$shortedArray['title'][] = $row['title'];
$shortedArray['data'][] = $row['data'];
}
print_r($shortedArray);
Hope this resolves your issue. Any query, let me know
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
I've seen many questions around this topic, but not any near the case I have.
I have like a really simple folder path as key and want to make the array into a multidimensional array.
My current array
[
'projects' => 'A path',
'projects/project-a' => 'Another path',
'projects/project-b' => 'Yet another path',
'about/me/and/someone/else' => 'Path about me'
]
This is the result I try to get:
[
'projects' => [
'path' => 'A path',
'children' => [
'project-a' => [
'path' => 'Another path'
],
'project-b' => [
'path' => 'Yet another path'
]
]
],
'about' => [
'children' => [
'me' => [
'children' => [
'and' => [
'children' => [
'someone' => [
'children' => [
'else' => [
'path' => 'Path about me'
]
]
]
]
]
]
]
]
]
]
Maybe I can use array_walk_recursive somehow. I know explode can be used to split the parts by /.
Notes
projects/project-a does not have children.
about and all children except the last one does not have a path.
The depth of the array is unknown.
$result = [];
foreach($arr as $k=>$v) {
$path = explode('/', $k);
// temporary array for one path
$temp = [];
// Pointer, used to add a next level
$p = &$temp;
// Save the last part of path
$last = array_pop($path);
foreach($path as $s) {
// Make level upto the last
$p[$s] = ['children' => []];
$p = &$p[$s]['children'];
}
// Add a value
$p[$last] = ['path' => $v];
$result = array_merge_recursive($result, $temp);
}
print_r($result);
demo
I'm struggeling second day with transforming dot to tree structure.
Can anyone help?
$input = [
'inbox' => ['name' => 'Inbox'],
'inbox.first' => ['name' => 'First'],
'inbox.second' => ['name' => 'Second'],
'inbox.second.other' => ['name' => 'Second Other'],
'inbox.third.another' => ['name' => 'Third Another'],
];
$expectedOutput = [
'inbox' => [
'name' => 'Inbox',
'mailbox' => 'inbox',
'subfolders' => [
'first' => [
'name' => 'First',
'mailbox' => 'inbox.first',
'subfolders' => [],
],
'second' => [
'name' => 'Second',
'mailbox' => 'inbox.second',
'subfolders' => [
'other' => [
'name' => 'Second Other',
'subfolders' => [],
'mailbox' => 'inbox.second.other',
],
],
],
'third' => [
'subfolders' => [
'another' => [
'name' => 'Third Another',
'subfolders' => [],
'mailbox' => 'inbox.third.another',
],
],
],
],
],
];
You can check the laravel function Arr::set as a basis. If you bring your array to a format that matches the output of that function you can use it like so:
//This is the set function from https://github.com/laravel/framework/blob/5.5/src/Illuminate/Support/Arr.php#L510
function set(&$array, $key, $value)
{
if (is_null($key)) {
return $array = $value;
}
$keys = explode('.', $key);
while (count($keys) > 1) {
$key = array_shift($keys);
// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if (! isset($array[$key]) || ! is_array($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
$input = [
'inbox' => ['name' => 'Inbox'],
'inbox.first' => ['name' => 'First'],
'inbox.second' => ['name' => 'Second'],
'inbox.second.other' => ['name' => 'Second Other'],
'inbox.third.another' => ['name' => 'Third Another'],
];
$newKeys = array_map(function ($key) {
$k = explode(".",$key);
$newkey = [];
foreach ($k as $segment) {
$newkey[] = $segment;
$newkey[] = "subfolders";
}
return implode(".",$newkey);
}, array_keys($input));
$input = array_combine($newKeys, array_map(function ($value,$key) {
return array_merge($value, ["mailbox"=>$key]);
},$input,array_keys($input)));
$res = [];
array_walk($input, function ($value,$key) use (&$res) {
set($res,$key,$value);
});
Demo: http://sandbox.onlinephpfunctions.com/code/9717e7606f099f1352a559c447b5225dd0d74f6c
I am trying to make a multi-dimensional array build an array path adding the hr field so it looks like this:
I just can't figure out how to add the totals, nor create a sub-array so the dot notation in an option too. My goal is to get something like this:
[1] => [1][2][1][5][0][6] = 35 (the second child path "1")
[1] => [1][2][1][5][0][7] = 25
or Something like this:
array (
[children.0.children.0.children.0.total] = 20
[children.0.children.1.children.1.total] = 35
// etc
)
The complicated part is that it goes in different directions and I want to know what is the highest and lowest total based on the path:
==> Run Code Here or Copy/Paste
// -------------
// The Flattener
// -------------
function doit($myArray) {
$iter = new RecursiveIteratorIterator(new RecursiveArrayIterator($myArray));
$result = array();
foreach ($iter as $leafKey => $leafValue) {
$keys = array();
foreach (range(0, $iter->getDepth()) as $depth) {
$keys[] = $iter->getSubIterator($depth)->key();
}
$result[ join('.', $keys) ] = $leafValue;
}
return $result;
}
// -------------
// Example Tree
// -------------
$tree = [
'id' => 1,
'type' => 'note',
'data' => [],
'children' => [
[
'id' => 2,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => [
[
'id' => 3,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => [
'id' => 4,
'type' => 'exit',
'data' => [],
'children' => []
]
],
[
'id' => 5,
'type' => 'note',
'data' => [
'hr' => 10,
],
'children' => [
[
'id' => 6,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => []
],
[
'id' => 7,
'type' => 'exit',
'data' => [],
'children' => []
],
]
]
],
]
]
];
$result = doit($tree);
print_r($result);
This seems to work, I found it somewhere googling all day.
array_reduce(array_reverse($keys), function($parent_array, $key) {
return $parent_array ? [$key => $parent_array] : [$key];
}, null);