PHP remove value from multi dimensional array - php

I have an array, $arr, which looks like this:
'sdb5' => [
'filters' => [
(int) 11 => [
'find' => [
(int) 0 => (int) 569
],
'exclude' => [
(int) 0 => (int) 89,
(int) 1 => (int) 573
]
],
(int) 86 => [
'find' => [
(int) 0 => (int) 49,
(int) 1 => (int) 522,
(int) 2 => (int) 803
],
'exclude' => [
(int) 0 => (int) 530,
(int) 1 => (int) 802,
(int) 2 => (int) 511
]
]
]
],
I've read Delete element from multidimensional-array based on value but am struggling to understand how to delete a value in an efficient way.
For example, let's say I want to delete the value 522. I'm doing it like this:
$remove = 522; // value to remove
foreach ($arr as $filters) {
foreach ($filters as $filter) {
foreach ($filter as $single_filter) {
foreach ($single_filter as $key => $value) {
if ($value == $remove) {
unset($key);
}
}
}
}
}
I couldn't work out from the above link how to do this because even though it's a multidimensional array, it doesn't have any sub-arrays like mine.
I also don't know how else to rewrite this without repeating foreach to get to the elements of the array I want. Again, I've read Avoid multiple foreach loops but cannot apply this to my array.
I am using PHP 7.x.

foreach() made copy of elements. Then unseting the key is not enough, because you are destroying a local variable.
You could use references & in your foreach() loops and unset like :
foreach ($arr as &$filters) {
foreach ($filters as &$filter) {
foreach ($filter as &$single_filter) {
foreach ($single_filter as $key => $value) {
if ($value == $remove) {
unset($single_filter[$key]);
}
}
}
}
}
Or using keys ($k1, $k2, ...) :
foreach ($arr as $k1 => $filters) {
foreach ($filters as $k2 => $filter) {
foreach ($filter as $k3 => $single_filter) {
foreach ($single_filter as $key => $value) {
if ($value == $remove) {
unset($arr[$k1][$k2][$k3][$key]);
}
}
}
}
}

You could also write a recursive function, so you don't have to use nested foreach:
function deleteRecursive(&$array, &$value) {
foreach($array as $key => &$subArray) {
if(is_array($subArray)) {
deleteRecursive($subArray, $value);
} elseif($subArray == $value) {
unset($array[$key]);
}
}
}
$valueToDelete = 522;
deleteRecursive($array, $valueToDelete);

function recursiveRemoval(&$array, $val)
{
if(is_array($array))
{
foreach($array as $key=>&$arrayElement)
{
if(is_array($arrayElement))
{
recursiveRemoval($arrayElement, $val);
}
else
{
if($arrayElement == $val)
{
unset($array[$key]);
}
}
}
}
}
Call function
recursiveRemoval($array, $value);

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

Recursively extract key paths from array - excluding numeric keys

