PHP: Getting values from array1 with from nested arrays - php

I have two arrays. $array2 to check whether $array1 value exist in 'slug'.
$array1 = [
0 => "tenants",
1 => "modules",
];
$array2 = [
"child" => [
"prefix" => "tenants",
"slug" => "tenants",
"child" => [
[
"prefix" => "modules/{id}",
"slug" => "modules"
],
[
"prefix" => "fetch/{id}",
"slug" => "fetch"
],
],
],
];
My Code:
foreach ($array1 as $value) {
array_walk_recursive($array2['child'],
function ($item, $key) use (&$result, &$res, &$value) {
if ($key == "slug") {
if ($item == $value) {
$res[] = $item;
}
}
if ($key == 'prefix') {
$result[] = $item;
}
});
}
Here I used array_walk_recursive to check every if array1 is exist in slug. But i want to get also it's prefix.
In my code above It will not get the modules/{id} since it's not equal to slug.
Example:
$array1 = [
0 => "tenants",
1 => "modules",
];
Result:
array:3 [▼
1 => "tenants"
2 => "modules/{id}"
]
Code: https://3v4l.org/E18Uq
Happy coding.

You don't need to use array_walk_recursive just use custom function as:
function getFromChild($item, $asd) {
if (!is_array($item)) return [];
$res = [];
if ($item["slug"] == $asd) // if have slug check and add if needed
$res[] = $item['prefix'];
if (isset($item['child']))
foreach($item['child'] as $child) // for all child loop and add
$res = array_merge($res, getFromChild($child, $asd));
return $res;
}
Now you can call it with:
$res = [];
foreach ($array1 as $asd) {
$res = array_merge($res, getFromChild($array2, $asd));
}
Live example: 3v4l

Related

Create a new array from a unknown depth multidimensional and keep the same structure

