Recursively sort keys of a multidimensional array - php

I am having a hard time trying recursively sort a multidimensional array on its keys. I tried with usort(), but with no success.
Sample data:
[
'first_level' => [
'dir_3' => [
'subdir_1' => [
'file_2.mp4' => (object) [
'name' => 'file_2.mp4',
],
'file_1.mp4' => (object) [
'name' => 'file_1.mp4',
],
],
],
'dir_1' => [
'subdir_2' => [
'file_6.mp4' => (object) [
'name' => 'file_6.mp4',
],
'file_9.mp4' => (object) [
'name' => 'file_9.mp4',
],
'file_7.mp4' => (object) [
'name' => 'file_7.mp4',
],
],
'subdir_1' => [
'file_8.mp4' => (object) [
'name' => 'file_8.mp4',
],
],
],
],
]
Desired result:
[
'first_level' => [
'dir_1' => [
'subdir_1' => [
'file_8.mp4' => (object) [
'name' => 'file_8.mp4',
],
],
'subdir_2' => [
'file_6.mp4' => (object) [
'name' => 'file_6.mp4',
],
'file_7.mp4' => (object) [
'name' => 'file_7.mp4',
],
'file_9.mp4' => (object) [
'name' => 'file_9.mp4',
],
],
],
'dir_3' => [
'subdir_1' => [
'file_1.mp4' => (object) [
'name' => 'file_1.mp4',
],
'file_2.mp4' => (object) [
'name' => 'file_2.mp4',
],
],
],
],
]

Use a recursive function to call ksort on the current level and all deeper subarrays.
function recur_ksort(&$array) {
foreach ($array as &$value) {
if (is_array($value))
recur_ksort($value);
}
ksort($array);
}
recur_ksort($array);
var_export($array);
Demo: https://3v4l.org/Xede5

You need to use ksort with recursion. Demo
function recursive_ksort(&$array) {
foreach ($array as &$v) {
if (is_array($v)) {
recursive_ksort($v);
}
}
ksort($array);
}
recursive_ksort($array);
var_export($array);

function ksort_recursive(&$array)
{
if (is_array($array)) {
ksort($array);
array_walk($array, 'ksort_recursive');
}
}
It is not necessary within the recursive function to return ksort() -- this would return the unwanted success boolean value from ksort() anyhow.
Note that this function does not throw "Warning: ksort() expects parameter 1 to be array" when given a non-array - this matches my requirements but perhaps not yours. Demo: https://3v4l.org/bogAU

It is fair to assume that you'd like your data ti be sorted "naturally" -- meaning that the number portion of the directory and file names should be sorted numerically instead of as simple strings. Without sorting naturally, dir_10 will be moved in front of dir_2 because when comparing the 5th character of the two strings, 1 is less than 2.
Code: (Demo)
function nat_ksort_r(&$data): void
{
if (is_array($data)) {
ksort($data, SORT_NATURAL);
array_walk($data, __METHOD__);
}
}
nat_ksort_r($array);
var_export($array);
For natural sorting, apply the SORT_NATURAL flag to the ksort() call.
For simpler maintenance of the recursive function, recall the function using the __METHOD__ magic constant. This makes one less place to change nat_ksort_r() if you wish to call the custom function something else.
This recursive function does not return any data; it modifies the original array by reference.
The lowest level contains objects and this data does not get sorted by the function.
The above function can also use a classic loop instead of a functional iterator. (Demo)
function nat_ksort_r(&$data): void
{
if (is_array($data)) {
ksort($data, SORT_NATURAL);
foreach ($data as &$item) {
(__METHOD__)($item);
}
}
}
nat_ksort_r($array);
var_export($array);
You can even write the code in a completely anonymous fashion. Demo
$nat_ksort_r = function(&$data) use (&$nat_ksort_r) {
if (is_array($data)) {
ksort($data, SORT_NATURAL);
foreach ($data as &$item) {
$nat_ksort_r($item);
}
}
};
$nat_ksort_r($array);
var_export($array);

Related

How can I get a value from an array by a child array value?

