I have a nested for each loop as a solution for my problem, however, I am looking for an alternative to the nested loop. I was reading about array_walk
and array_walk_recursive
but was unable to implement them to achieve what I want.
Also, I was also wondering is it worth it or is the nested foreach all right?
$data = [
'category' => [
'sector' => [
'Sample A',
'Sample B',
'Sample C'
]
],
'area' => [
'location' => [
'Location A',
'Location B',
'Location C'
]
],
];
$exportData = [];
foreach ($data as $index => $types) {
foreach ($types as $type => $values) {
foreach ($values as $value) {
$exportData [] = [$index, $type, $value];
}
}
}
CodePudding user response:
From a performance standpoint, you need to visit every item in that list and there's no algorithm to apply that would get around that fact.
At best an alternative would perform the same as the nested loops.
From a flexibility standpoint, you have locked this code to deal with exactly this structure. In order to add another level or change one of the dicts to a list or vice-versa you have to finagle the code.
If you want code to accomodate objects of arbitrary depth you need recursion, and you can add logic to handle list/dict cases.
From a memory usage standpoint, you are using the maximum amount of memory for this task by constructing the entire output array all at once. The higher the number of items and/or the deeper the depth of the structure, the more memory it will require. This will grow exponentially-ish.
More often than not these arrays are generated once, iterated sequentially, and never touched again. Which is a perfect use case for a Generator. TLDR: A Generator is an iterable type that produces one element at a time, so you only have to hold one bit in memory at a time.
Taken together, we get something like:
function expand_array($input, $skip_list_keys=true) {
$is_list = array_is_list($input);
foreach($input as $key => $value) {
if( is_array($value) ) {
foreach( expand_array($value, $skip_list_keys) as $item ) {
if( $is_list && $skip_list_keys ) {
yield [ $value ];
} else {
yield array_merge([$key], $item);
}
}
} else {
if( $is_list && $skip_list_keys ) {
yield [ $value ];
} else {
yield [ $key, $value ];
}
}
}
}
foreach( expand_array($data) as $item ) {
printf("%s\n", json_encode($item));
}
echo PHP_EOL;
foreach( expand_array($data, false) as $item ) {
printf("%s\n", json_encode($item));
}
Output:
["category","sector","Sample A"]
["category","sector","Sample B"]
["category","sector","Sample C"]
["area","location","Location A"]
["area","location","Location B"]
["area","location","Location C"]
["category","sector",0,"Sample A"]
["category","sector",1,"Sample B"]
["category","sector",2,"Sample C"]
["area","location",0,"Location A"]
["area","location",1,"Location B"]
["area","location",2,"Location C"]
CodePudding user response:
A possibility is to use two nested array_reduce:
$data = [
'category' => [
'sector' => [
'Sample A',
'Sample B',
'Sample C'
]
],
'area' => [
'location' => [
'Location A',
'Location B',
'Location C'
]
],
];
$result = array_reduce(
array_keys($data),
fn($carry, $key) => [
...$carry,
...array_reduce(
$data[$key][key($data[$key])],
fn($carry, $item) => [
...$carry,
[ $key, key($data[$key]), $item ]
],
[]
)
],
[]
);
print_r($result);
Note that this is specifically written for the OP's input array. This array has only one subkey for each main element ('category' has only 'sector', etc.). Should there be more than one subkey (for example 'sector', 'sector2', etc. in 'category') then the above code will not work.