In php, I need a function to return all keys in array of objects. For arrays and objects I need keys like parentkey->key. If the key is again an array, then I need parentkey->key->key1.
My code as below
$array=json_decode('{"client":"4","gateWay":"1","store":"store.shop.com",
"valid":"true","po":34535,"additionalPO":23423,"customerNotes":"",
"orderItems":[{"item":"123","quantity":10,"supplierLotNo":"",
"customsValue":"","customsDescription":"","hsCode":""},
{"item":"345","quantity":50}],
"shippingInfos":[{"address":{"city":"Chennai",
"country":"India","postalCode":"86715","state":"TN",
"streetAddress1":"6971 North Street","streetAddress2":null},
"contact":{"company":null,"email":"info#store.com","firstName":"test",
"lastName":"test","phoneNo":null},"ServiceId":"3","thirdPartyAccountNo":"",
"signatureConfirmation":false,"saturdayDelivery":false}]}',true);
function array_keys_multi(array $array,$headkey,$pkey){
$keys = array();$hkey='';$parentkey='';
foreach ($array as $key => $value) {
//echo $key;
if($headkey!='' && !is_integer($headkey)){
if(!is_integer($key)){
if (is_array($value) || is_object($value)) {
if($pkey!='')
$parentkey=$pkey."->".$key;
else
$parentkey=$key;
foreach($value as $kk=>$val){
foreach($val as $kk1=>$val1){
$keys[]=$parentkey."->".$kk1;
}
}
//$keys[]=$parentkey."->".$key;
}else{
$keys[] = $pkey."->".$key;
$hkey=$headkey."->".$key;
}
}
}
else{
if(!is_integer($key)){
if($pkey!='')
$parentkey=$pkey."->".$key;
else
$parentkey=$key;
if (is_array($value) || is_object($value)) {
if($pkey!='')
$parentkey=$pkey."->".$key;
else
$parentkey=$key;
foreach($value as $kk=>$val){
//print_r($val);echo "==========";
foreach($val as $kk1=>$val1){
$keys[]=$parentkey."->".$kk1;
}
}
$hkey=$key;
}else{
$keys[]=$key;
}
}
}
if (is_array($value) || is_object($value)) {
echo $key."-----".$hkey."<br>";
$keys = array_merge($keys, array_keys_multi($value,$hkey,$key));
}
}
return $keys;
}
$k=array_keys_multi($array,'','');
print_r($k);
I need output as an array with the following key paths (notice no numeric keys are retained):
[
"client",
"gateWay",
"store",
"valid",
"po",
"additionalPO",
"customerNotes",
"orderItems->item",
"orderItems->quantity",
"orderItems->supplierLotNo",
"orderItems->customsValue",
"orderItems->customsDescription",
"orderItems->hsCode",
"shippingInfos->address->city",
"shippingInfos->address->country",
"shippingInfos->address->postalCode",
"shippingInfos->address->state",
"shippingInfos->address->streetAddress1",
"shippingInfos->address->streetAddress2",
"shippingInfos->contact->company",
"shippingInfos->contact->email",
"shippingInfos->contact->firstName",
"shippingInfos->contact->lastName",
"shippingInfos->contact->phoneNo",
"shippingInfos->ServiceId",
"shippingInfos->thirdPartyAccountNo",
"shippingInfos->signatureConfirmation",
"shippingInfos->saturdayDelivery"
]
How can I achieve this?
Starting from this very similar recursive snippet, I changed the key path separator and extended the algorithm to exclude numeric keys and parents with children.
Code: (Demo)
function getUniqueObjectKeyPaths($array, $parentKey = "") {
$keys = [];
foreach ($array as $parentKey => $v) {
if (is_array($v)) {
$nestedKeys = getUniqueObjectKeyPaths($v, $parentKey);
foreach($nestedKeys as $index => $key) {
if (!is_numeric($parentKey) && !is_numeric($key)) {
$nestedKeys[$index] = $parentKey . "->" . $key;
}
}
array_push($keys, ...$nestedKeys);
} elseif (!is_numeric($parentKey)) {
$keys[] = $parentKey;
}
}
return $keys;
}
var_export(getUniqueObjectKeyPaths($array));
Output:
array (
0 => 'client',
1 => 'gateWay',
2 => 'store',
3 => 'valid',
4 => 'po',
5 => 'additionalPO',
6 => 'customerNotes',
7 => 'orderItems->item',
8 => 'orderItems->quantity',
9 => 'orderItems->supplierLotNo',
10 => 'orderItems->customsValue',
11 => 'orderItems->customsDescription',
12 => 'orderItems->hsCode',
13 => 'orderItems->item',
14 => 'orderItems->quantity',
15 => 'shippingInfos->address->city',
16 => 'shippingInfos->address->country',
17 => 'shippingInfos->address->postalCode',
18 => 'shippingInfos->address->state',
19 => 'shippingInfos->address->streetAddress1',
20 => 'shippingInfos->address->streetAddress2',
21 => 'shippingInfos->contact->company',
22 => 'shippingInfos->contact->email',
23 => 'shippingInfos->contact->firstName',
24 => 'shippingInfos->contact->lastName',
25 => 'shippingInfos->contact->phoneNo',
26 => 'shippingInfos->ServiceId',
27 => 'shippingInfos->thirdPartyAccountNo',
28 => 'shippingInfos->signatureConfirmation',
29 => 'shippingInfos->saturdayDelivery',
)

Create dynamic associative array in php in foreach loop

