Using PHP array iterator to edit array - php

I have a multidimensional array with variable number of levels of data. That is, I can't be sure how many iterations it will take to reach the Goal level, which is an array. Something like this:
[
'key1' = 'value1',
'key2' = 'value2',
'key3' = [
'key4' => [
'key5' => 'value3'
],
'key6' => 'value4'
],
'key7' => [
'Goal' => [
'value5',
'value6',
'value7'
]
],
'key8' => 'value8'],
'key9' => [
'Goal' => [
'value9',
'Foo',
'value10'
]
]
]
I've tried both array_walk_recursive and ArrayIterator, but neither seems to quite get me where I need to be.
I need to go through each element of the array, and if the key is Goal examine the value (eg. the array that Goal holds) and see if that array contains the value Foo.
If Foo is found in the array, I need to add a new value (in addition to Foo-- so call it Bar) to the array and then continue, since there may be more Goals in the parent array.
Is there a way to "stop" the iterator when we find a Goal, without iterating further, and then do the array_search operation?
Edit: Trying somethings along these lines--
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach($iterator as $key => $value)
{
if($key == 'Goal')
{
if (is_array($value)) {
if(array_search('Foo', $value)) {
$value[] = 'Bar';
}
}
}
}

Not entirely sure if this is what you want to achieve but here's a solution which adds Bar to arrays nested in the Goal key:
$array = [
'key1' => 'value1',
'key2' => 'value2',
'key3' => [
'key4' => [
'key5' => 'value3',
],
'key6' => 'value4',
],
'key7' => [
'Goal' => [
'value5',
'value6',
'value7',
],
],
'key8' => 'value8',
'key9' => [
'Goal' => [
'value9',
'Foo',
'value10',
],
],
];
function iterate(array $data, $goal = false)
{
foreach ($data as $key => &$value) {
if (is_array($value)) {
$value = iterate($value, $key === 'Goal');
} elseif (is_string($value)) {
if (($value === 'Foo') && $goal) {
$data[] = 'Bar';
return $data;
}
}
}
return $data;
}
var_export(iterate($array));
The code generates the following output:
array (
'key1' => 'value1',
'key2' => 'value2',
'key3' =>
array (
'key4' =>
array (
'key5' => 'value3',
),
'key6' => 'value4',
),
'key7' =>
array (
'Goal' =>
array (
0 => 'value5',
1 => 'value6',
2 => 'value7',
),
),
'key8' => 'value8',
'key9' =>
array (
'Goal' =>
array (
0 => 'value9',
1 => 'Foo',
2 => 'value10',
3 => 'Bar',
),
),
)

Iterators in my opinion would be weird to use in these kind of arrays... I would do it with something like this:
/*
Usage:
$wasFound = checkArray( "Goal", "Foo", $the_array);
if ( $wasFound ) echo "Key and Value pair found in the array!";
else { /* not found */ }
*/
function checkArray( $key_to_find, $value_to_find, $my_var, $last_key = NULL ) {
$found = FALSE;
if ( $last_key == $key_to_find && $my_var == $value_to_find )
return TRUE;
if ( $my_var == NULL )
return FALSE;
if ( is_array( $my_var ) ) {
foreach ( $my_var AS $key => $value )
{
if ( $found ) {
/* Do something else if needed when found */
break;
}
$found = checkArray( $key_to_find, $value_to_find, $value, $key );
}
}
return $found;
}

I agree that recursion is the way to do this. The problem with using array_walk_recursive is that you will not be able to see the Goal key, because as per the PHP documentation,
Any key that holds an array will not be passed to the function.
I am not really sure whether using a RecursiveIteratorIterator would be better than just writing a recursive function for it. A function for something like this should be fairly simple.
function addBar(&$array) {
foreach ($array as $key => &$value) {
if ($key === 'Goal') {
if(array_search('Foo', $value)) {
$value[] = 'Bar';
}
} elseif (is_array($value)) {
addBar($value);
}
}
}
This function takes a reference to your array, so it will update your actual array rather than creating a copy with Bar added to each Goal.

Related

get an array element where another element is known?