I have a multidimensional array that can have any depth. What im trying to do is to filter the whole path based on dynamic keys and create a new array of it.
Example of the array
$originalArray = [
"title" => "BACKPACK MULTICOLOUR",
"description" => "description here",
"images" => [
[
"id" => 12323123123,
"width" => 635,
"height" => 560,
"src" => "https://example.com",
"variant_ids": [32694976315473, 32863017926737],
],
[
"id" => 4365656656565,
"width" => 635,
"height" => 560,
"src" => "https://example.com",
"variant_ids": [32694976315473, 32863017926737],
]
],
"price" => [
"normal" => 11.00,
"discount" => [
"gold_members" => 9.00,
"silver_members" => 10.00,
"bronze_members" => null
]
]
];
Example how the output should look like with the key "title, width, height, gold_members" filtered out. Only keys from the end of the array tree should be valid, so nothing must happen when images is in the filter
$newArray = [
"title" => "BACKPACK MULTICOLOUR",
"images" => [
[
"width" => 635,
"height" => 560,
],
[
"width" => 635,
"height" => 560,
]
],
"price" => [
"discount" => [
"gold_members" => 9.00,
]
]
];
I guess that i should create a function that loop through each element and when it is an associative array, it should call itself again
Because the filtered paths are unknown i cannot make a hardcoded setter like this:
$newArray["images"][0]["width"] = 635
The following filter will be an example but it should basically be dynamic
example what i have now:
$newArray = handleArray($originalArray);
handleArray($array)
{
$filter = ["title", "width", "height", "gold_members"];
foreach ($array as $key => $value) {
if (is_array($value)) {
$this->handleArray($value);
} else {
if (in_array($key, $filter)) {
// put this full path in the new array
}
}
}
}
[Solved] Update:
I solved my problem thanks to #trincot
I used his code and added an extra check to add an array with multiple values to the new array
My code to solve the issue:
<?php
function isListOfValues($array) {
$listOfArrays = [];
foreach ($array as $key => $value) {
$listOfArrays[] = ! is_array($value) && is_int($key);
}
return array_sum($listOfArrays) === count($listOfArrays);
}
function filterKeysRecursive(&$arr, &$keep) {
$result = [];
foreach ($arr as $key => $value) {
if (is_array($value) && ! isListOfValues($value)) {
$value = filterKeysRecursive($value, $keep);
if (count($value)) {
$result[$key] = $value;
}
} else if (array_key_exists($key, $keep)) {
$result[$key] = $value;
}
}
return $result;
}
$keep = array_flip(["title", "width", "height", "gold_members"]);
$result = filterKeysRecursive($originalArray, $keep);
You could use a recursive function, with following logic:
base case: the value associated with a key is not an array (it is a "leaf"). In that case the new object will have that key/value only when the key is in the list of desired keys.
recursive case: the value associated with a key is an array. Apply recursion to that value. Only add the key when the returned result is not an empty array. In that case associate the filtered value to the key in the result object.
To speed up the look up in the list of keys, it is better to flip that list into an associative array.
Here is the implementation:
function filter_keys_recursive(&$arr, &$keep) {
foreach ($arr as $key => $value) {
if (is_array($value)) {
$value = filter_keys_recursive($value, $keep);
if (count($value)) $result[$key] = $value;
} else if (array_key_exists($key, $keep)) {
$result[$key] = $value;
}
}
return $result;
}
$originalArray = ["title" => "BACKPACK MULTICOLOUR","description" => "description here","images" => [["id" => 12323123123,"width" => 635,"height" => 560,"src" => "https://example.com"],["id" => 4365656656565,"width" => 635,"height" => 560,"src" => "https://example.com"]],"price" => ["normal" => 11.00,"discount" => ["gold_members" => 9.00,"silver_members" => 10.00,"bronze_members" => null]]];
$keep = array_flip(["title", "width", "height", "gold_members"]);
$result = filter_keys_recursive($originalArray, $keep);
My proposition to you is to write a custom function to transform structure from one schema to another:
function transform(array $originalArray): array {
array_walk($originalArray['images'], function (&$a, $k) {
unset($a['id']); unset($a['src']);
});
unset($originalArray['description']);
unset($originalArray['price']['normal']);
unset($originalArray['price']['discount']['silver_members']);
unset($originalArray['price']['discount']['bronze_members']);
return $originalArray;
}
var_dump(transform($originalArray));
If you are familiar with OOP I suggest you to look at how DTO works in API Platform for example and inject this idea into your code by creating custom DataTransformers where you specify which kind of structers you want to support with transformer and a method where you transform one structure to another.
Iterate over the array recursively on each key and subarray.
If the current key in the foreach is a required key in the result then:
If the value is not an array, simply assign the value
If the value is an array, iterate further down over value recursively just in case if there is any other filtering of the subarray keys that needs to be done.
If the current key in the foreach is NOT a required key in the result then:
Iterate over value recursively if it's an array in itself. This is required because there could be one of the filter keys deep down which we would need. Get the result and only include it in the current subresult if it's result is not an empty array. Else, we can skip it safely as there are no required keys down that line.
Snippet:
<?php
function filterKeys($array, $filter_keys) {
$sub_result = [];
foreach ($array as $key => $value) {
if(in_array($key, $filter_keys)){// if $key itself is present in $filter_keys
if(!is_array($value)) $sub_result[$key] = $value;
else{
$temp = filterKeys($value, $filter_keys);
$sub_result[$key] = count($temp) > 0 ? $temp : $value;
}
}else if(is_array($value)){// if $key is not present in $filter_keys - iterate over the remaining subarray for that key
$temp = filterKeys($value, $filter_keys);
if(count($temp) > 0) $sub_result[$key] = $temp;
}
}
return $sub_result;
}
$result = filterKeys($originalArray, ["title", "width", "height", "gold_members"]);
print_r($result);
Online Demo
Try this way.
$expectedKeys = ['title','images','width','height','price','gold_members'];
function removeUnexpectedKeys ($originalArray,$expectedKeys)
{
foreach ($originalArray as $key=>$value) {
if(is_array($value)) {
$originalArray[$key] = removeUnexpectedKeys($value,$expectedKeys);
if(!is_array($originalArray[$key]) or count($originalArray[$key]) == 0) {
unset($originalArray[$key]);
}
} else {
if (!in_array($key,$expectedKeys)){
unset($originalArray[$key]);
}
}
}
return $originalArray;
}
$newArray = removeUnexpectedKeys ($originalArray,$expectedKeys);
print_r($newArray);
check this on editor,
https://www.online-ide.com/vFN69waXMf