I've a huge problem. Somehow I need to get the variation_id from an array based on a value of a child array:
$array = [
[
'attributes' => [
'attribute_art-der-karte' => 'Rot'
],
'variation_id' => '222'
],
[
'attributes' => [
'attribute_art-der-karte' => 'Green'
],
'variation_id' => '221'
]
];
So in my case I've two things available:
The key attribute_art-der-karte
The value Rot
I found nothing here instead of a sorting. Any idea? Thanks!
Simply loop over the array and return the variation ID when your condition is met (an attribute exists with the given key and the given value):
function findVariationId(array $array, string $attrName, string $attrValue): ?string {
foreach ($array as $entry) {
if (($entry['attributes'][$attrName] ?? null) === $attrValue) {
return $entry['variation_id'];
}
}
return null;
}
Demo
You can also use array filter for this
<?php
$array = [
[
'attributes' => [
'attribute_art-der-karte' => 'Rot'
],
'variation_id' => '222'
],
[
'attributes' => [
'attribute_art-der-karte' => 'Green'
],
'variation_id' => '221'
]
];
function findByAttribute ($arr, $value) {
$result = array_filter($arr, function($elem) use($value){
return $elem['attributes']['attribute_art-der-karte'] == $value;
});
if ($result) {
return array_shift($result)['variation_id'];
}
return '';
}
var_dump(findByAttribute($array, 'Rot')); // gives '222'
var_dump(findByAttribute($array, 'a')); // returns ''
var_dump(findByAttribute($array, 'Green')); // gives 221
An additional option using array_search() and array_column():
<?php
$array = [
['attributes' => ['attribute_art-der-karte' => 'Rot'], 'variation_id' => '222'],
['attributes' => ['attribute_art-der-karte' => 'Green'], 'variation_id' => '221']
];
$key = array_search (
'Rot',
array_column(array_column($array, 'attributes'), 'attribute_art-der-karte')
);
echo "variation_id: ". (($key === false) ? 'Not found' : $array[$key]["variation_id"]);
?>
Output:
variation_id: 222
You can access the element variation_id using foreach
// One way using loops identify the non-array element
foreach($array as $ele){
if(!is_array($ele)){
print_r($ele);
}
}
// Otherway for direct calling
print_r($array['variation_id']);

Search into non symetrical multidimensional array with PHP

I have found a lot of question and some great answers in order to search into an array with PHP. But everytime, the script answered too perfectly to the quesiton, and not globaly OR all arrays are symetricals.
My Array can look like this:
$data = [
'steve' => [
'id' => [
'#text' => 1,
],
'pseudo' => [
'#text' => 'LOL'
],
],
'albert' => [
'id' => [
'#text' => 2,
],
'pseudo' => [
'#text' => 'KILLER'
],
],
'john' => [
'id' => [
'#text' => 3,
],
'pseudo' => [
'#text' => 'NOOBS'
],
],
];
Which mean that my Array can look beautifully symetrically generated, or completely messy and with random subarray.
My aim is to search inside and found the PSEUDO of AN ID. And unfortunately, I can't change the webservice which give me this result.
I had tried with array_column or array_search, but the only thing I had successfully returned is if it founds something, or not. But can't identify it. And my script was very slow.
Something like :
search($array, 2); //which return KILLER
or more optionable maybe?
My Array can have a lot of IDs (100+). So I'm trying to found something optimize. :/
There's probably a million ways to do this, but ultimately you'll need some recursive approach that's able to filter on the value and the path to that value. For this, the following would be one of those million ways.
It uses the predefined iterators:
CallbackFilterIterator
RecursiveIteratorIterator
RecursiveArrayIterator
in combination with a custom PathAsKeyDecorator iterator.
Demo here
<?php
declare(strict_types=1);
final class PathAsKeyDecorator implements \Iterator
{
private RecursiveIteratorIterator $inner;
public function __construct(RecursiveIteratorIterator $inner)
{
$this->inner = $inner;
}
public function current()
{
return $this->inner->current();
}
public function next(): void
{
$this->inner->next();
}
public function key()
{
$path = [];
for ($i = 0, $depth = $this->inner->getDepth(); $i <= $depth; $i++) {
$path[] = $this->inner->getSubIterator($i)->key();
}
return $path;
}
public function valid(): bool
{
return $this->inner->valid();
}
public function rewind(): void
{
$this->inner->rewind();
}
}
$input = [
'steve' => [
'id' => [
'#text' => 1,
],
],
'albert' => [
'id' => [
'#text' => 2,
],
],
'john' => [
'profil' => [
'id' => [
'#text' => 3,
],
],
],
];
// this is the filter function that should be customized given your requirements
// or create a factory function which produces these types of filter functions
$filter = static function ($current, array $path): bool {
// with help from the PathAsKeyDecorator
// we can decide on the path to the current value
return ['id', '#text'] === array_slice($path, -2)
// and the current value
&& 2 === $current;
};
// configure the iterator
$it = new CallbackFilterIterator(
new PathAsKeyDecorator(new RecursiveIteratorIterator(new RecursiveArrayIterator($input))),
$filter,
);
// traverse the iterator
foreach ($it as $path => $val) {
print_r([
'path' => $path,
'val' => $val
]);
}
An alternative approach could be one with simple functions, like:
Demo here
<?php
declare(strict_types=1);
function iterateWithPath(iterable $input, array $path = []): iterable {
foreach ($input as $key => $value) {
$pathToHere = [...$path, $key];
is_iterable($value)
? yield from iterateWithPath($value, $pathToHere)
: yield $pathToHere => $value;
}
}
function filterIterable(iterable $it, callable $filter): iterable {
foreach ($it as $key => $value) {
if ($filter($value, $key)) {
yield $key => $value;
}
}
}
$input = [
'steve' => [
'id' => [
'#text' => 1,
],
],
'albert' => [
'id' => [
'#text' => 2,
],
],
'john' => [
'profil' => [
'id' => [
'#text' => 3,
],
],
],
];
$it = filterIterable(iterateWithPath($input), static function ($current, array $path): bool {
return ['id', '#text'] === array_slice($path, -2)
&& 2 === $current;
});
foreach ($it as $path => $val) {
print_r([
'path' => $path,
'val' => $val
]);
}
complete edit cause of structure change of the array. the json_encode is used for changing the array to a string. Only does its job when there is in every array slice an id and an pseudo.
$data = [
'steve' => [
'id' => [
'#text' => 1,
],
'pseudo' => [
'#text' => 'LOL'
],
],
'albert' => [
'id' => [
'#text' => 2,
],
'pseudo' => [
'#text' => 'KILLER'
],
],
'john' => [
'id' => [
'#text' => 3,
],
'pseudo' => [
'#text' => 'NOOBS'
],
],
];
$data = json_encode($data, JSON_NUMERIC_CHECK);
preg_match_all('~"id":{"#text":([^{]*)}~i', $data, $ids);
preg_match_all('~"pseudo":{"#text":"([^{]*)"}~i', $data, $pseudos);
$lnCounter = 0;
$laResult = array();
foreach($ids[1] as $lnId) {
$laResult[$lnId] = $pseudos[1][$lnCounter];
$lnCounter++;
}
echo $laResult[2];
results in KILLER