Array
(
[0] => Array
(
[what] => b4
[map] => 74,76,77,83
)
[1] => Array
(
[what] => b2
[map] => 53,82
)
[2] => Array
(
[what] => b1
[map] => 36
)
)
abc('b4');
function abc($what){
$map = // element `map` where `what` = $what;
}
So I need to get map where what is equal to $what;
For example - if $what is b4 result should be 74,76,77,83; and so on.
How can I do this?
If you are going to access the data on a regular basis and the what is unique, then use array_column() with the third parameter as the column to use as the key. Then your array is easily access with what and no loops are harmed in this answer...
$array = Array
(
Array
(
"what" => "b4",
"map" => "74,76,77,83"
),
Array
(
"what" => "b2",
"map" => "53,82"
),
Array
(
"what" => "b1",
"map" => "36"
)
);
$array = array_column($array, null, "what");
echo $array['b4']['map'];
gives...
74,76,77,83
With array_search() and array_column() you can get the matching $map in one line:
<?php
$array = Array
(
Array
(
"what" => "b4",
"map" => "74,76,77,83"
),
Array
(
"what" => "b2",
"map" => "53,82"
),
Array
(
"what" => "b1",
"map" => "36"
)
);
function abc($array, $what) {
return $array[array_search($what, array_column($array, 'what'))]['map'];
}
echo abc($array, "b4");
The function de-constructed and explained:
function abc($array /* the complete input array */, $what /* the search string */) {
// get the key of the sub-array that has $what in column 'what':
$key = array_search($what, array_column($array, 'what'));
// use that key to get 'map' on index $key
return $array[$key]['map'];
}
A working fiddle can be found here: https://3v4l.org/0NpcX
I think "walking" through an array is easy to read and understand:
<?php
$map = [
[
'what' => "b4",
'map' => "74,76,77,83"
],
[
'what' => "b2",
'map' => "53,82"
],
[
'what' => "b1",
'map' => "36"
]
];
function lookupWhatInMap(&$map, $what) {
array_walk($map, function($entry, $key) use ($what) {
if ($entry['what'] == $what) {
print_r($entry['map']);
}
});
}
lookupWhatInMap($map, "b4");
All you have to do is loop through your map and compare values.
function abc($what){
$map = [...];
foreach($map as $item) {
if (isset($item[$what]) ) {
return $item["map"];
}
}
return false;
}
If you just want 1 value from the array, you could use a foreach and a return statement where there is a match:
$a = [
[
"what" => "b4",
"map" => "74,76,77,83"
],
[
"what" => "b2",
"map" => "53,82"
],
[
"what" => "b1",
"map" => "36"
]
];
function abc($what, $arrays)
{
foreach ($arrays as $array) {
if ($array['what'] === $what) {
return $array['map'];
}
}
return false;
}
echo(abc('b4', $a)); // 74,76,77,83
Demo
https://ideone.com/V9WNNx
$arr[] = [
'what' => 'b4',
'map' => '74,76,77,83'
];
$arr[] = [
'what' => 'b2',
'map' => '53,82'
];
$arr[] = [
'what' => 'b1',
'map' => '36'
];
echo abc('b4', $arr);
function abc($what, $arr){
$map = null;
$idx = array_search($what, array_column($arr, 'what'));
if ($idx !== false) {
$map = $arr[$idx]['map'];
}
return $map;
}

Check if a particular array key is sequential in an array with mixed sequential and associative entries

Let's say I have an array like:
$array = [
'value1',
71,
'stringKey' => 'value2',
3 => 'value3',
4 => 'value4',
64 => 'value5',
'value6',
];
I want to loop through the entries and do a different thing depending on whether the entry has a key "manually set" (e.g., 64 => 'value5'), or it's just a value with a "sequential" key (e.g., 'value1' - which is actually 0 => 'value1').
foreach ($array as $key => $value) {
if (/* $key has not been "manually set" (i.e., is "sequential") */) {
$result[$value] = 'default';
} else {
$result[$key] = $value;
}
}
So my resulting array would be:
[
'value1' => 'default',
71 => 'default',
'stringKey' => 'value2',
3 => 'value3',
4 => 'value4',
64 => 'value5',
'value6' => 'default',
]
I have been trying with array_keys(), checking if is_numeric($key), but nothing works for all the entries above.
I'm starting to think this is actually impossible...
You could do something like this:
$i = 0;
foreach($array as $key => $value) {
if ($key == $i) {
$result[$value] = 'default';
} else {
$result[$key] = $value;
}
if (is_integer($key) && $key >= $i) $i = (int)$key + 1;
}
However, it will treat 4 => 'value4' differently from what you had indicated, because the input array would be exactly the same if you would have put just 'value4'. There is no way to determine from the array whether you had explicitly mentioned the key 4 or omitted the key.
So the above code treats this key/value pair in the same way that it treats the last key/value pair.
The output is:
array (
'value1' => 'default',
71 => 'default',
'stringKey' => 'value2',
3 => 'value3',
'value4' => 'default',
64 => 'value5',
'value6' => 'default',
)