Find elements with empty value in multidimensional array

I have an array like the below:
$arrays = [
'a' => [
'name' => "Name 1",
'age' => "99",
'add' => ""
],
'b' => [
'name' => "Name 2",
'age' => "99",
'add' => "Add2"
],
'c' => [
'name' => "Name 3",
'age' => "99",
'add' => "Add3"
],
'd' => [
'name' => "",
'age' => "",
'add' => "Add4"
]
];
I want to get a result like:
$res = [
'a' => ['add'],
'd' => ['name','age']
];
I have tried with the below code, but it returns 1.
$status = array_walk_recursive($arrays, function($v, $k) {
global $output;
if (empty($v) && $v !== 0)
$output[$k] = $v;
});
I want to do it without using any loops because my real input array is very large and I am concerned with performance.
If your input is always of a fixed depth, you can map the existing values to the keys of all empty items:
$output = array_map(function($row) {
return array_keys(array_filter($row, function ($e) {
return empty($e) && $e !== 0;
}));
}, $arrays);
The outer function runs for each "row", the value of which is then converted into a list of all keys with empty values (excluding zeroes, as in the question).
This will keep the outer keys B & C as empty arrays, so if you want them to be removed as well then run another iteration of array_filter over the result:
$output = array_filter($output)
See https://3v4l.org/c23ZB
As mentioned in the comments, there are still several loops going on here, they're just not as visible in the code. A regular foreach loop might end up being a lot easier to read, and possibly perform faster as well.
You can use next combination of array_walk & array_filter:
$result = [];
array_walk(
$arrays,
function($el, $key) use(&$result) {
$empty = array_filter($el, function($el){return $el == "";});
$empty_keys = array_keys($empty);
if (count($empty_keys)) $result[$key] = $empty_keys;
}
);
Try it here
This is another way to achieve your desired output.
$result = [];
foreach($arrays as $key => $value) {
$empty_arr = array_filter($value, function ($ele) {
return empty($ele);
});
$empty_arr_keys = array_keys($empty_arr);
if(!empty($empty_arr_keys)) $result[$key] = $empty_arr_keys;
}
print_r($result);
#iainn's answer can be sharpened up by calling !strlen() on the deep values.
Code: (Demo)
var_export(
array_filter(
array_map(
fn($row) => array_keys(
array_filter(
$row,
fn($v) => !strlen($v)
)
),
$array
)
)
);
But you will end up making fewer iterations and writing cleaner, more intuitive/readable code if you use classic loops. This is how I would write it in my own project:
Code: (Demo)
$result = [];
foreach ($array as $rowKey => $row) {
foreach ($row as $key => $value) {
if (!strlen($value)) {
$result[$rowKey][] = $key;
}
}
}
var_export($result);

Specific problem with traversing an array in php

I have given the array:
array(
"firstName": null,
"lastName": null,
"category": [
"name": null,
"service": [
"foo" => [
"bar" => null
]
]
]
)
that needs to be transform into this:
array(
0 => "firstName",
1 => "lastName",
2 => "category",
"category" => [
0 => "name",
1 => "service",
"service" => [
0 => "foo",
"foo" => [
0 => "bar"
]
]
]
)
The loop should check if a value is an array and if so, it should add the key as a value (0 => category) to the root of array and then leave the key as it is (category => ...) and traverse the value again to build the tree as in example.
I am stuck with this and every time I try, I get wrong results. Is there someone who is array guru and knows how to simply do it?
The code so far:
private $array = [];
private function prepareFields(array $fields):array
{
foreach($fields as $key => $value)
{
if(is_array($value))
{
$this->array[] = $key;
$this->array[$key] = $this->prepareFields($value);
}
else
{
$this->array[] = $key;
}
}
return $this->array;
}
You could make use of array_reduce:
function prepareFields(array $array): array
{
return array_reduce(array_keys($array), function ($result, $key) use ($array) {
$result[] = $key;
if (is_array($array[$key])) {
$result[$key] = prepareFields($array[$key]);
}
return $result;
});
}
Demo: https://3v4l.org/3BfKD
You can do it with this, check the Demo
function array_format(&$array){
$temp_array = [];
foreach($array as $k=>$v){
$temp_array[] = $k;
if(is_array($v)){
array_format($v);
$temp_array[$k] = $v;
}
}
$array = $temp_array;
}
array_format($array);
print_r($array);

