I'm trying to write a recursive array iterator function in which the function will return a result set of all sets that are specified by '$needle'. Where $needle = key
Here is my function:
function recursive($needle, $array, $holder = array()) {
foreach ($array as $key => $value) {
if (gettype($value) == 'array') {
if ($key != $needle) {
recursive($needle, $value);
} elseif ($key == $needle) {
if (!empty($value)) {
array_push($holder, $value);
}
}
}
}
return $holder;
}
But I'm not getting all the results back and instead get a few empty results, if I don't specify the !empty($value), although the input array does not have any empty sets. What am I doing wrong?
You don't need to reinvent the wheel since PHP has standard Recursive Iterator API:
//$array is your multi-dimensional array
$result = [];
$search = 'foo';
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator(
$array,
RecursiveArrayIterator::CHILD_ARRAYS_ONLY
)
);
foreach($iterator as $key=>$value)
{
if($search==$key && $value!=='')
{
$result[] = $value;
}
}
-note, that, since you're searching for value by key - in common case $value will hold entire subsection.
If you want to do this in your own recursive function, here's one:
function recursive($needle, $array, $holder = [])
{
$holder = [];
foreach($array as $key=>$value)
{
if($key===$needle && $value!=='')
{
$holder = array_merge($holder, [$value]);
}
if(is_array($value))
{
$holder = array_merge($holder, recursive($needle, $value, $holder));
}
}
return $holder;
}
More fine-grained control is perhaps possible with true (tm) recursive array traversal via RecursiveIterator interface and some key filters and array conversion functions:
$needle = '0';
$array = [[1]];
$it = new KeyFilter(
new RecursiveIteratorIterator(
new MyRecursiveArrayIterator($array)
, RecursiveIteratorIterator::SELF_FIRST
)
, $needle
);
$result = iterator_to_array($it, FALSE);
var_dump($result);
Providing an exemplary result as:
array(2) {
[0] =>
array(1) {
[0] =>
int(1)
}
[1] =>
int(1)
}
Full code example (Demo):
<?php
/**
* #link http://stackoverflow.com/q/19709410/367456
*/
Class MyRecursiveArrayIterator extends ArrayIterator implements RecursiveIterator
{
public function hasChildren()
{
$current = $this->current();
return is_array($current) && count($current);
}
public function getChildren()
{
return new self($this->current());
}
}
class KeyFilter extends RegexIterator
{
public function __construct(Iterator $iterator, $key)
{
parent::__construct(
$iterator, '/' . preg_quote($key) . '/', NULL, RegexIterator::USE_KEY
);
}
}
$needle = '0';
$array = [[1]];
$it = new KeyFilter(
new RecursiveIteratorIterator(
new MyRecursiveArrayIterator($array)
, RecursiveIteratorIterator::SELF_FIRST
)
, $needle
);
$result = iterator_to_array($it, FALSE);
var_dump($result);
A tiny modification of your construction:
$holder = recursive($needle, $value, $holder);
Ay?
Related
I have the following
Multidimensional array.
What I'm trying to do is to search for an IDITEM value and if it's found, return the value of the "PRECO" key.
I'm using the following function to check if the value exists and it works fine, but I can't find a way to get the "PRECO" value of the found IDITEM.
Function:
function search_array($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && search_array($needle, $element))
return true;
}
return false;
}
Anyone can help me with that?
You can change the first if statement to return it instead of returning a boolean :
function search_array($needle, $haystack) {
if(in_array($needle, $haystack) && array_key_exists('PRECO', $haystack)) {
return $haystack['PRECO'];
}
foreach($haystack as $element) {
if(is_array($element))
{
$result = search_array($needle, $element);
if($result !== false)
return $result;
}
}
return false;
}
The easiest idea I can remember is converting that boolean search_array into a path creator, where it will return the path for the item, or false if it isn't found.
function get_array_path_to_needle($needle, array $haystack)
{
if(in_array($needle, $haystack))
{
return true;
}
foreach($haystack as $key => $element)
{
if(is_array($element) && ($path = get_array_path_to_needle($needle, $element)) !== false)
{
return $path === true ? $key : $key . '.' . $path;
}
}
return false;
}
Then, since you already have the path, then rerun the array to fetch the item
function get_array_value_from_path(array $path, array $haystack)
{
$current = $haystack;
foreach($path as $key)
{
if(is_array($current) && array_key_exists($key, $current))
{
$current = $current[$key];
}
else
{
return false;
}
}
return $current;
}
This wont get you the PRECO, but it will return the item (array) where id found the value you searched for.
So a simple usage would be:
$path = get_array_path_to_needle('000000000000001650', $data);
$item = get_array_value_from_path(explode('.', $path), $data);
// here you have full array for that item found
print_r($item);
// here you have your price
print_r($item['PRECO']);
Use a static variable to remember the status between multiple function calls, and also to store the desired PRECO value. It makes the function remember the value of the given variable ($needle_value in this example) between multiple calls.
So your search_array() function should be like this:
function search_array($needle, $haystack){
static $needle_value = null;
if($needle_value != null){
return $needle_value;
}
foreach($haystack as $key => $value){
if(is_array($value)){
search_array($needle, $value);
}else if($needle == $value){
$needle_value = $haystack['PRECO'];
break;
}
}
return $needle_value;
}
This function will finally return $needle_value, which is your desired PRECO value from the haystack.
The simplest way is to use a foreach loop twice. Check for the key and store the result into an array for later use.
Based on your array, the below
$search = '000000000000001650';
foreach($array as $element){
foreach ($element['ITEM'] as $item){
if (isset($item['IDITEM']) and $item['IDITEM'] == $search){
$results[] = $item['PRECO'];
}
}
}
print_r($results);
Will output
Array
(
[0] => 375
)
Here is the simple example with array:
// your array with two indexes
$yourArr = array(
0=>array(
'IDDEPARTAMENTO'=>'0000000001',
'DESCRDEPT'=>'Área',
'ITEM'=>
array(
array(
'SETID'=>'RX',
'IDITEM'=>'000000000000001367',
'DESCRITEM'=>'PISTA TESTE DRIV',
'PRECO'=>'1338.78'),
array(
'SETID'=>'RX',
'IDITEM'=>'000000000000001925',
'DESCRITEM'=>'PISTA TESTE DRIV2',
'PRECO'=>'916'),
)
),
1=>array(
'IDDEPARTAMENTO'=>'0000000010',
'DESCRDEPT'=>'Merch',
'ITEM'=>
array(
array(
'SETID'=>'RX',
'IDITEM'=>'000000000000002036',
'DESCRITEM'=>'PISTA TESTE DRIV23',
'PRECO'=>'200.78'),
array(
'SETID'=>'RX',
'IDITEM'=>'000000000000001608',
'DESCRITEM'=>'PISTA CRACHÁ DRIV4',
'PRECO'=>'44341'),
)
));
// solution
$newArr = array();
foreach ($yourArr as $value) {
foreach ($value as $key => $innerVal) {
if($key == 'ITEM'){
foreach ($innerVal as $key_inner => $keyinner) {
if(!empty($keyinner['IDITEM'])){
$newArr[$keyinner['IDITEM']] = $keyinner['PRECO'];
}
}
}
}
}
echo "<pre>";
print_r($newArr);
Result values with IDITEM:
Array
(
[000000000000001367] => 1338.78
[000000000000001925] => 916
[000000000000002036] => 200.78
[000000000000001608] => 44341
)
For a project we have created a recursive direcory iterator inside a class
the class is as follows
class Helpers {
public static function fs_to_array($directory){
$iritator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory), \RecursiveIteratorIterator::CHILD_FIRST);
$array_result = array();
foreach ($iritator as $splFileInfo) {
$fn = $splFileInfo->getFilename();
if ($splFileInfo->isDir()){
if ($fn == '..' || $fn == '.' ){
continue;
}
$rec_path = array($fn => array());
}else{
continue;
}
for ($depth = $iritator->getDepth() - 1; $depth >= 0; $depth--) {
$rec_path = array($iritator->getSubIterator($depth)->current()->getFilename() => $rec_path);
}
$array_result = array_merge_recursive($array_result, $rec_path);
}
return $array_result;
}
}
it takes a directory as an argument and returns the dir structure in the following form
Array
(
[dir3] => Array
(
[dir_in_dir3] => Array
(
)
)
[dir1] => Array
(
[dir_in_dir1] => Array
(
)
)
[dir2] => Array
(
)
)
I would like these to be allphavetically sorted.
How Can i make this with the iterator?
Thanks everybody in advance!
An iterator can't be sorted directly and the directory iterators also have not support for sorting of the underlying data. But you can convert the RecursiveIteratorIterator to an array with iterator_to_array() and then sort the array with usort() and a custom callback function using getPathName() on the elements. You could also use a CallbackFilterIterator() before the conversion to reduce the size of your array.
EDIT example:
$directory = '...';
$it = new CallbackFilterIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST
), function ($entry) {
// filtering unwanted elements to keep array small
$fn = $entry->getFilename();
if (!$entry->isDir()) {
return false;
} else if ($fn == '..' || $fn == '.') {
return false;
} else {
return true;
}
}
);
$array = iterator_to_array($it);
// sorting entries
uasort($array, function ($a, $b) {
return strcmp($a->getPathname(), $b->getPathname());
});
// do whatever you want - can be uses just like the RecursiveIteratorIterator before
foreach ($array as $v) {
var_dump($v->getPathname());
}
I have a variable which may be an object or an array. If it is an array, it will be of objects. Each object may have a parameter which is another object or an array of objects. All arrays may be n levels deep.
All objects have a parameter 'isAccessible'. The value of this parameter may be true or false.
If it is false, that object must be removed from the structure.
I have tried array_filter and because I was not able to recursively filter as described, I have written a home rolled recursion function.
However I have not been able to get it to work. Here is my code:
public static function jsonGateway($json) {
$object = json_decode($json);
$newJSON = '';
// $object may be a stdClass Object or it may be an array of stdClass Objects
// An objects parameters may be a string, integer, or an array of stdClass Objects.
// This function must recurse arbitrarily deep
// Any object where isAccessible = 0 must be purged (numeric string or int?)
if (is_object($object)) {
if ($object->isAccessible == 1 || $object->isAccessible == '1' || $object->isAccessible == true) {
$newJSON = $json;
}
} else if (is_array($object)) {
$newJSON = json_encode(self::filterRecursive($object));
}
echo $newJSON;
}
public static function filterRecursive(Array $source) {
$result = array();
foreach ($source as $key => $value) {
if (is_object($value)) {
$object = $value; // Just a comprehension aid
if ($object->isAccessible == 1 || $object->isAccessible == '1' || $object->isAccessible == true) {
// Keep this object
// This objec though, may have a parameter which is an array
// If so, we need to recurse
$objectVars = get_object_vars($object);
if (count($objectVars) > 0) {
foreach ($objectVars as $objectParameter => $objectValue) {
if (is_object($objectValue)) {
if ($object->isAccessible == 1 || $object->isAccessible == '1' || $object->isAccessible == true) {
$object[$objectParameter] = $objectValue;
}
} else if (is_array($objectValue)) {
$object[$objectParameter] = self::filterRecursive($objectValue); // Line 177
}
}
}
$result[$key] = $object;
} else {
// don't need this block
error_log("deleting: " . print_r($object,true));
}
}
if (is_array($value)) {
$array = $value; // Just a comprehension aid
$result[$key] = self::FilterRecursive($array);
continue;
}
}
return $result;
}
Not only am I not succeeding at filtering anything, I am getting the following error:
PHP Fatal error: Cannot use object of type stdClass as array in /home1/lowens/public_html/dev/classes/Lifestyle/RBAC.php on line 177
Can you help?
Solution:
/**
* #param array $array
* #return array
*/
public static function filterArray(Array $array) {
$result = array();
foreach ($array as $key => $value) {
if (is_object($value)) {
$newObject = self::filterObject($value);
if (count(get_object_vars($newObject)) > 0) {
$result[$key] = $newObject;
}
} else if (is_array($value)) {
$newArray = self::filterArray($value);
if (count($newArray) > 0) {
$result[$key] = $newArray;
}
} else {
$result[$key] = $value;
}
}
return $result;
}
/**
* #param \stdClass $object
* #return \stdClass
*/
public static function filterObject(\stdClass $object) {
$newObject = new \stdClass();
if ($object->isAccessible == 1) {
$objectVars = get_object_vars($object);
foreach ($objectVars as $objectParameter => $objectValue) {
if (is_object($objectValue)) {
$newObject->$objectParameter = self::filterObject($objectValue);
} else if (is_array($objectValue)) {
$newObject->$objectParameter = self::filterArray($objectValue);
} else {
$newObject->$objectParameter = $objectValue;
}
}
}
return $newObject;
}
private function find($needle, $haystack) {
foreach ($haystack as $name => $file) {
if ($needle == $name) {
return $file;
} else if(is_array($file)) { //is folder
return $this->find($needle, $file); //file is the new haystack
}
}
return "did not find";
}
Hey, this method searches for a specific key in an associative array and returns the value associated with it. There's some problem with the recursion. Any clue?
Maybe it's overkill, but it's funny to use RecursiveIterators :)
UPDATE: Maybe it was overkill with old versions of PHP, but with >=5.6 (specially with 7.0) I would totally use this without doubt.
function recursiveFind(array $haystack, $needle)
{
$iterator = new RecursiveArrayIterator($haystack);
$recursive = new RecursiveIteratorIterator(
$iterator,
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($recursive as $key => $value) {
if ($key === $needle) {
return $value;
}
}
}
UPDATE: Also, as of PHP 5.6, with generators you can easily iterate over all elements which pass the filter, not only the first one:
function recursiveFind(array $haystack, $needle)
{
$iterator = new RecursiveArrayIterator($haystack);
$recursive = new RecursiveIteratorIterator(
$iterator,
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($recursive as $key => $value) {
if ($key === $needle) {
yield $value;
}
}
}
// Usage
foreach (recursiveFind($haystack, $needle) as $value) {
// Use `$value` here
}
function array_search_key( $needle_key, $array ) {
foreach($array AS $key=>$value){
if($key == $needle_key) return $value;
if(is_array($value)){
if( ($result = array_search_key($needle_key,$value)) !== false)
return $result;
}
}
return false;
}
this will work !
you need to stop the recursive deep search, by return false and then check it in the function.
you can find more examples of functions (like using RecursiveArrayIterator and more) in this link :
http://php.net/manual/en/function.array-search.php
The answer provided by xPheRe was extremely helpful, but didn't quite solve the problem in my implementation. There are multiple nested associative arrays in our data structure, and there may be multiple occurrences of any given key.
In order to suit our purposes, I needed to implement a holder array that was updated while traversing the entire structure, instead of returning on the first match. The real work was provided by another poster, but I wanted to say thanks and share the final step that I had to cover.
public function recursiveFind(array $array, $needle)
{
$iterator = new RecursiveArrayIterator($array);
$recursive = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST);
$aHitList = array();
foreach ($recursive as $key => $value) {
if ($key === $needle) {
array_push($aHitList, $value);
}
}
return $aHitList;
}
try this:
array_walk_recursive(
$arrayToFindKey,
function($value, $key, $matchingKey){
return (strcasecmp($key, $matchingKey) == 0)? true : false;
}
, 'matchingKeyValue'
);
The best solution above misses the case if the key is repeated and only returns the first value, here I get all the values in an array instead:
function recursiveFind(array $array, $needle) {
$iterator = new RecursiveArrayIterator($array);
$recursive = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST);
$return = [];
foreach ($recursive as $key => $value) {
if ($key === $needle) {
$return[] = $value;
}
}
return $return;
}
I just been through a similar issue and here's what worked for me:
function searchArrayByKey($haystack, $needle, $i = 0) {
$result = array();
foreach($haystack as $key => $value) {
if (is_array($value)) {
$nextKey = searchArrayByKey($value, $needle);
if ($nextKey) {
return $nextKey;
}
}
if (is_array($value) && array_key_exists($needle, $value)) {
$result[$i++] = $value[$needle];
}
}
if (empty($result)) {
return false;
} else {
return $result;
}
}
This is going to return an array containing the value of all the matching keys it found in the multidimensional array. I tested this with arrays dinamically generated by an e-mail API. In the case of multiple matches, you just need to create a simple foreach loop to sort the array however you want.
I noticed the main mistake I was making was using if-ifelse conditions when I should be using if-if conditions. Any questions or criticism are very welcome, cheers!
I recently came across the same issue, when dealing with Yii2 query object.
The reason your function didn't work is that the return action doesn't work here. Just pass a reference parameter to store the value, and do whatever you want afterwards.
As you can see, this is a simple PHP function doesn't rely on any library. So I think its worth to mention with all the answer listed above.
function array_search_by_key_recursive($needle, array $haystack, &$return)
{
foreach ($haystack as $k => $v) {
if (is_array($v)) {
array_search_by_key_recursive($needle, $v, $return);
} else {
if($k === $needle){
$return = $v;
}
}
}
}
array_search_by_key_recursive($needle, array $haystack, $return);
print_r($return);
Is it possible in PHP to extract values from an array with a particular key path and return an array of those values? I'll explain with an example:
$user =
array (
array(
'id' => 1,
'email' =>'asd#example.com',
'project' => array ('project_id' => 222, 'project_name' => 'design')
),
array(
'id' => 2,
'email' =>'asd2#example.com',
'project' => array ('project_id' => 333, 'project_name' => 'design')
)
);
/** I have to write a function something like: */
$projectIds = extractValuesWithKey($user, array('project', 'project_id'));
print_r($projectIds);
Output:
Array(
[0] => 222,
[1] => 333
)
I would have gone for a different approach (not that there's anything wrong with the array-function-based answers) by using a recursive iterator to flatten the array which makes the key-path comparison fairly simple.
function extractValuesWithKey($array, $keys) {
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
$keys_count = count($keys);
// No point going deeper than we have to!
$iterator->setMaxDepth($keys_count);
$result = array();
foreach ($iterator as $value) {
// Skip any level that can never match our keys
if ($iterator->getDepth() !== $keys_count) {
continue;
}
// Build key path to current item for comparison
$key_path = array();
for ($depth = 1; $depth <= $keys_count; $depth++) {
$key_path[] = $iterator->getSubIterator($depth)->key();
}
// If key paths match, add to results
if ($key_path === $keys) {
$result[] = $value;
}
}
return $result;
}
To make the whole thing more useful, you could even wrap the code into a custom FilterIterator rather than a basic function, but I guess that's probably a different question entirely.
Well, that's easier than you think.
function extractValuesWithKey($array, $parts) {
$return = array();
$rawParts = $parts;
foreach ($array as $value) {
$tmp = $value;
$found = true;
foreach ($parts as $key) {
if (!is_array($tmp) || !isset($tmp[$key])) {
$found = false;
continue;
} else {
$tmp = $tmp[$key];
}
}
if ($found) {
$return[] = $tmp;
}
}
return $return;
}
If the 'key path' isn't dynamic, you can do a one-liner with array_map:
$projectIds = array_map(function($arr) { return $arr['project']['project_id']; }, $user);
Alternatively, for dynamic paths:
function extractValuesWithKey($users, $path) {
return array_map(function($array) use ($path) {
array_walk($path, function($key) use (&$array) { $array = $array[$key]; });
return $array;
}, $users);
}
The closures/anonymous functions only work with PHP 5.3+, and I've no idea how this would compare performance-wise to a double foreach loop. Note also that there's no error checking to ensure that the path exists.
I also used a similiar function in one of my projects, maybe you find this useful:
function extractValuesWithKey($data, $path) {
if(!count($path)) return false;
$currentPathKey = $path[0];
if(isset($data[$currentPathKey])) {
$value = $data[$currentPathKey];
return is_array($value) ? extractValuesWithKey($value, array_slice($path, 1)) : $value;
}
else {
$tmp = array();
foreach($data as $key => $value) {
if(is_array($value)) $tmp[] = extractValuesWithKey($value, $path);
}
return $tmp;
}
}