Lower child array keys and combine items

I've one array
$arr = [
'parent' => [
'CHILD' => [
5,6
],
'child' => [
1,2,3,4
],
'Child' => [
5,6,7,8
],
...
]
];
I want to lower the child keys and combine each child having the same case insensitive keys like
$arr = [
'parent' => [
'child' => [
1,2,3,4,5,6,7,8
],
]
];
I've tried with array_change_key_case which always takes the last element and ignores the others.
An array may have multiple children with the same key (with different case)
Try the code below should work:
<?php
$arr = [
'parent' => [
'CHILD' => [
5,6
],
'child' => [
1,2,3,4
],
]
];
$arNew = [];
foreach ($arr as $sParent => $ar) {
foreach ($ar as $sChild => $ar1) {
$sChild = strtolower($sChild);
if (empty($arNew[$sParent][$sChild])) {
$arNew[$sParent][$sChild] = $ar1;
} else {
$arNew[$sParent][$sChild] = array_merge($arNew[$sParent][$sChild], $ar1);
}
}
}
print_r($arNew);

array_walk not working as expected when modifying the values

I am trying to add a new value to the array (I know it is possible with array_map() but I would like to test it with the array_walk()).
This is the code:
$array = [
[
'id' => 1,
'name' => 'Jesus',
],
[
'id' => 2,
'name' => 'David',
],
];
And I want this output:
$array = [
[
'id' => 1,
'name' => 'Jesus',
'locked' => 0,
],
[
'id' => 2,
'name' => 'David',
'locked' => 0,
],
];
I tried with the following code:
array_walk($array, static function(array $item): array {
$item += ['locked' => 0];
//var_dump($item); // Here the array has the three values.
return $item;
});
// Also I tried the same code but not returning the array, I mean:
array_walk($array, static function(array $item): void {
$item += ['locked' => 0];
//var_dump($item); // Here the array has the three values.
});
Is it possible what I want with an array_walk()?
That would be the solution with an array_map().
$arrayMapped = array_map(static function(array $item): array {
return $item += ['locked' => 0];
}, $array);
var_dump($arrayMapped);
Cheers!
Arrays are passed by value. You need to define the argument by reference using &
array_walk($array, function(array &$item): void {
$item['locked'] = 0;
});

Is there a PHP function like array_column that only returns single values?

I am looping through instances of associative arrays (these associative arrays themselves are part of an array).
For each array I want to return a value, based on a key.
Currently I have:
$image_path = array_column($myarray, 'uri');
But of course array_column stores its values in a array, which, considering it's only returning 1 value, is useless to me.
Is there an existing function that will allow me to just get a value based off a supplied key?
Eg:
$image_path = get_keys_value($myarray, 'uri');
Example array. This is a very basic example. The real thing has many levels to it:
$myarray = array
(
'instance' => array(
'type' => 'somedata',
'content' => somedata',
'image' => array(
'name' => 'photo',
'uri' => 'path/to/file.png'
),
),
);
Desired outcome:
$image_path contains 'path/to/file.png' string.
Try this,
function array_column_recursive(array $haystack, $needle)
{
$found = [];
array_walk_recursive($haystack, function ($value, $key) use (&$found, $needle) {
if ($key == $needle) {
$found[] = $value;
}
});
return $found;
}
echo array_column_recursive($myarray, 'uri')[0];
Here is working code.
array_column will work with only 2 level array structure.
Above array will solve your problem.
I hope this will help
I guess you can use array_map.
Ex:
$arr = [
[
'root' => [
'child1' => [
'child2' => 123
]
]
],
[
'root' => [
'child1' => [
'child2' => 456
]
]
],
[
'root' => [
'child1' => [
'child2' => 789
]
]
],
[
'root' => [
'child1' => [
'child2' => 123
]
]
],
];
print_r(array_map(function($row) {
// here goes expression to get required path
return $row['root']['child1']['child2'];
}, $arr));
Output:
Array
(
[0] => 123
[1] => 456
[2] => 789
[3] => 123
)

Categories