Fastes PHP Multidimensional Array matching - php

If I have two arrays, one which is a one dimensional array, the second is a undefined depth Multidimensional Array.
I would like to execute a function on the fields from both if the key matches and its not an iterable instance.
The first array is something like this
$array = array(
"st" => "1",
"gr" => "2",
);
And the second...
$array = array(
"foo" => "bar",
"bar" => array(
"st" => "fr",
"gr" => "et",
),
);
What is the fastest method to achieve this?
what i have though about.
foreach (array as key => value) {
recursiveLoop($multidemsnionalArray, $key, $value)
}
function recursiveLoop($multidemsnionalArray, $key, $value){
foreach ($multidemsnionalArrayas as $key2 => $value2) {
if (is_array($value2) or ($value2 instanceof Traversable)){
recursiveLoop($value2, $key, $value);
}
else{
//do the Foo
}
}
}
This is very ineffcient, the time will be according to the length of the first array times the length of the second.
would i best do this or try something else?

Related

Get key (not index) from qualifying row in an associative array of associative arrays

Here my array:
$array = [
'key1' => [
'first' => 'azerty',
'second' => 'qwerty'
],
'key2' => [
'first' => 'hello',
'second' => 'world'
]
];
With the value 'qwerty' I would like to retrieve the 'key1'.
I looking for something like that:
$theKeyIWant = array_search('qwerty', array_column($array, 'second'));
But I get '0', instead of 'key1' (and I know that's how it works)
Anyone know how to adapt this code or know another code to get the key value?
Slight modification to your code to combine the keys and column values:
$theKeyIWant = array_search('qwerty', array_combine(array_keys($array), array_column($array, 'second')));
The problem with seeking "something sexier" for this task is that if "sexier" means a "functional iterator", then that comes a cost of not being able to "return early" (doing unnecessary cycles).
If you want a single-line function to call, you can build your own and put it in a helpers file somewhere in your project. My strong advice is to abandon "sexy" for this task and use a breakable foreach loop.
Code: (Demo)
function getRowKey($array, $column, $value) {
foreach ($array as $key => $row) {
if ($row[$column] === $value) {
return $key;
}
}
return null;
}
var_export(getRowKey($array, 'second', 'qwerty'));
If you are going to perform repeated searches on the same array and the second column is guaranteed to contain unique values, then you can convert the array into a lookup array without losing any data. (Demo)
function restructure($array, $columnToKey) {
$result = [];
foreach ($array as $key => $row) {
$result[$row[$columnToKey]] = $row + ['oldKey' => $key];
}
return $result;
}
var_export(restructure($array, 'second')['qwerty']);
To solve this is to loop through the outer array and use array_search() to search for the value within each inner array.
$value = 'qwerty';
$theKeyIWant = null;
foreach ($array as $key => $innerArray) {
if (array_search($value, $innerArray) !== false) {
$theKeyIWant = $key;
break;
}
}
echo $theKeyIWant; // Output: key1
array_keys returns an array of an array's keys.
<?php
$array = [
'key1' => [
'first' => 'azerty',
'second' => 'qwerty'
],
'key2' => [
'first' => 'hello',
'second' => 'world'
]
];
$theKeyIWant = array_search('qwerty', array_column($array, 'second'));
echo array_keys($array)[$theKeyIWant];
?>
3V4l

PHP: Unset All Empty Key Value Pairs of an Array

I have an associative array in my PHP code. I want to unset all the keys that have empty value.
I know individual keys can be unset using the following code
unset($array_name['key_name']);
What I don't know is how to recursively do this with an array. While searching for an answer I came across answers recommending PHP built-in function array_filter(), however I failed to grasp how that works. So here's the code I tried to write but it doesn't work either.
<?php
function unset_all_empty_array_keys($associative_array){
foreach ($associative_array as $key => $value){
if ($value == ""){
unset($associative_array['$key']); // This doesn't work.
}
}
};
$list = array(
"item1" => "one",
"item2" => "",
"item3" => "three",
);
unset_all_empty_array_keys($list, '$list');
print_r($list);
echo "<br>";
// Manually Unsetting key-value pair works fine.
unset($list['item2']);
print_r($list);
?>
When you want do it with 'foreach()' then by reference:
function unset_all_empty_array_keys(&$associative_array){
foreach ($associative_array as $key => &$value){
if ($value == ""){
unset($associative_array[$key]);
}
}
}
The way with 'array_filter()':
$list = array_filter($list,function($a){
return $a!='';
})
And can only work 'recursively' on multi-dimensional array
$a = ['a'=>['b'=>'']];
Your example is an flat array with only one dimension.
So here the version for multi-dimensional arrays:
$list = array(
"item1" => "one",
"item2" => "",
"item3" => array(
"item1" => "one",
"item2" => "",
"item3" => "three",
)
);
function unset_all_empty_array_keys_recursive(&$associative_array){
foreach ($associative_array as $key => &$value){
if ($value == ""){
unset($associative_array[$key]);
} else if (is_array($value)){
unset_all_empty_array_keys_recursive($value);
}
}
}
unset_all_empty_array_keys($list);
var_export($list);

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

PHP get index into new array

I have a json array like below. I need to get the index into a new array, how is this possible? Arrays are my weakness for some reason just cant grasp them. I can easily get id value, but cannot get the index (e.g 11111111). Any help would be appreciated.
Update please see the revised, my fault for not including the full multi dimensional array.
Below only outputs one result where I need all results.
<?php
$json = '[{
"11111111": {
"id": "val_somevalue5555",
"customer": {
"32312": {
"name": "jane doe"
}
}
},
"2222222": {
"id": "val_somevalue25",
"customer": {
"32312234": {
"name": "jane doe"
}
}
}
}]';
$jsonarr = json_decode($json, true);
$newarr = [];
foreach($jsonarr as $value)
{
$key = key($value);
$newarr[] = ['key' => $key, 'id' => $value[$key]['id']];
}
var_dump($newarr);
expected looped output
key 11111111
id val_somevalue5555
... looped.
You can create an array of the keys of an existing array using the array_keys() function
http://php.net/manual/en/function.array-keys.php
If you don't want the keys in a separate array, and instead just want to access them directly, when you are doing a 'foreach' loop of an array, you can choose to assign a variable to the current key by doing
foreach($jsonarr as $key => $value){...}
Because your original array is actually multidimensional (each $key has a $value that is also stored as an array of "id": "value") - this means taking one more step to get the value of key 'id':
foreach($jsonarr as $key => $value){
$newarray[] = ['key' => $key, 'id' => $value['id'];
}
you can use array_keys() or key() with a foreach loop for this(DEMO):
$newarr = [];
foreach($jsonarr as $value)
{
//$key = array_keys($value)[0];
$key = key($value);
$newarr[] = ['key' => $key, 'id' => $value[$key]['id']];
}
var_dump($newarr);
Output:
array(2) {
[0]=>
array(2) {
["key"]=>
int(11111111)
["id"]=>
string(17) "val_somevalue5555"
}
[1]=>
array(2) {
["key"]=>
int(2222222)
["id"]=>
string(15) "val_somevalue25"
}
}
Edit: With the updated json, you can use the following way, using 2 foreach loops (DEMO):
$newarr = [];
foreach($jsonarr as $json)
{
foreach($json as $key => $value)
{
$newarr[] = ['key' => $key, 'id' => $value['id']];
}
}
PHP supports a slightly different foreach syntax that extracts both the array key and the array value:
foreach ( $jsonarr as $key => $value ) {
$newarr[] = ['key' => $key, 'id' => $value];
}
Use this if you need the key ("11111111" and "2222222" in your example).
<?php
$json = '[{
"11111111": {
"id": "val_somevalue5555"
}
},
{
"2222222": {
"id": "val_somevalue25"
}
}
]';
$jsonarr = json_decode($json, true);
$newarr = [];
foreach($jsonarr as $key => $value) {
$newarr[] = ['key' => key($value), 'id' => current($value)['id']];
}
foreach($newarr as $key) {
echo 'key '.$key['key'] . PHP_EOL;
echo 'id '.$key['id'] . PHP_EOL;
}
If you remove what looks like embedded components in the $json string (otherwise it won't parse) then var_export the output of json_decode() you'll get this:
array (
0 => array (
11111111 => array (
'id' => 'val_somevalue5555',
),
),
1 => array (
2222222 => array (
'id' => 'val_somevalue25',
),
),
)
You have a double-nested array, hence...
foreach ($jsonarr as $obj) {
foreach ($obj as $name=>$value) {
print "$name = $value[id]\n";
break;
}
}
or you can reference the elements directly:
print $jsonarr[0]['11111111']['id'];
First, you are not accessing the deep enough before iterating.
If you call var_export($jsonarr); you will see:
array ( // an indexed array of subarrays
0 =>
array ( // an associative array of subarrays, access via [0] syntax (or a foreach loop that only iterates once)
11111111 => // this is the subarray's key that you want
array (
'id' => 'val_somevalue5555', // this is the value you seek from the id element of 1111111's subarray
'customer' =>
array (
32312 =>
array (
'name' => 'jane doe',
),
),
),
2222222 => // this is the subarray's key that you want
array (
'id' => 'val_somevalue25', // this is the value you seek from the id element of 2222222's subarray
'customer' =>
array (
32312234 =>
array (
'name' => 'jane doe',
),
),
),
),
)
Code: (Demo)
$jsonarr = json_decode($json, true);
$result=[];
// vvvv-avoid a function call (key()) on each iteration by declaring here
foreach($jsonarr[0] as $key=>$subarray){
// ^^^-drill down into the first level (use another foreach loop if there may be more than one)
$result[]=['key'=>$key,'id'=>$subarray['id']];
}
var_export($result);
Output:
array (
0 =>
array (
'key' => 11111111,
'id' => 'val_somevalue5555',
),
1 =>
array (
'key' => 2222222,
'id' => 'val_somevalue25',
),
)
p.s. If $jsonarr has more than one element in its first level, you should use a foreach() loop like this:
foreach($jsonarr as $array1){
foreach($array1 as $key=>$array2){
$result[]=['key'=>$key,'id'=>$array2['id']];
}
}

PHP Compare and change certain elements in multidimensional arrays

To be short, I have two simple arrays and I want to verify if certain keys from the second array have empty values and replace them with their correspondent values from the first array.
Example:
$a1 = [ 1 => 'one', 2 => 'two', 3 => 'three',5=>'cinco', 6=>'six'];
$a2 = [ 2 => 'two', 5=>'five', 6=>'' ];
Result:
Array
(
[2] => two
[5] => five
[6] => six
)
The following code works already for this.
$keys = array_keys($a1);
foreach ($keys as $k)
{
if ((isset($a2[$k])) && (($a2[$k]) == '')) {
$a2[$k] = $a1[$k];
}
}
print_r($a2);
But what if we want to apply this for two 2D arrays? What will be the proper approach in that case? Let's say these two 2D arrays will be:
$superheroes_complete = array(
"spiderman" => array(
"name" => "Peter Parker",
"email" => "peterparker#mail.com",
),
"superman" => array(
"name" => "Clark Kent",
"email" => "clarkkent#mail.com",
),
"ironman" => array(
"name" => "Harry Potter",
"email" => "harrypotter#mail.com",
)
);
$superheroes_empty = array(
"spiderman" => array(
"name" => "Peter Parker",
"email" => "",
),
"superman" => array(
"name" => "Clark Kent",
"email" => "something",
),
"ironman" => array(
"name" => "Harry Potter",
"email" => "another one",
)
);
Expectation:
$superheroes = array(
"spider-man" => array(
"name" => "Peter Parker",
"email" => "peterparker#mail.com",
),
"super-man" => array(
"name" => "Clark Kent",
"email" => "something",
),
"iron-man" => array(
"name" => "Harry Potter",
"email" => "another one",
)
);
Much appreciation and thank you in advance!
You've added another level to your data, so you can just add another level to your checking as well with a second foreach loop:
foreach ($superheroes_complete as $hero => $info) {
foreach ($info as $key => $value) {
if (empty($superheroes_empty[$hero][$key])) {
$superheroes_empty[$hero][$key] = $value;
}
}
}
First note that your 1D case can be simplified:
foreach ($a2 as $k => $v) {
if (!isset($v)) {
$a2[$k] = $a1[$k];
}
}
Then for the 2D case, assuming the 1st level keys are always the same (or it becomes a quite different question!):
foreach ($superheroes_complete as $main_k => $main_v) {
foreach ($main_v as $k => $v) {
if (!isset($v)) {
$superheroes_empty[$main_k][$k] = $superheroes_complete[$main_k][$k];
}
}
If you only need to take care of the "email" field you can do this :
<?php
$keys = array_keys($superheroes_complete);
foreach ($keys as $k)
{
if ((isset($superheroes_empty[$k]["email"])) &&
(($superheroes_empty[$k]["email"]) == '')) {
$superheroes_empty[$k]["email"] = $superheroes_complete[$k]["email"];
}
}
var_dump($superheroes_empty);
?>
For the generic case where the depth of nesting is unlimited, you could use this recursive function:
function fillEmpty(&$filled, &$empty) { // arguments are by reference
if ($empty === "") {
$empty = $filled;
return;
}
if (!is_array($filled) || !is_array($empty)) return;
foreach ($filled as $key => $value) {
if (isset($empty[$key])) fillEmpty($value, $empty[$key]);
}
}
Example call:
fillEmpty($superheroes_complete, $superheroes_empty);
This modifies the second argument, filling the empty values.
See it run on eval.in
It might be your lucky day, php has some built in functions to compare arrays values and keys. Use array_diff() which can compare two or more arrays and return the difference. You could also use array_intersect() which does the opposite.
If you want to only compare the keys, use array_diff_key()which returns only the key difference or array_intersect_key() which returns the matched keys.
You could also consider a recursive solution. This could work on both the 1D and 2D arrays, or even an array of N dimensions.
I'm aware that recursion should be used with care, as it can be quite resource intensive. This is however a more versatile solution, and keeps the code cleaner with less nested control structures and early returns, which I find better readable. I'll use the native array_walk method because I was taught that it should perform better then a php loop.
So this is what my code would look like:
function array_complement_recursive(&$partial, $complete) {
array_walk($partial, function(&$value, $key) use ($complete) {
// (for safety) complete does not contain matching key: done
if (! array_key_exists($key, $complete)) {
return;
}
// value is array: call recursive
if (is_array($value)) {
array_complement_recursive($value, $complete[$key]);
return;
}
// value is not 'empty': done
// note that null, 0, false will also not match, you may want to make
// this check more specific to match only the empty string
if ($value) {
return;
}
$value = $complete[$key];
});
}
I've set up a little demo so you can see that it works on both your examples. And as I said, it should even work for arrays with more dimensions, or a more irregular structure.
http://phpfiddle.org/main/code/49iz-vrwg
I've added some comments to explain, but feel free to ask if anything is unclear.

Categories