PHP: Link address of multiple array keys into another bigger array

I want to combine 3 small arrays that have unique keys between them into 1 big array but when I modify a value in the big array I want it also to reflect in the corresponding small array.
For example I have these 3 small arrays:
$arr1 = ['key1' => 'data1', 'key2' => 'data2'];
$arr2 = ['key3' => 'data3', 'key4' => 'data4', 'key5' => 'data5'];
$arr3 = ['key6' => 'data6'];
I want to have a $bigArray that has each key's address linked/mapped to each value of the small arrays. So if I do something like:
$bigArray['key4'] = 'something else';
then it would modify $arr2['key4'] to the same value ('something else').
If I try something like:
$bigArray = [&$arr1, &$arr2, &$arr3];
It has the unfortunate effect of making a multidimensional array with the keys to the values mapped.
Two ways i found
<?php
error_reporting(E_ALL);
$arr1 = ['key1' => 'data1', 'key2' => 'data2'];
$arr2 = ['key3' => 'data3', 'key4' => 'data4', 'key5' => 'data5'];
$arr3 = ['key6' => 'data6'];
$big = [];
if (true) {
foreach (['arr1', 'arr2', 'arr3'] as $v) {
foreach (array_keys($$v) as $k) {
$big[$k] = &${$v}[$k];
}
}
}
else {
foreach ([&$arr1, &$arr2, &$arr3] as &$v) {
foreach (array_keys($v) as $k) {
$big[$k] = &$v[$k];
}
}
}
$big['key1'] = 'data1mod';
print_r($big);
print_r($arr1);
3rd way with function
$big = [];
$bindToBig = function (&$a) use (&$big) {
foreach (array_keys($a) as $k) {
$big[$k] = &$a[$k];
}
};
$bindToBig($arr1);
$bindToBig($arr2);
$bindToBig($arr3);
You can't bind data that way, but you can link them in the same object:
class ArrayLink {
public $bigArray;
public $linkedChildrenArray;
protected $childrenArray;
public function __construct( $childrenArray ) {
$this->childrenArray = $childrenArray;
}
public function changeValueForKey( $arrKey, $arrValue ) {
foreach ( $this->childrenArray as $key => $value ) {
foreach ( $value as $subKey => $subValue ) {
if ( $arrKey == $subKey ) {
$this->bigArray[ $subKey ] = $arrValue;
$this->childrenArray[ $key ][ $subKey ] = $arrValue;
}
}
}
$this->linkedChildrenArray = (object) $this->childrenArray;
}
}
As you can see, the $arr2 now need to be access from $arrayLink object:
$arr1 = [ 'key1' => 'data1', 'key2' => 'data2' ];
$arr2 = [ 'key3' => 'data3', 'key4' => 'data4', 'key5' => 'data5' ];
$arr3 = [ 'key6' => 'data6' ];
$arrayLink = new ArrayLink( array( 'arr1' => $arr1, 'arr2' => $arr2, 'arr3' => $arr3 ) );
$arrayLink->changeValueForKey( 'key3', 'new value for key 3' );
echo $arrayLink->bigArray['key3']; //new value for key 3
echo $arrayLink->linkedChildrenArray->arr2['key3']; //new value for key 3

Select what fields to display in an array of arrays

I have an array of data like this:
dataArray = [
'index1' => 'value1',
'index2' => 'value2',
'index3' => '[
'index1' => 'value1',
'index2' => [
'index1' => 'value1',
],
'index3' => 'value3',
]
]
The dimension of the array is unknown.
I have another array that defines what values from dataArray I need to print:
maskArray = ['index2', 'index3' => [ 'index1', 'index2' => [ 'index1' ] ]]
I need to output an array that match the fields from maskArray and dataArray, so in this case the output should be:
result = [
'index2' => 'value2',
'index3' => [
'index1' => 'value1'
'index2' => [
'index1' => 'value1'
]
]
]
In this scenario the maskArray is 3 levels deep, but it could be n levels deep.
If you don't know how deep the array could be nested, a recursive solution is probably appropriate. Maybe there are better ones, but I think this is quite short and readable:
function recursiveFilter($data, $whiteList)
{
$results = [];
foreach ($data as $key => $value) {
// if the current key is on the whitelist, either as value
// or as a key (it's a key if it's nested, otherwise a value)
if(in_array($key, $whiteList) or array_key_exists($key, $whiteList))
{
// if the current value is an array and the whitelist
// has filters for that array, then call this function
// again with just the relevant portion of data and filters,
// otherwise just grab the whole value as it is.
$results[$key] = is_array($value) && isset($whiteList[$key])
? recursiveFilter($value, $whiteList[$key])
: $value;
}
}
return $results;
}
Here's a working example: http://codepad.viper-7.com/16JIJf