How to combine two different multi dimensional arrays (PHP)

I want to combine two different multi-dimensional arrays, with one providing the correct structure (keys) and the other one the data to fill it (values).
Notice that I can't control how the arrays are formed, the structure might vary in different situations.
$structure = [
"a",
"b" => [
"b1",
"b2" => [
"b21",
"b22"
]
]
];
$data = [A, B1, B21, B22];
Expected result:
$array = [
"a" => "A",
"b" => [
"b1" => "B1",
"b2" => [
"b21" => "B21",
"b22" => "B22"
]
]
];
You can use the following code, however it will only work if number of elements in $data is same or more than $structure.
$filled = 0;
array_walk_recursive ($structure, function (&$val) use (&$filled, $data) {
$val = array( $val => $data[ $filled ] );
$filled++;
});
print_r( $structure );
Here is a working demo
You can try by a recursive way. Write a recursive method which takes an array as first argument to alter and the data set as its second argument. This method itself call when any array element is another array, else it alters the key and value with the help of data set.
$structure = [
"a",
"b" => [
"b1",
"b2" => [
"b21",
"b22"
]
]
];
$data = ['A', 'B1', 'B21', 'B22'];
function alterKey(&$arr, $data) {
foreach ($arr as $key => $val) {
if (!is_array($val)) {
$data_key = array_search(strtoupper($val), $data);
$arr[$val] = $data[$data_key];
unset($arr[$key]);
} else {
$arr[$key] = alterKey($val, $data);
}
}
ksort($arr);
return $arr;
}
alterKey($structure, $data);
echo '<pre>', print_r($structure);
Working demo.
This should work.
$structure = [
"a",
"b" => [
"b1",
"b2" => [
"b21",
"b22"
]
]
];
$new_structure = array();
foreach($structure as $key =>$value)
{
if(!is_array($value))
{
$new_structure[$value]= $value;
}else{
foreach($value as $k =>$v)
{
if(!is_array($v))
{
$new_structure[$key][$v]=$v;
}else
{
foreach($v as $kk => $vv)
{
$new_structure[$k][$vv]=$vv;
}
}
}
}
}
print_r($new_structure);exit;
Use
$array=array_merge($structure,$data);
for more information follow this link
how to join two multidimensional arrays in php

I have a question about php search associative array

I have this ff. assoc array
$array = [
'school' => [
'college' => [
'nursing' => ['n1a', 'n2a', 'n3a', 'n4a'],
'hrm' => ['h1a', 'h2a', 'h3a', 'h4a'],
'tourism' => ['t1a', 't2a', 't3a', 't4a'],
'it' => ['i1a', 'i2a', 'i3a', 'i4a'],
],
'senior' => [],
],
'business' => [
'office' => [
'dep1' => ['team1', 'team2'],
'dep2' => ['team1', 'team2'],
'dep3' => ['team1', 'team2'],
'dep4' => ['team1', 'team2'],
],
],
]
And I have this code, but this only search first level array.
function searchItemsByKey($array, $key) {
$results = array();
if (is_array($array))
{
if (isset($array[$key]) && key($array)==$key){
$results[] = $array[$key];
}
foreach ($array as $sub_array){
$results = array_merge($results, $this->searchItemsByKey($sub_array, $key));
}
}
return $results;
}
All I want is to search all keys in this array that will result all arrays associated with keys like:
searchItemsByKey($array, 'hrm');
That will return:
['h1a', 'h2a', 'h3a', 'h4a']
Thanks.
You can use array_walk_recursive,
$result = [];
$search = "hrm";
function searchItemsByKey($array, $key)
{
$retArr = [];
if (is_array($array)) {
if (!empty($array[$key])) {
return $array[$key];
}
foreach ($array as $val) {
$retArr = array_merge($retArr, searchItemsByKey($val, $key));
}
}
return $retArr;
}
$temp = searchItemsByKey($array, 'hrm');
Demo.

Categories