I have loop like this
foreach($this->input->post('users') as $value)
{
foreach($this->input->post('group_name') as $v)
{
echo $value.','.$v.'<br>';
}
}
And its ouput is
17,5
17,6
18,5
18,6
19,5
19,6
20,5
20,6
Now i want to create an associative array like this using the above values.
array(
array(
'user_id' => 17,
'group_id' => 15
),
....
....
array(
'user_id' => 20,
'group_id' => 6
)
)
How can i do that
I've tried this in foreach loop but it will print two separate arrays.
$temp['user_id'][]=$v;
$temp['group_id'][]=$value;
All you have to do is append array with respective values.
$result = [];
foreach($this->input->post('users') as $value)
{
foreach($this->input->post('group_name') as $v)
{
$result[] = ['user_id' => $value, 'group_id' => $v];
}
}
var_dump($result);
This loop should help you out.
$resultArray = [];
foreach($this->input->post('users') as $value) {
foreach($this->input->post('group_name') as $v) {
$resultArray[] = array(
'user_id' => $value,
'group_id' => $v,
);
}
}
var_dump($resultArray);
It's very simple, you just have to append/push the child array into main array.
Like this,
$main_array=array();
foreach($this->input->post('users') as $value)
{
foreach($this->input->post('group_name') as $v)
{
$group_array=array();
$group_array["group_id"]=$v;
$group_array["user_id"]=$value;
$main_array[]=$group_array;
//echo $value.','.$v.'<br>';
}
}
print_r($group_array);
You may also use array_push() to push child array into main array.
Syntax for that would be,
array_push($main_array, $child_array);
Can u try this
$temp = array();
foreach($this->input->post('users') as $key=>$value)
{
foreach($this->input->post('group_name') as $v)
{
$temp[$key]['user_id']=$v;
$temp[$key]['group_id']=$value;
}
}
print_r($temp);

How do I reduce this function into a recursive one?

I have a function that returns an array with empty keys from the input array. The problem is that I've been working on an inconsistent data. The data could go to any level of nested array. For example,
$inputArray = [
'a' => 'value a',
'b' => [
1 => [],
2 => 'value b2',
3 => [
'x' => 'value x'
'y' => ''
],
'c' => ''
],
];
I need an output that converts this kind of data to string. So,
$outputArray = [
'empty' => [
'b[1]',
'b[3][y]',
'c'
]
];
Here's what I have so far to get keys with empty values:
$outputArray = [];
foreach ($inputArray as $key => $value) {
if (is_array($value)) {
foreach ($value as $index => $field) {
if (is_array($field)) {
foreach ($field as $index1 => $value1) {
if (empty($value1)) {
array_push($outputArray['empty'], $key . '[' . $index . ']' . '[' . $index1 . ']');
}
}
}
if (empty($field)) {
array_push($outputArray['empty'], $key . '[' . $index . ']');
}
}
}
if (empty($value)) {
array_push($outputArray['empty'], $key);
}
}
return $outputArray;
As I said the input array could be nested to any level. I cannot keep on adding if (is_array) block every time the array is nested one more level. I believe it could be solved using a recursive function, but I cannot seem to figure out how. Please help me with this. Thanks.
You are right about a recursive function but you should also be aware of recursions, whether we are in or out of a recursion. The tricky part is passing current level keys to the recursive function:
function findEmpties($input, $currentLevel = null) {
static $empties = [];
foreach ($input as $key => $value) {
$levelItem = $currentLevel ? "{$currentLevel}[{$key}]" : $key;
if (empty($value)) {
$empties['empty'][] = $levelItem;
} else {
if (is_array($value)) {
findEmpties($value, $levelItem);
}
}
}
return $empties;
}
Live demo

PHP: Find element with certain property value in array

I'm sure there is an easy way to do this, but I can't think of it right now. Is there an array function or something that lets me search through an array and find the item that has a certain property value? For example:
$people = array(
array(
'name' => 'Alice',
'age' => 25,
),
array(
'name' => 'Waldo',
'age' => 89,
),
array(
'name' => 'Bob',
'age' => 27,
),
);
How can I find and get Waldo?
With the following snippet you have a general idea how to do it:
foreach ($people as $i => $person)
{
if (array_key_exists('name', $person) && $person['name'] == 'Waldo')
echo('Waldo found at ' . $i);
}
Then you can make the previous snippet as a general use function like:
function SearchArray($array, $searchIndex, $searchValue)
{
if (!is_array($array) || $searchIndex == '')
return false;
foreach ($array as $k => $v)
{
if (is_array($v) && array_key_exists($searchIndex, $v) && $v[$searchIndex] == $searchValue)
return $k;
}
return false;
}
And use it like this:
$foundIndex = SearchArray($people, 'name', 'Waldo'); //Search by name
$foundIndex = SearchArray($people, 'age', 89); //Search by age
But watch out as the function can return 0 and false which both evaluates to false (use something like if ($foundIndex !== false) or if ($foundIndex === false)).
function findByName($array, $name) {
foreach ($array as $person) {
if ($person['name'] == $name) {
return $person;
}
}
}
function getCustomSearch($key) {
foreach($people as $p) {
if($p['name']==$searchKey) return $p
}
}
getCustomSearch('waldo');

Categories