PHP Question: how to array_intersect_assoc() recursively

Let's say I want to do this:
$a = array_intersect_assoc(
array(
'key1' => array(
'key2' => 'value2'
),
'key3' => 'value3',
'key4' => 'value4'
),
array(
'key1' => array(
'key2' => 'some value not in the first parameter'
),
'key3' => 'another value'
)
);
var_dump( $a );
The printed result is:
array
'key1' =>
array
'key2' => string 'value2' (length=6)
It's clear that values associated with 'key2' in both arrays are not the same, however array_intersect_assoc() still return 'key2' => 'value2' as the intersected value.
Is this the expected behavior of array_intersect_assoc()?
Thanks!
Yes, it's the expected behavior, because the comparison is done using string representations, and the function does not recurse down nested arrays. From the manual:
The two values from the key => value pairs are considered equal only if (string) $elem1 === (string) $elem2 . In other words a strict type check is executed so the string representation must be the same.
If you tried to intersect with an array with 'key1' => 'Array', you'd get the same result because the string representation of an array is always 'Array'.
One of the user-contributed notes, by nleippe, contains a recursive implementation that looks promising (I modified the third line to do string comparison on any non-array values):
function array_intersect_assoc_recursive(&$arr1, &$arr2) {
if (!is_array($arr1) || !is_array($arr2)) {
// return $arr1 == $arr2; // Original line
return (string) $arr1 == (string) $arr2;
}
$commonkeys = array_intersect(array_keys($arr1), array_keys($arr2));
$ret = array();
foreach ($commonkeys as $key) {
$ret[$key] =& array_intersect_assoc_recursive($arr1[$key], $arr2[$key]);
}
return $ret;
}
function array_key_match_recursive(array $main, array $other, $i = 0, &$result = []) {
foreach($main as $key => $value) {
$k = sprintf('%s%s', str_repeat('=', $i), $key);
if (!isset($other[$key])) {
$result[$k][] = 'not key';
}
if (!is_array($value) && empty($other[$key])) {
$result[$k][] = 'value empty';
}
if (is_array($value) && isset($other[$key])) {
array_key_match_recursive($value, $other[$key], ++$i, $result);
}
}
//return (bool) !$result;
return $result;
}
A function that does what you need:
/**
* Get array intersect assoc recursive.
*
* #param mixed $value1
* #param mixed $value2
*
* #return array|bool
*/
function getArrayIntersectAssocRecursive(&$value1, &$value2)
{
if (!is_array($value1) || !is_array($value1)) {
return $value1 === $value2;
}
$intersectKeys = array_intersect(array_keys($value1), array_keys($value2));
$intersectValues = [];
foreach ($intersectKeys as $key) {
if (getArrayIntersectAssocRecursive($value1[$key], $value2[$key])) {
$intersectValues[$key] = $value1[$key];
}
}
return $intersectValues;
}
My version based on #BoltClock version:
function array_intersect_assoc_recursive($arr1, $arr2) {
if (!is_array($arr1) || !is_array($arr2)) {
return $arr1;
}
$commonkeys = array_keys($arr1);
if (!array_key_exists('$', $arr2)){
$commonkeys = array_intersect(array_keys($arr1), array_keys($arr2));
}
$ret = array();
foreach ($commonkeys as $key) {
$ret[$key] = array_intersect_assoc_recursive($arr1[$key], array_key_exists('$', $arr2) ? $arr2['$'] : $arr2[$key]);
}
return $ret;
}
I use this code to filter data inside complex array
example:
$filter = [
'channels' => [
'$' => [
'id' => 1,
'type' => 1,
'count' => 1
]
],
'user' => [
'id' => 1,
'type' => 1
]
];
$data = [
'user' => [
'id' => '1234',
'type' => true,
'counter' => 14,
],
'filteredField' => 4,
'channels' => [
['id' => '567', 'type' => 'other', 'count' => 1345, 'filteredField' => 5,],
['id' => '890', 'type' => 'other', 'count' => 5456, 'filteredField' => 7,],
],
];
print_r(array_intersect_assoc_recursive($data, $filter));
test online:
https://onlinephp.io/c/3be04

Categories